plotters_unsable/chart/
context.rs

1use std::borrow::Borrow;
2use std::fmt::Debug;
3use std::marker::PhantomData;
4use std::ops::Range;
5
6use super::mesh::MeshStyle;
7use super::series::SeriesLabelStyle;
8
9use crate::coord::{CoordTranslate, MeshLine, Ranged, RangedCoord, ReverseCoordTranslate, Shift};
10use crate::drawing::backend::{BackendCoord, DrawingBackend};
11use crate::drawing::{DrawingArea, DrawingAreaErrorKind};
12use crate::element::{Drawable, DynElement, IntoDynElement, Path, PointCollection};
13use crate::style::{FontTransform, ShapeStyle, TextStyle};
14
15/// The annotations (such as the label of the series, the legend element, etc)
16pub struct SeriesAnno<DB: DrawingBackend> {
17    label: Option<String>,
18    draw_func: Option<Box<dyn Fn(BackendCoord) -> DynElement<DB, BackendCoord>>>,
19    phantom_data: PhantomData<DB>,
20}
21
22impl<DB: DrawingBackend> SeriesAnno<DB> {
23    pub(crate) fn get_label(&self) -> &str {
24        self.label.as_ref().map(|x| x.as_str()).unwrap_or("")
25    }
26
27    pub(crate) fn get_draw_func(
28        &self,
29    ) -> Option<&dyn Fn(BackendCoord) -> DynElement<DB, BackendCoord>> {
30        self.draw_func.as_ref().map(|x| x.borrow())
31    }
32
33    fn new() -> Self {
34        Self {
35            label: None,
36            draw_func: None,
37            phantom_data: PhantomData,
38        }
39    }
40
41    /// Set the series label
42    /// `label`: The string would be use as label for current series
43    pub fn label<L: Into<String>>(&mut self, label: L) -> &mut Self {
44        self.label = Some(label.into());
45        self
46    }
47
48    /// Set the legend element creator function
49    /// - `func`: The function use to create the element
50    /// *Note*: The creation function uses a shifted pixel-based coordinate system. And place the
51    /// point (0,0) to the mid-right point of the shape
52    pub fn legend<E: IntoDynElement<DB, BackendCoord>, T: Fn(BackendCoord) -> E + 'static>(
53        &mut self,
54        func: T,
55    ) -> &mut Self {
56        self.draw_func = Some(Box::new(move |p| func(p).into_dyn()));
57        self
58    }
59}
60
61/// The context of the chart. This is the core object of Plotters.
62/// Any plot/chart is abstracted as this type, and any data series can be placed to the chart
63/// context.
64pub struct ChartContext<DB: DrawingBackend, CT: CoordTranslate> {
65    pub(super) x_label_area: Option<DrawingArea<DB, Shift>>,
66    pub(super) y_label_area: Option<DrawingArea<DB, Shift>>,
67    pub(super) drawing_area: DrawingArea<DB, CT>,
68    pub(super) series_anno: Vec<SeriesAnno<DB>>,
69}
70
71impl<
72        DB: DrawingBackend,
73        XT: Debug,
74        YT: Debug,
75        X: Ranged<ValueType = XT>,
76        Y: Ranged<ValueType = YT>,
77    > ChartContext<DB, RangedCoord<X, Y>>
78{
79    /// Initialize a mesh configuration object and mesh drawing can be finalized by calling
80    /// the function `MeshStyle::draw`
81    pub fn configure_mesh(&mut self) -> MeshStyle<X, Y, DB> {
82        MeshStyle {
83            axis_style: None,
84            x_label_offset: 0,
85            draw_x_mesh: true,
86            draw_y_mesh: true,
87            draw_x_axis: true,
88            draw_y_axis: true,
89            n_x_labels: 10,
90            n_y_labels: 10,
91            line_style_1: None,
92            line_style_2: None,
93            label_style: None,
94            format_x: &|x| format!("{:?}", x),
95            format_y: &|y| format!("{:?}", y),
96            target: Some(self),
97            _pahtom_data: PhantomData,
98            x_desc: None,
99            y_desc: None,
100            axis_desc_style: None,
101        }
102    }
103}
104
105impl<DB: DrawingBackend, CT: CoordTranslate> ChartContext<DB, CT> {
106    /// Configure the styles for drawing series labels in the chart
107    pub fn configure_series_labels(&mut self) -> SeriesLabelStyle<DB, CT> {
108        SeriesLabelStyle::new(self)
109    }
110
111    /// Get a reference of underlying plotting area
112    pub fn plotting_area(&self) -> &DrawingArea<DB, CT> {
113        &self.drawing_area
114    }
115}
116
117impl<DB: DrawingBackend, CT: ReverseCoordTranslate> ChartContext<DB, CT> {
118    /// Convert the chart context into an closure that can be used for coordinate translation
119    pub fn into_coord_trans(self) -> impl Fn(BackendCoord) -> Option<CT::From> {
120        let coord_spec = self.drawing_area.into_coord_spec();
121        move |coord| coord_spec.reverse_translate(coord)
122    }
123}
124
125impl<DB: DrawingBackend, X: Ranged, Y: Ranged> ChartContext<DB, RangedCoord<X, Y>> {
126    /// Get the range of X axis
127    pub fn x_range(&self) -> Range<X::ValueType> {
128        self.drawing_area.get_x_range()
129    }
130
131    /// Get range of the Y axis
132    pub fn y_range(&self) -> Range<Y::ValueType> {
133        self.drawing_area.get_y_range()
134    }
135
136    /// Maps the coordinate to the backend coordinate. This is typically used
137    /// with an interactive chart.
138    pub fn backend_coord(&self, coord: &(X::ValueType, Y::ValueType)) -> BackendCoord {
139        self.drawing_area.map_coordinate(coord)
140    }
141
142    /// Draw a data series. A data series in Plotters is abstracted as an iterator of elements
143    pub fn draw_series<E, R, S>(
144        &mut self,
145        series: S,
146    ) -> Result<&mut SeriesAnno<DB>, DrawingAreaErrorKind<DB::ErrorType>>
147    where
148        for<'a> &'a E: PointCollection<'a, (X::ValueType, Y::ValueType)>,
149        E: Drawable<DB>,
150        R: Borrow<E>,
151        S: IntoIterator<Item = R>,
152    {
153        for element in series {
154            self.drawing_area.draw(element.borrow())?;
155        }
156
157        let idx = self.series_anno.len();
158
159        self.series_anno.push(SeriesAnno::new());
160
161        Ok(&mut self.series_anno[idx])
162    }
163
164    #[allow(clippy::too_many_arguments)]
165    pub(super) fn draw_mesh<FmtLabel>(
166        &mut self,
167        (r, c): (usize, usize),
168        mesh_line_style: &ShapeStyle,
169        label_style: &TextStyle,
170        mut fmt_label: FmtLabel,
171        x_mesh: bool,
172        y_mesh: bool,
173        x_label_offset: i32,
174        x_axis: bool,
175        y_axis: bool,
176        axis_style: &ShapeStyle,
177        axis_desc_style: &TextStyle,
178        x_desc: Option<String>,
179        y_desc: Option<String>,
180    ) -> Result<(), DrawingAreaErrorKind<DB::ErrorType>>
181    where
182        FmtLabel: FnMut(&MeshLine<X, Y>) -> Option<String>,
183    {
184        let mut x_labels = vec![];
185        let mut y_labels = vec![];
186        self.drawing_area.draw_mesh(
187            |b, l| {
188                let draw;
189                match l {
190                    MeshLine::XMesh((x, _), _, _) => {
191                        if let Some(label_text) = fmt_label(&l) {
192                            x_labels.push((x, label_text));
193                        }
194                        draw = x_mesh;
195                    }
196                    MeshLine::YMesh((_, y), _, _) => {
197                        if let Some(label_text) = fmt_label(&l) {
198                            y_labels.push((y, label_text));
199                        }
200                        draw = y_mesh;
201                    }
202                };
203                if draw {
204                    l.draw(b, mesh_line_style)
205                } else {
206                    Ok(())
207                }
208            },
209            r,
210            c,
211        )?;
212
213        let (x0, y0) = self.drawing_area.get_base_pixel();
214
215        if let Some(ref xl) = self.x_label_area {
216            let (tw, th) = xl.dim_in_pixel();
217            if x_axis {
218                xl.draw(&Path::new(vec![(0, 0), (tw as i32, 0)], axis_style.clone()))?;
219            }
220            for (p, t) in x_labels {
221                let (w, _) = label_style.font.box_size(&t).unwrap_or((0, 0));
222
223                if p - x0 + x_label_offset > 0 && p - x0 + x_label_offset + w as i32 / 2 < tw as i32
224                {
225                    if x_axis {
226                        xl.draw(&Path::new(
227                            vec![(p - x0, 0), (p - x0, 5)],
228                            axis_style.clone(),
229                        ))?;
230                    }
231                    xl.draw_text(
232                        &t,
233                        label_style,
234                        (p - x0 - w as i32 / 2 + x_label_offset, 10),
235                    )?;
236                }
237            }
238
239            if let Some(ref text) = x_desc {
240                let (w, h) = label_style.font.box_size(text).unwrap_or((0, 0));
241
242                let left = (tw - w) / 2;
243                let top = th - h;
244
245                xl.draw_text(&text, axis_desc_style, (left as i32, top as i32))?;
246            }
247        }
248
249        if let Some(ref yl) = self.y_label_area {
250            let (tw, th) = yl.dim_in_pixel();
251            if y_axis {
252                yl.draw(&Path::new(
253                    vec![(tw as i32, 0), (tw as i32, th as i32)],
254                    axis_style.clone(),
255                ))?;
256            }
257            for (p, t) in y_labels {
258                let (w, h) = label_style.font.box_size(&t).unwrap_or((0, 0));
259                if p - y0 >= 0 && p - y0 - h as i32 / 2 <= th as i32 {
260                    yl.draw_text(
261                        &t,
262                        label_style,
263                        (tw as i32 - w as i32 - 10, p - y0 - h as i32 / 2),
264                    )?;
265                    if y_axis {
266                        yl.draw(&Path::new(
267                            vec![(tw as i32 - 5, p - y0), (tw as i32, p - y0)],
268                            axis_style.clone(),
269                        ))?;
270                    }
271                }
272            }
273
274            if let Some(ref text) = y_desc {
275                let (w, _) = label_style.font.box_size(text).unwrap_or((0, 0));
276
277                let top = (th - w) / 2;
278
279                let mut y_style = axis_desc_style.clone();
280                let y_font = axis_desc_style.font.transform(FontTransform::Rotate270);
281                y_style.font = &y_font;
282
283                yl.draw_text(&text, &y_style, (0, top as i32))?;
284            }
285        }
286
287        Ok(())
288    }
289}