From dc2c9bb34b0e68b52ff2065b0452601462d423e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Thebault?= Date: Tue, 21 Apr 2026 21:11:49 +0200 Subject: [PATCH 1/2] histograms can rebuild bins with update_data --- src/drawing/series.rs | 73 ++++++++++++++++++++++++++++++------------- 1 file changed, 51 insertions(+), 22 deletions(-) diff --git a/src/drawing/series.rs b/src/drawing/series.rs index ed4d921..b987042 100644 --- a/src/drawing/series.rs +++ b/src/drawing/series.rs @@ -267,7 +267,7 @@ impl Series { } SeriesPlot::Scatter(sc) => sc.update_data(data_source, rect, cm), SeriesPlot::Histogram(hist) => { - hist.update_data(rect, cm); + hist.update_data(data_source, rect, cm); } SeriesPlot::Bars(bars) => { bars.update_data(data_source, rect, cm); @@ -717,7 +717,7 @@ impl Scatter { } } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq)] struct HistBin { /// Start and end of this bin range: (f64, f64), @@ -728,12 +728,16 @@ struct HistBin { #[derive(Debug, Clone)] struct Histogram { index: usize, + data_col: des::DataCol, + bin_count: u32, + density: bool, ab: (axis::NumBounds, axis::NumBounds), axes: (des::axis::Ref, des::axis::Ref), bins: Vec, path: Option, fill: style::series::Fill, line: Option, + updated_once: bool, } impl Histogram { @@ -745,15 +749,39 @@ impl Histogram { where D: data::Source + ?Sized, { - let mut bins = Vec::with_capacity(hist.bins() as usize); - - let col = get_column(hist.data(), data_source)?; + let data_col = hist.data().clone(); + let col = get_column(&data_col, data_source)?; let col = col.f64().ok_or(Error::InconsistentData( "Histogram data must be numeric".into(), ))?; let x_bounds = col.bounds().ok_or(Error::UnboundedAxis)?; - let width = x_bounds.span() / hist.bins() as f64; + let bins = Self::calc_bins(col, x_bounds, hist.bins(), hist.density())?; + + let mut y_bounds = axis::NumBounds::NAN; + for bin in bins.iter() { + y_bounds.add_sample(bin.value); + } + + Ok(Histogram { + index, + data_col, + bin_count: hist.bins(), + density: hist.density(), + ab: (x_bounds, y_bounds), + axes: (hist.x_axis().clone(), hist.y_axis().clone()), + bins, + path: None, + fill: hist.fill().clone(), + line: hist.line().cloned(), + updated_once: false, + }) + } + + fn calc_bins(col: &dyn data::F64Column, x_bounds: axis::NumBounds, bins: u32, density: bool) -> Result, Error> + { + let width = x_bounds.span() / bins as f64; + let mut bins = Vec::with_capacity(bins as usize); let mut val = x_bounds.start(); while val <= x_bounds.end() { bins.push(HistBin { @@ -763,7 +791,7 @@ impl Histogram { val += width; } - let samp_add = if hist.density() { + let samp_add = if density { 1.0 / (col.len_some() as f64 * width) } else { 1.0 @@ -776,23 +804,24 @@ impl Histogram { } } - let mut y_bounds = axis::NumBounds::NAN; - for bin in bins.iter() { - y_bounds.add_sample(bin.value); - } - - Ok(Histogram { - index, - ab: (x_bounds, y_bounds), - axes: (hist.x_axis().clone(), hist.y_axis().clone()), - bins, - path: None, - fill: hist.fill().clone(), - line: hist.line().cloned(), - }) + Ok(bins) } - fn update_data(&mut self, rect: &geom::Rect, cm: &CoordMapXy) { + fn update_data(&mut self, data_source: &D, rect: &geom::Rect, cm: &CoordMapXy) + where D: data::Source + ?Sized, + { + if !self.updated_once { + self.updated_once = true; + // no need to recalculate bins, as first call is made with the same data_source as prepare + } else { + let x_bounds = self.ab.0; + let col = get_column(&self.data_col, data_source).expect("TODO: error handling"); + let col = col.f64().expect("TODO: error handling"); + let bins = Self::calc_bins(col, x_bounds, self.bin_count, self.density).expect("TODO: error handling"); + + self.bins = bins; + } + let mut pb = geom::PathBuilder::new(); let mut x = rect.left() + cm.x.map_coord_num(self.bins[0].range.0); let mut y = rect.bottom() - cm.y.map_coord_num(0.0); From a6b248cc1cd41d72e3b846642e9f00db8e3e6654 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Thebault?= Date: Fri, 24 Apr 2026 08:16:03 +0200 Subject: [PATCH 2/2] cargo +nightly fmt --- src/drawing/series.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/drawing/series.rs b/src/drawing/series.rs index b987042..4850aa7 100644 --- a/src/drawing/series.rs +++ b/src/drawing/series.rs @@ -778,8 +778,12 @@ impl Histogram { }) } - fn calc_bins(col: &dyn data::F64Column, x_bounds: axis::NumBounds, bins: u32, density: bool) -> Result, Error> - { + fn calc_bins( + col: &dyn data::F64Column, + x_bounds: axis::NumBounds, + bins: u32, + density: bool, + ) -> Result, Error> { let width = x_bounds.span() / bins as f64; let mut bins = Vec::with_capacity(bins as usize); let mut val = x_bounds.start(); @@ -808,7 +812,8 @@ impl Histogram { } fn update_data(&mut self, data_source: &D, rect: &geom::Rect, cm: &CoordMapXy) - where D: data::Source + ?Sized, + where + D: data::Source + ?Sized, { if !self.updated_once { self.updated_once = true; @@ -817,7 +822,8 @@ impl Histogram { let x_bounds = self.ab.0; let col = get_column(&self.data_col, data_source).expect("TODO: error handling"); let col = col.f64().expect("TODO: error handling"); - let bins = Self::calc_bins(col, x_bounds, self.bin_count, self.density).expect("TODO: error handling"); + let bins = Self::calc_bins(col, x_bounds, self.bin_count, self.density) + .expect("TODO: error handling"); self.bins = bins; }