1#[derive(Clone, Debug, PartialEq)]
3pub struct Axes {
4 pub xlim: (f64, f64),
6 pub ylim: (f64, f64),
8 pub xticks: Vec<f64>,
10 pub yticks: Vec<f64>,
12 pub xlabel: String,
14 pub ylabel: String,
16 pub title: String,
18 auto_xlim: bool,
19 auto_ylim: bool,
20}
21
22impl Axes {
23 pub fn new() -> Self {
25 Self {
26 xlim: (0.0, 1.0),
27 ylim: (0.0, 1.0),
28 xticks: evenly_spaced_ticks((0.0, 1.0), 4),
29 yticks: evenly_spaced_ticks((0.0, 1.0), 4),
30 xlabel: "x".to_owned(),
31 ylabel: "y".to_owned(),
32 title: "plot".to_owned(),
33 auto_xlim: true,
34 auto_ylim: true,
35 }
36 }
37
38 pub fn from_limits(xlim: (f64, f64), ylim: (f64, f64)) -> Self {
40 Self::new().with_xlim(xlim).with_ylim(ylim)
41 }
42
43 pub fn x_limits(self, limits: (f64, f64)) -> Self {
45 self.with_xlim(limits)
46 }
47
48 pub fn y_limits(self, limits: (f64, f64)) -> Self {
50 self.with_ylim(limits)
51 }
52
53 pub fn labels(self, x: impl Into<String>, y: impl Into<String>) -> Self {
55 self.with_labels(x, y)
56 }
57
58 pub fn title(self, title: impl Into<String>) -> Self {
60 self.with_title(title)
61 }
62
63 pub fn x_ticks(mut self, ticks: impl IntoIterator<Item = f64>) -> Self {
65 self.xticks = ticks.into_iter().collect();
66 self
67 }
68
69 pub fn y_ticks(mut self, ticks: impl IntoIterator<Item = f64>) -> Self {
71 self.yticks = ticks.into_iter().collect();
72 self
73 }
74
75 pub fn with_xlim(mut self, xlim: (f64, f64)) -> Self {
77 self.set_xlim(xlim);
78 self
79 }
80
81 pub fn with_ylim(mut self, ylim: (f64, f64)) -> Self {
83 self.set_ylim(ylim);
84 self
85 }
86
87 pub fn set_xlim(&mut self, xlim: (f64, f64)) {
89 self.xlim = xlim;
90 self.xticks = evenly_spaced_ticks(xlim, 4);
91 self.auto_xlim = false;
92 }
93
94 pub fn set_ylim(&mut self, ylim: (f64, f64)) {
96 self.ylim = ylim;
97 self.yticks = evenly_spaced_ticks(ylim, 4);
98 self.auto_ylim = false;
99 }
100
101 pub fn use_auto_xlim(&mut self) {
103 self.auto_xlim = true;
104 }
105
106 pub fn use_auto_ylim(&mut self) {
108 self.auto_ylim = true;
109 }
110
111 pub fn with_labels(mut self, xlabel: impl Into<String>, ylabel: impl Into<String>) -> Self {
113 self.xlabel = xlabel.into();
114 self.ylabel = ylabel.into();
115 self
116 }
117
118 pub fn with_title(mut self, title: impl Into<String>) -> Self {
120 self.title = title.into();
121 self
122 }
123
124 pub(crate) fn update_auto_limits(
125 &mut self,
126 xlim: Option<(f64, f64)>,
127 ylim: Option<(f64, f64)>,
128 ) -> bool {
129 let mut changed = false;
130
131 if self.auto_xlim
132 && let Some(xlim) = xlim
133 {
134 changed |= self.xlim != xlim;
135 self.xlim = xlim;
136 self.xticks = evenly_spaced_ticks(xlim, 4);
137 }
138 if self.auto_ylim
139 && let Some(ylim) = ylim
140 {
141 changed |= self.ylim != ylim;
142 self.ylim = ylim;
143 self.yticks = evenly_spaced_ticks(ylim, 4);
144 }
145
146 changed
147 }
148}
149
150impl Default for Axes {
151 fn default() -> Self {
152 Self::new()
153 }
154}
155
156fn evenly_spaced_ticks(limits: (f64, f64), intervals: usize) -> Vec<f64> {
157 if intervals == 0 {
158 return vec![limits.0];
159 }
160
161 (0..=intervals)
162 .map(|index| limits.0 + (limits.1 - limits.0) * index as f64 / intervals as f64)
163 .collect()
164}