Charts: draw, read and write value-axis minor gridlines

This commit is contained in:
Nikita Rakitin
2026-10-14 04:39:00 +03:00
parent 457406a7a5
commit aed386e269
5 changed files with 38 additions and 1 deletions
+3
View File
@@ -159,6 +159,8 @@ pub struct Chart {
pub title_size: Option<f64>,
pub data_labels: bool,
pub gridlines: bool,
/// `c:minorGridlines` on the value axis: five to a major unit, between the major ones.
pub minor_gridlines: bool,
pub axis_titles: (Option<String>, Option<String>),
/// Chart style number (1–48) and colour palette (`colorful1`, `monochrome3`…).
pub style: u32,
@@ -204,6 +206,7 @@ impl Default for Chart {
title_size: None,
data_labels: false,
gridlines: true,
minor_gridlines: false,
axis_titles: (None, None),
style: 2,
palette: "colorful1".into(),
+11
View File
@@ -266,6 +266,7 @@ pub(crate) fn chart_from(root: &El) -> Option<Chart> {
let cat_ax = pa.child("catAx").or_else(|| pa.child("dateAx")).or_else(|| pa.children_named("valAx").find(|a| !vertical(a)));
if let Some(v) = val_ax {
c.gridlines = v.child("majorGridlines").is_some();
c.minor_gridlines = v.child("minorGridlines").is_some();
let sc = v.child("scaling");
c.y_min = sc.and_then(|s| s.child_val("min")).and_then(|v| v.parse().ok()).filter(|v: &f64| v.is_finite());
c.y_max = sc.and_then(|s| s.child_val("max")).and_then(|v| v.parse().ok()).filter(|v: &f64| v.is_finite());
@@ -310,6 +311,16 @@ fn text_size(e: Option<&El>) -> Option<f64> {
mod tests {
use super::*;
#[test]
fn minor_gridlines_are_read_and_written_back() {
let mut c = Chart::sample(ChartType::Line);
c.minor_gridlines = true;
let back = read_chart_xml(&crate::write::chart::chart_xml(&c)).unwrap();
assert!(back.gridlines && back.minor_gridlines);
c.minor_gridlines = false;
assert!(!read_chart_xml(&crate::write::chart::chart_xml(&c)).unwrap().minor_gridlines);
}
#[test]
fn legend_position_and_overlay_are_read() {
let legend = r#"<c:legend><c:legendPos val="r"/><c:overlay val="1"/></c:legend>"#;
+3
View File
@@ -286,6 +286,9 @@ fn axes(w: &mut W, c: &Chart, g: Group) {
if c.gridlines {
w.empty0("c:majorGridlines");
}
if c.minor_gridlines {
w.empty0("c:minorGridlines");
}
if let Some(t) = &c.axis_titles.1 {
title(w, t);
}
+1 -1
View File
@@ -1,6 +1,6 @@
//! PPTX export: [`Presentation`] → package.
mod chart;
pub(crate) mod chart;
pub(crate) mod dml;
mod shapes;
mod theme;
+20
View File
@@ -282,6 +282,26 @@ pub fn draw(ctx: &mut RenderContext, rctx: &Ctx, c: &Chart, m: Affine, w: f64, h
(true, Some((min2, max2, _))) => plot.y1 - (v - min2) / (max2 - min2).max(1e-9) * plot.height(),
_ => to_v(v),
};
// Minor gridlines, under the major ones: five to a major unit.
if c.minor_gridlines {
let minor = step / 5.0;
let n = (((max - min) / minor.max(1e-12)).round() as i64).clamp(1, 500);
let mut g = BezPath::new();
for k in (0..=n).filter(|k| k % 5 != 0) {
let pos = to_v(min + k as f64 * minor);
if horiz {
g.move_to((pos, plot.y0));
g.line_to((pos, plot.y1));
} else {
g.move_to((plot.x0, pos));
g.line_to((plot.x1, pos));
}
}
ctx.set_transform(m);
ctx.set_paint(color(Rgba::rgb(0xF2, 0xF2, 0xF2), 1.0));
ctx.set_stroke(kurbo::Stroke::new(0.75));
ctx.stroke_path(&g);
}
// Gridlines and value labels.
for v in ticks((min, max, step)) {
let pos = to_v(v);