From c4a9f25754708d3c14dd4c701abb6f1378aea780 Mon Sep 17 00:00:00 2001 From: Nikita Rakitin Date: Wed, 14 Oct 2026 05:58:30 +0300 Subject: [PATCH] Charts: a chart frame with no width no longer panics --- crates/render/src/chart.rs | 6 ++--- crates/render/src/tests.rs | 48 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/crates/render/src/chart.rs b/crates/render/src/chart.rs index eb2b0a96..2b8763ca 100644 --- a/crates/render/src/chart.rs +++ b/crates/render/src/chart.rs @@ -264,9 +264,9 @@ pub fn draw(ctx: &mut RenderContext, rctx: &Ctx, c: &Chart, m: Affine, w: f64, h } else if c.y_hidden { base * 0.5 } else { - label_w(false, (min, max, step)).clamp(base * 1.5, area.width() * 0.3) + label_w(false, (min, max, step)).min(area.width() * 0.3).max(base * 1.5) }; - let gutter_r = second.filter(|_| !c.y2_hidden).map(|sc| label_w(true, sc).clamp(base * 1.5, area.width() * 0.3)).unwrap_or(0.0); + let gutter_r = second.filter(|_| !c.y2_hidden).map(|sc| label_w(true, sc).min(area.width() * 0.3).max(base * 1.5)).unwrap_or(0.0); let plot = if horiz { Rect::new(area.x0 + gutter, area.y0, area.x1, area.y1 - base * 1.6) } else { @@ -378,7 +378,7 @@ pub fn draw(ctx: &mut RenderContext, rctx: &Ctx, c: &Chart, m: Affine, w: f64, h ); } } - let zero = to_v(0.0f64.clamp(min, max)); + let zero = to_v(0.0f64.max(min).min(max)); if let Some(bk) = bar_kind { let bars: Vec<(usize, &deckcraft_model::chart::Series)> = c.series.iter().enumerate().filter(|(_, s)| c.series_kind(s).is_bar_like()).collect(); diff --git a/crates/render/src/tests.rs b/crates/render/src/tests.rs index 0de3d695..64e44670 100644 --- a/crates/render/src/tests.rs +++ b/crates/render/src/tests.rs @@ -1711,3 +1711,51 @@ fn east_asian_vertical_text_stands_ideographs_upright() { let (w, h) = ink(deckcraft_model::text::TextDir::Vertical); assert!(h > w * 3, "vert: stroke on its side {w}x{h}"); } + +#[test] +fn charts_in_degenerate_frames_draw_without_panicking() { + // A chart frame squashed to no width (cx="0" in a generated file) made the value labels' + // gutter clamp panic (its upper bound, a share of the width, fell below its lower bound). + // Every chart type and legend position must survive any frame size. + let p = Presentation::default(); + let rctx = Ctx::for_slide(&p, &p.slides[0]).unwrap(); + let kinds = [ + ChartType::Column, + ChartType::StackedColumn, + ChartType::PercentColumn, + ChartType::Bar, + ChartType::StackedBar, + ChartType::PercentBar, + ChartType::Line, + ChartType::LineMarkers, + ChartType::StackedLine, + ChartType::Area, + ChartType::StackedArea, + ChartType::Pie, + ChartType::Doughnut, + ChartType::Scatter, + ChartType::Bubble, + ChartType::Radar, + ChartType::FilledRadar, + ChartType::Stock, + ChartType::Surface, + ChartType::Histogram, + ChartType::Pareto, + ChartType::BoxWhisker, + ChartType::Waterfall, + ChartType::Funnel, + ChartType::Treemap, + ChartType::Sunburst, + ChartType::Combo, + ]; + let mut ctx = vello_cpu::RenderContext::new(64, 64); + for k in kinds { + for (w, h) in [(0.0, 403.0), (403.0, 0.0), (0.0, 0.0), (2.0, 400.0), (400.0, 2.0), (12.0, 12.0)] { + for legend in [None, Some("b"), Some("t"), Some("r"), Some("l"), Some("tr")] { + let mut c = Chart::sample(k); + c.legend = legend.map(String::from); + crate::chart::draw(&mut ctx, &rctx, &c, kurbo::Affine::IDENTITY, w, h); + } + } + } +}