1use super::common::AxisScale;
14use serde::{Deserialize, Serialize};
15use std::fmt;
16use substring::Substring;
17
18pub static NIL_VALUE: f32 = f32::MIN;
19
20pub(crate) static THOUSANDS_FORMAT_LABEL: &str = "{t}";
21pub(crate) static SERIES_NAME_FORMAT_LABEL: &str = "{a}";
22pub(crate) static CATEGORY_NAME_FORMAT_LABEL: &str = "{b}";
23pub(crate) static VALUE_FORMAT_LABEL: &str = "{c}";
24pub(crate) static PERCENTAGE_FORMAT_LABEL: &str = "{d}";
25
26#[derive(Clone, Copy, PartialEq, Debug, Default)]
27pub struct Point {
28 pub x: f32,
29 pub y: f32,
30}
31impl From<(f32, f32)> for Point {
32 fn from(val: (f32, f32)) -> Self {
33 Point { x: val.0, y: val.1 }
34 }
35}
36impl fmt::Display for Point {
37 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
38 let m = format!("({},{})", format_float(self.x), format_float(self.y));
39 write!(f, "{m}")
40 }
41}
42
43#[derive(Serialize, Deserialize, Clone, Debug, Default)]
44pub struct Box {
45 pub left: f32,
46 pub top: f32,
47 pub right: f32,
48 pub bottom: f32,
49}
50impl Box {
51 pub fn width(&self) -> f32 {
52 self.right - self.left
53 }
54 pub fn height(&self) -> f32 {
55 self.bottom - self.top
56 }
57 pub fn outer_width(&self) -> f32 {
58 self.right
59 }
60 pub fn outer_height(&self) -> f32 {
61 self.bottom
62 }
63}
64impl fmt::Display for Box {
65 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
66 let m = format!(
67 "({},{},{},{})",
68 format_float(self.left),
69 format_float(self.top),
70 format_float(self.right),
71 format_float(self.bottom)
72 );
73 write!(f, "{m}")
74 }
75}
76
77impl From<f32> for Box {
78 fn from(val: f32) -> Self {
79 Box {
80 left: val,
81 top: val,
82 right: val,
83 bottom: val,
84 }
85 }
86}
87impl From<(f32, f32)> for Box {
88 fn from(val: (f32, f32)) -> Self {
89 Box {
90 left: val.0,
91 top: val.1,
92 right: val.0,
93 bottom: val.1,
94 }
95 }
96}
97impl From<(f32, f32, f32)> for Box {
98 fn from(val: (f32, f32, f32)) -> Self {
99 Box {
100 left: val.0,
101 top: val.1,
102 right: val.2,
103 bottom: val.1,
104 }
105 }
106}
107impl From<(f32, f32, f32, f32)> for Box {
108 fn from(val: (f32, f32, f32, f32)) -> Self {
109 Box {
110 left: val.0,
111 top: val.1,
112 right: val.2,
113 bottom: val.3,
114 }
115 }
116}
117
118fn parse_precision(formatter: &str) -> Option<usize> {
119 if formatter.is_empty() {
120 return None;
121 }
122 if let Ok(precision) = formatter.parse::<usize>() {
124 return Some(precision);
125 }
126
127 if let Some(inner) = formatter
129 .strip_prefix("{:.")
130 .and_then(|s| s.strip_suffix("}"))
131 && let Ok(precision) = inner.parse::<usize>()
132 {
133 return Some(precision);
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 pub scale: AxisScale,
202}
203#[derive(Clone, Debug, Default)]
204pub struct AxisValues {
205 pub data: Vec<String>,
206 pub min: f32,
207 pub max: f32,
208 pub scale: AxisScale,
209}
210
211impl AxisValues {
212 pub(crate) fn get_offset(&self) -> f32 {
213 self.max - self.min
214 }
215 pub(crate) fn get_offset_height(&self, value: f32, max_height: f32) -> f32 {
216 match &self.scale {
217 AxisScale::Linear => {
218 let offset = self.get_offset();
219 if offset == 0.0 {
220 return max_height;
221 }
222 let percent = (value - self.min) / offset;
223 max_height - percent * max_height
224 }
225 AxisScale::Log(base) => {
226 let safe = value.max(f32::MIN_POSITIVE);
227 let log_val = safe.log(*base);
228 let log_min = self.min.max(f32::MIN_POSITIVE).log(*base);
229 let log_max = self.max.max(f32::MIN_POSITIVE).log(*base);
230 let log_range = log_max - log_min;
231 if log_range == 0.0 {
232 return max_height;
233 }
234 let percent = (log_val - log_min) / log_range;
235 max_height - percent * max_height
236 }
237 }
238 }
239}
240
241const K_VALUE: f32 = 1000.00_f32;
242const M_VALUE: f32 = K_VALUE * K_VALUE;
243const G_VALUE: f32 = M_VALUE * K_VALUE;
244const T_VALUE: f32 = G_VALUE * K_VALUE;
245
246fn format_axis_value(value: f32) -> String {
247 let mut v = value;
248 let mut unit = "";
249 v = if v >= T_VALUE {
250 unit = "T";
251 v / T_VALUE
252 } else if v >= G_VALUE {
253 unit = "G";
254 v / G_VALUE
255 } else if v >= M_VALUE {
256 unit = "M";
257 v / M_VALUE
258 } else if v >= K_VALUE {
259 unit = "k";
260 v / K_VALUE
261 } else {
262 v
263 };
264 format_float(v) + unit
265}
266
267fn get_log_axis_values(params: AxisValueParams, base: f32) -> AxisValues {
268 let split_number = if params.split_number == 0 {
269 6
270 } else {
271 params.split_number
272 };
273
274 let mut min_val = f32::MAX;
275 let mut max_val = f32::MIN_POSITIVE;
276 for &v in ¶ms.data_list {
277 if v != NIL_VALUE && v > 0.0 {
278 if v < min_val {
279 min_val = v;
280 }
281 if v > max_val {
282 max_val = v;
283 }
284 }
285 }
286 if let Some(m) = params.min
287 && m > 0.0
288 && m < min_val
289 {
290 min_val = m;
291 }
292 if let Some(m) = params.max
293 && m > 0.0
294 && m > max_val
295 {
296 max_val = m;
297 }
298
299 if min_val == f32::MAX || max_val <= 0.0 {
300 return AxisValues::default();
301 }
302
303 let exp_min = min_val.log(base).floor() as i32;
304 let exp_max = max_val.log(base).ceil() as i32;
305
306 let num_powers = (exp_max - exp_min).max(1) as usize;
308 let step = ((num_powers as f32 / split_number as f32).ceil() as i32).max(1);
309
310 let mut data = vec![];
311 let mut exp = exp_min;
312 loop {
313 data.push(format_axis_value(base.powi(exp)));
314 if exp >= exp_max {
315 break;
316 }
317 exp = (exp + step).min(exp_max);
318 }
319
320 if params.reverse.unwrap_or_default() {
321 data.reverse();
322 }
323
324 AxisValues {
325 data,
326 min: base.powi(exp_min),
327 max: base.powi(exp_max),
328 scale: AxisScale::Log(base),
329 }
330}
331
332pub(crate) fn get_axis_values(params: AxisValueParams) -> AxisValues {
333 if let AxisScale::Log(base) = params.scale {
334 return get_log_axis_values(params, base);
335 }
336
337 let mut min = f32::MAX;
338 let mut max = f32::MIN;
339
340 let mut split_number = params.split_number;
341 if split_number == 0 {
342 split_number = 6;
343 }
344 for item in params.data_list.iter() {
345 let value = item.to_owned();
346 if value == NIL_VALUE {
347 continue;
348 }
349 if value > max {
350 max = value;
351 }
352 if value < min {
353 min = value;
354 }
355 }
356 let mut is_custom_min = false;
357
358 if let Some(value) = params.min
359 && value < min
360 {
361 min = value;
362 is_custom_min = true;
363 }
364 if !is_custom_min && min > 0.0 {
366 min = 0.0;
367 }
368 let mut is_custom_max = false;
369 if let Some(value) = params.max
370 && value > max
371 {
372 max = value;
373 is_custom_max = true
374 }
375 let mut unit = (max - min) / split_number as f32;
376 if !is_custom_max {
377 let ceil_value = (unit * 10.0).ceil();
378 if ceil_value < 12.0 {
379 unit = ceil_value / 10.0;
380 } else {
381 let mut new_unit = unit as i32;
382 let adjust_unit = |current: i32, small_unit: i32| -> i32 {
383 if current % small_unit == 0 {
384 return current + small_unit;
385 }
386 ((current / small_unit) + 1) * small_unit
387 };
388 if new_unit < 10 {
389 new_unit = adjust_unit(new_unit, 2);
390 } else if new_unit < 100 {
391 new_unit = adjust_unit(new_unit, 5);
392 } else if new_unit < 500 {
393 new_unit = adjust_unit(new_unit, 10);
394 } else if new_unit < 1000 {
395 new_unit = adjust_unit(new_unit, 20);
396 } else if new_unit < 5000 {
397 new_unit = adjust_unit(new_unit, 50);
398 } else if new_unit < 10000 {
399 new_unit = adjust_unit(new_unit, 100);
400 } else {
401 let small_unit = ((max - min) / 20.0) as i32;
402 new_unit = adjust_unit(new_unit, small_unit / 100 * 100);
403 }
404 unit = new_unit as f32;
405 }
406 }
407 let split_unit = unit;
408
409 let mut data = vec![];
410 for i in 0..=split_number {
411 let value = min + (i as f32) * split_unit;
412 if params.thousands_format {
413 data.push(thousands_format_float(value));
414 } else {
415 data.push(format_axis_value(value));
416 }
417 }
418 if params.reverse.unwrap_or_default() {
419 data.reverse();
420 }
421
422 AxisValues {
423 data,
424 min,
425 max: min + split_unit * split_number as f32,
426 scale: AxisScale::Linear,
427 }
428}
429pub fn convert_to_points(values: &[(f32, f32)]) -> Vec<Point> {
430 values.iter().map(|item| item.to_owned().into()).collect()
431}
432
433pub fn get_quadrant(cx: f32, cy: f32, point: &Point) -> u8 {
434 if point.x > cx {
435 if point.y > cy { 4 } else { 1 }
436 } else if point.y > cy {
437 3
438 } else {
439 2
440 }
441}
442
443#[derive(Clone, Debug, Default)]
444pub(crate) struct LabelOption {
445 pub series_name: String,
446 pub category_name: String,
447 pub value: f32,
448 pub percentage: f32,
449 pub formatter: String,
450}
451impl LabelOption {
452 pub fn format(&self) -> String {
453 let value = format_float(self.value);
455 let percentage = format_float(self.percentage * 100.0) + "%";
456 if self.formatter.is_empty() {
457 return value;
458 }
459 self.formatter
460 .replace(SERIES_NAME_FORMAT_LABEL, &self.series_name)
461 .replace(CATEGORY_NAME_FORMAT_LABEL, &self.category_name)
462 .replace(VALUE_FORMAT_LABEL, &value)
463 .replace(PERCENTAGE_FORMAT_LABEL, &percentage)
464 .replace(THOUSANDS_FORMAT_LABEL, &thousands_format_float(self.value))
465 }
466}
467
468pub fn format_string(value: &str, formatter: &str) -> String {
469 if formatter.is_empty() {
470 value.to_string()
471 } else {
472 formatter
473 .replace(VALUE_FORMAT_LABEL, value)
474 .replace(THOUSANDS_FORMAT_LABEL, value)
475 }
476}
477
478pub(crate) fn get_pie_point(cx: f32, cy: f32, r: f32, angle: f32) -> Point {
479 let value = angle / 180.0 * std::f32::consts::PI;
480 let x = cx + r * value.sin();
481 let y = cy - r * value.cos();
482 Point { x, y }
483}
484pub(crate) fn get_box_of_points(points: &[Point]) -> Box {
485 let mut b = Box {
486 left: f32::MAX,
487 top: f32::MAX,
488 ..Default::default()
489 };
490 for p in points.iter() {
491 if p.x < b.left {
492 b.left = p.x;
493 }
494 if p.x > b.right {
495 b.right = p.x;
496 }
497 if p.y < b.top {
498 b.top = p.y;
499 }
500 if p.y > b.bottom {
501 b.bottom = p.y;
502 }
503 }
504 b
505}
506
507#[cfg(test)]
508mod tests {
509 use crate::{AxisScale, thousands_format_float};
510
511 use super::{
512 AxisValueParams, Box, Point, convert_to_points, format_float, get_axis_values,
513 get_box_of_points,
514 };
515 use pretty_assertions::assert_eq;
516
517 #[test]
518 fn point() {
519 let p: Point = (1.2, 1.3).into();
520
521 assert_eq!(1.2, p.x);
522 assert_eq!(1.3, p.y);
523 }
524
525 #[test]
526 fn box_width_height() {
527 let b: Box = (10.0).into();
528
529 assert_eq!(10.0, b.left);
530 assert_eq!(10.0, b.top);
531 assert_eq!(10.0, b.right);
532 assert_eq!(10.0, b.bottom);
533 assert_eq!(0.0, b.width());
534 assert_eq!(10.0, b.outer_width());
535 assert_eq!(0.0, b.height());
536 assert_eq!(10.0, b.outer_height());
537
538 let b: Box = (5.0, 10.0, 30.0, 50.0).into();
539 assert_eq!(5.0, b.left);
540 assert_eq!(10.0, b.top);
541 assert_eq!(30.0, b.right);
542 assert_eq!(50.0, b.bottom);
543 assert_eq!(25.0, b.width());
544 assert_eq!(30.0, b.outer_width());
545 assert_eq!(40.0, b.height());
546 assert_eq!(50.0, b.outer_height());
547 }
548
549 #[test]
550 fn format() {
551 assert_eq!("1", format_float(1.0));
552 assert_eq!("1.1", format_float(1.12));
553 assert_eq!("100.1", format_float(100.14));
554 assert_eq!("100", format_float(100.04));
555 assert_eq!("1000.1", format_float(1000.14));
556 }
557 #[test]
558 fn thousands_format() {
559 assert_eq!("1", thousands_format_float(1.0));
560 assert_eq!("1.1", thousands_format_float(1.12));
561 assert_eq!("100.1", thousands_format_float(100.14));
562 assert_eq!("100", thousands_format_float(100.04));
563 assert_eq!("1,000", thousands_format_float(1000.14));
564 assert_eq!("100,000", thousands_format_float(100000.14));
565 assert_eq!("1,000,000", thousands_format_float(1_000_000.1));
566 }
567
568 #[test]
569 fn axis_values() {
570 let values = get_axis_values(AxisValueParams {
571 data_list: vec![1.0, 10.0, 13.5, 18.9],
572 ..Default::default()
573 });
574
575 assert_eq!(vec!["0", "4", "8", "12", "16", "20", "24"], values.data);
576 assert_eq!(0.0, values.min);
577 assert_eq!(24.0, values.max);
578 assert_eq!(24.0, values.get_offset());
579 assert_eq!(50.0, values.get_offset_height(12.0, 100.0));
580 }
581
582 #[test]
583 fn axis_values_log() {
584 let values = get_axis_values(AxisValueParams {
586 data_list: vec![1.0, 5.0, 100.0, 800.0],
587 scale: AxisScale::Log(10.0),
588 ..Default::default()
589 });
590 assert_eq!(1.0, values.min);
593 assert_eq!(1000.0, values.max);
594 assert_eq!(vec!["1", "10", "100", "1k"], values.data);
596 let h = values.get_offset_height(10.0, 100.0);
598 assert!((h - 66.67).abs() < 0.1, "expected ~66.67, got {h}");
599 let h = values.get_offset_height(100.0, 100.0);
601 assert!((h - 33.33).abs() < 0.1, "expected ~33.33, got {h}");
602 assert!((values.get_offset_height(1.0, 100.0) - 100.0).abs() < 0.01);
604 assert!((values.get_offset_height(1000.0, 100.0)).abs() < 0.01);
605 }
606
607 #[test]
608 fn get_box() {
609 let points: Vec<Point> = convert_to_points(&[
610 (2.0, 10.0),
611 (50.0, 10.0),
612 (50.0, 30.0),
613 (150.0, 30.0),
614 (150.0, 80.0),
615 (210.0, 60.0),
616 (250.0, 90.0),
617 ]);
618 let b = get_box_of_points(&points);
619 assert_eq!(2.0, b.left);
620 assert_eq!(10.0, b.top);
621 assert_eq!(250.0, b.right);
622 assert_eq!(90.0, b.bottom);
623 }
624}