gpui_component/plot/mod.rs
1mod axis;
2mod grid;
3pub mod label;
4mod path_cache;
5pub mod scale;
6pub mod shape;
7pub mod tooltip;
8
9pub use gpui_component_macros::IntoPlot;
10
11use std::{fmt::Debug, ops::Add};
12
13use gpui::{
14 AnyElement, App, Bounds, ElementId, IntoElement, Path, PathBuilder, Pixels, Point, Window,
15 point, px,
16};
17
18pub use axis::{AXIS_GAP, AxisLabelSide, AxisText, PlotAxis};
19pub use grid::Grid;
20pub use label::PlotLabel;
21pub use path_cache::{PathCache, PathCaches, ShapeKey};
22
23use tooltip::{PlotHover, TooltipState};
24
25pub trait Plot: IntoElement {
26 /// Lay out and place the child elements this plot hosts (e.g. element labels).
27 ///
28 /// Called during the element's prepaint phase, so implementations may use
29 /// [`AnyElement::layout_as_root`] / [`AnyElement::prepaint_at`] to measure and
30 /// position children — neither is legal from [`Plot::paint`]. The returned
31 /// elements are painted right after `paint`, below the tooltip overlay.
32 ///
33 /// Runs before [`Plot::tooltip_state`] and [`Plot::tooltip`], so anything
34 /// resolved here can be reused by them.
35 ///
36 /// The default returns no children.
37 fn prepaint(
38 &mut self,
39 _bounds: Bounds<Pixels>,
40 _window: &mut Window,
41 _cx: &mut App,
42 ) -> Vec<AnyElement> {
43 vec![]
44 }
45
46 fn paint(&mut self, bounds: Bounds<Pixels>, window: &mut Window, cx: &mut App);
47
48 /// A stable element id that enables interactive tooltip support for this plot.
49 ///
50 /// Return `Some(id)` to opt in to tooltips; the id must be unique among sibling
51 /// elements. Returning `None` (the default) disables all tooltip behavior, leaving
52 /// the plot a pure, non-interactive element identical to the pre-tooltip behavior.
53 fn id(&self) -> Option<ElementId> {
54 None
55 }
56
57 /// Map the cursor to the tooltip state to display.
58 ///
59 /// `position` is the cursor position relative to the plot's top-left origin (already
60 /// origin-subtracted), and `bounds` is the painted area. Return the [`TooltipState`]
61 /// to display (highlighted index, crosshair point, dots, side), or `None` to show
62 /// nothing. Only called while the cursor is inside `bounds`.
63 ///
64 /// The default returns `None`.
65 fn tooltip_state(
66 &self,
67 _position: Point<Pixels>,
68 _bounds: Bounds<Pixels>,
69 _cx: &App,
70 ) -> Option<TooltipState> {
71 None
72 }
73
74 /// Receive the datum in focus this frame, before [`Plot::tooltip`] and
75 /// [`Plot::paint`] run.
76 ///
77 /// `hover` carries the [`TooltipState`] the cursor resolved to, and it
78 /// lingers after the cursor leaves while [`PlotHover::focus`] eases back to
79 /// zero, so a hover-driven presentation can fade out over the last datum
80 /// instead of vanishing. `None` means nothing is hovered and nothing is
81 /// fading.
82 ///
83 /// Called on every frame the plot has an [`Plot::id`], so this is where a
84 /// plot samples its hover motion ([`gpui_base::transition`],
85 /// [`gpui_base::spring`]) and keeps the result for the other two methods.
86 /// The default ignores the hover.
87 fn hover(&mut self, _hover: Option<&PlotHover>, _window: &mut Window, _cx: &mut App) {}
88
89 /// Render the tooltip overlay for the active [`TooltipState`].
90 ///
91 /// `cursor` is the live cursor position (relative to the plot origin) and `bounds` is the
92 /// plot's painted area, so the tooltip box can follow the cursor (pass `cursor` and
93 /// `bounds.size` to [`tooltip::Tooltip::new`]). Return the overlay element; it is painted
94 /// absolutely positioned above the plot graphics but below sibling content drawn after
95 /// the plot ([`tooltip::Tooltip`] defers its box to paint above everything). The default
96 /// returns `None`.
97 ///
98 /// Also called while the hover fades out, with the lingering `state` and the
99 /// last `cursor`; a [`tooltip::Tooltip`] returned here fades with the hover
100 /// on its own.
101 fn tooltip(
102 &self,
103 _state: &TooltipState,
104 _cursor: Point<Pixels>,
105 _bounds: Bounds<Pixels>,
106 _window: &mut Window,
107 _cx: &mut App,
108 ) -> Option<AnyElement> {
109 None
110 }
111}
112
113#[derive(Clone, Copy, Default, Hash, PartialEq, Eq)]
114pub enum StrokeStyle {
115 #[default]
116 Natural,
117 Linear,
118 StepAfter,
119}
120
121pub fn origin_point<T>(x: T, y: T, origin: Point<T>) -> Point<T>
122where
123 T: Default + Clone + Debug + PartialEq + Add<Output = T>,
124{
125 point(x, y) + origin
126}
127
128pub fn polygon<T>(points: &[Point<T>], bounds: &Bounds<Pixels>) -> Option<Path<Pixels>>
129where
130 T: Default + Clone + Copy + Debug + Into<f32> + PartialEq,
131{
132 let mut path = PathBuilder::stroke(px(1.));
133 let points = &points
134 .iter()
135 .map(|p| {
136 point(
137 px(p.x.into() + bounds.origin.x.as_f32()),
138 px(p.y.into() + bounds.origin.y.as_f32()),
139 )
140 })
141 .collect::<Vec<_>>();
142 path.add_polygon(points, false);
143 path.build().ok()
144}