diff --git a/crates/model/src/chart.rs b/crates/model/src/chart.rs index 98214f88..4492576d 100644 --- a/crates/model/src/chart.rs +++ b/crates/model/src/chart.rs @@ -609,27 +609,44 @@ impl Chart { return (!secondary && self.kind != ChartType::Combo).then_some((0.0, 0.0)); } let (mut lo, mut hi) = (f64::INFINITY, f64::NEG_INFINITY); - let n = ser.iter().map(|s| s.values.len()).max().unwrap_or(0); + let has_stacked_or_percent = ser.iter().any(|s| { + let k = self.series_kind(s); + k.stacked() || k.percent() + }); + let mut percent = false; - for i in 0..n { - let (mut p, mut m, mut stacked) = (0.0, 0.0, false); + if has_stacked_or_percent { + let n = ser.iter().map(|s| s.values.len()).max().unwrap_or(0); + let budget = (ser.len() * n).min(10_000_000); + let n_clamped = if ser.is_empty() { 0 } else { n.min(budget / ser.len()) }; + + for i in 0..n_clamped { + let (mut p, mut m, mut stacked) = (0.0, 0.0, false); + for s in &ser { + let k = self.series_kind(s); + let Some(v) = s.values.get(i).copied().flatten().filter(|v| v.is_finite()) else { continue }; + if k.percent() { + percent = true; + } else if k.stacked() { + stacked = true; + if v >= 0.0 { p += v } else { m += v } + } else { + hi = hi.max(v); + lo = lo.min(v); + } + } + if stacked { + hi = hi.max(p); + lo = lo.min(m); + } + } + } else { for s in &ser { - let k = self.series_kind(s); - let Some(v) = s.values.get(i).copied().flatten().filter(|v| v.is_finite()) else { continue }; - if k.percent() { - percent = true; - } else if k.stacked() { - stacked = true; - if v >= 0.0 { p += v } else { m += v } - } else { + for v in s.values.iter().flatten().copied().filter(|v| v.is_finite()) { hi = hi.max(v); lo = lo.min(v); } } - if stacked { - hi = hi.max(p); - lo = lo.min(m); - } } if !percent { for s in &ser { diff --git a/crates/render/src/chart.rs b/crates/render/src/chart.rs index ac185780..3a3f06bf 100644 --- a/crates/render/src/chart.rs +++ b/crates/render/src/chart.rs @@ -696,6 +696,7 @@ fn draw_inner(ctx: &mut RenderContext, rctx: &Ctx, c: &Chart, m: Affine, w: f64, let stacked = kind.stacked(); let area_kind = matches!(kind, ChartType::Area | ChartType::StackedArea | ChartType::FilledRadar); let col = if area_kind { series_fill(rctx, c, si, Some(s)) } else { series_line(rctx, c, si, s) }; + let series_stats = s.error_bars.as_ref().map(|_| deckcraft_model::chart::SeriesStats::of(&s.values)); let mut pts = vec![]; for i in 0..ncat { let Some(v) = s.values.get(i).copied().flatten() else { continue }; @@ -719,7 +720,7 @@ fn draw_inner(ctx: &mut RenderContext, rctx: &Ctx, c: &Chart, m: Affine, w: f64, } _ => plot.x0 + band * (slot(i) + 0.5), }; - if let Some((lo, hi)) = s.error_bars.as_ref().and_then(|e| e.range(&s.values, i, v)) { + if let Some((lo, hi)) = s.error_bars.as_ref().and_then(|e| series_stats.as_ref().and_then(|st| e.range_in(st, i, v))) { err_marks.push((x, to_axis(sec, lo), to_axis(sec, hi), base * 0.4, s.error_bars.as_ref().is_some_and(|e| e.end_cap))); } pts.push(Point::new(x, to_axis(sec, v)));