1use std::sync::atomic::{AtomicU64, Ordering};
4use std::sync::{Arc, RwLock};
5
6use crate::datasource::{AppendError, AppendOnlyData, SeriesStore};
7use crate::geom::Point;
8use crate::render::{LineStyle, MarkerStyle};
9use crate::view::Viewport;
10
11static SERIES_ID_COUNTER: AtomicU64 = AtomicU64::new(1);
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
18pub struct SeriesId(u64);
19
20impl SeriesId {
21 fn next() -> Self {
22 Self(SERIES_ID_COUNTER.fetch_add(1, Ordering::Relaxed))
23 }
24}
25
26#[derive(Debug, Clone)]
30pub enum SeriesKind {
31 Line(LineStyle),
33 Scatter(MarkerStyle),
35}
36
37#[derive(Debug)]
45pub struct Series {
46 id: SeriesId,
47 name: String,
48 kind: SeriesKind,
49 data: Arc<RwLock<SeriesStore>>,
50 visible: bool,
51}
52
53impl Series {
54 pub fn line(name: impl Into<String>) -> Self {
58 Self {
59 id: SeriesId::next(),
60 name: name.into(),
61 kind: SeriesKind::Line(LineStyle::default()),
62 data: Arc::new(RwLock::new(SeriesStore::indexed())),
63 visible: true,
64 }
65 }
66
67 pub fn scatter(name: impl Into<String>) -> Self {
71 Self {
72 id: SeriesId::next(),
73 name: name.into(),
74 kind: SeriesKind::Scatter(MarkerStyle::default()),
75 data: Arc::new(RwLock::new(SeriesStore::indexed())),
76 visible: true,
77 }
78 }
79
80 pub(crate) fn with_data(
82 name: impl Into<String>,
83 data: AppendOnlyData,
84 kind: SeriesKind,
85 ) -> Self {
86 Self {
87 id: SeriesId::next(),
88 name: name.into(),
89 kind,
90 data: Arc::new(RwLock::new(SeriesStore::with_base_chunk(data, 64))),
91 visible: true,
92 }
93 }
94
95 pub fn from_iter_y<I, T>(name: impl Into<String>, iter: I, kind: SeriesKind) -> Self
99 where
100 I: IntoIterator<Item = T>,
101 T: Into<f64>,
102 {
103 let data = AppendOnlyData::from_iter_y(iter);
104 Self::with_data(name, data, kind)
105 }
106
107 pub fn from_iter_points<I>(name: impl Into<String>, iter: I, kind: SeriesKind) -> Self
111 where
112 I: IntoIterator<Item = Point>,
113 {
114 let data = AppendOnlyData::from_iter_points(iter);
115 Self::with_data(name, data, kind)
116 }
117
118 pub fn from_explicit_callback(
122 name: impl Into<String>,
123 function: impl Fn(f64) -> f64,
124 x_range: crate::view::Range,
125 points: usize,
126 kind: SeriesKind,
127 ) -> Self {
128 let data = AppendOnlyData::from_explicit_callback(function, x_range, points);
129 Self::with_data(name, data, kind)
130 }
131
132 pub fn id(&self) -> SeriesId {
134 self.id
135 }
136
137 pub fn name(&self) -> &str {
139 &self.name
140 }
141
142 pub fn kind(&self) -> &SeriesKind {
144 &self.kind
145 }
146
147 pub fn with_kind(mut self, kind: SeriesKind) -> Self {
149 self.kind = kind;
150 self
151 }
152
153 pub fn share(&self) -> Self {
159 Self {
160 id: SeriesId::next(),
161 name: self.name.clone(),
162 kind: self.kind.clone(),
163 data: Arc::clone(&self.data),
164 visible: self.visible,
165 }
166 }
167
168 pub(crate) fn with_store<R>(&self, f: impl FnOnce(&SeriesStore) -> R) -> R {
170 let data = self.data.read().expect("series data lock");
171 f(&data)
172 }
173
174 pub fn push_y(&mut self, y: f64) -> Result<usize, AppendError> {
176 self.with_store_mut(|data| data.push_y(y))
177 }
178
179 pub fn extend_y<I, T>(&mut self, values: I) -> Result<usize, AppendError>
183 where
184 I: IntoIterator<Item = T>,
185 T: Into<f64>,
186 {
187 self.with_store_mut(|data| data.extend_y(values))
188 }
189
190 pub fn push_point(&mut self, point: Point) -> Result<usize, AppendError> {
192 self.with_store_mut(|data| data.push_point(point))
193 }
194
195 pub fn extend_points<I>(&mut self, points: I) -> Result<usize, AppendError>
201 where
202 I: IntoIterator<Item = Point>,
203 {
204 self.with_store_mut(|data| data.extend_points(points))
205 }
206
207 pub fn bounds(&self) -> Option<Viewport> {
209 self.with_store(SeriesStore::bounds)
210 }
211
212 pub fn generation(&self) -> u64 {
216 self.with_store(SeriesStore::generation)
217 }
218
219 pub fn is_visible(&self) -> bool {
221 self.visible
222 }
223
224 pub fn set_visible(&mut self, visible: bool) {
226 self.visible = visible;
227 }
228
229 fn with_store_mut<R>(&self, f: impl FnOnce(&mut SeriesStore) -> R) -> R {
230 let mut data = self.data.write().expect("series data lock");
231 f(&mut data)
232 }
233}
234
235impl Clone for Series {
236 fn clone(&self) -> Self {
237 let data = self.data.read().expect("series data lock").clone();
238 Self {
239 id: self.id,
240 name: self.name.clone(),
241 kind: self.kind.clone(),
242 data: Arc::new(RwLock::new(data)),
243 visible: self.visible,
244 }
245 }
246}
247
248#[cfg(test)]
249mod tests {
250 use super::*;
251
252 #[test]
253 fn share_observes_appends_from_source() {
254 let mut source = Series::line("shared");
255 let mut shared = source.share();
256
257 let _ = source.extend_y([1.0, 2.0, 3.0]);
258 assert_eq!(shared.generation(), 3);
259
260 let _ = shared.push_y(4.0);
261 assert_eq!(source.generation(), 4);
262 assert_eq!(source.bounds(), shared.bounds());
263 }
264
265 #[test]
266 fn clone_is_independent_copy() {
267 let mut source = Series::line("sensor");
268 let mut cloned = source.clone();
269
270 let _ = source.push_y(1.0);
271 assert_eq!(source.generation(), 1);
272 assert_eq!(cloned.generation(), 0);
273
274 let _ = cloned.push_y(2.0);
275 assert_eq!(source.generation(), 1);
276 assert_eq!(cloned.generation(), 1);
277 }
278}