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

Issue:
[Feature Request] Slice as a discrete coordinate 
### What is the feature ?
Use of slice as a discrete coordinate. We can do 
```rust
.build_ranged(["foo", "bar", "baz"], 0..100)
```



Discussion / hints:
My PR #83 may help with that, however the category I've added are not implicitly created from the array and I guess some additional changes will be required.

Fix patch:
diff --git a/examples/boxplot.rs b/examples/boxplot.rs
index 47f3b1af..e845db16 100644
--- a/examples/boxplot.rs
+++ b/examples/boxplot.rs
@@ -42,15 +42,12 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
         .map(|(k, v)| (k.0.clone(), k.1.clone(), Quartiles::new(&v)))
         .collect();
 
-    let category = Category::new(
-        "Host",
-        dataset
-            .iter()
-            .unique_by(|x| x.0.clone())
-            .sorted_by(|a, b| b.2.median().partial_cmp(&a.2.median()).unwrap())
-            .map(|x| x.0.clone())
-            .collect(),
-    );
+    let host_list: Vec<_> = dataset
+        .iter()
+        .unique_by(|x| x.0.clone())
+        .sorted_by(|a, b| b.2.median().partial_cmp(&a.2.median()).unwrap())
+        .map(|x| x.0.clone())
+        .collect();
 
     let mut colors = (0..).map(Palette99::pick);
     let mut offsets = (-12..).step_by(24);
@@ -75,21 +72,21 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
         .caption("Ping Boxplot", ("sans-serif", 20).into_font())
         .build_ranged(
             values_range.start - 1.0..values_range.end + 1.0,
-            category.range(),
+            host_list[..].into_centric(),
         )?;
 
     chart
         .configure_mesh()
         .x_desc("Ping, ms")
-        .y_desc(category.name())
-        .y_labels(category.len())
+        .y_desc("Host")
+        .y_labels(host_list.len())
         .line_style_2(&WHITE)
         .draw()?;
 
     for (label, (values, style, offset)) in &series {
         chart
             .draw_series(values.iter().map(|x| {
-                Boxplot::new_horizontal(category.get(&x.0).unwrap(), &x.1)
+                Boxplot::new_horizontal(CentricValues::CenterOf(&x.0), &x.1)
                     .width(20)
                     .whisker_width(0.5)
                     .style(style)
@@ -113,7 +110,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
         6.0, 7.0, 15.9, 36.9, 39.0, 40.0, 41.0, 42.0, 43.0, 47.0, 49.0,
     ]);
     let quartiles_b = Quartiles::new(&[16.0, 17.0, 50.0, 60.0, 40.2, 41.3, 42.7, 43.3, 47.0]);
-    let category_ab = Category::new("", vec!["a", "b"]);
+
+    let ab_axis = ["a", "b"];
+
     let values_range = fitting_range(
         quartiles_a
             .values()
@@ -125,14 +124,14 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
         .y_label_area_size(40)
         .caption("Vertical Boxplot", ("sans-serif", 20).into_font())
         .build_ranged(
-            category_ab.clone(),
+            ab_axis[..].into_centric(),
             values_range.start - 10.0..values_range.end + 10.0,
         )?;
 
     chart.configure_mesh().line_style_2(&WHITE).draw()?;
     chart.draw_series(vec![
-        Boxplot::new_vertical(category_ab.get(&"a").unwrap(), &quartiles_a),
-        Boxplot::new_vertical(category_ab.get(&"b").unwrap(), &quartiles_b),
+        Boxplot::new_vertical(CentricValues::CenterOf(&"a"), &quartiles_a),
+        Boxplot::new_vertical(CentricValues::CenterOf(&"b"), &quartiles_b),
     ])?;
 
     let mut chart = ChartBuilder::on(&right)
diff --git a/src/coord/category.rs b/src/coord/category.rs
deleted file mode 100644
index f6dc6864..00000000
--- a/src/coord/category.rs
+++ /dev/null
@@ -1,204 +0,0 @@
-use std::fmt;
-use std::ops::Range;
-use std::rc::Rc;
-
-use super::Ranged;
-
-/// The category coordinate
-pub struct Category<T: PartialEq> {
-    name: String,
-    elements: Rc<Vec<T>>,
-    // i32 type is required for the empty ref (having -1 value)
-    idx: i32,
-}
-
-impl<T: PartialEq> Clone for Category<T> {
-    fn clone(&self) -> Self {
-        Category {
-            name: self.name.clone(),
-            elements: Rc::clone(&self.elements),
-            idx: self.idx,
-        }
-    }
-}
-
-impl<T: PartialEq + fmt::Display> fmt::Debug for Category<T> {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        let element = &self.elements[self.idx as usize];
-        write!(f, "{}", element)
-    }
-}
-
-impl<T: PartialEq> Category<T> {
-    /// Create a new category coordinate.
-    ///
-    /// - `name`: The name of the category
-    /// - `elements`: The vector of category elements
-    /// - **returns** The newly created category coordinate
-    ///
-    /// ```rust
-    /// use plotters::prelude::*;
-    ///
-    /// let category = Category::new("color", vec!["red", "green", "blue"]);
-    /// ```
-    pub fn new<S: Into<String>>(name: S, elements: Vec<T>) -> Self {
-        Self {
-            name: name.into(),
-            elements: Rc::new(elements),
-            idx: -1,
-        }
-    }
-
-    /// Get an element reference (tick) by its value.
-    ///
-    /// - `val`: The value of the element
-    /// - **returns** The optional reference
-    ///
-    /// ```rust
-    /// use plotters::prelude::*;
-    ///
-    /// let category = Category::new("color", vec!["red", "green", "blue"]);
-    /// let red = category.get(&"red");
-    /// assert!(red.is_some());
-    /// let unknown = category.get(&"unknown");
-    /// assert!(unknown.is_none());
-    /// ```
-    pub fn get(&self, val: &T) -> Option<Category<T>> {
-        match self.elements.iter().position(|x| x == val) {
-            Some(pos) => {
-                let element_ref = Category {
-                    name: self.name.clone(),
-                    elements: Rc::clone(&self.elements),
-                    idx: pos as i32,
-                };
-                Some(element_ref)
-            }
-            _ => None,
-        }
-    }
-
-    /// Create a full range over the category elements.
-    ///
-    /// - **returns** The range including all category elements
-    ///
-    /// ```rust
-    /// use plotters::prelude::*;
-    ///
-    /// let category = Category::new("color", vec!["red", "green", "blue"]);
-    /// let range = category.range();
-    /// ```
-    pub fn range(&self) -> Self {
-        self.clone()
-    }
-
-    /// Get the number of elements in the category.
-    ///
-    /// - **returns** The number of elements
-    ///
-    /// ```rust
-    /// use plotters::prelude::*;
-    ///
-    /// let category = Category::new("color", vec!["red", "green", "blue"]);
-    /// assert_eq!(category.len(), 3);
-    /// ```
-    pub fn len(&self) -> usize {
-        self.elements.len()
-    }
-
-    /// Returns `true` if the category contains no elements.
-    ///
-    /// - **returns** `true` is no elements, otherwise - `false`
-    ///
-    /// ```rust
-    /// use plotters::prelude::*;
-    ///
-    /// let category = Category::new("color", vec!["red", "green", "blue"]);
-    /// assert_eq!(category.is_empty(), false);
-    ///
-    /// let category = Category::new("empty", Vec::<&str>::new());
-    /// assert_eq!(category.is_empty(), true);
-    /// ```
-    pub fn is_empty(&self) -> bool {
-        self.elements.is_empty()
-    }
-
-    /// Get the category name.
-    ///
-    /// - **returns** The name of the category
-    ///
-    /// ```rust
-    /// use plotters::prelude::*;
-    ///
-    /// let category = Category::new("color", vec!["red", "green", "blue"]);
-    /// assert_eq!(category.name(), "color");
-    /// ```
-    pub fn name(&self) -> String {
-        self.name.clone()
-    }
-}
-
-impl<T: PartialEq> Ranged for Category<T> {
-    type ValueType = Category<T>;
-
-    fn range(&self) -> Range<Category<T>> {
-        let mut left = self.clone();
-        let mut right = self.clone();
-        left.idx = 0;
-        right.idx = right.len() as i32 - 1;
-        left..right
-    }
-
-    fn map(&self, value: &Self::ValueType, limit: (i32, i32)) -> i32 {
-        // Add margins to spans as edge values are not applicable to category
-        let total_span = (self.len() + 1) as f64;
-        let value_span = f64::from(value.idx + 1);
-        (f64::from(limit.1 - limit.0) * value_span / total_span) as i32 + limit.0
-    }
-
-    fn key_points(&self, max_points: usize) -> Vec<Self::ValueType> {
-        let mut ret = vec![];
-        let intervals = (self.len() - 1) as f64;
-        let elements = &self.elements;
-        let name = &self.name;
-        let step = (intervals / max_points as f64 + 1.0) as usize;
-        for idx in (0..self.len()).step_by(step) {
-            ret.push(Category {
-                name: name.clone(),
-                elements: Rc::clone(&elements),
-                idx: idx as i32,
-            });
-        }
-        ret
-    }
-}
-
-#[cfg(test)]
-mod test {
-    use super::*;
-
-    #[test]
-    fn test_clone_trait() {
-        let category = Category::new("color", vec!["red", "green", "blue"]);
-        let red = category.get(&"red").unwrap();
-        assert_eq!(red.idx, 0);
-        let clone = red.clone();
-        assert_eq!(clone.idx, 0);
-    }
-
-    #[test]
-    fn test_debug_trait() {
-        let category = Category::new("color", vec!["red", "green", "blue"]);
-        let red = category.get(&"red").unwrap();
-        assert_eq!(format!("{:?}", red), "red");
-    }
-
-    #[test]
-    fn test_ranged_trait() {
-        let category = Category::new("color", vec!["red", "green", "blue"]);
-        assert_eq!(category.map(&category.get(&"red").unwrap(), (0, 8)), 2);
-        assert_eq!(category.map(&category.get(&"green").unwrap(), (0, 8)), 4);
-        assert_eq!(category.map(&category.get(&"blue").unwrap(), (0, 8)), 6);
-        assert_eq!(category.key_points(3).len(), 3);
-        assert_eq!(category.key_points(5).len(), 3);
-    }
-}
diff --git a/src/coord/discrete.rs b/src/coord/discrete.rs
index c63a3f40..e3eda849 100644
--- a/src/coord/discrete.rs
+++ b/src/coord/discrete.rs
@@ -108,6 +108,7 @@ where
 impl<R: AsRangedCoord> IntoCentric for R where R::CoordDescType: DiscreteRanged {}
 
 /// The value that used by the centric coordinate
+#[derive(Clone)]
 pub enum CentricValues<T> {
     Exact(T),
     CenterOf(T),
diff --git a/src/coord/mod.rs b/src/coord/mod.rs
index 87400bc9..d8eb680a 100644
--- a/src/coord/mod.rs
+++ b/src/coord/mod.rs
@@ -22,7 +22,6 @@ Also, the ranged axis can be deserted, and this is required by the histogram ser
 */
 use plotters_backend::BackendCoord;
 
-mod category;
 #[cfg(feature = "chrono")]
 mod datetime;
 mod discrete;
@@ -31,6 +30,9 @@ mod logarithmic;
 mod numeric;
 mod partial_axis;
 mod ranged;
+mod slice;
+
+pub use slice::RangedSlice;
 
 #[cfg(feature = "chrono")]
 pub use datetime::{IntoMonthly, IntoYearly, RangedDate, RangedDateTime, RangedDuration};
@@ -42,7 +44,7 @@ pub use ranged::{AsRangedCoord, MeshLine, Ranged, RangedCoord, ReversibleRanged}
 
 pub use partial_axis::{make_partial_axis, IntoPartialAxis};
 
-pub use discrete::{DiscreteRanged, IntoCentric};
+pub use discrete::{CentricValues, DiscreteRanged, IntoCentric};
 
 pub use logarithmic::{LogCoord, LogRange, LogScalable};
 
@@ -50,8 +52,6 @@ pub use group_by::{GroupBy, ToGroupByRange};
 use std::rc::Rc;
 use std::sync::Arc;
 
-pub use category::Category;
-
 /// The trait that translates some customized object to the backend coordinate
 pub trait CoordTranslate {
     type From;
diff --git a/src/coord/slice.rs b/src/coord/slice.rs
new file mode 100644
index 00000000..d2e1f2a9
--- /dev/null
+++ b/src/coord/slice.rs
@@ -0,0 +1,91 @@
+use super::{AsRangedCoord, DiscreteRanged, Ranged};
+use std::ops::Range;
+
+pub struct RangedSlice<'a, T: PartialEq>(&'a [T]);
+
+impl<'a, T: PartialEq> Ranged for RangedSlice<'a, T> {
+    type ValueType = &'a T;
+
+    fn range(&self) -> Range<&'a T> {
+        &self.0[0]..&self.0[self.0.len() - 1]
+    }
+
+    fn map(&self, value: &Self::ValueType, limit: (i32, i32)) -> i32 {
+        match self.0.iter().position(|x| &x == value) {
+            Some(pos) => {
+                let pixel_span = limit.1 - limit.0;
+                let value_span = self.0.len() - 1;
+                (f64::from(limit.0)
+                    + f64::from(pixel_span)
+                        * (f64::from(pos as u32) / f64::from(value_span as u32)))
+                .round() as i32
+            }
+            None => limit.0,
+        }
+    }
+
+    fn key_points(&self, max_points: usize) -> Vec<Self::ValueType> {
+        let mut ret = vec![];
+        let intervals = (self.0.len() - 1) as f64;
+        let step = (intervals / max_points as f64 + 1.0) as usize;
+        for idx in (0..self.0.len()).step_by(step) {
+            ret.push(&self.0[idx]);
+        }
+        ret
+    }
+}
+
+impl<'a, T: PartialEq> DiscreteRanged for RangedSlice<'a, T> {
+    fn size(&self) -> usize {
+        self.0.len()
+    }
+
+    fn index_of(&self, value: &&'a T) -> Option<usize> {
+        self.0.iter().position(|x| &x == value)
+    }
+
+    fn from_index(&self, index: usize) -> Option<&'a T> {
+        if self.0.len() <= index {
+            return None;
+        }
+        Some(&self.0[index])
+    }
+}
+
+impl<'a, T: PartialEq> From<&'a [T]> for RangedSlice<'a, T> {
+    fn from(range: &'a [T]) -> Self {
+        RangedSlice(range)
+    }
+}
+
+impl<'a, T: PartialEq> AsRangedCoord for &'a [T] {
+    type CoordDescType = RangedSlice<'a, T>;
+    type Value = &'a T;
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+    #[test]
+    fn test_slice_range() {
+        let my_slice = [1, 2, 3, 0, -1, -2];
+        let slice_range: RangedSlice<i32> = my_slice[..].into();
+
+        assert_eq!(slice_range.range(), &1..&-2);
+        assert_eq!(
+            slice_range.key_points(6),
+            my_slice.iter().collect::<Vec<_>>()
+        );
+        assert_eq!(slice_range.map(&&0, (0, 50)), 30);
+    }
+
+    #[test]
+    fn test_slice_range_discrete() {
+        let my_slice = [1, 2, 3, 0, -1, -2];
+        let slice_range: RangedSlice<i32> = my_slice[..].into();
+
+        assert_eq!(slice_range.size(), 6);
+        assert_eq!(slice_range.index_of(&&3), Some(2));
+        assert_eq!(slice_range.from_index(2), Some(&3));
+    }
+}
diff --git a/src/element/boxplot.rs b/src/element/boxplot.rs
index c371ed19..c882bcf6 100644
--- a/src/element/boxplot.rs
+++ b/src/element/boxplot.rs
@@ -177,7 +177,7 @@ impl<K, O: BoxplotOrient<K, f32>> Boxplot<K, O> {
     }
 }
 
-impl<'a, K: 'a + Clone, O: BoxplotOrient<K, f32>> PointCollection<'a, (O::XType, O::YType)>
+impl<'a, K: Clone, O: BoxplotOrient<K, f32>> PointCollection<'a, (O::XType, O::YType)>
     for &'a Boxplot<K, O>
 {
     type Borrow = (O::XType, O::YType);
diff --git a/src/element/errorbar.rs b/src/element/errorbar.rs
index 696ba641..63023ac7 100644
--- a/src/element/errorbar.rs
+++ b/src/element/errorbar.rs
@@ -94,8 +94,8 @@ impl<K, V> ErrorBar<K, V, ErrorBarOrientH<K, V>> {
     }
 }
 
-impl<'a, K: 'a + Clone, V: 'a + Clone, O: ErrorBarOrient<K, V>>
-    PointCollection<'a, (O::XType, O::YType)> for &'a ErrorBar<K, V, O>
+impl<'a, K: Clone, V: Clone, O: ErrorBarOrient<K, V>> PointCollection<'a, (O::XType, O::YType)>
+    for &'a ErrorBar<K, V, O>
 {
     type Borrow = (O::XType, O::YType);
     type IntoIter = Vec<Self::Borrow>;
diff --git a/src/lib.rs b/src/lib.rs
index 5f0b9dff..423730d2 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -708,7 +708,7 @@ pub mod prelude {
 
     // Coordinates
     pub use crate::coord::{
-        Category, CoordTranslate, GroupBy, IntoCentric, IntoPartialAxis, LogCoord, LogRange,
+        CentricValues, CoordTranslate, GroupBy, IntoCentric, IntoPartialAxis, LogCoord, LogRange,
         LogScalable, Ranged, RangedCoord, RangedCoordf32, RangedCoordf64, RangedCoordi32,
         RangedCoordi64, RangedCoordu32, RangedCoordu64, ToGroupByRange,
     };


Test patch: