1use super::{Rect, RenderError};
2
3#[derive(Clone, Debug, Copy, PartialEq, Eq)]
5pub enum PointShape {
6 Circle,
7 Triangle,
8 Square,
9 Diamond,
10 Cross,
11 Plus,
12}
13
14impl PointShape {
15 pub const ALL: &[PointShape] = &[
17 PointShape::Circle,
18 PointShape::Triangle,
19 PointShape::Square,
20 PointShape::Diamond,
21 PointShape::Cross,
22 PointShape::Plus,
23 ];
24}
25
26#[derive(Clone, Debug, Copy, PartialEq, Eq)]
28pub enum Linetype {
29 Solid,
30 Dashed,
31 Dotted,
32 DashDot,
33 LongDash,
34 TwoDash,
35}
36
37impl Linetype {
38 pub const ALL: &[Linetype] = &[
40 Linetype::Solid,
41 Linetype::Dashed,
42 Linetype::Dotted,
43 Linetype::DashDot,
44 Linetype::LongDash,
45 Linetype::TwoDash,
46 ];
47
48 pub fn pattern(&self) -> &[(f64, f64)] {
50 match self {
51 Linetype::Solid => &[],
52 Linetype::Dashed => &[(6.0, 3.0)],
53 Linetype::Dotted => &[(2.0, 2.0)],
54 Linetype::DashDot => &[(6.0, 2.0), (2.0, 2.0)],
55 Linetype::LongDash => &[(10.0, 4.0)],
56 Linetype::TwoDash => &[(8.0, 3.0), (3.0, 3.0)],
57 }
58 }
59}
60
61#[derive(Clone, Debug)]
63pub struct PointStyle {
64 pub color: (u8, u8, u8),
65 pub alpha: f64,
66 pub filled: bool,
67 pub shape: PointShape,
68}
69
70impl Default for PointStyle {
71 fn default() -> Self {
72 PointStyle {
73 color: (0, 0, 0),
74 alpha: 1.0,
75 filled: true,
76 shape: PointShape::Circle,
77 }
78 }
79}
80
81#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
85pub enum LineEnd {
86 #[default]
87 Butt,
88 Round,
89 Square,
90}
91
92#[derive(Clone, Debug)]
94pub struct LineStyle {
95 pub color: (u8, u8, u8),
96 pub alpha: f64,
97 pub width: f64,
98 pub linetype: Linetype,
99 pub lineend: LineEnd,
100}
101
102impl Default for LineStyle {
103 fn default() -> Self {
104 LineStyle {
105 color: (0, 0, 0),
106 alpha: 1.0,
107 width: 1.0,
108 linetype: Linetype::Solid,
109 lineend: LineEnd::Butt,
110 }
111 }
112}
113
114#[derive(Clone, Debug)]
116pub struct RectStyle {
117 pub fill: Option<(u8, u8, u8)>,
118 pub stroke: Option<(u8, u8, u8)>,
119 pub stroke_width: f64,
120 pub alpha: f64,
121 pub clip: bool,
124}
125
126impl Default for RectStyle {
127 fn default() -> Self {
128 RectStyle {
129 fill: Some((128, 128, 128)),
130 stroke: None,
131 stroke_width: 1.0,
132 alpha: 1.0,
133 clip: true,
134 }
135 }
136}
137
138#[derive(Clone, Debug)]
140pub struct TextStyle {
141 pub color: (u8, u8, u8),
142 pub size: f64,
143 pub anchor: TextAnchor,
144 pub angle: f64,
145 pub family: Option<String>,
147 pub face: FontFace,
149}
150
151#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
153pub enum FontFace {
154 #[default]
155 Plain,
156 Bold,
157 Italic,
158}
159
160impl Default for TextStyle {
161 fn default() -> Self {
162 TextStyle {
163 color: (50, 50, 50),
164 size: 12.0,
165 anchor: TextAnchor::Middle,
166 angle: 0.0,
167 family: None,
168 face: crate::render::backend::FontFace::Plain,
169 }
170 }
171}
172
173#[derive(Clone, Debug)]
174pub enum TextAnchor {
175 Start,
176 Middle,
177 End,
178}
179
180pub trait DrawBackend {
182 fn draw_circle(
183 &mut self,
184 center: (f64, f64),
185 radius: f64,
186 style: &PointStyle,
187 ) -> Result<(), RenderError>;
188 fn draw_line(&mut self, points: &[(f64, f64)], style: &LineStyle) -> Result<(), RenderError>;
189 fn draw_rect(
190 &mut self,
191 top_left: (f64, f64),
192 bottom_right: (f64, f64),
193 style: &RectStyle,
194 ) -> Result<(), RenderError>;
195 fn draw_text(
196 &mut self,
197 text: &str,
198 pos: (f64, f64),
199 style: &TextStyle,
200 ) -> Result<(), RenderError>;
201 fn draw_polygon(&mut self, points: &[(f64, f64)], style: &RectStyle)
202 -> Result<(), RenderError>;
203
204 fn draw_text_with_title(
212 &mut self,
213 text: &str,
214 pos: (f64, f64),
215 style: &TextStyle,
216 _title: Option<&str>,
217 ) -> Result<(), RenderError> {
218 self.draw_text(text, pos, style)
219 }
220 fn plot_area(&self) -> Rect;
221 fn total_area(&self) -> Rect;
222
223 fn set_tooltip(&mut self, _tooltip: Option<String>) {}
227
228 fn set_mark_axis(&mut self, _key: Option<String>) {}
232
233 fn set_mark_series(&mut self, _series: Option<String>) {}
237
238 fn set_mark_value(&mut self, _value: Option<String>) {}
241
242 fn set_mark_geom(&mut self, _geom: Option<&str>) {}
248
249 fn warn(&mut self, _message: String) {}
253
254 fn set_mark_part(&mut self, _part: Option<&str>) {}
262
263 fn set_mark_attrs(&mut self, _attrs: Vec<(String, String)>) {}
269
270 fn wants_formatted_values(&self) -> bool {
276 false
277 }
278
279 fn set_mark_fvalue(&mut self, _value: Option<String>) {}
286
287 fn set_mark_fx(&mut self, _value: Option<String>) {}
293
294 fn draw_shape(
296 &mut self,
297 center: (f64, f64),
298 radius: f64,
299 style: &PointStyle,
300 ) -> Result<(), RenderError> {
301 match style.shape {
302 PointShape::Circle => self.draw_circle(center, radius, style),
303 _ => {
304 self.draw_circle(center, radius, style)
306 }
307 }
308 }
309
310 fn draw_quadratic(
316 &mut self,
317 p0: (f64, f64),
318 ctrl: (f64, f64),
319 p1: (f64, f64),
320 style: &LineStyle,
321 ) -> Result<(), RenderError> {
322 if !(p0.0.is_finite()
323 && p0.1.is_finite()
324 && ctrl.0.is_finite()
325 && ctrl.1.is_finite()
326 && p1.0.is_finite()
327 && p1.1.is_finite())
328 {
329 return Ok(());
330 }
331 const STEPS: usize = 32;
332 let mut pts = Vec::with_capacity(STEPS + 1);
333 for i in 0..=STEPS {
334 let t = i as f64 / STEPS as f64;
335 let one_minus_t = 1.0 - t;
336 let x =
337 one_minus_t * one_minus_t * p0.0 + 2.0 * one_minus_t * t * ctrl.0 + t * t * p1.0;
338 let y =
339 one_minus_t * one_minus_t * p0.1 + 2.0 * one_minus_t * t * ctrl.1 + t * t * p1.1;
340 pts.push((x, y));
341 }
342 self.draw_line(&pts, style)
343 }
344}
345
346#[cfg(test)]
347mod tests {
348 use super::*;
349 use crate::render::{Rect, RenderError};
350 use std::cell::RefCell;
351
352 #[derive(Default)]
355 struct RecordingBackend {
356 lines: RefCell<Vec<Vec<(f64, f64)>>>,
357 circles: RefCell<Vec<((f64, f64), f64)>>,
358 }
359
360 impl DrawBackend for RecordingBackend {
361 fn draw_circle(
362 &mut self,
363 center: (f64, f64),
364 radius: f64,
365 _style: &PointStyle,
366 ) -> Result<(), RenderError> {
367 self.circles.borrow_mut().push((center, radius));
368 Ok(())
369 }
370 fn draw_line(
371 &mut self,
372 points: &[(f64, f64)],
373 _style: &LineStyle,
374 ) -> Result<(), RenderError> {
375 self.lines.borrow_mut().push(points.to_vec());
376 Ok(())
377 }
378 fn draw_rect(
379 &mut self,
380 _top_left: (f64, f64),
381 _bottom_right: (f64, f64),
382 _style: &RectStyle,
383 ) -> Result<(), RenderError> {
384 Ok(())
385 }
386 fn draw_text(
387 &mut self,
388 _text: &str,
389 _pos: (f64, f64),
390 _style: &TextStyle,
391 ) -> Result<(), RenderError> {
392 Ok(())
393 }
394 fn draw_polygon(
395 &mut self,
396 _points: &[(f64, f64)],
397 _style: &RectStyle,
398 ) -> Result<(), RenderError> {
399 Ok(())
400 }
401 fn plot_area(&self) -> Rect {
402 Rect {
403 x: 0.0,
404 y: 0.0,
405 width: 100.0,
406 height: 100.0,
407 }
408 }
409 fn total_area(&self) -> Rect {
410 self.plot_area()
411 }
412 }
413
414 #[test]
415 fn default_draw_quadratic_flattens_to_33_points() {
416 let mut b = RecordingBackend::default();
417 b.draw_quadratic((0.0, 0.0), (5.0, 10.0), (10.0, 0.0), &LineStyle::default())
418 .unwrap();
419 let lines = b.lines.borrow();
420 assert_eq!(lines.len(), 1);
421 assert_eq!(lines[0].len(), 33);
422 assert_eq!(lines[0][0], (0.0, 0.0));
423 assert_eq!(lines[0][32], (10.0, 0.0));
424 let mid = lines[0][16];
427 assert!((mid.0 - 5.0).abs() < 1e-9);
428 assert!((mid.1 - 5.0).abs() < 1e-9);
429 }
430
431 #[test]
432 fn default_draw_quadratic_drops_non_finite() {
433 let mut b = RecordingBackend::default();
434 b.draw_quadratic(
435 (f64::NAN, 0.0),
436 (5.0, 10.0),
437 (10.0, 0.0),
438 &LineStyle::default(),
439 )
440 .unwrap();
441 assert!(b.lines.borrow().is_empty());
442 }
443
444 #[test]
449 fn default_draw_shape_falls_back_to_draw_circle_for_every_point_shape() {
450 let mut b = RecordingBackend::default();
451 for shape in PointShape::ALL {
452 b.draw_shape(
453 (1.0, 2.0),
454 3.0,
455 &PointStyle {
456 shape: *shape,
457 ..Default::default()
458 },
459 )
460 .unwrap();
461 }
462 assert_eq!(b.circles.borrow().len(), PointShape::ALL.len());
463 for (center, radius) in b.circles.borrow().iter() {
464 assert_eq!(*center, (1.0, 2.0));
465 assert_eq!(*radius, 3.0);
466 }
467 }
468
469 #[test]
470 fn recording_backend_areas_and_remaining_draw_methods_behave_as_declared() {
471 let mut b = RecordingBackend::default();
472 let pa = b.plot_area();
473 let ta = b.total_area();
474 assert_eq!(
475 (pa.x, pa.y, pa.width, pa.height),
476 (ta.x, ta.y, ta.width, ta.height)
477 );
478 assert_eq!((pa.x, pa.y, pa.width, pa.height), (0.0, 0.0, 100.0, 100.0));
479 b.draw_rect((0.0, 0.0), (1.0, 1.0), &RectStyle::default())
480 .unwrap();
481 b.draw_text("label", (0.0, 0.0), &TextStyle::default())
482 .unwrap();
483 b.draw_polygon(&[(0.0, 0.0), (1.0, 0.0), (0.0, 1.0)], &RectStyle::default())
484 .unwrap();
485 }
486}