plotters_ratatui_backend/
lib.rs

1use plotters_backend::{
2    BackendColor, BackendCoord, BackendTextStyle, DrawingBackend, DrawingErrorKind,
3};
4use ratatui::style::{Color as RataColor, Style as RataStyle};
5use ratatui::widgets::canvas;
6use ratatui::{layout, text};
7
8#[cfg(feature = "widget")]
9mod widget;
10#[cfg(feature = "widget")]
11pub use widget::*;
12
13pub const CHAR_PIXEL_SIZE: u32 = 4;
14
15pub struct RatatuiBackend<'a, 'b> {
16    pub canvas: &'a mut canvas::Context<'b>,
17    pub size:   layout::Rect,
18}
19
20impl<'a, 'b> DrawingBackend for RatatuiBackend<'a, 'b> {
21    type ErrorType = Error;
22
23    fn get_size(&self) -> (u32, u32) { rect_to_size(self.size) }
24
25    fn ensure_prepared(&mut self) -> Result { Ok(()) }
26
27    fn present(&mut self) -> Result { Ok(()) }
28
29    fn draw_pixel(&mut self, coord: BackendCoord, color: BackendColor) -> Result {
30        self.canvas.draw(&canvas::Points {
31            coords: &[backend_to_canvas_coords(coord, self.size)],
32            color:  convert_color(color),
33        });
34        Ok(())
35    }
36
37    fn draw_text<TStyle: BackendTextStyle>(
38        &mut self,
39        text: &str,
40        style: &TStyle,
41        mut coord: BackendCoord,
42    ) -> Result {
43        let width = text.chars().count();
44        coord.0 -= (width as u32 * CHAR_PIXEL_SIZE / 2) as i32;
45
46        let (x, y) = backend_to_canvas_coords(coord, self.size);
47        self.canvas.print(x, y, text::Line::styled(text.to_string(), convert_style(style)));
48        Ok(())
49    }
50
51    fn draw_line<S: plotters_backend::BackendStyle>(
52        &mut self,
53        coord1: BackendCoord,
54        coord2: BackendCoord,
55        style: &S,
56    ) -> std::result::Result<(), DrawingErrorKind<Self::ErrorType>> {
57        let (x1, y1) = backend_to_canvas_coords(coord1, self.size);
58        let (x2, y2) = backend_to_canvas_coords(coord2, self.size);
59
60        self.canvas.draw(&canvas::Line::new(x1, y1, x2, y2, convert_color(style.color())));
61        Ok(())
62    }
63
64    fn draw_circle<S: plotters_backend::BackendStyle>(
65        &mut self,
66        coord: BackendCoord,
67        radius: u32,
68        style: &S,
69        _fill: bool,
70    ) -> std::result::Result<(), DrawingErrorKind<Self::ErrorType>> {
71        let (x, y) = backend_to_canvas_coords(coord, self.size);
72        self.canvas.draw(&canvas::Circle {
73            x,
74            y,
75            radius: radius.into(),
76            color: convert_color(style.color()),
77        });
78        Ok(())
79    }
80
81    fn draw_rect<S: plotters_backend::BackendStyle>(
82        &mut self,
83        coord1: BackendCoord,
84        coord2: BackendCoord,
85        style: &S,
86        _fill: bool,
87    ) -> std::result::Result<(), DrawingErrorKind<Self::ErrorType>> {
88        let (x1, y1) = backend_to_canvas_coords(coord1, self.size);
89        let (x2, y2) = backend_to_canvas_coords(coord2, self.size);
90
91        self.canvas.draw(&canvas::Rectangle {
92            x:      x1.min(x2),
93            y:      y1.min(y2),
94            width:  (x2 - x1).abs(),
95            height: (y2 - y1).abs(),
96            color:  convert_color(style.color()),
97        });
98        Ok(())
99    }
100
101    fn estimate_text_size<TStyle: BackendTextStyle>(
102        &self,
103        text: &str,
104        _style: &TStyle,
105    ) -> std::result::Result<(u32, u32), DrawingErrorKind<Self::ErrorType>> {
106        Ok((text.chars().count() as u32 * CHAR_PIXEL_SIZE, CHAR_PIXEL_SIZE))
107    }
108}
109
110fn rect_to_size(rect: layout::Rect) -> (u32, u32) {
111    (u32::from(rect.width) * CHAR_PIXEL_SIZE, u32::from(rect.height) * CHAR_PIXEL_SIZE)
112}
113
114fn backend_to_canvas_coords((x, y): BackendCoord, rect: layout::Rect) -> (f64, f64) {
115    let (width, height) = rect_to_size(rect);
116
117    let x = f64::from(x) / f64::from(width);
118    let mut y = f64::from(y) / f64::from(height);
119    y = 1. - y;
120    (x, y)
121}
122
123fn convert_color(color: BackendColor) -> RataColor {
124    RataColor::Rgb(color.rgb.0, color.rgb.1, color.rgb.2)
125}
126fn convert_style(style: &impl BackendTextStyle) -> RataStyle {
127    RataStyle::default().fg(convert_color(style.color()))
128}
129
130pub type Result<T = ()> = std::result::Result<T, DrawingErrorKind<Error>>;
131
132#[derive(Debug, thiserror::Error)]
133pub enum Error {}