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 SANS: &[u8] = include_bytes!("../../assets/fonts/DejaVuSans.ttf");
16const SANS_BOLD: &[u8] = include_bytes!("../../assets/fonts/DejaVuSans-Bold.ttf");
17const SANS_OBLIQUE: &[u8] = include_bytes!("../../assets/fonts/DejaVuSans-Oblique.ttf");
18const SERIF: &[u8] = include_bytes!("../../assets/fonts/DejaVuSerif.ttf");
19const SERIF_BOLD: &[u8] = include_bytes!("../../assets/fonts/DejaVuSerif-Bold.ttf");
20const SERIF_ITALIC: &[u8] = include_bytes!("../../assets/fonts/DejaVuSerif-Italic.ttf");
21const MONO: &[u8] = include_bytes!("../../assets/fonts/DejaVuSansMono.ttf");
22const MONO_BOLD: &[u8] = include_bytes!("../../assets/fonts/DejaVuSansMono-Bold.ttf");
23
24fn faces_for(family: &str) -> [(FontStyle, &'static [u8]); 4] {
28 let f = family.to_lowercase();
29 if f.contains("mono") || f.contains("courier") {
30 [
31 (FontStyle::Normal, MONO),
32 (FontStyle::Bold, MONO_BOLD),
33 (FontStyle::Italic, MONO),
34 (FontStyle::Oblique, MONO),
35 ]
36 } else if f == "serif" || f.contains("times") || (f.contains("serif") && !f.contains("sans")) {
37 [
38 (FontStyle::Normal, SERIF),
39 (FontStyle::Bold, SERIF_BOLD),
40 (FontStyle::Italic, SERIF_ITALIC),
41 (FontStyle::Oblique, SERIF_ITALIC),
42 ]
43 } else {
44 [
45 (FontStyle::Normal, SANS),
46 (FontStyle::Bold, SANS_BOLD),
47 (FontStyle::Italic, SANS_OBLIQUE),
48 (FontStyle::Oblique, SANS_OBLIQUE),
49 ]
50 }
51}
52
53fn registered_families() -> &'static Mutex<HashSet<String>> {
54 static REGISTERED: OnceLock<Mutex<HashSet<String>>> = OnceLock::new();
55 REGISTERED.get_or_init(|| Mutex::new(HashSet::new()))
56}
57
58fn ensure_font(family: &str) {
65 let mut set = registered_families().lock().unwrap();
66 if set.insert(family.to_string()) {
67 for (style, bytes) in faces_for(family) {
70 let _ = plotters::style::register_font(family, style, bytes);
72 }
73 }
74}
75
76pub struct PlottersAdapter<'a, DB: DrawingBackend> {
78 area: &'a DrawingArea<DB, plotters::coord::Shift>,
79 plot_area: Rect,
80 total_area: Rect,
81 corner_claims: Vec<Rect>,
84 clip: bool,
87}
88
89impl<'a, DB: DrawingBackend> PlottersAdapter<'a, DB> {
90 pub fn new(area: &'a DrawingArea<DB, plotters::coord::Shift>, plot_area: Rect) -> Self {
91 let (w, h) = area.dim_in_pixel();
92 PlottersAdapter {
93 area,
94 plot_area,
95 total_area: Rect {
96 x: 0.0,
97 y: 0.0,
98 width: w as f64,
99 height: h as f64,
100 },
101 corner_claims: Vec::new(),
102 clip: true,
103 }
104 }
105}
106
107fn to_rgba(color: (u8, u8, u8), alpha: f64) -> RGBAColor {
108 RGBAColor(color.0, color.1, color.2, alpha)
109}
110
111fn clip_point(x: f64, y: f64, rect: &Rect) -> (f64, f64) {
113 (
114 x.clamp(rect.x, rect.x + rect.width),
115 y.clamp(rect.y, rect.y + rect.height),
116 )
117}
118
119fn point_in_rect(x: f64, y: f64, rect: &Rect) -> bool {
121 let margin = 2.0;
122 x >= rect.x - margin
123 && x <= rect.x + rect.width + margin
124 && y >= rect.y - margin
125 && y <= rect.y + rect.height + margin
126}
127
128fn clip_line_segment(
130 mut x0: f64,
131 mut y0: f64,
132 mut x1: f64,
133 mut y1: f64,
134 rect: &Rect,
135) -> Option<((f64, f64), (f64, f64))> {
136 let xmin = rect.x;
137 let xmax = rect.x + rect.width;
138 let ymin = rect.y;
139 let ymax = rect.y + rect.height;
140
141 const INSIDE: u8 = 0;
142 const LEFT: u8 = 1;
143 const RIGHT: u8 = 2;
144 const BOTTOM: u8 = 4;
145 const TOP: u8 = 8;
146
147 let outcode = |x: f64, y: f64| -> u8 {
148 let mut code = INSIDE;
149 if x < xmin {
150 code |= LEFT;
151 } else if x > xmax {
152 code |= RIGHT;
153 }
154 if y < ymin {
155 code |= TOP;
156 } else if y > ymax {
157 code |= BOTTOM;
158 }
159 code
160 };
161
162 let mut code0 = outcode(x0, y0);
163 let mut code1 = outcode(x1, y1);
164
165 for _ in 0..20 {
166 if (code0 | code1) == 0 {
167 return Some(((x0, y0), (x1, y1)));
168 }
169 if (code0 & code1) != 0 {
170 return None;
171 }
172
173 let code_out = if code0 != 0 { code0 } else { code1 };
174 let (x, y);
175
176 if code_out & TOP != 0 {
177 x = x0 + (x1 - x0) * (ymin - y0) / (y1 - y0);
178 y = ymin;
179 } else if code_out & BOTTOM != 0 {
180 x = x0 + (x1 - x0) * (ymax - y0) / (y1 - y0);
181 y = ymax;
182 } else if code_out & RIGHT != 0 {
183 y = y0 + (y1 - y0) * (xmax - x0) / (x1 - x0);
184 x = xmax;
185 } else {
186 y = y0 + (y1 - y0) * (xmin - x0) / (x1 - x0);
187 x = xmin;
188 }
189
190 if code_out == code0 {
191 x0 = x;
192 y0 = y;
193 code0 = outcode(x0, y0);
194 } else {
195 x1 = x;
196 y1 = y;
197 code1 = outcode(x1, y1);
198 }
199 }
200
201 None
202}
203
204fn map_err<E: std::fmt::Debug>(e: E) -> RenderError {
205 RenderError::BackendError(format!("{:?}", e))
206}
207
208fn segment_dashed(points: &[(f64, f64)], pattern: &[(f64, f64)]) -> Vec<Vec<(f64, f64)>> {
210 if pattern.is_empty() || points.len() < 2 {
211 return vec![points.to_vec()];
212 }
213
214 let mut segments: Vec<Vec<(f64, f64)>> = Vec::new();
215 let mut current_seg: Vec<(f64, f64)> = Vec::new();
216 let mut drawing = true;
217 let mut pat_idx = 0;
218 let mut remaining_in_pat = pattern[0].0; for window in points.windows(2) {
221 let (x0, y0) = window[0];
222 let (x1, y1) = window[1];
223 let dx = x1 - x0;
224 let dy = y1 - y0;
225 let seg_len = (dx * dx + dy * dy).sqrt();
226 if seg_len < 0.001 {
227 continue;
228 }
229 let ux = dx / seg_len;
230 let uy = dy / seg_len;
231 let mut consumed = 0.0;
232
233 while consumed < seg_len - 0.001 {
234 let available = seg_len - consumed;
235 let step = remaining_in_pat.min(available);
236 let px = x0 + ux * (consumed + step);
237 let py = y0 + uy * (consumed + step);
238
239 if drawing {
240 if current_seg.is_empty() {
241 current_seg.push((x0 + ux * consumed, y0 + uy * consumed));
242 }
243 current_seg.push((px, py));
244 }
245
246 consumed += step;
247 remaining_in_pat -= step;
248
249 if remaining_in_pat < 0.001 {
250 if drawing {
251 if current_seg.len() >= 2 {
252 segments.push(std::mem::take(&mut current_seg));
253 } else {
254 current_seg.clear();
255 }
256 drawing = false;
257 remaining_in_pat = pattern[pat_idx].1; } else {
259 drawing = true;
260 pat_idx = (pat_idx + 1) % pattern.len();
261 remaining_in_pat = pattern[pat_idx].0; }
263 }
264 }
265 }
266
267 if drawing && current_seg.len() >= 2 {
268 segments.push(current_seg);
269 }
270
271 segments
272}
273
274impl<DB: DrawingBackend> PlottersAdapter<'_, DB> {
275 fn clip_area(&self) -> Rect {
277 if self.clip {
278 self.plot_area.clone()
279 } else {
280 self.total_area.clone()
281 }
282 }
283}
284
285impl<'a, DB: DrawingBackend> DrawBackend for PlottersAdapter<'a, DB> {
286 fn set_clipping(&mut self, on: bool) {
287 self.clip = on;
288 }
289
290 fn draw_circle(
291 &mut self,
292 center: (f64, f64),
293 radius: f64,
294 style: &PointStyle,
295 ) -> Result<(), RenderError> {
296 if !point_in_rect(center.0, center.1, &self.clip_area()) {
298 return Ok(());
299 }
300 let color = to_rgba(style.color, style.alpha);
301 if style.filled {
302 self.area
303 .draw(&Circle::new(
304 (center.0 as i32, center.1 as i32),
305 radius as i32,
306 color.filled(),
307 ))
308 .map_err(map_err)?;
309 } else {
310 self.area
311 .draw(&Circle::new(
312 (center.0 as i32, center.1 as i32),
313 radius as i32,
314 color.stroke_width(1),
315 ))
316 .map_err(map_err)?;
317 }
318 Ok(())
319 }
320
321 fn draw_line(&mut self, points: &[(f64, f64)], style: &LineStyle) -> Result<(), RenderError> {
322 if points.len() < 2 {
323 return Ok(());
324 }
325 let (pixel_width, alpha) = if style.width >= 1.0 {
327 (style.width as u32, style.alpha)
328 } else if style.width > 0.0 {
329 (1, style.alpha * style.width)
330 } else {
331 (0, style.alpha)
332 };
333 let color = to_rgba(style.color, alpha);
334 let stroke = color.stroke_width(pixel_width);
335
336 let pattern = style.linetype.dash_pattern();
337 let sub_paths = segment_dashed(points, &pattern);
338
339 for path in &sub_paths {
340 for window in path.windows(2) {
341 if let Some((p1, p2)) = clip_line_segment(
343 window[0].0,
344 window[0].1,
345 window[1].0,
346 window[1].1,
347 &self.clip_area(),
348 ) {
349 self.area
350 .draw(&PathElement::new(
351 vec![(p1.0 as i32, p1.1 as i32), (p2.0 as i32, p2.1 as i32)],
352 stroke,
353 ))
354 .map_err(map_err)?;
355 }
356 }
357 }
358 Ok(())
359 }
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 let (tl, br) = if style.clip {
368 let area = self.clip_area();
370 let clamped_tl = clip_point(top_left.0, top_left.1, &area);
371 let clamped_br = clip_point(bottom_right.0, bottom_right.1, &area);
372
373 if (clamped_tl.0 - clamped_br.0).abs() < 0.5
375 && (clamped_tl.1 - clamped_br.1).abs() < 0.5
376 {
377 if (top_left.0 - bottom_right.0).abs() > 1.0
379 || (top_left.1 - bottom_right.1).abs() > 1.0
380 {
381 return Ok(());
382 }
383 }
384
385 (
386 (clamped_tl.0 as i32, clamped_tl.1 as i32),
387 (clamped_br.0 as i32, clamped_br.1 as i32),
388 )
389 } else {
390 (
392 (top_left.0 as i32, top_left.1 as i32),
393 (bottom_right.0 as i32, bottom_right.1 as i32),
394 )
395 };
396
397 if style.corner_radius > 0.0 {
398 let pts = super::backend::rounded_rect_points(
403 tl.0 as f64,
404 tl.1 as f64,
405 br.0 as f64,
406 br.1 as f64,
407 style.corner_radius,
408 );
409 let int_pts: Vec<(i32, i32)> =
410 pts.iter().map(|(x, y)| (*x as i32, *y as i32)).collect();
411 if let Some(fill) = style.fill {
412 let fill_color = to_rgba(fill, style.alpha);
413 self.area
414 .draw(&Polygon::new(int_pts.clone(), fill_color.filled()))
415 .map_err(map_err)?;
416 }
417 if let Some(stroke) = style.stroke {
418 let stroke_color = to_rgba(stroke, style.alpha);
419 let width = if style.stroke_width > 0.0 {
420 (style.stroke_width as u32).max(1)
421 } else {
422 0
423 };
424 let mut loop_pts = int_pts;
425 if let Some(first) = loop_pts.first().copied() {
426 loop_pts.push(first);
427 }
428 self.area
429 .draw(&PathElement::new(
430 loop_pts,
431 stroke_color.stroke_width(width),
432 ))
433 .map_err(map_err)?;
434 }
435 return Ok(());
436 }
437
438 if let Some(fill) = style.fill {
439 let fill_color = to_rgba(fill, style.alpha);
440 self.area
441 .draw(&plotters::prelude::Rectangle::new(
442 [tl, br],
443 fill_color.filled(),
444 ))
445 .map_err(map_err)?;
446 }
447
448 if let Some(stroke) = style.stroke {
449 let stroke_color = to_rgba(stroke, style.alpha);
450 self.area
451 .draw(&plotters::prelude::Rectangle::new(
452 [tl, br],
453 stroke_color.stroke_width(if style.stroke_width > 0.0 {
454 (style.stroke_width as u32).max(1)
455 } else {
456 0
457 }),
458 ))
459 .map_err(map_err)?;
460 }
461
462 Ok(())
463 }
464
465 fn draw_text(
466 &mut self,
467 text: &str,
468 pos: (f64, f64),
469 style: &TextStyle,
470 ) -> Result<(), RenderError> {
471 let color = to_rgba(style.color, 1.0);
472 let family = style.family.as_deref().unwrap_or("sans-serif");
473 ensure_font(family);
474 let font_style = match style.face {
475 crate::render::backend::FontFace::Plain => FontStyle::Normal,
476 crate::render::backend::FontFace::Bold => FontStyle::Bold,
477 crate::render::backend::FontFace::Italic => FontStyle::Italic,
478 };
479 let angle_i = style.angle.round() as i32;
480
481 if let Some(bg) = style.bg_colour {
487 let r = if style.bg_r.is_finite() {
488 style.bg_r
489 } else {
490 0.0
491 };
492 if r > 0.0 {
493 let halo_color = to_rgba(bg, 1.0);
494 for i in 0..8 {
495 let theta = i as f64 * std::f64::consts::FRAC_PI_4;
496 let offset = (pos.0 + r * theta.cos(), pos.1 + r * theta.sin());
497 self.draw_text_pass(
498 text,
499 offset,
500 style.anchor.clone(),
501 family,
502 style.size,
503 font_style,
504 angle_i,
505 halo_color,
506 )?;
507 }
508 }
509 }
510
511 self.draw_text_pass(
512 text,
513 pos,
514 style.anchor.clone(),
515 family,
516 style.size,
517 font_style,
518 angle_i,
519 color,
520 )?;
521 Ok(())
522 }
523
524 fn draw_polygon(
525 &mut self,
526 points: &[(f64, f64)],
527 style: &RectStyle,
528 ) -> Result<(), RenderError> {
529 if points.len() < 3 {
530 return Ok(());
531 }
532 let int_points: Vec<(i32, i32)> =
533 points.iter().map(|(x, y)| (*x as i32, *y as i32)).collect();
534
535 if let Some(fill) = style.fill {
536 let fill_color = to_rgba(fill, style.alpha);
537 self.area
538 .draw(&Polygon::new(int_points.clone(), fill_color.filled()))
539 .map_err(map_err)?;
540 }
541 if let (Some(stroke), true) = (style.stroke, style.stroke_width > 0.0) {
545 let mut pts = int_points;
546 pts.push(pts[0]);
547 let width = (style.stroke_width as u32).max(1);
548 self.area
549 .draw(&PathElement::new(
550 pts,
551 to_rgba(stroke, style.alpha).stroke_width(width),
552 ))
553 .map_err(map_err)?;
554 }
555
556 Ok(())
557 }
558
559 fn draw_polygon_rings(
565 &mut self,
566 rings: &[Vec<(f64, f64)>],
567 style: &RectStyle,
568 ) -> Result<(), RenderError> {
569 if let Some(fill) = style.fill {
570 let color = to_rgba(fill, style.alpha);
571 let (mut y0, mut y1) = (f64::INFINITY, f64::NEG_INFINITY);
572 for ring in rings {
573 if ring.len() < 3 {
574 continue;
575 }
576 for p in ring {
577 y0 = y0.min(p.1);
578 y1 = y1.max(p.1);
579 }
580 }
581 if y0.is_finite() && y1.is_finite() {
582 let (y0, y1) = (y0.floor() as i32, y1.ceil() as i32);
583 for y in y0..=y1 {
584 let yc = y as f64 + 0.5;
585 let mut xs: Vec<f64> = Vec::new();
586 for ring in rings {
587 let n = ring.len();
588 if n < 3 {
589 continue;
590 }
591 for i in 0..n {
592 let (ax, ay) = ring[i];
593 let (bx, by) = ring[(i + 1) % n];
594 if (ay <= yc && by > yc) || (by <= yc && ay > yc) {
595 xs.push(ax + (yc - ay) / (by - ay) * (bx - ax));
596 }
597 }
598 }
599 xs.sort_by(|p, q| p.total_cmp(q));
600 for span in xs.chunks(2) {
601 if let [xa, xb] = span {
602 for x in xa.round() as i32..=xb.round() as i32 {
603 self.area.draw_pixel((x, y), &color).map_err(map_err)?;
604 }
605 }
606 }
607 }
608 }
609 }
610 if let Some(stroke) = style.stroke {
611 let stroke_color = to_rgba(stroke, style.alpha);
612 let width = if style.stroke_width > 0.0 {
613 (style.stroke_width as u32).max(1)
614 } else {
615 0
616 };
617 if width > 0 {
618 for ring in rings {
619 if ring.len() < 3 {
620 continue;
621 }
622 let mut pts: Vec<(i32, i32)> =
623 ring.iter().map(|(x, y)| (*x as i32, *y as i32)).collect();
624 pts.push(pts[0]);
625 self.area
626 .draw(&PathElement::new(pts, stroke_color.stroke_width(width)))
627 .map_err(map_err)?;
628 }
629 }
630 }
631 Ok(())
632 }
633
634 fn draw_shape(
635 &mut self,
636 center: (f64, f64),
637 radius: f64,
638 style: &PointStyle,
639 ) -> Result<(), RenderError> {
640 if !point_in_rect(center.0, center.1, &self.clip_area()) {
642 return Ok(());
643 }
644 let color = to_rgba(style.color, style.alpha);
645 let (cx, cy) = (center.0 as i32, center.1 as i32);
646 let r = radius as i32;
647
648 match style.shape {
649 PointShape::Circle => self.draw_circle(center, radius, style),
650 PointShape::Square => {
651 let tl = (cx - r, cy - r);
652 let br = (cx + r, cy + r);
653 if style.filled {
654 self.area
655 .draw(&plotters::prelude::Rectangle::new([tl, br], color.filled()))
656 .map_err(map_err)?;
657 } else {
658 self.area
659 .draw(&plotters::prelude::Rectangle::new(
660 [tl, br],
661 color.stroke_width(1),
662 ))
663 .map_err(map_err)?;
664 }
665 Ok(())
666 }
667 PointShape::Triangle => {
668 let pts = vec![(cx, cy - r), (cx - r, cy + r), (cx + r, cy + r)];
669 if style.filled {
670 self.area
671 .draw(&Polygon::new(pts, color.filled()))
672 .map_err(map_err)?;
673 } else {
674 let outline = vec![
675 (cx, cy - r),
676 (cx - r, cy + r),
677 (cx + r, cy + r),
678 (cx, cy - r),
679 ];
680 self.area
681 .draw(&PathElement::new(outline, color.stroke_width(1)))
682 .map_err(map_err)?;
683 }
684 Ok(())
685 }
686 PointShape::Diamond => {
687 let pts = vec![(cx, cy - r), (cx + r, cy), (cx, cy + r), (cx - r, cy)];
688 if style.filled {
689 self.area
690 .draw(&Polygon::new(pts, color.filled()))
691 .map_err(map_err)?;
692 } else {
693 let outline = vec![
694 (cx, cy - r),
695 (cx + r, cy),
696 (cx, cy + r),
697 (cx - r, cy),
698 (cx, cy - r),
699 ];
700 self.area
701 .draw(&PathElement::new(outline, color.stroke_width(1)))
702 .map_err(map_err)?;
703 }
704 Ok(())
705 }
706 PointShape::Cross => {
707 self.area
709 .draw(&PathElement::new(
710 vec![(cx - r, cy - r), (cx + r, cy + r)],
711 color.stroke_width(1),
712 ))
713 .map_err(map_err)?;
714 self.area
715 .draw(&PathElement::new(
716 vec![(cx - r, cy + r), (cx + r, cy - r)],
717 color.stroke_width(1),
718 ))
719 .map_err(map_err)?;
720 Ok(())
721 }
722 PointShape::Plus => {
723 self.area
725 .draw(&PathElement::new(
726 vec![(cx - r, cy), (cx + r, cy)],
727 color.stroke_width(1),
728 ))
729 .map_err(map_err)?;
730 self.area
731 .draw(&PathElement::new(
732 vec![(cx, cy - r), (cx, cy + r)],
733 color.stroke_width(1),
734 ))
735 .map_err(map_err)?;
736 Ok(())
737 }
738 }
739 }
740
741 fn draw_image(
744 &mut self,
745 bytes: &[u8],
746 rect: &Rect,
747 clip: &Rect,
748 _alpha: f64,
749 ) -> Result<(), RenderError> {
750 let (w, h) = self.area.dim_in_pixel();
751 let Some((pos, img)) = crate::render::raster::decode_visible(bytes, rect, clip, w, h)
752 else {
753 return Ok(());
754 };
755 let rgb = image::DynamicImage::ImageRgba8(img).to_rgb8();
756 let element: plotters::element::BitMapElement<(i32, i32)> =
757 (pos, image::DynamicImage::ImageRgb8(rgb)).into();
758 self.area.draw(&element).map_err(map_err)?;
759 Ok(())
760 }
761
762 fn plot_area(&self) -> Rect {
763 self.plot_area.clone()
764 }
765
766 fn total_area(&self) -> Rect {
767 self.total_area.clone()
768 }
769
770 fn corner_claims(&self) -> Vec<Rect> {
771 self.corner_claims.clone()
772 }
773 fn claim_corner_box(&mut self, rect: Rect) {
774 self.corner_claims.push(rect);
775 }
776 fn clear_corner_claims(&mut self) {
777 self.corner_claims.clear();
778 }
779}
780
781impl<'a, DB: DrawingBackend> PlottersAdapter<'a, DB> {
782 #[allow(clippy::too_many_arguments)]
791 fn draw_text_pass(
792 &mut self,
793 text: &str,
794 pos: (f64, f64),
795 anchor: TextAnchor,
796 family: &str,
797 size: f64,
798 font_style: FontStyle,
799 angle_i: i32,
800 color: RGBAColor,
801 ) -> Result<(), RenderError> {
802 let font = (family, size).into_font().style(font_style);
803
804 let pos_adj = match anchor {
805 TextAnchor::Start => plotters::style::text_anchor::Pos::new(
806 plotters::style::text_anchor::HPos::Left,
807 plotters::style::text_anchor::VPos::Center,
808 ),
809 TextAnchor::Middle => plotters::style::text_anchor::Pos::new(
810 plotters::style::text_anchor::HPos::Center,
811 plotters::style::text_anchor::VPos::Center,
812 ),
813 TextAnchor::End => plotters::style::text_anchor::Pos::new(
814 plotters::style::text_anchor::HPos::Right,
815 plotters::style::text_anchor::VPos::Center,
816 ),
817 };
818
819 if angle_i != 0 {
820 let (font_transform, rotated_pos) = match angle_i.rem_euclid(360) {
821 80..=100 => (
822 FontTransform::Rotate90,
823 plotters::style::text_anchor::Pos::new(
824 plotters::style::text_anchor::HPos::Center,
825 plotters::style::text_anchor::VPos::Center,
826 ),
827 ),
828 170..=190 => (
829 FontTransform::Rotate180,
830 plotters::style::text_anchor::Pos::new(
831 plotters::style::text_anchor::HPos::Center,
832 plotters::style::text_anchor::VPos::Center,
833 ),
834 ),
835 _ => (
837 FontTransform::Rotate270,
838 plotters::style::text_anchor::Pos::new(
839 plotters::style::text_anchor::HPos::Right,
840 plotters::style::text_anchor::VPos::Center,
841 ),
842 ),
843 };
844
845 let text_style = plotters::prelude::TextStyle::from((family, size).into_font())
846 .color(&color)
847 .transform(font_transform)
848 .pos(rotated_pos);
849
850 self.area
851 .draw_text(text, &text_style, (pos.0 as i32, pos.1 as i32))
852 .map_err(map_err)?;
853 } else {
854 let text_style = plotters::prelude::TextStyle::from(font)
855 .color(&color)
856 .pos(pos_adj);
857 self.area
858 .draw_text(text, &text_style, (pos.0 as i32, pos.1 as i32))
859 .map_err(map_err)?;
860 }
861
862 Ok(())
863 }
864}