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
32pub 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 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 pub fn legend_area_size(&mut self, size: u32) -> &mut Self {
71 self.legend_area_size = size;
72 self
73 }
74
75 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 pub fn background_style<S: Into<ShapeStyle<'a>>>(&mut self, style: S) -> &mut Self {
85 self.background = style.into();
86 self
87 }
88
89 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 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}