1use serde::{Deserialize, Serialize};
14use std::fmt;
15use substring::Substring;
16
17pub static NIL_VALUE: f32 = f32::MIN;
18
19pub(crate) static THOUSANDS_FORMAT_LABEL: &str = "{t}";
20pub(crate) static SERIES_NAME_FORMAT_LABEL: &str = "{a}";
21pub(crate) static CATEGORY_NAME_FORMAT_LABEL: &str = "{b}";
22pub(crate) static VALUE_FORMAT_LABEL: &str = "{c}";
23pub(crate) static PERCENTAGE_FORMAT_LABEL: &str = "{d}";
24
25#[derive(Clone, Copy, PartialEq, Debug, Default)]
26pub struct Point {
27 pub x: f32,
28 pub y: f32,
29}
30impl From<(f32, f32)> for Point {
31 fn from(val: (f32, f32)) -> Self {
32 Point { x: val.0, y: val.1 }
33 }
34}
35impl fmt::Display for Point {
36 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
37 let m = format!("({},{})", format_float(self.x), format_float(self.y));
38 write!(f, "{m}")
39 }
40}
41
42#[derive(Serialize, Deserialize, Clone, Debug, Default)]
43pub struct Box {
44 pub left: f32,
45 pub top: f32,
46 pub right: f32,
47 pub bottom: f32,
48}
49impl Box {
50 pub fn width(&self) -> f32 {
51 self.right - self.left
52 }
53 pub fn height(&self) -> f32 {
54 self.bottom - self.top
55 }
56 pub fn outer_width(&self) -> f32 {
57 self.right
58 }
59 pub fn outer_height(&self) -> f32 {
60 self.bottom
61 }
62}
63impl fmt::Display for Box {
64 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
65 let m = format!(
66 "({},{},{},{})",
67 format_float(self.left),
68 format_float(self.top),
69 format_float(self.right),
70 format_float(self.bottom)
71 );
72 write!(f, "{m}")
73 }
74}
75
76impl From<f32> for Box {
77 fn from(val: f32) -> Self {
78 Box {
79 left: val,
80 top: val,
81 right: val,
82 bottom: val,
83 }
84 }
85}
86impl From<(f32, f32)> for Box {
87 fn from(val: (f32, f32)) -> Self {
88 Box {
89 left: val.0,
90 top: val.1,
91 right: val.0,
92 bottom: val.1,
93 }
94 }
95}
96impl From<(f32, f32, f32)> for Box {
97 fn from(val: (f32, f32, f32)) -> Self {
98 Box {
99 left: val.0,
100 top: val.1,
101 right: val.2,
102 bottom: val.1,
103 }
104 }
105}
106impl From<(f32, f32, f32, f32)> for Box {
107 fn from(val: (f32, f32, f32, f32)) -> Self {
108 Box {
109 left: val.0,
110 top: val.1,
111 right: val.2,
112 bottom: val.3,
113 }
114 }
115}
116
117fn parse_precision(formatter: &str) -> Option<usize> {
118 if formatter.is_empty() {
119 return None;
120 }
121 if let Ok(precision) = formatter.parse::<usize>() {
123 return Some(precision);
124 }
125
126 if let Some(inner) = formatter
128 .strip_prefix("{:.")
129 .and_then(|s| s.strip_suffix("}"))
130 {
131 if let Ok(precision) = inner.parse::<usize>() {
132 return Some(precision);
133 }
134 }
135
136 None
137}
138
139pub(crate) fn format_series_value(value: f32, formatter: &str) -> String {
140 if formatter == THOUSANDS_FORMAT_LABEL {
141 return thousands_format_float(value);
142 }
143 let mut str = if let Some(precision) = parse_precision(formatter) {
144 format!("{:.precision$}", value, precision = precision)
145 } else if value < 1.1 {
146 format!("{:.2}", value)
147 } else {
148 format!("{:.1}", value)
149 };
150 if str.contains('.') {
151 while str.ends_with('0') {
152 str.pop();
153 }
154
155 if str.ends_with('.') {
156 str.pop();
157 }
158 }
159
160 str
161}
162
163pub(crate) fn thousands_format_float(value: f32) -> String {
164 if value < 1000.0 {
165 return format_float(value);
166 }
167 let str = format!("{:.0}", value);
168 let unit = 3;
169 let mut index = str.len() % unit;
170 let mut arr = vec![];
171 if index != 0 {
172 arr.push(str.substring(0, index))
173 }
174
175 loop {
176 if index >= str.len() {
177 break;
178 }
179 arr.push(str.substring(index, index + unit));
180 index += unit;
181 }
182 arr.join(",")
183}
184
185pub(crate) fn format_float(value: f32) -> String {
186 let str = format!("{:.1}", value);
187 if str.ends_with(".0") {
188 return str.substring(0, str.len() - 2).to_string();
189 }
190 str
191}
192
193#[derive(Clone, Debug, Default)]
194pub(crate) struct AxisValueParams {
195 pub data_list: Vec<f32>,
196 pub min: Option<f32>,
197 pub max: Option<f32>,
198 pub split_number: usize,
199 pub reverse: Option<bool>,
200 pub thousands_format: bool,
201}
202#[derive(Clone, Debug, Default)]
203pub struct AxisValues {
204 pub data: Vec<String>,
205 pub min: f32,
206 pub max: f32,
207}
208
209impl AxisValues {
210 fn get_offset(&self) -> f32 {
211 self.max - self.min
212 }
213 pub(crate) fn get_offset_height(&self, value: f32, max_height: f32) -> f32 {
214 let percent = (value - self.min) / self.get_offset();
215 max_height - percent * max_height
216 }
217}
218
219const K_VALUE: f32 = 1000.00_f32;
220const M_VALUE: f32 = K_VALUE * K_VALUE;
221const G_VALUE: f32 = M_VALUE * K_VALUE;
222const T_VALUE: f32 = G_VALUE * K_VALUE;
223
224pub(crate) fn get_axis_values(params: AxisValueParams) -> AxisValues {
225 let mut min = f32::MAX;
226 let mut max = f32::MIN;
227
228 let mut split_number = params.split_number;
229 if split_number == 0 {
230 split_number = 6;
231 }
232 for item in params.data_list.iter() {
233 let value = item.to_owned();
234 if value == NIL_VALUE {
235 continue;
236 }
237 if value > max {
238 max = value;
239 }
240 if value < min {
241 min = value;
242 }
243 }
244 let mut is_custom_min = false;
245
246 if let Some(value) = params.min {
247 if value < min {
248 min = value;
249 is_custom_min = true;
250 }
251 }
252 if !is_custom_min && min > 0.0 {
254 min = 0.0;
255 }
256 let mut is_custom_max = false;
257 if let Some(value) = params.max {
258 if value > max {
259 max = value;
260 is_custom_max = true
261 }
262 }
263 let mut unit = (max - min) / split_number as f32;
264 if !is_custom_max {
265 let ceil_value = (unit * 10.0).ceil();
266 if ceil_value < 12.0 {
267 unit = ceil_value / 10.0;
268 } else {
269 let mut new_unit = unit as i32;
270 let adjust_unit = |current: i32, small_unit: i32| -> i32 {
271 if current % small_unit == 0 {
272 return current + small_unit;
273 }
274 ((current / small_unit) + 1) * small_unit
275 };
276 if new_unit < 10 {
277 new_unit = adjust_unit(new_unit, 2);
278 } else if new_unit < 100 {
279 new_unit = adjust_unit(new_unit, 5);
280 } else if new_unit < 500 {
281 new_unit = adjust_unit(new_unit, 10);
282 } else if new_unit < 1000 {
283 new_unit = adjust_unit(new_unit, 20);
284 } else if new_unit < 5000 {
285 new_unit = adjust_unit(new_unit, 50);
286 } else if new_unit < 10000 {
287 new_unit = adjust_unit(new_unit, 100);
288 } else {
289 let small_unit = ((max - min) / 20.0) as i32;
290 new_unit = adjust_unit(new_unit, small_unit / 100 * 100);
291 }
292 unit = new_unit as f32;
293 }
294 }
295 let split_unit = unit;
296
297 let mut data = vec![];
298 for i in 0..=split_number {
299 let mut value = min + (i as f32) * split_unit;
300 if params.thousands_format {
301 data.push(thousands_format_float(value));
302 continue;
303 }
304 let mut unit = "";
305 value = if value >= T_VALUE {
306 unit = "T";
307 value / T_VALUE
308 } else if value >= G_VALUE {
309 unit = "G";
310 value / G_VALUE
311 } else if value >= M_VALUE {
312 unit = "M";
313 value / M_VALUE
314 } else if value >= K_VALUE {
315 unit = "k";
316 value / K_VALUE
317 } else {
318 value
319 };
320 data.push(format_float(value) + unit);
321 }
322 if params.reverse.unwrap_or_default() {
323 data.reverse();
324 }
325
326 AxisValues {
327 data,
328 min,
329 max: min + split_unit * split_number as f32,
330 }
331}
332pub fn convert_to_points(values: &[(f32, f32)]) -> Vec<Point> {
333 values.iter().map(|item| item.to_owned().into()).collect()
334}
335
336pub fn get_quadrant(cx: f32, cy: f32, point: &Point) -> u8 {
337 if point.x > cx {
338 if point.y > cy {
339 4
340 } else {
341 1
342 }
343 } else if point.y > cy {
344 3
345 } else {
346 2
347 }
348}
349
350#[derive(Clone, Debug, Default)]
351pub(crate) struct LabelOption {
352 pub series_name: String,
353 pub category_name: String,
354 pub value: f32,
355 pub percentage: f32,
356 pub formatter: String,
357}
358impl LabelOption {
359 pub fn format(&self) -> String {
360 let value = format_float(self.value);
362 let percentage = format_float(self.percentage * 100.0) + "%";
363 if self.formatter.is_empty() {
364 return value;
365 }
366 self.formatter
367 .replace(SERIES_NAME_FORMAT_LABEL, &self.series_name)
368 .replace(CATEGORY_NAME_FORMAT_LABEL, &self.category_name)
369 .replace(VALUE_FORMAT_LABEL, &value)
370 .replace(PERCENTAGE_FORMAT_LABEL, &percentage)
371 .replace(THOUSANDS_FORMAT_LABEL, &thousands_format_float(self.value))
372 }
373}
374
375pub fn format_string(value: &str, formatter: &str) -> String {
376 if formatter.is_empty() {
377 value.to_string()
378 } else {
379 formatter
380 .replace(VALUE_FORMAT_LABEL, value)
381 .replace(THOUSANDS_FORMAT_LABEL, value)
382 }
383}
384
385pub(crate) fn get_pie_point(cx: f32, cy: f32, r: f32, angle: f32) -> Point {
386 let value = angle / 180.0 * std::f32::consts::PI;
387 let x = cx + r * value.sin();
388 let y = cy - r * value.cos();
389 Point { x, y }
390}
391pub(crate) fn get_box_of_points(points: &[Point]) -> Box {
392 let mut b = Box {
393 left: f32::MAX,
394 top: f32::MAX,
395 ..Default::default()
396 };
397 for p in points.iter() {
398 if p.x < b.left {
399 b.left = p.x;
400 }
401 if p.x > b.right {
402 b.right = p.x;
403 }
404 if p.y < b.top {
405 b.top = p.y;
406 }
407 if p.y > b.bottom {
408 b.bottom = p.y;
409 }
410 }
411 b
412}
413
414#[cfg(test)]
415mod tests {
416 use crate::thousands_format_float;
417
418 use super::{
419 convert_to_points, format_float, get_axis_values, get_box_of_points, AxisValueParams, Box,
420 Point,
421 };
422 use pretty_assertions::assert_eq;
423
424 #[test]
425 fn point() {
426 let p: Point = (1.2, 1.3).into();
427
428 assert_eq!(1.2, p.x);
429 assert_eq!(1.3, p.y);
430 }
431
432 #[test]
433 fn box_width_height() {
434 let b: Box = (10.0).into();
435
436 assert_eq!(10.0, b.left);
437 assert_eq!(10.0, b.top);
438 assert_eq!(10.0, b.right);
439 assert_eq!(10.0, b.bottom);
440 assert_eq!(0.0, b.width());
441 assert_eq!(10.0, b.outer_width());
442 assert_eq!(0.0, b.height());
443 assert_eq!(10.0, b.outer_height());
444
445 let b: Box = (5.0, 10.0, 30.0, 50.0).into();
446 assert_eq!(5.0, b.left);
447 assert_eq!(10.0, b.top);
448 assert_eq!(30.0, b.right);
449 assert_eq!(50.0, b.bottom);
450 assert_eq!(25.0, b.width());
451 assert_eq!(30.0, b.outer_width());
452 assert_eq!(40.0, b.height());
453 assert_eq!(50.0, b.outer_height());
454 }
455
456 #[test]
457 fn format() {
458 assert_eq!("1", format_float(1.0));
459 assert_eq!("1.1", format_float(1.12));
460 assert_eq!("100.1", format_float(100.14));
461 assert_eq!("100", format_float(100.04));
462 assert_eq!("1000.1", format_float(1000.14));
463 }
464 #[test]
465 fn thousands_format() {
466 assert_eq!("1", thousands_format_float(1.0));
467 assert_eq!("1.1", thousands_format_float(1.12));
468 assert_eq!("100.1", thousands_format_float(100.14));
469 assert_eq!("100", thousands_format_float(100.04));
470 assert_eq!("1,000", thousands_format_float(1000.14));
471 assert_eq!("100,000", thousands_format_float(100000.14));
472 assert_eq!("1,000,000", thousands_format_float(1_000_000.1));
473 }
474
475 #[test]
476 fn axis_values() {
477 let values = get_axis_values(AxisValueParams {
478 data_list: vec![1.0, 10.0, 13.5, 18.9],
479 ..Default::default()
480 });
481
482 assert_eq!(vec!["0", "4", "8", "12", "16", "20", "24"], values.data);
483 assert_eq!(0.0, values.min);
484 assert_eq!(24.0, values.max);
485 assert_eq!(24.0, values.get_offset());
486 assert_eq!(50.0, values.get_offset_height(12.0, 100.0));
487 }
488
489 #[test]
490 fn get_box() {
491 let points: Vec<Point> = convert_to_points(&[
492 (2.0, 10.0),
493 (50.0, 10.0),
494 (50.0, 30.0),
495 (150.0, 30.0),
496 (150.0, 80.0),
497 (210.0, 60.0),
498 (250.0, 90.0),
499 ]);
500 let b = get_box_of_points(&points);
501 assert_eq!(2.0, b.left);
502 assert_eq!(10.0, b.top);
503 assert_eq!(250.0, b.right);
504 assert_eq!(90.0, b.bottom);
505 }
506}