1 use std::borrow::Borrow;
2 
3 use plotters_backend::{BackendCoord, DrawingBackend};
4 
5 use crate::chart::{SeriesAnno, SeriesLabelStyle};
6 use crate::coord::{CoordTranslate, ReverseCoordTranslate, Shift};
7 use crate::drawing::{DrawingArea, DrawingAreaErrorKind};
8 use crate::element::{CoordMapper, Drawable, PointCollection};
9 
10 pub(super) mod cartesian2d;
11 pub(super) mod cartesian3d;
12 
13 pub(super) use cartesian3d::Coord3D;
14 
15 /**
16 The context of the chart. This is the core object of Plotters.
17 
18 Any 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 
23 See [`crate::series::LineSeries`] and [`ChartContext::configure_series_labels()`] for more information and examples
24 */
25 pub 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 
33 impl<'a, DB: DrawingBackend, CT: ReverseCoordTranslate> ChartContext<'a, DB, CT> {
34     /// Convert the chart context into an closure that can be used for coordinate translation
into_coord_trans(self) -> impl Fn(BackendCoord) -> Option<CT::From>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 
41 impl<'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     */
configure_series_labels<'b>(&'b mut self) -> SeriesLabelStyle<'a, 'b, DB, CT> where DB: 'a,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
plotting_area(&self) -> &DrawingArea<DB, CT>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.
as_coord_spec(&self) -> &CT83     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
draw_series_impl<B, E, R, S>( &mut self, series: S, ) -> Result<(), DrawingAreaErrorKind<DB::ErrorType>> where B: CoordMapper, for<'b> &'b E: PointCollection<'b, CT::From, B>, E: Drawable<DB, B>, R: Borrow<E>, S: IntoIterator<Item = R>,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 
alloc_series_anno(&mut self) -> &mut SeriesAnno<'a, DB>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     */
draw_series<B, E, R, S>( &mut self, series: S, ) -> Result<&mut SeriesAnno<'a, DB>, DrawingAreaErrorKind<DB::ErrorType>> where B: CoordMapper, for<'b> &'b E: PointCollection<'b, CT::From, B>, E: Drawable<DB, B>, R: Borrow<E>, S: IntoIterator<Item = R>,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)]
137 mod test {
138     use crate::prelude::*;
139 
140     #[test]
test_chart_context()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]
test_chart_context_3d()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 }
222