1/// The dual coordinate system support
2use std::borrow::{Borrow, BorrowMut};
3use std::ops::{Deref, DerefMut};
4use std::sync::Arc;
5
6use super::mesh::SecondaryMeshStyle;
7use super::{ChartContext, ChartState, SeriesAnno};
8
9use crate::coord::cartesian::Cartesian2d;
10use crate::coord::ranged1d::{Ranged, ValueFormatter};
11use crate::coord::{CoordTranslate, ReverseCoordTranslate, Shift};
12
13use crate::drawing::DrawingArea;
14use crate::drawing::DrawingAreaErrorKind;
15use crate::element::{Drawable, PointCollection};
16
17use plotters_backend::{BackendCoord, DrawingBackend};
18
19/// The chart context that has two coordinate system attached.
20/// This situation is quite common, for example, we with two different coodinate system.
21/// For instance this example <img src="https://plotters-rs.github.io/plotters-doc-data/twoscale.png"></img>
22/// This is done by attaching a second coordinate system to ChartContext by method [ChartContext::set_secondary_coord](struct.ChartContext.html#method.set_secondary_coord).
23/// For instance of dual coordinate charts, see [this example](https://github.com/plotters-rs/plotters/blob/master/examples/two-scales.rs#L15).
24/// Note: `DualCoordChartContext` is always deref to the chart context.
25/// - If you want to configure the secondary axis, method [DualCoordChartContext::configure_secondary_axes](struct.DualCoordChartContext.html#method.configure_secondary_axes)
26/// - If you want to draw a series using secondary coordinate system, use [DualCoordChartContext::draw_secondary_series](struct.DualCoordChartContext.html#method.draw_secondary_series). And method [ChartContext::draw_series](struct.ChartContext.html#method.draw_series) will always use primary coordinate spec.
27pub struct DualCoordChartContext<'a, DB: DrawingBackend, CT1: CoordTranslate, CT2: CoordTranslate> {
28 pub(super) primary: ChartContext<'a, DB, CT1>,
29 pub(super) secondary: ChartContext<'a, DB, CT2>,
30}
31
32/// The chart state for a dual coord chart, see the detailed description for `ChartState` for more
33/// information about the purpose of a chart state.
34/// Similar to [ChartState](struct.ChartState.html), but used for the dual coordinate charts.
35#[derive(Clone)]
36pub struct DualCoordChartState<CT1: CoordTranslate, CT2: CoordTranslate> {
37 primary: ChartState<CT1>,
38 secondary: ChartState<CT2>,
39}
40
41impl<DB: DrawingBackend, CT1: CoordTranslate, CT2: CoordTranslate>
42 DualCoordChartContext<'_, DB, CT1, CT2>
43{
44 /// Convert the chart context into a chart state, similar to [ChartContext::into_chart_state](struct.ChartContext.html#method.into_chart_state)
45 pub fn into_chart_state(self) -> DualCoordChartState<CT1, CT2> {
46 DualCoordChartState {
47 primary: self.primary.into(),
48 secondary: self.secondary.into(),
49 }
50 }
51
52 /// Convert the chart context into a sharable chart state.
53 pub fn into_shared_chart_state(self) -> DualCoordChartState<Arc<CT1>, Arc<CT2>> {
54 DualCoordChartState {
55 primary: self.primary.into_shared_chart_state(),
56 secondary: self.secondary.into_shared_chart_state(),
57 }
58 }
59
60 /// Copy the coordinate specs and make a chart state
61 pub fn to_chart_state(&self) -> DualCoordChartState<CT1, CT2>
62 where
63 CT1: Clone,
64 CT2: Clone,
65 {
66 DualCoordChartState {
67 primary: self.primary.to_chart_state(),
68 secondary: self.secondary.to_chart_state(),
69 }
70 }
71}
72
73impl<CT1: CoordTranslate, CT2: CoordTranslate> DualCoordChartState<CT1, CT2> {
74 /// Restore the chart state on the given drawing area
75 pub fn restore<DB: DrawingBackend>(
76 self,
77 area: &DrawingArea<DB, Shift>,
78 ) -> DualCoordChartContext<'_, DB, CT1, CT2> {
79 let primary = self.primary.restore(area);
80 let secondary = self
81 .secondary
82 .restore(&primary.plotting_area().strip_coord_spec());
83 DualCoordChartContext { primary, secondary }
84 }
85}
86
87impl<DB: DrawingBackend, CT1: CoordTranslate, CT2: CoordTranslate>
88 From<DualCoordChartContext<'_, DB, CT1, CT2>> for DualCoordChartState<CT1, CT2>
89{
90 fn from(chart: DualCoordChartContext<'_, DB, CT1, CT2>) -> DualCoordChartState<CT1, CT2> {
91 chart.into_chart_state()
92 }
93}
94
95impl<'b, DB: DrawingBackend, CT1: CoordTranslate + Clone, CT2: CoordTranslate + Clone>
96 From<&'b DualCoordChartContext<'_, DB, CT1, CT2>> for DualCoordChartState<CT1, CT2>
97{
98 fn from(chart: &'b DualCoordChartContext<'_, DB, CT1, CT2>) -> DualCoordChartState<CT1, CT2> {
99 chart.to_chart_state()
100 }
101}
102
103impl<'a, DB: DrawingBackend, CT1: CoordTranslate, CT2: CoordTranslate>
104 DualCoordChartContext<'a, DB, CT1, CT2>
105{
106 pub(super) fn new(mut primary: ChartContext<'a, DB, CT1>, secondary_coord: CT2) -> Self {
107 let secondary_drawing_area = primary
108 .drawing_area
109 .strip_coord_spec()
110 .apply_coord_spec(secondary_coord);
111 let mut secondary_x_label_area = [None, None];
112 let mut secondary_y_label_area = [None, None];
113
114 std::mem::swap(&mut primary.x_label_area[0], &mut secondary_x_label_area[0]);
115 std::mem::swap(&mut primary.y_label_area[1], &mut secondary_y_label_area[1]);
116
117 Self {
118 primary,
119 secondary: ChartContext {
120 x_label_area: secondary_x_label_area,
121 y_label_area: secondary_y_label_area,
122 drawing_area: secondary_drawing_area,
123 series_anno: vec![],
124 drawing_area_pos: (0, 0),
125 },
126 }
127 }
128
129 /// Get a reference to the drawing area that uses the secondary coordinate system
130 pub fn secondary_plotting_area(&self) -> &DrawingArea<DB, CT2> {
131 &self.secondary.drawing_area
132 }
133
134 /// Borrow a mutable reference to the chart context that uses the secondary
135 /// coordinate system
136 pub fn borrow_secondary(&self) -> &ChartContext<'a, DB, CT2> {
137 &self.secondary
138 }
139}
140
141impl<DB: DrawingBackend, CT1: CoordTranslate, CT2: ReverseCoordTranslate>
142 DualCoordChartContext<'_, DB, CT1, CT2>
143{
144 /// Convert the chart context into the secondary coordinate translation function
145 pub fn into_secondary_coord_trans(self) -> impl Fn(BackendCoord) -> Option<CT2::From> {
146 let coord_spec = self.secondary.drawing_area.into_coord_spec();
147 move |coord| coord_spec.reverse_translate(coord)
148 }
149}
150
151impl<DB: DrawingBackend, CT1: ReverseCoordTranslate, CT2: ReverseCoordTranslate>
152 DualCoordChartContext<'_, DB, CT1, CT2>
153{
154 /// Convert the chart context into a pair of closures that maps the pixel coordinate into the
155 /// logical coordinate for both primary coordinate system and secondary coordinate system.
156 pub fn into_coord_trans_pair(
157 self,
158 ) -> (
159 impl Fn(BackendCoord) -> Option<CT1::From>,
160 impl Fn(BackendCoord) -> Option<CT2::From>,
161 ) {
162 let coord_spec_1 = self.primary.drawing_area.into_coord_spec();
163 let coord_spec_2 = self.secondary.drawing_area.into_coord_spec();
164 (
165 move |coord| coord_spec_1.reverse_translate(coord),
166 move |coord| coord_spec_2.reverse_translate(coord),
167 )
168 }
169}
170
171impl<
172 'a,
173 DB: DrawingBackend,
174 CT1: CoordTranslate,
175 XT,
176 YT,
177 SX: Ranged<ValueType = XT>,
178 SY: Ranged<ValueType = YT>,
179 > DualCoordChartContext<'a, DB, CT1, Cartesian2d<SX, SY>>
180where
181 SX: ValueFormatter<XT>,
182 SY: ValueFormatter<YT>,
183{
184 /// Start configure the style for the secondary axes
185 pub fn configure_secondary_axes<'b>(&'b mut self) -> SecondaryMeshStyle<'a, 'b, SX, SY, DB> {
186 SecondaryMeshStyle::new(&mut self.secondary)
187 }
188}
189
190impl<'a, DB: DrawingBackend, X: Ranged, Y: Ranged, SX: Ranged, SY: Ranged>
191 DualCoordChartContext<'a, DB, Cartesian2d<X, Y>, Cartesian2d<SX, SY>>
192{
193 /// Draw a series use the secondary coordinate system.
194 /// - `series`: The series to draw
195 /// - `Returns` the series annotation object or error code
196 pub fn draw_secondary_series<E, R, S>(
197 &mut self,
198 series: S,
199 ) -> Result<&mut SeriesAnno<'a, DB>, DrawingAreaErrorKind<DB::ErrorType>>
200 where
201 for<'b> &'b E: PointCollection<'b, (SX::ValueType, SY::ValueType)>,
202 E: Drawable<DB>,
203 R: Borrow<E>,
204 S: IntoIterator<Item = R>,
205 {
206 self.secondary.draw_series_impl(series)?;
207 Ok(self.primary.alloc_series_anno())
208 }
209}
210
211impl<'a, DB: DrawingBackend, CT1: CoordTranslate, CT2: CoordTranslate>
212 Borrow<ChartContext<'a, DB, CT1>> for DualCoordChartContext<'a, DB, CT1, CT2>
213{
214 fn borrow(&self) -> &ChartContext<'a, DB, CT1> {
215 &self.primary
216 }
217}
218
219impl<'a, DB: DrawingBackend, CT1: CoordTranslate, CT2: CoordTranslate>
220 BorrowMut<ChartContext<'a, DB, CT1>> for DualCoordChartContext<'a, DB, CT1, CT2>
221{
222 fn borrow_mut(&mut self) -> &mut ChartContext<'a, DB, CT1> {
223 &mut self.primary
224 }
225}
226
227impl<'a, DB: DrawingBackend, CT1: CoordTranslate, CT2: CoordTranslate> Deref
228 for DualCoordChartContext<'a, DB, CT1, CT2>
229{
230 type Target = ChartContext<'a, DB, CT1>;
231 fn deref(&self) -> &Self::Target {
232 self.borrow()
233 }
234}
235
236impl<'a, DB: DrawingBackend, CT1: CoordTranslate, CT2: CoordTranslate> DerefMut
237 for DualCoordChartContext<'a, DB, CT1, CT2>
238{
239 fn deref_mut(&mut self) -> &mut Self::Target {
240 self.borrow_mut()
241 }
242}
243