#[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 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>,
}
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)
}
}
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 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 enum ChartKind {
Bar { horizontal: bool, stacked: bool },
Line, Pie { donut_ratio: f64 }, Scatter, Bubble, Radar, Mixed, }
#[derive(Clone, Debug, PartialEq)]
pub struct Theme {
pub palette: Vec<Color>,
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(),
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,
};
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,
};
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,
};
assert_eq!(s.stroke_at(0), c(0, 0, 0));
}
}