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}