1use std::borrow::Borrow;
2use std::fmt::Debug;
3use std::marker::PhantomData;
4use std::ops::Range;
5
6use super::mesh::MeshStyle;
7use super::series::SeriesLabelStyle;
8
9use crate::coord::{CoordTranslate, MeshLine, Ranged, RangedCoord, ReverseCoordTranslate, Shift};
10use crate::drawing::backend::{BackendCoord, DrawingBackend};
11use crate::drawing::{DrawingArea, DrawingAreaErrorKind};
12use crate::element::{Drawable, DynElement, IntoDynElement, Path, PointCollection};
13use crate::style::{FontTransform, ShapeStyle, TextStyle};
14
15pub struct SeriesAnno<DB: DrawingBackend> {
17 label: Option<String>,
18 draw_func: Option<Box<dyn Fn(BackendCoord) -> DynElement<DB, BackendCoord>>>,
19 phantom_data: PhantomData<DB>,
20}
21
22impl<DB: DrawingBackend> SeriesAnno<DB> {
23 pub(crate) fn get_label(&self) -> &str {
24 self.label.as_ref().map(|x| x.as_str()).unwrap_or("")
25 }
26
27 pub(crate) fn get_draw_func(
28 &self,
29 ) -> Option<&dyn Fn(BackendCoord) -> DynElement<DB, BackendCoord>> {
30 self.draw_func.as_ref().map(|x| x.borrow())
31 }
32
33 fn new() -> Self {
34 Self {
35 label: None,
36 draw_func: None,
37 phantom_data: PhantomData,
38 }
39 }
40
41 pub fn label<L: Into<String>>(&mut self, label: L) -> &mut Self {
44 self.label = Some(label.into());
45 self
46 }
47
48 pub fn legend<E: IntoDynElement<DB, BackendCoord>, T: Fn(BackendCoord) -> E + 'static>(
53 &mut self,
54 func: T,
55 ) -> &mut Self {
56 self.draw_func = Some(Box::new(move |p| func(p).into_dyn()));
57 self
58 }
59}
60
61pub struct ChartContext<DB: DrawingBackend, CT: CoordTranslate> {
65 pub(super) x_label_area: Option<DrawingArea<DB, Shift>>,
66 pub(super) y_label_area: Option<DrawingArea<DB, Shift>>,
67 pub(super) drawing_area: DrawingArea<DB, CT>,
68 pub(super) series_anno: Vec<SeriesAnno<DB>>,
69}
70
71impl<
72 DB: DrawingBackend,
73 XT: Debug,
74 YT: Debug,
75 X: Ranged<ValueType = XT>,
76 Y: Ranged<ValueType = YT>,
77 > ChartContext<DB, RangedCoord<X, Y>>
78{
79 pub fn configure_mesh(&mut self) -> MeshStyle<X, Y, DB> {
82 MeshStyle {
83 axis_style: None,
84 x_label_offset: 0,
85 draw_x_mesh: true,
86 draw_y_mesh: true,
87 draw_x_axis: true,
88 draw_y_axis: true,
89 n_x_labels: 10,
90 n_y_labels: 10,
91 line_style_1: None,
92 line_style_2: None,
93 label_style: None,
94 format_x: &|x| format!("{:?}", x),
95 format_y: &|y| format!("{:?}", y),
96 target: Some(self),
97 _pahtom_data: PhantomData,
98 x_desc: None,
99 y_desc: None,
100 axis_desc_style: None,
101 }
102 }
103}
104
105impl<DB: DrawingBackend, CT: CoordTranslate> ChartContext<DB, CT> {
106 pub fn configure_series_labels(&mut self) -> SeriesLabelStyle<DB, CT> {
108 SeriesLabelStyle::new(self)
109 }
110
111 pub fn plotting_area(&self) -> &DrawingArea<DB, CT> {
113 &self.drawing_area
114 }
115}
116
117impl<DB: DrawingBackend, CT: ReverseCoordTranslate> ChartContext<DB, CT> {
118 pub fn into_coord_trans(self) -> impl Fn(BackendCoord) -> Option<CT::From> {
120 let coord_spec = self.drawing_area.into_coord_spec();
121 move |coord| coord_spec.reverse_translate(coord)
122 }
123}
124
125impl<DB: DrawingBackend, X: Ranged, Y: Ranged> ChartContext<DB, RangedCoord<X, Y>> {
126 pub fn x_range(&self) -> Range<X::ValueType> {
128 self.drawing_area.get_x_range()
129 }
130
131 pub fn y_range(&self) -> Range<Y::ValueType> {
133 self.drawing_area.get_y_range()
134 }
135
136 pub fn backend_coord(&self, coord: &(X::ValueType, Y::ValueType)) -> BackendCoord {
139 self.drawing_area.map_coordinate(coord)
140 }
141
142 pub fn draw_series<E, R, S>(
144 &mut self,
145 series: S,
146 ) -> Result<&mut SeriesAnno<DB>, DrawingAreaErrorKind<DB::ErrorType>>
147 where
148 for<'a> &'a E: PointCollection<'a, (X::ValueType, Y::ValueType)>,
149 E: Drawable<DB>,
150 R: Borrow<E>,
151 S: IntoIterator<Item = R>,
152 {
153 for element in series {
154 self.drawing_area.draw(element.borrow())?;
155 }
156
157 let idx = self.series_anno.len();
158
159 self.series_anno.push(SeriesAnno::new());
160
161 Ok(&mut self.series_anno[idx])
162 }
163
164 #[allow(clippy::too_many_arguments)]
165 pub(super) fn draw_mesh<FmtLabel>(
166 &mut self,
167 (r, c): (usize, usize),
168 mesh_line_style: &ShapeStyle,
169 label_style: &TextStyle,
170 mut fmt_label: FmtLabel,
171 x_mesh: bool,
172 y_mesh: bool,
173 x_label_offset: i32,
174 x_axis: bool,
175 y_axis: bool,
176 axis_style: &ShapeStyle,
177 axis_desc_style: &TextStyle,
178 x_desc: Option<String>,
179 y_desc: Option<String>,
180 ) -> Result<(), DrawingAreaErrorKind<DB::ErrorType>>
181 where
182 FmtLabel: FnMut(&MeshLine<X, Y>) -> Option<String>,
183 {
184 let mut x_labels = vec![];
185 let mut y_labels = vec![];
186 self.drawing_area.draw_mesh(
187 |b, l| {
188 let draw;
189 match l {
190 MeshLine::XMesh((x, _), _, _) => {
191 if let Some(label_text) = fmt_label(&l) {
192 x_labels.push((x, label_text));
193 }
194 draw = x_mesh;
195 }
196 MeshLine::YMesh((_, y), _, _) => {
197 if let Some(label_text) = fmt_label(&l) {
198 y_labels.push((y, label_text));
199 }
200 draw = y_mesh;
201 }
202 };
203 if draw {
204 l.draw(b, mesh_line_style)
205 } else {
206 Ok(())
207 }
208 },
209 r,
210 c,
211 )?;
212
213 let (x0, y0) = self.drawing_area.get_base_pixel();
214
215 if let Some(ref xl) = self.x_label_area {
216 let (tw, th) = xl.dim_in_pixel();
217 if x_axis {
218 xl.draw(&Path::new(vec![(0, 0), (tw as i32, 0)], axis_style.clone()))?;
219 }
220 for (p, t) in x_labels {
221 let (w, _) = label_style.font.box_size(&t).unwrap_or((0, 0));
222
223 if p - x0 + x_label_offset > 0 && p - x0 + x_label_offset + w as i32 / 2 < tw as i32
224 {
225 if x_axis {
226 xl.draw(&Path::new(
227 vec![(p - x0, 0), (p - x0, 5)],
228 axis_style.clone(),
229 ))?;
230 }
231 xl.draw_text(
232 &t,
233 label_style,
234 (p - x0 - w as i32 / 2 + x_label_offset, 10),
235 )?;
236 }
237 }
238
239 if let Some(ref text) = x_desc {
240 let (w, h) = label_style.font.box_size(text).unwrap_or((0, 0));
241
242 let left = (tw - w) / 2;
243 let top = th - h;
244
245 xl.draw_text(&text, axis_desc_style, (left as i32, top as i32))?;
246 }
247 }
248
249 if let Some(ref yl) = self.y_label_area {
250 let (tw, th) = yl.dim_in_pixel();
251 if y_axis {
252 yl.draw(&Path::new(
253 vec![(tw as i32, 0), (tw as i32, th as i32)],
254 axis_style.clone(),
255 ))?;
256 }
257 for (p, t) in y_labels {
258 let (w, h) = label_style.font.box_size(&t).unwrap_or((0, 0));
259 if p - y0 >= 0 && p - y0 - h as i32 / 2 <= th as i32 {
260 yl.draw_text(
261 &t,
262 label_style,
263 (tw as i32 - w as i32 - 10, p - y0 - h as i32 / 2),
264 )?;
265 if y_axis {
266 yl.draw(&Path::new(
267 vec![(tw as i32 - 5, p - y0), (tw as i32, p - y0)],
268 axis_style.clone(),
269 ))?;
270 }
271 }
272 }
273
274 if let Some(ref text) = y_desc {
275 let (w, _) = label_style.font.box_size(text).unwrap_or((0, 0));
276
277 let top = (th - w) / 2;
278
279 let mut y_style = axis_desc_style.clone();
280 let y_font = axis_desc_style.font.transform(FontTransform::Rotate270);
281 y_style.font = &y_font;
282
283 yl.draw_text(&text, &y_style, (0, top as i32))?;
284 }
285 }
286
287 Ok(())
288 }
289}