]> pintos-os.org Git - pspp/commitdiff
label alignment
authorBen Pfaff <blp@cs.stanford.edu>
Tue, 28 Jul 2026 00:09:26 +0000 (17:09 -0700)
committerBen Pfaff <blp@cs.stanford.edu>
Tue, 28 Jul 2026 00:09:26 +0000 (17:09 -0700)
rust/pspp/src/spv/read/graph.rs
rust/pspp/src/spv/read/graph/style.rs
rust/pspp/src/spv/testdata/graph1.expected.svg
rust/pspp/src/spv/testdata/graph2.expected.svg

index bf033c0e19ba9746645bd02da75ce03d5ec1d5e0..5984568fab33557b6395a5c9aabd75c9dd094188 100644 (file)
@@ -22,7 +22,6 @@ use std::{
     fmt::Debug,
     io::{Cursor, ErrorKind},
     iter::{repeat, zip},
-    marker::PhantomData,
     mem::take,
     num::NonZeroUsize,
     ops::{Not, Range},
@@ -55,7 +54,10 @@ use crate::{
     output::{
         SpvGraphMembers, SpvInfo, SpvMembers,
         drivers::cairo::{pt_to_xr, xr_to_pt},
-        pivot::{Axis2, Axis3, Length, ParseLengthError, look::Color},
+        pivot::{
+            Axis2, Axis3, Length, ParseLengthError,
+            look::{Color, HorzAlign},
+        },
     },
     spv::{
         legacy_bin::{DataSeries, DataSource, DataSources, LegacyBin, LegacyBinWarning},
@@ -74,30 +76,6 @@ mod patterns;
 mod style;
 mod symbols;
 
-#[derive(Debug)]
-struct Ref<T> {
-    references: String,
-    _phantom: PhantomData<T>,
-}
-
-impl<T> Ref<T> {
-    fn get<'a>(&self, table: &HashMap<&str, &'a T>) -> Option<&'a T> {
-        table.get(self.references.as_str()).map(|v| &**v)
-    }
-}
-
-impl<'de, T> Deserialize<'de> for Ref<T> {
-    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
-    where
-        D: serde::Deserializer<'de>,
-    {
-        Ok(Self {
-            references: String::deserialize(deserializer)?,
-            _phantom: PhantomData,
-        })
-    }
-}
-
 #[derive(Clone, Debug, Default)]
 struct Map(HashMap<Datum<String>, String>);
 
@@ -158,9 +136,9 @@ pub struct Visualization {
     #[serde(rename = "@lang")]
     lang: Option<String>,
 
-    /// Base style for the graph.
+    /// The `id` for the base [Style] for the graph.
     #[serde(rename = "@style")]
-    _style: Option<Ref<Style>>,
+    _style: Option<String>,
 
     /// Title or caption.
     #[serde(rename = "@description")]
@@ -587,18 +565,31 @@ impl Visualization {
         for frame in &frames.frames {
             if let Some(label_frame) = frame.frame_type.as_label_frame()
                 && let Some(left) = frame.position[Axis2::X].start()
-                && let Some(_width) = frame.position[Axis2::X].length()
+                && let Some(right) = frame.position[Axis2::X].end()
                 && let Some(top) = frame.position[Axis2::Y].start()
                 && let Some(_height) = frame.position[Axis2::Y].length()
-                && let Some(text) = label_frame.text()
+                && let Some(label) = &label_frame.label
             {
+                let text = label.text().unwrap_or_default();
                 let _metrics = font_metrics.measure(&text);
+                let text_alignment = label
+                    .style
+                    .as_deref()
+                    .and_then(|style| styles.get(style))
+                    .and_then(|style| style.label_horz_align())
+                    .unwrap_or(HorzAlign::Center);
+                let (x, text_anchor) = match text_alignment {
+                    HorzAlign::Left | HorzAlign::Decimal { .. } => (left, "start"),
+                    HorzAlign::Right => (right, "end"),
+                    HorzAlign::Center => (left.midpoint(right), "middle"),
+                };
                 document.append(
                     svg::node::element::Text::new(text)
                         .with_id(&label_frame.id)
-                        .set("x", left)
+                        .set("x", x)
                         .set("y", top)
                         .set("dominant-baseline", "hanging")
+                        .set("text-anchor", text_anchor)
                         .set("font-family", "sans-serif"),
                 ); /*
                 document.append(
@@ -1471,10 +1462,11 @@ struct SourceVariable {
     #[serde(default)] // XXX not sure this is the correct default
     categorical: bool,
 
-    /// A variable whose string values correspond one-to-one with the values of
-    /// this variable and are suitable as value labels.
+    /// The `id` for a [SourceVariable] whose string values correspond
+    /// one-to-one with the values of this variable and are suitable as value
+    /// labels.
     #[serde(rename = "@labelVariable")]
-    label_variable: Option<Ref<SourceVariable>>,
+    label_variable: Option<String>,
 
     format: Option<Format>,
     string_format: Option<StringFormat>,
@@ -1500,7 +1492,7 @@ impl Variable for SourceVariable {
         _warn: &mut dyn FnMut(GraphWarning),
     ) -> bool {
         let label_series = if let Some(label_variable) = &self.label_variable {
-            if let Some(label_series) = series.get(label_variable.references.as_str()) {
+            if let Some(label_series) = series.get(label_variable.as_str()) {
                 Some(label_series)
             } else {
                 return false;
@@ -3634,8 +3626,9 @@ struct SetStyle {
     #[serde(rename = "@target")]
     target: String,
 
+    /// The `id` for a [Style].
     #[serde(rename = "@style")]
-    style: Ref<Style>,
+    style: String,
 }
 
 #[derive(Deserialize, Debug)]
@@ -3644,8 +3637,9 @@ struct SetFrameStyle {
     #[serde(rename = "@target")]
     target: String,
 
+    /// The `id` for a [Style].
     #[serde(rename = "@style")]
-    style: Ref<Style>,
+    style: String,
 }
 
 #[derive(Deserialize, Debug)]
@@ -3984,11 +3978,13 @@ struct Labeling {
     #[serde(default, rename = "@connectingLines")]
     connecting_lines: bool,
 
+    /// The `id` for a [Style].
     #[serde(rename = "@style")]
-    style: Option<Ref<Style>>,
+    style: Option<String>,
 
+    /// The `id` for a [Style].
     #[serde(rename = "@textFrameStyle")]
-    text_frame_style: Option<Ref<Style>>,
+    text_frame_style: Option<String>,
 
     #[serde(rename = "$value", default)]
     children: Vec<LabelingChild>,
@@ -4070,18 +4066,21 @@ struct Axis {
 #[derive(Deserialize, Debug)]
 #[serde(rename_all = "camelCase")]
 struct MajorTicks {
+    /// The `id` for a [Style].
     #[serde(rename = "@style")]
-    style: Ref<Style>,
+    style: String,
 }
 
 #[derive(Deserialize, Debug)]
 #[serde(rename_all = "camelCase")]
 struct Label {
+    /// The `id` for a [Style].
     #[serde(rename = "@style")]
-    style: Option<Ref<Style>>,
+    style: Option<String>,
 
+    /// The `id` for a [Style].
     #[serde(rename = "@textFrameStyle")]
-    text_frame_style: Option<Ref<Style>>,
+    text_frame_style: Option<String>,
 
     #[serde(rename = "@purpose")]
     purpose: Option<Purpose>,
index df50175cd0707405eb5da270c77377e8be649f46..50a0a17e705a75025d8214b8a105379ea3bbb5a2 100644 (file)
@@ -199,7 +199,7 @@ impl Style {
     }
 
     pub fn label_horz_align(&self) -> Option<HorzAlign> {
-        match self.text_alignment.unwrap_or_default() {
+        match self.text_alignment.unwrap_or(TextAlignment::Center) {
             TextAlignment::Left => Some(HorzAlign::Left),
             TextAlignment::Right => Some(HorzAlign::Right),
             TextAlignment::Center => Some(HorzAlign::Center),
index 0a7ed7da0a582c481f7632f571a8920532fc28f3..b9ae21e9dbf05ba7d866ca5e04413827c069c351 100644 (file)
@@ -1,8 +1,8 @@
 <svg height="500" style="font-family: &quot;Sans Serif&quot;" viewBox="0 0 625 500" width="625" xmlns="http://www.w3.org/2000/svg" xmlns:c="http://www.example.org/">
-<text dominant-baseline="hanging" font-family="sans-serif" id="frame_subtitle_1" x="0" y="28">
+<text dominant-baseline="hanging" font-family="sans-serif" id="frame_subtitle_1" text-anchor="middle" x="312.5" y="28">
 Group Name
 </text>
-<text dominant-baseline="hanging" font-family="sans-serif" id="frame_title_1" x="0" y="5">
+<text dominant-baseline="hanging" font-family="sans-serif" id="frame_title_1" text-anchor="middle" x="312.5" y="5">
 ROC Curve
 </text>
 <g id="frame_data_1">
index 64f75595b1769e207d1aee35a623d042fa0a12ac..7a28ebdb8aec5ce78e9238c225e0d799b63ca117 100644 (file)
@@ -1,5 +1,5 @@
 <svg height="168" style="font-family: &quot;Sans Serif&quot;" viewBox="0 0 504 168" width="504" xmlns="http://www.w3.org/2000/svg" xmlns:c="http://www.example.org/">
-<text dominant-baseline="hanging" font-family="sans-serif" id="frame_subtitle_1" x="0" y="0">
+<text dominant-baseline="hanging" font-family="sans-serif" id="frame_subtitle_1" text-anchor="middle" x="252" y="0">
 Title
 </text>
 <g id="graph_11559">