Skip to main content

charts_rs/charts/
common.rs

1// Licensed under the Apache License, Version 2.0 (the "License");
2// you may not use this file except in compliance with the License.
3// You may obtain a copy of the License at
4//
5//     http://www.apache.org/licenses/LICENSE-2.0
6//
7// Unless required by applicable law or agreed to in writing, software
8// distributed under the License is distributed on an "AS IS" BASIS,
9// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10// See the License for the specific language governing permissions and
11// limitations under the License.
12
13use super::{Box, Color, NIL_VALUE};
14use crate::Point;
15use serde::{Deserialize, Serialize};
16
17#[derive(Serialize, Deserialize, Clone, PartialEq, Debug, Default)]
18pub enum AxisScale {
19    #[default]
20    Linear,
21    /// Logarithmic scale; the field is the base (commonly 10.0).
22    Log(f32),
23}
24
25#[derive(Clone, PartialEq, Debug, Default, Serialize, Deserialize)]
26pub enum Position {
27    #[default]
28    Left,
29    Top,
30    Right,
31    Bottom,
32    Inside,
33}
34
35#[derive(Serialize, Deserialize, Clone, PartialEq, Debug, Default)]
36pub enum Align {
37    Left,
38    #[default]
39    Center,
40    Right,
41}
42
43#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
44pub enum Symbol {
45    None,
46    /// Circle: (radius, optional fill color override)
47    Circle(f32, Option<Color>),
48    /// Square: (half-side, optional fill color override)
49    Rect(f32, Option<Color>),
50    /// Equilateral triangle pointing up: (circumradius, optional fill color override)
51    Triangle(f32, Option<Color>),
52    /// Diamond (rotated square): (half-diagonal, optional fill color override)
53    Diamond(f32, Option<Color>),
54}
55
56#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
57pub enum SeriesCategory {
58    Line,
59    Bar,
60}
61
62#[derive(Serialize, Deserialize, Clone, PartialEq, Debug, Default)]
63pub enum MarkLineCategory {
64    #[default]
65    Average,
66    Min,
67    Max,
68}
69
70#[derive(Serialize, Deserialize, Clone, PartialEq, Debug, Default)]
71pub enum MarkPointCategory {
72    #[default]
73    Min,
74    Max,
75}
76
77#[derive(Serialize, Deserialize, Clone, PartialEq, Debug, Default)]
78pub struct MarkLine {
79    pub category: MarkLineCategory,
80}
81
82#[derive(Serialize, Deserialize, Clone, PartialEq, Debug, Default)]
83pub struct MarkPoint {
84    pub category: MarkPointCategory,
85}
86
87#[derive(Serialize, Deserialize, Clone, PartialEq, Debug, Default)]
88pub struct Series {
89    // name of series
90    pub name: String,
91    // data list of series; `None` marks a missing / null data point
92    pub data: Vec<Option<f32>>,
93    // start index of series
94    pub start_index: usize,
95    // index of series
96    pub index: Option<usize>,
97    // y axis index of series
98    pub y_axis_index: usize,
99    // whether to display the label
100    pub label_show: bool,
101    // mark lines
102    pub mark_lines: Vec<MarkLine>,
103    // mark points
104    pub mark_points: Vec<MarkPoint>,
105    // colors of series bar
106    pub colors: Option<Vec<Option<Color>>>,
107    // category of series
108    pub category: Option<SeriesCategory>,
109    // stroke dash array for series
110    pub stroke_dash_array: Option<String>,
111    // stack group name; series with the same name and y_axis_index are stacked
112    pub stack: Option<String>,
113}
114
115/// Animation configuration for SVG chart animations.
116/// When set, bars grow from the bottom, lines draw progressively, and
117/// pie / sunburst slices expand from the center while labels fade in.
118/// PNG/JPEG export via resvg renders the fully-drawn static state.
119#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
120pub struct AnimationConfig {
121    /// Total animation duration in milliseconds (default: 1000).
122    pub duration: u32,
123    /// CSS easing function: "ease", "linear", "ease-in", "ease-out", "ease-in-out" (default: "ease").
124    pub easing: String,
125    /// Stagger delay in milliseconds between each column (bars), series
126    /// (lines), slice (pie), or ring level (sunburst) (default: 80).
127    pub delay: u32,
128}
129
130impl Default for AnimationConfig {
131    fn default() -> Self {
132        AnimationConfig {
133            duration: 1000,
134            easing: "ease".to_string(),
135            delay: 80,
136        }
137    }
138}
139
140#[derive(Clone, PartialEq, Debug, Default)]
141pub struct SeriesLabel {
142    pub point: Point,
143    pub text: String,
144}
145
146impl Series {
147    /// Creates a series from a flat value list. For backward compatibility the
148    /// legacy `NIL_VALUE` sentinel is mapped to a missing point (`None`).
149    pub fn new(name: String, data: Vec<f32>) -> Self {
150        Series {
151            name,
152            data: data
153                .into_iter()
154                .map(|v| if v == NIL_VALUE { None } else { Some(v) })
155                .collect(),
156            index: None,
157            ..Default::default()
158        }
159    }
160    /// Creates a series from nullable values, where `None` marks a missing
161    /// data point (rendered as a gap).
162    pub fn new_nullable(name: String, data: Vec<Option<f32>>) -> Self {
163        Series {
164            name,
165            data,
166            index: None,
167            ..Default::default()
168        }
169    }
170    /// Effective values with the legacy `NIL_VALUE` sentinel substituted for
171    /// missing points. Lets the renderers keep their existing sentinel-based
172    /// arithmetic while the public data model uses `Option<f32>`.
173    pub(crate) fn data_values(&self) -> Vec<f32> {
174        self.data.iter().map(|v| v.unwrap_or(NIL_VALUE)).collect()
175    }
176}
177impl From<(&str, Vec<f32>)> for Series {
178    fn from(value: (&str, Vec<f32>)) -> Self {
179        Series::new(value.0.to_string(), value.1)
180    }
181}
182impl From<(&str, Vec<Option<f32>>)> for Series {
183    fn from(value: (&str, Vec<Option<f32>>)) -> Self {
184        Series::new_nullable(value.0.to_string(), value.1)
185    }
186}
187
188#[derive(Serialize, Deserialize, Clone, Debug, Default)]
189pub struct YAxisConfig {
190    pub axis_font_size: f32,
191    pub axis_font_color: Color,
192    pub axis_font_weight: Option<String>,
193    pub axis_stroke_color: Color,
194    pub axis_width: Option<f32>,
195    pub axis_split_number: usize,
196    pub axis_name_gap: f32,
197    pub axis_name_align: Option<Align>,
198    pub axis_margin: Option<Box>,
199    pub axis_formatter: Option<String>,
200    pub axis_min: Option<f32>,
201    pub axis_max: Option<f32>,
202    pub axis_scale: AxisScale,
203}
204
205/// A fill that can be either a solid color or a linear gradient.
206#[derive(Clone, Copy, PartialEq, Debug)]
207pub enum Fill {
208    Solid(Color),
209    LinearGradient {
210        start_color: Color,
211        end_color: Color,
212        /// Angle in degrees: 0 = top→bottom, 90 = left→right, 180 = bottom→top, 270 = right→left.
213        angle: f32,
214    },
215}
216
217impl Default for Fill {
218    fn default() -> Self {
219        Fill::Solid(Color::default())
220    }
221}
222
223impl From<Color> for Fill {
224    fn from(c: Color) -> Self {
225        Fill::Solid(c)
226    }
227}
228
229impl Fill {
230    /// Returns true if the fill is fully transparent (only for solid fills).
231    pub fn is_transparent(&self) -> bool {
232        matches!(self, Fill::Solid(c) if c.is_transparent())
233    }
234}