Merge PR #151 from MarekSpodBiedry: Charts: a missing varyColors varies single-series bar colours by point (#143)

# Conflicts:
#	crates/pptx/src/read/chart.rs
#	crates/render/src/chart.rs
#	crates/render/src/tests.rs
This commit is contained in:
Brandon Thomas
2026-10-14 15:13:35 -04:00
3 changed files with 51 additions and 4 deletions
+21 -3
View File
@@ -195,9 +195,8 @@ fn chart_within(root: &El, mut budget: usize) -> Option<Chart> {
"surfaceChart" | "surface3DChart" => ChartType::Surface,
_ => continue,
};
if let Some(v) = g.child_val("varyColors") {
c.vary_colors = v == "1" || v == "true";
}
// A missing <c:varyColors> means "vary by point" (the schema default is true), as PowerPoint draws it.
c.vary_colors = g.child_val("varyColors").is_none_or(|v| v == "1" || v == "true");
// A bar group without c:gapWidth / c:overlap takes the schema's defaults (150 %, 0 %),
// not the 219 % / -27 % PowerPoint writes into its new charts.
let bar_group = g.is("barChart") || g.is("bar3DChart");
@@ -618,4 +617,23 @@ mod tests {
let t = "<c:title><c:tx><c:rich><a:p><a:r><a:t>Mine</a:t></a:r></a:p></c:rich></c:tx></c:title>";
assert_eq!(chart(r#"<c:autoTitleDeleted val="0"/>"#, t, 1).title.as_deref(), Some("Mine"));
}
const NS_CHART: &str = "http://schemas.openxmlformats.org/drawingml/2006/chart";
fn vary_xml(vary: &str) -> String {
let ser = r#"<c:ser><c:idx val="0"/><c:order val="0"/></c:ser>"#;
let bar = format!(r#"<c:barChart><c:barDir val="col"/><c:grouping val="clustered"/>{vary}{ser}</c:barChart>"#);
format!(r#"<c:chartSpace xmlns:c="{NS_CHART}"><c:chart><c:plotArea>{bar}</c:plotArea></c:chart></c:chartSpace>"#)
}
#[test]
fn missing_vary_colors_means_vary_by_point() {
assert!(read_chart_xml(vary_xml("").as_bytes()).is_some_and(|c| c.vary_colors));
}
#[test]
fn explicit_vary_colors_is_respected() {
assert!(read_chart_xml(vary_xml(r#"<c:varyColors val="0"/>"#).as_bytes()).is_some_and(|c| !c.vary_colors));
assert!(read_chart_xml(vary_xml(r#"<c:varyColors val="1"/>"#).as_bytes()).is_some_and(|c| c.vary_colors));
}
}
+13 -1
View File
@@ -44,6 +44,18 @@ fn point_fill(ctx: &Ctx, c: &Chart, i: usize, s: &deckcraft_model::chart::Series
}
}
/// Fill of one bar: a `<c:dPt>` fill wins, and a single-series chart that varies colours by point uses one accent per point
/// (not waterfalls, which are never read from a `<c:barChart>`).
fn bar_fill(ctx: &Ctx, c: &Chart, si: usize, pi: usize, s: &deckcraft_model::chart::Series) -> Rgba {
if s.point_fills.iter().any(|(k, _)| *k == pi) {
return point_fill(ctx, c, si, s, pi);
}
if c.vary_colors && c.series.len() == 1 && s.fill.is_none() && c.kind != ChartType::Waterfall {
return series_color(ctx, c, pi);
}
series_fill(ctx, c, si, Some(s))
}
fn polyline(pts: &[Point]) -> BezPath {
let mut p = BezPath::new();
for (k, q) in pts.iter().enumerate() {
@@ -619,7 +631,7 @@ fn draw_inner(ctx: &mut RenderContext, rctx: &Ctx, c: &Chart, m: Affine, w: f64,
};
series_color(rctx, c, slot)
} else {
point_fill(rctx, c, *si, s, i)
bar_fill(rctx, c, *si, i, s)
};
ctx.set_transform(m);
ctx.set_paint(color(fill, 1.0));
+17
View File
@@ -2400,3 +2400,20 @@ fn render_an_emf_file_to_look_at() {
let img = render_slide(&p, 0, &RenderOpts { scale: 1.0, ..Default::default() });
let _ = std::fs::write(out, img.to_png());
}
#[test]
fn single_series_column_chart_varies_colours_by_point() {
let render = |vary: bool| {
let mut c = Chart::sample(ChartType::Column);
c.series.truncate(1);
c.vary_colors = vary;
let sh = Shape {
id: ShapeId(800),
xfrm: Some(Xfrm::new(50.0, 50.0, 400.0, 300.0)),
kind: ShapeKind::Chart(Box::new(c)),
..Default::default()
};
render_slide(&deck_with(vec![sh]), 0, &RenderOpts { scale: 1.0, ..Default::default() }).pixels
};
assert_ne!(render(true), render(false));
}