From e24b1943c70a916f928a9019c32d10fe191f41f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Thebault?= Date: Sun, 26 Jul 2026 22:46:29 +0200 Subject: [PATCH 1/3] color categories to legend --- src/des/series.rs | 19 +- src/drawing.rs | 4 + src/drawing/figure.rs | 20 ++- src/drawing/legend.rs | 56 ++---- src/drawing/plot.rs | 142 ++++++++++----- src/drawing/series.rs | 190 +++++++++++++++----- src/sd/series.rs | 8 + tests/figs/series/color-cats-to-legend.json | 13 ++ tests/refs/series/color-cats-to-legend.png | Bin 0 -> 13990 bytes tests/refs/series/color-cats-to-legend.svg | 23 +++ tests/src/tests/series.rs | 8 +- 11 files changed, 351 insertions(+), 132 deletions(-) create mode 100644 tests/figs/series/color-cats-to-legend.json create mode 100644 tests/refs/series/color-cats-to-legend.png create mode 100644 tests/refs/series/color-cats-to-legend.svg diff --git a/src/des/series.rs b/src/des/series.rs index 7995fc66..5054a48f 100644 --- a/src/des/series.rs +++ b/src/des/series.rs @@ -344,6 +344,7 @@ impl Line { /// | i64 | [`ColorMap::Literal`] | Interpreted as a color, where the integer is treated as a 32-bit RGBA value (e.g. 0xRRGGBBAA). See [`Rgba8::to_rgba_int`](crate::Rgba8::to_rgba_int). | /// Other combinations of data type and color map will result in an error when rendering the plot. /// If the plot's colorbar is set, the colorbar will be automatically configured based on the color data column and color map. +/// Color categories can be added to the legend if [`color_cats_to_legend`](Scatter::color_cats_to_legend) is true. #[derive(Debug, Clone, PartialEq)] pub struct Scatter { x_data: DataCol, @@ -355,6 +356,7 @@ pub struct Scatter { marker: style::series::Marker, size_data: Option, color_data: Option<(DataCol, ColorMap)>, + color_cats_to_legend: bool, } impl Scatter { @@ -370,6 +372,7 @@ impl Scatter { marker: style::series::Marker::default(), size_data: None, color_data: None, + color_cats_to_legend: false, } } @@ -413,6 +416,13 @@ impl Scatter { self } + /// Configure this series so that the each category in the color data contributes + /// to legend entries + pub fn with_color_cats_to_legend(mut self) -> Self { + self.color_cats_to_legend = true; + self + } + /// Get the x data column pub fn x_data(&self) -> &DataCol { &self.x_data @@ -453,6 +463,11 @@ impl Scatter { self.color_data.as_ref().map(|(data, cmap)| (data, cmap)) } + /// Check whether the color categories contribute to the legend + pub fn color_cats_to_legend(&self) -> bool { + self.color_cats_to_legend + } + /// Chaining helper to build a plot from this series /// This can only be used if your plot contains a single series. /// This is equivalent to `Plot::new(vec![self.into()])` @@ -462,8 +477,8 @@ impl Scatter { /// use plotive::des; /// use plotive::des::series::{self, data_src_ref}; /// - /// let fig: des::Figure = series::Line::new(data_src_ref("x_values"), data_src_ref("y_values")) - /// .with_name("Line Series") + /// let fig: des::Figure = series::Scatter::new(data_src_ref("x_values"), data_src_ref("y_values")) + /// .with_name("Scatter Series") /// .into_plot() /// .with_x_axis(des::Axis::new().with_ticks(Default::default())) /// .with_y_axis(des::Axis::new().with_ticks(Default::default()).with_grid(Default::default())) diff --git a/src/drawing.rs b/src/drawing.rs index 6e8b6621..88ffd052 100644 --- a/src/drawing.rs +++ b/src/drawing.rs @@ -396,6 +396,10 @@ impl Categories { self.cats.iter().map(|c| c.0.as_str()) } + fn into_iter(self) -> impl Iterator { + self.cats.into_iter().map(|c| c.0) + } + fn get(&self, idx: usize) -> Option<&str> { self.cats.get(idx).map(|c| c.0.as_str()) } diff --git a/src/drawing/figure.rs b/src/drawing/figure.rs index aed62771..a936a424 100644 --- a/src/drawing/figure.rs +++ b/src/drawing/figure.rs @@ -135,13 +135,23 @@ where rect.width(), self.fontdb(), ); - + + let data_source = self.data_source(); for plot in fig.plots().iter().filter_map(|p| p) { let mut idx = 0; - plot::for_each_series(plot, |s| { - if let Some(entry) = s.legend_entry() { - builder.add_entry(idx, entry)?; - idx += 1; + plot::for_each_legend_entries(plot, data_source, |entries| { + match entries { + legend::Entries::Single(entry) => { + builder.add_entry(idx, entry)?; + idx += 1; + } + legend::Entries::Multi(entries) => { + for entry in entries { + builder.add_entry(idx, entry)?; + idx += 1; + } + } + legend::Entries::None => {} } Ok(()) })?; diff --git a/src/drawing/legend.rs b/src/drawing/legend.rs index 9c865d14..babd8bf8 100644 --- a/src/drawing/legend.rs +++ b/src/drawing/legend.rs @@ -16,46 +16,26 @@ pub enum Shape { }, } -#[derive(Debug, Clone, Copy)] -pub enum ShapeRef<'a> { - Line(&'a style::series::Stroke, Option<&'a style::series::Marker>), - Marker(&'a style::series::Marker), - Rect( - Option<&'a style::series::Fill>, - Option<&'a style::series::Stroke>, - ), - AreaRect { - fill: Option<&'a style::series::Fill>, - y1_stroke: Option<&'a style::series::Stroke>, - y2_stroke: Option<&'a style::series::Stroke>, - }, +/// A legend entry, used to populate the legend +#[derive(Debug, Clone)] +pub struct Entry { + pub label: String, + pub txt_props: Option>, + pub shape: Shape, } -impl ShapeRef<'_> { - pub fn to_shape(&self) -> Shape { - match self { - &ShapeRef::Line(line, marker) => Shape::Line(line.clone(), marker.cloned()), - &ShapeRef::Marker(marker) => Shape::Marker(marker.clone()), - &ShapeRef::Rect(fill, line) => Shape::Rect(fill.cloned(), line.cloned()), - &ShapeRef::AreaRect { - fill, - y1_stroke, - y2_stroke, - } => Shape::AreaRect { - fill: fill.cloned(), - y1_stroke: y1_stroke.cloned(), - y2_stroke: y2_stroke.cloned(), - }, - } - } +#[derive(Debug, Clone, Default)] +pub enum Entries { + #[default] + None, + Single(Entry), + Multi(Vec), } -/// A legend entry, used to populate the legend -#[derive(Debug, Clone)] -pub struct Entry<'a> { - pub label: &'a str, - pub txt_props: Option<&'a text::TextProps>, - pub shape: ShapeRef<'a>, +impl From for Entries { + fn from(entry: Entry) -> Self { + Entries::Single(entry) + } } /// A legend entry, as built during setup phase @@ -127,8 +107,8 @@ impl<'a> LegendBuilder<'a> { } pub fn add_entry(&mut self, index: usize, entry: Entry) -> Result<(), drawing::Error> { - let shape = entry.shape.to_shape(); - let txt_props = entry.txt_props.unwrap_or(&self.txt_props); + let shape = entry.shape; + let txt_props = entry.txt_props.as_ref().unwrap_or_else(|| &self.txt_props); let font = super::resolve_line_font(txt_props, Default::default()); let font_size = txt_props.size.unwrap_or(defaults::LEGEND_LABEL_FONT_SIZE); let fill = txt_props diff --git a/src/drawing/plot.rs b/src/drawing/plot.rs index fcafa659..101f28fe 100644 --- a/src/drawing/plot.rs +++ b/src/drawing/plot.rs @@ -4,14 +4,14 @@ use std::f32; use std::rc::Rc; use std::sync::Arc; -use crate::des::{PlotIdx, annot, colorbar}; +use crate::des::{PlotIdx, annot}; use crate::drawing::annot::Annot; use crate::drawing::axis::{ AsBoundRef, Axis, AxisCacheKey, AxisCacheMap, AxisScale, Bounds, Orientation, Side, }; use crate::drawing::cmap::ColorMap; -use crate::drawing::colorbar::{ColorBar, ColorBarBuilder}; -use crate::drawing::legend::{Legend, LegendBuilder}; +use crate::drawing::colorbar::{self, ColorBar, ColorBarBuilder}; +use crate::drawing::legend::{self, Legend, LegendBuilder}; use crate::drawing::scale::CoordMapXy; use crate::drawing::series::{self, Series, SeriesExt}; use crate::drawing::{ColumnExt, Ctx, Error, get_column}; @@ -595,11 +595,21 @@ where self.fontdb(), ); + let data_source = self.data_source(); let mut idx = 0; - for_each_series(des_plot, |s| { - if let Some(entry) = s.legend_entry() { - builder.add_entry(idx, entry)?; - idx += 1; + for_each_legend_entries(des_plot, data_source, |entries| { + match entries { + legend::Entries::Single(entry) => { + builder.add_entry(idx, entry)?; + idx += 1; + } + legend::Entries::Multi(entries) => { + for entry in entries { + builder.add_entry(idx, entry)?; + idx += 1; + } + } + legend::Entries::None => {} } Ok(()) })?; @@ -615,30 +625,28 @@ where let mut builders: Vec = Vec::new(); - for_each_series(des_plot, |s| { - if let Some(entry) = s.colorbar_entry() { - let col = get_column(entry.data_col, self.data_source())?; - let bounds = col - .bounds() - .expect("Should get bounds for colormap data column"); - - let locator = des_colorbar - .map(|cb| cb.ticks_locator()) - .cloned() - .unwrap_or_default(); - - let hash = entry.cmap_build.hash(bounds.as_bound_ref()); - - if let Some(cbb) = builders.iter_mut().find(|b| b.hash() == hash) { - cbb.unite_bounds(bounds.as_bound_ref())?; - } else { - builders.push(ColorBarBuilder::new( - entry.cmap_build, - hash, - bounds, - locator, - )?); - } + for_each_colorbar_entry(des_plot, |entry| { + let col = get_column(entry.data_col, self.data_source())?; + let bounds = col + .bounds() + .expect("Should get bounds for colormap data column"); + + let locator = des_colorbar + .map(|cb| cb.ticks_locator()) + .cloned() + .unwrap_or_default(); + + let hash = entry.cmap_build.hash(bounds.as_bound_ref()); + + if let Some(cbb) = builders.iter_mut().find(|b| b.hash() == hash) { + cbb.unite_bounds(bounds.as_bound_ref())?; + } else { + builders.push(ColorBarBuilder::new( + entry.cmap_build, + hash, + bounds, + locator, + )?); } Ok(()) })?; @@ -962,20 +970,64 @@ where } } -pub fn for_each_series(plot: &des::Plot, mut f: F) -> Result<(), Error> +pub fn for_each_legend_entries(plot: &des::Plot, data_source: &D, mut f: F) -> Result<(), Error> where - F: FnMut(&dyn SeriesExt) -> Result<(), Error>, + D: data::Source + ?Sized, + F: FnMut(legend::Entries) -> Result<(), Error>, { for s in plot.series() { match &s { - des::Series::Line(line) => f(line)?, - des::Series::Scatter(scatter) => f(scatter)?, - des::Series::Area(area) => f(area)?, - des::Series::Histogram(hist) => f(hist)?, - des::Series::Bars(bars) => f(bars)?, + des::Series::Line(line) => f(line.legend_entries(data_source)?)?, + des::Series::Scatter(scatter) => f(scatter.legend_entries(data_source)?)?, + des::Series::Area(area) => f(area.legend_entries(data_source)?)?, + des::Series::Histogram(hist) => f(hist.legend_entries(data_source)?)?, + des::Series::Bars(bars) => f(bars.legend_entries(data_source)?)?, des::Series::BarsGroup(bars_group) => { for bs in bars_group.series() { - f(bs)? + f(bs.legend_entries(data_source)?)? + } + } + } + } + Ok(()) +} + +pub fn for_each_colorbar_entry(plot: &des::Plot, mut f: F) -> Result<(), Error> +where + F: FnMut(colorbar::Entry<'_>) -> Result<(), Error>, +{ + for s in plot.series() { + match &s { + des::Series::Line(line) => { + if let Some(entry) = line.colorbar_entry() { + f(entry)?; + } + } + des::Series::Scatter(scatter) => { + if let Some(entry) = scatter.colorbar_entry() { + f(entry)?; + } + } + des::Series::Area(area) => { + if let Some(entry) = area.colorbar_entry() { + f(entry)?; + } + } + des::Series::Histogram(hist) => { + if let Some(entry) = hist.colorbar_entry() { + f(entry)?; + } + } + des::Series::Bars(bars) => { + if let Some(entry) = bars.colorbar_entry() { + f(entry)?; + } + } + des::Series::BarsGroup(bars_group) => { + for bs in bars_group.series() { + if let Some(entry) = bs.colorbar_entry() { + f(entry)?; + } } } } @@ -1038,18 +1090,18 @@ fn y_side_matches_out_legend_pos(side: des::axis::Side, legend_pos: des::plot::L } } -fn x_side_matches_colorbar_pos(side: des::axis::Side, pos: colorbar::Pos) -> bool { +fn x_side_matches_colorbar_pos(side: des::axis::Side, pos: des::colorbar::Pos) -> bool { match (side, pos) { - (des::axis::Side::Main, colorbar::Pos::Bottom) => true, - (des::axis::Side::Opposite, colorbar::Pos::Top) => true, + (des::axis::Side::Main, des::colorbar::Pos::Bottom) => true, + (des::axis::Side::Opposite, des::colorbar::Pos::Top) => true, _ => false, } } -fn y_side_matches_colorbar_pos(side: des::axis::Side, pos: colorbar::Pos) -> bool { +fn y_side_matches_colorbar_pos(side: des::axis::Side, pos: des::colorbar::Pos) -> bool { match (side, pos) { - (des::axis::Side::Main, colorbar::Pos::Left) => true, - (des::axis::Side::Opposite, colorbar::Pos::Right) => true, + (des::axis::Side::Main, des::colorbar::Pos::Left) => true, + (des::axis::Side::Opposite, des::colorbar::Pos::Right) => true, _ => false, } } diff --git a/src/drawing/series.rs b/src/drawing/series.rs index 062d1ff7..c0789375 100644 --- a/src/drawing/series.rs +++ b/src/drawing/series.rs @@ -15,29 +15,101 @@ use crate::{Style, data, des, geom, render, style}; /// trait implemented by series, or any other item that /// has to populate the legend pub trait SeriesExt { - fn legend_entry(&self) -> Option>; + fn legend_entries(&self, data_source: &D) -> Result + where + D: data::Source + ?Sized; + fn colorbar_entry(&self) -> Option> { None } } impl SeriesExt for des::series::Line { - fn legend_entry(&self) -> Option> { - self.name().map(|n| legend::Entry { - label: n.as_ref(), - txt_props: None, - shape: legend::ShapeRef::Line(self.stroke(), self.marker()), - }) + fn legend_entries(&self, _data_source: &D) -> Result + where + D: data::Source + ?Sized, + { + Ok(self + .name() + .map(|n| { + legend::Entry { + label: n.to_string(), + txt_props: None, + shape: legend::Shape::Line(self.stroke().clone(), self.marker().cloned()), + } + .into() + }) + .unwrap_or_default()) } } impl SeriesExt for des::series::Scatter { - fn legend_entry(&self) -> Option> { - self.name().map(|n| legend::Entry { - label: n.as_ref(), - txt_props: None, - shape: legend::ShapeRef::Marker(self.marker()), - }) + fn legend_entries(&self, data_source: &D) -> Result + where + D: data::Source + ?Sized, + { + if self.color_cats_to_legend() { + if let Some((color_col, cmap)) = self.color_data() { + match cmap { + des::cmap::ColorMap::Auto + | des::cmap::ColorMap::Cat(des::cmap::CatColorMap::Auto) => { + let col = get_column(color_col, data_source)?; + if let Some(col) = col.str() { + let cats: super::Categories = col.into(); + let entries = cats + .into_iter() + .enumerate() + .map(|(i, cat)| { + let color: style::series::Color = + style::series::IndexColor(i).into(); + legend::Entry { + label: cat, + txt_props: None, + shape: legend::Shape::Marker( + self.marker().clone().with_color(color), + ), + } + }) + .collect(); + return Ok(legend::Entries::Multi(entries)); + } + } + des::cmap::ColorMap::Cat(des::cmap::CatColorMap::Strings(cmap)) => { + let col = get_column(color_col, data_source)?; + if let Some(col) = col.str() { + let cats: super::Categories = col.into(); + let entries = cats + .into_iter() + .filter_map(|cat| { + let color = cmap.get(&cat)?; + Some(legend::Entry { + label: cat, + txt_props: None, + shape: legend::Shape::Marker( + self.marker().clone().with_color(*color), + ), + }) + }) + .collect(); + return Ok(legend::Entries::Multi(entries)); + } + } + _ => (), + } + } + } + + Ok(self + .name() + .map(|n| { + legend::Entry { + label: n.to_string(), + txt_props: None, + shape: legend::Shape::Marker(self.marker().clone()), + } + .into() + }) + .unwrap_or_default()) } fn colorbar_entry(&self) -> Option> { @@ -50,46 +122,82 @@ impl SeriesExt for des::series::Scatter { } impl SeriesExt for des::series::Area { - fn legend_entry(&self) -> Option> { - self.name().map(|n| legend::Entry { - label: n.as_ref(), - txt_props: None, - shape: legend::ShapeRef::AreaRect { - fill: Some(self.fill()), - y1_stroke: self.y1_stroke(), - y2_stroke: self.y2_stroke(), - }, - }) + fn legend_entries(&self, _data_source: &D) -> Result + where + D: data::Source + ?Sized, + { + Ok(self + .name() + .map(|n| { + legend::Entry { + label: n.to_string(), + txt_props: None, + shape: legend::Shape::AreaRect { + fill: Some(self.fill().clone()), + y1_stroke: self.y1_stroke().cloned(), + y2_stroke: self.y2_stroke().cloned(), + }, + } + .into() + }) + .unwrap_or_default()) } } impl SeriesExt for des::series::Histogram { - fn legend_entry(&self) -> Option> { - self.name().map(|n| legend::Entry { - label: n.as_ref(), - txt_props: None, - shape: legend::ShapeRef::Rect(Some(self.fill()), self.stroke()), - }) + fn legend_entries(&self, _data_source: &D) -> Result + where + D: data::Source + ?Sized, + { + Ok(self + .name() + .map(|n| { + legend::Entry { + label: n.to_string(), + txt_props: None, + shape: legend::Shape::Rect(Some(self.fill().clone()), self.stroke().cloned()), + } + .into() + }) + .unwrap_or_default()) } } impl SeriesExt for des::series::Bars { - fn legend_entry(&self) -> Option> { - self.name().map(|n| legend::Entry { - label: n.as_ref(), - txt_props: None, - shape: legend::ShapeRef::Rect(Some(self.fill()), self.stroke()), - }) + fn legend_entries(&self, _data_source: &D) -> Result + where + D: data::Source + ?Sized, + { + Ok(self + .name() + .map(|n| { + legend::Entry { + label: n.to_string(), + txt_props: None, + shape: legend::Shape::Rect(Some(self.fill().clone()), self.stroke().cloned()), + } + .into() + }) + .unwrap_or_default()) } } impl SeriesExt for des::series::BarSeries { - fn legend_entry(&self) -> Option> { - self.name().map(|n| legend::Entry { - label: n.as_ref(), - txt_props: None, - shape: legend::ShapeRef::Rect(Some(self.fill()), self.outline()), - }) + fn legend_entries(&self, _data_source: &D) -> Result + where + D: data::Source + ?Sized, + { + Ok(self + .name() + .map(|n| { + legend::Entry { + label: n.to_string(), + txt_props: None, + shape: legend::Shape::Rect(Some(self.fill().clone()), self.outline().cloned()), + } + .into() + }) + .unwrap_or_default()) } } diff --git a/src/sd/series.rs b/src/sd/series.rs index d62eb97d..13e9248f 100644 --- a/src/sd/series.rs +++ b/src/sd/series.rs @@ -454,6 +454,10 @@ impl serde::Serialize for series::Scatter { } } + if self.color_cats_to_legend() { + state.serialize_entry("colorCatsToLegend", &true)?; + } + state.end() } } @@ -478,6 +482,7 @@ where "name" => name: Option, "xAxis" => x_axis: Option, "yAxis" => y_axis: Option, + "colorCatsToLegend" => color_cats_to_legend: Option, } let mut scatter = series::Scatter::new(x_data, y_data); @@ -505,6 +510,9 @@ where if let Some(y_axis) = y_axis { scatter = scatter.with_y_axis(y_axis); } + if color_cats_to_legend == Some(true) { + scatter = scatter.with_color_cats_to_legend(); + } Ok(scatter) } diff --git a/tests/figs/series/color-cats-to-legend.json b/tests/figs/series/color-cats-to-legend.json new file mode 100644 index 00000000..f2ff1bd9 --- /dev/null +++ b/tests/figs/series/color-cats-to-legend.json @@ -0,0 +1,13 @@ +{ + "size": [400, 300], + "plot": { + "series": { + "type": "scatter", + "x": [1, 2, 3, 4, 5], + "y": [1, 4, 9, 16, 25], + "colors": ["cat1", "cat2", "cat1", "cat3", "cat2"], + "colorCatsToLegend": true + }, + "legend": "auto" + } +} \ No newline at end of file diff --git a/tests/refs/series/color-cats-to-legend.png b/tests/refs/series/color-cats-to-legend.png new file mode 100644 index 0000000000000000000000000000000000000000..2ca7aaf43b4f708c4ac2dc77d57656661ed5b5e8 GIT binary patch literal 13990 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 literal 0 HcmV?d00001 diff --git a/tests/refs/series/color-cats-to-legend.svg b/tests/refs/series/color-cats-to-legend.svg new file mode 100644 index 00000000..d1e7013d --- /dev/null +++ b/tests/refs/series/color-cats-to-legend.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/src/tests/series.rs b/tests/src/tests/series.rs index e2f9a492..e85ff561 100644 --- a/tests/src/tests/series.rs +++ b/tests/src/tests/series.rs @@ -1,5 +1,5 @@ use plotive::des::cmap; -use plotive::{data, des, style}; +use plotive::{Style, data, des, style}; use crate::tests::fig_small; use crate::{TestHarness, assert_fig_eq_ref}; @@ -203,3 +203,9 @@ fn series_area_double_legend() { assert_fig_eq_ref!(&fig, "series/area-double-legend"); } + +#[test] +fn series_color_cats_to_legend() { + let fig: des::Figure = crate::json_figure("series/color-cats-to-legend"); + assert_fig_eq_ref!(&fig, "series/color-cats-to-legend", &Style::light()); +} From 29275c2659c2e003870cff4a9adb6aac07930d3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Thebault?= Date: Mon, 27 Jul 2026 09:11:27 +0200 Subject: [PATCH 2/3] reorder legend tests --- .../scatter-color-cats.json} | 2 +- .../area-double.png} | Bin .../area-double.svg} | 0 .../bottom.png => legend/pos-bottom.png} | Bin .../bottom.svg => legend/pos-bottom.svg} | 0 .../pos-in_bottom.png} | Bin .../pos-in_bottom.svg} | 0 .../pos-in_bottom_left.png} | Bin .../pos-in_bottom_left.svg} | 0 .../pos-in_bottom_right.png} | Bin .../pos-in_bottom_right.svg} | 0 .../in_left.png => legend/pos-in_left.png} | Bin .../in_left.svg => legend/pos-in_left.svg} | 0 .../in_right.png => legend/pos-in_right.png} | Bin .../in_right.svg => legend/pos-in_right.svg} | 0 .../in_top.png => legend/pos-in_top.png} | Bin .../in_top.svg => legend/pos-in_top.svg} | 0 .../pos-in_top_left.png} | Bin .../pos-in_top_left.svg} | 0 .../pos-in_top_right.png} | Bin .../pos-in_top_right.svg} | 0 .../left.png => legend/pos-left.png} | Bin .../left.svg => legend/pos-left.svg} | 0 .../right.png => legend/pos-right.png} | Bin .../right.svg => legend/pos-right.svg} | 0 .../top.png => legend/pos-top.png} | Bin .../top.svg => legend/pos-top.svg} | 0 .../scatter-color-cats.png} | Bin .../scatter-color-cats.svg} | 0 tests/src/tests/legend.rs | 72 ++++++++++++++---- tests/src/tests/series.rs | 44 +---------- 31 files changed, 60 insertions(+), 58 deletions(-) rename tests/figs/{series/color-cats-to-legend.json => legend/scatter-color-cats.json} (95%) rename tests/refs/{series/area-double-legend.png => legend/area-double.png} (100%) rename tests/refs/{series/area-double-legend.svg => legend/area-double.svg} (100%) rename tests/refs/{legend-pos/bottom.png => legend/pos-bottom.png} (100%) rename tests/refs/{legend-pos/bottom.svg => legend/pos-bottom.svg} (100%) rename tests/refs/{legend-pos/in_bottom.png => legend/pos-in_bottom.png} (100%) rename tests/refs/{legend-pos/in_bottom.svg => legend/pos-in_bottom.svg} (100%) rename tests/refs/{legend-pos/in_bottom_left.png => legend/pos-in_bottom_left.png} (100%) rename tests/refs/{legend-pos/in_bottom_left.svg => legend/pos-in_bottom_left.svg} (100%) rename tests/refs/{legend-pos/in_bottom_right.png => legend/pos-in_bottom_right.png} (100%) rename tests/refs/{legend-pos/in_bottom_right.svg => legend/pos-in_bottom_right.svg} (100%) rename tests/refs/{legend-pos/in_left.png => legend/pos-in_left.png} (100%) rename tests/refs/{legend-pos/in_left.svg => legend/pos-in_left.svg} (100%) rename tests/refs/{legend-pos/in_right.png => legend/pos-in_right.png} (100%) rename tests/refs/{legend-pos/in_right.svg => legend/pos-in_right.svg} (100%) rename tests/refs/{legend-pos/in_top.png => legend/pos-in_top.png} (100%) rename tests/refs/{legend-pos/in_top.svg => legend/pos-in_top.svg} (100%) rename tests/refs/{legend-pos/in_top_left.png => legend/pos-in_top_left.png} (100%) rename tests/refs/{legend-pos/in_top_left.svg => legend/pos-in_top_left.svg} (100%) rename tests/refs/{legend-pos/in_top_right.png => legend/pos-in_top_right.png} (100%) rename tests/refs/{legend-pos/in_top_right.svg => legend/pos-in_top_right.svg} (100%) rename tests/refs/{legend-pos/left.png => legend/pos-left.png} (100%) rename tests/refs/{legend-pos/left.svg => legend/pos-left.svg} (100%) rename tests/refs/{legend-pos/right.png => legend/pos-right.png} (100%) rename tests/refs/{legend-pos/right.svg => legend/pos-right.svg} (100%) rename tests/refs/{legend-pos/top.png => legend/pos-top.png} (100%) rename tests/refs/{legend-pos/top.svg => legend/pos-top.svg} (100%) rename tests/refs/{series/color-cats-to-legend.png => legend/scatter-color-cats.png} (100%) rename tests/refs/{series/color-cats-to-legend.svg => legend/scatter-color-cats.svg} (100%) diff --git a/tests/figs/series/color-cats-to-legend.json b/tests/figs/legend/scatter-color-cats.json similarity index 95% rename from tests/figs/series/color-cats-to-legend.json rename to tests/figs/legend/scatter-color-cats.json index f2ff1bd9..21a50ea7 100644 --- a/tests/figs/series/color-cats-to-legend.json +++ b/tests/figs/legend/scatter-color-cats.json @@ -10,4 +10,4 @@ }, "legend": "auto" } -} \ No newline at end of file +} diff --git a/tests/refs/series/area-double-legend.png b/tests/refs/legend/area-double.png similarity index 100% rename from tests/refs/series/area-double-legend.png rename to tests/refs/legend/area-double.png diff --git a/tests/refs/series/area-double-legend.svg b/tests/refs/legend/area-double.svg similarity index 100% rename from tests/refs/series/area-double-legend.svg rename to tests/refs/legend/area-double.svg diff --git a/tests/refs/legend-pos/bottom.png b/tests/refs/legend/pos-bottom.png similarity index 100% rename from tests/refs/legend-pos/bottom.png rename to tests/refs/legend/pos-bottom.png diff --git a/tests/refs/legend-pos/bottom.svg b/tests/refs/legend/pos-bottom.svg similarity index 100% rename from tests/refs/legend-pos/bottom.svg rename to tests/refs/legend/pos-bottom.svg diff --git a/tests/refs/legend-pos/in_bottom.png b/tests/refs/legend/pos-in_bottom.png similarity index 100% rename from tests/refs/legend-pos/in_bottom.png rename to tests/refs/legend/pos-in_bottom.png diff --git a/tests/refs/legend-pos/in_bottom.svg b/tests/refs/legend/pos-in_bottom.svg similarity index 100% rename from tests/refs/legend-pos/in_bottom.svg rename to tests/refs/legend/pos-in_bottom.svg diff --git a/tests/refs/legend-pos/in_bottom_left.png b/tests/refs/legend/pos-in_bottom_left.png similarity index 100% rename from tests/refs/legend-pos/in_bottom_left.png rename to tests/refs/legend/pos-in_bottom_left.png diff --git a/tests/refs/legend-pos/in_bottom_left.svg b/tests/refs/legend/pos-in_bottom_left.svg similarity index 100% rename from tests/refs/legend-pos/in_bottom_left.svg rename to tests/refs/legend/pos-in_bottom_left.svg diff --git a/tests/refs/legend-pos/in_bottom_right.png b/tests/refs/legend/pos-in_bottom_right.png similarity index 100% rename from tests/refs/legend-pos/in_bottom_right.png rename to tests/refs/legend/pos-in_bottom_right.png diff --git a/tests/refs/legend-pos/in_bottom_right.svg b/tests/refs/legend/pos-in_bottom_right.svg similarity index 100% rename from tests/refs/legend-pos/in_bottom_right.svg rename to tests/refs/legend/pos-in_bottom_right.svg diff --git a/tests/refs/legend-pos/in_left.png b/tests/refs/legend/pos-in_left.png similarity index 100% rename from tests/refs/legend-pos/in_left.png rename to tests/refs/legend/pos-in_left.png diff --git a/tests/refs/legend-pos/in_left.svg b/tests/refs/legend/pos-in_left.svg similarity index 100% rename from tests/refs/legend-pos/in_left.svg rename to tests/refs/legend/pos-in_left.svg diff --git a/tests/refs/legend-pos/in_right.png b/tests/refs/legend/pos-in_right.png similarity index 100% rename from tests/refs/legend-pos/in_right.png rename to tests/refs/legend/pos-in_right.png diff --git a/tests/refs/legend-pos/in_right.svg b/tests/refs/legend/pos-in_right.svg similarity index 100% rename from tests/refs/legend-pos/in_right.svg rename to tests/refs/legend/pos-in_right.svg diff --git a/tests/refs/legend-pos/in_top.png b/tests/refs/legend/pos-in_top.png similarity index 100% rename from tests/refs/legend-pos/in_top.png rename to tests/refs/legend/pos-in_top.png diff --git a/tests/refs/legend-pos/in_top.svg b/tests/refs/legend/pos-in_top.svg similarity index 100% rename from tests/refs/legend-pos/in_top.svg rename to tests/refs/legend/pos-in_top.svg diff --git a/tests/refs/legend-pos/in_top_left.png b/tests/refs/legend/pos-in_top_left.png similarity index 100% rename from tests/refs/legend-pos/in_top_left.png rename to tests/refs/legend/pos-in_top_left.png diff --git a/tests/refs/legend-pos/in_top_left.svg b/tests/refs/legend/pos-in_top_left.svg similarity index 100% rename from tests/refs/legend-pos/in_top_left.svg rename to tests/refs/legend/pos-in_top_left.svg diff --git a/tests/refs/legend-pos/in_top_right.png b/tests/refs/legend/pos-in_top_right.png similarity index 100% rename from tests/refs/legend-pos/in_top_right.png rename to tests/refs/legend/pos-in_top_right.png diff --git a/tests/refs/legend-pos/in_top_right.svg b/tests/refs/legend/pos-in_top_right.svg similarity index 100% rename from tests/refs/legend-pos/in_top_right.svg rename to tests/refs/legend/pos-in_top_right.svg diff --git a/tests/refs/legend-pos/left.png b/tests/refs/legend/pos-left.png similarity index 100% rename from tests/refs/legend-pos/left.png rename to tests/refs/legend/pos-left.png diff --git a/tests/refs/legend-pos/left.svg b/tests/refs/legend/pos-left.svg similarity index 100% rename from tests/refs/legend-pos/left.svg rename to tests/refs/legend/pos-left.svg diff --git a/tests/refs/legend-pos/right.png b/tests/refs/legend/pos-right.png similarity index 100% rename from tests/refs/legend-pos/right.png rename to tests/refs/legend/pos-right.png diff --git a/tests/refs/legend-pos/right.svg b/tests/refs/legend/pos-right.svg similarity index 100% rename from tests/refs/legend-pos/right.svg rename to tests/refs/legend/pos-right.svg diff --git a/tests/refs/legend-pos/top.png b/tests/refs/legend/pos-top.png similarity index 100% rename from tests/refs/legend-pos/top.png rename to tests/refs/legend/pos-top.png diff --git a/tests/refs/legend-pos/top.svg b/tests/refs/legend/pos-top.svg similarity index 100% rename from tests/refs/legend-pos/top.svg rename to tests/refs/legend/pos-top.svg diff --git a/tests/refs/series/color-cats-to-legend.png b/tests/refs/legend/scatter-color-cats.png similarity index 100% rename from tests/refs/series/color-cats-to-legend.png rename to tests/refs/legend/scatter-color-cats.png diff --git a/tests/refs/series/color-cats-to-legend.svg b/tests/refs/legend/scatter-color-cats.svg similarity index 100% rename from tests/refs/series/color-cats-to-legend.svg rename to tests/refs/legend/scatter-color-cats.svg diff --git a/tests/src/tests/legend.rs b/tests/src/tests/legend.rs index 79404e60..11196866 100644 --- a/tests/src/tests/legend.rs +++ b/tests/src/tests/legend.rs @@ -1,4 +1,4 @@ -use plotive::des; +use plotive::{Style, des, style}; use super::{fig_small, line}; use crate::{TestHarness, assert_fig_eq_ref}; @@ -9,7 +9,7 @@ fn legend_pos_default() { let plot = des::Plot::new(vec![series]).with_legend(Default::default()); let fig = fig_small(plot); - assert_fig_eq_ref!(&fig, "legend-pos/bottom"); + assert_fig_eq_ref!(&fig, "legend/pos-bottom"); } #[test] @@ -18,7 +18,7 @@ fn legend_pos_top() { let plot = des::Plot::new(vec![series]).with_legend(des::plot::LegendPos::OutTop.into()); let fig = fig_small(plot); - assert_fig_eq_ref!(&fig, "legend-pos/top"); + assert_fig_eq_ref!(&fig, "legend/pos-top"); } #[test] @@ -27,7 +27,7 @@ fn legend_pos_right() { let plot = des::Plot::new(vec![series]).with_legend(des::plot::LegendPos::OutRight.into()); let fig = fig_small(plot); - assert_fig_eq_ref!(&fig, "legend-pos/right"); + assert_fig_eq_ref!(&fig, "legend/pos-right"); } #[test] @@ -36,7 +36,7 @@ fn legend_pos_bottom() { let plot = des::Plot::new(vec![series]).with_legend(des::plot::LegendPos::OutBottom.into()); let fig = fig_small(plot); - assert_fig_eq_ref!(&fig, "legend-pos/bottom"); + assert_fig_eq_ref!(&fig, "legend/pos-bottom"); } #[test] @@ -45,7 +45,7 @@ fn legend_pos_left() { let plot = des::Plot::new(vec![series]).with_legend(des::plot::LegendPos::OutLeft.into()); let fig = fig_small(plot); - assert_fig_eq_ref!(&fig, "legend-pos/left"); + assert_fig_eq_ref!(&fig, "legend/pos-left"); } #[test] @@ -54,7 +54,7 @@ fn legend_pos_in_top_left() { let plot = des::Plot::new(vec![series]).with_legend(des::plot::LegendPos::InTopLeft.into()); let fig = fig_small(plot); - assert_fig_eq_ref!(&fig, "legend-pos/in_top_left"); + assert_fig_eq_ref!(&fig, "legend/pos-in_top_left"); } #[test] @@ -63,7 +63,7 @@ fn legend_pos_in_top() { let plot = des::Plot::new(vec![series]).with_legend(des::plot::LegendPos::InTop.into()); let fig = fig_small(plot); - assert_fig_eq_ref!(&fig, "legend-pos/in_top"); + assert_fig_eq_ref!(&fig, "legend/pos-in_top"); } #[test] @@ -72,7 +72,7 @@ fn legend_pos_in_top_right() { let plot = des::Plot::new(vec![series]).with_legend(des::plot::LegendPos::InTopRight.into()); let fig = fig_small(plot); - assert_fig_eq_ref!(&fig, "legend-pos/in_top_right"); + assert_fig_eq_ref!(&fig, "legend/pos-in_top_right"); } #[test] @@ -81,7 +81,7 @@ fn legend_pos_in_right() { let plot = des::Plot::new(vec![series]).with_legend(des::plot::LegendPos::InRight.into()); let fig = fig_small(plot); - assert_fig_eq_ref!(&fig, "legend-pos/in_right"); + assert_fig_eq_ref!(&fig, "legend/pos-in_right"); } #[test] @@ -90,7 +90,7 @@ fn legend_pos_in_bottom_right() { let plot = des::Plot::new(vec![series]).with_legend(des::plot::LegendPos::InBottomRight.into()); let fig = fig_small(plot); - assert_fig_eq_ref!(&fig, "legend-pos/in_bottom_right"); + assert_fig_eq_ref!(&fig, "legend/pos-in_bottom_right"); } #[test] @@ -99,7 +99,7 @@ fn legend_pos_in_bottom() { let plot = des::Plot::new(vec![series]).with_legend(des::plot::LegendPos::InBottom.into()); let fig = fig_small(plot); - assert_fig_eq_ref!(&fig, "legend-pos/in_bottom"); + assert_fig_eq_ref!(&fig, "legend/pos-in_bottom"); } #[test] @@ -108,7 +108,7 @@ fn legend_pos_in_bottom_left() { let plot = des::Plot::new(vec![series]).with_legend(des::plot::LegendPos::InBottomLeft.into()); let fig = fig_small(plot); - assert_fig_eq_ref!(&fig, "legend-pos/in_bottom_left"); + assert_fig_eq_ref!(&fig, "legend/pos-in_bottom_left"); } #[test] @@ -117,5 +117,49 @@ fn legend_pos_in_left() { let plot = des::Plot::new(vec![series]).with_legend(des::plot::LegendPos::InLeft.into()); let fig = fig_small(plot); - assert_fig_eq_ref!(&fig, "legend-pos/in_left"); + assert_fig_eq_ref!(&fig, "legend/pos-in_left"); } + +#[test] +fn legend_area_double() { + let x = vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0]; + let y1 = vec![10.0, 15.0, 8.0, 6.0, 12.0, 10.0]; + let y2 = vec![4.0, 9.0, 2.0, 0.0, 6.0, 4.0]; + + let fill1 = style::series::Fill::solid(plotive::Rgba8::from_hex(b"#888").into()); + let fill2 = style::series::Fill::solid(plotive::Rgba8::from_hex(b"#444").into()); + let stroke = style::series::Stroke::solid(plotive::Rgba8::from_hex(b"#000").into()); + + let plot = des::Plot::new(vec![ + des::series::Area::new( + des::data_inline(x.clone()), + des::data_inline(y1.clone()), + des::data_inline(y2.clone()).into(), + ) + .with_name("area1") + .with_fill(fill1) + .with_y1_stroke(stroke.clone()) + .with_y2_stroke(stroke.clone()) + .into(), + des::series::Area::new( + des::data_inline(x.clone()), + des::data_inline(y2.clone()), + Default::default(), + ) + .with_name("area2") + .with_fill(fill2) + .with_y1_stroke(stroke.clone()) + .with_y2_stroke(stroke.clone()) + .into(), + ]); + let fig = fig_small(plot).with_legend(Default::default()); + + assert_fig_eq_ref!(&fig, "legend/area-double"); +} + + +#[test] +fn legend_scatter_color_cats() { + let fig: des::Figure = crate::json_figure("legend/scatter-color-cats"); + assert_fig_eq_ref!(&fig, "legend/scatter-color-cats", &Style::light()); +} \ No newline at end of file diff --git a/tests/src/tests/series.rs b/tests/src/tests/series.rs index e85ff561..010d171c 100644 --- a/tests/src/tests/series.rs +++ b/tests/src/tests/series.rs @@ -1,5 +1,5 @@ use plotive::des::cmap; -use plotive::{Style, data, des, style}; +use plotive::{data, des, style}; use crate::tests::fig_small; use crate::{TestHarness, assert_fig_eq_ref}; @@ -167,45 +167,3 @@ fn series_area_double() { assert_fig_eq_ref!(&fig, "series/area-double"); } -#[test] -fn series_area_double_legend() { - let x = vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0]; - let y1 = vec![10.0, 15.0, 8.0, 6.0, 12.0, 10.0]; - let y2 = vec![4.0, 9.0, 2.0, 0.0, 6.0, 4.0]; - - let fill1 = style::series::Fill::solid(plotive::Rgba8::from_hex(b"#888").into()); - let fill2 = style::series::Fill::solid(plotive::Rgba8::from_hex(b"#444").into()); - let stroke = style::series::Stroke::solid(plotive::Rgba8::from_hex(b"#000").into()); - - let plot = des::Plot::new(vec![ - des::series::Area::new( - des::data_inline(x.clone()), - des::data_inline(y1.clone()), - des::data_inline(y2.clone()).into(), - ) - .with_name("area1") - .with_fill(fill1) - .with_y1_stroke(stroke.clone()) - .with_y2_stroke(stroke.clone()) - .into(), - des::series::Area::new( - des::data_inline(x.clone()), - des::data_inline(y2.clone()), - Default::default(), - ) - .with_name("area2") - .with_fill(fill2) - .with_y1_stroke(stroke.clone()) - .with_y2_stroke(stroke.clone()) - .into(), - ]); - let fig = fig_small(plot).with_legend(Default::default()); - - assert_fig_eq_ref!(&fig, "series/area-double-legend"); -} - -#[test] -fn series_color_cats_to_legend() { - let fig: des::Figure = crate::json_figure("series/color-cats-to-legend"); - assert_fig_eq_ref!(&fig, "series/color-cats-to-legend", &Style::light()); -} From 74154c12a580c2993e9cb9a9b0220b4b7200fa36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Thebault?= Date: Fri, 31 Jul 2026 11:42:59 +0200 Subject: [PATCH 3/3] formatting --- src/drawing/figure.rs | 2 +- src/drawing/plot.rs | 6 +++++- tests/src/tests/legend.rs | 3 +-- tests/src/tests/series.rs | 1 - 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/drawing/figure.rs b/src/drawing/figure.rs index a936a424..5e4aee7d 100644 --- a/src/drawing/figure.rs +++ b/src/drawing/figure.rs @@ -135,7 +135,7 @@ where rect.width(), self.fontdb(), ); - + let data_source = self.data_source(); for plot in fig.plots().iter().filter_map(|p| p) { let mut idx = 0; diff --git a/src/drawing/plot.rs b/src/drawing/plot.rs index 101f28fe..e0d8b1a0 100644 --- a/src/drawing/plot.rs +++ b/src/drawing/plot.rs @@ -970,7 +970,11 @@ where } } -pub fn for_each_legend_entries(plot: &des::Plot, data_source: &D, mut f: F) -> Result<(), Error> +pub fn for_each_legend_entries( + plot: &des::Plot, + data_source: &D, + mut f: F, +) -> Result<(), Error> where D: data::Source + ?Sized, F: FnMut(legend::Entries) -> Result<(), Error>, diff --git a/tests/src/tests/legend.rs b/tests/src/tests/legend.rs index 11196866..8e8e0b05 100644 --- a/tests/src/tests/legend.rs +++ b/tests/src/tests/legend.rs @@ -157,9 +157,8 @@ fn legend_area_double() { assert_fig_eq_ref!(&fig, "legend/area-double"); } - #[test] fn legend_scatter_color_cats() { let fig: des::Figure = crate::json_figure("legend/scatter-color-cats"); assert_fig_eq_ref!(&fig, "legend/scatter-color-cats", &Style::light()); -} \ No newline at end of file +} diff --git a/tests/src/tests/series.rs b/tests/src/tests/series.rs index 010d171c..6bdea838 100644 --- a/tests/src/tests/series.rs +++ b/tests/src/tests/series.rs @@ -166,4 +166,3 @@ fn series_area_double() { assert_fig_eq_ref!(&fig, "series/area-double"); } -