ggplot_rs/scale/
sec_axis.rs1pub type SecLabeller = std::sync::Arc<dyn Fn(f64) -> String + Send + Sync>;
3
4#[derive(Clone)]
14pub struct SecAxis {
15 pub transform: fn(f64) -> f64,
17 pub name: String,
19 pub breaks: Option<Vec<f64>>,
21 pub percent_of_max: bool,
24 pub linear: Option<(f64, f64)>,
27 pub labeller: Option<SecLabeller>,
30}
31
32impl SecAxis {
33 pub fn new(transform: fn(f64) -> f64) -> Self {
35 SecAxis {
36 transform,
37 name: String::new(),
38 breaks: None,
39 percent_of_max: false,
40 linear: None,
41 labeller: None,
42 }
43 }
44
45 pub fn percent_of_max() -> Self {
51 SecAxis {
52 percent_of_max: true,
53 ..SecAxis::new(|v| v)
54 }
55 }
56
57 pub fn linear(scale: f64, offset: f64) -> Self {
63 let ok = scale.is_finite() && scale != 0.0 && offset.is_finite();
64 SecAxis {
65 linear: Some(if ok { (scale, offset) } else { (1.0, 0.0) }),
66 ..SecAxis::new(|v| v)
67 }
68 }
69
70 pub fn with_labeller(mut self, f: impl Fn(f64) -> String + Send + Sync + 'static) -> Self {
73 self.labeller = Some(std::sync::Arc::new(f));
74 self
75 }
76
77 pub fn with_name(mut self, name: &str) -> Self {
78 self.name = name.to_string();
79 self
80 }
81
82 pub fn with_breaks(mut self, breaks: Vec<f64>) -> Self {
83 self.breaks = Some(breaks);
84 self
85 }
86
87 pub fn transform_value(&self, v: f64) -> f64 {
89 match self.linear {
90 Some((a, b)) => v * a + b,
91 None => (self.transform)(v),
92 }
93 }
94
95 pub(crate) fn label(&self, v: f64, locale: Option<&crate::locale::Locale>) -> String {
98 match &self.labeller {
99 Some(f) => f(v),
100 None => crate::scale::util::format_number_opt(v, locale),
101 }
102 }
103
104 pub(crate) fn own_ticks(&self, primary: &dyn super::Scale) -> Option<Vec<(f64, String)>> {
108 if let Some((a, b)) = self.linear {
109 let (lo, hi) = primary.expanded_domain()?;
110 let (s1, s2) = (lo * a + b, hi * a + b);
111 let (smin, smax) = (s1.min(s2), s1.max(s2));
112 let eps = 1e-9 * (smax - smin).abs().max(1e-12);
113 return Some(
114 super::util::extended_breaks(smin, smax, 5)
115 .into_iter()
116 .filter(|t| *t >= smin - eps && *t <= smax + eps)
117 .map(|t| {
118 let pos = primary.map(&crate::data::Value::Float((t - b) / a));
119 (pos, self.label(t, primary.locale()))
120 })
121 .collect(),
122 );
123 }
124 if !self.percent_of_max {
125 return None;
126 }
127 let (_, max) = primary.domain()?;
128 let (lo, hi) = primary.expanded_domain()?;
129 if !(max > 0.0 && max.is_finite()) {
130 return Some(vec![]);
131 }
132 let (lo_pct, hi_pct) = (lo / max * 100.0, hi / max * 100.0);
133 let eps = 1e-9 * (hi_pct - lo_pct).abs().max(1.0);
134 Some(
135 super::util::extended_breaks(0.0, 100.0, 5)
136 .into_iter()
137 .filter(|b| *b >= lo_pct - eps && *b <= hi_pct + eps)
138 .map(|b| {
139 let pos = primary.map(&crate::data::Value::Float(b / 100.0 * max));
140 let label = crate::scale::util::format_number_opt(b, primary.locale());
141 (pos, format!("{label}%"))
142 })
143 .collect(),
144 )
145 }
146}
147
148impl std::fmt::Debug for SecAxis {
149 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
150 f.debug_struct("SecAxis")
151 .field("name", &self.name)
152 .field("breaks", &self.breaks)
153 .field("percent_of_max", &self.percent_of_max)
154 .field("linear", &self.linear)
155 .field("labeller", &self.labeller.is_some())
156 .finish()
157 }
158}