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