egui_plot_bintrade/items/
values.rs1use std::ops::{Bound, RangeBounds, RangeInclusive};
2
3use egui::{
4 Pos2, Rect, Shape, Stroke, Vec2,
5 epaint::{ColorMode, PathStroke},
6 lerp, pos2,
7};
8
9use crate::transform::PlotBounds;
10
11#[derive(Clone, Copy, Debug, PartialEq)]
16pub struct PlotPoint {
17 pub x: f64,
20
21 pub y: f64,
23}
24
25impl From<[f64; 2]> for PlotPoint {
26 #[inline]
27 fn from([x, y]: [f64; 2]) -> Self {
28 Self { x, y }
29 }
30}
31
32impl PlotPoint {
33 #[inline(always)]
34 pub fn new(x: impl Into<f64>, y: impl Into<f64>) -> Self {
35 Self {
36 x: x.into(),
37 y: y.into(),
38 }
39 }
40
41 #[inline(always)]
42 pub fn to_pos2(self) -> Pos2 {
43 Pos2::new(self.x as f32, self.y as f32)
44 }
45
46 #[inline(always)]
47 pub fn to_vec2(self) -> Vec2 {
48 Vec2::new(self.x as f32, self.y as f32)
49 }
50}
51
52#[derive(Debug, PartialEq, Clone, Copy)]
56#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
57pub enum LineStyle {
58 Solid,
59 Dotted { spacing: f32 },
60 Dashed { length: f32 },
61}
62
63impl LineStyle {
64 pub fn dashed_loose() -> Self {
65 Self::Dashed { length: 10.0 }
66 }
67
68 pub fn dashed_dense() -> Self {
69 Self::Dashed { length: 5.0 }
70 }
71
72 pub fn dotted_loose() -> Self {
73 Self::Dotted { spacing: 10.0 }
74 }
75
76 pub fn dotted_dense() -> Self {
77 Self::Dotted { spacing: 5.0 }
78 }
79
80 pub(super) fn style_line(
81 &self,
82 line: Vec<Pos2>,
83 mut stroke: PathStroke,
84 highlight: bool,
85 shapes: &mut Vec<Shape>,
86 ) {
87 let path_stroke_color = match &stroke.color {
88 ColorMode::Solid(c) => *c,
89 ColorMode::UV(callback) => {
90 callback(Rect::from_min_max(pos2(0., 0.), pos2(0., 0.)), pos2(0., 0.))
91 }
92 };
93 match line.len() {
94 0 => {}
95 1 => {
96 let mut radius = stroke.width / 2.0;
97 if highlight {
98 radius *= 2f32.sqrt();
99 }
100 shapes.push(Shape::circle_filled(line[0], radius, path_stroke_color));
101 }
102 _ => {
103 match self {
104 Self::Solid => {
105 if highlight {
106 stroke.width *= 2.0;
107 }
108 shapes.push(Shape::line(line, stroke));
109 }
110 Self::Dotted { spacing } => {
111 let mut radius = stroke.width;
114 if highlight {
115 radius *= 2f32.sqrt();
116 }
117 shapes.extend(Shape::dotted_line(
118 &line,
119 path_stroke_color,
120 *spacing,
121 radius,
122 ));
123 }
124 Self::Dashed { length } => {
125 if highlight {
126 stroke.width *= 2.0;
127 }
128 let golden_ratio = (5.0_f32.sqrt() - 1.0) / 2.0; shapes.extend(Shape::dashed_line(
130 &line,
131 Stroke::new(stroke.width, path_stroke_color),
132 *length,
133 length * golden_ratio,
134 ));
135 }
136 }
137 }
138 }
139 }
140}
141
142impl std::fmt::Display for LineStyle {
143 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
144 match self {
145 Self::Solid => write!(f, "Solid"),
146 Self::Dotted { spacing } => write!(f, "Dotted({spacing} px)"),
147 Self::Dashed { length } => write!(f, "Dashed({length} px)"),
148 }
149 }
150}
151
152#[derive(Copy, Clone, Debug, PartialEq, Eq)]
156pub enum Orientation {
157 Horizontal,
158 Vertical,
159}
160
161impl Default for Orientation {
162 fn default() -> Self {
163 Self::Vertical
164 }
165}
166
167pub enum PlotPoints<'a> {
175 Owned(Vec<PlotPoint>),
176 Generator(ExplicitGenerator<'a>),
177 Borrowed(&'a [PlotPoint]),
178}
179
180impl Default for PlotPoints<'_> {
181 fn default() -> Self {
182 Self::Owned(Vec::new())
183 }
184}
185
186impl From<[f64; 2]> for PlotPoints<'_> {
187 fn from(coordinate: [f64; 2]) -> Self {
188 Self::new(vec![coordinate])
189 }
190}
191
192impl From<Vec<[f64; 2]>> for PlotPoints<'_> {
193 #[inline]
194 fn from(coordinates: Vec<[f64; 2]>) -> Self {
195 Self::new(coordinates)
196 }
197}
198
199impl<'a> From<&'a [PlotPoint]> for PlotPoints<'a> {
200 #[inline]
201 fn from(points: &'a [PlotPoint]) -> Self {
202 Self::Borrowed(points)
203 }
204}
205
206impl FromIterator<[f64; 2]> for PlotPoints<'_> {
207 fn from_iter<T: IntoIterator<Item = [f64; 2]>>(iter: T) -> Self {
208 Self::Owned(iter.into_iter().map(|point| point.into()).collect())
209 }
210}
211
212impl<'a> PlotPoints<'a> {
213 pub fn new(points: Vec<[f64; 2]>) -> Self {
214 Self::from_iter(points)
215 }
216
217 pub fn points(&self) -> &[PlotPoint] {
218 match self {
219 Self::Owned(points) => points.as_slice(),
220 Self::Generator(_) => &[],
221 Self::Borrowed(points) => points,
222 }
223 }
224
225 pub fn from_explicit_callback(
227 function: impl Fn(f64) -> f64 + 'a,
228 x_range: impl RangeBounds<f64>,
229 points: usize,
230 ) -> Self {
231 let start = match x_range.start_bound() {
232 Bound::Included(x) | Bound::Excluded(x) => *x,
233 Bound::Unbounded => f64::NEG_INFINITY,
234 };
235 let end = match x_range.end_bound() {
236 Bound::Included(x) | Bound::Excluded(x) => *x,
237 Bound::Unbounded => f64::INFINITY,
238 };
239 let x_range = start..=end;
240
241 let generator = ExplicitGenerator {
242 function: Box::new(function),
243 x_range,
244 points,
245 };
246
247 Self::Generator(generator)
248 }
249
250 pub fn from_parametric_callback(
253 function: impl Fn(f64) -> (f64, f64),
254 t_range: impl RangeBounds<f64>,
255 points: usize,
256 ) -> Self {
257 let start = match t_range.start_bound() {
258 Bound::Included(x) => x,
259 Bound::Excluded(_) => unreachable!(),
260 Bound::Unbounded => panic!("The range for parametric functions must be bounded!"),
261 };
262 let end = match t_range.end_bound() {
263 Bound::Included(x) | Bound::Excluded(x) => x,
264 Bound::Unbounded => panic!("The range for parametric functions must be bounded!"),
265 };
266 let last_point_included = matches!(t_range.end_bound(), Bound::Included(_));
267 let increment = if last_point_included {
268 (end - start) / (points - 1) as f64
269 } else {
270 (end - start) / points as f64
271 };
272 (0..points)
273 .map(|i| {
274 let t = start + i as f64 * increment;
275 function(t).into()
276 })
277 .collect()
278 }
279
280 pub fn from_ys_f32(ys: &[f32]) -> Self {
283 ys.iter()
284 .enumerate()
285 .map(|(i, &y)| [i as f64, y as f64])
286 .collect()
287 }
288
289 pub fn from_ys_f64(ys: &[f64]) -> Self {
292 ys.iter().enumerate().map(|(i, &y)| [i as f64, y]).collect()
293 }
294
295 pub(crate) fn is_empty(&self) -> bool {
297 match self {
298 Self::Owned(points) => points.is_empty(),
299 Self::Generator(_) => false,
300 Self::Borrowed(points) => points.is_empty(),
301 }
302 }
303
304 pub(super) fn generate_points(&mut self, x_range: RangeInclusive<f64>) {
307 if let Self::Generator(generator) = self {
308 *self = Self::range_intersection(&x_range, &generator.x_range)
309 .map(|intersection| {
310 let increment =
311 (intersection.end() - intersection.start()) / (generator.points - 1) as f64;
312 (0..generator.points)
313 .map(|i| {
314 let x = intersection.start() + i as f64 * increment;
315 let y = (generator.function)(x);
316 [x, y]
317 })
318 .collect()
319 })
320 .unwrap_or_default();
321 }
322 }
323
324 fn range_intersection(
326 range1: &RangeInclusive<f64>,
327 range2: &RangeInclusive<f64>,
328 ) -> Option<RangeInclusive<f64>> {
329 let start = range1.start().max(*range2.start());
330 let end = range1.end().min(*range2.end());
331 (start < end).then_some(start..=end)
332 }
333
334 pub(super) fn bounds(&self) -> PlotBounds {
335 match self {
336 Self::Owned(points) => {
337 let mut bounds = PlotBounds::NOTHING;
338 for point in points {
339 bounds.extend_with(point);
340 }
341 bounds
342 }
343 Self::Generator(generator) => generator.estimate_bounds(),
344 Self::Borrowed(points) => {
345 let mut bounds = PlotBounds::NOTHING;
346 for point in *points {
347 bounds.extend_with(point);
348 }
349 bounds
350 }
351 }
352 }
353}
354
355#[derive(Debug, PartialEq, Eq, Clone, Copy)]
359pub enum MarkerShape {
360 Circle,
361 Diamond,
362 Square,
363 Cross,
364 Plus,
365 Up,
366 Down,
367 Left,
368 Right,
369 Asterisk,
370}
371
372impl MarkerShape {
373 pub fn all() -> impl ExactSizeIterator<Item = Self> {
375 [
376 Self::Circle,
377 Self::Diamond,
378 Self::Square,
379 Self::Cross,
380 Self::Plus,
381 Self::Up,
382 Self::Down,
383 Self::Left,
384 Self::Right,
385 Self::Asterisk,
386 ]
387 .iter()
388 .copied()
389 }
390}
391
392pub enum PlotGeometry<'a> {
396 None,
398
399 Points(&'a [PlotPoint]),
401
402 Rects,
406}
407
408pub struct ExplicitGenerator<'a> {
412 function: Box<dyn Fn(f64) -> f64 + 'a>,
413 x_range: RangeInclusive<f64>,
414 points: usize,
415}
416
417impl ExplicitGenerator<'_> {
418 fn estimate_bounds(&self) -> PlotBounds {
419 let mut bounds = PlotBounds::NOTHING;
420
421 let mut add_x = |x: f64| {
422 if x.is_finite() {
424 bounds.extend_with_x(x);
425 }
426 let y = (self.function)(x);
427 if y.is_finite() {
428 bounds.extend_with_y(y);
429 }
430 };
431
432 let min_x = *self.x_range.start();
433 let max_x = *self.x_range.end();
434
435 add_x(min_x);
436 add_x(max_x);
437
438 if min_x.is_finite() && max_x.is_finite() {
439 const N: u32 = 8;
441 for i in 1..N {
442 let t = i as f64 / (N - 1) as f64;
443 let x = lerp(min_x..=max_x, t);
444 add_x(x);
445 }
446 } else {
447 for x in [-1, 0, 1] {
449 let x = x as f64;
450 if min_x <= x && x <= max_x {
451 add_x(x);
452 }
453 }
454 }
455
456 bounds
457 }
458}
459
460pub struct ClosestElem {
464 pub index: usize,
466
467 pub dist_sq: f32,
469}