diff options
Diffstat (limited to 'src/chart/dual_coord.rs')
-rw-r--r-- | src/chart/dual_coord.rs | 231 |
1 files changed, 231 insertions, 0 deletions
diff --git a/src/chart/dual_coord.rs b/src/chart/dual_coord.rs new file mode 100644 index 0000000..dcc0ce8 --- /dev/null +++ b/src/chart/dual_coord.rs @@ -0,0 +1,231 @@ +/// The dual coordinate system support +use std::borrow::{Borrow, BorrowMut}; +use std::fmt::Debug; +use std::ops::{Deref, DerefMut}; +use std::sync::Arc; + +use super::context::{ChartContext, ChartState, SeriesAnno}; +use super::mesh::SecondaryMeshStyle; + +use crate::coord::{CoordTranslate, Ranged, RangedCoord, ReverseCoordTranslate, Shift}; +use crate::drawing::backend::{BackendCoord, DrawingBackend}; +use crate::drawing::DrawingArea; +use crate::drawing::DrawingAreaErrorKind; +use crate::element::{Drawable, PointCollection}; + +/// The chart context that has two coordinate system attached +pub struct DualCoordChartContext<'a, DB: DrawingBackend, CT1: CoordTranslate, CT2: CoordTranslate> { + pub(super) primary: ChartContext<'a, DB, CT1>, + pub(super) secondary: ChartContext<'a, DB, CT2>, +} + +/// The chart state for a dual coord chart, see the detailed description for `ChartState` for more +/// information about the purpose of a chart state +pub struct DualCoordChartState<CT1: CoordTranslate, CT2: CoordTranslate> { + primary: ChartState<CT1>, + secondary: ChartState<CT2>, +} + +impl<'a, DB: DrawingBackend, CT1: CoordTranslate, CT2: CoordTranslate> + DualCoordChartContext<'a, DB, CT1, CT2> +{ + /// Convert the chart context into a chart state + pub fn into_chart_state(self) -> DualCoordChartState<CT1, CT2> { + DualCoordChartState { + primary: self.primary.into(), + secondary: self.secondary.into(), + } + } + + /// Convert the chart context into a sharable chart state + pub fn into_shared_chart_state(self) -> DualCoordChartState<Arc<CT1>, Arc<CT2>> { + DualCoordChartState { + primary: self.primary.into_shared_chart_state(), + secondary: self.secondary.into_shared_chart_state(), + } + } +} + +impl<'a, DB: DrawingBackend, CT1: CoordTranslate, CT2: CoordTranslate> + DualCoordChartContext<'a, DB, CT1, CT2> +where + CT1: Clone, + CT2: Clone, +{ + /// Copy the coordinate specs and make the chart state + pub fn to_chart_state(&self) -> DualCoordChartState<CT1, CT2> { + DualCoordChartState { + primary: self.primary.to_chart_state(), + secondary: self.secondary.to_chart_state(), + } + } +} + +impl<CT1: CoordTranslate, CT2: CoordTranslate> DualCoordChartState<CT1, CT2> { + /// Restore the chart state on the given drawing area + pub fn restore<'a, DB: DrawingBackend + 'a>( + self, + area: &DrawingArea<DB, Shift>, + ) -> DualCoordChartContext<'a, DB, CT1, CT2> { + let primary = self.primary.restore(area); + let secondary = self + .secondary + .restore(&primary.plotting_area().strip_coord_spec()); + DualCoordChartContext { primary, secondary } + } +} + +impl<'a, DB: DrawingBackend, CT1: CoordTranslate, CT2: CoordTranslate> + From<DualCoordChartContext<'a, DB, CT1, CT2>> for DualCoordChartState<CT1, CT2> +{ + fn from(chart: DualCoordChartContext<'a, DB, CT1, CT2>) -> DualCoordChartState<CT1, CT2> { + chart.into_chart_state() + } +} + +impl<'a, 'b, DB: DrawingBackend, CT1: CoordTranslate + Clone, CT2: CoordTranslate + Clone> + From<&'b DualCoordChartContext<'a, DB, CT1, CT2>> for DualCoordChartState<CT1, CT2> +{ + fn from(chart: &'b DualCoordChartContext<'a, DB, CT1, CT2>) -> DualCoordChartState<CT1, CT2> { + chart.to_chart_state() + } +} + +impl<'a, DB: DrawingBackend, CT1: CoordTranslate, CT2: CoordTranslate> + DualCoordChartContext<'a, DB, CT1, CT2> +{ + pub(super) fn new(mut primary: ChartContext<'a, DB, CT1>, secondary_coord: CT2) -> Self { + let secondary_drawing_area = primary + .drawing_area + .strip_coord_spec() + .apply_coord_spec(secondary_coord); + let mut secondary_x_label_area = [None, None]; + let mut secondary_y_label_area = [None, None]; + + std::mem::swap(&mut primary.x_label_area[0], &mut secondary_x_label_area[0]); + std::mem::swap(&mut primary.y_label_area[1], &mut secondary_y_label_area[1]); + + Self { + primary, + secondary: ChartContext { + x_label_area: secondary_x_label_area, + y_label_area: secondary_y_label_area, + drawing_area: secondary_drawing_area, + series_anno: vec![], + drawing_area_pos: (0, 0), + }, + } + } + + /// Get a reference to the drawing area that uses the secondary coordinate system + pub fn secondary_plotting_area(&self) -> &DrawingArea<DB, CT2> { + &self.secondary.drawing_area + } + + /// Borrow a mutable reference to the chart context that uses the secondary + /// coordinate system + pub fn borrow_secondary(&self) -> &ChartContext<'a, DB, CT2> { + &self.secondary + } +} + +impl<'a, DB: DrawingBackend, CT1: CoordTranslate, CT2: ReverseCoordTranslate> + DualCoordChartContext<'a, DB, CT1, CT2> +{ + /// Convert the chart context into the secondary coordinate translation function + pub fn into_secondary_coord_trans(self) -> impl Fn(BackendCoord) -> Option<CT2::From> { + let coord_spec = self.secondary.drawing_area.into_coord_spec(); + move |coord| coord_spec.reverse_translate(coord) + } +} + +impl<'a, DB: DrawingBackend, CT1: ReverseCoordTranslate, CT2: ReverseCoordTranslate> + DualCoordChartContext<'a, DB, CT1, CT2> +{ + /// Convert the chart context into a pair of closures that maps the pixel coordinate into the + /// logical coordinate for both primary coordinate system and secondary coordinate system. + pub fn into_coord_trans_pair( + self, + ) -> ( + impl Fn(BackendCoord) -> Option<CT1::From>, + impl Fn(BackendCoord) -> Option<CT2::From>, + ) { + let coord_spec_1 = self.primary.drawing_area.into_coord_spec(); + let coord_spec_2 = self.secondary.drawing_area.into_coord_spec(); + ( + move |coord| coord_spec_1.reverse_translate(coord), + move |coord| coord_spec_2.reverse_translate(coord), + ) + } +} + +impl<'a, DB: DrawingBackend, CT1: CoordTranslate, SX: Ranged, SY: Ranged> + DualCoordChartContext<'a, DB, CT1, RangedCoord<SX, SY>> +where + SX::ValueType: Debug, + SY::ValueType: Debug, +{ + /// Start configure the style for the secondary axes + pub fn configure_secondary_axes<'b>(&'b mut self) -> SecondaryMeshStyle<'a, 'b, SX, SY, DB> { + SecondaryMeshStyle::new(&mut self.secondary) + } +} + +impl<'a, DB: DrawingBackend, X: Ranged, Y: Ranged, SX: Ranged, SY: Ranged> + DualCoordChartContext<'a, DB, RangedCoord<X, Y>, RangedCoord<SX, SY>> +where + X::ValueType: Debug, + Y::ValueType: Debug, + SX::ValueType: Debug, + SY::ValueType: Debug, +{ + /// Draw a series use the secondary coordinate system + /// - `series`: The series to draw + /// - `Returns` the series annotation object or error code + pub fn draw_secondary_series<E, R, S>( + &mut self, + series: S, + ) -> Result<&mut SeriesAnno<'a, DB>, DrawingAreaErrorKind<DB::ErrorType>> + where + for<'b> &'b E: PointCollection<'b, (SX::ValueType, SY::ValueType)>, + E: Drawable<DB>, + R: Borrow<E>, + S: IntoIterator<Item = R>, + { + self.secondary.draw_series_impl(series)?; + Ok(self.primary.alloc_series_anno()) + } +} + +impl<'a, DB: DrawingBackend, CT1: CoordTranslate, CT2: CoordTranslate> + Borrow<ChartContext<'a, DB, CT1>> for DualCoordChartContext<'a, DB, CT1, CT2> +{ + fn borrow(&self) -> &ChartContext<'a, DB, CT1> { + &self.primary + } +} + +impl<'a, DB: DrawingBackend, CT1: CoordTranslate, CT2: CoordTranslate> + BorrowMut<ChartContext<'a, DB, CT1>> for DualCoordChartContext<'a, DB, CT1, CT2> +{ + fn borrow_mut(&mut self) -> &mut ChartContext<'a, DB, CT1> { + &mut self.primary + } +} + +impl<'a, DB: DrawingBackend, CT1: CoordTranslate, CT2: CoordTranslate> Deref + for DualCoordChartContext<'a, DB, CT1, CT2> +{ + type Target = ChartContext<'a, DB, CT1>; + fn deref(&self) -> &Self::Target { + self.borrow() + } +} + +impl<'a, DB: DrawingBackend, CT1: CoordTranslate, CT2: CoordTranslate> DerefMut + for DualCoordChartContext<'a, DB, CT1, CT2> +{ + fn deref_mut(&mut self) -> &mut Self::Target { + self.borrow_mut() + } +} |