plotters/chart/
context.rs

1use std::borrow::Borrow;
2
3use plotters_backend::{BackendCoord, DrawingBackend};
4
5use crate::chart::{SeriesAnno, SeriesLabelStyle};
6use crate::coord::{CoordTranslate, ReverseCoordTranslate, Shift};
7use crate::drawing::{DrawingArea, DrawingAreaErrorKind};
8use crate::element::{CoordMapper, Drawable, PointCollection};
9
10pub(super) mod cartesian2d;
11pub(super) mod cartesian3d;
12
13pub(super) use cartesian3d::Coord3D;
14
15/**
16The context of the chart. This is the core object of Plotters.
17
18Any plot/chart is abstracted as this type, and any data series can be placed to the chart context.
19
20- To draw a series on a chart context, use [`ChartContext::draw_series()`].
21- To draw a single element on the chart, you may want to use [`ChartContext::plotting_area()`].
22
23See [`crate::series::LineSeries`] and [`ChartContext::configure_series_labels()`] for more information and examples
24*/
25pub struct ChartContext<'a, DB: DrawingBackend, CT: CoordTranslate> {
26    pub(crate) x_label_area: [Option<DrawingArea<DB, Shift>>; 2],
27    pub(crate) y_label_area: [Option<DrawingArea<DB, Shift>>; 2],
28    pub(crate) drawing_area: DrawingArea<DB, CT>,
29    pub(crate) series_anno: Vec<SeriesAnno<'a, DB>>,
30    pub(crate) drawing_area_pos: (i32, i32),
31}
32
33impl<'a, DB: DrawingBackend, CT: ReverseCoordTranslate> ChartContext<'a, DB, CT> {
34    /// Convert the chart context into an closure that can be used for coordinate translation
35    pub fn into_coord_trans(self) -> impl Fn(BackendCoord) -> Option<CT::From> {
36        let coord_spec = self.drawing_area.into_coord_spec();
37        move |coord| coord_spec.reverse_translate(coord)
38    }
39}
40
41impl<'a, DB: DrawingBackend, CT: CoordTranslate> ChartContext<'a, DB, CT> {
42    /**
43    Configure the styles for drawing series labels in the chart
44
45    # Example
46
47    ```
48    use plotters::prelude::*;
49    let data = [(1.0, 3.3), (2., 2.1), (3., 1.5), (4., 1.9), (5., 1.0)];
50    let drawing_area = SVGBackend::new("configure_series_labels.svg", (300, 200)).into_drawing_area();
51    drawing_area.fill(&WHITE).unwrap();
52    let mut chart_builder = ChartBuilder::on(&drawing_area);
53    chart_builder.margin(7).set_left_and_bottom_label_area_size(20);
54    let mut chart_context = chart_builder.build_cartesian_2d(0.0..5.5, 0.0..5.5).unwrap();
55    chart_context.configure_mesh().draw().unwrap();
56    chart_context.draw_series(LineSeries::new(data, BLACK)).unwrap().label("Series 1")
57        .legend(|(x,y)| Rectangle::new([(x - 15, y + 1), (x, y)], BLACK));
58    chart_context.configure_series_labels().position(SeriesLabelPosition::UpperRight).margin(20)
59        .legend_area_size(5).border_style(BLUE).background_style(BLUE.mix(0.1)).label_font(("Calibri", 20)).draw().unwrap();
60    ```
61
62    The result is a chart with one data series labeled "Series 1" in a blue legend box:
63
64    ![](https://cdn.jsdelivr.net/gh/facorread/plotters-doc-data@8e0fe60/apidoc/configure_series_labels.svg)
65
66    # See also
67
68    See [`crate::series::LineSeries`] for more information and examples
69    */
70    pub fn configure_series_labels<'b>(&'b mut self) -> SeriesLabelStyle<'a, 'b, DB, CT>
71    where
72        DB: 'a,
73    {
74        SeriesLabelStyle::new(self)
75    }
76
77    /// Get a reference of underlying plotting area
78    pub fn plotting_area(&self) -> &DrawingArea<DB, CT> {
79        &self.drawing_area
80    }
81
82    /// Cast the reference to a chart context to a reference to underlying coordinate specification.
83    pub fn as_coord_spec(&self) -> &CT {
84        self.drawing_area.as_coord_spec()
85    }
86
87    // TODO: All draw_series_impl is overly strict about lifetime, because we don't have stable HKT,
88    //       what we can ensure is for all lifetime 'b the element reference &'b E is a iterator
89    //       of points reference with the same lifetime.
90    //       However, this doesn't work if the coordinate doesn't live longer than the backend,
91    //       this is unnecessarily strict
92    pub(crate) fn draw_series_impl<B, E, R, S>(
93        &mut self,
94        series: S,
95    ) -> Result<(), DrawingAreaErrorKind<DB::ErrorType>>
96    where
97        B: CoordMapper,
98        for<'b> &'b E: PointCollection<'b, CT::From, B>,
99        E: Drawable<DB, B>,
100        R: Borrow<E>,
101        S: IntoIterator<Item = R>,
102    {
103        for element in series {
104            self.drawing_area.draw(element.borrow())?;
105        }
106        Ok(())
107    }
108
109    pub(crate) fn alloc_series_anno(&mut self) -> &mut SeriesAnno<'a, DB> {
110        let idx = self.series_anno.len();
111        self.series_anno.push(SeriesAnno::new());
112        &mut self.series_anno[idx]
113    }
114
115    /**
116    Draws a data series. A data series in Plotters is abstracted as an iterator of elements.
117
118    See [`crate::series::LineSeries`] and [`ChartContext::configure_series_labels()`] for more information and examples.
119    */
120    pub fn draw_series<B, E, R, S>(
121        &mut self,
122        series: S,
123    ) -> Result<&mut SeriesAnno<'a, DB>, DrawingAreaErrorKind<DB::ErrorType>>
124    where
125        B: CoordMapper,
126        for<'b> &'b E: PointCollection<'b, CT::From, B>,
127        E: Drawable<DB, B>,
128        R: Borrow<E>,
129        S: IntoIterator<Item = R>,
130    {
131        self.draw_series_impl(series)?;
132        Ok(self.alloc_series_anno())
133    }
134}
135
136#[cfg(test)]
137mod test {
138    use crate::prelude::*;
139
140    #[test]
141    fn test_chart_context() {
142        let drawing_area = create_mocked_drawing_area(200, 200, |_| {});
143
144        drawing_area.fill(&WHITE).expect("Fill");
145
146        let mut chart = ChartBuilder::on(&drawing_area)
147            .caption("Test Title", ("serif", 10))
148            .x_label_area_size(20)
149            .y_label_area_size(20)
150            .set_label_area_size(LabelAreaPosition::Top, 20)
151            .set_label_area_size(LabelAreaPosition::Right, 20)
152            .build_cartesian_2d(0..10, 0..10)
153            .expect("Create chart")
154            .set_secondary_coord(0.0..1.0, 0.0..1.0);
155
156        chart
157            .configure_mesh()
158            .x_desc("X")
159            .y_desc("Y")
160            .draw()
161            .expect("Draw mesh");
162        chart
163            .configure_secondary_axes()
164            .x_desc("X")
165            .y_desc("Y")
166            .draw()
167            .expect("Draw Secondary axes");
168
169        // test that chart states work correctly with dual coord charts
170        let cs = chart.into_chart_state();
171        let mut chart = cs.clone().restore(&drawing_area);
172
173        chart
174            .draw_series(std::iter::once(Circle::new((5, 5), 5, RED)))
175            .expect("Drawing error");
176        chart
177            .draw_secondary_series(std::iter::once(Circle::new((0.3, 0.8), 5, GREEN)))
178            .expect("Drawing error")
179            .label("Test label")
180            .legend(|(x, y)| Rectangle::new([(x - 10, y - 5), (x, y + 5)], GREEN));
181
182        chart
183            .configure_series_labels()
184            .position(SeriesLabelPosition::UpperMiddle)
185            .draw()
186            .expect("Drawing error");
187    }
188
189    #[test]
190    fn test_chart_context_3d() {
191        let drawing_area = create_mocked_drawing_area(200, 200, |_| {});
192
193        drawing_area.fill(&WHITE).expect("Fill");
194
195        let mut chart = ChartBuilder::on(&drawing_area)
196            .caption("Test Title", ("serif", 10))
197            .x_label_area_size(20)
198            .y_label_area_size(20)
199            .set_label_area_size(LabelAreaPosition::Top, 20)
200            .set_label_area_size(LabelAreaPosition::Right, 20)
201            .build_cartesian_3d(0..10, 0..10, 0..10)
202            .expect("Create chart");
203
204        chart.with_projection(|mut pb| {
205            pb.yaw = 0.5;
206            pb.pitch = 0.5;
207            pb.scale = 0.5;
208            pb.into_matrix()
209        });
210
211        chart.configure_axes().draw().expect("Drawing axes");
212
213        // test that chart states work correctly with 3d coordinates
214        let cs = chart.into_chart_state();
215        let mut chart = cs.clone().restore(&drawing_area);
216
217        chart
218            .draw_series(std::iter::once(Circle::new((5, 5, 5), 5, RED)))
219            .expect("Drawing error");
220    }
221}