1#![expect(clippy::type_complexity)] use std::{ops::RangeInclusive, sync::Arc};
5
6use egui::{
7 Align2, Color32, CornerRadius, Id, ImageOptions, Mesh, NumExt as _, PopupAnchor, Pos2, Rect,
8 Rgba, Shape, Stroke, TextStyle, TextureId, Ui, Vec2, WidgetText,
9 emath::Rot2,
10 epaint::{CircleShape, PathStroke, TextShape},
11 pos2, vec2,
12};
13
14use emath::Float as _;
15use rect_elem::{RectElement, highlighted_color};
16
17use super::{Cursor, LabelFormatter, PlotBounds, PlotTransform};
18
19pub use bar::Bar;
20pub use box_elem::{BoxElem, BoxSpread};
21pub use values::{
22 ClosestElem, LineStyle, MarkerShape, Orientation, PlotGeometry, PlotPoint, PlotPoints,
23};
24
25mod bar;
26mod box_elem;
27mod rect_elem;
28mod values;
29
30const DEFAULT_FILL_ALPHA: f32 = 0.05;
31
32#[derive(Clone, Debug, PartialEq, Eq)]
33pub struct PlotItemBase {
34 name: String,
35 id: Id,
36 highlight: bool,
37 allow_hover: bool,
38}
39
40impl PlotItemBase {
41 pub fn new(name: String) -> Self {
42 let id = Id::new(&name);
43 Self {
44 name,
45 id,
46 highlight: false,
47 allow_hover: true,
48 }
49 }
50}
51
52macro_rules! builder_methods_for_base {
53 () => {
54 #[inline]
63 pub fn name(mut self, name: impl ToString) -> Self {
64 self.base_mut().name = name.to_string();
65 self
66 }
67
68 #[inline]
72 pub fn highlight(mut self, highlight: bool) -> Self {
73 self.base_mut().highlight = highlight;
74 self
75 }
76
77 #[inline]
79 pub fn allow_hover(mut self, hovering: bool) -> Self {
80 self.base_mut().allow_hover = hovering;
81 self
82 }
83
84 #[inline]
89 pub fn id(mut self, id: impl Into<Id>) -> Self {
90 self.base_mut().id = id.into();
91 self
92 }
93 };
94}
95
96pub struct PlotConfig<'a> {
98 pub ui: &'a Ui,
99 pub transform: &'a PlotTransform,
100 pub show_x: bool,
101 pub show_y: bool,
102}
103
104pub trait PlotItem {
106 fn shapes(&self, ui: &Ui, transform: &PlotTransform, shapes: &mut Vec<Shape>);
107
108 fn initialize(&mut self, x_range: RangeInclusive<f64>);
110
111 fn name(&self) -> &str {
112 &self.base().name
113 }
114
115 fn color(&self) -> Color32;
116
117 fn highlight(&mut self) {
118 self.base_mut().highlight = true;
119 }
120
121 fn highlighted(&self) -> bool {
122 self.base().highlight
123 }
124
125 fn allow_hover(&self) -> bool {
127 self.base().allow_hover
128 }
129
130 fn geometry(&self) -> PlotGeometry<'_>;
131
132 fn bounds(&self) -> PlotBounds;
133
134 fn base(&self) -> &PlotItemBase;
135
136 fn base_mut(&mut self) -> &mut PlotItemBase;
137
138 fn id(&self) -> Id {
139 self.base().id
140 }
141
142 fn find_closest(&self, point: Pos2, transform: &PlotTransform) -> Option<ClosestElem> {
143 match self.geometry() {
144 PlotGeometry::None => None,
145
146 PlotGeometry::Points(points) => points
147 .iter()
148 .enumerate()
149 .map(|(index, value)| {
150 let pos = transform.position_from_point(value);
151 let dist_sq = point.distance_sq(pos);
152 ClosestElem { index, dist_sq }
153 })
154 .min_by_key(|e| e.dist_sq.ord()),
155
156 PlotGeometry::Rects => {
157 panic!("If the PlotItem is made of rects, it should implement find_closest()")
158 }
159 }
160 }
161
162 fn on_hover(
163 &self,
164 plot_area_response: &egui::Response,
165 elem: ClosestElem,
166 shapes: &mut Vec<Shape>,
167 cursors: &mut Vec<Cursor>,
168 plot: &PlotConfig<'_>,
169 label_formatter: &LabelFormatter<'_>,
170 ) {
171 let points = match self.geometry() {
172 PlotGeometry::Points(points) => points,
173 PlotGeometry::None => {
174 panic!("If the PlotItem has no geometry, on_hover() must not be called")
175 }
176 PlotGeometry::Rects => {
177 panic!("If the PlotItem is made of rects, it should implement on_hover()")
178 }
179 };
180
181 let line_color = if plot.ui.visuals().dark_mode {
182 Color32::from_gray(100).additive()
183 } else {
184 Color32::from_black_alpha(180)
185 };
186
187 let value = points[elem.index];
189 let pointer = plot.transform.position_from_point(&value);
190 shapes.push(Shape::circle_filled(pointer, 3.0, line_color));
191
192 rulers_and_tooltip_at_value(
193 plot_area_response,
194 value,
195 self.name(),
196 plot,
197 cursors,
198 label_formatter,
199 );
200 }
201}
202
203#[derive(Clone, Debug, PartialEq)]
207pub struct HLine {
208 base: PlotItemBase,
209 pub(super) y: f64,
210 pub(super) stroke: Stroke,
211 pub(super) style: LineStyle,
212}
213
214impl HLine {
215 pub fn new(name: impl Into<String>, y: impl Into<f64>) -> Self {
216 Self {
217 base: PlotItemBase::new(name.into()),
218 y: y.into(),
219 stroke: Stroke::new(1.0, Color32::TRANSPARENT),
220 style: LineStyle::Solid,
221 }
222 }
223
224 #[inline]
226 pub fn stroke(mut self, stroke: impl Into<Stroke>) -> Self {
227 self.stroke = stroke.into();
228 self
229 }
230
231 #[inline]
233 pub fn width(mut self, width: impl Into<f32>) -> Self {
234 self.stroke.width = width.into();
235 self
236 }
237
238 #[inline]
240 pub fn color(mut self, color: impl Into<Color32>) -> Self {
241 self.stroke.color = color.into();
242 self
243 }
244
245 #[inline]
247 pub fn style(mut self, style: LineStyle) -> Self {
248 self.style = style;
249 self
250 }
251
252 builder_methods_for_base!();
253}
254
255impl PlotItem for HLine {
256 fn shapes(&self, _ui: &Ui, transform: &PlotTransform, shapes: &mut Vec<Shape>) {
257 let Self {
258 base,
259 y,
260 stroke,
261 style,
262 ..
263 } = self;
264
265 let points = vec![
266 transform.position_from_point(&PlotPoint::new(transform.bounds().min[0], *y)),
267 transform.position_from_point(&PlotPoint::new(transform.bounds().max[0], *y)),
268 ];
269 style.style_line(
270 points,
271 PathStroke::new(stroke.width, stroke.color),
272 base.highlight,
273 shapes,
274 );
275 }
276
277 fn initialize(&mut self, _x_range: RangeInclusive<f64>) {}
278
279 fn color(&self) -> Color32 {
280 self.stroke.color
281 }
282
283 fn base(&self) -> &PlotItemBase {
284 &self.base
285 }
286
287 fn base_mut(&mut self) -> &mut PlotItemBase {
288 &mut self.base
289 }
290
291 fn geometry(&self) -> PlotGeometry<'_> {
292 PlotGeometry::None
293 }
294
295 fn bounds(&self) -> PlotBounds {
296 let mut bounds = PlotBounds::NOTHING;
297 bounds.min[1] = self.y;
298 bounds.max[1] = self.y;
299 bounds
300 }
301}
302
303#[derive(Clone, Debug, PartialEq)]
305pub struct VLine {
306 base: PlotItemBase,
307 pub(super) x: f64,
308 pub(super) stroke: Stroke,
309 pub(super) style: LineStyle,
310}
311
312impl VLine {
313 pub fn new(name: impl Into<String>, x: impl Into<f64>) -> Self {
314 Self {
315 base: PlotItemBase::new(name.into()),
316 x: x.into(),
317 stroke: Stroke::new(1.0, Color32::TRANSPARENT),
318 style: LineStyle::Solid,
319 }
320 }
321
322 #[inline]
324 pub fn stroke(mut self, stroke: impl Into<Stroke>) -> Self {
325 self.stroke = stroke.into();
326 self
327 }
328
329 #[inline]
331 pub fn width(mut self, width: impl Into<f32>) -> Self {
332 self.stroke.width = width.into();
333 self
334 }
335
336 #[inline]
338 pub fn color(mut self, color: impl Into<Color32>) -> Self {
339 self.stroke.color = color.into();
340 self
341 }
342
343 #[inline]
345 pub fn style(mut self, style: LineStyle) -> Self {
346 self.style = style;
347 self
348 }
349
350 builder_methods_for_base!();
351}
352
353impl PlotItem for VLine {
354 fn shapes(&self, _ui: &Ui, transform: &PlotTransform, shapes: &mut Vec<Shape>) {
355 let Self {
356 base,
357 x,
358 stroke,
359 style,
360 ..
361 } = self;
362
363 let points = vec![
364 transform.position_from_point(&PlotPoint::new(*x, transform.bounds().min[1])),
365 transform.position_from_point(&PlotPoint::new(*x, transform.bounds().max[1])),
366 ];
367 style.style_line(
368 points,
369 PathStroke::new(stroke.width, stroke.color),
370 base.highlight,
371 shapes,
372 );
373 }
374
375 fn initialize(&mut self, _x_range: RangeInclusive<f64>) {}
376
377 fn color(&self) -> Color32 {
378 self.stroke.color
379 }
380
381 fn base(&self) -> &PlotItemBase {
382 &self.base
383 }
384
385 fn base_mut(&mut self) -> &mut PlotItemBase {
386 &mut self.base
387 }
388
389 fn geometry(&self) -> PlotGeometry<'_> {
390 PlotGeometry::None
391 }
392
393 fn bounds(&self) -> PlotBounds {
394 let mut bounds = PlotBounds::NOTHING;
395 bounds.min[0] = self.x;
396 bounds.max[0] = self.x;
397 bounds
398 }
399}
400
401pub struct Line<'a> {
403 base: PlotItemBase,
404 pub(super) series: PlotPoints<'a>,
405 pub(super) stroke: Stroke,
406 pub(super) fill: Option<f32>,
407 pub(super) fill_alpha: f32,
408 pub(super) gradient_color: Option<Arc<dyn Fn(PlotPoint) -> Color32 + Send + Sync>>,
409 pub(super) gradient_fill: bool,
410 pub(super) style: LineStyle,
411}
412
413impl<'a> Line<'a> {
414 pub fn new(name: impl Into<String>, series: impl Into<PlotPoints<'a>>) -> Self {
415 Self {
416 base: PlotItemBase::new(name.into()),
417 series: series.into(),
418 stroke: Stroke::new(1.5, Color32::TRANSPARENT), fill: None,
420 fill_alpha: DEFAULT_FILL_ALPHA,
421 gradient_color: None,
422 gradient_fill: false,
423 style: LineStyle::Solid,
424 }
425 }
426
427 #[inline]
429 pub fn stroke(mut self, stroke: impl Into<Stroke>) -> Self {
430 self.stroke = stroke.into();
431 self
432 }
433
434 #[inline]
441 pub fn gradient_color(
442 mut self,
443 callback: Arc<dyn Fn(PlotPoint) -> Color32 + Send + Sync>,
444 gradient_fill: bool,
445 ) -> Self {
446 self.gradient_color = Some(callback);
447 self.gradient_fill = gradient_fill;
448 self
449 }
450
451 #[inline]
453 pub fn width(mut self, width: impl Into<f32>) -> Self {
454 self.stroke.width = width.into();
455 self
456 }
457
458 #[inline]
460 pub fn color(mut self, color: impl Into<Color32>) -> Self {
461 self.stroke.color = color.into();
462 self
463 }
464
465 #[inline]
467 pub fn fill(mut self, y_reference: impl Into<f32>) -> Self {
468 self.fill = Some(y_reference.into());
469 self
470 }
471
472 #[inline]
474 pub fn fill_alpha(mut self, alpha: impl Into<f32>) -> Self {
475 self.fill_alpha = alpha.into();
476 self
477 }
478
479 #[inline]
481 pub fn style(mut self, style: LineStyle) -> Self {
482 self.style = style;
483 self
484 }
485
486 builder_methods_for_base!();
487}
488
489fn y_intersection(p1: &Pos2, p2: &Pos2, y: f32) -> Option<f32> {
492 ((p1.y > y && p2.y < y) || (p1.y < y && p2.y > y))
493 .then_some(((y * (p1.x - p2.x)) - (p1.x * p2.y - p1.y * p2.x)) / (p1.y - p2.y))
494}
495
496impl PlotItem for Line<'_> {
497 fn shapes(&self, _ui: &Ui, transform: &PlotTransform, shapes: &mut Vec<Shape>) {
498 let Self {
499 base,
500 series,
501 stroke,
502 fill,
503 gradient_fill,
504 style,
505 ..
506 } = self;
507 let mut fill = *fill;
508
509 let mut final_stroke: PathStroke = (*stroke).into();
510 if let Some(gradient_callback) = self.gradient_color.clone() {
513 let local_transform = *transform;
514 let wrapped_callback = move |_rec: Rect, pos: Pos2| -> Color32 {
515 let point = local_transform.value_from_position(pos);
516 gradient_callback(point)
517 };
518 final_stroke = PathStroke::new_uv(stroke.width, wrapped_callback.clone());
519 }
520
521 let values_tf: Vec<_> = series
522 .points()
523 .iter()
524 .map(|v| transform.position_from_point(v))
525 .collect();
526 let n_values = values_tf.len();
527
528 if n_values < 2 {
530 fill = None;
531 }
532 if let Some(y_reference) = fill {
533 let mut fill_alpha = self.fill_alpha;
534 if base.highlight {
535 fill_alpha = (2.0 * fill_alpha).at_most(1.0);
536 }
537 let y = transform
538 .position_from_point(&PlotPoint::new(0.0, y_reference))
539 .y;
540 let default_fill_color = Rgba::from(stroke.color)
541 .to_opaque()
542 .multiply(fill_alpha)
543 .into();
544
545 let fill_color_for_point = |pos| {
546 if *gradient_fill && self.gradient_color.is_some() {
547 Rgba::from(self
548 .gradient_color
549 .clone()
550 .expect("Could not find gradient color callback")(
551 transform.value_from_position(pos),
552 ))
553 .to_opaque()
554 .multiply(fill_alpha)
555 .into()
556 } else {
557 default_fill_color
558 }
559 };
560
561 let mut mesh = Mesh::default();
562 let expected_intersections = 20;
563 mesh.reserve_triangles((n_values - 1) * 2);
564 mesh.reserve_vertices(n_values * 2 + expected_intersections);
565 values_tf.windows(2).for_each(|w| {
566 let fill_color = fill_color_for_point(w[0]);
567 let i = mesh.vertices.len() as u32;
568 mesh.colored_vertex(w[0], fill_color);
569 mesh.colored_vertex(pos2(w[0].x, y), fill_color);
570 if let Some(x) = y_intersection(&w[0], &w[1], y) {
571 let point = pos2(x, y);
572 mesh.colored_vertex(point, fill_color_for_point(point));
573 mesh.add_triangle(i, i + 1, i + 2);
574 mesh.add_triangle(i + 2, i + 3, i + 4);
575 } else {
576 mesh.add_triangle(i, i + 1, i + 2);
577 mesh.add_triangle(i + 1, i + 2, i + 3);
578 }
579 });
580 let last = values_tf[n_values - 1];
581 let fill_color = fill_color_for_point(last);
582 mesh.colored_vertex(last, fill_color);
583 mesh.colored_vertex(pos2(last.x, y), fill_color);
584 shapes.push(Shape::Mesh(std::sync::Arc::new(mesh)));
585 }
586 style.style_line(values_tf, final_stroke, base.highlight, shapes);
587 }
588
589 fn initialize(&mut self, x_range: RangeInclusive<f64>) {
590 self.series.generate_points(x_range);
591 }
592
593 fn color(&self) -> Color32 {
594 self.stroke.color
595 }
596
597 fn base(&self) -> &PlotItemBase {
598 &self.base
599 }
600
601 fn base_mut(&mut self) -> &mut PlotItemBase {
602 &mut self.base
603 }
604
605 fn geometry(&self) -> PlotGeometry<'_> {
606 PlotGeometry::Points(self.series.points())
607 }
608
609 fn bounds(&self) -> PlotBounds {
610 self.series.bounds()
611 }
612}
613
614pub struct Polygon<'a> {
616 base: PlotItemBase,
617 pub(super) series: PlotPoints<'a>,
618 pub(super) stroke: Stroke,
619 pub(super) fill_color: Option<Color32>,
620 pub(super) style: LineStyle,
621}
622
623impl<'a> Polygon<'a> {
624 pub fn new(name: impl Into<String>, series: impl Into<PlotPoints<'a>>) -> Self {
625 Self {
626 base: PlotItemBase::new(name.into()),
627 series: series.into(),
628 stroke: Stroke::new(1.0, Color32::TRANSPARENT),
629 fill_color: None,
630 style: LineStyle::Solid,
631 }
632 }
633
634 #[inline]
636 pub fn stroke(mut self, stroke: impl Into<Stroke>) -> Self {
637 self.stroke = stroke.into();
638 self
639 }
640
641 #[inline]
643 pub fn width(mut self, width: impl Into<f32>) -> Self {
644 self.stroke.width = width.into();
645 self
646 }
647
648 #[inline]
650 pub fn fill_color(mut self, color: impl Into<Color32>) -> Self {
651 self.fill_color = Some(color.into());
652 self
653 }
654
655 #[inline]
657 pub fn style(mut self, style: LineStyle) -> Self {
658 self.style = style;
659 self
660 }
661
662 builder_methods_for_base!();
663}
664
665impl PlotItem for Polygon<'_> {
666 fn shapes(&self, _ui: &Ui, transform: &PlotTransform, shapes: &mut Vec<Shape>) {
667 let Self {
668 base,
669 series,
670 stroke,
671 fill_color,
672 style,
673 ..
674 } = self;
675
676 let mut values_tf: Vec<_> = series
677 .points()
678 .iter()
679 .map(|v| transform.position_from_point(v))
680 .collect();
681
682 let fill_color = fill_color.unwrap_or(stroke.color.linear_multiply(DEFAULT_FILL_ALPHA));
683
684 let shape = Shape::convex_polygon(values_tf.clone(), fill_color, Stroke::NONE);
685 shapes.push(shape);
686
687 if let Some(first) = values_tf.first() {
688 values_tf.push(*first); }
690
691 style.style_line(
692 values_tf,
693 PathStroke::new(stroke.width, stroke.color),
694 base.highlight,
695 shapes,
696 );
697 }
698
699 fn initialize(&mut self, x_range: RangeInclusive<f64>) {
700 self.series.generate_points(x_range);
701 }
702
703 fn color(&self) -> Color32 {
704 self.stroke.color
705 }
706
707 fn geometry(&self) -> PlotGeometry<'_> {
708 PlotGeometry::Points(self.series.points())
709 }
710
711 fn bounds(&self) -> PlotBounds {
712 self.series.bounds()
713 }
714
715 fn base(&self) -> &PlotItemBase {
716 &self.base
717 }
718
719 fn base_mut(&mut self) -> &mut PlotItemBase {
720 &mut self.base
721 }
722}
723
724#[derive(Clone)]
726pub struct Text {
727 base: PlotItemBase,
728 pub(super) text: WidgetText,
729 pub(super) position: PlotPoint,
730 pub(super) color: Color32,
731 pub(super) anchor: Align2,
732}
733
734impl Text {
735 pub fn new(name: impl Into<String>, position: PlotPoint, text: impl Into<WidgetText>) -> Self {
736 Self {
737 base: PlotItemBase::new(name.into()),
738 text: text.into(),
739 position,
740 color: Color32::TRANSPARENT,
741 anchor: Align2::CENTER_CENTER,
742 }
743 }
744
745 #[inline]
747 pub fn color(mut self, color: impl Into<Color32>) -> Self {
748 self.color = color.into();
749 self
750 }
751
752 #[inline]
754 pub fn anchor(mut self, anchor: Align2) -> Self {
755 self.anchor = anchor;
756 self
757 }
758
759 builder_methods_for_base!();
760}
761
762impl PlotItem for Text {
763 fn shapes(&self, ui: &Ui, transform: &PlotTransform, shapes: &mut Vec<Shape>) {
764 let color = if self.color == Color32::TRANSPARENT {
765 ui.style().visuals.text_color()
766 } else {
767 self.color
768 };
769
770 let galley = self.text.clone().into_galley(
771 ui,
772 Some(egui::TextWrapMode::Extend),
773 f32::INFINITY,
774 TextStyle::Small,
775 );
776
777 let pos = transform.position_from_point(&self.position);
778 let rect = self.anchor.anchor_size(pos, galley.size());
779
780 shapes.push(TextShape::new(rect.min, galley, color).into());
781
782 if self.base.highlight {
783 shapes.push(Shape::rect_stroke(
784 rect.expand(1.0),
785 1.0,
786 Stroke::new(0.5, color),
787 egui::StrokeKind::Outside,
788 ));
789 }
790 }
791
792 fn initialize(&mut self, _x_range: RangeInclusive<f64>) {}
793
794 fn color(&self) -> Color32 {
795 self.color
796 }
797
798 fn geometry(&self) -> PlotGeometry<'_> {
799 PlotGeometry::None
800 }
801
802 fn bounds(&self) -> PlotBounds {
803 let mut bounds = PlotBounds::NOTHING;
804 bounds.extend_with(&self.position);
805 bounds
806 }
807
808 fn base(&self) -> &PlotItemBase {
809 &self.base
810 }
811
812 fn base_mut(&mut self) -> &mut PlotItemBase {
813 &mut self.base
814 }
815}
816
817pub struct Points<'a> {
819 base: PlotItemBase,
820
821 pub(super) series: PlotPoints<'a>,
822
823 pub(super) shape: MarkerShape,
824
825 pub(super) color: Color32,
827
828 pub(super) filled: bool,
830
831 pub(super) radius: f32,
833
834 pub(super) stems: Option<f32>,
835}
836
837impl<'a> Points<'a> {
838 pub fn new(name: impl Into<String>, series: impl Into<PlotPoints<'a>>) -> Self {
839 Self {
840 base: PlotItemBase::new(name.into()),
841 series: series.into(),
842 shape: MarkerShape::Circle,
843 color: Color32::TRANSPARENT,
844 filled: true,
845 radius: 1.0,
846 stems: None,
847 }
848 }
849
850 #[inline]
852 pub fn shape(mut self, shape: MarkerShape) -> Self {
853 self.shape = shape;
854 self
855 }
856
857 #[inline]
859 pub fn color(mut self, color: impl Into<Color32>) -> Self {
860 self.color = color.into();
861 self
862 }
863
864 #[inline]
866 pub fn filled(mut self, filled: bool) -> Self {
867 self.filled = filled;
868 self
869 }
870
871 #[inline]
873 pub fn stems(mut self, y_reference: impl Into<f32>) -> Self {
874 self.stems = Some(y_reference.into());
875 self
876 }
877
878 #[inline]
880 pub fn radius(mut self, radius: impl Into<f32>) -> Self {
881 self.radius = radius.into();
882 self
883 }
884
885 builder_methods_for_base!();
886}
887
888impl PlotItem for Points<'_> {
889 fn shapes(&self, _ui: &Ui, transform: &PlotTransform, shapes: &mut Vec<Shape>) {
890 let sqrt_3 = 3_f32.sqrt();
891 let frac_sqrt_3_2 = 3_f32.sqrt() / 2.0;
892 let frac_1_sqrt_2 = 1.0 / 2_f32.sqrt();
893
894 let Self {
895 base,
896 series,
897 shape,
898 color,
899 filled,
900 radius,
901 stems,
902 ..
903 } = self;
904
905 let mut radius = *radius;
906
907 let stroke_size = radius / 5.0;
908
909 let default_stroke = Stroke::new(stroke_size, *color);
910 let mut stem_stroke = default_stroke;
911 let (fill, stroke) = if *filled {
912 (*color, Stroke::NONE)
913 } else {
914 (Color32::TRANSPARENT, default_stroke)
915 };
916
917 if base.highlight {
918 radius *= 2f32.sqrt();
919 stem_stroke.width *= 2.0;
920 }
921
922 let y_reference = stems.map(|y| transform.position_from_point(&PlotPoint::new(0.0, y)).y);
923
924 series
925 .points()
926 .iter()
927 .map(|value| transform.position_from_point(value))
928 .for_each(|center| {
929 let tf = |dx: f32, dy: f32| -> Pos2 { center + radius * vec2(dx, dy) };
930
931 if let Some(y) = y_reference {
932 let stem = Shape::line_segment([center, pos2(center.x, y)], stem_stroke);
933 shapes.push(stem);
934 }
935
936 match shape {
937 MarkerShape::Circle => {
938 shapes.push(Shape::Circle(CircleShape {
939 center,
940 radius,
941 fill,
942 stroke,
943 }));
944 }
945 MarkerShape::Diamond => {
946 let points = vec![
947 tf(0.0, 1.0), tf(-1.0, 0.0), tf(0.0, -1.0), tf(1.0, 0.0), ];
952 shapes.push(Shape::convex_polygon(points, fill, stroke));
953 }
954 MarkerShape::Square => {
955 let points = vec![
956 tf(-frac_1_sqrt_2, frac_1_sqrt_2),
957 tf(-frac_1_sqrt_2, -frac_1_sqrt_2),
958 tf(frac_1_sqrt_2, -frac_1_sqrt_2),
959 tf(frac_1_sqrt_2, frac_1_sqrt_2),
960 ];
961 shapes.push(Shape::convex_polygon(points, fill, stroke));
962 }
963 MarkerShape::Cross => {
964 let diagonal1 = [
965 tf(-frac_1_sqrt_2, -frac_1_sqrt_2),
966 tf(frac_1_sqrt_2, frac_1_sqrt_2),
967 ];
968 let diagonal2 = [
969 tf(frac_1_sqrt_2, -frac_1_sqrt_2),
970 tf(-frac_1_sqrt_2, frac_1_sqrt_2),
971 ];
972 shapes.push(Shape::line_segment(diagonal1, default_stroke));
973 shapes.push(Shape::line_segment(diagonal2, default_stroke));
974 }
975 MarkerShape::Plus => {
976 let horizontal = [tf(-1.0, 0.0), tf(1.0, 0.0)];
977 let vertical = [tf(0.0, -1.0), tf(0.0, 1.0)];
978 shapes.push(Shape::line_segment(horizontal, default_stroke));
979 shapes.push(Shape::line_segment(vertical, default_stroke));
980 }
981 MarkerShape::Up => {
982 let points =
983 vec![tf(0.0, -1.0), tf(0.5 * sqrt_3, 0.5), tf(-0.5 * sqrt_3, 0.5)];
984 shapes.push(Shape::convex_polygon(points, fill, stroke));
985 }
986 MarkerShape::Down => {
987 let points = vec![
988 tf(0.0, 1.0),
989 tf(-0.5 * sqrt_3, -0.5),
990 tf(0.5 * sqrt_3, -0.5),
991 ];
992 shapes.push(Shape::convex_polygon(points, fill, stroke));
993 }
994 MarkerShape::Left => {
995 let points =
996 vec![tf(-1.0, 0.0), tf(0.5, -0.5 * sqrt_3), tf(0.5, 0.5 * sqrt_3)];
997 shapes.push(Shape::convex_polygon(points, fill, stroke));
998 }
999 MarkerShape::Right => {
1000 let points = vec![
1001 tf(1.0, 0.0),
1002 tf(-0.5, 0.5 * sqrt_3),
1003 tf(-0.5, -0.5 * sqrt_3),
1004 ];
1005 shapes.push(Shape::convex_polygon(points, fill, stroke));
1006 }
1007 MarkerShape::Asterisk => {
1008 let vertical = [tf(0.0, -1.0), tf(0.0, 1.0)];
1009 let diagonal1 = [tf(-frac_sqrt_3_2, 0.5), tf(frac_sqrt_3_2, -0.5)];
1010 let diagonal2 = [tf(-frac_sqrt_3_2, -0.5), tf(frac_sqrt_3_2, 0.5)];
1011 shapes.push(Shape::line_segment(vertical, default_stroke));
1012 shapes.push(Shape::line_segment(diagonal1, default_stroke));
1013 shapes.push(Shape::line_segment(diagonal2, default_stroke));
1014 }
1015 }
1016 });
1017 }
1018
1019 fn initialize(&mut self, x_range: RangeInclusive<f64>) {
1020 self.series.generate_points(x_range);
1021 }
1022
1023 fn color(&self) -> Color32 {
1024 self.color
1025 }
1026
1027 fn geometry(&self) -> PlotGeometry<'_> {
1028 PlotGeometry::Points(self.series.points())
1029 }
1030
1031 fn bounds(&self) -> PlotBounds {
1032 self.series.bounds()
1033 }
1034
1035 fn base(&self) -> &PlotItemBase {
1036 &self.base
1037 }
1038
1039 fn base_mut(&mut self) -> &mut PlotItemBase {
1040 &mut self.base
1041 }
1042}
1043
1044pub struct Arrows<'a> {
1046 base: PlotItemBase,
1047 pub(super) origins: PlotPoints<'a>,
1048 pub(super) tips: PlotPoints<'a>,
1049 pub(super) tip_length: Option<f32>,
1050 pub(super) color: Color32,
1051}
1052
1053impl<'a> Arrows<'a> {
1054 pub fn new(
1055 name: impl Into<String>,
1056 origins: impl Into<PlotPoints<'a>>,
1057 tips: impl Into<PlotPoints<'a>>,
1058 ) -> Self {
1059 Self {
1060 base: PlotItemBase::new(name.into()),
1061 origins: origins.into(),
1062 tips: tips.into(),
1063 tip_length: None,
1064 color: Color32::TRANSPARENT,
1065 }
1066 }
1067
1068 #[inline]
1070 pub fn tip_length(mut self, tip_length: f32) -> Self {
1071 self.tip_length = Some(tip_length);
1072 self
1073 }
1074
1075 #[inline]
1077 pub fn color(mut self, color: impl Into<Color32>) -> Self {
1078 self.color = color.into();
1079 self
1080 }
1081
1082 builder_methods_for_base!();
1083}
1084
1085impl PlotItem for Arrows<'_> {
1086 fn shapes(&self, _ui: &Ui, transform: &PlotTransform, shapes: &mut Vec<Shape>) {
1087 let Self {
1088 origins,
1089 tips,
1090 tip_length,
1091 color,
1092 base,
1093 ..
1094 } = self;
1095 let stroke = Stroke::new(if base.highlight { 2.0 } else { 1.0 }, *color);
1096 origins
1097 .points()
1098 .iter()
1099 .zip(tips.points().iter())
1100 .map(|(origin, tip)| {
1101 (
1102 transform.position_from_point(origin),
1103 transform.position_from_point(tip),
1104 )
1105 })
1106 .for_each(|(origin, tip)| {
1107 let vector = tip - origin;
1108 let rot = Rot2::from_angle(std::f32::consts::TAU / 10.0);
1109 let tip_length = if let Some(tip_length) = tip_length {
1110 *tip_length
1111 } else {
1112 vector.length() / 4.0
1113 };
1114 let tip = origin + vector;
1115 let dir = vector.normalized();
1116 shapes.push(Shape::line_segment([origin, tip], stroke));
1117 shapes.push(Shape::line(
1118 vec![
1119 tip - tip_length * (rot.inverse() * dir),
1120 tip,
1121 tip - tip_length * (rot * dir),
1122 ],
1123 stroke,
1124 ));
1125 });
1126 }
1127
1128 fn initialize(&mut self, _x_range: RangeInclusive<f64>) {
1129 self.origins
1130 .generate_points(f64::NEG_INFINITY..=f64::INFINITY);
1131 self.tips.generate_points(f64::NEG_INFINITY..=f64::INFINITY);
1132 }
1133
1134 fn color(&self) -> Color32 {
1135 self.color
1136 }
1137
1138 fn geometry(&self) -> PlotGeometry<'_> {
1139 PlotGeometry::Points(self.origins.points())
1140 }
1141
1142 fn bounds(&self) -> PlotBounds {
1143 self.origins.bounds()
1144 }
1145
1146 fn base(&self) -> &PlotItemBase {
1147 &self.base
1148 }
1149
1150 fn base_mut(&mut self) -> &mut PlotItemBase {
1151 &mut self.base
1152 }
1153}
1154
1155#[derive(Clone)]
1157pub struct PlotImage {
1158 base: PlotItemBase,
1159 pub(super) position: PlotPoint,
1160 pub(super) texture_id: TextureId,
1161 pub(super) uv: Rect,
1162 pub(super) size: Vec2,
1163 pub(crate) rotation: f64,
1164 pub(super) bg_fill: Color32,
1165 pub(super) tint: Color32,
1166}
1167
1168impl PlotImage {
1169 pub fn new(
1171 name: impl Into<String>,
1172 texture_id: impl Into<TextureId>,
1173 center_position: PlotPoint,
1174 size: impl Into<Vec2>,
1175 ) -> Self {
1176 Self {
1177 base: PlotItemBase::new(name.into()),
1178 position: center_position,
1179 texture_id: texture_id.into(),
1180 uv: Rect::from_min_max(pos2(0.0, 0.0), pos2(1.0, 1.0)),
1181 size: size.into(),
1182 rotation: 0.0,
1183 bg_fill: Default::default(),
1184 tint: Color32::WHITE,
1185 }
1186 }
1187
1188 #[inline]
1190 pub fn uv(mut self, uv: impl Into<Rect>) -> Self {
1191 self.uv = uv.into();
1192 self
1193 }
1194
1195 #[inline]
1197 pub fn bg_fill(mut self, bg_fill: impl Into<Color32>) -> Self {
1198 self.bg_fill = bg_fill.into();
1199 self
1200 }
1201
1202 #[inline]
1204 pub fn tint(mut self, tint: impl Into<Color32>) -> Self {
1205 self.tint = tint.into();
1206 self
1207 }
1208
1209 #[inline]
1211 pub fn rotate(mut self, angle: f64) -> Self {
1212 self.rotation = angle;
1213 self
1214 }
1215
1216 builder_methods_for_base!();
1217}
1218
1219impl PlotItem for PlotImage {
1220 fn shapes(&self, ui: &Ui, transform: &PlotTransform, shapes: &mut Vec<Shape>) {
1221 let Self {
1222 position,
1223 rotation,
1224 texture_id,
1225 uv,
1226 size,
1227 bg_fill,
1228 tint,
1229 base,
1230 ..
1231 } = self;
1232 let image_screen_rect = {
1233 let left_top = PlotPoint::new(
1234 position.x - 0.5 * size.x as f64,
1235 position.y - 0.5 * size.y as f64,
1236 );
1237 let right_bottom = PlotPoint::new(
1238 position.x + 0.5 * size.x as f64,
1239 position.y + 0.5 * size.y as f64,
1240 );
1241 let left_top_screen = transform.position_from_point(&left_top);
1242 let right_bottom_screen = transform.position_from_point(&right_bottom);
1243 Rect::from_two_pos(left_top_screen, right_bottom_screen)
1244 };
1245 let screen_rotation = -*rotation as f32;
1246
1247 egui::paint_texture_at(
1248 ui.painter(),
1249 image_screen_rect,
1250 &ImageOptions {
1251 uv: *uv,
1252 bg_fill: *bg_fill,
1253 tint: *tint,
1254 rotation: Some((Rot2::from_angle(screen_rotation), Vec2::splat(0.5))),
1255 corner_radius: CornerRadius::ZERO,
1256 },
1257 &(*texture_id, image_screen_rect.size()).into(),
1258 );
1259 if base.highlight {
1260 let center = image_screen_rect.center();
1261 let rotation = Rot2::from_angle(screen_rotation);
1262 let outline = [
1263 image_screen_rect.right_bottom(),
1264 image_screen_rect.right_top(),
1265 image_screen_rect.left_top(),
1266 image_screen_rect.left_bottom(),
1267 ]
1268 .iter()
1269 .map(|point| center + rotation * (*point - center))
1270 .collect();
1271 shapes.push(Shape::closed_line(
1272 outline,
1273 Stroke::new(1.0, ui.visuals().strong_text_color()),
1274 ));
1275 }
1276 }
1277
1278 fn initialize(&mut self, _x_range: RangeInclusive<f64>) {}
1279
1280 fn color(&self) -> Color32 {
1281 Color32::TRANSPARENT
1282 }
1283
1284 fn geometry(&self) -> PlotGeometry<'_> {
1285 PlotGeometry::None
1286 }
1287
1288 fn bounds(&self) -> PlotBounds {
1289 let mut bounds = PlotBounds::NOTHING;
1290 let left_top = PlotPoint::new(
1291 self.position.x as f32 - self.size.x / 2.0,
1292 self.position.y as f32 - self.size.y / 2.0,
1293 );
1294 let right_bottom = PlotPoint::new(
1295 self.position.x as f32 + self.size.x / 2.0,
1296 self.position.y as f32 + self.size.y / 2.0,
1297 );
1298 bounds.extend_with(&left_top);
1299 bounds.extend_with(&right_bottom);
1300 bounds
1301 }
1302
1303 fn base(&self) -> &PlotItemBase {
1304 &self.base
1305 }
1306
1307 fn base_mut(&mut self) -> &mut PlotItemBase {
1308 &mut self.base
1309 }
1310}
1311
1312pub struct BarChart {
1316 base: PlotItemBase,
1317
1318 pub(super) bars: Vec<Bar>,
1319 default_color: Color32,
1320
1321 pub(super) element_formatter: Option<Box<dyn Fn(&Bar, &BarChart) -> String>>,
1323}
1324
1325impl BarChart {
1326 pub fn new(name: impl Into<String>, bars: Vec<Bar>) -> Self {
1328 Self {
1329 base: PlotItemBase::new(name.into()),
1330 bars,
1331 default_color: Color32::TRANSPARENT,
1332 element_formatter: None,
1333 }
1334 }
1335
1336 #[inline]
1341 pub fn color(mut self, color: impl Into<Color32>) -> Self {
1342 let plot_color = color.into();
1343 self.default_color = plot_color;
1344 for b in &mut self.bars {
1345 if b.fill == Color32::TRANSPARENT && b.stroke.color == Color32::TRANSPARENT {
1346 b.fill = plot_color.linear_multiply(0.2);
1347 b.stroke.color = plot_color;
1348 }
1349 }
1350 self
1351 }
1352
1353 #[inline]
1356 pub fn vertical(mut self) -> Self {
1357 for b in &mut self.bars {
1358 b.orientation = Orientation::Vertical;
1359 }
1360 self
1361 }
1362
1363 #[inline]
1366 pub fn horizontal(mut self) -> Self {
1367 for b in &mut self.bars {
1368 b.orientation = Orientation::Horizontal;
1369 }
1370 self
1371 }
1372
1373 #[inline]
1375 pub fn width(mut self, width: f64) -> Self {
1376 for b in &mut self.bars {
1377 b.bar_width = width;
1378 }
1379 self
1380 }
1381
1382 #[inline]
1385 pub fn element_formatter(mut self, formatter: Box<dyn Fn(&Bar, &Self) -> String>) -> Self {
1386 self.element_formatter = Some(formatter);
1387 self
1388 }
1389
1390 #[inline]
1394 pub fn stack_on(mut self, others: &[&Self]) -> Self {
1395 for (index, bar) in self.bars.iter_mut().enumerate() {
1396 let new_base_offset = if bar.value.is_sign_positive() {
1397 others
1398 .iter()
1399 .filter_map(|other_chart| other_chart.bars.get(index).map(|bar| bar.upper()))
1400 .max_by_key(|value| value.ord())
1401 } else {
1402 others
1403 .iter()
1404 .filter_map(|other_chart| other_chart.bars.get(index).map(|bar| bar.lower()))
1405 .min_by_key(|value| value.ord())
1406 };
1407
1408 if let Some(value) = new_base_offset {
1409 bar.base_offset = Some(value);
1410 }
1411 }
1412 self
1413 }
1414
1415 builder_methods_for_base!();
1416}
1417
1418impl PlotItem for BarChart {
1419 fn shapes(&self, _ui: &Ui, transform: &PlotTransform, shapes: &mut Vec<Shape>) {
1420 for b in &self.bars {
1421 b.add_shapes(transform, self.base.highlight, shapes);
1422 }
1423 }
1424
1425 fn initialize(&mut self, _x_range: RangeInclusive<f64>) {
1426 }
1428
1429 fn color(&self) -> Color32 {
1430 self.default_color
1431 }
1432
1433 fn geometry(&self) -> PlotGeometry<'_> {
1434 PlotGeometry::Rects
1435 }
1436
1437 fn bounds(&self) -> PlotBounds {
1438 let mut bounds = PlotBounds::NOTHING;
1439 for b in &self.bars {
1440 bounds.merge(&b.bounds());
1441 }
1442 bounds
1443 }
1444
1445 fn find_closest(&self, point: Pos2, transform: &PlotTransform) -> Option<ClosestElem> {
1446 find_closest_rect(&self.bars, point, transform)
1447 }
1448
1449 fn on_hover(
1450 &self,
1451 _plot_area_response: &egui::Response,
1452 elem: ClosestElem,
1453 shapes: &mut Vec<Shape>,
1454 cursors: &mut Vec<Cursor>,
1455 plot: &PlotConfig<'_>,
1456 _: &LabelFormatter<'_>,
1457 ) {
1458 let bar = &self.bars[elem.index];
1459
1460 bar.add_shapes(plot.transform, true, shapes);
1461 bar.add_rulers_and_text(self, plot, shapes, cursors);
1462 }
1463
1464 fn base(&self) -> &PlotItemBase {
1465 &self.base
1466 }
1467
1468 fn base_mut(&mut self) -> &mut PlotItemBase {
1469 &mut self.base
1470 }
1471}
1472
1473pub struct BoxPlot {
1475 base: PlotItemBase,
1476
1477 pub(super) boxes: Vec<BoxElem>,
1478 default_color: Color32,
1479
1480 pub(super) element_formatter: Option<Box<dyn Fn(&BoxElem, &BoxPlot) -> String>>,
1482}
1483
1484impl BoxPlot {
1485 pub fn new(name: impl Into<String>, boxes: Vec<BoxElem>) -> Self {
1487 Self {
1488 base: PlotItemBase::new(name.into()),
1489 boxes,
1490 default_color: Color32::TRANSPARENT,
1491 element_formatter: None,
1492 }
1493 }
1494
1495 #[inline]
1500 pub fn color(mut self, color: impl Into<Color32>) -> Self {
1501 let plot_color = color.into();
1502 self.default_color = plot_color;
1503 for box_elem in &mut self.boxes {
1504 if box_elem.fill == Color32::TRANSPARENT
1505 && box_elem.stroke.color == Color32::TRANSPARENT
1506 {
1507 box_elem.fill = plot_color.linear_multiply(0.2);
1508 box_elem.stroke.color = plot_color;
1509 }
1510 }
1511 self
1512 }
1513
1514 #[inline]
1517 pub fn vertical(mut self) -> Self {
1518 for box_elem in &mut self.boxes {
1519 box_elem.orientation = Orientation::Vertical;
1520 }
1521 self
1522 }
1523
1524 #[inline]
1527 pub fn horizontal(mut self) -> Self {
1528 for box_elem in &mut self.boxes {
1529 box_elem.orientation = Orientation::Horizontal;
1530 }
1531 self
1532 }
1533
1534 #[inline]
1537 pub fn element_formatter(mut self, formatter: Box<dyn Fn(&BoxElem, &Self) -> String>) -> Self {
1538 self.element_formatter = Some(formatter);
1539 self
1540 }
1541
1542 builder_methods_for_base!();
1543}
1544
1545impl PlotItem for BoxPlot {
1546 fn shapes(&self, _ui: &Ui, transform: &PlotTransform, shapes: &mut Vec<Shape>) {
1547 for b in &self.boxes {
1548 b.add_shapes(transform, self.base.highlight, shapes);
1549 }
1550 }
1551
1552 fn initialize(&mut self, _x_range: RangeInclusive<f64>) {
1553 }
1555
1556 fn color(&self) -> Color32 {
1557 self.default_color
1558 }
1559
1560 fn geometry(&self) -> PlotGeometry<'_> {
1561 PlotGeometry::Rects
1562 }
1563
1564 fn bounds(&self) -> PlotBounds {
1565 let mut bounds = PlotBounds::NOTHING;
1566 for b in &self.boxes {
1567 bounds.merge(&b.bounds());
1568 }
1569 bounds
1570 }
1571
1572 fn find_closest(&self, point: Pos2, transform: &PlotTransform) -> Option<ClosestElem> {
1573 find_closest_rect(&self.boxes, point, transform)
1574 }
1575
1576 fn on_hover(
1577 &self,
1578 _plot_area_response: &egui::Response,
1579 elem: ClosestElem,
1580 shapes: &mut Vec<Shape>,
1581 cursors: &mut Vec<Cursor>,
1582 plot: &PlotConfig<'_>,
1583 _: &LabelFormatter<'_>,
1584 ) {
1585 let box_plot = &self.boxes[elem.index];
1586
1587 box_plot.add_shapes(plot.transform, true, shapes);
1588 box_plot.add_rulers_and_text(self, plot, shapes, cursors);
1589 }
1590
1591 fn base(&self) -> &PlotItemBase {
1592 &self.base
1593 }
1594
1595 fn base_mut(&mut self) -> &mut PlotItemBase {
1596 &mut self.base
1597 }
1598}
1599
1600pub(crate) fn rulers_color(ui: &Ui) -> Color32 {
1604 if ui.visuals().dark_mode {
1605 Color32::from_gray(100).additive()
1606 } else {
1607 Color32::from_black_alpha(180)
1608 }
1609}
1610
1611pub(crate) fn vertical_line(
1612 pointer: Pos2,
1613 transform: &PlotTransform,
1614 line_color: Color32,
1615) -> Shape {
1616 let frame = transform.frame();
1617 Shape::line_segment(
1618 [
1619 pos2(pointer.x, frame.top()),
1620 pos2(pointer.x, frame.bottom()),
1621 ],
1622 (1.0, line_color),
1623 )
1624}
1625
1626pub(crate) fn horizontal_line(
1627 pointer: Pos2,
1628 transform: &PlotTransform,
1629 line_color: Color32,
1630) -> Shape {
1631 let frame = transform.frame();
1632 Shape::line_segment(
1633 [
1634 pos2(frame.left(), pointer.y),
1635 pos2(frame.right(), pointer.y),
1636 ],
1637 (1.0, line_color),
1638 )
1639}
1640
1641fn add_rulers_and_text(
1642 elem: &dyn RectElement,
1643 plot: &PlotConfig<'_>,
1644 text: Option<String>,
1645 shapes: &mut Vec<Shape>,
1646 cursors: &mut Vec<Cursor>,
1647) {
1648 let orientation = elem.orientation();
1649 let show_argument = plot.show_x && orientation == Orientation::Vertical
1650 || plot.show_y && orientation == Orientation::Horizontal;
1651 let show_values = plot.show_y && orientation == Orientation::Vertical
1652 || plot.show_x && orientation == Orientation::Horizontal;
1653
1654 if show_argument {
1656 for pos in elem.arguments_with_ruler() {
1657 cursors.push(match orientation {
1658 Orientation::Horizontal => Cursor::Horizontal { y: pos.y },
1659 Orientation::Vertical => Cursor::Vertical { x: pos.x },
1660 });
1661 }
1662 }
1663
1664 if show_values {
1666 for pos in elem.values_with_ruler() {
1667 cursors.push(match orientation {
1668 Orientation::Horizontal => Cursor::Vertical { x: pos.x },
1669 Orientation::Vertical => Cursor::Horizontal { y: pos.y },
1670 });
1671 }
1672 }
1673
1674 let text = text.unwrap_or({
1676 let mut text = elem.name().to_owned(); if show_values {
1679 text.push('\n');
1680 text.push_str(&elem.default_values_format(plot.transform));
1681 }
1682
1683 text
1684 });
1685
1686 let font_id = TextStyle::Body.resolve(plot.ui.style());
1687
1688 let corner_value = elem.corner_value();
1689 plot.ui.fonts_mut(|f| {
1690 shapes.push(Shape::text(
1691 f,
1692 plot.transform.position_from_point(&corner_value) + vec2(3.0, -2.0),
1693 Align2::LEFT_BOTTOM,
1694 text,
1695 font_id,
1696 plot.ui.visuals().text_color(),
1697 ));
1698 });
1699}
1700
1701pub(super) fn rulers_and_tooltip_at_value(
1706 plot_area_response: &egui::Response,
1707 value: PlotPoint,
1708 name: &str,
1709 plot: &PlotConfig<'_>,
1710 cursors: &mut Vec<Cursor>,
1711 label_formatter: &LabelFormatter<'_>,
1712) {
1713 if plot.show_x {
1714 cursors.push(Cursor::Vertical { x: value.x });
1715 }
1716 if plot.show_y {
1717 cursors.push(Cursor::Horizontal { y: value.y });
1718 }
1719
1720 let text = if let Some(custom_label) = label_formatter {
1721 custom_label(name, &value)
1722 } else {
1723 let prefix = if name.is_empty() {
1724 String::new()
1725 } else {
1726 format!("{name}\n")
1727 };
1728 let scale = plot.transform.dvalue_dpos();
1729 let x_decimals = ((-scale[0].abs().log10()).ceil().at_least(0.0) as usize).clamp(1, 6);
1730 let y_decimals = ((-scale[1].abs().log10()).ceil().at_least(0.0) as usize).clamp(1, 6);
1731 if plot.show_x && plot.show_y {
1732 format!(
1733 "{}x = {:.*}\ny = {:.*}",
1734 prefix, x_decimals, value.x, y_decimals, value.y
1735 )
1736 } else if plot.show_x {
1737 format!("{}x = {:.*}", prefix, x_decimals, value.x)
1738 } else if plot.show_y {
1739 format!("{}y = {:.*}", prefix, y_decimals, value.y)
1740 } else {
1741 unreachable!()
1742 }
1743 };
1744
1745 let mut tooltip = egui::Tooltip::always_open(
1747 plot_area_response.ctx.clone(),
1748 plot_area_response.layer_id,
1749 plot_area_response.id,
1750 PopupAnchor::Pointer,
1751 );
1752
1753 let tooltip_width = plot_area_response.ctx.style().spacing.tooltip_width;
1754
1755 tooltip.popup = tooltip.popup.width(tooltip_width);
1756
1757 tooltip.gap(12.0).show(|ui| {
1758 ui.set_max_width(tooltip_width);
1759 ui.label(text);
1760 });
1761}
1762
1763fn find_closest_rect<'a, T>(
1764 rects: impl IntoIterator<Item = &'a T>,
1765 point: Pos2,
1766 transform: &PlotTransform,
1767) -> Option<ClosestElem>
1768where
1769 T: 'a + RectElement,
1770{
1771 rects
1772 .into_iter()
1773 .enumerate()
1774 .map(|(index, bar)| {
1775 let bar_rect = transform.rect_from_values(&bar.bounds_min(), &bar.bounds_max());
1776 let dist_sq = bar_rect.distance_sq_to_pos(point);
1777
1778 ClosestElem { index, dist_sq }
1779 })
1780 .min_by_key(|e| e.dist_sq.ord())
1781}