Repository: plotters-rs/plotters (language: rust)

Issue:
Support linspace and interval wrapper for datetime and float
Noticed that i cant draw Histogram with timeseries.
```
error[E0277]: the trait bound `plotters::coord::datetime::RangedDateTime<chrono::offset::utc::Utc>: plotters::coord::ranged::DescreteRanged` is not satisfied
   --> src\common\chart\mod.rs:189:24
    |
189 |         let buy_vols = Histogram::vertical(&vols_chart)
    |                        ^^^^^^^^^^^^^^^^^^^ the trait `plotters::coord::ranged::DescreteRanged` is not implemented for `plotters::coord::datetime::RangedDateTime<chrono::offset::utc::Utc>`
```



Discussion / hints:
This is because we currently don't have the time-slice type. Because it's not well-defined for a histogram on `DateTime` type, since we don't know the bucket size. (For example, one bar per 5 minutes, etc...)

The type slice coordinate should be added. But before that happens, you actually can draw a histogram without using the `Histogram` help. Since all the series types are just defined for convenience.

As you know the series is just a iterator of element, Thus for your case, you can actually have things like that:

```rust
    let histogram_values: Vec<(DateTime, f32)>;
    chart.draw_series(histogram_values.iter().map(|(time, value)| {
        Rectangle::new(
            [(time, value), (time + Duration::minutes(5), value)],
            RED.filled(),
        )
    }))?;

``` 

This should just work. But you are right, the time slice should be supported. 

In addition, `Date` is automatically sliced, because it has minimal resulution which is 1 day.

For the same reason, the float number can't use the `Histogram` type, but integer can.

Fix patch:
diff --git a/examples/chart.rs b/examples/chart.rs
index 15fb23d0..45d045e3 100644
--- a/examples/chart.rs
+++ b/examples/chart.rs
@@ -10,11 +10,13 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
 
     let (upper, lower) = root_area.split_vertically(512);
 
+    let x_axis = (-3.4f32..3.4).step(0.1);
+
     let mut cc = ChartBuilder::on(&upper)
         .margin(5)
         .set_all_label_area_size(50)
         .caption("Sine and Cosine", ("sans-serif", 40).into_font())
-        .build_ranged(-3.4f32..3.4f32, -1.2f32..1.2f32)?;
+        .build_ranged(x_axis.clone(), -1.2f32..1.2f32)?;
 
     cc.configure_mesh()
         .x_labels(20)
@@ -24,20 +26,12 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
         .y_label_formatter(&|v| format!("{:.1}", v))
         .draw()?;
 
-    cc.draw_series(LineSeries::new(
-        (0..12).map(|x| ((x - 6) as f32 / 2.0, ((x - 6) as f32 / 2.0).sin())),
-        &RED,
-    ))?
-    .label("Sine")
-    .legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], &RED));
+    cc.draw_series(LineSeries::new(x_axis.values().map(|x| (x, x.sin())), &RED))?
+        .label("Sine")
+        .legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], &RED));
 
     cc.draw_series(LineSeries::new(
-        (0..6800).map(|x| {
-            (
-                (x - 3400) as f32 / 1000.0,
-                ((x - 3400) as f32 / 1000.0).cos(),
-            )
-        }),
+        x_axis.values().map(|x| (x, x.cos())),
         &BLUE,
     ))?
     .label("Cosine")
@@ -48,14 +42,14 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
     /*
     // It's possible to use a existing pointing element
      cc.draw_series(PointSeries::<_, _, Circle<_>>::new(
-        (0..6).map(|x| ((x - 3) as f32 / 1.0, ((x - 3) as f32 / 1.0).sin())),
+        (-3.0f32..2.1f32).step(1.0).values().map(|x| (x, x.sin())),
         5,
         Into::<ShapeStyle>::into(&RGBColor(255,0,0)).filled(),
     ))?;*/
 
     // Otherwise you can use a function to construct your pointing element yourself
     cc.draw_series(PointSeries::of_element(
-        (0..6).map(|x| ((x - 3) as f32 / 1.0, ((x - 3) as f32 / 1.0).sin())),
+        (-3.0f32..2.1f32).step(1.0).values().map(|x| (x, x.sin())),
         5,
         ShapeStyle::from(&RED).filled(),
         &|coord, size, style| {
@@ -84,12 +78,10 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
         cc.configure_mesh().x_labels(5).y_labels(3).draw()?;
 
         cc.draw_series(LineSeries::new(
-            (-100..100).map(|x| {
-                (
-                    x as f32 / 100.0,
-                    (x as f32 / 100.0).powf(idx as f32 * 2.0 + 1.0),
-                )
-            }),
+            (-1f32..1f32)
+                .step(0.01)
+                .values()
+                .map(|x| (x, x.powf(idx as f32 * 2.0 + 1.0))),
             &BLUE,
         ))?;
     }
diff --git a/examples/normal-dist.rs b/examples/normal-dist.rs
index 65cf0e38..67307813 100644
--- a/examples/normal-dist.rs
+++ b/examples/normal-dist.rs
@@ -25,10 +25,10 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
 
     let mut x_hist_ctx = ChartBuilder::on(&areas[0])
         .y_label_area_size(40)
-        .build_ranged(0u32..100u32, 0f64..0.5f64)?;
+        .build_ranged((0.0..1.0).step(0.01).use_round().into_centric(), 0..250)?;
     let mut y_hist_ctx = ChartBuilder::on(&areas[3])
         .x_label_area_size(40)
-        .build_ranged(0f64..0.5f64, 0..100u32)?;
+        .build_ranged(0..250, (0.0..1.0).step(0.01).use_round())?;
     let mut scatter_ctx = ChartBuilder::on(&areas[2])
         .x_label_area_size(40)
         .y_label_area_size(40)
@@ -46,19 +46,11 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
     let x_hist = Histogram::vertical(&x_hist_ctx)
         .style(GREEN.filled())
         .margin(0)
-        .data(
-            random_points
-                .iter()
-                .map(|(x, _)| ((x * 100.0) as u32, 0.002)),
-        );
+        .data(random_points.iter().map(|(x, _)| (*x, 1)));
     let y_hist = Histogram::horizontal(&y_hist_ctx)
         .style(GREEN.filled())
         .margin(0)
-        .data(
-            random_points
-                .iter()
-                .map(|(_, y)| ((y * 100.0) as u32, 0.002)),
-        );
+        .data(random_points.iter().map(|(_, y)| (*y, 1)));
     x_hist_ctx.draw_series(x_hist)?;
     y_hist_ctx.draw_series(y_hist)?;
 
diff --git a/examples/normal-dist2.rs b/examples/normal-dist2.rs
index eaebfdc5..eac9e2e5 100644
--- a/examples/normal-dist2.rs
+++ b/examples/normal-dist2.rs
@@ -28,7 +28,10 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
         .set_label_area_size(LabelAreaPosition::Bottom, 60)
         .set_label_area_size(LabelAreaPosition::Right, 60)
         .build_ranged(-4f64..4f64, 0f64..0.1)?
-        .set_secondary_coord(-40i32..40i32, 0u32..500u32);
+        .set_secondary_coord(
+            (-4f64..4f64).step(0.1).use_round().into_centric(),
+            0u32..500u32,
+        );
 
     chart
         .configure_mesh()
@@ -43,11 +46,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
     let actual = Histogram::vertical(chart.borrow_secondary())
         .style(GREEN.filled())
         .margin(3)
-        .data(
-            random_points
-                .iter()
-                .map(|x| ((x * 10.0).round() as i32, 1u32)),
-        );
+        .data(random_points.iter().map(|x| (*x, 1)));
 
     chart
         .draw_secondary_series(actual)?
diff --git a/src/coord/linspace.rs b/src/coord/linspace.rs
new file mode 100644
index 00000000..be919d35
--- /dev/null
+++ b/src/coord/linspace.rs
@@ -0,0 +1,410 @@
+use super::{numeric::RangedCoordusize, AsRangedCoord, DiscreteRanged, Ranged};
+use std::cmp::{Ordering, PartialOrd};
+use std::marker::PhantomData;
+use std::ops::{Add, Range, Sub};
+
+/// The type marker used to denote the rounding method.
+/// Since we are mapping any range to a discrete range thus not all values are
+/// perfect mapped to the grid points. In this case, this type marker gives hints
+/// for the linspace coord for how to treat the non-grid-point values.
+pub trait LinspaceRoundingMethod<V> {
+    /// Search for the value within the given values array and rounding method
+    ///
+    /// - `values`: The values we want to search
+    /// - `target`: The target value
+    /// - `returns`: The index if we found the matching item, otherwise none
+    fn search(values: &[V], target: &V) -> Option<usize>;
+}
+
+/// This type marker means linspace do the exact match for searching
+/// which means if there's no value strictly equals to the target, the coord spec
+/// reports not found result.
+#[derive(Clone)]
+pub struct Exact<V>(PhantomData<V>);
+
+impl<V: PartialOrd> LinspaceRoundingMethod<V> for Exact<V> {
+    fn search(values: &[V], target: &V) -> Option<usize> {
+        values.iter().position(|x| target == x)
+    }
+}
+
+/// This type marker means we round up the value. Which means we try to find a
+/// minimal value in the values array that is greater or equal to the target.
+#[derive(Clone)]
+pub struct Ceil<V>(PhantomData<V>);
+
+impl<V: PartialOrd> LinspaceRoundingMethod<V> for Ceil<V> {
+    fn search(values: &[V], target: &V) -> Option<usize> {
+        let ascending = if values.len() < 2 {
+            true
+        } else {
+            values[0].partial_cmp(&values[1]) == Some(Ordering::Less)
+        };
+
+        match values.binary_search_by(|probe| {
+            if ascending {
+                probe.partial_cmp(target).unwrap()
+            } else {
+                target.partial_cmp(probe).unwrap()
+            }
+        }) {
+            Ok(idx) => Some(idx),
+            Err(idx) => {
+                let offset = if ascending { 0 } else { 1 };
+
+                if idx < offset || idx >= values.len() + offset {
+                    return None;
+                }
+                Some(idx - offset)
+            }
+        }
+    }
+}
+
+/// This means we use the round down. Which means we try to find a
+/// maximum value in the values array that is less or equal to the target.
+#[derive(Clone)]
+pub struct Floor<V>(PhantomData<V>);
+
+impl<V: PartialOrd> LinspaceRoundingMethod<V> for Floor<V> {
+    fn search(values: &[V], target: &V) -> Option<usize> {
+        let ascending = if values.len() < 2 {
+            true
+        } else {
+            values[0].partial_cmp(&values[1]) == Some(Ordering::Less)
+        };
+
+        match values.binary_search_by(|probe| {
+            if ascending {
+                probe.partial_cmp(target).unwrap()
+            } else {
+                target.partial_cmp(probe).unwrap()
+            }
+        }) {
+            Ok(idx) => Some(idx),
+            Err(idx) => {
+                let offset = if ascending { 1 } else { 0 };
+
+                if idx < offset || idx >= values.len() + offset {
+                    return None;
+                }
+                Some(idx - offset)
+            }
+        }
+    }
+}
+
+/// This means we use the rounding. Which means we try to find the closet
+/// value in the array that matches the target
+#[derive(Clone)]
+pub struct Round<V, S>(PhantomData<(V, S)>);
+
+impl<V, S> LinspaceRoundingMethod<V> for Round<V, S>
+where
+    V: Add<S, Output = V> + PartialOrd + Sub<V, Output = S> + Clone,
+    S: PartialOrd + Clone,
+{
+    fn search(values: &[V], target: &V) -> Option<usize> {
+        let ascending = if values.len() < 2 {
+            true
+        } else {
+            values[0].partial_cmp(&values[1]) == Some(Ordering::Less)
+        };
+
+        match values.binary_search_by(|probe| {
+            if ascending {
+                probe.partial_cmp(target).unwrap()
+            } else {
+                target.partial_cmp(probe).unwrap()
+            }
+        }) {
+            Ok(idx) => Some(idx),
+            Err(idx) => {
+                if idx == 0 {
+                    return Some(0);
+                }
+
+                if idx == values.len() {
+                    return Some(idx - 1);
+                }
+
+                let left_delta = if ascending {
+                    target.clone() - values[idx - 1].clone()
+                } else {
+                    values[idx - 1].clone() - target.clone()
+                };
+                let right_delta = if ascending {
+                    values[idx].clone() - target.clone()
+                } else {
+                    target.clone() - values[idx].clone()
+                };
+
+                if left_delta.partial_cmp(&right_delta) == Some(Ordering::Less) {
+                    Some(idx - 1)
+                } else {
+                    Some(idx)
+                }
+            }
+        }
+    }
+}
+
+/// The linspace coordinate decorator, which allows the conversion from a continuous coordinate
+/// to a discrete coordinate by a giving step.
+///
+/// For example, range `0f32..100f32` is a continuous coordinate, thus this prevent us having a
+/// histogram on it. In this case, to get a histogram, we need to split the original range to a
+/// set of discrete buckets (for example, 0.5 per bucket).
+///
+/// The linspace decorate abstracting this method. For example, we can have a discrete coordinate:
+/// `(0f32..100f32).step(0.5)`.
+///
+/// Linspace also supports different types of bucket matching method: ceil, floor, round or exact.
+#[derive(Clone)]
+pub struct Linspace<T: Ranged, S: Clone, R: LinspaceRoundingMethod<T::ValueType>>
+where
+    T::ValueType: Add<S, Output = T::ValueType> + PartialOrd + Clone,
+{
+    step: S,
+    inner: T,
+    grid_value: Vec<T::ValueType>,
+    _phatom: PhantomData<R>,
+}
+
+impl<T: Ranged, S: Clone, R: LinspaceRoundingMethod<T::ValueType>> Linspace<T, S, R>
+where
+    T::ValueType: Add<S, Output = T::ValueType> + PartialOrd + Clone,
+{
+    fn compute_grid_values(&mut self) {
+        let range = self.inner.range();
+
+        match (
+            range.start.partial_cmp(&range.end),
+            (range.start.clone() + self.step.clone()).partial_cmp(&range.end),
+        ) {
+            (Some(a), Some(b)) if a != b || a == Ordering::Equal || b == Ordering::Equal => (),
+            (Some(a), Some(_)) => {
+                let mut current = range.start;
+                while current.partial_cmp(&range.end) == Some(a) {
+                    self.grid_value.push(current.clone());
+                    current = current + self.step.clone();
+                }
+            }
+            _ => (),
+        }
+    }
+
+    /// Set the linspace use the round up method for value matching
+    ///
+    /// - **returns**: The newly created linspace that uses new matching method
+    pub fn use_ceil(self) -> Linspace<T, S, Ceil<T::ValueType>> {
+        Linspace {
+            step: self.step,
+            inner: self.inner,
+            grid_value: self.grid_value,
+            _phatom: PhantomData,
+        }
+    }
+
+    /// Set the linspace use the round down method for value matching
+    ///
+    /// - **returns**: The newly created linspace that uses new matching method
+    pub fn use_floor(self) -> Linspace<T, S, Floor<T::ValueType>> {
+        Linspace {
+            step: self.step,
+            inner: self.inner,
+            grid_value: self.grid_value,
+            _phatom: PhantomData,
+        }
+    }
+
+    /// Set the linspace use the best match method for value matching
+    ///
+    /// - **returns**: The newly created linspace that uses new matching method
+    pub fn use_round(self) -> Linspace<T, S, Round<T::ValueType, S>>
+    where
+        T::ValueType: Sub<T::ValueType, Output = S>,
+        S: PartialOrd,
+    {
+        Linspace {
+            step: self.step,
+            inner: self.inner,
+            grid_value: self.grid_value,
+            _phatom: PhantomData,
+        }
+    }
+
+    /// Set the linspace use the exact match method for value matching
+    ///
+    /// - **returns**: The newly created linspace that uses new matching method
+    pub fn use_exact(self) -> Linspace<T, S, Exact<T::ValueType>>
+    where
+        T::ValueType: Sub<T::ValueType, Output = S>,
+        S: PartialOrd,
+    {
+        Linspace {
+            step: self.step,
+            inner: self.inner,
+            grid_value: self.grid_value,
+            _phatom: PhantomData,
+        }
+    }
+}
+
+impl<T: Ranged, S: Clone, R: LinspaceRoundingMethod<T::ValueType>> Ranged for Linspace<T, S, R>
+where
+    T::ValueType: Add<S, Output = T::ValueType> + PartialOrd + Clone,
+{
+    type ValueType = T::ValueType;
+
+    fn range(&self) -> Range<T::ValueType> {
+        self.inner.range()
+    }
+
+    fn map(&self, value: &T::ValueType, limit: (i32, i32)) -> i32 {
+        self.inner.map(value, limit)
+    }
+
+    fn key_points(&self, n: usize) -> Vec<T::ValueType> {
+        if self.grid_value.len() == 0 {
+            return vec![];
+        }
+        let idx_range: RangedCoordusize = (0..(self.grid_value.len() - 1)).into();
+
+        idx_range
+            .key_points(n)
+            .into_iter()
+            .map(|x| self.grid_value[x].clone())
+            .collect()
+    }
+}
+
+impl<T: Ranged, S: Clone, R: LinspaceRoundingMethod<T::ValueType>> DiscreteRanged
+    for Linspace<T, S, R>
+where
+    T::ValueType: Add<S, Output = T::ValueType> + PartialOrd + Clone,
+{
+    fn size(&self) -> usize {
+        self.grid_value.len()
+    }
+
+    fn index_of(&self, value: &T::ValueType) -> Option<usize> {
+        R::search(self.grid_value.as_ref(), value)
+    }
+
+    fn from_index(&self, idx: usize) -> Option<T::ValueType> {
+        self.grid_value.get(idx).map(Clone::clone)
+    }
+}
+
+pub trait IntoLinspace: AsRangedCoord {
+    /// Set the step value, make a linspace coordinate from the given range.
+    /// By default the matching method use the exact match
+    ///
+    /// - `val`: The step value
+    /// - **returns*: The newly created linspace
+    fn step<S: Clone>(self, val: S) -> Linspace<Self::CoordDescType, S, Exact<Self::Value>>
+    where
+        Self::Value: Add<S, Output = Self::Value> + PartialOrd + Clone,
+    {
+        let mut ret = Linspace {
+            step: val,
+            inner: self.into(),
+            grid_value: vec![],
+            _phatom: PhantomData,
+        };
+
+        ret.compute_grid_values();
+
+        ret
+    }
+}
+
+impl<T: AsRangedCoord> IntoLinspace for T {}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[test]
+    fn test_float_linspace() {
+        let coord = (0.0f64..100.0f64).step(0.1);
+
+        assert_eq!(coord.map(&23.12, (0, 10000)), 2312);
+        assert_eq!(coord.range(), 0.0..100.0);
+        assert_eq!(coord.key_points(100000).len(), 1001);
+        assert_eq!(coord.size(), 1001);
+        assert_eq!(coord.index_of(&coord.from_index(230).unwrap()), Some(230));
+        assert!((coord.from_index(230).unwrap() - 23.0).abs() < 1e-5);
+    }
+
+    #[test]
+    fn test_rounding_methods() {
+        let coord = (0.0f64..100.0f64).step(1.0);
+
+        assert_eq!(coord.index_of(&1.0), Some(1));
+        assert_eq!(coord.index_of(&1.2), None);
+
+        let coord = coord.use_floor();
+        assert_eq!(coord.index_of(&1.0), Some(1));
+        assert_eq!(coord.index_of(&1.2), Some(1));
+        assert_eq!(coord.index_of(&23.9), Some(23));
+        assert_eq!(coord.index_of(&10000.0), Some(99));
+        assert_eq!(coord.index_of(&-1.0), None);
+
+        let coord = coord.use_ceil();
+        assert_eq!(coord.index_of(&1.0), Some(1));
+        assert_eq!(coord.index_of(&1.2), Some(2));
+        assert_eq!(coord.index_of(&23.9), Some(24));
+        assert_eq!(coord.index_of(&10000.0), None);
+        assert_eq!(coord.index_of(&-1.0), Some(0));
+
+        let coord = coord.use_round();
+        assert_eq!(coord.index_of(&1.0), Some(1));
+        assert_eq!(coord.index_of(&1.2), Some(1));
+        assert_eq!(coord.index_of(&1.7), Some(2));
+        assert_eq!(coord.index_of(&23.9), Some(24));
+        assert_eq!(coord.index_of(&10000.0), Some(99));
+        assert_eq!(coord.index_of(&-1.0), Some(0));
+
+        let coord = (0.0f64..-100.0f64).step(-1.0);
+
+        assert_eq!(coord.index_of(&-1.0), Some(1));
+        assert_eq!(coord.index_of(&-1.2), None);
+
+        let coord = coord.use_floor();
+        assert_eq!(coord.index_of(&-1.0), Some(1));
+        assert_eq!(coord.index_of(&-1.2), Some(2));
+        assert_eq!(coord.index_of(&-23.9), Some(24));
+        assert_eq!(coord.index_of(&-10000.0), None);
+        assert_eq!(coord.index_of(&1.0), Some(0));
+
+        let coord = coord.use_ceil();
+        assert_eq!(coord.index_of(&-1.0), Some(1));
+        assert_eq!(coord.index_of(&-1.2), Some(1));
+        assert_eq!(coord.index_of(&-23.9), Some(23));
+        assert_eq!(coord.index_of(&-10000.0), Some(99));
+        assert_eq!(coord.index_of(&1.0), None);
+
+        let coord = coord.use_round();
+        assert_eq!(coord.index_of(&-1.0), Some(1));
+        assert_eq!(coord.index_of(&-1.2), Some(1));
+        assert_eq!(coord.index_of(&-1.7), Some(2));
+        assert_eq!(coord.index_of(&-23.9), Some(24));
+        assert_eq!(coord.index_of(&-10000.0), Some(99));
+        assert_eq!(coord.index_of(&1.0), Some(0));
+    }
+
+    #[cfg(feature = "chrono")]
+    #[test]
+    fn test_duration_linspace() {
+        use chrono::Duration;
+        let coord = (Duration::seconds(0)..Duration::seconds(100)).step(Duration::milliseconds(1));
+
+        assert_eq!(coord.size(), 100_000);
+        assert_eq!(coord.index_of(&coord.from_index(230).unwrap()), Some(230));
+        assert_eq!(coord.key_points(10000000).len(), 100_000);
+        assert_eq!(coord.range(), Duration::seconds(0)..Duration::seconds(100));
+        assert_eq!(coord.map(&Duration::seconds(25), (0, 100_000)), 25000);
+    }
+}
diff --git a/src/coord/mod.rs b/src/coord/mod.rs
index 74a05b43..b3f3dc4e 100644
--- a/src/coord/mod.rs
+++ b/src/coord/mod.rs
@@ -26,6 +26,7 @@ use plotters_backend::BackendCoord;
 mod datetime;
 mod discrete;
 mod group_by;
+mod linspace;
 mod logarithmic;
 mod nested;
 mod numeric;
@@ -46,6 +47,7 @@ pub use ranged::{AsRangedCoord, MeshLine, Ranged, RangedCoord, ReversibleRanged}
 pub use partial_axis::{make_partial_axis, IntoPartialAxis};
 
 pub use discrete::{CentricValues, DiscreteRanged, IntoCentric};
+pub use linspace::{IntoLinspace, Linspace};
 
 pub use logarithmic::{LogCoord, LogRange, LogScalable};
 
diff --git a/src/coord/slice.rs b/src/coord/slice.rs
index 0136e1a2..9ece2bfb 100644
--- a/src/coord/slice.rs
+++ b/src/coord/slice.rs
@@ -4,6 +4,7 @@ use std::ops::Range;
 /// A range that is defined by a slice of values.
 ///
 /// Please note: the behavior of constructing an empty range may cause panic
+#[derive(Clone)]
 pub struct RangedSlice<'a, T: PartialEq>(&'a [T]);
 
 impl<'a, T: PartialEq> Ranged for RangedSlice<'a, T> {
diff --git a/src/lib.rs b/src/lib.rs
index 2355ea80..f542ea7a 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -708,10 +708,10 @@ pub mod prelude {
 
     // Coordinates
     pub use crate::coord::{
-        BuildNestedCoord, CentricValues, CoordTranslate, GroupBy, IntoCentric, IntoPartialAxis,
-        LogCoord, LogRange, LogScalable, NestedRange, NestedValue, Ranged, RangedCoord,
-        RangedCoordf32, RangedCoordf64, RangedCoordi32, RangedCoordi64, RangedCoordu32,
-        RangedCoordu64, ToGroupByRange,
+        BuildNestedCoord, CentricValues, CoordTranslate, DiscreteRanged, GroupBy, IntoCentric,
+        IntoLinspace, IntoPartialAxis, Linspace, LogCoord, LogRange, LogScalable, NestedRange,
+        NestedValue, Ranged, RangedCoord, RangedCoordf32, RangedCoordf64, RangedCoordi32,
+        RangedCoordi64, RangedCoordu32, RangedCoordu64, ToGroupByRange,
     };
 
     #[cfg(feature = "chrono")]


Test patch: