1#[derive(Clone, Copy, Debug, PartialEq)]
5pub struct Color {
6 pub r: u8,
7 pub g: u8,
8 pub b: u8,
9 pub a: f32, }
11
12#[derive(Clone, Copy, Debug, PartialEq)]
15pub struct Point {
16 pub x: f64,
17 pub y: f64,
18 pub r: Option<f64>,
19}
20
21#[derive(Clone, Copy, Debug, PartialEq)]
23pub struct BoxPoint {
24 pub min: f64,
25 pub q1: f64,
26 pub median: f64,
27 pub q3: f64,
28 pub max: f64,
29}
30
31#[derive(Clone, Debug, PartialEq)]
33pub struct WordEntry {
34 pub text: String,
36 pub size: f64,
38 pub color: Option<Color>,
40}
41
42#[derive(Clone, Debug, PartialEq)]
45pub struct TreeNode {
46 pub label: String,
47 pub value: f64,
48 pub children: Vec<TreeNode>,
49}
50
51#[derive(Clone, Debug, PartialEq)]
58pub struct SankeyLink {
59 pub from: String,
60 pub to: String,
61 pub flow: f64,
62 pub color_from: Option<Color>,
63 pub color_to: Option<Color>,
64}
65
66#[derive(Clone, Copy, Debug, PartialEq)]
69pub enum SeriesType {
70 Bar,
71 Line,
72}
73
74#[derive(Clone, Debug, PartialEq)]
77pub struct Series {
78 pub name: String,
79 pub values: Vec<f64>,
80 pub points: Vec<Point>,
82 pub fill: Vec<Color>, pub stroke: Vec<Color>, pub stroke_width: f64,
85 pub area: bool, pub tension: f64, pub series_type: SeriesType,
89 pub point_radius: Option<f64>,
92 pub box_points: Vec<BoxPoint>,
94 pub tree: Vec<TreeNode>,
96 pub links: Vec<SankeyLink>,
98}
99
100impl Series {
101 pub fn fill_at(&self, i: usize) -> Color {
103 color_at(&self.fill, i)
104 }
105 pub fn stroke_at(&self, i: usize) -> Color {
106 color_at(&self.stroke, i)
107 }
108}
109
110pub fn color_at(colors: &[Color], i: usize) -> Color {
114 match colors.len() {
115 0 => Color {
116 r: 0,
117 g: 0,
118 b: 0,
119 a: 1.0,
120 },
121 1 => colors[0],
122 _ => colors[i % colors.len()],
123 }
124}
125
126#[derive(Clone, Debug, PartialEq)]
127pub struct AxisSpec {
128 pub title: Option<String>,
129 pub min: Option<f64>,
130 pub max: Option<f64>,
131 pub suggested_min: Option<f64>,
132 pub suggested_max: Option<f64>,
133 pub begin_at_zero: bool,
134 pub offset: bool,
138 pub grid: bool,
139}
140
141#[derive(Clone, Copy, Debug, PartialEq)]
142pub enum LegendPos {
143 Top,
144 Bottom,
145 Left,
146 Right,
147 None,
148}
149
150#[derive(Clone, Debug, PartialEq)]
152pub struct OutlabelConfig {
153 pub text: String,
155 pub color: Color,
157 pub background: Option<Color>,
159 pub stretch: f64,
161}
162
163impl Default for OutlabelConfig {
164 fn default() -> Self {
165 OutlabelConfig {
166 text: "%l\n%p%".to_string(),
167 color: Color {
168 r: 255,
169 g: 255,
170 b: 255,
171 a: 1.0,
172 },
173 background: None,
174 stretch: 40.0,
175 }
176 }
177}
178
179#[derive(Clone, Copy, Debug, PartialEq)]
181pub enum SankeyColorMode {
182 From,
183 To,
184 Gradient,
185}
186
187#[derive(Clone, Copy, Debug, PartialEq)]
189pub enum SankeyModeX {
190 Edge,
191 Even,
192}
193
194#[derive(Clone, Copy, Debug, PartialEq)]
196pub enum SankeySize {
197 Min,
198 Max,
199}
200
201#[derive(Clone, Debug, PartialEq)]
202pub enum ChartKind {
203 Bar {
204 horizontal: bool,
205 placement_stacked: bool,
207 value_stacked: bool,
209 },
210 Line, Pie {
212 donut_ratio: f64,
213 }, Scatter, Bubble, Radar, Mixed, Matrix {
219 color_lo: Color, color_hi: Color, },
222 VegaRect {
227 x_labels: Vec<String>,
229 y_labels: Vec<String>,
231 cells: Vec<Vec<Option<Color>>>,
234 },
235 Progress,
238 BoxPlot,
240 Sparkline,
242 PolarArea,
244 RadialGauge {
247 min: f64,
248 max: f64,
249 track: Color,
250 inner_ratio: f64, rounded: bool,
252 display_text: bool,
253 center_font_size: Option<f64>,
255 },
256 Gauge {
259 value: f64,
260 min: f64,
261 needle: Color,
262 label: bool, label_color: Color, label_bg: Color, },
266 OutlabeledPie {
269 donut_ratio: f64,
270 outlabel: OutlabelConfig,
271 },
272 Treemap,
275 WordCloud {
278 entries: Vec<WordEntry>,
279 min_rotation: f64,
281 max_rotation: f64,
283 rotation_steps: u32,
285 padding: f64,
287 },
288 Sankey {
291 color_from: Color,
292 color_to: Color,
293 color_mode: SankeyColorMode,
294 alpha: f32,
296 node_width: f64,
297 node_padding: f64,
298 mode_x: SankeyModeX,
299 size: SankeySize,
300 border: Color,
301 border_width: f64,
302 label_color: Color,
303 labels: std::collections::HashMap<String, String>,
305 priority: std::collections::HashMap<String, f64>,
307 columns: std::collections::HashMap<String, usize>,
309 },
310}
311
312#[derive(Clone, Debug, PartialEq)]
315pub struct Theme {
316 pub palette: Vec<Color>,
318 pub is_custom_palette: bool,
320 pub grid_color: Color,
322 pub text_color: Color,
324 pub background: Option<Color>,
326 pub font_size: f64,
328}
329
330impl Default for Theme {
331 fn default() -> Self {
332 Theme {
333 palette: crate::palette::PALETTE.to_vec(),
334 is_custom_palette: false,
335 grid_color: Color {
336 r: 224,
337 g: 224,
338 b: 224,
339 a: 1.0,
340 },
341 text_color: Color {
342 r: 102,
343 g: 102,
344 b: 102,
345 a: 1.0,
346 },
347 background: None,
348 font_size: 12.0,
349 }
350 }
351}
352
353#[derive(Debug, Clone, Copy, PartialEq, Eq)]
355pub enum DecimationAlgorithm {
356 MinMax,
357 Lttb,
358}
359
360#[derive(Debug, Clone, Copy, PartialEq)]
363pub struct Decimation {
364 pub enabled: bool,
365 pub algorithm: DecimationAlgorithm,
366 pub samples: Option<f64>,
368 pub threshold: Option<f64>,
370}
371
372impl Default for Decimation {
373 fn default() -> Self {
374 Decimation {
375 enabled: true,
376 algorithm: DecimationAlgorithm::MinMax,
377 samples: None,
378 threshold: None,
379 }
380 }
381}
382
383#[derive(Clone, Debug, PartialEq)]
384pub struct ChartSpec {
385 pub kind: ChartKind,
386 pub series: Vec<Series>,
387 pub categories: Vec<String>,
388 pub x_axis: AxisSpec,
389 pub y_axis: AxisSpec,
390 pub legend: LegendPos,
391 pub title: Option<String>,
392 pub width: f64,
393 pub height: f64,
394 pub data_labels: bool,
396 pub theme: Theme,
398 pub decimation: Decimation,
400}
401
402#[cfg(test)]
403mod tests {
404 use super::*;
405
406 fn c(r: u8, g: u8, b: u8) -> Color {
407 Color { r, g, b, a: 1.0 }
408 }
409
410 #[test]
411 fn fill_at_broadcasts_single_color() {
412 let s = Series {
413 name: "x".into(),
414 values: vec![1.0, 2.0, 3.0],
415 points: vec![],
416 fill: vec![c(1, 2, 3)],
417 stroke: vec![],
418 stroke_width: 1.0,
419 area: false,
420 tension: 0.0,
421 series_type: SeriesType::Bar,
422 point_radius: None,
423 box_points: vec![],
424 tree: vec![],
425 links: vec![],
426 };
427 assert_eq!(s.fill_at(0), c(1, 2, 3));
428 assert_eq!(s.fill_at(2), c(1, 2, 3)); }
430
431 #[test]
432 fn fill_at_indexes_per_point_colors() {
433 let s = Series {
434 name: "x".into(),
435 values: vec![1.0, 2.0],
436 points: vec![],
437 fill: vec![c(10, 0, 0), c(0, 20, 0)],
438 stroke: vec![],
439 stroke_width: 1.0,
440 area: false,
441 tension: 0.0,
442 series_type: SeriesType::Bar,
443 point_radius: None,
444 box_points: vec![],
445 tree: vec![],
446 links: vec![],
447 };
448 assert_eq!(s.fill_at(0), c(10, 0, 0));
449 assert_eq!(s.fill_at(1), c(0, 20, 0));
450 assert_eq!(s.fill_at(2), c(10, 0, 0)); }
452
453 #[test]
454 fn stroke_at_empty_is_black() {
455 let s = Series {
456 name: "x".into(),
457 values: vec![1.0],
458 points: vec![],
459 fill: vec![],
460 stroke: vec![],
461 stroke_width: 1.0,
462 area: false,
463 tension: 0.0,
464 series_type: SeriesType::Bar,
465 point_radius: None,
466 box_points: vec![],
467 tree: vec![],
468 links: vec![],
469 };
470 assert_eq!(s.stroke_at(0), c(0, 0, 0));
471 }
472
473 #[test]
474 fn theme_default_palette_is_not_custom() {
475 let t = Theme::default();
476 assert!(!t.is_custom_palette);
477 }
478
479 #[test]
480 fn box_point_fields_accessible() {
481 let bp = BoxPoint {
482 min: 1.0,
483 q1: 2.0,
484 median: 3.0,
485 q3: 4.0,
486 max: 5.0,
487 };
488 assert_eq!(bp.median, 3.0);
489 assert_eq!(bp.max - bp.min, 4.0);
490 }
491
492 #[test]
493 fn outlabel_config_default_values() {
494 let c = OutlabelConfig::default();
495 assert_eq!(c.text, "%l\n%p%");
496 assert!((c.stretch - 40.0).abs() < 1e-9);
497 assert!(c.background.is_none());
498 assert_eq!(c.color.r, 255);
499 assert_eq!(c.color.a, 1.0);
500 }
501
502 #[test]
503 fn tree_node_is_recursive() {
504 let leaf = TreeNode {
505 label: "a".into(),
506 value: 3.0,
507 children: vec![],
508 };
509 let group = TreeNode {
510 label: "g".into(),
511 value: 3.0,
512 children: vec![leaf.clone()],
513 };
514 assert_eq!(group.children.len(), 1);
515 assert_eq!(group.children[0].value, 3.0);
516 assert!(leaf.children.is_empty());
517 }
518}