#[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, 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>,
}
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 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, 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, },
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,
},
}
#[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(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,
}
#[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![],
};
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![],
};
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![],
};
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);
}
}