1use crate::transform::PlotBounds;
2use egui::{Pos2, Shape, Stroke, Vec2};
3use std::ops::{Bound, RangeBounds, RangeInclusive};
4
5#[derive(Clone, Copy, Debug, PartialEq)]
10pub struct PlotPoint {
11 pub x: f64,
14
15 pub y: f64,
17}
18
19impl From<[f64; 2]> for PlotPoint {
20 #[inline]
21 fn from([x, y]: [f64; 2]) -> Self {
22 Self { x, y }
23 }
24}
25
26impl PlotPoint {
27 #[inline(always)]
28 pub fn new(x: impl Into<f64>, y: impl Into<f64>) -> Self {
29 Self {
30 x: x.into(),
31 y: y.into(),
32 }
33 }
34
35 #[inline(always)]
36 pub fn to_pos2(self) -> Pos2 {
37 Pos2::new(self.x as f32, self.y as f32)
38 }
39
40 #[inline(always)]
41 pub fn to_vec2(self) -> Vec2 {
42 Vec2::new(self.x as f32, self.y as f32)
43 }
44}
45
46#[derive(Debug, PartialEq, Clone, Copy)]
50pub enum LineStyle {
51 Solid,
52 Dotted { spacing: f32 },
53 Dashed { length: f32 },
54}
55
56impl LineStyle {
57 pub fn dashed_loose() -> Self {
58 Self::Dashed { length: 10.0 }
59 }
60
61 pub fn dashed_dense() -> Self {
62 Self::Dashed { length: 5.0 }
63 }
64
65 pub fn dotted_loose() -> Self {
66 Self::Dotted { spacing: 10.0 }
67 }
68
69 pub fn dotted_dense() -> Self {
70 Self::Dotted { spacing: 5.0 }
71 }
72
73 pub(super) fn style_line(
74 &self,
75 line: Vec<Pos2>,
76 mut stroke: Stroke,
77 highlight: bool,
78 shapes: &mut Vec<Shape>,
79 ) {
80 match line.len() {
81 0 => {}
82 1 => {
83 let mut radius = stroke.width / 2.0;
84 if highlight {
85 radius *= 2f32.sqrt();
86 }
87 shapes.push(Shape::circle_filled(line[0], radius, stroke.color));
88 }
89 _ => {
90 match self {
91 LineStyle::Solid => {
92 if highlight {
93 stroke.width *= 2.0;
94 }
95 shapes.push(Shape::line(line, stroke));
96 }
97 LineStyle::Dotted { spacing } => {
98 let mut radius = stroke.width;
101 if highlight {
102 radius *= 2f32.sqrt();
103 }
104 shapes.extend(Shape::dotted_line(&line, stroke.color, *spacing, radius));
105 }
106 LineStyle::Dashed { length } => {
107 if highlight {
108 stroke.width *= 2.0;
109 }
110 let golden_ratio = (5.0_f32.sqrt() - 1.0) / 2.0; shapes.extend(Shape::dashed_line(
112 &line,
113 stroke,
114 *length,
115 length * golden_ratio,
116 ));
117 }
118 }
119 }
120 }
121 }
122}
123
124#[derive(Copy, Clone, Debug, PartialEq, Eq)]
128pub enum Orientation {
129 Horizontal,
130 Vertical,
131}
132
133impl Default for Orientation {
134 fn default() -> Self {
135 Self::Vertical
136 }
137}
138
139pub enum PlotPoints {
145 Owned(Vec<PlotPoint>),
146 Generator(ExplicitGenerator),
147 }
149
150impl Default for PlotPoints {
151 fn default() -> Self {
152 Self::Owned(Vec::new())
153 }
154}
155
156impl From<[f64; 2]> for PlotPoints {
157 fn from(coordinate: [f64; 2]) -> Self {
158 Self::new(vec![coordinate])
159 }
160}
161
162impl From<Vec<[f64; 2]>> for PlotPoints {
163 fn from(coordinates: Vec<[f64; 2]>) -> Self {
164 Self::new(coordinates)
165 }
166}
167
168impl FromIterator<[f64; 2]> for PlotPoints {
169 fn from_iter<T: IntoIterator<Item = [f64; 2]>>(iter: T) -> Self {
170 Self::Owned(iter.into_iter().map(|point| point.into()).collect())
171 }
172}
173
174impl PlotPoints {
175 pub fn new(points: Vec<[f64; 2]>) -> Self {
176 Self::from_iter(points)
177 }
178
179 pub fn points(&self) -> &[PlotPoint] {
180 match self {
181 PlotPoints::Owned(points) => points.as_slice(),
182 PlotPoints::Generator(_) => &[],
183 }
184 }
185
186 pub fn from_explicit_callback(
188 function: impl Fn(f64) -> f64 + 'static,
189 x_range: impl RangeBounds<f64>,
190 points: usize,
191 ) -> Self {
192 let start = match x_range.start_bound() {
193 Bound::Included(x) | Bound::Excluded(x) => *x,
194 Bound::Unbounded => f64::NEG_INFINITY,
195 };
196 let end = match x_range.end_bound() {
197 Bound::Included(x) | Bound::Excluded(x) => *x,
198 Bound::Unbounded => f64::INFINITY,
199 };
200 let x_range = start..=end;
201
202 let generator = ExplicitGenerator {
203 function: Box::new(function),
204 x_range,
205 points,
206 };
207
208 Self::Generator(generator)
209 }
210
211 pub fn from_parametric_callback(
214 function: impl Fn(f64) -> (f64, f64),
215 t_range: impl RangeBounds<f64>,
216 points: usize,
217 ) -> Self {
218 let start = match t_range.start_bound() {
219 Bound::Included(x) => x,
220 Bound::Excluded(_) => unreachable!(),
221 Bound::Unbounded => panic!("The range for parametric functions must be bounded!"),
222 };
223 let end = match t_range.end_bound() {
224 Bound::Included(x) | Bound::Excluded(x) => x,
225 Bound::Unbounded => panic!("The range for parametric functions must be bounded!"),
226 };
227 let last_point_included = matches!(t_range.end_bound(), Bound::Included(_));
228 let increment = if last_point_included {
229 (end - start) / (points - 1) as f64
230 } else {
231 (end - start) / points as f64
232 };
233 (0..points)
234 .map(|i| {
235 let t = start + i as f64 * increment;
236 let (x, y) = function(t);
237 [x, y]
238 })
239 .collect()
240 }
241
242 pub fn from_ys_f32(ys: &[f32]) -> Self {
245 ys.iter()
246 .enumerate()
247 .map(|(i, &y)| [i as f64, y as f64])
248 .collect()
249 }
250
251 pub fn from_ys_f64(ys: &[f64]) -> Self {
254 ys.iter().enumerate().map(|(i, &y)| [i as f64, y]).collect()
255 }
256
257 pub(crate) fn is_empty(&self) -> bool {
259 match self {
260 PlotPoints::Owned(points) => points.is_empty(),
261 PlotPoints::Generator(_) => false,
262 }
263 }
264
265 pub(super) fn generate_points(&mut self, x_range: RangeInclusive<f64>) {
268 if let Self::Generator(generator) = self {
269 *self = Self::range_intersection(&x_range, &generator.x_range)
270 .map(|intersection| {
271 let increment =
272 (intersection.end() - intersection.start()) / (generator.points - 1) as f64;
273 (0..generator.points)
274 .map(|i| {
275 let x = intersection.start() + i as f64 * increment;
276 let y = (generator.function)(x);
277 [x, y]
278 })
279 .collect()
280 })
281 .unwrap_or_default();
282 }
283 }
284
285 fn range_intersection(
287 range1: &RangeInclusive<f64>,
288 range2: &RangeInclusive<f64>,
289 ) -> Option<RangeInclusive<f64>> {
290 let start = range1.start().max(*range2.start());
291 let end = range1.end().min(*range2.end());
292 (start < end).then_some(start..=end)
293 }
294
295 pub(super) fn bounds(&self) -> PlotBounds {
296 match self {
297 PlotPoints::Owned(points) => {
298 let mut bounds = PlotBounds::NOTHING;
299 for point in points {
300 bounds.extend_with(point);
301 }
302 bounds
303 }
304 PlotPoints::Generator(generator) => generator.estimate_bounds(),
305 }
306 }
307}
308
309#[derive(Debug, PartialEq, Eq, Clone, Copy)]
313pub enum MarkerShape {
314 Circle,
315 Diamond,
316 Square,
317 Cross,
318 Plus,
319 Up,
320 Down,
321 Left,
322 Right,
323 Asterisk,
324}
325
326impl MarkerShape {
327 pub fn all() -> impl ExactSizeIterator<Item = MarkerShape> {
329 [
330 Self::Circle,
331 Self::Diamond,
332 Self::Square,
333 Self::Cross,
334 Self::Plus,
335 Self::Up,
336 Self::Down,
337 Self::Left,
338 Self::Right,
339 Self::Asterisk,
340 ]
341 .iter()
342 .copied()
343 }
344}
345
346pub(crate) enum PlotGeometry<'a> {
350 None,
352
353 Points(&'a [PlotPoint]),
355}
356
357pub struct ExplicitGenerator {
361 function: Box<dyn Fn(f64) -> f64>,
362 x_range: RangeInclusive<f64>,
363 points: usize,
364}
365
366impl ExplicitGenerator {
367 fn estimate_bounds(&self) -> PlotBounds {
368 let mut bounds = PlotBounds::NOTHING;
369
370 let mut add_x = |x: f64| {
371 if x.is_finite() {
373 bounds.extend_with_x(x);
374 }
375 let y = (self.function)(x);
376 if y.is_finite() {
377 bounds.extend_with_y(y);
378 }
379 };
380
381 let min_x = *self.x_range.start();
382 let max_x = *self.x_range.end();
383
384 add_x(min_x);
385 add_x(max_x);
386
387 if min_x.is_finite() && max_x.is_finite() {
388 const N: u32 = 8;
390 for i in 1..N {
391 let t = i as f64 / (N - 1) as f64;
392 let x = egui::lerp(min_x..=max_x, t);
393 add_x(x);
394 }
395 } else {
396 for x in [-1, 0, 1] {
398 let x = x as f64;
399 if min_x <= x && x <= max_x {
400 add_x(x);
401 }
402 }
403 }
404
405 bounds
406 }
407}
408
409pub(crate) struct ClosestElem {
413 pub index: usize,
415
416 pub dist_sq: f32,
418}