use std::borrow::Borrow; use plotters_backend::{BackendCoord, DrawingBackend}; use crate::chart::{SeriesAnno, SeriesLabelStyle}; use crate::coord::{CoordTranslate, ReverseCoordTranslate, Shift}; use crate::drawing::{DrawingArea, DrawingAreaErrorKind}; use crate::element::{CoordMapper, Drawable, PointCollection}; pub(super) mod cartesian2d; pub(super) mod cartesian3d; pub(super) use cartesian3d::Coord3D; /** The context of the chart. This is the core object of Plotters. Any plot/chart is abstracted as this type, and any data series can be placed to the chart context. - To draw a series on a chart context, use [`ChartContext::draw_series()`]. - To draw a single element on the chart, you may want to use [`ChartContext::plotting_area()`]. See [`crate::series::LineSeries`] and [`ChartContext::configure_series_labels()`] for more information and examples */ pub struct ChartContext<'a, DB: DrawingBackend, CT: CoordTranslate> { pub(crate) x_label_area: [Option>; 2], pub(crate) y_label_area: [Option>; 2], pub(crate) drawing_area: DrawingArea, pub(crate) series_anno: Vec>, pub(crate) drawing_area_pos: (i32, i32), } impl<'a, DB: DrawingBackend, CT: ReverseCoordTranslate> ChartContext<'a, DB, CT> { /// Convert the chart context into an closure that can be used for coordinate translation pub fn into_coord_trans(self) -> impl Fn(BackendCoord) -> Option { let coord_spec = self.drawing_area.into_coord_spec(); move |coord| coord_spec.reverse_translate(coord) } } impl<'a, DB: DrawingBackend, CT: CoordTranslate> ChartContext<'a, DB, CT> { /** Configure the styles for drawing series labels in the chart # Example ``` use plotters::prelude::*; let data = [(1.0, 3.3), (2., 2.1), (3., 1.5), (4., 1.9), (5., 1.0)]; let drawing_area = SVGBackend::new("configure_series_labels.svg", (300, 200)).into_drawing_area(); drawing_area.fill(&WHITE).unwrap(); let mut chart_builder = ChartBuilder::on(&drawing_area); chart_builder.margin(7).set_left_and_bottom_label_area_size(20); let mut chart_context = chart_builder.build_cartesian_2d(0.0..5.5, 0.0..5.5).unwrap(); chart_context.configure_mesh().draw().unwrap(); chart_context.draw_series(LineSeries::new(data, BLACK)).unwrap().label("Series 1") .legend(|(x,y)| Rectangle::new([(x - 15, y + 1), (x, y)], BLACK)); chart_context.configure_series_labels().position(SeriesLabelPosition::UpperRight).margin(20) .legend_area_size(5).border_style(BLUE).background_style(BLUE.mix(0.1)).label_font(("Calibri", 20)).draw().unwrap(); ``` The result is a chart with one data series labeled "Series 1" in a blue legend box: ![](https://cdn.jsdelivr.net/gh/facorread/plotters-doc-data@8e0fe60/apidoc/configure_series_labels.svg) # See also See [`crate::series::LineSeries`] for more information and examples */ pub fn configure_series_labels<'b>(&'b mut self) -> SeriesLabelStyle<'a, 'b, DB, CT> where DB: 'a, { SeriesLabelStyle::new(self) } /// Get a reference of underlying plotting area pub fn plotting_area(&self) -> &DrawingArea { &self.drawing_area } /// Cast the reference to a chart context to a reference to underlying coordinate specification. pub fn as_coord_spec(&self) -> &CT { self.drawing_area.as_coord_spec() } // TODO: All draw_series_impl is overly strict about lifetime, because we don't have stable HKT, // what we can ensure is for all lifetime 'b the element reference &'b E is a iterator // of points reference with the same lifetime. // However, this doesn't work if the coordinate doesn't live longer than the backend, // this is unnecessarily strict pub(crate) fn draw_series_impl( &mut self, series: S, ) -> Result<(), DrawingAreaErrorKind> where B: CoordMapper, for<'b> &'b E: PointCollection<'b, CT::From, B>, E: Drawable, R: Borrow, S: IntoIterator, { for element in series { self.drawing_area.draw(element.borrow())?; } Ok(()) } pub(crate) fn alloc_series_anno(&mut self) -> &mut SeriesAnno<'a, DB> { let idx = self.series_anno.len(); self.series_anno.push(SeriesAnno::new()); &mut self.series_anno[idx] } /** Draws a data series. A data series in Plotters is abstracted as an iterator of elements. See [`crate::series::LineSeries`] and [`ChartContext::configure_series_labels()`] for more information and examples. */ pub fn draw_series( &mut self, series: S, ) -> Result<&mut SeriesAnno<'a, DB>, DrawingAreaErrorKind> where B: CoordMapper, for<'b> &'b E: PointCollection<'b, CT::From, B>, E: Drawable, R: Borrow, S: IntoIterator, { self.draw_series_impl(series)?; Ok(self.alloc_series_anno()) } } #[cfg(test)] mod test { use crate::prelude::*; #[test] fn test_chart_context() { let drawing_area = create_mocked_drawing_area(200, 200, |_| {}); drawing_area.fill(&WHITE).expect("Fill"); let mut chart = ChartBuilder::on(&drawing_area) .caption("Test Title", ("serif", 10)) .x_label_area_size(20) .y_label_area_size(20) .set_label_area_size(LabelAreaPosition::Top, 20) .set_label_area_size(LabelAreaPosition::Right, 20) .build_cartesian_2d(0..10, 0..10) .expect("Create chart") .set_secondary_coord(0.0..1.0, 0.0..1.0); chart .configure_mesh() .x_desc("X") .y_desc("Y") .draw() .expect("Draw mesh"); chart .configure_secondary_axes() .x_desc("X") .y_desc("Y") .draw() .expect("Draw Secondary axes"); // test that chart states work correctly with dual coord charts let cs = chart.into_chart_state(); let mut chart = cs.clone().restore(&drawing_area); chart .draw_series(std::iter::once(Circle::new((5, 5), 5, &RED))) .expect("Drawing error"); chart .draw_secondary_series(std::iter::once(Circle::new((0.3, 0.8), 5, &GREEN))) .expect("Drawing error") .label("Test label") .legend(|(x, y)| Rectangle::new([(x - 10, y - 5), (x, y + 5)], &GREEN)); chart .configure_series_labels() .position(SeriesLabelPosition::UpperMiddle) .draw() .expect("Drawing error"); } #[test] fn test_chart_context_3d() { let drawing_area = create_mocked_drawing_area(200, 200, |_| {}); drawing_area.fill(&WHITE).expect("Fill"); let mut chart = ChartBuilder::on(&drawing_area) .caption("Test Title", ("serif", 10)) .x_label_area_size(20) .y_label_area_size(20) .set_label_area_size(LabelAreaPosition::Top, 20) .set_label_area_size(LabelAreaPosition::Right, 20) .build_cartesian_3d(0..10, 0..10, 0..10) .expect("Create chart"); chart.with_projection(|mut pb| { pb.yaw = 0.5; pb.pitch = 0.5; pb.scale = 0.5; pb.into_matrix() }); chart.configure_axes().draw().expect("Drawing axes"); // test that chart states work correctly with 3d coordinates let cs = chart.into_chart_state(); let mut chart = cs.clone().restore(&drawing_area); chart .draw_series(std::iter::once(Circle::new((5, 5, 5), 5, &RED))) .expect("Drawing error"); } }