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 fn plot_area(&self) -> Rect;
204 fn total_area(&self) -> Rect;
205
206 fn set_tooltip(&mut self, _tooltip: Option<String>) {}
210
211 fn set_mark_axis(&mut self, _key: Option<String>) {}
215
216 fn set_mark_series(&mut self, _series: Option<String>) {}
220
221 fn set_mark_value(&mut self, _value: Option<String>) {}
224
225 fn set_mark_geom(&mut self, _geom: Option<&str>) {}
231
232 fn warn(&mut self, _message: String) {}
236
237 fn set_mark_part(&mut self, _part: Option<&str>) {}
245
246 fn set_mark_attrs(&mut self, _attrs: Vec<(String, String)>) {}
252
253 fn wants_formatted_values(&self) -> bool {
259 false
260 }
261
262 fn set_mark_fvalue(&mut self, _value: Option<String>) {}
269
270 fn set_mark_fx(&mut self, _value: Option<String>) {}
276
277 fn draw_shape(
279 &mut self,
280 center: (f64, f64),
281 radius: f64,
282 style: &PointStyle,
283 ) -> Result<(), RenderError> {
284 match style.shape {
285 PointShape::Circle => self.draw_circle(center, radius, style),
286 _ => {
287 self.draw_circle(center, radius, style)
289 }
290 }
291 }
292
293 fn draw_quadratic(
299 &mut self,
300 p0: (f64, f64),
301 ctrl: (f64, f64),
302 p1: (f64, f64),
303 style: &LineStyle,
304 ) -> Result<(), RenderError> {
305 if !(p0.0.is_finite()
306 && p0.1.is_finite()
307 && ctrl.0.is_finite()
308 && ctrl.1.is_finite()
309 && p1.0.is_finite()
310 && p1.1.is_finite())
311 {
312 return Ok(());
313 }
314 const STEPS: usize = 32;
315 let mut pts = Vec::with_capacity(STEPS + 1);
316 for i in 0..=STEPS {
317 let t = i as f64 / STEPS as f64;
318 let one_minus_t = 1.0 - t;
319 let x =
320 one_minus_t * one_minus_t * p0.0 + 2.0 * one_minus_t * t * ctrl.0 + t * t * p1.0;
321 let y =
322 one_minus_t * one_minus_t * p0.1 + 2.0 * one_minus_t * t * ctrl.1 + t * t * p1.1;
323 pts.push((x, y));
324 }
325 self.draw_line(&pts, style)
326 }
327}
328
329#[cfg(test)]
330mod tests {
331 use super::*;
332 use crate::render::{Rect, RenderError};
333 use std::cell::RefCell;
334
335 #[derive(Default)]
338 struct RecordingBackend {
339 lines: RefCell<Vec<Vec<(f64, f64)>>>,
340 circles: RefCell<Vec<((f64, f64), f64)>>,
341 }
342
343 impl DrawBackend for RecordingBackend {
344 fn draw_circle(
345 &mut self,
346 center: (f64, f64),
347 radius: f64,
348 _style: &PointStyle,
349 ) -> Result<(), RenderError> {
350 self.circles.borrow_mut().push((center, radius));
351 Ok(())
352 }
353 fn draw_line(
354 &mut self,
355 points: &[(f64, f64)],
356 _style: &LineStyle,
357 ) -> Result<(), RenderError> {
358 self.lines.borrow_mut().push(points.to_vec());
359 Ok(())
360 }
361 fn draw_rect(
362 &mut self,
363 _top_left: (f64, f64),
364 _bottom_right: (f64, f64),
365 _style: &RectStyle,
366 ) -> Result<(), RenderError> {
367 Ok(())
368 }
369 fn draw_text(
370 &mut self,
371 _text: &str,
372 _pos: (f64, f64),
373 _style: &TextStyle,
374 ) -> Result<(), RenderError> {
375 Ok(())
376 }
377 fn draw_polygon(
378 &mut self,
379 _points: &[(f64, f64)],
380 _style: &RectStyle,
381 ) -> Result<(), RenderError> {
382 Ok(())
383 }
384 fn plot_area(&self) -> Rect {
385 Rect {
386 x: 0.0,
387 y: 0.0,
388 width: 100.0,
389 height: 100.0,
390 }
391 }
392 fn total_area(&self) -> Rect {
393 self.plot_area()
394 }
395 }
396
397 #[test]
398 fn default_draw_quadratic_flattens_to_33_points() {
399 let mut b = RecordingBackend::default();
400 b.draw_quadratic((0.0, 0.0), (5.0, 10.0), (10.0, 0.0), &LineStyle::default())
401 .unwrap();
402 let lines = b.lines.borrow();
403 assert_eq!(lines.len(), 1);
404 assert_eq!(lines[0].len(), 33);
405 assert_eq!(lines[0][0], (0.0, 0.0));
406 assert_eq!(lines[0][32], (10.0, 0.0));
407 let mid = lines[0][16];
410 assert!((mid.0 - 5.0).abs() < 1e-9);
411 assert!((mid.1 - 5.0).abs() < 1e-9);
412 }
413
414 #[test]
415 fn default_draw_quadratic_drops_non_finite() {
416 let mut b = RecordingBackend::default();
417 b.draw_quadratic(
418 (f64::NAN, 0.0),
419 (5.0, 10.0),
420 (10.0, 0.0),
421 &LineStyle::default(),
422 )
423 .unwrap();
424 assert!(b.lines.borrow().is_empty());
425 }
426
427 #[test]
432 fn default_draw_shape_falls_back_to_draw_circle_for_every_point_shape() {
433 let mut b = RecordingBackend::default();
434 for shape in PointShape::ALL {
435 b.draw_shape(
436 (1.0, 2.0),
437 3.0,
438 &PointStyle {
439 shape: *shape,
440 ..Default::default()
441 },
442 )
443 .unwrap();
444 }
445 assert_eq!(b.circles.borrow().len(), PointShape::ALL.len());
446 for (center, radius) in b.circles.borrow().iter() {
447 assert_eq!(*center, (1.0, 2.0));
448 assert_eq!(*radius, 3.0);
449 }
450 }
451
452 #[test]
453 fn recording_backend_areas_and_remaining_draw_methods_behave_as_declared() {
454 let mut b = RecordingBackend::default();
455 let pa = b.plot_area();
456 let ta = b.total_area();
457 assert_eq!(
458 (pa.x, pa.y, pa.width, pa.height),
459 (ta.x, ta.y, ta.width, ta.height)
460 );
461 assert_eq!((pa.x, pa.y, pa.width, pa.height), (0.0, 0.0, 100.0, 100.0));
462 b.draw_rect((0.0, 0.0), (1.0, 1.0), &RectStyle::default())
463 .unwrap();
464 b.draw_text("label", (0.0, 0.0), &TextStyle::default())
465 .unwrap();
466 b.draw_polygon(&[(0.0, 0.0), (1.0, 0.0), (0.0, 1.0)], &RectStyle::default())
467 .unwrap();
468 }
469}