plotters_unsable/chart/
series.rs

1use super::ChartContext;
2use crate::coord::CoordTranslate;
3use crate::drawing::backend::{BackendCoord, DrawingErrorKind};
4use crate::drawing::{DrawingAreaErrorKind, DrawingBackend};
5use crate::element::{EmptyElement, IntoDynElement, MultiLineText, Rectangle};
6use crate::style::{IntoFont, ShapeStyle, TextStyle, Transparent};
7
8pub enum SeriesLabelPosition {
9    UpperRight,
10    MiddleRight,
11    LowerRight,
12    Coordinate(i32, i32),
13}
14
15impl SeriesLabelPosition {
16    fn layout_label_area(&self, label_dim: (i32, i32), area_dim: (u32, u32)) -> (i32, i32) {
17        match self {
18            SeriesLabelPosition::UpperRight => (area_dim.0 as i32 - label_dim.0 as i32, 0),
19            SeriesLabelPosition::MiddleRight => (
20                area_dim.0 as i32 - label_dim.0 as i32,
21                (area_dim.1 as i32 - label_dim.1 as i32) / 2,
22            ),
23            SeriesLabelPosition::LowerRight => (
24                area_dim.0 as i32 - label_dim.0 as i32,
25                area_dim.1 as i32 - label_dim.1 as i32,
26            ),
27            SeriesLabelPosition::Coordinate(x, y) => (*x, *y),
28        }
29    }
30}
31
32/// The struct to sepcify the series label of a target chart context
33pub struct SeriesLabelStyle<'a, DB: DrawingBackend, CT: CoordTranslate> {
34    target: &'a mut ChartContext<DB, CT>,
35    position: SeriesLabelPosition,
36    legend_area_size: u32,
37    border_style: ShapeStyle<'a>,
38    background: ShapeStyle<'a>,
39    label_font: Option<TextStyle<'a>>,
40    margin: u32,
41}
42
43impl<'a, DB: DrawingBackend, CT: CoordTranslate> SeriesLabelStyle<'a, DB, CT> {
44    pub(super) fn new(target: &'a mut ChartContext<DB, CT>) -> Self {
45        Self {
46            target,
47            position: SeriesLabelPosition::MiddleRight,
48            legend_area_size: 30,
49            border_style: (&Transparent).into(),
50            background: (&Transparent).into(),
51            label_font: None,
52            margin: 10,
53        }
54    }
55
56    /// Set the series label positioning style
57    /// `pos` - The positioning style
58    pub fn position(&mut self, pos: SeriesLabelPosition) -> &mut Self {
59        self.position = pos;
60        self
61    }
62
63    pub fn margin(&mut self, value: u32) -> &mut Self {
64        self.margin = value;
65        self
66    }
67
68    /// Set the size of legend area
69    /// `size` - The size of legend area in pixel
70    pub fn legend_area_size(&mut self, size: u32) -> &mut Self {
71        self.legend_area_size = size;
72        self
73    }
74
75    /// Set the style of the label series area
76    /// `style` - The style of the border
77    pub fn border_style<S: Into<ShapeStyle<'a>>>(&mut self, style: S) -> &mut Self {
78        self.border_style = style.into();
79        self
80    }
81
82    /// Set the background style
83    /// `style` - The style of the border
84    pub fn background_style<S: Into<ShapeStyle<'a>>>(&mut self, style: S) -> &mut Self {
85        self.background = style.into();
86        self
87    }
88
89    /// Set the series label font
90    /// `font` - The font
91    pub fn label_font<F: Into<TextStyle<'a>>>(&mut self, font: F) -> &mut Self {
92        self.label_font = Some(font.into());
93        self
94    }
95
96    /// Draw the series label area
97    pub fn draw(&mut self) -> Result<(), DrawingAreaErrorKind<DB::ErrorType>>
98    where
99        DB: 'static,
100    {
101        let drawing_area = self.target.plotting_area().strip_coord_spec();
102        let default_font = ("Arial", 12).into_font();
103        let default_style: TextStyle = (&default_font).into();
104
105        let font = {
106            let mut temp = None;
107            std::mem::swap(&mut self.label_font, &mut temp);
108            temp.unwrap_or(default_style)
109        };
110
111        let mut label_element = MultiLineText::<_, &str>::new((0, 0), &font);
112        let mut funcs = vec![];
113
114        for anno in self.target.series_anno.iter() {
115            let label_text = anno.get_label();
116            let draw_func = anno.get_draw_func();
117
118            if label_text == "" && draw_func.is_none() {
119                continue;
120            }
121
122            funcs.push(
123                draw_func.unwrap_or_else(|| &|p: BackendCoord| EmptyElement::at(p).into_dyn()),
124            );
125            label_element.push_line(label_text);
126        }
127
128        let (mut w, mut h) = label_element
129            .estimate_dimension()
130            .map_err(|e| DrawingAreaErrorKind::BackendError(DrawingErrorKind::FontError(e)))?;
131
132        let margin = self.margin as i32;
133
134        w += self.legend_area_size as i32 + margin * 2;
135        h += margin * 2;
136
137        let (area_w, area_h) = drawing_area.dim_in_pixel();
138
139        let (label_x, label_y) = self.position.layout_label_area((w, h), (area_w, area_h));
140
141        label_element.relocate((
142            label_x + self.legend_area_size as i32 + margin,
143            label_y + margin,
144        ));
145
146        drawing_area.draw(&Rectangle::new(
147            [(label_x, label_y), (label_x + w, label_y + h)],
148            self.background.filled(),
149        ))?;
150        drawing_area.draw(&Rectangle::new(
151            [(label_x, label_y), (label_x + w, label_y + h)],
152            self.border_style.clone(),
153        ))?;
154        drawing_area.draw(&label_element)?;
155
156        for (((_, y0), (_, y1)), make_elem) in label_element
157            .compute_line_layout()
158            .map_err(|e| DrawingAreaErrorKind::BackendError(DrawingErrorKind::FontError(e)))?
159            .into_iter()
160            .zip(funcs.into_iter())
161        {
162            let legend_element = make_elem((label_x + margin, (y0 + y1) / 2));
163            drawing_area.draw(&legend_element)?;
164        }
165
166        Ok(())
167    }
168}