#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Color {
pub r: u8,
pub g: u8,
pub b: u8,
pub a: f32, }
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Point {
pub x: f64,
pub y: f64,
pub r: Option<f64>,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct BoxPoint {
pub min: f64,
pub q1: f64,
pub median: f64,
pub q3: f64,
pub max: f64,
}
#[derive(Clone, Debug, PartialEq)]
pub struct WordEntry {
pub text: String,
pub size: f64,
pub color: Option<Color>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct TreeNode {
pub label: String,
pub value: f64,
pub children: Vec<TreeNode>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct SankeyLink {
pub from: String,
pub to: String,
pub flow: f64,
pub color_from: Option<Color>,
pub color_to: Option<Color>,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum SeriesType {
Bar,
Line,
}
#[derive(Clone, Debug, PartialEq)]
pub struct Series {
pub name: String,
pub values: Vec<f64>,
pub points: Vec<Point>,
pub fill: Vec<Color>, pub stroke: Vec<Color>, pub stroke_width: f64,
pub area: bool, pub tension: f64, pub series_type: SeriesType,
pub point_radius: Option<f64>,
pub box_points: Vec<BoxPoint>,
pub tree: Vec<TreeNode>,
pub links: Vec<SankeyLink>,
}
impl Series {
pub fn fill_at(&self, i: usize) -> Color {
color_at(&self.fill, i)
}
pub fn stroke_at(&self, i: usize) -> Color {
color_at(&self.stroke, i)
}
}
pub fn color_at(colors: &[Color], i: usize) -> Color {
match colors.len() {
0 => Color {
r: 0,
g: 0,
b: 0,
a: 1.0,
},
1 => colors[0],
_ => colors[i % colors.len()],
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct AxisSpec {
pub title: Option<String>,
pub min: Option<f64>,
pub max: Option<f64>,
pub suggested_min: Option<f64>,
pub suggested_max: Option<f64>,
pub begin_at_zero: bool,
pub offset: bool,
pub grid: bool,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum LegendPos {
Top,
Bottom,
Left,
Right,
None,
}
#[derive(Clone, Debug, PartialEq)]
pub struct OutlabelConfig {
pub text: String,
pub color: Color,
pub background: Option<Color>,
pub stretch: f64,
}
impl Default for OutlabelConfig {
fn default() -> Self {
OutlabelConfig {
text: "%l\n%p%".to_string(),
color: Color {
r: 255,
g: 255,
b: 255,
a: 1.0,
},
background: None,
stretch: 40.0,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum SankeyColorMode {
From,
To,
Gradient,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum SankeyModeX {
Edge,
Even,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum SankeySize {
Min,
Max,
}
#[derive(Clone, Debug, PartialEq)]
pub enum ChartKind {
Bar {
horizontal: bool,
placement_stacked: bool,
value_stacked: bool,
},
Line, Pie {
donut_ratio: f64,
}, Scatter, Bubble, Radar, Mixed, Matrix {
color_lo: Color, color_hi: Color, },
VegaRect {
x_labels: Vec<String>,
y_labels: Vec<String>,
cells: Vec<Vec<Option<Color>>>,
},
Progress,
BoxPlot,
Sparkline,
PolarArea,
RadialGauge {
min: f64,
max: f64,
track: Color,
inner_ratio: f64, rounded: bool,
display_text: bool,
center_font_size: Option<f64>,
},
Gauge {
value: f64,
min: f64,
needle: Color,
label: bool, label_color: Color, label_bg: Color, },
OutlabeledPie {
donut_ratio: f64,
outlabel: OutlabelConfig,
},
Treemap,
WordCloud {
entries: Vec<WordEntry>,
min_rotation: f64,
max_rotation: f64,
rotation_steps: u32,
padding: f64,
},
Sankey {
color_from: Color,
color_to: Color,
color_mode: SankeyColorMode,
alpha: f32,
node_width: f64,
node_padding: f64,
mode_x: SankeyModeX,
size: SankeySize,
border: Color,
border_width: f64,
label_color: Color,
labels: std::collections::HashMap<String, String>,
priority: std::collections::HashMap<String, f64>,
columns: std::collections::HashMap<String, usize>,
},
}
#[derive(Clone, Debug, PartialEq)]
pub struct Theme {
pub palette: Vec<Color>,
pub is_custom_palette: bool,
pub grid_color: Color,
pub text_color: Color,
pub background: Option<Color>,
pub font_size: f64,
}
impl Default for Theme {
fn default() -> Self {
Theme {
palette: crate::palette::PALETTE.to_vec(),
is_custom_palette: false,
grid_color: Color {
r: 224,
g: 224,
b: 224,
a: 1.0,
},
text_color: Color {
r: 102,
g: 102,
b: 102,
a: 1.0,
},
background: None,
font_size: 12.0,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DecimationAlgorithm {
MinMax,
Lttb,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Decimation {
pub enabled: bool,
pub algorithm: DecimationAlgorithm,
pub samples: Option<f64>,
pub threshold: Option<f64>,
}
impl Default for Decimation {
fn default() -> Self {
Decimation {
enabled: true,
algorithm: DecimationAlgorithm::MinMax,
samples: None,
threshold: None,
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct ChartSpec {
pub kind: ChartKind,
pub series: Vec<Series>,
pub categories: Vec<String>,
pub x_axis: AxisSpec,
pub y_axis: AxisSpec,
pub legend: LegendPos,
pub title: Option<String>,
pub width: f64,
pub height: f64,
pub data_labels: bool,
pub theme: Theme,
pub decimation: Decimation,
}
#[cfg(test)]
mod tests {
use super::*;
fn c(r: u8, g: u8, b: u8) -> Color {
Color { r, g, b, a: 1.0 }
}
#[test]
fn fill_at_broadcasts_single_color() {
let s = Series {
name: "x".into(),
values: vec![1.0, 2.0, 3.0],
points: vec![],
fill: vec![c(1, 2, 3)],
stroke: vec![],
stroke_width: 1.0,
area: false,
tension: 0.0,
series_type: SeriesType::Bar,
point_radius: None,
box_points: vec![],
tree: vec![],
links: vec![],
};
assert_eq!(s.fill_at(0), c(1, 2, 3));
assert_eq!(s.fill_at(2), c(1, 2, 3)); }
#[test]
fn fill_at_indexes_per_point_colors() {
let s = Series {
name: "x".into(),
values: vec![1.0, 2.0],
points: vec![],
fill: vec![c(10, 0, 0), c(0, 20, 0)],
stroke: vec![],
stroke_width: 1.0,
area: false,
tension: 0.0,
series_type: SeriesType::Bar,
point_radius: None,
box_points: vec![],
tree: vec![],
links: vec![],
};
assert_eq!(s.fill_at(0), c(10, 0, 0));
assert_eq!(s.fill_at(1), c(0, 20, 0));
assert_eq!(s.fill_at(2), c(10, 0, 0)); }
#[test]
fn stroke_at_empty_is_black() {
let s = Series {
name: "x".into(),
values: vec![1.0],
points: vec![],
fill: vec![],
stroke: vec![],
stroke_width: 1.0,
area: false,
tension: 0.0,
series_type: SeriesType::Bar,
point_radius: None,
box_points: vec![],
tree: vec![],
links: vec![],
};
assert_eq!(s.stroke_at(0), c(0, 0, 0));
}
#[test]
fn theme_default_palette_is_not_custom() {
let t = Theme::default();
assert!(!t.is_custom_palette);
}
#[test]
fn box_point_fields_accessible() {
let bp = BoxPoint {
min: 1.0,
q1: 2.0,
median: 3.0,
q3: 4.0,
max: 5.0,
};
assert_eq!(bp.median, 3.0);
assert_eq!(bp.max - bp.min, 4.0);
}
#[test]
fn outlabel_config_default_values() {
let c = OutlabelConfig::default();
assert_eq!(c.text, "%l\n%p%");
assert!((c.stretch - 40.0).abs() < 1e-9);
assert!(c.background.is_none());
assert_eq!(c.color.r, 255);
assert_eq!(c.color.a, 1.0);
}
#[test]
fn tree_node_is_recursive() {
let leaf = TreeNode {
label: "a".into(),
value: 3.0,
children: vec![],
};
let group = TreeNode {
label: "g".into(),
value: 3.0,
children: vec![leaf.clone()],
};
assert_eq!(group.children.len(), 1);
assert_eq!(group.children[0].value, 3.0);
assert!(leaf.children.is_empty());
}
}