/// The dual coordinate system support
use std::borrow::{Borrow, BorrowMut};
use std::ops::{Deref, DerefMut};
use std::sync::Arc;
use super::mesh::SecondaryMeshStyle;
use super::{ChartContext, ChartState, SeriesAnno};
use crate::coord::cartesian::Cartesian2d;
use crate::coord::ranged1d::{Ranged, ValueFormatter};
use crate::coord::{CoordTranslate, ReverseCoordTranslate, Shift};
use crate::drawing::DrawingArea;
use crate::drawing::DrawingAreaErrorKind;
use crate::element::{Drawable, PointCollection};
use plotters_backend::{BackendCoord, DrawingBackend};
/// The chart context that has two coordinate system attached.
/// This situation is quite common, for example, we with two different coodinate system.
/// For instance this example
/// This is done by attaching a second coordinate system to ChartContext by method [ChartContext::set_secondary_coord](struct.ChartContext.html#method.set_secondary_coord).
/// For instance of dual coordinate charts, see [this example](https://github.com/38/plotters/blob/master/examples/two-scales.rs#L15).
/// Note: `DualCoordChartContext` is always deref to the chart context.
/// - If you want to configure the secondary axis, method [DualCoordChartContext::configure_secondary_axes](struct.DualCoordChartContext.html#method.configure_secondary_axes)
/// - 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.
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.
/// Similar to [ChartState](struct.ChartState.html), but used for the dual coordinate charts.
pub struct DualCoordChartState {
primary: ChartState,
secondary: ChartState,
}
impl
DualCoordChartContext<'_, DB, CT1, CT2>
{
/// Convert the chart context into a chart state, similar to [ChartContext::into_chart_state](struct.ChartContext.html#method.into_chart_state)
pub fn into_chart_state(self) -> DualCoordChartState {
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> {
DualCoordChartState {
primary: self.primary.into_shared_chart_state(),
secondary: self.secondary.into_shared_chart_state(),
}
}
/// Copy the coordinate specs and make a chart state
pub fn to_chart_state(&self) -> DualCoordChartState
where
CT1: Clone,
CT2: Clone,
{
DualCoordChartState {
primary: self.primary.to_chart_state(),
secondary: self.secondary.to_chart_state(),
}
}
}
impl DualCoordChartState {
/// Restore the chart state on the given drawing area
pub fn restore(
self,
area: &DrawingArea,
) -> DualCoordChartContext<'_, DB, CT1, CT2> {
let primary = self.primary.restore(area);
let secondary = self
.secondary
.restore(&primary.plotting_area().strip_coord_spec());
DualCoordChartContext { primary, secondary }
}
}
impl
From> for DualCoordChartState
{
fn from(chart: DualCoordChartContext<'_, DB, CT1, CT2>) -> DualCoordChartState {
chart.into_chart_state()
}
}
impl<'b, DB: DrawingBackend, CT1: CoordTranslate + Clone, CT2: CoordTranslate + Clone>
From<&'b DualCoordChartContext<'_, DB, CT1, CT2>> for DualCoordChartState
{
fn from(chart: &'b DualCoordChartContext<'_, DB, CT1, CT2>) -> DualCoordChartState {
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 {
&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
DualCoordChartContext<'_, DB, CT1, CT2>
{
/// Convert the chart context into the secondary coordinate translation function
pub fn into_secondary_coord_trans(self) -> impl Fn(BackendCoord) -> Option {
let coord_spec = self.secondary.drawing_area.into_coord_spec();
move |coord| coord_spec.reverse_translate(coord)
}
}
impl
DualCoordChartContext<'_, 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,
impl Fn(BackendCoord) -> Option,
) {
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,
XT,
YT,
SX: Ranged,
SY: Ranged,
> DualCoordChartContext<'a, DB, CT1, Cartesian2d>
where
SX: ValueFormatter,
SY: ValueFormatter,
{
/// 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, Cartesian2d, Cartesian2d>
{
/// 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(
&mut self,
series: S,
) -> Result<&mut SeriesAnno<'a, DB>, DrawingAreaErrorKind>
where
for<'b> &'b E: PointCollection<'b, (SX::ValueType, SY::ValueType)>,
E: Drawable,
R: Borrow,
S: IntoIterator- ,
{
self.secondary.draw_series_impl(series)?;
Ok(self.primary.alloc_series_anno())
}
}
impl<'a, DB: DrawingBackend, CT1: CoordTranslate, CT2: CoordTranslate>
Borrow> for DualCoordChartContext<'a, DB, CT1, CT2>
{
fn borrow(&self) -> &ChartContext<'a, DB, CT1> {
&self.primary
}
}
impl<'a, DB: DrawingBackend, CT1: CoordTranslate, CT2: CoordTranslate>
BorrowMut> 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()
}
}