egui_plotter/
chart.rs

1//! Structs used to simplify the process of making interactive charts
2
3use egui::{PointerState, Ui};
4use plotters::{
5    coord::Shift,
6    prelude::{DrawingArea, IntoDrawingArea},
7};
8
9use crate::EguiBackend;
10
11/// Default pitch and yaw scale for mouse rotations.
12pub const DEFAULT_MOVE_SCALE: f32 = 0.01;
13/// Default zoom scale for scroll wheel zooming.
14pub const DEFAULT_SCROLL_SCALE: f32 = 0.001;
15
16#[derive(Debug, Copy, Clone)]
17/// Transformations to be applied to your chart. Is modified by user input(if the mouse is enabled) and
18/// used by Chart::draw() and your builder callback.
19///
20/// Chart::draw() applies the scale and the x/y offset to your plot, so unless
21/// you want to create some effects on your own you don't need to worry about them.
22///
23/// If you are creating a 3d plot however you will have to manually apply the pitch and
24/// yaw to your chart with the following code:
25///
26/// ```ignore
27/// chart.with_projection(|mut pb| {
28///     pb.yaw = transform.yaw;
29///     pb.pitch = transform.pitch;
30///     pb.scale = 0.7; // Set scale to 0.7 to avoid artifacts caused by plotter's renderer
31///     pb.into_matrix()
32/// });
33///```
34pub struct Transform {
35    /// Pitch of your graph in 3d
36    pub pitch: f64,
37    /// Yaw of your graph in 3d
38    pub yaw: f64,
39    /// Scale of your graph. Applied in Chart::draw()
40    pub scale: f64,
41    /// X offset of your graph. Applied in Chart::draw()
42    pub x: i32,
43    /// Y offset of your graph. Applied in Chart::draw()
44    pub y: i32,
45}
46
47impl Default for Transform {
48    fn default() -> Self {
49        Self {
50            pitch: 0.0,
51            yaw: 0.0,
52            scale: 1.0,
53            x: 0,
54            y: 0,
55        }
56    }
57}
58
59#[derive(Debug, Copy, Clone)]
60/// Mouse buttons that can be bound to chart actions
61pub enum MouseButton {
62    Primary,
63    Middle,
64    Secondary,
65}
66
67impl MouseButton {
68    /// See if the mouse button is down given a PointerState
69    pub fn is_down(&self, pointer: &PointerState) -> bool {
70        match self {
71            Self::Primary => pointer.primary_down(),
72            Self::Middle => pointer.middle_down(),
73            Self::Secondary => pointer.secondary_down(),
74        }
75    }
76}
77
78#[derive(Debug, Copy, Clone)]
79/// Used to configure how the mouse interacts with the chart.
80///
81/// ## Usage
82/// MouseConfig allows you to change the ways the user interacts with your chart in the following
83/// ways:
84///  * `drag`, `rotate`, & `zoom` - Enables dragging, rotating, and zooming in on your plots with
85///  mouse controls.
86///  * `pitch_scale` & `yaw_scale` - Modifies how quickly the pitch and yaw are rotated when rotating with the
87///  mouse.
88///  * `zoom_scale` - Modifies how quickly you zoom in/out.
89///  * `drag_bind` - Mouse button bound to dragging your plot.
90///  * `rotate_bind` - Mouse button bound to rotating your plot.
91pub struct MouseConfig {
92    drag: bool,
93    rotate: bool,
94    zoom: bool,
95    yaw_scale: f32,
96    pitch_scale: f32,
97    zoom_scale: f32,
98    drag_bind: MouseButton,
99    rotate_bind: MouseButton,
100}
101
102impl Default for MouseConfig {
103    fn default() -> Self {
104        Self {
105            drag: false,
106            rotate: false,
107            zoom: false,
108            yaw_scale: DEFAULT_MOVE_SCALE,
109            pitch_scale: DEFAULT_MOVE_SCALE,
110            zoom_scale: DEFAULT_SCROLL_SCALE,
111            drag_bind: MouseButton::Middle,
112            rotate_bind: MouseButton::Primary,
113        }
114    }
115}
116
117impl MouseConfig {
118    #[inline]
119    /// Create a new MouseConfig with dragging, rotationg, and zooming enabled.
120    pub fn enabled() -> Self {
121        Self {
122            drag: true,
123            rotate: true,
124            zoom: true,
125            yaw_scale: DEFAULT_MOVE_SCALE,
126            pitch_scale: DEFAULT_MOVE_SCALE,
127            zoom_scale: DEFAULT_SCROLL_SCALE,
128            drag_bind: MouseButton::Middle,
129            rotate_bind: MouseButton::Primary,
130        }
131    }
132
133    #[inline]
134    /// Enables dragging, rotating, and zooming in on your plots.
135    fn set_enable_all(&mut self) {
136        self.set_drag(true);
137        self.set_zoom(true);
138        self.set_rotate(true);
139    }
140
141    #[inline]
142    /// Enables dragging, rotating, and zooming in on your plots. Consumes self.
143    pub fn enable_all(mut self) -> Self {
144        self.set_enable_all();
145
146        self
147    }
148
149    #[inline]
150    /// Enable/disable dragging of the chart.
151    pub fn set_drag(&mut self, drag: bool) {
152        self.drag = drag
153    }
154
155    #[inline]
156    /// Enable/disable dragging of the chart. Consumes self.
157    pub fn drag(mut self, drag: bool) -> Self {
158        self.set_drag(drag);
159
160        self
161    }
162
163    #[inline]
164    /// Enable/disable rotation of the chart.
165    pub fn set_rotate(&mut self, rotate: bool) {
166        self.rotate = rotate
167    }
168
169    #[inline]
170    /// Enable/disable rotation of the chart. Consumes self.
171    pub fn rotate(mut self, rotate: bool) -> Self {
172        self.set_rotate(rotate);
173
174        self
175    }
176
177    #[inline]
178    /// Enable/disable zoom of the chart.
179    pub fn set_zoom(&mut self, zoom: bool) {
180        self.zoom = zoom;
181    }
182
183    #[inline]
184    /// Enable/disable zoom of the chart. Consumes self.
185    pub fn zoom(mut self, zoom: bool) -> Self {
186        self.set_zoom(zoom);
187
188        self
189    }
190
191    #[inline]
192    /// Change the pitch scale.
193    pub fn set_pitch_scale(&mut self, scale: f32) {
194        self.pitch_scale = scale
195    }
196
197    #[inline]
198    /// Change the pitch scale. Consumes self.
199    pub fn pitch_scale(mut self, scale: f32) -> Self {
200        self.set_pitch_scale(scale);
201
202        self
203    }
204}
205
206/// Allows users to drag, rotate, and zoom in/out on your plots.
207///
208/// ## Usage
209/// Charts are designed to be easy to implement and use, while simultaniously
210/// being powerful enough for your application. You can manipulate the
211/// following properties of a chart to get the effects you want:
212///  * `builder_cb` - Callback used to populate the chart. Is provided a DrawingArea and the
213///  chart's `data`.
214///  * `mouse` - Mouse configuration. Configure how you wish the mouse to affect/manipulate the
215///  chart.
216///  * `data` - A Box of data of any type to be stored with the chart. Provided so that you can modify data
217///  without having to specify a new callback during runtime. For example, `examples/parachart.rs`
218///  uses it to store the range so it can be changed during runtime.
219///
220///  ## Examples
221///  See `examples/3dchart.rs` and `examples/parachart.rs` for examples of usage.
222pub struct Chart<Data> {
223    transform: Transform,
224    mouse: MouseConfig,
225    builder_cb: Option<Box<dyn FnMut(&mut DrawingArea<EguiBackend, Shift>, &Transform, &Data)>>,
226    data: Data,
227}
228
229impl<Data> Chart<Data> {
230    /// Create a new chart with default settings (if not using data supply ())
231    pub fn new(data: Data) -> Self {
232        Self {
233            transform: Transform::default(),
234            mouse: MouseConfig::default(),
235            builder_cb: None,
236            data,
237        }
238    }
239
240    #[inline]
241    /// Enable or disable mouse controls.
242    pub fn set_mouse(&mut self, mouse: MouseConfig) {
243        self.mouse = mouse
244    }
245
246    #[inline]
247    /// Enable or disable mouse controls. Consumes self.
248    pub fn mouse(mut self, mouse: MouseConfig) -> Self {
249        self.set_mouse(mouse);
250
251        self
252    }
253
254    #[inline]
255    /// Set the builder callback.
256    pub fn set_builder_cb(
257        &mut self,
258        builder_cb: Box<dyn FnMut(&mut DrawingArea<EguiBackend, Shift>, &Transform, &Data)>,
259    ) {
260        self.builder_cb = Some(builder_cb)
261    }
262
263    #[inline]
264    /// Set the builder callback. Consumes self.
265    pub fn builder_cb(
266        mut self,
267        builder_cb: Box<dyn FnMut(&mut DrawingArea<EguiBackend, Shift>, &Transform, &Data)>,
268    ) -> Self {
269        self.set_builder_cb(builder_cb);
270
271        self
272    }
273
274    #[inline]
275    /// Set the pitch of the chart.
276    pub fn set_pitch(&mut self, pitch: f64) {
277        self.transform.pitch = pitch
278    }
279
280    #[inline]
281    /// Set the pitch of the chart. Consumes self.
282    pub fn pitch(mut self, pitch: f64) -> Self {
283        self.set_pitch(pitch);
284
285        self
286    }
287
288    #[inline]
289    /// Set the yaw of the chart.
290    pub fn set_yaw(&mut self, yaw: f64) {
291        self.transform.yaw = yaw
292    }
293
294    #[inline]
295    /// Set the yaw of the chart. Consumes self.
296    pub fn yaw(mut self, yaw: f64) -> Self {
297        self.set_yaw(yaw);
298
299        self
300    }
301
302    #[inline]
303    /// Set the scale of the chart.
304    pub fn set_scale(&mut self, scale: f64) {
305        self.transform.scale = scale
306    }
307
308    #[inline]
309    /// Set the scale of the chart. Consumes self.
310    pub fn scale(mut self, scale: f64) -> Self {
311        self.set_scale(scale);
312
313        self
314    }
315
316    #[inline]
317    /// Get the data of the chart as a reference.
318    pub fn get_data(&self) -> &Data {
319        &self.data
320    }
321
322    #[inline]
323    /// Get the data of the chart as a mutable reference.
324    pub fn get_data_mut(&mut self) -> &mut Data {
325        &mut self.data
326    }
327
328    /// Call the callback and draw the chart to a UI element.
329    pub fn draw(&mut self, ui: &Ui) {
330        let transform = &mut self.transform;
331
332        // First, get mouse data
333        ui.input(|input| {
334            let pointer = &input.pointer;
335            let delta = pointer.delta();
336
337            // Adjust the pitch/yaw if the primary button is pressed and rotation is enabled
338            if self.mouse.rotate && self.mouse.rotate_bind.is_down(pointer) {
339                let pitch_delta = delta.y * self.mouse.pitch_scale;
340                let yaw_delta = delta.x * self.mouse.yaw_scale;
341
342                transform.pitch += pitch_delta as f64;
343                transform.yaw += -yaw_delta as f64;
344            }
345
346            // Adjust the x/y if the middle button is down and dragging is enabled
347            if self.mouse.drag && self.mouse.drag_bind.is_down(pointer) {
348                let x_delta = delta.x;
349                let y_delta = delta.y;
350
351                transform.x += x_delta as i32;
352                transform.y += y_delta as i32;
353            }
354
355            // Adjust zoom if zoom is enabled
356            if self.mouse.zoom {
357                let scale_delta = input.smooth_scroll_delta.y * self.mouse.zoom_scale;
358
359                // !TODO! make scaling exponential
360                transform.scale = (transform.scale + scale_delta as f64).abs();
361            }
362        });
363
364        let mut area = EguiBackend::new(ui)
365            .offset((transform.x, transform.y))
366            .scale(transform.scale as f32)
367            .into_drawing_area();
368
369        if let Some(cb) = &mut self.builder_cb {
370            cb(&mut area, transform, &self.data);
371        }
372
373        area.present().unwrap();
374    }
375}