1pub use values::{LineStyle, MarkerShape, Orientation, PlotPoint, PlotPoints};
2
3use super::{Cursor, LabelFormatter, PlotBounds, PlotTransform};
4use egui::{
5 epaint,
6 epaint::{util::FloatOrd, Mesh},
7 pos2, vec2, Align2, Color32, NumExt as _, Pos2, Rect, Rgba, Shape, Stroke, TextStyle, Ui,
8 WidgetText,
9};
10use std::ops::RangeInclusive;
11use values::{ClosestElem, PlotGeometry};
12
13mod values;
14
15const DEFAULT_FILL_ALPHA: f32 = 0.05;
16
17pub(super) struct PlotConfig<'a> {
19 pub ui: &'a Ui,
20 pub transform: &'a PlotTransform,
21 pub show_x: bool,
22 pub show_y: bool,
23}
24
25pub(super) trait PlotItem {
27 fn shapes(&self, ui: &mut Ui, transform: &PlotTransform, shapes: &mut Vec<Shape>);
28
29 fn initialize(&mut self, x_range: RangeInclusive<f64>);
31
32 fn name(&self) -> &str;
33
34 fn color(&self) -> Color32;
35
36 fn highlight(&mut self);
37
38 fn highlighted(&self) -> bool;
39
40 fn geometry(&self) -> PlotGeometry<'_>;
41
42 fn bounds(&self) -> PlotBounds;
43
44 fn find_closest(&self, point: Pos2, transform: &PlotTransform) -> Option<ClosestElem> {
45 match self.geometry() {
46 PlotGeometry::None => None,
47
48 PlotGeometry::Points(points) => points
49 .iter()
50 .enumerate()
51 .map(|(index, value)| {
52 let pos = transform.position_from_point(value);
53 let dist_sq = point.distance_sq(pos);
54 ClosestElem { index, dist_sq }
55 })
56 .min_by_key(|e| e.dist_sq.ord()),
57 }
58 }
59
60 fn on_hover(
61 &self,
62 elem: ClosestElem,
63 shapes: &mut Vec<Shape>,
64 cursors: &mut Vec<Cursor>,
65 plot: &PlotConfig<'_>,
66 label_formatter: &LabelFormatter,
67 ) {
68 let points = match self.geometry() {
69 PlotGeometry::Points(points) => points,
70 PlotGeometry::None => {
71 panic!("If the PlotItem has no geometry, on_hover() must not be called")
72 }
73 };
74
75 let line_color = if plot.ui.visuals().dark_mode {
76 Color32::from_gray(100).additive()
77 } else {
78 Color32::from_black_alpha(180)
79 };
80
81 let value = points[elem.index];
83 let pointer = plot.transform.position_from_point(&value);
84 shapes.push(Shape::circle_filled(pointer, 3.0, line_color));
85
86 rulers_at_value(
87 pointer,
88 value,
89 self.name(),
90 plot,
91 shapes,
92 cursors,
93 label_formatter,
94 );
95 }
96}
97
98pub struct Line {
100 pub(super) series: PlotPoints,
101 pub(super) stroke: Stroke,
102 pub(super) name: String,
103 pub(super) highlight: bool,
104 pub(super) fill: Option<f32>,
105 pub(super) style: LineStyle,
106}
107
108impl Line {
109 pub fn new(series: impl Into<PlotPoints>) -> Self {
110 Self {
111 series: series.into(),
112 stroke: Stroke::new(1.0, Color32::TRANSPARENT),
113 name: Default::default(),
114 highlight: false,
115 fill: None,
116 style: LineStyle::Solid,
117 }
118 }
119
120 pub fn highlight(mut self, highlight: bool) -> Self {
122 self.highlight = highlight;
123 self
124 }
125
126 pub fn stroke(mut self, stroke: impl Into<Stroke>) -> Self {
128 self.stroke = stroke.into();
129 self
130 }
131
132 pub fn width(mut self, width: impl Into<f32>) -> Self {
134 self.stroke.width = width.into();
135 self
136 }
137
138 pub fn color(mut self, color: impl Into<Color32>) -> Self {
140 self.stroke.color = color.into();
141 self
142 }
143
144 pub fn fill(mut self, y_reference: impl Into<f32>) -> Self {
146 self.fill = Some(y_reference.into());
147 self
148 }
149
150 pub fn style(mut self, style: LineStyle) -> Self {
152 self.style = style;
153 self
154 }
155
156 #[allow(clippy::needless_pass_by_value)]
163 pub fn name(mut self, name: impl ToString) -> Self {
164 self.name = name.to_string();
165 self
166 }
167}
168
169fn y_intersection(p1: &Pos2, p2: &Pos2, y: f32) -> Option<f32> {
172 ((p1.y > y && p2.y < y) || (p1.y < y && p2.y > y))
173 .then_some(((y * (p1.x - p2.x)) - (p1.x * p2.y - p1.y * p2.x)) / (p1.y - p2.y))
174}
175
176impl PlotItem for Line {
177 fn shapes(&self, _ui: &mut Ui, transform: &PlotTransform, shapes: &mut Vec<Shape>) {
178 let Self {
179 series,
180 stroke,
181 highlight,
182 mut fill,
183 style,
184 ..
185 } = self;
186
187 let values_tf: Vec<_> = series
188 .points()
189 .iter()
190 .map(|v| transform.position_from_point(v))
191 .collect();
192 let n_values = values_tf.len();
193
194 if n_values < 2 {
196 fill = None;
197 }
198 if let Some(y_reference) = fill {
199 let mut fill_alpha = DEFAULT_FILL_ALPHA;
200 if *highlight {
201 fill_alpha = (2.0 * fill_alpha).at_most(1.0);
202 }
203 let y = transform
204 .position_from_point(&PlotPoint::new(0.0, y_reference))
205 .y;
206 let fill_color = Rgba::from(stroke.color)
207 .to_opaque()
208 .multiply(fill_alpha)
209 .into();
210 let mut mesh = Mesh::default();
211 let expected_intersections = 20;
212 mesh.reserve_triangles((n_values - 1) * 2);
213 mesh.reserve_vertices(n_values * 2 + expected_intersections);
214 values_tf.windows(2).for_each(|w| {
215 let i = mesh.vertices.len() as u32;
216 mesh.colored_vertex(w[0], fill_color);
217 mesh.colored_vertex(pos2(w[0].x, y), fill_color);
218 if let Some(x) = y_intersection(&w[0], &w[1], y) {
219 let point = pos2(x, y);
220 mesh.colored_vertex(point, fill_color);
221 mesh.add_triangle(i, i + 1, i + 2);
222 mesh.add_triangle(i + 2, i + 3, i + 4);
223 } else {
224 mesh.add_triangle(i, i + 1, i + 2);
225 mesh.add_triangle(i + 1, i + 2, i + 3);
226 }
227 });
228 let last = values_tf[n_values - 1];
229 mesh.colored_vertex(last, fill_color);
230 mesh.colored_vertex(pos2(last.x, y), fill_color);
231 shapes.push(Shape::Mesh(mesh));
232 }
233 style.style_line(values_tf, *stroke, *highlight, shapes);
234 }
235
236 fn initialize(&mut self, x_range: RangeInclusive<f64>) {
237 self.series.generate_points(x_range);
238 }
239
240 fn name(&self) -> &str {
241 self.name.as_str()
242 }
243
244 fn color(&self) -> Color32 {
245 self.stroke.color
246 }
247
248 fn highlight(&mut self) {
249 self.highlight = true;
250 }
251
252 fn highlighted(&self) -> bool {
253 self.highlight
254 }
255
256 fn geometry(&self) -> PlotGeometry<'_> {
257 PlotGeometry::Points(self.series.points())
258 }
259
260 fn bounds(&self) -> PlotBounds {
261 self.series.bounds()
262 }
263}
264
265pub struct StackedLine {
267 pub(super) series: PlotPoints,
268 pub(super) stroke: Stroke,
269 pub(super) name: String,
270 pub(super) highlight: bool,
271 pub(super) stacked_on: Option<PlotPoints>,
272 pub(super) style: LineStyle,
273}
274
275impl StackedLine {
276 pub fn new(series: impl Into<PlotPoints>) -> Self {
277 Self {
278 series: series.into(),
279 stroke: Stroke::new(1.0, Color32::TRANSPARENT),
280 name: Default::default(),
281 highlight: false,
282 stacked_on: None,
283 style: LineStyle::Solid,
284 }
285 }
286
287 pub fn highlight(mut self, highlight: bool) -> Self {
289 self.highlight = highlight;
290 self
291 }
292
293 pub fn stroke(mut self, stroke: impl Into<Stroke>) -> Self {
295 self.stroke = stroke.into();
296 self
297 }
298
299 pub fn width(mut self, width: impl Into<f32>) -> Self {
301 self.stroke.width = width.into();
302 self
303 }
304
305 pub fn color(mut self, color: impl Into<Color32>) -> Self {
307 self.stroke.color = color.into();
308 self
309 }
310
311 pub fn stacked_on(mut self, stacked_on: impl Into<PlotPoints>) -> Self {
313 self.stacked_on = Some(stacked_on.into());
314 self
315 }
316
317 pub fn style(mut self, style: LineStyle) -> Self {
319 self.style = style;
320 self
321 }
322
323 #[allow(clippy::needless_pass_by_value)]
330 pub fn name(mut self, name: impl ToString) -> Self {
331 self.name = name.to_string();
332 self
333 }
334}
335
336impl PlotItem for StackedLine {
337 fn shapes(&self, _ui: &mut Ui, transform: &PlotTransform, shapes: &mut Vec<Shape>) {
338 let Self {
339 series,
340 stroke,
341 highlight,
342 stacked_on,
343 style,
344 ..
345 } = self;
346
347 if series.points().len() <= 1 {
348 let values_tf: Vec<_> = series
349 .points()
350 .iter()
351 .map(|v| transform.position_from_point(v))
352 .collect();
353 style.style_line(values_tf, *stroke, *highlight, shapes);
354 return;
355 }
356
357 let bottom = PlotPoints::new(
358 (0..series.points().len())
359 .map(|i| [i as f64, 0.0])
360 .collect(),
361 );
362 let stacked_on = stacked_on.as_ref().unwrap_or(&bottom);
363 let values_tf: Vec<_> = series
364 .points()
365 .iter()
366 .zip(stacked_on.points().iter())
367 .map(|(v, y)| {
368 (
369 transform.position_from_point(v),
370 transform.position_from_point(y),
371 )
372 })
373 .collect();
374
375 let n_values = values_tf.len();
376 let mut fill_alpha = DEFAULT_FILL_ALPHA;
377 if *highlight {
378 fill_alpha = (2.0 * fill_alpha).at_most(1.0);
379 }
380 let fill_color = Rgba::from(stroke.color)
381 .to_opaque()
382 .multiply(fill_alpha)
383 .into();
384 let mut mesh = Mesh::default();
385 let expected_intersections = 20;
386 mesh.reserve_triangles((n_values - 1) * 2);
387 mesh.reserve_vertices(n_values * 2 + expected_intersections);
388 values_tf.windows(2).for_each(|w| {
389 let primary = [w[0].0, w[1].0];
390 let stacked = [w[0].1, w[1].1];
391 let i = mesh.vertices.len() as u32;
392 mesh.colored_vertex(primary[0], fill_color);
393 mesh.colored_vertex(pos2(primary[0].x, stacked[0].y), fill_color);
394 if let Some(x) = y_intersection(&primary[0], &primary[1], stacked[0].y) {
395 let point = pos2(x, stacked[0].y);
396 mesh.colored_vertex(point, fill_color);
397 mesh.add_triangle(i, i + 1, i + 2);
398 mesh.add_triangle(i + 2, i + 3, i + 4);
399 } else {
400 mesh.add_triangle(i, i + 1, i + 2);
401 mesh.add_triangle(i + 1, i + 2, i + 3);
402 }
403 });
404 let last = values_tf[n_values - 1];
405 mesh.colored_vertex(last.0, fill_color);
406 mesh.colored_vertex(pos2(last.0.x, last.1.y), fill_color);
407 shapes.push(Shape::Mesh(mesh));
408
409 let values_tf = values_tf.iter().map(|e| e.0).collect();
410 style.style_line(values_tf, *stroke, *highlight, shapes);
411 }
412
413 fn initialize(&mut self, x_range: RangeInclusive<f64>) {
414 self.series.generate_points(x_range);
415 }
416
417 fn name(&self) -> &str {
418 self.name.as_str()
419 }
420
421 fn color(&self) -> Color32 {
422 self.stroke.color
423 }
424
425 fn highlight(&mut self) {
426 self.highlight = true;
427 }
428
429 fn highlighted(&self) -> bool {
430 self.highlight
431 }
432
433 fn geometry(&self) -> PlotGeometry<'_> {
434 PlotGeometry::Points(self.series.points())
435 }
436
437 fn bounds(&self) -> PlotBounds {
438 self.series.bounds()
439 }
440}
441
442pub struct Polygon {
444 pub(super) series: PlotPoints,
445 pub(super) stroke: Stroke,
446 pub(super) name: String,
447 pub(super) highlight: bool,
448 pub(super) fill_color: Option<Color32>,
449 pub(super) style: LineStyle,
450}
451
452impl Polygon {
453 pub fn new(series: impl Into<PlotPoints>) -> Self {
454 Self {
455 series: series.into(),
456 stroke: Stroke::new(1.0, Color32::TRANSPARENT),
457 name: Default::default(),
458 highlight: false,
459 fill_color: None,
460 style: LineStyle::Solid,
461 }
462 }
463
464 pub fn highlight(mut self, highlight: bool) -> Self {
467 self.highlight = highlight;
468 self
469 }
470
471 pub fn stroke(mut self, stroke: impl Into<Stroke>) -> Self {
473 self.stroke = stroke.into();
474 self
475 }
476
477 pub fn width(mut self, width: impl Into<f32>) -> Self {
479 self.stroke.width = width.into();
480 self
481 }
482
483 #[deprecated = "Use `fill_color`."]
484 #[allow(unused, clippy::needless_pass_by_value)]
485 pub fn color(mut self, color: impl Into<Color32>) -> Self {
486 self
487 }
488
489 #[deprecated = "Use `fill_color`."]
490 #[allow(unused, clippy::needless_pass_by_value)]
491 pub fn fill_alpha(mut self, _alpha: impl Into<f32>) -> Self {
492 self
493 }
494
495 pub fn fill_color(mut self, color: impl Into<Color32>) -> Self {
497 self.fill_color = Some(color.into());
498 self
499 }
500
501 pub fn style(mut self, style: LineStyle) -> Self {
503 self.style = style;
504 self
505 }
506
507 #[allow(clippy::needless_pass_by_value)]
514 pub fn name(mut self, name: impl ToString) -> Self {
515 self.name = name.to_string();
516 self
517 }
518}
519
520impl PlotItem for Polygon {
521 fn shapes(&self, _ui: &mut Ui, transform: &PlotTransform, shapes: &mut Vec<Shape>) {
522 let Self {
523 series,
524 stroke,
525 highlight,
526 fill_color,
527 style,
528 ..
529 } = self;
530
531 let mut values_tf: Vec<_> = series
532 .points()
533 .iter()
534 .map(|v| transform.position_from_point(v))
535 .collect();
536
537 let fill_color = fill_color.unwrap_or(stroke.color.linear_multiply(DEFAULT_FILL_ALPHA));
538
539 let shape = Shape::convex_polygon(values_tf.clone(), fill_color, Stroke::NONE);
540 shapes.push(shape);
541 values_tf.push(*values_tf.first().unwrap());
542 style.style_line(values_tf, *stroke, *highlight, shapes);
543 }
544
545 fn initialize(&mut self, x_range: RangeInclusive<f64>) {
546 self.series.generate_points(x_range);
547 }
548
549 fn name(&self) -> &str {
550 self.name.as_str()
551 }
552
553 fn color(&self) -> Color32 {
554 self.stroke.color
555 }
556
557 fn highlight(&mut self) {
558 self.highlight = true;
559 }
560
561 fn highlighted(&self) -> bool {
562 self.highlight
563 }
564
565 fn geometry(&self) -> PlotGeometry<'_> {
566 PlotGeometry::Points(self.series.points())
567 }
568
569 fn bounds(&self) -> PlotBounds {
570 self.series.bounds()
571 }
572}
573
574#[derive(Clone)]
576pub struct Text {
577 pub(super) text: WidgetText,
578 pub(super) position: PlotPoint,
579 pub(super) name: String,
580 pub(super) highlight: bool,
581 pub(super) color: Color32,
582 pub(super) anchor: Align2,
583}
584
585impl Text {
586 pub fn new(position: PlotPoint, text: impl Into<WidgetText>) -> Self {
587 Self {
588 text: text.into(),
589 position,
590 name: Default::default(),
591 highlight: false,
592 color: Color32::TRANSPARENT,
593 anchor: Align2::CENTER_CENTER,
594 }
595 }
596
597 pub fn highlight(mut self, highlight: bool) -> Self {
599 self.highlight = highlight;
600 self
601 }
602
603 pub fn color(mut self, color: impl Into<Color32>) -> Self {
605 self.color = color.into();
606 self
607 }
608
609 pub fn anchor(mut self, anchor: Align2) -> Self {
611 self.anchor = anchor;
612 self
613 }
614
615 #[allow(clippy::needless_pass_by_value)]
622 pub fn name(mut self, name: impl ToString) -> Self {
623 self.name = name.to_string();
624 self
625 }
626}
627
628impl PlotItem for Text {
629 fn shapes(&self, ui: &mut Ui, transform: &PlotTransform, shapes: &mut Vec<Shape>) {
630 let color = if self.color == Color32::TRANSPARENT {
631 ui.style().visuals.text_color()
632 } else {
633 self.color
634 };
635
636 let galley =
637 self.text
638 .clone()
639 .into_galley(ui, Some(false), f32::INFINITY, TextStyle::Small);
640
641 let pos = transform.position_from_point(&self.position);
642 let rect = self
643 .anchor
644 .anchor_rect(Rect::from_min_size(pos, galley.size()));
645
646 shapes.push(egui::epaint::TextShape::new(rect.min, galley, color).into());
647
648 if self.highlight {
649 shapes.push(Shape::rect_stroke(
650 rect.expand(2.0),
651 1.0,
652 Stroke::new(0.5, color),
653 ));
654 }
655 }
656
657 fn initialize(&mut self, _x_range: RangeInclusive<f64>) {}
658
659 fn name(&self) -> &str {
660 self.name.as_str()
661 }
662
663 fn color(&self) -> Color32 {
664 self.color
665 }
666
667 fn highlight(&mut self) {
668 self.highlight = true;
669 }
670
671 fn highlighted(&self) -> bool {
672 self.highlight
673 }
674
675 fn geometry(&self) -> PlotGeometry<'_> {
676 PlotGeometry::None
677 }
678
679 fn bounds(&self) -> PlotBounds {
680 let mut bounds = PlotBounds::NOTHING;
681 bounds.extend_with(&self.position);
682 bounds
683 }
684}
685
686pub struct Points {
688 pub(super) series: PlotPoints,
689
690 pub(super) shape: MarkerShape,
691
692 pub(super) color: Color32,
694
695 pub(super) filled: bool,
697
698 pub(super) radius: f32,
700
701 pub(super) name: String,
702
703 pub(super) highlight: bool,
704
705 pub(super) stems: Option<f32>,
706}
707
708impl Points {
709 pub fn new(series: impl Into<PlotPoints>) -> Self {
710 Self {
711 series: series.into(),
712 shape: MarkerShape::Circle,
713 color: Color32::TRANSPARENT,
714 filled: true,
715 radius: 1.0,
716 name: Default::default(),
717 highlight: false,
718 stems: None,
719 }
720 }
721
722 pub fn shape(mut self, shape: MarkerShape) -> Self {
724 self.shape = shape;
725 self
726 }
727
728 pub fn highlight(mut self, highlight: bool) -> Self {
730 self.highlight = highlight;
731 self
732 }
733
734 pub fn color(mut self, color: impl Into<Color32>) -> Self {
736 self.color = color.into();
737 self
738 }
739
740 pub fn filled(mut self, filled: bool) -> Self {
742 self.filled = filled;
743 self
744 }
745
746 pub fn stems(mut self, y_reference: impl Into<f32>) -> Self {
748 self.stems = Some(y_reference.into());
749 self
750 }
751
752 pub fn radius(mut self, radius: impl Into<f32>) -> Self {
754 self.radius = radius.into();
755 self
756 }
757
758 #[allow(clippy::needless_pass_by_value)]
765 pub fn name(mut self, name: impl ToString) -> Self {
766 self.name = name.to_string();
767 self
768 }
769}
770
771impl PlotItem for Points {
772 fn shapes(&self, _ui: &mut Ui, transform: &PlotTransform, shapes: &mut Vec<Shape>) {
773 let sqrt_3 = 3_f32.sqrt();
774 let frac_sqrt_3_2 = 3_f32.sqrt() / 2.0;
775 let frac_1_sqrt_2 = 1.0 / 2_f32.sqrt();
776
777 let Self {
778 series,
779 shape,
780 color,
781 filled,
782 mut radius,
783 highlight,
784 stems,
785 ..
786 } = self;
787
788 let stroke_size = radius / 5.0;
789
790 let default_stroke = Stroke::new(stroke_size, *color);
791 let mut stem_stroke = default_stroke;
792 let (fill, stroke) = if *filled {
793 (*color, Stroke::NONE)
794 } else {
795 (Color32::TRANSPARENT, default_stroke)
796 };
797
798 if *highlight {
799 radius *= 2f32.sqrt();
800 stem_stroke.width *= 2.0;
801 }
802
803 let y_reference = stems.map(|y| transform.position_from_point(&PlotPoint::new(0.0, y)).y);
804
805 series
806 .points()
807 .iter()
808 .map(|value| transform.position_from_point(value))
809 .for_each(|center| {
810 let tf = |dx: f32, dy: f32| -> Pos2 { center + radius * vec2(dx, dy) };
811
812 if let Some(y) = y_reference {
813 let stem = Shape::line_segment([center, pos2(center.x, y)], stem_stroke);
814 shapes.push(stem);
815 }
816
817 match shape {
818 MarkerShape::Circle => {
819 shapes.push(Shape::Circle(epaint::CircleShape {
820 center,
821 radius,
822 fill,
823 stroke,
824 }));
825 }
826 MarkerShape::Diamond => {
827 let points = vec![
828 tf(0.0, 1.0), tf(-1.0, 0.0), tf(0.0, -1.0), tf(1.0, 0.0), ];
833 shapes.push(Shape::convex_polygon(points, fill, stroke));
834 }
835 MarkerShape::Square => {
836 let points = vec![
837 tf(-frac_1_sqrt_2, frac_1_sqrt_2),
838 tf(-frac_1_sqrt_2, -frac_1_sqrt_2),
839 tf(frac_1_sqrt_2, -frac_1_sqrt_2),
840 tf(frac_1_sqrt_2, frac_1_sqrt_2),
841 ];
842 shapes.push(Shape::convex_polygon(points, fill, stroke));
843 }
844 MarkerShape::Cross => {
845 let diagonal1 = [
846 tf(-frac_1_sqrt_2, -frac_1_sqrt_2),
847 tf(frac_1_sqrt_2, frac_1_sqrt_2),
848 ];
849 let diagonal2 = [
850 tf(frac_1_sqrt_2, -frac_1_sqrt_2),
851 tf(-frac_1_sqrt_2, frac_1_sqrt_2),
852 ];
853 shapes.push(Shape::line_segment(diagonal1, default_stroke));
854 shapes.push(Shape::line_segment(diagonal2, default_stroke));
855 }
856 MarkerShape::Plus => {
857 let horizontal = [tf(-1.0, 0.0), tf(1.0, 0.0)];
858 let vertical = [tf(0.0, -1.0), tf(0.0, 1.0)];
859 shapes.push(Shape::line_segment(horizontal, default_stroke));
860 shapes.push(Shape::line_segment(vertical, default_stroke));
861 }
862 MarkerShape::Up => {
863 let points =
864 vec![tf(0.0, -1.0), tf(0.5 * sqrt_3, 0.5), tf(-0.5 * sqrt_3, 0.5)];
865 shapes.push(Shape::convex_polygon(points, fill, stroke));
866 }
867 MarkerShape::Down => {
868 let points = vec![
869 tf(0.0, 1.0),
870 tf(-0.5 * sqrt_3, -0.5),
871 tf(0.5 * sqrt_3, -0.5),
872 ];
873 shapes.push(Shape::convex_polygon(points, fill, stroke));
874 }
875 MarkerShape::Left => {
876 let points =
877 vec![tf(-1.0, 0.0), tf(0.5, -0.5 * sqrt_3), tf(0.5, 0.5 * sqrt_3)];
878 shapes.push(Shape::convex_polygon(points, fill, stroke));
879 }
880 MarkerShape::Right => {
881 let points = vec![
882 tf(1.0, 0.0),
883 tf(-0.5, 0.5 * sqrt_3),
884 tf(-0.5, -0.5 * sqrt_3),
885 ];
886 shapes.push(Shape::convex_polygon(points, fill, stroke));
887 }
888 MarkerShape::Asterisk => {
889 let vertical = [tf(0.0, -1.0), tf(0.0, 1.0)];
890 let diagonal1 = [tf(-frac_sqrt_3_2, 0.5), tf(frac_sqrt_3_2, -0.5)];
891 let diagonal2 = [tf(-frac_sqrt_3_2, -0.5), tf(frac_sqrt_3_2, 0.5)];
892 shapes.push(Shape::line_segment(vertical, default_stroke));
893 shapes.push(Shape::line_segment(diagonal1, default_stroke));
894 shapes.push(Shape::line_segment(diagonal2, default_stroke));
895 }
896 }
897 });
898 }
899
900 fn initialize(&mut self, x_range: RangeInclusive<f64>) {
901 self.series.generate_points(x_range);
902 }
903
904 fn name(&self) -> &str {
905 self.name.as_str()
906 }
907
908 fn color(&self) -> Color32 {
909 self.color
910 }
911
912 fn highlight(&mut self) {
913 self.highlight = true;
914 }
915
916 fn highlighted(&self) -> bool {
917 self.highlight
918 }
919
920 fn geometry(&self) -> PlotGeometry<'_> {
921 PlotGeometry::Points(self.series.points())
922 }
923
924 fn bounds(&self) -> PlotBounds {
925 self.series.bounds()
926 }
927}
928
929pub(crate) fn rulers_color(ui: &Ui) -> Color32 {
933 if ui.visuals().dark_mode {
934 Color32::from_gray(100).additive()
935 } else {
936 Color32::from_black_alpha(180)
937 }
938}
939
940pub(crate) fn vertical_line(
941 pointer: Pos2,
942 transform: &PlotTransform,
943 line_color: Color32,
944) -> Shape {
945 let frame = transform.frame();
946 Shape::line_segment(
947 [
948 pos2(pointer.x, frame.top()),
949 pos2(pointer.x, frame.bottom()),
950 ],
951 (1.0, line_color),
952 )
953}
954
955pub(crate) fn horizontal_line(
956 pointer: Pos2,
957 transform: &PlotTransform,
958 line_color: Color32,
959) -> Shape {
960 let frame = transform.frame();
961 Shape::line_segment(
962 [
963 pos2(frame.left(), pointer.y),
964 pos2(frame.right(), pointer.y),
965 ],
966 (1.0, line_color),
967 )
968}
969
970#[allow(clippy::too_many_arguments)]
973pub(super) fn rulers_at_value(
974 pointer: Pos2,
975 value: PlotPoint,
976 name: &str,
977 plot: &PlotConfig<'_>,
978 shapes: &mut Vec<Shape>,
979 cursors: &mut Vec<Cursor>,
980 label_formatter: &LabelFormatter,
981) {
982 if plot.show_x {
983 cursors.push(Cursor::Vertical { x: value.x });
984 }
985 if plot.show_y {
986 cursors.push(Cursor::Horizontal { y: value.y });
987 }
988
989 let mut prefix = String::new();
990
991 if !name.is_empty() {
992 prefix = format!("{name}\n");
993 }
994
995 let text = {
996 let scale = plot.transform.dvalue_dpos();
997 let x_decimals = ((-scale[0].abs().log10()).ceil().at_least(0.0) as usize).clamp(1, 6);
998 let y_decimals = ((-scale[1].abs().log10()).ceil().at_least(0.0) as usize).clamp(1, 6);
999 if let Some(custom_label) = label_formatter {
1000 custom_label(name, &value)
1001 } else if plot.show_x && plot.show_y {
1002 format!(
1003 "{}x = {:.*}\ny = {:.*}",
1004 prefix, x_decimals, value.x, y_decimals, value.y
1005 )
1006 } else if plot.show_x {
1007 format!("{}x = {:.*}", prefix, x_decimals, value.x)
1008 } else if plot.show_y {
1009 format!("{}y = {:.*}", prefix, y_decimals, value.y)
1010 } else {
1011 unreachable!()
1012 }
1013 };
1014
1015 let font_id = TextStyle::Body.resolve(plot.ui.style());
1016 plot.ui.fonts(|f| {
1017 shapes.push(Shape::text(
1018 f,
1019 pointer + vec2(3.0, -2.0),
1020 Align2::LEFT_BOTTOM,
1021 text,
1022 font_id,
1023 plot.ui.visuals().text_color(),
1024 ));
1025 });
1026}