| 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 |  |
| 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 | |