egui_plotter/
backend.rs

1//! Plotter backend for egui
2
3use std::error::Error as ErrorTrait;
4use std::f32::consts::FRAC_PI_2;
5use std::fmt::{Display, Formatter, Result as FmtResult};
6use std::ops::{Add, AddAssign, MulAssign, Sub, SubAssign};
7
8use egui::{
9    epaint::{PathShape, TextShape},
10    Align, Align2, Color32, FontFamily as EguiFontFamily, FontId, Pos2, Rect, Stroke, Ui, CornerRadius,
11};
12use plotters_backend::{
13    text_anchor::{HPos, Pos, VPos},
14    BackendColor, BackendCoord, BackendStyle, BackendTextStyle, DrawingBackend, DrawingErrorKind,
15    FontFamily as PlottersFontFamily,
16};
17
18#[derive(Debug, Clone, Copy)]
19/// Error to be returned by the backend. Since egui doesn't return any errors
20/// on any painter operations, this is a stub type.
21pub struct EguiBackendError;
22
23impl Display for EguiBackendError {
24    #[inline]
25    fn fmt(&self, _f: &mut Formatter<'_>) -> FmtResult {
26        Ok(())
27    }
28}
29
30impl ErrorTrait for EguiBackendError {
31    #[inline]
32    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
33        None
34    }
35}
36
37#[derive(Debug, Clone, Copy)]
38/// Struct used to convert between egui's Pos2 type and plotter's coordinate tuple.
39/// Also provides implementations for adding coordinates together.
40struct EguiBackendCoord {
41    x: f32,
42    y: f32,
43}
44
45impl From<(i32, i32)> for EguiBackendCoord {
46    #[inline]
47    fn from(value: (i32, i32)) -> Self {
48        let (x, y) = value;
49
50        Self {
51            x: x as f32,
52            y: y as f32,
53        }
54    }
55}
56
57impl From<EguiBackendCoord> for Pos2 {
58    #[inline]
59    fn from(val: EguiBackendCoord) -> Self {
60        Pos2 { x: val.x, y: val.y }
61    }
62}
63
64impl From<EguiBackendCoord> for (u32, u32) {
65    #[inline]
66    fn from(val: EguiBackendCoord) -> Self {
67        (val.x as u32, val.y as u32)
68    }
69}
70
71impl From<Pos2> for EguiBackendCoord {
72    #[inline]
73    fn from(value: Pos2) -> Self {
74        Self {
75            x: value.x,
76            y: value.y,
77        }
78    }
79}
80
81impl Add<EguiBackendCoord> for EguiBackendCoord {
82    type Output = EguiBackendCoord;
83
84    #[inline]
85    fn add(self, rhs: EguiBackendCoord) -> Self::Output {
86        Self {
87            x: self.x + rhs.x,
88            y: self.y + rhs.y,
89        }
90    }
91}
92
93impl Sub<EguiBackendCoord> for EguiBackendCoord {
94    type Output = EguiBackendCoord;
95
96    #[inline]
97    fn sub(self, rhs: EguiBackendCoord) -> Self::Output {
98        Self {
99            x: self.x - rhs.x,
100            y: self.y - rhs.y,
101        }
102    }
103}
104
105impl Add<Pos2> for EguiBackendCoord {
106    type Output = EguiBackendCoord;
107
108    #[inline]
109    fn add(self, rhs: Pos2) -> Self::Output {
110        Self {
111            x: self.x + rhs.x,
112            y: self.y + rhs.y,
113        }
114    }
115}
116
117impl Add<f32> for EguiBackendCoord {
118    type Output = EguiBackendCoord;
119
120    #[inline]
121    fn add(self, rhs: f32) -> Self::Output {
122        Self {
123            x: self.x + rhs,
124            y: self.y + rhs,
125        }
126    }
127}
128
129impl AddAssign<EguiBackendCoord> for EguiBackendCoord {
130    fn add_assign(&mut self, rhs: EguiBackendCoord) {
131        self.x += rhs.x;
132        self.y += rhs.y;
133    }
134}
135
136impl SubAssign<EguiBackendCoord> for EguiBackendCoord {
137    fn sub_assign(&mut self, rhs: EguiBackendCoord) {
138        self.x -= rhs.x;
139        self.y -= rhs.y;
140    }
141}
142
143impl MulAssign<f32> for EguiBackendCoord {
144    fn mul_assign(&mut self, rhs: f32) {
145        self.x *= rhs;
146        self.y *= rhs;
147    }
148}
149
150#[derive(Debug, Clone, Copy)]
151/// Struct used to convert between Egui and Plotter's color types
152struct EguiBackendColor {
153    r: u8,
154    g: u8,
155    b: u8,
156    a: u8,
157}
158
159impl From<BackendColor> for EguiBackendColor {
160    #[inline]
161    fn from(value: BackendColor) -> Self {
162        let (r, g, b) = value.rgb;
163
164        let a = (value.alpha * 255.0) as u8;
165
166        Self { r, g, b, a }
167    }
168}
169
170impl From<EguiBackendColor> for Color32 {
171    #[inline]
172    fn from(val: EguiBackendColor) -> Self {
173        Color32::from_rgba_unmultiplied(val.r, val.g, val.b, val.a)
174    }
175}
176
177/// Plotter backend for egui; simply provide a reference to the ui element to
178/// use.
179pub struct EguiBackend<'a> {
180    ui: &'a Ui,
181    x: i32,
182    y: i32,
183    scale: f32,
184}
185
186impl<'a> EguiBackend<'a> {
187    #[inline]
188    /// Create a backend given a reference to a Ui.
189    pub fn new(ui: &'a Ui) -> Self {
190        Self {
191            ui,
192            x: 0,
193            y: 0,
194            scale: 1.0,
195        }
196    }
197
198    #[inline]
199    /// Transform point
200    fn point_transform(&self, mut point: EguiBackendCoord, bounds: Rect) -> EguiBackendCoord {
201        let center = EguiBackendCoord::from(bounds.center()) - EguiBackendCoord::from(bounds.min);
202        point -= center;
203        point *= self.scale;
204        point += center;
205
206        point += EguiBackendCoord::from((self.x, self.y));
207        point += EguiBackendCoord::from(bounds.min);
208
209        point
210    }
211    #[inline]
212    /// Set the offset(x + y) of the backend.
213    pub fn set_offset(&mut self, offset: (i32, i32)) {
214        (self.x, self.y) = offset
215    }
216
217    #[inline]
218    /// Set the offset(x + y) of the backend. Consumes self.
219    pub fn offset(mut self, offset: (i32, i32)) -> Self {
220        self.set_offset(offset);
221
222        self
223    }
224
225    #[inline]
226    /// Set the scale of the backend.
227    pub fn set_scale(&mut self, scale: f32) {
228        self.scale = scale
229    }
230
231    #[inline]
232    /// Set the scale of the backend. Consume self.
233    pub fn scale(mut self, scale: f32) -> Self {
234        self.set_scale(scale);
235
236        self
237    }
238}
239
240impl<'a> DrawingBackend for EguiBackend<'a> {
241    type ErrorType = std::io::Error;
242
243    fn get_size(&self) -> (u32, u32) {
244        let bounds = self.ui.max_rect();
245        (bounds.width() as u32, bounds.height() as u32)
246    }
247
248    fn ensure_prepared(&mut self) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
249        Ok(())
250    }
251
252    fn present(&mut self) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
253        Ok(())
254    }
255
256    fn draw_pixel(
257        &mut self,
258        point: (i32, i32),
259        color: BackendColor,
260    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
261        let bounds = self.ui.max_rect();
262        let painter = self.ui.painter().with_clip_rect(bounds);
263
264        let p0 = self.point_transform(EguiBackendCoord::from(point), bounds);
265
266        let p1 = p0 + 1.0;
267
268        let color: Color32 = EguiBackendColor::from(color).into();
269
270        let stroke = Stroke::new(1.0, color);
271
272        painter.line_segment([p0.into(), p1.into()], stroke);
273
274        Ok(())
275    }
276
277    fn draw_line<S: BackendStyle>(
278        &mut self,
279        from: (i32, i32),
280        to: (i32, i32),
281        style: &S,
282    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
283        let bounds = self.ui.max_rect();
284        let painter = self.ui.painter().with_clip_rect(bounds);
285
286        let p0 = self.point_transform(EguiBackendCoord::from(from), bounds);
287        let p1 = self.point_transform(EguiBackendCoord::from(to), bounds);
288
289        let color: Color32 = EguiBackendColor::from(style.color()).into();
290
291        let stroke = Stroke::new(style.stroke_width() as f32, color);
292
293        painter.line_segment([p0.into(), p1.into()], stroke);
294
295        Ok(())
296    }
297
298    fn draw_rect<S: BackendStyle>(
299        &mut self,
300        upper_left: BackendCoord,
301        bottom_right: BackendCoord,
302        style: &S,
303        fill: bool,
304    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
305        let bounds = self.ui.max_rect();
306        let painter = self.ui.painter().with_clip_rect(bounds);
307
308        let p0 = self.point_transform(EguiBackendCoord::from(upper_left), bounds);
309        let p1 = self.point_transform(EguiBackendCoord::from(bottom_right), bounds);
310        let color: Color32 = EguiBackendColor::from(style.color()).into();
311        if fill {
312            painter.rect_filled(
313                egui::Rect {
314                    min: p0.into(),
315                    max: p1.into(),
316                },
317                CornerRadius::default(),
318                color,
319            );
320        } else {
321            let stroke = Stroke::new(style.stroke_width() as f32, color);
322            painter.rect(
323                egui::Rect {
324                    min: p0.into(),
325                    max: p1.into(),
326                },
327                CornerRadius::default(),
328                Color32::TRANSPARENT,
329                stroke,
330                egui::StrokeKind::Inside
331            );
332        }
333
334        Ok(())
335    }
336
337    fn draw_path<S: BackendStyle, I: IntoIterator<Item = BackendCoord>>(
338        &mut self,
339        path: I,
340        style: &S,
341    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
342        let bounds = self.ui.max_rect();
343        let painter = self.ui.painter().with_clip_rect(bounds);
344
345        let points: Vec<Pos2> = path
346            .into_iter()
347            .map(|point| {
348                let point = self.point_transform(EguiBackendCoord::from(point), bounds);
349
350                point.into()
351            })
352            .collect();
353
354        let color: Color32 = EguiBackendColor::from(style.color()).into();
355
356        let stroke = Stroke::new(style.stroke_width() as f32, color);
357
358        let shape = PathShape::line(points, stroke);
359
360        painter.add(shape);
361        Ok(())
362    }
363
364    fn draw_circle<S: BackendStyle>(
365        &mut self,
366        center: BackendCoord,
367        radius: u32,
368        style: &S,
369        fill: bool,
370    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
371        let bounds = self.ui.max_rect();
372        let painter = self.ui.painter().with_clip_rect(bounds);
373
374        let center = self.point_transform(EguiBackendCoord::from(center), bounds);
375        let color: Color32 = EguiBackendColor::from(style.color()).into();
376        if fill {
377            painter.circle_filled(center.into(), radius as _, color);
378        } else {
379            let stroke = Stroke::new(style.stroke_width() as f32, color);
380            painter.circle(center.into(), radius as _, Color32::TRANSPARENT, stroke);
381        }
382
383        Ok(())
384    }
385
386    fn fill_polygon<S: BackendStyle, I: IntoIterator<Item = BackendCoord>>(
387        &mut self,
388        vert: I,
389        style: &S,
390    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
391        let bounds = self.ui.max_rect();
392        let painter = self.ui.painter().with_clip_rect(bounds);
393
394        let points: Vec<Pos2> = vert
395            .into_iter()
396            .map(|point| {
397                let point = self.point_transform(EguiBackendCoord::from(point), bounds);
398
399                point.into()
400            })
401            .collect();
402
403        let color: Color32 = EguiBackendColor::from(style.color()).into();
404
405        let stroke = Stroke::NONE;
406
407        let shape = PathShape::convex_polygon(points, color, stroke);
408
409        painter.add(shape);
410
411        Ok(())
412    }
413
414    fn draw_text<TStyle: BackendTextStyle>(
415        &mut self,
416        text: &str,
417        style: &TStyle,
418        pos: (i32, i32),
419    ) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
420        let bounds = self.ui.max_rect();
421        let painter = self.ui.painter().with_clip_rect(bounds);
422
423        let pos = self.point_transform(EguiBackendCoord::from(pos), bounds);
424
425        let font_size = style.size() as f32;
426        let font_family = match style.family() {
427            PlottersFontFamily::Serif | PlottersFontFamily::SansSerif => {
428                EguiFontFamily::Proportional
429            }
430            PlottersFontFamily::Monospace => EguiFontFamily::Monospace,
431            PlottersFontFamily::Name(string) => EguiFontFamily::Name(string.into()),
432        };
433
434        let font = FontId {
435            size: font_size,
436            family: font_family,
437        };
438
439        let color: Color32 = EguiBackendColor::from(style.color()).into();
440
441        let rotations = style.transform() as usize;
442        let angle = rotations as f32 * FRAC_PI_2;
443
444        let Pos { h_pos, v_pos } = style.anchor();
445
446        // !TODO! Find a slightly more eligant rotation function.
447        let mut anchor = Align2([
448            match h_pos {
449                HPos::Left => Align::LEFT,
450                HPos::Right => Align::RIGHT,
451                HPos::Center => Align::Center,
452            },
453            match v_pos {
454                VPos::Top => Align::TOP,
455                VPos::Center => Align::Center,
456                VPos::Bottom => Align::BOTTOM,
457            },
458        ]);
459        fn rotate(anchor: &mut Align2) {
460            *anchor = match anchor {
461                &mut Align2::LEFT_TOP => Align2::RIGHT_TOP,
462                &mut Align2::RIGHT_TOP => Align2::RIGHT_BOTTOM,
463                &mut Align2::RIGHT_BOTTOM => Align2::LEFT_BOTTOM,
464                &mut Align2::LEFT_BOTTOM => Align2::LEFT_TOP,
465                &mut Align2::LEFT_CENTER => Align2::CENTER_TOP,
466                &mut Align2::CENTER_TOP => Align2::RIGHT_CENTER,
467                &mut Align2::RIGHT_CENTER => Align2::CENTER_BOTTOM,
468                &mut Align2::CENTER_BOTTOM => Align2::LEFT_CENTER,
469                &mut Align2::CENTER_CENTER => Align2::CENTER_CENTER,
470            }
471        }
472        for _ in 0..rotations {
473            rotate(&mut anchor)
474        }
475        let galley = painter.layout_no_wrap(text.to_string(), font, color);
476        let rect = anchor.anchor_rect(Rect::from_min_size(pos.into(), galley.size()));
477        if !galley.is_empty() {
478            painter.add(TextShape {
479                angle,
480                ..TextShape::new(rect.min, galley, Color32::PLACEHOLDER)
481            });
482        }
483
484        Ok(())
485    }
486}