use crate::models::DataPoint;
use num_format::Locale;
use plotters::prelude::*;
use std::collections::BTreeSet;
use super::text::estimate_text_width_px;
const OFFICE10: [RGBColor; 10] = [
RGBColor(68, 114, 196), RGBColor(237, 125, 49), RGBColor(165, 165, 165), RGBColor(255, 192, 0), RGBColor(91, 155, 213), RGBColor(112, 173, 71), RGBColor(38, 68, 120), RGBColor(158, 72, 14), RGBColor(99, 99, 99), RGBColor(153, 115, 0), ];
#[inline]
pub fn office_color(idx: usize) -> RGBAColor {
OFFICE10[idx % OFFICE10.len()].to_rgba()
}
pub fn choose_axis_scale(max_abs: f64) -> (f64, &'static str) {
if max_abs >= 1.0e12 {
(1.0e12, "trillions")
} else if max_abs >= 1.0e9 {
(1.0e9, "billions")
} else if max_abs >= 1.0e6 {
(1.0e6, "millions")
} else if max_abs >= 1.0e3 {
(1.0e3, "thousands")
} else {
(1.0, "")
}
}
pub fn extract_unit_from_indicator_name(name: &str) -> Option<String> {
let open = name.rfind('(')?;
let close = name.rfind(')')?;
if close > open {
let inner = name[open + 1..close].trim();
if !inner.is_empty() {
Some(inner.to_string())
} else {
None
}
} else {
None
}
}
pub fn derive_axis_unit(points: &[DataPoint]) -> Option<String> {
let units: BTreeSet<&str> = points
.iter()
.filter_map(|p| p.unit.as_deref())
.filter(|u| !u.is_empty())
.collect();
if units.len() == 1 {
return units.iter().next().map(|u| u.to_string());
}
let names: BTreeSet<&str> = points.iter().map(|p| p.indicator_name.as_str()).collect();
if names.len() == 1 {
names.iter().next()
.and_then(|name| extract_unit_from_indicator_name(name))
} else {
None
}
}
pub fn is_percentage_like(unit: &str) -> bool {
let u = unit.to_ascii_lowercase();
u.contains('%') || u.contains("percent") || u.contains("percentage") || u.contains("per cent")
}
pub fn map_locale(tag: &str) -> (&'static Locale, char) {
match tag.to_lowercase().as_str() {
"de" | "de_de" | "german" => (&Locale::de, ','),
"fr" | "fr_fr" => (&Locale::fr, ','),
"es" | "es_es" => (&Locale::es, ','),
"it" | "it_it" => (&Locale::it, ','),
"pt" | "pt_pt" | "pt_br" => (&Locale::pt, ','),
"nl" | "nl_nl" => (&Locale::nl, ','),
_ => (&Locale::en, '.'), }
}
pub fn compute_left_label_area_px(
ymin_scaled: f64,
ymax_scaled: f64,
ticks: usize,
font_px: u32,
) -> u32 {
let y_label_fmt = |v: f64| {
let a = v.abs();
let prec = if a >= 100.0 {
0
} else if a >= 10.0 {
1
} else {
2
};
format!("{:.*}", prec, v)
};
let mut max_px = 0u32;
for i in 0..=ticks {
let t = if ticks == 0 {
0.0
} else {
i as f64 / ticks as f64
};
let v = ymin_scaled + (ymax_scaled - ymin_scaled) * t;
let s = y_label_fmt(v);
max_px = max_px.max(estimate_text_width_px(&s, font_px));
}
let with_padding = max_px.saturating_add(18);
with_padding.clamp(48, 140)
}