series: &BTreeMap<&str, Series>,
bin_widths: &mut BTreeMap<String, f64>,
axis_domains: &mut EnumMap<Axis3, AxisDomain<'a>>,
+ styles: &'a HashMap<String, Style>,
) -> Result<Vec<Element<'a>>, GraphError> {
let elements = graph
.elements()
- .map(|(raw, graphic)| Element::new(raw, graphic, series, bin_widths, axis_domains))
+ .map(|(raw, graphic)| {
+ Element::new(raw, graphic, series, bin_widths, axis_domains, styles)
+ })
.collect::<Result<Vec<_>, _>>()?;
let coordinates = graph
.coordinates()
mut csv_data: Option<DataSource>,
warn: &mut dyn FnMut(GraphWarning),
) -> Result<svg::Document, GraphError> {
- let _styles = self.decode_styles();
let style_cycles = self.decode_style_cycles();
for delimited_source in self
.children
.map_or(&EN, |lang| Locale::for_lang(lang));
let series = self.decode_series(locale, data, warn);
let domains = self.decode_domains(&series, warn);
- let _styles = self
- .children
- .iter()
- .filter_map(|child| child.style())
- .filter_map(|style| style.id.as_ref().map(|id| (id.as_str(), style)))
- .collect::<HashMap<_, _>>();
+ let styles = self.decode_styles();
let frames = self.decode_frames();
/*
&domains,
&series,
&style_cycles,
+ &styles,
)?);
}
domains: &BTreeMap<&str, Domain>,
series: &BTreeMap<&str, Series>,
style_cycles: &HashMap<&str, &StyleCycle>,
+ styles: &HashMap<String, Style>,
) -> Result<svg::node::element::Group, GraphError> {
let mut graph_svg = Group::new().with_id(&graph.id);
let mut bin_widths = BTreeMap::new();
&series,
&mut bin_widths,
&mut axis_domains,
+ styles,
)?;
let elements = self.pass(
&graph,
&series,
&mut bin_widths,
&mut axis_domains,
+ styles,
)?;
let mut font_metrics = FontMetrics::new();
for element in &elements {
let mut categoricals = IndexMap::new();
let mut color_index = None;
- if let Some(color) = element.raw.color.first()
+ if let Some(color) = element
+ .raw
+ .color
+ .iter()
+ .find(|color| color.affect.unwrap_or_default().includes_main())
&& let Some(variable) = &color.variable
{
let series = series
Some(categoricals.insert_full(series.name.as_str(), series).0);
}
}
+ let mut color2_index = None;
+ if let Some(color2) = element
+ .raw
+ .color
+ .iter()
+ .find(|color| color.affect.unwrap_or_default().includes_secondary())
+ && let Some(variable) = &color2.variable
+ {
+ let series = series
+ .get(variable.as_str())
+ .ok_or_else(|| GraphError::MissingVariable(variable.clone()))?;
+ if series.categorical {
+ color2_index =
+ Some(categoricals.insert_full(series.name.as_str(), series).0);
+ }
+ }
let mut styling = None;
if let Some(style_by) = element.raw.style_by.first()
&& let Some(variable) = &style_by.variable
}
for (categoricals, group) in &grouped_data {
let mut color = Color::BLACK;
+ if let Some(style_color) = element.style.color {
+ color = style_color;
+ }
if let Some(color_index) = color_index {
let index = categoricals[color_index]
.as_number()
}
}
+ let mut color2 = Color::BLACK;
+ if let Some(style_color) = element.style.color2 {
+ color2 = style_color;
+ }
+ if let Some(color2_index) = color2_index {
+ let index = categoricals[color2_index]
+ .as_number()
+ .unwrap_or_default()
+ .unwrap_or_default();
+ let index = <usize as NumCast>::from(index).unwrap_or_default();
+ color2 = PALETTE[index % PALETTE.len()];
+ }
+ if let Some((index, style_cycle)) = styling {
+ let index = categoricals[index]
+ .as_number()
+ .unwrap_or_default()
+ .unwrap_or_default();
+ let index = <usize as NumCast>::from(index).unwrap_or_default();
+ let style = style_cycle.get_style(index);
+ if let Some(style_color) = style.color2 {
+ color2 = style_color;
+ }
+ }
+
fn points(
rect: &Rect,
group: &[Vec<&SeriesValue>],
}
element_svg.append(
Polygon::new()
- .set("fill", "black")
- .set("stroke", "black")
+ .set("fill", color.display_css().to_string())
+ .set("stroke", color2.display_css().to_string())
.set("points", data),
);
}
}
}
-#[derive(Deserialize, Debug, Default)]
+#[derive(Copy, Clone, Deserialize, Debug, Default, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
enum DelimitedFileSourceMissing {
#[default]
order: String,
}
-#[derive(Deserialize, Debug)]
+#[derive(Copy, Clone, Deserialize, Debug, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
enum SortMethod {
Data,
struct Element<'a> {
raw: &'a RawElement,
+ style: Cow<'a, Style>,
graphic: Graphic,
variables: EnumMap<Axis3, Option<Series>>,
ignore_minmax: EnumMap<Axis3, bool>,
series: &BTreeMap<&str, Series>,
bin_widths: &mut BTreeMap<String, f64>,
axis_domains: &mut EnumMap<Axis3, AxisDomain<'a>>,
+ styles: &'a HashMap<String, Style>,
) -> Result<Self, GraphError> {
let mut variables = EnumMap::default();
for (coordinate, axis) in
}
Ok(Self {
raw,
+ style: if let Some(style) = raw.style.as_deref()
+ && let Some(style) = styles.get(style)
+ {
+ Cow::Borrowed(style)
+ } else {
+ Cow::Owned(Style::default())
+ },
graphic,
variables,
ignore_minmax,
}
}
-#[derive(Deserialize, Debug)]
+#[derive(Copy, Clone, Deserialize, Debug, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
enum BreakRepresentation {
Gap,
Polygon,
}
-#[derive(Deserialize, Debug)]
+#[derive(Deserialize, Debug, Default)]
#[serde(rename_all = "camelCase")]
+#[serde(default)]
struct RawElement {
#[serde(rename = "@id")]
id: Option<String>,
#[serde(rename = "@breakRepresentation")]
break_representation: Option<BreakRepresentation>,
- #[serde(default, rename = "@closed")]
+ #[serde(rename = "@closed")]
closed: bool,
- #[serde(default, rename = "@preserveStraightLines")]
+ #[serde(rename = "@preserveStraightLines")]
preserve_straight_lines: bool,
- #[serde(default, rename = "@centerIn3D")]
+ #[serde(rename = "@centerIn3D")]
center_in_3d: bool,
- #[serde(default)]
- position: Vec<Coordinate>,
-
#[serde(rename = "@medianStyle")]
median_style: Option<String>,
#[serde(rename = "@extremeStyle")]
extreme_style: Option<String>,
- #[serde(default, rename = "$value")]
+ #[serde(rename = "@style")]
+ style: Option<String>,
+
+ #[serde(rename = "$value")]
statistics: Vec<Statistic>,
+ position: Vec<Coordinate>,
x: Option<Coordinate>,
y: Option<Coordinate>,
z: Option<Coordinate>,
- #[serde(default)]
color: Vec<ColorAesthetic>,
- #[serde(default)]
style_by: Vec<StyleBy>,
}
to: String,
}
-#[derive(Deserialize, Debug)]
+#[derive(Copy, Clone, Deserialize, Debug, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
enum ColorScale {
BipolarBlack,
Interpolated,
}
-#[derive(Deserialize, Debug)]
+#[derive(Copy, Clone, Deserialize, Debug, Default, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
enum ColorAffect {
Both,
+ #[serde(alias = "fill")]
+ #[default]
Main,
+ #[serde(alias = "stroke")]
Secondary,
- Stroke,
- Fill,
+}
+
+impl ColorAffect {
+ pub fn includes_main(&self) -> bool {
+ matches!(self, ColorAffect::Both | ColorAffect::Main)
+ }
+
+ pub fn includes_secondary(&self) -> bool {
+ matches!(self, ColorAffect::Both | ColorAffect::Secondary)
+ }
}
#[derive(Deserialize, Debug)]
}
}
-#[derive(Deserialize, Debug)]
+#[derive(Copy, Clone, Deserialize, Debug, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
enum LocationMethod {
Fixed,
method: Method,
}
-#[derive(Deserialize, Debug)]
+#[derive(Copy, Clone, Deserialize, Debug, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
enum Method {
Linear,
dimensions: String,
}
-#[derive(Deserialize, Debug, Default)]
+#[derive(Copy, Clone, Deserialize, Debug, Default, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
enum BinShape {
#[default]
Voronoi,
}
-#[derive(Deserialize, Debug, Default)]
+#[derive(Copy, Clone, Deserialize, Debug, Default, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
enum BinLocation {
/// Center of the bin, disregarding the data in the bin.
Centroid,
}
-#[derive(Deserialize, Debug, Default)]
+#[derive(Copy, Clone, Deserialize, Debug, Default, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
enum BinMethod {
/// Bin across all the dimensions, without aggregating. All the points in a
augment: Augment,
}
-#[derive(Deserialize, Debug, Default)]
+#[derive(Copy, Clone, Deserialize, Debug, Default, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
enum SummaryMethod {
Mean,
FractionTrue,
}
-#[derive(Deserialize, Debug, Default)]
+#[derive(Copy, Clone, Deserialize, Debug, Default, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
enum SummaryBase {
/// From data for a chart or panel.
Coordinate,
}
-#[derive(Deserialize, Debug, Default)]
+#[derive(Copy, Clone, Deserialize, Debug, Default, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
enum Augment {
/// Treat missing values as zero.
segments: Option<isize>,
}
-#[derive(Deserialize, Debug, Default)]
+#[derive(Copy, Clone, Deserialize, Debug, Default, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
enum Distribution {
Uniform,