1use crate::axis::AxisConfig;
7use crate::interaction::Pin;
8use crate::series::Series;
9use crate::style::Theme;
10use crate::view::{Range, View, Viewport};
11
12#[derive(Debug, Clone)]
18pub struct Plot {
19 theme: Theme,
20 x_axis: AxisConfig,
21 y_axis: AxisConfig,
22 view: View,
23 viewport: Option<Viewport>,
24 series: Vec<Series>,
25 pins: Vec<Pin>,
26}
27
28impl Plot {
29 pub fn new() -> Self {
33 Self {
34 theme: Theme::default(),
35 x_axis: AxisConfig::default(),
36 y_axis: AxisConfig::default(),
37 view: View::default(),
38 viewport: None,
39 series: Vec::new(),
40 pins: Vec::new(),
41 }
42 }
43
44 pub fn builder() -> PlotBuilder {
46 PlotBuilder::default()
47 }
48
49 pub fn theme(&self) -> &Theme {
51 &self.theme
52 }
53
54 pub fn set_theme(&mut self, theme: Theme) {
56 self.theme = theme;
57 }
58
59 pub fn x_axis(&self) -> &AxisConfig {
61 &self.x_axis
62 }
63
64 pub fn y_axis(&self) -> &AxisConfig {
66 &self.y_axis
67 }
68
69 pub fn view(&self) -> View {
71 self.view
72 }
73
74 pub fn viewport(&self) -> Option<Viewport> {
78 self.viewport
79 }
80
81 pub fn series(&self) -> &[Series] {
83 &self.series
84 }
85
86 pub fn series_mut(&mut self) -> &mut Vec<Series> {
91 &mut self.series
92 }
93
94 pub fn add_series(&mut self, series: &Series) {
99 self.series.push(series.share());
100 }
101
102 pub fn pins(&self) -> &[Pin] {
104 &self.pins
105 }
106
107 pub fn pins_mut(&mut self) -> &mut Vec<Pin> {
109 &mut self.pins
110 }
111
112 pub fn data_bounds(&self) -> Option<Viewport> {
114 let mut x_range: Option<Range> = None;
115 let mut y_range: Option<Range> = None;
116 for series in &self.series {
117 if !series.is_visible() {
118 continue;
119 }
120 if let Some(bounds) = series.bounds() {
121 x_range = Some(match x_range {
122 None => bounds.x,
123 Some(existing) => Range::union(existing, bounds.x)?,
124 });
125 y_range = Some(match y_range {
126 None => bounds.y,
127 Some(existing) => Range::union(existing, bounds.y)?,
128 });
129 }
130 }
131 match (x_range, y_range) {
132 (Some(x), Some(y)) => Some(Viewport::new(x, y)),
133 _ => None,
134 }
135 }
136
137 pub fn set_manual_view(&mut self, viewport: Viewport) {
139 self.view = View::Manual;
140 self.viewport = Some(viewport);
141 }
142
143 pub fn reset_view(&mut self) {
145 self.view = View::default();
146 self.viewport = None;
147 }
148
149 pub fn refresh_viewport(&mut self, padding_frac: f64, min_padding: f64) -> Option<Viewport> {
154 let bounds = self.data_bounds()?;
155 match self.view {
156 View::AutoAll { auto_x, auto_y } => {
157 let mut next = bounds;
158 if let Some(current) = self.viewport {
159 if !auto_x {
160 next.x = current.x;
161 }
162 if !auto_y {
163 next.y = current.y;
164 }
165 }
166 self.viewport = Some(next.padded(padding_frac, min_padding));
167 }
168 View::Manual => {
169 if self.viewport.is_none() {
170 self.viewport = Some(bounds);
171 }
172 }
173 View::FollowLastN { points } => {
174 self.viewport = self.follow_last(points, false);
175 }
176 View::FollowLastNXY { points } => {
177 self.viewport = self.follow_last(points, true);
178 }
179 }
180 self.viewport
181 }
182
183 fn follow_last(&self, points: usize, follow_y: bool) -> Option<Viewport> {
184 let mut max_series: Option<&Series> = None;
185 let mut max_point: Option<crate::geom::Point> = None;
186 for series in &self.series {
187 if !series.is_visible() {
188 continue;
189 }
190 let last_point = series.with_store(|store| store.data().points().last().copied());
191 if let Some(point) = last_point
192 && max_point.is_none_or(|max| point.x > max.x)
193 {
194 max_point = Some(point);
195 max_series = Some(series);
196 }
197 }
198
199 let max_series = max_series?;
200 let max_point = max_point?;
201 let (len, start_point) = max_series.with_store(|store| {
202 let data = store.data();
203 let len = data.len();
204 let start_index = len.saturating_sub(points);
205 (len, data.point(start_index))
206 });
207 if len == 0 {
208 return None;
209 }
210 let start_point = start_point?;
211 let x_range = Range::new(start_point.x, max_point.x);
212
213 let y_range = if follow_y {
214 let mut y_range: Option<Range> = None;
215 for series in &self.series {
216 if !series.is_visible() {
217 continue;
218 }
219 series.with_store(|store| {
220 let series_data = store.data();
221 let index_range = series_data.range_by_x(x_range);
222 for index in index_range {
223 if let Some(point) = series_data.point(index) {
224 y_range = Some(match y_range {
225 None => Range::new(point.y, point.y),
226 Some(mut existing) => {
227 existing.expand_to_include(point.y);
228 existing
229 }
230 });
231 }
232 }
233 });
234 }
235 y_range?
236 } else if let Some(current) = self.viewport {
237 current.y
238 } else {
239 self.data_bounds()?.y
240 };
241
242 Some(Viewport::new(x_range, y_range))
243 }
244}
245
246impl Default for Plot {
247 fn default() -> Self {
248 Self::new()
249 }
250}
251
252#[derive(Debug, Default)]
256pub struct PlotBuilder {
257 theme: Theme,
258 x_axis: AxisConfig,
259 y_axis: AxisConfig,
260 view: View,
261 series: Vec<Series>,
262}
263
264impl PlotBuilder {
265 pub fn theme(mut self, theme: Theme) -> Self {
267 self.theme = theme;
268 self
269 }
270
271 pub fn x_axis(mut self, axis: AxisConfig) -> Self {
273 self.x_axis = axis;
274 self
275 }
276
277 pub fn y_axis(mut self, axis: AxisConfig) -> Self {
279 self.y_axis = axis;
280 self
281 }
282
283 pub fn view(mut self, view: View) -> Self {
285 self.view = view;
286 self
287 }
288
289 pub fn series(mut self, series: &Series) -> Self {
293 self.series.push(series.share());
294 self
295 }
296
297 pub fn build(self) -> Plot {
299 Plot {
300 theme: self.theme,
301 x_axis: self.x_axis,
302 y_axis: self.y_axis,
303 view: self.view,
304 viewport: None,
305 series: self.series,
306 pins: Vec::new(),
307 }
308 }
309}
310
311#[cfg(test)]
312mod tests {
313 use super::*;
314 use crate::series::Series;
315
316 #[test]
317 fn add_series_uses_shared_data_stream() {
318 let mut source = Series::line("shared");
319 let _ = source.extend_y([1.0, 2.0]);
320
321 let mut plot = Plot::new();
322 plot.add_series(&source);
323
324 let initial_bounds = plot.data_bounds().expect("plot bounds");
325 assert_eq!(initial_bounds.y.min, 1.0);
326 assert_eq!(initial_bounds.y.max, 2.0);
327
328 let _ = source.push_y(3.0);
329 let next_bounds = plot.data_bounds().expect("plot bounds");
330 assert_eq!(next_bounds.y.max, 3.0);
331 }
332
333 #[test]
334 fn series_mut_can_remove_series() {
335 let mut first = Series::line("first");
336 let mut second = Series::line("second");
337 let _ = first.push_y(1.0);
338 let _ = second.push_y(9.0);
339
340 let mut plot = Plot::new();
341 plot.add_series(&first);
342 plot.add_series(&second);
343
344 let removed = plot.series_mut().remove(1);
345 assert_eq!(removed.name(), "second");
346 assert_eq!(plot.series().len(), 1);
347 assert_eq!(plot.series()[0].name(), "first");
348 }
349}