1use std::collections::HashSet;
2use std::sync::{Mutex, OnceLock};
3
4use plotters::prelude::*;
5use plotters::style::FontStyle;
6
7use super::backend::{
8 DrawBackend, LineStyle, PointShape, PointStyle, RectStyle, TextAnchor, TextStyle,
9};
10use super::{Rect, RenderError};
11
12const BUNDLED_FONT: &[u8] = include_bytes!("../../assets/fonts/DejaVuSans.ttf");
16
17fn registered_families() -> &'static Mutex<HashSet<String>> {
18 static REGISTERED: OnceLock<Mutex<HashSet<String>>> = OnceLock::new();
19 REGISTERED.get_or_init(|| Mutex::new(HashSet::new()))
20}
21
22fn ensure_font(family: &str) {
29 let mut set = registered_families().lock().unwrap();
30 if set.insert(family.to_string()) {
31 for style in [
32 FontStyle::Normal,
33 FontStyle::Bold,
34 FontStyle::Italic,
35 FontStyle::Oblique,
36 ] {
37 let _ = plotters::style::register_font(family, style, BUNDLED_FONT);
39 }
40 }
41}
42
43pub struct PlottersAdapter<'a, DB: DrawingBackend> {
45 area: &'a DrawingArea<DB, plotters::coord::Shift>,
46 plot_area: Rect,
47 total_area: Rect,
48}
49
50impl<'a, DB: DrawingBackend> PlottersAdapter<'a, DB> {
51 pub fn new(area: &'a DrawingArea<DB, plotters::coord::Shift>, plot_area: Rect) -> Self {
52 let (w, h) = area.dim_in_pixel();
53 PlottersAdapter {
54 area,
55 plot_area,
56 total_area: Rect {
57 x: 0.0,
58 y: 0.0,
59 width: w as f64,
60 height: h as f64,
61 },
62 }
63 }
64}
65
66fn to_rgba(color: (u8, u8, u8), alpha: f64) -> RGBAColor {
67 RGBAColor(color.0, color.1, color.2, alpha)
68}
69
70fn clip_point(x: f64, y: f64, rect: &Rect) -> (f64, f64) {
72 (
73 x.clamp(rect.x, rect.x + rect.width),
74 y.clamp(rect.y, rect.y + rect.height),
75 )
76}
77
78fn point_in_rect(x: f64, y: f64, rect: &Rect) -> bool {
80 let margin = 2.0;
81 x >= rect.x - margin
82 && x <= rect.x + rect.width + margin
83 && y >= rect.y - margin
84 && y <= rect.y + rect.height + margin
85}
86
87fn clip_line_segment(
89 mut x0: f64,
90 mut y0: f64,
91 mut x1: f64,
92 mut y1: f64,
93 rect: &Rect,
94) -> Option<((f64, f64), (f64, f64))> {
95 let xmin = rect.x;
96 let xmax = rect.x + rect.width;
97 let ymin = rect.y;
98 let ymax = rect.y + rect.height;
99
100 const INSIDE: u8 = 0;
101 const LEFT: u8 = 1;
102 const RIGHT: u8 = 2;
103 const BOTTOM: u8 = 4;
104 const TOP: u8 = 8;
105
106 let outcode = |x: f64, y: f64| -> u8 {
107 let mut code = INSIDE;
108 if x < xmin {
109 code |= LEFT;
110 } else if x > xmax {
111 code |= RIGHT;
112 }
113 if y < ymin {
114 code |= TOP;
115 } else if y > ymax {
116 code |= BOTTOM;
117 }
118 code
119 };
120
121 let mut code0 = outcode(x0, y0);
122 let mut code1 = outcode(x1, y1);
123
124 for _ in 0..20 {
125 if (code0 | code1) == 0 {
126 return Some(((x0, y0), (x1, y1)));
127 }
128 if (code0 & code1) != 0 {
129 return None;
130 }
131
132 let code_out = if code0 != 0 { code0 } else { code1 };
133 let (x, y);
134
135 if code_out & TOP != 0 {
136 x = x0 + (x1 - x0) * (ymin - y0) / (y1 - y0);
137 y = ymin;
138 } else if code_out & BOTTOM != 0 {
139 x = x0 + (x1 - x0) * (ymax - y0) / (y1 - y0);
140 y = ymax;
141 } else if code_out & RIGHT != 0 {
142 y = y0 + (y1 - y0) * (xmax - x0) / (x1 - x0);
143 x = xmax;
144 } else {
145 y = y0 + (y1 - y0) * (xmin - x0) / (x1 - x0);
146 x = xmin;
147 }
148
149 if code_out == code0 {
150 x0 = x;
151 y0 = y;
152 code0 = outcode(x0, y0);
153 } else {
154 x1 = x;
155 y1 = y;
156 code1 = outcode(x1, y1);
157 }
158 }
159
160 None
161}
162
163fn map_err<E: std::fmt::Debug>(e: E) -> RenderError {
164 RenderError::BackendError(format!("{:?}", e))
165}
166
167fn segment_dashed(points: &[(f64, f64)], pattern: &[(f64, f64)]) -> Vec<Vec<(f64, f64)>> {
169 if pattern.is_empty() || points.len() < 2 {
170 return vec![points.to_vec()];
171 }
172
173 let mut segments: Vec<Vec<(f64, f64)>> = Vec::new();
174 let mut current_seg: Vec<(f64, f64)> = Vec::new();
175 let mut drawing = true;
176 let mut pat_idx = 0;
177 let mut remaining_in_pat = pattern[0].0; for window in points.windows(2) {
180 let (x0, y0) = window[0];
181 let (x1, y1) = window[1];
182 let dx = x1 - x0;
183 let dy = y1 - y0;
184 let seg_len = (dx * dx + dy * dy).sqrt();
185 if seg_len < 0.001 {
186 continue;
187 }
188 let ux = dx / seg_len;
189 let uy = dy / seg_len;
190 let mut consumed = 0.0;
191
192 while consumed < seg_len - 0.001 {
193 let available = seg_len - consumed;
194 let step = remaining_in_pat.min(available);
195 let px = x0 + ux * (consumed + step);
196 let py = y0 + uy * (consumed + step);
197
198 if drawing {
199 if current_seg.is_empty() {
200 current_seg.push((x0 + ux * consumed, y0 + uy * consumed));
201 }
202 current_seg.push((px, py));
203 }
204
205 consumed += step;
206 remaining_in_pat -= step;
207
208 if remaining_in_pat < 0.001 {
209 if drawing {
210 if current_seg.len() >= 2 {
211 segments.push(std::mem::take(&mut current_seg));
212 } else {
213 current_seg.clear();
214 }
215 drawing = false;
216 remaining_in_pat = pattern[pat_idx].1; } else {
218 drawing = true;
219 pat_idx = (pat_idx + 1) % pattern.len();
220 remaining_in_pat = pattern[pat_idx].0; }
222 }
223 }
224 }
225
226 if drawing && current_seg.len() >= 2 {
227 segments.push(current_seg);
228 }
229
230 segments
231}
232
233impl<'a, DB: DrawingBackend> DrawBackend for PlottersAdapter<'a, DB> {
234 fn draw_circle(
235 &mut self,
236 center: (f64, f64),
237 radius: f64,
238 style: &PointStyle,
239 ) -> Result<(), RenderError> {
240 if !point_in_rect(center.0, center.1, &self.plot_area) {
242 return Ok(());
243 }
244 let color = to_rgba(style.color, style.alpha);
245 if style.filled {
246 self.area
247 .draw(&Circle::new(
248 (center.0 as i32, center.1 as i32),
249 radius as i32,
250 color.filled(),
251 ))
252 .map_err(map_err)?;
253 } else {
254 self.area
255 .draw(&Circle::new(
256 (center.0 as i32, center.1 as i32),
257 radius as i32,
258 color.stroke_width(1),
259 ))
260 .map_err(map_err)?;
261 }
262 Ok(())
263 }
264
265 fn draw_line(&mut self, points: &[(f64, f64)], style: &LineStyle) -> Result<(), RenderError> {
266 if points.len() < 2 {
267 return Ok(());
268 }
269 let (pixel_width, alpha) = if style.width >= 1.0 {
271 (style.width as u32, style.alpha)
272 } else if style.width > 0.0 {
273 (1, style.alpha * style.width)
274 } else {
275 (0, style.alpha)
276 };
277 let color = to_rgba(style.color, alpha);
278 let stroke = color.stroke_width(pixel_width);
279
280 let pattern = style.linetype.pattern();
281 let sub_paths = segment_dashed(points, pattern);
282
283 for path in &sub_paths {
284 for window in path.windows(2) {
285 if let Some((p1, p2)) = clip_line_segment(
287 window[0].0,
288 window[0].1,
289 window[1].0,
290 window[1].1,
291 &self.plot_area,
292 ) {
293 self.area
294 .draw(&PathElement::new(
295 vec![(p1.0 as i32, p1.1 as i32), (p2.0 as i32, p2.1 as i32)],
296 stroke,
297 ))
298 .map_err(map_err)?;
299 }
300 }
301 }
302 Ok(())
303 }
304
305 fn draw_rect(
306 &mut self,
307 top_left: (f64, f64),
308 bottom_right: (f64, f64),
309 style: &RectStyle,
310 ) -> Result<(), RenderError> {
311 let (tl, br) = if style.clip {
312 let clamped_tl = clip_point(top_left.0, top_left.1, &self.plot_area);
314 let clamped_br = clip_point(bottom_right.0, bottom_right.1, &self.plot_area);
315
316 if (clamped_tl.0 - clamped_br.0).abs() < 0.5
318 && (clamped_tl.1 - clamped_br.1).abs() < 0.5
319 {
320 if (top_left.0 - bottom_right.0).abs() > 1.0
322 || (top_left.1 - bottom_right.1).abs() > 1.0
323 {
324 return Ok(());
325 }
326 }
327
328 (
329 (clamped_tl.0 as i32, clamped_tl.1 as i32),
330 (clamped_br.0 as i32, clamped_br.1 as i32),
331 )
332 } else {
333 (
335 (top_left.0 as i32, top_left.1 as i32),
336 (bottom_right.0 as i32, bottom_right.1 as i32),
337 )
338 };
339
340 if let Some(fill) = style.fill {
341 let fill_color = to_rgba(fill, style.alpha);
342 self.area
343 .draw(&plotters::prelude::Rectangle::new(
344 [tl, br],
345 fill_color.filled(),
346 ))
347 .map_err(map_err)?;
348 }
349
350 if let Some(stroke) = style.stroke {
351 let stroke_color = to_rgba(stroke, style.alpha);
352 self.area
353 .draw(&plotters::prelude::Rectangle::new(
354 [tl, br],
355 stroke_color.stroke_width(if style.stroke_width > 0.0 {
356 (style.stroke_width as u32).max(1)
357 } else {
358 0
359 }),
360 ))
361 .map_err(map_err)?;
362 }
363
364 Ok(())
365 }
366
367 fn draw_text(
368 &mut self,
369 text: &str,
370 pos: (f64, f64),
371 style: &TextStyle,
372 ) -> Result<(), RenderError> {
373 let color = to_rgba(style.color, 1.0);
374 let family = style.family.as_deref().unwrap_or("sans-serif");
375 ensure_font(family);
376 let font = (family, style.size).into_font();
377
378 let pos_adj = match style.anchor {
379 TextAnchor::Start => plotters::style::text_anchor::Pos::new(
380 plotters::style::text_anchor::HPos::Left,
381 plotters::style::text_anchor::VPos::Center,
382 ),
383 TextAnchor::Middle => plotters::style::text_anchor::Pos::new(
384 plotters::style::text_anchor::HPos::Center,
385 plotters::style::text_anchor::VPos::Center,
386 ),
387 TextAnchor::End => plotters::style::text_anchor::Pos::new(
388 plotters::style::text_anchor::HPos::Right,
389 plotters::style::text_anchor::VPos::Center,
390 ),
391 };
392
393 let angle_i = style.angle.round() as i32;
394 if angle_i != 0 {
395 let (font_transform, rotated_pos) = match angle_i.rem_euclid(360) {
396 80..=100 => (
397 FontTransform::Rotate90,
398 plotters::style::text_anchor::Pos::new(
399 plotters::style::text_anchor::HPos::Center,
400 plotters::style::text_anchor::VPos::Center,
401 ),
402 ),
403 170..=190 => (
404 FontTransform::Rotate180,
405 plotters::style::text_anchor::Pos::new(
406 plotters::style::text_anchor::HPos::Center,
407 plotters::style::text_anchor::VPos::Center,
408 ),
409 ),
410 _ => (
412 FontTransform::Rotate270,
413 plotters::style::text_anchor::Pos::new(
414 plotters::style::text_anchor::HPos::Right,
415 plotters::style::text_anchor::VPos::Center,
416 ),
417 ),
418 };
419
420 let text_style = plotters::prelude::TextStyle::from((family, style.size).into_font())
421 .color(&color)
422 .transform(font_transform)
423 .pos(rotated_pos);
424
425 self.area
426 .draw_text(text, &text_style, (pos.0 as i32, pos.1 as i32))
427 .map_err(map_err)?;
428 } else {
429 let text_style = plotters::prelude::TextStyle::from(font)
430 .color(&color)
431 .pos(pos_adj);
432 self.area
433 .draw_text(text, &text_style, (pos.0 as i32, pos.1 as i32))
434 .map_err(map_err)?;
435 }
436
437 Ok(())
438 }
439
440 fn draw_polygon(
441 &mut self,
442 points: &[(f64, f64)],
443 style: &RectStyle,
444 ) -> Result<(), RenderError> {
445 if points.len() < 3 {
446 return Ok(());
447 }
448 let int_points: Vec<(i32, i32)> =
449 points.iter().map(|(x, y)| (*x as i32, *y as i32)).collect();
450
451 if let Some(fill) = style.fill {
452 let fill_color = to_rgba(fill, style.alpha);
453 self.area
454 .draw(&Polygon::new(int_points.clone(), fill_color.filled()))
455 .map_err(map_err)?;
456 }
457
458 Ok(())
459 }
460
461 fn draw_shape(
462 &mut self,
463 center: (f64, f64),
464 radius: f64,
465 style: &PointStyle,
466 ) -> Result<(), RenderError> {
467 if !point_in_rect(center.0, center.1, &self.plot_area) {
469 return Ok(());
470 }
471 let color = to_rgba(style.color, style.alpha);
472 let (cx, cy) = (center.0 as i32, center.1 as i32);
473 let r = radius as i32;
474
475 match style.shape {
476 PointShape::Circle => self.draw_circle(center, radius, style),
477 PointShape::Square => {
478 let tl = (cx - r, cy - r);
479 let br = (cx + r, cy + r);
480 if style.filled {
481 self.area
482 .draw(&plotters::prelude::Rectangle::new([tl, br], color.filled()))
483 .map_err(map_err)?;
484 } else {
485 self.area
486 .draw(&plotters::prelude::Rectangle::new(
487 [tl, br],
488 color.stroke_width(1),
489 ))
490 .map_err(map_err)?;
491 }
492 Ok(())
493 }
494 PointShape::Triangle => {
495 let pts = vec![(cx, cy - r), (cx - r, cy + r), (cx + r, cy + r)];
496 if style.filled {
497 self.area
498 .draw(&Polygon::new(pts, color.filled()))
499 .map_err(map_err)?;
500 } else {
501 let outline = vec![
502 (cx, cy - r),
503 (cx - r, cy + r),
504 (cx + r, cy + r),
505 (cx, cy - r),
506 ];
507 self.area
508 .draw(&PathElement::new(outline, color.stroke_width(1)))
509 .map_err(map_err)?;
510 }
511 Ok(())
512 }
513 PointShape::Diamond => {
514 let pts = vec![(cx, cy - r), (cx + r, cy), (cx, cy + r), (cx - r, cy)];
515 if style.filled {
516 self.area
517 .draw(&Polygon::new(pts, color.filled()))
518 .map_err(map_err)?;
519 } else {
520 let outline = vec![
521 (cx, cy - r),
522 (cx + r, cy),
523 (cx, cy + r),
524 (cx - r, cy),
525 (cx, cy - r),
526 ];
527 self.area
528 .draw(&PathElement::new(outline, color.stroke_width(1)))
529 .map_err(map_err)?;
530 }
531 Ok(())
532 }
533 PointShape::Cross => {
534 self.area
536 .draw(&PathElement::new(
537 vec![(cx - r, cy - r), (cx + r, cy + r)],
538 color.stroke_width(1),
539 ))
540 .map_err(map_err)?;
541 self.area
542 .draw(&PathElement::new(
543 vec![(cx - r, cy + r), (cx + r, cy - r)],
544 color.stroke_width(1),
545 ))
546 .map_err(map_err)?;
547 Ok(())
548 }
549 PointShape::Plus => {
550 self.area
552 .draw(&PathElement::new(
553 vec![(cx - r, cy), (cx + r, cy)],
554 color.stroke_width(1),
555 ))
556 .map_err(map_err)?;
557 self.area
558 .draw(&PathElement::new(
559 vec![(cx, cy - r), (cx, cy + r)],
560 color.stroke_width(1),
561 ))
562 .map_err(map_err)?;
563 Ok(())
564 }
565 }
566 }
567
568 fn plot_area(&self) -> Rect {
569 self.plot_area.clone()
570 }
571
572 fn total_area(&self) -> Rect {
573 self.total_area.clone()
574 }
575}