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