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 |
35 | pub fn into_coord_trans(self) -> impl Fn(BackendCoord) -> Option<CT::From> { |
36 | let coord_spec: CT = self.drawing_area.into_coord_spec(); |
37 | move |coord: (i32, i32)| coord_spec.reverse_translate(input: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 | */ |
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)] |
137 | mod 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 | } |
222 | |