}
/// Color used in [FontStyle].
-#[derive(Copy, Clone, PartialEq, Eq)]
+#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct Color {
/// Alpha channel.
///
(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) {
Document, Node,
node::{
Comment,
- element::{Circle, Group, Line, Path, Polygon, Rectangle, path::Data},
+ element::{Circle, Definitions, Group, Line, Path, Polygon, Rectangle, path::Data},
},
};
read::graph::{
format::Formatter,
frames::{Component, Frames, Position},
+ patterns::Patterns,
style::{Style, StyleCycle},
},
},
let mut graph_svg = Group::new().with_id(&graph.id);
let mut bin_widths = BTreeMap::new();
let mut axis_domains = EnumMap::<Axis3, AxisDomain>::default();
+ let mut patterns = Patterns::new();
self.pass(
&graph,
&domains,
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,
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)
}
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),
);
}
}
}
+ 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)
}
}
-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 {
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<usize, BTreeSet<Color>>,
+}
+
+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<Item = svg::node::element::Pattern> {
+ 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())
+ })
+ })
}
}
#[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#"<pattern id="p1-red" fill="red" height="4" patternUnits="userSpaceOnUse" width="10">
+ r#"<pattern height="4" patternUnits="userSpaceOnUse" width="10">
<circle cx="4.5" cy="3.5" r="0.5" height="1" />
<rect x="9" y="1" width="1" height="1" />
</pattern>"#,