Skip to main content

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}