1#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]
11#![forbid(unsafe_code)]
12
13mod axis;
14mod raster;
15mod render;
16mod resolve;
17mod sparkline;
18mod trend;
19
20#[cfg(test)]
21mod tests;
22
23pub use crate::model::{Chart, ChartKind, LegendPos, SparklineKind, Trendline, TrendlineKind};
24use serde::{Deserialize, Serialize};
25
26pub use axis::{Scale, format_number, nice_scale};
27pub use raster::rasterize;
28pub use render::{Swatch, legend_entries, render, supports_trendline};
29pub use resolve::{default_palette, resolve, series_color, series_plot_kind};
30pub use sparkline::render_sparkline;
31
32pub type Rgba = [u8; 4];
34
35#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
36pub enum HAlign {
37 #[default]
38 Left,
39 Center,
40 Right,
41}
42
43#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
44pub enum VAlign {
45 #[default]
46 Top,
47 Middle,
48 Bottom,
49}
50
51#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
53pub enum Prim {
54 Rect {
55 x: f32,
56 y: f32,
57 w: f32,
58 h: f32,
59 fill: Option<Rgba>,
60 stroke: Option<(Rgba, f32)>,
61 radius: f32,
62 },
63 Line {
64 pts: Vec<[f32; 2]>,
65 color: Rgba,
66 width: f32,
67 dash: bool,
68 },
69 Polygon {
70 pts: Vec<[f32; 2]>,
71 fill: Rgba,
72 stroke: Option<(Rgba, f32)>,
73 },
74 Wedge {
76 cx: f32,
77 cy: f32,
78 r_outer: f32,
79 r_inner: f32,
80 a0: f32,
81 a1: f32,
82 fill: Rgba,
83 stroke: Option<(Rgba, f32)>,
84 },
85 Circle {
86 cx: f32,
87 cy: f32,
88 r: f32,
89 fill: Option<Rgba>,
90 stroke: Option<(Rgba, f32)>,
91 },
92 Text {
94 x: f32,
95 y: f32,
96 text: String,
97 size: f32,
98 color: Rgba,
99 bold: bool,
100 align: HAlign,
101 valign: VAlign,
102 rotation: f32,
103 },
104}
105
106pub trait Measure {
108 fn text_width(&self, text: &str, size: f32, bold: bool) -> f32;
109}
110
111#[derive(Clone, Copy, Debug, Default)]
113pub struct ApproxMeasure;
114
115impl Measure for ApproxMeasure {
116 fn text_width(&self, text: &str, size: f32, bold: bool) -> f32 {
117 let n = text.chars().count() as f32;
118 n * 0.55 * size * if bold { 1.1 } else { 1.0 }
119 }
120}
121
122#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
124pub struct ChartData {
125 pub categories: Vec<String>,
126 #[serde(default)]
130 pub category_levels: Vec<Vec<String>>,
131 pub series: Vec<SeriesData>,
132 #[serde(default)]
134 pub palette: Vec<Rgba>,
135}
136
137#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
138pub struct SeriesData {
139 pub name: String,
140 pub values: Vec<Option<f64>>,
141 pub x: Option<Vec<Option<f64>>>,
143 pub sizes: Option<Vec<Option<f64>>>,
145 pub color: Rgba,
146 pub kind: ChartKind,
148 pub secondary: bool,
150 pub smooth: bool,
152 pub trendlines: Vec<TrendlineData>,
154 pub number_format: String,
156}
157
158#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
160pub struct TrendlineData {
161 pub spec: Trendline,
162 pub color: Rgba,
163}
164
165impl Default for SeriesData {
166 fn default() -> Self {
167 SeriesData {
168 name: String::new(),
169 values: vec![],
170 x: None,
171 sizes: None,
172 color: [0x15, 0x60, 0x82, 0xFF],
173 kind: ChartKind::ColumnClustered,
174 secondary: false,
175 smooth: false,
176 trendlines: vec![],
177 number_format: "General".into(),
178 }
179 }
180}