vectorcraft/crates/doc/src/graph.rs

117 lines
3.8 KiB
Rust

//! Graphs (Illustrator's graph tools): a group whose art is generated from a [`GraphSpec`].
use serde::{Deserialize, Serialize};
use vectorcraft_geom::Rect;
/// The nine graph types, in the Graph tool group's order.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum GraphKind {
#[default]
Column,
StackedColumn,
Bar,
StackedBar,
Line,
Area,
Scatter,
Pie,
Radar,
}
impl GraphKind {
pub const ALL: [GraphKind; 9] = [
GraphKind::Column,
GraphKind::StackedColumn,
GraphKind::Bar,
GraphKind::StackedBar,
GraphKind::Line,
GraphKind::Area,
GraphKind::Scatter,
GraphKind::Pie,
GraphKind::Radar,
];
pub fn id(self) -> &'static str {
match self {
GraphKind::Column => "column",
GraphKind::StackedColumn => "stackedColumn",
GraphKind::Bar => "bar",
GraphKind::StackedBar => "stackedBar",
GraphKind::Line => "line",
GraphKind::Area => "area",
GraphKind::Scatter => "scatter",
GraphKind::Pie => "pie",
GraphKind::Radar => "radar",
}
}
/// Parse an id, also accepting the tool ids (`columnGraph`, `stackedBarGraph`…).
pub fn parse(s: &str) -> Option<Self> {
let s = s.strip_suffix("Graph").unwrap_or(s);
Self::ALL.into_iter().find(|k| k.id().eq_ignore_ascii_case(s))
}
pub fn label(self) -> &'static str {
match self {
GraphKind::Column => "Column",
GraphKind::StackedColumn => "Stacked Column",
GraphKind::Bar => "Bar",
GraphKind::StackedBar => "Stacked Bar",
GraphKind::Line => "Line",
GraphKind::Area => "Area",
GraphKind::Scatter => "Scatter",
GraphKind::Pie => "Pie",
GraphKind::Radar => "Radar",
}
}
}
/// Graph data and options. `rows` are categories (one per row of the Graph Data window), each
/// holding one value per series; `series` are the legend labels (the first row of the window).
/// Scatter graphs read each series as (y, x) column pairs, like Illustrator.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(default, rename_all = "camelCase")]
pub struct GraphSpec {
pub kind: GraphKind,
/// The plot rectangle (the area the graph tool dragged), document points.
pub rect: Rect,
pub series: Vec<String>,
pub categories: Vec<String>,
pub rows: Vec<Vec<f64>>,
/// Column/bar width as a % of the available category width (90).
pub column_width: f64,
/// Cluster width as a % (80).
pub cluster_width: f64,
/// Legend on the right (Graph Type options: Add Legend Across Top = false).
pub legend: bool,
/// Value axis: tick count override (0 = automatic), min/max override.
pub ticks: usize,
pub axis_min: Option<f64>,
pub axis_max: Option<f64>,
/// Line graphs: mark data points, connect them, fill (area-like) the lines.
pub mark_points: bool,
pub connect_points: bool,
/// Bounds of the art when it was last generated (detects moves/scales of the graph group).
#[serde(skip_serializing_if = "Option::is_none")]
pub placed: Option<Rect>,
}
impl Default for GraphSpec {
fn default() -> Self {
Self {
kind: GraphKind::Column,
rect: Rect::new(0.0, 0.0, 200.0, 150.0),
series: vec![],
categories: vec![],
rows: vec![vec![1.0]],
column_width: 90.0,
cluster_width: 80.0,
legend: true,
ticks: 0,
axis_min: None,
axis_max: None,
mark_points: true,
connect_points: true,
placed: None,
}
}
}