From c887d0e6e72a3706f7c88ac50bdbb19a0365340a Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Sat, 27 Jun 2026 15:59:45 -0700 Subject: [PATCH] work on ticks --- rust/pspp/src/spv/read/graph.rs | 159 ++++++++++++++------------ rust/pspp/src/spv/testdata/graph5.xml | 2 +- 2 files changed, 85 insertions(+), 76 deletions(-) diff --git a/rust/pspp/src/spv/read/graph.rs b/rust/pspp/src/spv/read/graph.rs index bf54877891..32536e699a 100644 --- a/rust/pspp/src/spv/read/graph.rs +++ b/rust/pspp/src/spv/read/graph.rs @@ -512,6 +512,7 @@ impl Visualization { axis_domains[axis] = AxisDomain { domain, formatter: formatter.unwrap_or_else(|| Formatter::default_for_locale(locale)), + axis: axis2, label: dbg!(axis2.and_then(|axis| axis.label.as_ref())), major_ticks: axis2.and_then(|axis| axis.major_ticks.as_ref()), }; @@ -733,6 +734,7 @@ impl Visualization { let heading = FacetHeadings::new( Side::default_for_axis(axis), &faceting[axis], + &axis_domains[axis.into()], rect.0[axis].length() - heading_heights[!axis], graph.axis_dimension(axis), &mut font_metrics, @@ -788,77 +790,6 @@ impl Visualization { format!("rotate(90 {x} {y}) translate({x} {y})") }); - for (x_index, _x_facet) in x_facets.clone().enumerate() { - { - let rect = inner.subrect(x_index, nx_facets, ny_facets - 1, ny_facets); - // X axis. - let y = rect.0[Axis2::Y].1; - let x_span = rect.0[Axis2::X]; - /*graph_svg.append( - Line::new() - .set("x1", x_span.0) - .set("x2", x_span.1) - .set("y1", y) - .set("y2", y) - .set("stroke", "black"), - );*/ - for (frac, label) in axis_domains[Axis3::X].ticks() { - let svg_x = rect.0[Axis2::X].frac(frac); - graph_svg = graph_svg - .add( - Line::new() - .set("x1", svg_x) - .set("x2", svg_x) - .set("y1", y) - .set("y2", y + 10.0) - .set("stroke", "black"), - ) - .add( - svg::node::element::Text::new(dbg!(&label)) - .set("x", svg_x) - .set("y", y + 10.0) - .set("dominant-baseline", "hanging") - .set("text-anchor", "middle"), - ); - } - } - } - - for (y_index, _y_facet) in y_facets.clone().enumerate() { - let subrect = inner.subrect(0, nx_facets, y_index, ny_facets); - // Y axis. - let x = subrect.0[Axis2::X].0; - let y_span = subrect.0[Axis2::Y]; - /*graph_svg.append( - Line::new() - .set("y1", y_span.0) - .set("y2", y_span.1) - .set("x1", x) - .set("x2", x) - .set("stroke", "black") - .set("c:description", format!("Y axis {y_index}")), - );*/ - for (frac, label) in axis_domains[Axis3::Y].ticks() { - let svg_y = subrect.0[Axis2::Y].frac(1.0 - frac); - graph_svg = graph_svg - .add( - Line::new() - .set("y1", svg_y) - .set("y2", svg_y) - .set("x1", x) - .set("x2", x - 10.0) - .set("stroke", "black"), - ) - .add( - svg::node::element::Text::new(label) - .set("y", svg_y) - .set("x", x - 10.0) - .set("dominant-baseline", "middle") - .set("text-anchor", "end"), - ); - } - } - for (x_index, x_facet) in x_facets.enumerate() { for (y_index, y_facet) in y_facets.clone().enumerate() { let rect = inner.subrect(x_index, nx_facets, y_index, ny_facets); @@ -1226,8 +1157,8 @@ impl Visualization { graph_svg.append(bottom); graph_svg.append(left); graph_svg.append(right); - //graph_svg.append(rect.as_svg().set("fill", "none").set("stroke", "red")); - //graph_svg.append(inner.as_svg().set("fill", "none").set("stroke", "green")); + graph_svg.append(rect.as_svg().set("fill", "none").set("stroke", "red")); + graph_svg.append(inner.as_svg().set("fill", "none").set("stroke", "green")); Ok(graph_svg) } } @@ -1732,6 +1663,7 @@ impl Variable for DerivedVariable { struct AxisDomain<'a> { domain: Domain, formatter: Formatter, + axis: Option<&'a Axis>, label: Option<&'a Label>, major_ticks: Option<&'a MajorTicks>, } @@ -1741,6 +1673,7 @@ impl<'a> AxisDomain<'a> { Self { domain: Domain::default(), formatter: Formatter::default_for_locale(locale), + axis: None, label: None, major_ticks: None, } @@ -4970,21 +4903,33 @@ struct FacetHeadings<'a> { /// Width, fixed by the caller. width: f64, - /// Axes and their labels. + /// Axis labels (e.g. X-axis or Y-axis label). /// /// Ordinarily there is at most one axis on each side; if there happens to /// be more than one then these go from innermost to outermost. `axes` /// appear inside `facets` if both are present on the same side. axes: Vec>, + /// Hierarchical facet labels (e.g. the value for each facet, stacked if + /// there is more than one level of facets on an axis). + /// /// Per-row heights on the default (left or top) side and the opposite side, /// calculated based on `axis_facets` and `width`. /// /// From innermost to outermost. facets: Vec>, + /// Axis tick marks. + ticks: Option<(Side, Vec<(f64, String)>, f64)>, + /// Number of facets. n_facets: usize, + + /// [AxisDomain] for X or Y axis. + /// + /// Currently there's always exactly one of these for an axis, although + /// perhaps there should be up to 2. + axis_domain: &'a AxisDomain<'a>, } struct FacetAxisHeight<'a> { @@ -5024,11 +4969,19 @@ impl<'a> FacetHeadings<'a> { .filter(|row_height| row_height.side == side) .map(|row_height| row_height.height()) .sum::() + + if let Some((ticks_side, _, height)) = &self.ticks + && *ticks_side == side + { + *height + } else { + 0.0 + } } fn new( default_side: Side, facets: &'a [Facet<'a>], + axis_domain: &'a AxisDomain<'a>, width: f64, dimension: Option<&'a Dimension>, font_metrics: &mut FontMetrics, @@ -5099,9 +5052,22 @@ impl<'a> FacetHeadings<'a> { } }) .collect(); + let ticks = axis_domain.axis.map(|axis| { + ( + if axis.opposite { + !default_side + } else { + default_side + }, + axis_domain.ticks(), + 10.0, + ) + }); Self { width, axes, + axis_domain, + ticks, facets, n_facets, } @@ -5111,6 +5077,43 @@ impl<'a> FacetHeadings<'a> { let span = Span(0.0, self.width).subdivide(self.n_facets, 5.0); let mut group = Group::new(); + fn render_ticks( + this: &FacetHeadings, + ticks: &[(f64, String)], + group: &mut Group, + span: &DividedSpan, + y: f64, + ) { + for facet_span in (0..this.n_facets).map(|index| span.nth(index)) { + for (frac, label) in ticks { + let x = facet_span.frac(*frac); + group.append( + Line::new() + .set("x1", x) + .set("x2", x) + .set("y1", y) + .set("y2", y + 10.0) + .set("stroke", "black"), + ); + group.append( + svg::node::element::Text::new(dbg!(label)) + .set("x", x) + .set("y", y + 10.0) + .set("dominant-baseline", "hanging") + .set("text-anchor", "middle"), + ); + } + } + } + + let mut y = 0.0; + if let Some((ticks_side, ticks, height)) = &self.ticks + && side == *ticks_side + && side == Side::Bottom + { + render_ticks(self, ticks, &mut group, &span, y); + y += height; + } /* group.append( Rect(enum_map! { @@ -5121,7 +5124,6 @@ impl<'a> FacetHeadings<'a> { .set("fill", "none") .set("stroke", "red"), );*/ - let mut y = 0.0; for axis in self.axes.iter().filter(|axis| axis.side == side) { if dbg!(axis.axis.combine_axis_labels) { group.append( @@ -5205,6 +5207,13 @@ impl<'a> FacetHeadings<'a> { } y += height.facet_label_height; } + if let Some((ticks_side, ticks, height)) = &self.ticks + && *ticks_side == side + && side == Side::Top + { + render_ticks(self, ticks, &mut group, &span, y); + y += height; + } group } } diff --git a/rust/pspp/src/spv/testdata/graph5.xml b/rust/pspp/src/spv/testdata/graph5.xml index cdb2240507..4f06ca6e33 100644 --- a/rust/pspp/src/spv/testdata/graph5.xml +++ b/rust/pspp/src/spv/testdata/graph5.xml @@ -46,7 +46,7 @@ - + -- 2.47.3