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 dup() -> Self {
48 SecAxis::new(|v| v)
49 }
50
51 pub fn percent_of_max() -> Self {
57 SecAxis {
58 percent_of_max: true,
59 ..SecAxis::new(|v| v)
60 }
61 }
62
63 pub fn linear(scale: f64, offset: f64) -> Self {
69 let ok = scale.is_finite() && scale != 0.0 && offset.is_finite();
70 SecAxis {
71 linear: Some(if ok { (scale, offset) } else { (1.0, 0.0) }),
72 ..SecAxis::new(|v| v)
73 }
74 }
75
76 pub fn with_labeller(mut self, f: impl Fn(f64) -> String + Send + Sync + 'static) -> Self {
79 self.labeller = Some(std::sync::Arc::new(f));
80 self
81 }
82
83 pub fn with_name(mut self, name: &str) -> Self {
84 self.name = name.to_string();
85 self
86 }
87
88 pub fn with_breaks(mut self, breaks: Vec<f64>) -> Self {
89 self.breaks = Some(breaks);
90 self
91 }
92
93 pub fn transform_value(&self, v: f64) -> f64 {
95 match self.linear {
96 Some((a, b)) => v * a + b,
97 None => (self.transform)(v),
98 }
99 }
100
101 pub(crate) fn label(&self, v: f64, locale: Option<&crate::locale::Locale>) -> String {
104 match &self.labeller {
105 Some(f) => f(v),
106 None => crate::scale::util::format_number_opt(v, locale),
107 }
108 }
109
110 pub(crate) fn own_ticks(&self, primary: &dyn super::Scale) -> Option<Vec<(f64, String)>> {
114 if let Some((a, b)) = self.linear {
115 let (lo, hi) = primary.expanded_domain()?;
116 let (s1, s2) = (lo * a + b, hi * a + b);
117 let (smin, smax) = (s1.min(s2), s1.max(s2));
118 let eps = 1e-9 * (smax - smin).abs().max(1e-12);
119 return Some(
120 super::util::extended_breaks(smin, smax, 5)
121 .into_iter()
122 .filter(|t| *t >= smin - eps && *t <= smax + eps)
123 .map(|t| {
124 let pos = primary.map(&crate::data::Value::Float((t - b) / a));
125 (pos, self.label(t, primary.locale()))
126 })
127 .collect(),
128 );
129 }
130 if !self.percent_of_max {
131 return None;
132 }
133 let (_, max) = primary.domain()?;
134 let (lo, hi) = primary.expanded_domain()?;
135 if !(max > 0.0 && max.is_finite()) {
136 return Some(vec![]);
137 }
138 let (lo_pct, hi_pct) = (lo / max * 100.0, hi / max * 100.0);
139 let eps = 1e-9 * (hi_pct - lo_pct).abs().max(1.0);
140 Some(
141 super::util::extended_breaks(0.0, 100.0, 5)
142 .into_iter()
143 .filter(|b| *b >= lo_pct - eps && *b <= hi_pct + eps)
144 .map(|b| {
145 let pos = primary.map(&crate::data::Value::Float(b / 100.0 * max));
146 let label = crate::scale::util::format_number_opt(b, primary.locale());
147 (pos, format!("{label}%"))
148 })
149 .collect(),
150 )
151 }
152}
153
154impl std::fmt::Debug for SecAxis {
155 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
156 f.debug_struct("SecAxis")
157 .field("name", &self.name)
158 .field("breaks", &self.breaks)
159 .field("percent_of_max", &self.percent_of_max)
160 .field("linear", &self.linear)
161 .field("labeller", &self.labeller.is_some())
162 .finish()
163 }
164}