From: Ben Pfaff Date: Sat, 25 Jul 2026 15:18:36 +0000 (-0700) Subject: implement opacity and improve fitting to frames X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=6b563bc5fdbb35993907b4cc76e248646083dd69;p=pspp implement opacity and improve fitting to frames --- diff --git a/rust/pspp/src/spv/read/graph.rs b/rust/pspp/src/spv/read/graph.rs index dabe3ab6c9..8d5b29f4dc 100644 --- a/rust/pspp/src/spv/read/graph.rs +++ b/rust/pspp/src/spv/read/graph.rs @@ -565,34 +565,6 @@ impl Visualization { let styles = self.decode_styles(); let frames = self.decode_frames(); - /* - for frame in &frames.frames { - if let Some(left) = frame.position[Axis2::X].start() - && let Some(width) = frame.position[Axis2::X].length() - && let Some(top) = frame.position[Axis2::Y].start() - && let Some(height) = frame.position[Axis2::Y].length() - { - document.append( - Rectangle::new() - .set("x", left) - .set("y", top) - .set("width", width) - .set("height", height) - .set("fill", "none") - .set("stroke", "black") - .add(Title::new(frame.id.as_deref().unwrap_or(""))), - ); - println!( - "{:?}: {}..{}, {}..{}", - frame.id, - left, - frame.position[Axis2::X].end().unwrap(), - top, - frame.position[Axis2::Y].end().unwrap() - ); - } - }*/ - let mut document = svg::Document::new() .set("xmlns:c", "http://www.example.org/") .set("style", "font-family: \"Sans Serif\""); @@ -601,11 +573,16 @@ impl Visualization { && let Some(top) = frames.frames[0].position[Axis2::Y].start() && let Some(height) = frames.frames[0].position[Axis2::Y].length() { - document.assign("viewBox", (left, top, left + width + 75.0, top + height)); + document.assign("viewBox", (left, top, left + width, top + height)); document.assign("width", width); document.assign("height", height); } + let outline_frames = false; + if outline_frames { + document.append(frames.to_svg()); + } + let font_metrics = FontMetrics::new(); for frame in &frames.frames { if let Some(label_frame) = frame.frame_type.as_label_frame() @@ -688,8 +665,7 @@ impl Visualization { //dbg!(&axis_domains); let font_metrics = FontMetrics::new(); - let font_height = font_metrics.measure("0")[Axis2::Y]; - let mut rect = graph_frame.rect().unwrap(); + let rect = graph_frame.rect().unwrap(); let cell_style = graph .cell_style @@ -698,23 +674,6 @@ impl Visualization { .cloned() .unwrap_or_default(); - let y_axis = axis_domains[Axis3::Y].ticks(); - let y_axis_text_width = y_axis - .iter() - .map(|(_y, label)| OrderedFloat(font_metrics.measure(label)[Axis2::X])) - .max() - .map(|OrderedFloat(f)| f) - .unwrap_or_default(); - rect.0[Axis2::X].0 += 10.0 + y_axis_text_width; - if axis_domains[Axis3::Y].label.is_some() { - rect.0[Axis2::X].0 += font_height; - } - - rect.0[Axis2::Y].1 -= font_height + 10.0; - if axis_domains[Axis3::X].label.is_some() { - rect.0[Axis2::Y].1 -= font_height; - } - let facet_levels = graph.facet_levels(); let faceting = graph .faceting() @@ -804,11 +763,7 @@ impl Visualization { .set("fill", cell_style.fill(&mut patterns)) .set( "stroke", - cell_style - .color2 - .unwrap_or(Color::BLACK) - .display_css() - .to_string(), + cell_style.color2(Color::BLACK).display_css().to_string(), ), ); } @@ -938,8 +893,8 @@ impl Visualization { style.color2 = Some(PALETTE[index % PALETTE.len()]); } - let color = style.color.unwrap_or(Color::BLACK); - let color2 = style.color2.unwrap_or(Color::BLACK); + let color = style.color(Color::BLACK); + let color2 = style.color2(Color::BLACK); let pattern = style.fill(&mut patterns); fn points( @@ -1682,7 +1637,6 @@ impl<'a> AxisDomain<'a> { } } fn ticks(&self) -> Vec<(f64, String)> { - dbg!(&self.domain); let mut ticks = Vec::new(); match &self.domain { Domain::Interval { @@ -1727,7 +1681,7 @@ impl<'a> AxisDomain<'a> { } _ => (), }; - dbg!(ticks) + ticks } } @@ -5068,7 +5022,7 @@ impl<'a> FacetHeadings<'a> { let ticks_height = ticks .iter() .map(|(_, s)| { - font_metrics.measure(s)[if rotate_ticks { Axis2::Y } else { Axis2::Y }] + font_metrics.measure(s)[if rotate_ticks { Axis2::X } else { Axis2::Y }] }) .max_by(|a, b| a.total_cmp(b)) .unwrap_or_default(); @@ -5133,6 +5087,14 @@ impl<'a> FacetHeadings<'a> { for (frac, label) in ticks { let x = facet_span.frac(*frac); let label = if label.is_empty() { "x" } else { &label }; + /*group.append( + Circle::new() + .set("cx", x) + .set("cy", y) + .set("r", "5px") + .set("fill", "none") + .set("stroke", "red"), + );*/ if rotation == 0.0 { group.append( svg::node::element::Text::new(label) @@ -5147,7 +5109,7 @@ impl<'a> FacetHeadings<'a> { .set("x", x) .set("y", y) .set("dominant-baseline", "middle") - .set("text-anchor", "right") + .set("text-anchor", "end") .set("transform", format!("rotate(90 {x} {y})")), ); } @@ -5266,7 +5228,7 @@ impl<'a> FacetHeadings<'a> { &mut group, &span, if rotation != 0.0 { - y + (*height - 20.0) + y + (*height - 10.0) } else { y }, diff --git a/rust/pspp/src/spv/read/graph/frames.rs b/rust/pspp/src/spv/read/graph/frames.rs index 9bfdad3a51..0f47ef5682 100644 --- a/rust/pspp/src/spv/read/graph/frames.rs +++ b/rust/pspp/src/spv/read/graph/frames.rs @@ -2,6 +2,10 @@ use std::{fmt::Debug, ops::Range}; use enum_iterator::all; use enum_map::{Enum, EnumMap, enum_map}; +use svg::{ + Node as _, + node::element::{Group, Rectangle, Title}, +}; use crate::{ output::pivot::Axis2, @@ -383,6 +387,30 @@ impl<'a> Frames<'a> { _ => None, }) } + + pub fn to_svg(&self) -> Group { + let mut group = Group::new(); + for frame in &self.frames { + if let Some(left) = frame.position[Axis2::X].start() + && let Some(width) = frame.position[Axis2::X].length() + && let Some(top) = frame.position[Axis2::Y].start() + && let Some(height) = frame.position[Axis2::Y].length() + { + let mut rectangle = Rectangle::new() + .set("x", left) + .set("y", top) + .set("width", width) + .set("height", height) + .set("fill", "none") + .set("stroke", "red"); + if let Some(id) = &frame.id { + rectangle.append(Title::new(id)); + } + group.append(rectangle); + } + } + group + } } /// Position. diff --git a/rust/pspp/src/spv/read/graph/style.rs b/rust/pspp/src/spv/read/graph/style.rs index 44993c5e22..df50175cd0 100644 --- a/rust/pspp/src/spv/read/graph/style.rs +++ b/rust/pspp/src/spv/read/graph/style.rs @@ -75,9 +75,19 @@ pub struct Style { #[serde(rename = "@color")] pub color: Option, + /// The opacity of `color`, in the range 0 to 1, overriding the color's + /// alpha channel. + #[serde(rename = "@opacity")] + pub opacity: Option, + #[serde(rename = "@color2")] pub color2: Option, + /// The opacity of `color2`, in the range 0 to 1, overriding the color's + /// alpha channel. + #[serde(rename = "@opacity2")] + pub opacity2: Option, + #[serde(rename = "@visible")] pub visible: Option, @@ -158,7 +168,9 @@ impl Style { Self { id: overrides.id.clone().or(self.id), color: overrides.color.or(self.color), + opacity: overrides.opacity.or(self.opacity), color2: overrides.color2.or(self.color2), + opacity2: overrides.opacity2.or(self.opacity2), visible: overrides.visible.or(self.visible), padding: overrides.padding.or(self.padding), pattern: overrides.pattern.or(self.pattern), @@ -204,10 +216,28 @@ impl Style { pub fn fill(&self, patterns: &mut Patterns) -> String { patterns.insert(ColoredPattern { index: self.pattern.unwrap_or_default(), - color: self.color.unwrap_or(Color::BLACK), - color2: self.color2.unwrap_or(Color::WHITE), + color: self.color(Color::BLACK), + color2: self.color2(Color::WHITE), }) } + + fn color_with_opacity(color: Color, opacity: Option) -> Color { + if let Some(opacity) = opacity + && (0.0..=1.0).contains(&opacity) + { + color.with_alpha((opacity * 255.0) as u8) + } else { + color + } + } + + pub fn color(&self, default: Color) -> Color { + Self::color_with_opacity(self.color.unwrap_or(default), self.opacity) + } + + pub fn color2(&self, default: Color) -> Color { + Self::color_with_opacity(self.color2.unwrap_or(default), self.opacity2) + } } #[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize)] diff --git a/rust/pspp/src/spv/testdata/graph1.expected.svg b/rust/pspp/src/spv/testdata/graph1.expected.svg index f8c099091e..0a7ed7da0a 100644 --- a/rust/pspp/src/spv/testdata/graph1.expected.svg +++ b/rust/pspp/src/spv/testdata/graph1.expected.svg @@ -1,4 +1,4 @@ - + Group Name @@ -6,71 +6,71 @@ Group Name ROC Curve - + - + - + - - + + - - - - - + + + + + 0 - + 0,2 - + 0,4 - + 0,6 - + 0,8 - + 1 - + X Axis - - + + Y Axis - + 0 - + 0,2 - + 0,4 - + 0,6 - + 0,8 - + 1 - - - - - - + + + + + + diff --git a/rust/pspp/src/spv/testdata/graph2.expected.svg b/rust/pspp/src/spv/testdata/graph2.expected.svg index f1020c90ce..c343ab61ab 100644 --- a/rust/pspp/src/spv/testdata/graph2.expected.svg +++ b/rust/pspp/src/spv/testdata/graph2.expected.svg @@ -1,155 +1,155 @@ - + Title - + - + - - - - - + + + + + 10 - + 15 - + V2 - - + + 0 - + 10 - + 20 - + 30 - + 40 - + 50 - + 60 - - - - - - - + + + + + + + - + - + - - - - - - + + + + + + 0.575 - + 0.58 - + 0.585 - + V1 - - + + 0 - + 10 - + 20 - + 30 - + 40 - + 50 - - - - - - + + + + + + - + - + - - - - - - + + + + + + 30 - + 35 - + 40 - + V3 - - + + 0 - + 10 - + 20 - + 30 - - - - + + + + diff --git a/rust/pspp/src/spv/testdata/graph3.expected.svg b/rust/pspp/src/spv/testdata/graph3.expected.svg index 8e6c3dcb0d..aac1b3ece2 100644 --- a/rust/pspp/src/spv/testdata/graph3.expected.svg +++ b/rust/pspp/src/spv/testdata/graph3.expected.svg @@ -1,14 +1,14 @@ - + - + - - + + - + - - + + @@ -20,65 +20,65 @@ - - - - - + + + + + Value0 - + Value1 - + V0 - - + + Percent - + .0% - + 10.0% - + 20.0% - + 30.0% - + .0% - + 10.0% - + 20.0% - + 30.0% - - - - - - - - + + + + + + + + - + Graph0 - + no - + yes diff --git a/rust/pspp/src/spv/testdata/graph4.expected.svg b/rust/pspp/src/spv/testdata/graph4.expected.svg index ee53b57d3f..7b15361785 100644 --- a/rust/pspp/src/spv/testdata/graph4.expected.svg +++ b/rust/pspp/src/spv/testdata/graph4.expected.svg @@ -1,27 +1,27 @@ - + - + - + - + - + - + - - - + + + - + Graph0 - + no - + yes diff --git a/rust/pspp/src/spv/testdata/graph5.expected.svg b/rust/pspp/src/spv/testdata/graph5.expected.svg index 4afd31b926..006738198c 100644 --- a/rust/pspp/src/spv/testdata/graph5.expected.svg +++ b/rust/pspp/src/spv/testdata/graph5.expected.svg @@ -1,201 +1,201 @@ - + - + - - + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - - + + Variable 11 - + V11-1 - + V11-2 - - - - - - + + + + + + 101.10 - + 102.10 - + 101.10 - + 102.10 - + Variable 4 - - + + Frequency - + 0 - + 1 - + 2 - + 3 - + 4 - + 5 - + 6 - + 0 - + 1 - + 2 - + 3 - + 4 - + 5 - + 6 - + 0 - + 1 - + 2 - + 3 - + 4 - + 5 - + 6 - + 0 - + 1 - + 2 - + 3 - + 4 - + 5 - + 6 - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + Variable 12 - + V12-4 - + V12-5 - + V12-6 - + V12-7