1pub mod area;
8pub mod bar;
9pub mod dot;
10pub mod line;
11pub mod pie;
12
13use crate::font::metrics::StandardFontMetrics;
14use crate::font::StandardFont;
15use crate::style::Color;
16
17#[derive(Debug, Clone)]
19pub enum ChartPrimitive {
20 Rect {
22 x: f64,
23 y: f64,
24 w: f64,
25 h: f64,
26 fill: Color,
27 },
28 Line {
30 x1: f64,
31 y1: f64,
32 x2: f64,
33 y2: f64,
34 stroke: Color,
35 width: f64,
36 },
37 Polyline {
39 points: Vec<(f64, f64)>,
40 stroke: Color,
41 width: f64,
42 },
43 FilledPath {
45 points: Vec<(f64, f64)>,
46 fill: Color,
47 opacity: f64,
48 },
49 Circle {
51 cx: f64,
52 cy: f64,
53 r: f64,
54 fill: Color,
55 },
56 ArcSector {
58 cx: f64,
59 cy: f64,
60 r: f64,
61 start_angle: f64,
62 end_angle: f64,
63 fill: Color,
64 },
65 Label {
67 text: String,
68 x: f64,
69 y: f64,
70 font_size: f64,
71 color: Color,
72 anchor: TextAnchor,
73 },
74 VerticalLabel {
77 text: String,
78 x: f64,
79 y: f64,
80 font_size: f64,
81 color: Color,
82 },
83}
84
85#[derive(Debug, Clone, Copy)]
87pub enum TextAnchor {
88 Left,
89 Center,
90 Right,
91}
92
93pub const DEFAULT_COLORS: &[&str] = &[
97 "#1a365d", "#2b6cb0", "#3182ce", "#4299e1", "#63b3ed", "#90cdf4", "#e53e3e", "#dd6b20",
98 "#38a169", "#805ad5",
99];
100
101pub const Y_AXIS_WIDTH: f64 = 28.0;
102pub const X_AXIS_HEIGHT: f64 = 20.0;
103pub const AXIS_LABEL_FONT: f64 = 8.0;
104pub const LABEL_MARGIN: f64 = 4.0;
105pub const TITLE_FONT: f64 = 11.0;
106pub const TITLE_HEIGHT: f64 = 20.0;
107pub const GRID_COLOR: Color = Color {
108 r: 0.88,
109 g: 0.88,
110 b: 0.88,
111 a: 1.0,
112};
113pub const AXIS_COLOR: Color = Color {
114 r: 0.4,
115 g: 0.4,
116 b: 0.4,
117 a: 1.0,
118};
119pub const LABEL_COLOR: Color = Color {
120 r: 0.3,
121 g: 0.3,
122 b: 0.3,
123 a: 1.0,
124};
125
126fn helvetica_metrics() -> StandardFontMetrics {
130 StandardFont::Helvetica.metrics()
131}
132
133pub fn measure_label(text: &str, font_size: f64) -> f64 {
135 helvetica_metrics().measure_string(text, font_size, 0.0)
136}
137
138pub fn nice_number(value: f64) -> f64 {
140 if value <= 0.0 {
141 return 1.0;
142 }
143 let exp = value.log10().floor();
144 let frac = value / 10.0_f64.powf(exp);
145 let nice = if frac <= 1.0 {
146 1.0
147 } else if frac <= 2.0 {
148 2.0
149 } else if frac <= 5.0 {
150 5.0
151 } else {
152 10.0
153 };
154 nice * 10.0_f64.powf(exp)
155}
156
157pub fn format_number(value: f64) -> String {
159 if value.abs() >= 1_000_000.0 {
160 format!("{:.1}M", value / 1_000_000.0)
161 } else if value.abs() >= 1_000.0 {
162 format!("{:.1}K", value / 1_000.0)
163 } else if value == value.floor() {
164 format!("{}", value as i64)
165 } else {
166 format!("{:.1}", value)
167 }
168}
169
170pub fn lighten_color(color: &Color, factor: f64) -> Color {
172 Color {
173 r: color.r + (1.0 - color.r) * factor,
174 g: color.g + (1.0 - color.g) * factor,
175 b: color.b + (1.0 - color.b) * factor,
176 a: color.a,
177 }
178}
179
180pub fn parse_hex_color(hex: &str) -> Color {
182 let hex = hex.trim_start_matches('#');
183 match hex.len() {
184 3 => {
185 let r = u8::from_str_radix(&hex[0..1].repeat(2), 16).unwrap_or(0);
186 let g = u8::from_str_radix(&hex[1..2].repeat(2), 16).unwrap_or(0);
187 let b = u8::from_str_radix(&hex[2..3].repeat(2), 16).unwrap_or(0);
188 Color::rgb(r as f64 / 255.0, g as f64 / 255.0, b as f64 / 255.0)
189 }
190 6 => {
191 let r = u8::from_str_radix(&hex[0..2], 16).unwrap_or(0);
192 let g = u8::from_str_radix(&hex[2..4], 16).unwrap_or(0);
193 let b = u8::from_str_radix(&hex[4..6], 16).unwrap_or(0);
194 Color::rgb(r as f64 / 255.0, g as f64 / 255.0, b as f64 / 255.0)
195 }
196 _ => Color::BLACK,
197 }
198}
199
200pub fn resolve_color(custom: Option<&str>, index: usize) -> Color {
202 match custom {
203 Some(c) => parse_hex_color(c),
204 None => parse_hex_color(DEFAULT_COLORS[index % DEFAULT_COLORS.len()]),
205 }
206}