From 3e9723ef3f13ecea93fa5cc0f2ec06e0a59ce6f7 Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Thu, 7 May 2026 20:26:24 -0700 Subject: [PATCH] patterns work --- rust/pspp/src/output/pivot/look.rs | 8 ++- rust/pspp/src/spv/read/graph.rs | 18 ++++- rust/pspp/src/spv/read/graph/patterns.rs | 90 ++++++++++++++++++------ rust/pspp/src/spv/read/graph/style.rs | 2 +- 4 files changed, 92 insertions(+), 26 deletions(-) diff --git a/rust/pspp/src/output/pivot/look.rs b/rust/pspp/src/output/pivot/look.rs index a46676c4a0..80863fafc1 100644 --- a/rust/pspp/src/output/pivot/look.rs +++ b/rust/pspp/src/output/pivot/look.rs @@ -882,7 +882,7 @@ impl FontStyle { } /// Color used in [FontStyle]. -#[derive(Copy, Clone, PartialEq, Eq)] +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct Color { /// Alpha channel. /// @@ -948,6 +948,12 @@ impl Color { (self.r, self.g, self.b) } + /// Returns the red, green, blue, and alpha channels of this color, in that + /// order. + pub fn into_rgba(&self) -> (u8, u8, u8, u8) { + (self.r, self.g, self.b, self.alpha) + } + /// Returns 16-bit versions of the red, green, and blue channels of this /// color. pub fn into_rgb16(&self) -> (u16, u16, u16) { diff --git a/rust/pspp/src/spv/read/graph.rs b/rust/pspp/src/spv/read/graph.rs index 2cd3b32fb3..c3ed50f7a6 100644 --- a/rust/pspp/src/spv/read/graph.rs +++ b/rust/pspp/src/spv/read/graph.rs @@ -46,7 +46,7 @@ use svg::{ Document, Node, node::{ Comment, - element::{Circle, Group, Line, Path, Polygon, Rectangle, path::Data}, + element::{Circle, Definitions, Group, Line, Path, Polygon, Rectangle, path::Data}, }, }; @@ -64,6 +64,7 @@ use crate::{ read::graph::{ format::Formatter, frames::{Component, Frames, Position}, + patterns::Patterns, style::{Style, StyleCycle}, }, }, @@ -646,6 +647,7 @@ impl Visualization { let mut graph_svg = Group::new().with_id(&graph.id); let mut bin_widths = BTreeMap::new(); let mut axis_domains = EnumMap::::default(); + let mut patterns = Patterns::new(); self.pass( &graph, &domains, @@ -915,6 +917,7 @@ impl Visualization { let color = style.color.unwrap_or(Color::BLACK); let color2 = style.color2.unwrap_or(Color::BLACK); + let pattern = patterns.insert(style.pattern.unwrap_or_default(), color); fn points( rect: &Rect, @@ -978,7 +981,7 @@ impl Visualization { let y1 = rect.0[Axis2::Y].1; element_svg.append( Rectangle::new() - .set("fill", color.display_css().to_string()) + .set("fill", pattern.clone()) .set("stroke", color2.display_css().to_string()) .set("x", x.start) .set("y", y0) @@ -1106,7 +1109,7 @@ impl Visualization { } element_svg.append( Polygon::new() - .set("fill", color.display_css().to_string()) + .set("fill", pattern) .set("stroke", color2.display_css().to_string()) .set("points", data), ); @@ -1118,6 +1121,15 @@ impl Visualization { } } } + let mut defs = Definitions::new(); + for node in patterns.to_svg() { + defs.append(node); + } + if let Some(children) = defs.get_children() + && !children.is_empty() + { + graph_svg.append(defs); + } Ok(graph_svg) } } diff --git a/rust/pspp/src/spv/read/graph/patterns.rs b/rust/pspp/src/spv/read/graph/patterns.rs index 89e74406a3..72590084f0 100644 --- a/rust/pspp/src/spv/read/graph/patterns.rs +++ b/rust/pspp/src/spv/read/graph/patterns.rs @@ -1,10 +1,12 @@ -use std::io::{Cursor, Write}; - -use quick_xml::{ - Writer, - events::{BytesEnd, BytesStart, Event}, +use std::{ + collections::{BTreeMap, BTreeSet}, + fmt::Display, }; +use svg::{Node, node::Blob}; + +use crate::output::pivot::look::Color; + mod data; pub struct Pattern { @@ -22,20 +24,66 @@ impl Pattern { data::PATTERNS.get(index) } - pub fn to_svg(&self, id: &str, fill: &str) -> String { - let mut writer = Writer::new(Cursor::new(Vec::new())); - writer - .write_event(Event::Start( - BytesStart::new("pattern") - .with_attributes([("id", id), ("fill", fill)]) - .with_attributes(self.attributes.iter().copied()), - )) - .unwrap(); - writer.get_mut().write_all(self.content.as_bytes()).unwrap(); - writer - .write_event(Event::End(BytesEnd::new("pattern"))) - .unwrap(); - String::from_utf8(writer.into_inner().into_inner()).unwrap() + pub fn to_svg(&self) -> svg::node::element::Pattern { + let mut group = svg::node::element::Pattern::new().add(Blob::new(self.content)); + for (name, value) in self.attributes.into_iter().copied() { + group.assign(name, value); + } + group + } +} + +#[derive(Clone, Debug, Default)] +pub struct Patterns { + instances: BTreeMap>, +} + +struct PatternName { + index: usize, + color: Color, +} +impl Display for PatternName { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let (r, g, b, a) = self.color.into_rgba(); + write!(f, "pattern{}_{r:02x}{g:02x}{b:02x}", self.index)?; + if a != 255 { + write!(f, "_{a:02x}")?; + } + Ok(()) + } +} + +impl Patterns { + pub fn new() -> Self { + Self::default() + } + + pub fn insert(&mut self, index: usize, color: Color) -> String { + if index != 0 && Pattern::for_index(index).is_some() { + self.instances.entry(index).or_default().insert(color); + format!("url(#{})", PatternName { index, color }) + } else { + color.display_css().to_string() + } + } + + pub fn to_svg(&self) -> impl Iterator { + self.instances.iter().flat_map(|(index, colors)| { + colors.iter().map(move |color| { + Pattern::for_index(*index) + .unwrap() + .to_svg() + .set( + "id", + PatternName { + index: *index, + color: *color, + } + .to_string(), + ) + .set("fill", color.display_css().to_string()) + }) + }) } } @@ -46,11 +94,11 @@ mod tests { #[test] fn pattern() { let pattern = Pattern::for_index(1).unwrap(); - let svg = pattern.to_svg("p1-red", "red"); + let svg = pattern.to_svg().to_string(); assert_eq!( svg, - r#" + r#" "#, diff --git a/rust/pspp/src/spv/read/graph/style.rs b/rust/pspp/src/spv/read/graph/style.rs index d53b46a488..83ed4074ef 100644 --- a/rust/pspp/src/spv/read/graph/style.rs +++ b/rust/pspp/src/spv/read/graph/style.rs @@ -82,7 +82,7 @@ pub struct Style { pub padding: Option, #[serde(rename = "@pattern")] - pub pattern: Option, + pub pattern: Option, #[serde(rename = "@symbol")] pub symbol: Option, -- 2.47.3