1use crate::aes::Aesthetic;
2use crate::annotate::Annotation;
3use crate::build::BuiltPlot;
4use crate::facet::Facet;
5use crate::guide::{axis, legend};
6use crate::render::backend::{DrawBackend, LineStyle, Linetype, RectStyle, TextAnchor, TextStyle};
7use crate::render::RenderError;
8
9pub struct PlotRenderer;
11
12impl PlotRenderer {
13 pub fn render(built: &BuiltPlot, backend: &mut dyn DrawBackend) -> Result<(), RenderError> {
14 if !built.facet.is_none() && !built.panels.is_empty() {
15 Self::render_faceted(built, backend)
16 } else {
17 Self::render_single(built, backend)
18 }
19 }
20
21 fn render_single(built: &BuiltPlot, backend: &mut dyn DrawBackend) -> Result<(), RenderError> {
22 let theme = &built.theme;
23 let plot_area = backend.plot_area();
24 let total_area = backend.total_area();
25 backend.clear_corner_claims();
31 if let Some(rect) = legend::inside_legend_box(
32 &built.scales,
33 theme,
34 &plot_area,
35 &total_area,
36 &built.guide_legend,
37 &built.suppressed_aes,
38 &built.legend_plans,
39 ) {
40 backend.claim_corner_box(rect);
41 }
42
43 if theme.plot_background.visible {
45 if let Some(fill) = theme.plot_background.fill {
46 backend.draw_rect(
47 (total_area.x, total_area.y),
48 (
49 total_area.x + total_area.width,
50 total_area.y + total_area.height,
51 ),
52 &RectStyle {
53 fill: Some(fill),
54 stroke: None,
55 stroke_width: 0.0,
56 alpha: 1.0,
57 clip: false,
58 corner_radius: 0.0,
59 },
60 )?;
61 }
62 }
63
64 if theme.panel_background.visible {
66 if let Some(fill) = theme.panel_background.fill {
67 backend.draw_rect(
68 (plot_area.x, plot_area.y),
69 (
70 plot_area.x + plot_area.width,
71 plot_area.y + plot_area.height,
72 ),
73 &RectStyle {
74 fill: Some(fill),
75 stroke: theme.panel_background.color,
76 stroke_width: theme.panel_background.width,
77 alpha: 1.0,
78 clip: false,
79 corner_radius: 0.0,
80 },
81 )?;
82 }
83 }
84
85 if theme.panel_border.visible {
87 let style = LineStyle {
88 color: theme.panel_border.color,
89 width: theme.panel_border.width,
90 alpha: 1.0,
91 linetype: Linetype::Solid,
92 ..Default::default()
93 };
94 let x0 = plot_area.x;
95 let y0 = plot_area.y;
96 let x1 = plot_area.x + plot_area.width;
97 let y1 = plot_area.y + plot_area.height;
98 backend.draw_line(&[(x0, y0), (x1, y0)], &style)?;
99 backend.draw_line(&[(x1, y0), (x1, y1)], &style)?;
100 backend.draw_line(&[(x1, y1), (x0, y1)], &style)?;
101 backend.draw_line(&[(x0, y1), (x0, y0)], &style)?;
102 }
103
104 let x_scale = built.scales.get(&Aesthetic::X);
106 let y_scale = built.scales.get(&Aesthetic::Y);
107
108 let (h_scale, v_scale) = if built.coord.is_flipped() {
109 (y_scale, x_scale)
110 } else {
111 (x_scale, y_scale)
112 };
113
114 if let (true, Some(xs), Some(ys)) = (built.coord.is_radar(), x_scale, y_scale) {
115 crate::guide::polar::draw_radar_guides(
117 xs,
118 ys,
119 built.coord.as_ref(),
120 theme,
121 &plot_area,
122 backend,
123 )?;
124 } else if let (Some(hs), Some(vs)) = (h_scale, v_scale) {
125 if built.coord.gridlines() && !theme.panel_ontop {
127 axis::draw_gridlines(hs, vs, built.coord.as_ref(), theme, &plot_area, backend)?;
128 }
129
130 axis::draw_x_axis(
132 hs,
133 built.coord.as_ref(),
134 theme,
135 &plot_area,
136 &total_area,
137 backend,
138 )?;
139 axis::draw_y_axis(vs, built.coord.as_ref(), theme, &plot_area, backend)?;
140
141 if let Some(sec) = built.scales.sec_axis(&Aesthetic::Y) {
143 axis::draw_sec_y_axis(vs, sec, built.coord.as_ref(), theme, &plot_area, backend)?;
144 }
145 }
146
147 for layer in built.layers.iter().filter(|l| l.data.nrows() > 0) {
151 backend.set_mark_geom(Some(layer.geom.name()));
152 let drawn = layer.geom.draw(
153 &layer.data,
154 built.coord.as_ref(),
155 &built.scales,
156 theme,
157 backend,
158 );
159 backend.set_mark_geom(None);
160 drawn?;
161 }
162
163 if theme.panel_ontop && built.coord.gridlines() {
165 if let (Some(hs), Some(vs)) = (h_scale, v_scale) {
166 axis::draw_gridlines(hs, vs, built.coord.as_ref(), theme, &plot_area, backend)?;
167 }
168 }
169
170 Self::draw_annotations(
172 &built.annotations,
173 &built.scales,
174 built.coord.as_ref(),
175 &plot_area,
176 theme,
177 backend,
178 )?;
179
180 let title_ref = Self::title_ref_rect(theme, &plot_area, &total_area);
182 let legend_lift = legend::top_legend_lift(
187 theme,
188 &built.scales,
189 &built.suppressed_aes,
190 &built.legend_plans,
191 &built.guide_legend,
192 );
193 if let Some(ref title) = built.labels.title {
194 let (tx, anchor) = Self::hjust_pos(&theme.title, &title_ref);
195 let x_axis_top = built
198 .scales
199 .get(&Aesthetic::X)
200 .map(|s| s.axis_position_opposite())
201 .unwrap_or(false);
202 let x_axis_lift = if x_axis_top {
203 theme.axis_ticks_length + theme.axis_text_x.size + theme.legend_spacing
204 } else {
205 0.0
206 };
207 let title_y = plot_area.y - theme.title.size * 0.9 - x_axis_lift - legend_lift;
208 let family = if theme.title.family.is_empty() {
209 None
210 } else {
211 Some(theme.title.family.clone())
212 };
213 backend.draw_text(
214 title,
215 (tx, title_y.max(theme.title.size)),
216 &TextStyle {
217 color: theme.title.color,
218 size: theme.title.size,
219 anchor,
220 angle: 0.0,
221 family,
222 face: theme.title.face,
223 bg_colour: None,
224 bg_r: 0.0,
225 },
226 )?;
227 }
228
229 if let Some(ref subtitle) = built.labels.subtitle {
231 let (sx, anchor) = Self::hjust_pos(&theme.subtitle, &title_ref);
232 let subtitle_y =
241 (plot_area.y - 2.0).max(theme.title.size + theme.subtitle.size) - legend_lift;
242 let family = if theme.subtitle.family.is_empty() {
243 None
244 } else {
245 Some(theme.subtitle.family.clone())
246 };
247 backend.draw_text(
248 subtitle,
249 (sx, subtitle_y),
250 &TextStyle {
251 color: theme.subtitle.color,
252 size: theme.subtitle.size,
253 anchor,
254 angle: 0.0,
255 family,
256 face: theme.subtitle.face,
257 bg_colour: None,
258 bg_r: 0.0,
259 },
260 )?;
261 }
262
263 legend::draw_legend_planned(
265 &built.scales,
266 theme,
267 &plot_area,
268 plot_area.width,
269 backend,
270 &built.guide_legend,
271 &built.suppressed_aes,
272 &built.legend_plans,
273 )?;
274
275 if let Some(ref caption) = built.labels.caption {
277 let (cx, anchor) = Self::hjust_pos(&theme.caption, &title_ref);
278 let caption_y = total_area.y + total_area.height - theme.caption.size * 0.5;
279 let family = if theme.caption.family.is_empty() {
280 None
281 } else {
282 Some(theme.caption.family.clone())
283 };
284 backend.draw_text(
285 caption,
286 (cx, caption_y),
287 &TextStyle {
288 color: theme.caption.color,
289 size: theme.caption.size,
290 anchor,
291 angle: 0.0,
292 family,
293 face: theme.caption.face,
294 bg_colour: None,
295 bg_r: 0.0,
296 },
297 )?;
298 }
299
300 Self::draw_tag(&built.labels, theme, &total_area, backend)?;
302
303 Ok(())
304 }
305
306 fn hjust_pos(
309 el: &crate::theme::elements::ElementText,
310 area: &crate::render::Rect,
311 ) -> (f64, TextAnchor) {
312 let x = area.x + el.hjust.clamp(0.0, 1.0) * area.width;
313 let anchor = if el.hjust <= 0.02 {
314 TextAnchor::Start
315 } else if el.hjust >= 0.98 {
316 TextAnchor::End
317 } else {
318 TextAnchor::Middle
319 };
320 (x, anchor)
321 }
322
323 fn title_ref_rect(
326 theme: &crate::theme::Theme,
327 plot_area: &crate::render::Rect,
328 total_area: &crate::render::Rect,
329 ) -> crate::render::Rect {
330 match theme.title_position {
331 crate::theme::TitlePosition::Panel => plot_area.clone(),
332 crate::theme::TitlePosition::Plot => crate::render::Rect {
333 x: total_area.x + theme.plot_margin.left,
334 y: plot_area.y,
335 width: (total_area.width - theme.plot_margin.left - theme.plot_margin.right)
336 .max(1.0),
337 height: plot_area.height,
338 },
339 }
340 }
341
342 fn draw_tag(
344 labels: &crate::plot::Labels,
345 theme: &crate::theme::Theme,
346 total_area: &crate::render::Rect,
347 backend: &mut dyn DrawBackend,
348 ) -> Result<(), RenderError> {
349 if let Some(ref tag) = labels.tag {
350 let family = if theme.title.family.is_empty() {
351 None
352 } else {
353 Some(theme.title.family.clone())
354 };
355 use crate::theme::TagPosition::*;
356 let pad = theme.title.size;
357 let (left, right) = (total_area.x + pad, total_area.x + total_area.width - pad);
358 let (top, bottom) = (total_area.y + pad, total_area.y + total_area.height - pad);
359 let (x, y, anchor) = match theme.tag_position {
360 TopLeft => (left, top, TextAnchor::Start),
361 TopRight => (right, top, TextAnchor::End),
362 BottomLeft => (left, bottom, TextAnchor::Start),
363 BottomRight => (right, bottom, TextAnchor::End),
364 };
365 backend.draw_text(
366 tag,
367 (x, y),
368 &TextStyle {
369 color: theme.title.color,
370 size: theme.title.size,
371 anchor,
372 angle: 0.0,
373 family,
374 face: theme.title.face,
375 bg_colour: None,
376 bg_r: 0.0,
377 },
378 )?;
379 }
380 Ok(())
381 }
382
383 fn render_faceted(built: &BuiltPlot, backend: &mut dyn DrawBackend) -> Result<(), RenderError> {
384 let theme = &built.theme;
385 let plot_area = backend.plot_area();
386 let total_area = backend.total_area();
387
388 if theme.plot_background.visible {
390 if let Some(fill) = theme.plot_background.fill {
391 backend.draw_rect(
392 (total_area.x, total_area.y),
393 (
394 total_area.x + total_area.width,
395 total_area.y + total_area.height,
396 ),
397 &RectStyle {
398 fill: Some(fill),
399 stroke: None,
400 stroke_width: 0.0,
401 alpha: 1.0,
402 clip: false,
403 corner_radius: 0.0,
404 },
405 )?;
406 }
407 }
408
409 let ncol = match &built.facet {
411 Facet::Wrap { ncol, .. } => {
412 ncol.unwrap_or_else(|| (built.panels.len() as f64).sqrt().ceil() as usize)
413 }
414 Facet::Grid { .. } => built.panels.iter().map(|p| p.col).max().unwrap_or(0) + 1,
415 Facet::None => 1,
416 };
417 let nrow = built.panels.len().div_ceil(ncol);
418
419 let strip_height = theme.strip_text.size + 8.0;
420 let gap_x = theme.get_panel_spacing_x();
421 let gap_y = theme.get_panel_spacing_y();
422
423 let space = match &built.facet {
426 Facet::Grid { space, .. } => space.clone(),
427 _ => crate::facet::FacetSpace::Fixed,
428 };
429 let extent = |pi: usize, col: &str| -> Option<f64> {
430 let (mut lo, mut hi) = (f64::INFINITY, f64::NEG_INFINITY);
431 for df in &built.panels_data[pi] {
432 if let Some(c) = df.column(col) {
433 for v in c {
434 if let Some(f) = v.as_f64() {
435 lo = lo.min(f);
436 hi = hi.max(f);
437 }
438 }
439 }
440 }
441 (lo <= hi).then_some(hi - lo)
442 };
443 let avail_w = plot_area.width - gap_x * (ncol as f64 - 1.0);
444 let col_widths: Vec<f64> = if space.free_x() && ncol > 0 {
445 let mut ranges = vec![0.0f64; ncol];
446 for (pi, panel) in built.panels.iter().enumerate() {
447 if let Some(r) = extent(pi, "x") {
448 ranges[panel.col] = ranges[panel.col].max(r);
449 }
450 }
451 let total: f64 = ranges.iter().sum();
452 if total > 0.0 {
453 ranges.iter().map(|r| avail_w * (r / total)).collect()
454 } else {
455 vec![avail_w / ncol as f64; ncol]
456 }
457 } else {
458 vec![avail_w / ncol.max(1) as f64; ncol.max(1)]
459 };
460 let mut col_x = vec![plot_area.x; ncol.max(1)];
461 {
462 let mut acc = plot_area.x;
463 for c in 0..ncol {
464 col_x[c] = acc;
465 acc += col_widths[c] + gap_x;
466 }
467 }
468
469 let avail_h = plot_area.height - gap_y * (nrow as f64 - 1.0) - strip_height * nrow as f64;
470 let row_heights: Vec<f64> = if space.free_y() && nrow > 0 {
471 let mut ranges = vec![0.0f64; nrow];
472 for (pi, panel) in built.panels.iter().enumerate() {
473 if let Some(r) = extent(pi, "y") {
474 ranges[panel.row] = ranges[panel.row].max(r);
475 }
476 }
477 let total: f64 = ranges.iter().sum();
478 if total > 0.0 {
479 ranges.iter().map(|r| avail_h * (r / total)).collect()
480 } else {
481 vec![avail_h / nrow as f64; nrow]
482 }
483 } else {
484 vec![avail_h / nrow.max(1) as f64; nrow.max(1)]
485 };
486 let mut row_y = vec![plot_area.y; nrow.max(1)];
487 {
488 let mut acc = plot_area.y;
489 for r in 0..nrow {
490 row_y[r] = acc;
491 acc += row_heights[r] + strip_height + gap_y;
492 }
493 }
494
495 for (pi, panel) in built.panels.iter().enumerate() {
496 let panel_width = col_widths[panel.col];
497 let panel_height = row_heights[panel.row];
498 let px = col_x[panel.col];
499 let py = row_y[panel.row];
500
501 let panel_rect = crate::render::Rect {
502 x: px,
503 y: py + strip_height,
504 width: panel_width,
505 height: panel_height,
506 };
507 backend.clear_corner_claims();
515
516 if theme.strip_background.visible {
518 backend.draw_rect(
519 (px, py),
520 (px + panel_width, py + strip_height),
521 &RectStyle {
522 fill: theme.strip_background.fill,
523 stroke: theme.strip_background.color,
524 stroke_width: theme.strip_background.width,
525 alpha: 1.0,
526 clip: false,
527 corner_radius: 0.0,
528 },
529 )?;
530 }
531
532 if theme.strip_text.visible {
534 let label = panel.col_label.as_deref().unwrap_or(&panel.label);
535 let family = if theme.strip_text.family.is_empty() {
536 None
537 } else {
538 Some(theme.strip_text.family.clone())
539 };
540 backend.draw_text(
541 label,
542 (px + panel_width / 2.0, py + strip_height / 2.0),
543 &TextStyle {
544 color: theme.strip_text.color,
545 size: theme.strip_text.size,
546 anchor: TextAnchor::Middle,
547 angle: 0.0,
548 family,
549 face: theme.strip_text.face,
550 bg_colour: None,
551 bg_r: 0.0,
552 },
553 )?;
554 }
555
556 if theme.panel_background.visible {
558 if let Some(fill) = theme.panel_background.fill {
559 backend.draw_rect(
560 (panel_rect.x, panel_rect.y),
561 (
562 panel_rect.x + panel_rect.width,
563 panel_rect.y + panel_rect.height,
564 ),
565 &RectStyle {
566 fill: Some(fill),
567 stroke: theme.panel_background.color,
568 stroke_width: theme.panel_background.width,
569 alpha: 1.0,
570 clip: false,
571 corner_radius: 0.0,
572 },
573 )?;
574 }
575 }
576
577 if theme.panel_border.visible {
579 let style = LineStyle {
580 color: theme.panel_border.color,
581 width: theme.panel_border.width,
582 alpha: 1.0,
583 linetype: Linetype::Solid,
584 ..Default::default()
585 };
586 let x0 = panel_rect.x;
587 let y0 = panel_rect.y;
588 let x1 = panel_rect.x + panel_rect.width;
589 let y1 = panel_rect.y + panel_rect.height;
590 backend.draw_line(&[(x0, y0), (x1, y0)], &style)?;
591 backend.draw_line(&[(x1, y0), (x1, y1)], &style)?;
592 backend.draw_line(&[(x1, y1), (x0, y1)], &style)?;
593 backend.draw_line(&[(x0, y1), (x0, y0)], &style)?;
594 }
595
596 let panel_scale_set = if pi < built.panel_scales.len() {
598 &built.panel_scales[pi]
599 } else {
600 &built.scales
601 };
602
603 let x_scale = panel_scale_set.get(&Aesthetic::X);
605 let y_scale = panel_scale_set.get(&Aesthetic::Y);
606
607 if let (Some(xs), Some(ys)) = (x_scale, y_scale) {
608 if built.coord.gridlines() {
609 axis::draw_gridlines(
610 xs,
611 ys,
612 built.coord.as_ref(),
613 theme,
614 &panel_rect,
615 backend,
616 )?;
617 }
618
619 if panel.row == nrow - 1 || pi + ncol >= built.panels.len() {
621 axis::draw_x_axis(
622 xs,
623 built.coord.as_ref(),
624 theme,
625 &panel_rect,
626 &total_area,
627 backend,
628 )?;
629 }
630
631 if panel.col == 0 {
633 axis::draw_y_axis(ys, built.coord.as_ref(), theme, &panel_rect, backend)?;
634 }
635 }
636
637 if pi < built.panels_data.len() {
639 for (li, layer_data) in built.panels_data[pi].iter().enumerate() {
640 if li < built.layers.len() && layer_data.nrows() > 0 {
641 let mut panel_backend = PanelBackendAdapter {
642 inner: backend,
643 panel_rect: panel_rect.clone(),
644 };
645 let geom = &built.layers[li].geom;
646 panel_backend.set_mark_geom(Some(geom.name()));
647 let drawn = geom.draw(
648 layer_data,
649 built.coord.as_ref(),
650 panel_scale_set,
651 theme,
652 &mut panel_backend,
653 );
654 panel_backend.set_mark_geom(None);
655 drawn?;
656 }
657 }
658 }
659 }
660
661 let title_ref = Self::title_ref_rect(theme, &plot_area, &total_area);
663 let legend_lift = legend::top_legend_lift(
666 theme,
667 &built.scales,
668 &built.suppressed_aes,
669 &built.legend_plans,
670 &built.guide_legend,
671 );
672 if let Some(ref title) = built.labels.title {
673 let (tx, anchor) = Self::hjust_pos(&theme.title, &title_ref);
674 let title_y = plot_area.y - theme.title.size * 0.9 - legend_lift;
675 let family = if theme.title.family.is_empty() {
676 None
677 } else {
678 Some(theme.title.family.clone())
679 };
680 backend.draw_text(
681 title,
682 (tx, title_y.max(theme.title.size)),
683 &TextStyle {
684 color: theme.title.color,
685 size: theme.title.size,
686 anchor,
687 angle: 0.0,
688 family,
689 face: theme.title.face,
690 bg_colour: None,
691 bg_r: 0.0,
692 },
693 )?;
694 }
695
696 if let Some(ref subtitle) = built.labels.subtitle {
698 let (sx, anchor) = Self::hjust_pos(&theme.subtitle, &title_ref);
699 let subtitle_y =
702 (plot_area.y - 2.0).max(theme.title.size + theme.subtitle.size) - legend_lift;
703 let family = if theme.subtitle.family.is_empty() {
704 None
705 } else {
706 Some(theme.subtitle.family.clone())
707 };
708 backend.draw_text(
709 subtitle,
710 (sx, subtitle_y),
711 &TextStyle {
712 color: theme.subtitle.color,
713 size: theme.subtitle.size,
714 anchor,
715 angle: 0.0,
716 family,
717 face: theme.subtitle.face,
718 bg_colour: None,
719 bg_r: 0.0,
720 },
721 )?;
722 }
723
724 if let Some(ref caption) = built.labels.caption {
726 let (cx, anchor) = Self::hjust_pos(&theme.caption, &title_ref);
727 let caption_y = total_area.y + total_area.height - theme.caption.size * 0.5;
728 let family = if theme.caption.family.is_empty() {
729 None
730 } else {
731 Some(theme.caption.family.clone())
732 };
733 backend.draw_text(
734 caption,
735 (cx, caption_y),
736 &TextStyle {
737 color: theme.caption.color,
738 size: theme.caption.size,
739 anchor,
740 angle: 0.0,
741 family,
742 face: theme.caption.face,
743 bg_colour: None,
744 bg_r: 0.0,
745 },
746 )?;
747 }
748
749 Self::draw_annotations(
751 &built.annotations,
752 &built.scales,
753 built.coord.as_ref(),
754 &plot_area,
755 theme,
756 backend,
757 )?;
758
759 legend::draw_legend_planned(
761 &built.scales,
762 theme,
763 &plot_area,
764 plot_area.width,
765 backend,
766 &built.guide_legend,
767 &built.suppressed_aes,
768 &built.legend_plans,
769 )?;
770
771 Self::draw_tag(&built.labels, theme, &total_area, backend)?;
773
774 Ok(())
775 }
776
777 fn draw_annotations(
792 annotations: &[Annotation],
793 scales: &crate::scale::ScaleSet,
794 coord: &dyn crate::coord::Coord,
795 plot_area: &crate::render::Rect,
796 theme: &crate::theme::Theme,
797 backend: &mut dyn DrawBackend,
798 ) -> Result<(), RenderError> {
799 use crate::data::Value;
800
801 let x_scale = scales.get(&Aesthetic::X);
802 let y_scale = scales.get(&Aesthetic::Y);
803
804 for ann in annotations {
805 match ann {
806 Annotation::Text {
807 label,
808 x,
809 y,
810 size,
811 color,
812 } => {
813 let nx = x_scale.map(|s| s.map(&Value::Float(*x))).unwrap_or(0.0);
814 let ny = y_scale.map(|s| s.map(&Value::Float(*y))).unwrap_or(0.0);
815 let pos = coord.transform((nx, ny), plot_area);
816 backend.draw_text(
817 label,
818 pos,
819 &TextStyle {
820 color: annotation_ink(*color, theme),
821 size: *size,
822 anchor: TextAnchor::Middle,
823 angle: 0.0,
824 family: crate::geom::resolve_font_family("", theme),
825 face: crate::render::backend::FontFace::Plain,
826 bg_colour: None,
827 bg_r: 0.0,
828 },
829 )?;
830 }
831 Annotation::Rect {
832 xmin,
833 xmax,
834 ymin,
835 ymax,
836 fill,
837 alpha,
838 } => {
839 let nx0 = x_scale.map(|s| s.map(&Value::Float(*xmin))).unwrap_or(0.0);
840 let nx1 = x_scale.map(|s| s.map(&Value::Float(*xmax))).unwrap_or(1.0);
841 let ny0 = y_scale.map(|s| s.map(&Value::Float(*ymin))).unwrap_or(0.0);
842 let ny1 = y_scale.map(|s| s.map(&Value::Float(*ymax))).unwrap_or(1.0);
843 let tl = coord.transform((nx0, ny1), plot_area);
844 let br = coord.transform((nx1, ny0), plot_area);
845 backend.draw_rect(
846 tl,
847 br,
848 &RectStyle {
849 fill: Some(*fill),
850 stroke: None,
851 stroke_width: 0.0,
852 alpha: *alpha,
853 clip: false,
854 corner_radius: 0.0,
855 },
856 )?;
857 }
858 Annotation::Segment {
859 x,
860 y,
861 xend,
862 yend,
863 color,
864 width,
865 } => {
866 let nx0 = x_scale.map(|s| s.map(&Value::Float(*x))).unwrap_or(0.0);
867 let ny0 = y_scale.map(|s| s.map(&Value::Float(*y))).unwrap_or(0.0);
868 let nx1 = x_scale.map(|s| s.map(&Value::Float(*xend))).unwrap_or(1.0);
869 let ny1 = y_scale.map(|s| s.map(&Value::Float(*yend))).unwrap_or(1.0);
870 let p0 = coord.transform((nx0, ny0), plot_area);
871 let p1 = coord.transform((nx1, ny1), plot_area);
872 backend.draw_line(
873 &[p0, p1],
874 &LineStyle {
875 color: annotation_ink(*color, theme),
876 alpha: 1.0,
877 width: *width,
878 linetype: Linetype::Solid,
879 ..Default::default()
880 },
881 )?;
882 }
883 }
884 }
885 Ok(())
886 }
887}
888
889fn annotation_ink(color: (u8, u8, u8), theme: &crate::theme::Theme) -> (u8, u8, u8) {
895 match (color == (50, 50, 50), theme.geom.ink) {
896 (true, Some(ink)) => ink,
897 _ => color,
898 }
899}
900
901struct PanelBackendAdapter<'a> {
903 inner: &'a mut dyn DrawBackend,
904 panel_rect: crate::render::Rect,
905}
906
907impl<'a> DrawBackend for PanelBackendAdapter<'a> {
908 fn draw_circle(
909 &mut self,
910 center: (f64, f64),
911 radius: f64,
912 style: &crate::render::backend::PointStyle,
913 ) -> Result<(), RenderError> {
914 self.inner.draw_circle(center, radius, style)
915 }
916 fn draw_line(
917 &mut self,
918 points: &[(f64, f64)],
919 style: &crate::render::backend::LineStyle,
920 ) -> Result<(), RenderError> {
921 self.inner.draw_line(points, style)
922 }
923 fn draw_quadratic(
924 &mut self,
925 p0: (f64, f64),
926 ctrl: (f64, f64),
927 p1: (f64, f64),
928 style: &crate::render::backend::LineStyle,
929 ) -> Result<(), RenderError> {
930 self.inner.draw_quadratic(p0, ctrl, p1, style)
931 }
932 fn draw_rect(
933 &mut self,
934 top_left: (f64, f64),
935 bottom_right: (f64, f64),
936 style: &RectStyle,
937 ) -> Result<(), RenderError> {
938 self.inner.draw_rect(top_left, bottom_right, style)
939 }
940 fn draw_text(
941 &mut self,
942 text: &str,
943 pos: (f64, f64),
944 style: &TextStyle,
945 ) -> Result<(), RenderError> {
946 self.inner.draw_text(text, pos, style)
947 }
948 fn draw_polygon(
949 &mut self,
950 points: &[(f64, f64)],
951 style: &RectStyle,
952 ) -> Result<(), RenderError> {
953 self.inner.draw_polygon(points, style)
954 }
955 fn draw_polygon_rings(
956 &mut self,
957 rings: &[Vec<(f64, f64)>],
958 style: &RectStyle,
959 ) -> Result<(), RenderError> {
960 self.inner.draw_polygon_rings(rings, style)
961 }
962 fn draw_shape(
963 &mut self,
964 center: (f64, f64),
965 radius: f64,
966 style: &crate::render::backend::PointStyle,
967 ) -> Result<(), RenderError> {
968 self.inner.draw_shape(center, radius, style)
969 }
970 fn plot_area(&self) -> crate::render::Rect {
971 self.panel_rect.clone()
972 }
973 fn set_tooltip(&mut self, tooltip: Option<String>) {
974 self.inner.set_tooltip(tooltip)
975 }
976 fn set_mark_axis(&mut self, key: Option<String>) {
977 self.inner.set_mark_axis(key)
978 }
979 fn set_mark_series(&mut self, series: Option<String>) {
980 self.inner.set_mark_series(series)
981 }
982 fn set_mark_value(&mut self, value: Option<String>) {
983 self.inner.set_mark_value(value)
984 }
985 fn set_mark_geom(&mut self, geom: Option<&str>) {
986 self.inner.set_mark_geom(geom)
987 }
988 fn warn(&mut self, message: String) {
989 self.inner.warn(message)
990 }
991 fn set_mark_part(&mut self, part: Option<&str>) {
992 self.inner.set_mark_part(part)
993 }
994 fn set_mark_attrs(&mut self, attrs: Vec<(String, String)>) {
995 self.inner.set_mark_attrs(attrs)
996 }
997
998 fn set_mark_class(&mut self, class: Option<String>) {
999 self.inner.set_mark_class(class)
1000 }
1001
1002 fn set_mark_group(&mut self, group: Option<(String, String)>) {
1003 self.inner.set_mark_group(group)
1004 }
1005
1006 fn set_layer_attrs(&mut self, attrs: Vec<(String, String)>) {
1007 self.inner.set_layer_attrs(attrs)
1008 }
1009 fn wants_formatted_values(&self) -> bool {
1010 self.inner.wants_formatted_values()
1011 }
1012 fn set_mark_fvalue(&mut self, value: Option<String>) {
1013 self.inner.set_mark_fvalue(value)
1014 }
1015 fn set_mark_fx(&mut self, value: Option<String>) {
1016 self.inner.set_mark_fx(value)
1017 }
1018 fn total_area(&self) -> crate::render::Rect {
1019 self.inner.total_area()
1020 }
1021 fn corner_claims(&self) -> Vec<crate::render::Rect> {
1022 self.inner.corner_claims()
1023 }
1024 fn claim_corner_box(&mut self, rect: crate::render::Rect) {
1025 self.inner.claim_corner_box(rect)
1026 }
1027 fn clear_corner_claims(&mut self) {
1028 self.inner.clear_corner_claims()
1029 }
1030}
1031
1032#[cfg(test)]
1033mod tests {
1034 use super::*;
1035 use crate::render::backend::LineStyle;
1036 use crate::render::svg_backend::SvgBackend;
1037
1038 #[test]
1044 fn panel_backend_adapter_forwards_quadratic_and_mark_metadata() {
1045 let total = crate::render::Rect {
1046 x: 0.0,
1047 y: 0.0,
1048 width: 200.0,
1049 height: 200.0,
1050 };
1051 let panel_rect = crate::render::Rect {
1052 x: 10.0,
1053 y: 10.0,
1054 width: 80.0,
1055 height: 80.0,
1056 };
1057 let mut backend = SvgBackend::new(200, 200, total);
1058 let mut adapter = PanelBackendAdapter {
1059 inner: &mut backend,
1060 panel_rect: panel_rect.clone(),
1061 };
1062
1063 assert_eq!(adapter.plot_area().x, panel_rect.x);
1064 assert_eq!(adapter.plot_area().width, panel_rect.width);
1065
1066 adapter.set_mark_part(Some("arrow"));
1067 adapter.set_mark_attrs(vec![("data-x".to_string(), "v".to_string())]);
1068 adapter
1069 .draw_quadratic((0.0, 0.0), (5.0, 10.0), (10.0, 0.0), &LineStyle::default())
1070 .unwrap();
1071 adapter.set_mark_part(None);
1072 adapter.set_mark_attrs(Vec::new());
1073
1074 let svg = backend.finish();
1075 assert!(svg.contains("<path"));
1076 assert!(svg.contains("data-part=\"arrow\""));
1077 assert!(svg.contains("data-x=\"v\""));
1078 }
1079}