From f77ed98cdb47c39b592c6bbd2bb7d05a8585fc3f Mon Sep 17 00:00:00 2001 From: "Dylan M. Taylor" Date: Tue, 3 Nov 2026 22:56:56 -0500 Subject: [PATCH] chart: a series without values takes no category-span entries (#3300) --- crates/chart/src/render/cartesian.rs | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/crates/chart/src/render/cartesian.rs b/crates/chart/src/render/cartesian.rs index 05105dbf4..7fae90a9f 100644 --- a/crates/chart/src/render/cartesian.rs +++ b/crates/chart/src/render/cartesian.rs @@ -96,8 +96,11 @@ fn series_spans(data: &ChartData, fams: &[Fam], sec: &[bool], ncat: usize) -> Ve } else { None }; - let mut row = Vec::with_capacity(ncat); - for j in 0..ncat { + // A series without values has no spans: 255 empty series over 20,000 categories would + // otherwise fill ~5 million entries. + let n = if s.values.is_empty() { 0 } else { ncat }; + let mut row = Vec::with_capacity(n); + for j in 0..n { let v = s.values.get(j).copied().flatten(); let span = match (si.and_then(|p| stacks.get_mut(p)), fam) { (Some((_, pos, neg)), Fam::ColStack | Fam::Col100) => v.and_then(|v| { @@ -450,3 +453,18 @@ pub(super) fn scatter(ctx: &mut Ctx, chart: &Chart, data: &ChartData, area: Bx) } draw_trend_labels(ctx, plot, &trend_labels); } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn empty_series_get_no_span_entries() { + let empty = SeriesData::default(); + let full = SeriesData { values: vec![Some(1.0); 3], ..SeriesData::default() }; + let data = ChartData { series: vec![empty.clone(), full, empty], ..ChartData::default() }; + let fams = [Fam::Col; 3]; + let spans = series_spans(&data, &fams, &[false; 3], MAX_CATS); + assert_eq!(spans.iter().map(Vec::len).collect::>(), [0, MAX_CATS, 0]); + } +}