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 warn(&mut self, _message: String) {}
229
230 fn set_mark_part(&mut self, _part: Option<&str>) {}
238
239 fn set_mark_attrs(&mut self, _attrs: Vec<(String, String)>) {}
245
246 fn draw_shape(
248 &mut self,
249 center: (f64, f64),
250 radius: f64,
251 style: &PointStyle,
252 ) -> Result<(), RenderError> {
253 match style.shape {
254 PointShape::Circle => self.draw_circle(center, radius, style),
255 _ => {
256 self.draw_circle(center, radius, style)
258 }
259 }
260 }
261
262 fn draw_quadratic(
268 &mut self,
269 p0: (f64, f64),
270 ctrl: (f64, f64),
271 p1: (f64, f64),
272 style: &LineStyle,
273 ) -> Result<(), RenderError> {
274 if !(p0.0.is_finite()
275 && p0.1.is_finite()
276 && ctrl.0.is_finite()
277 && ctrl.1.is_finite()
278 && p1.0.is_finite()
279 && p1.1.is_finite())
280 {
281 return Ok(());
282 }
283 const STEPS: usize = 32;
284 let mut pts = Vec::with_capacity(STEPS + 1);
285 for i in 0..=STEPS {
286 let t = i as f64 / STEPS as f64;
287 let one_minus_t = 1.0 - t;
288 let x =
289 one_minus_t * one_minus_t * p0.0 + 2.0 * one_minus_t * t * ctrl.0 + t * t * p1.0;
290 let y =
291 one_minus_t * one_minus_t * p0.1 + 2.0 * one_minus_t * t * ctrl.1 + t * t * p1.1;
292 pts.push((x, y));
293 }
294 self.draw_line(&pts, style)
295 }
296}
297
298#[cfg(test)]
299mod tests {
300 use super::*;
301 use crate::render::{Rect, RenderError};
302 use std::cell::RefCell;
303
304 #[derive(Default)]
307 struct RecordingBackend {
308 lines: RefCell<Vec<Vec<(f64, f64)>>>,
309 circles: RefCell<Vec<((f64, f64), f64)>>,
310 }
311
312 impl DrawBackend for RecordingBackend {
313 fn draw_circle(
314 &mut self,
315 center: (f64, f64),
316 radius: f64,
317 _style: &PointStyle,
318 ) -> Result<(), RenderError> {
319 self.circles.borrow_mut().push((center, radius));
320 Ok(())
321 }
322 fn draw_line(
323 &mut self,
324 points: &[(f64, f64)],
325 _style: &LineStyle,
326 ) -> Result<(), RenderError> {
327 self.lines.borrow_mut().push(points.to_vec());
328 Ok(())
329 }
330 fn draw_rect(
331 &mut self,
332 _top_left: (f64, f64),
333 _bottom_right: (f64, f64),
334 _style: &RectStyle,
335 ) -> Result<(), RenderError> {
336 Ok(())
337 }
338 fn draw_text(
339 &mut self,
340 _text: &str,
341 _pos: (f64, f64),
342 _style: &TextStyle,
343 ) -> Result<(), RenderError> {
344 Ok(())
345 }
346 fn draw_polygon(
347 &mut self,
348 _points: &[(f64, f64)],
349 _style: &RectStyle,
350 ) -> Result<(), RenderError> {
351 Ok(())
352 }
353 fn plot_area(&self) -> Rect {
354 Rect {
355 x: 0.0,
356 y: 0.0,
357 width: 100.0,
358 height: 100.0,
359 }
360 }
361 fn total_area(&self) -> Rect {
362 self.plot_area()
363 }
364 }
365
366 #[test]
367 fn default_draw_quadratic_flattens_to_33_points() {
368 let mut b = RecordingBackend::default();
369 b.draw_quadratic((0.0, 0.0), (5.0, 10.0), (10.0, 0.0), &LineStyle::default())
370 .unwrap();
371 let lines = b.lines.borrow();
372 assert_eq!(lines.len(), 1);
373 assert_eq!(lines[0].len(), 33);
374 assert_eq!(lines[0][0], (0.0, 0.0));
375 assert_eq!(lines[0][32], (10.0, 0.0));
376 let mid = lines[0][16];
379 assert!((mid.0 - 5.0).abs() < 1e-9);
380 assert!((mid.1 - 5.0).abs() < 1e-9);
381 }
382
383 #[test]
384 fn default_draw_quadratic_drops_non_finite() {
385 let mut b = RecordingBackend::default();
386 b.draw_quadratic(
387 (f64::NAN, 0.0),
388 (5.0, 10.0),
389 (10.0, 0.0),
390 &LineStyle::default(),
391 )
392 .unwrap();
393 assert!(b.lines.borrow().is_empty());
394 }
395
396 #[test]
401 fn default_draw_shape_falls_back_to_draw_circle_for_every_point_shape() {
402 let mut b = RecordingBackend::default();
403 for shape in PointShape::ALL {
404 b.draw_shape(
405 (1.0, 2.0),
406 3.0,
407 &PointStyle {
408 shape: *shape,
409 ..Default::default()
410 },
411 )
412 .unwrap();
413 }
414 assert_eq!(b.circles.borrow().len(), PointShape::ALL.len());
415 for (center, radius) in b.circles.borrow().iter() {
416 assert_eq!(*center, (1.0, 2.0));
417 assert_eq!(*radius, 3.0);
418 }
419 }
420
421 #[test]
422 fn recording_backend_areas_and_remaining_draw_methods_behave_as_declared() {
423 let mut b = RecordingBackend::default();
424 let pa = b.plot_area();
425 let ta = b.total_area();
426 assert_eq!(
427 (pa.x, pa.y, pa.width, pa.height),
428 (ta.x, ta.y, ta.width, ta.height)
429 );
430 assert_eq!((pa.x, pa.y, pa.width, pa.height), (0.0, 0.0, 100.0, 100.0));
431 b.draw_rect((0.0, 0.0), (1.0, 1.0), &RectStyle::default())
432 .unwrap();
433 b.draw_text("label", (0.0, 0.0), &TextStyle::default())
434 .unwrap();
435 b.draw_polygon(&[(0.0, 0.0), (1.0, 0.0), (0.0, 1.0)], &RectStyle::default())
436 .unwrap();
437 }
438}