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 );
192 if let Some(ref title) = built.labels.title {
193 let (tx, anchor) = Self::hjust_pos(&theme.title, &title_ref);
194 let x_axis_top = built
197 .scales
198 .get(&Aesthetic::X)
199 .map(|s| s.axis_position_opposite())
200 .unwrap_or(false);
201 let x_axis_lift = if x_axis_top {
202 theme.axis_ticks_length + theme.axis_text_x.size + theme.legend_spacing
203 } else {
204 0.0
205 };
206 let title_y = plot_area.y - theme.title.size * 0.9 - x_axis_lift - legend_lift;
207 let family = if theme.title.family.is_empty() {
208 None
209 } else {
210 Some(theme.title.family.clone())
211 };
212 backend.draw_text(
213 title,
214 (tx, title_y.max(theme.title.size)),
215 &TextStyle {
216 color: theme.title.color,
217 size: theme.title.size,
218 anchor,
219 angle: 0.0,
220 family,
221 face: theme.title.face,
222 bg_colour: None,
223 bg_r: 0.0,
224 },
225 )?;
226 }
227
228 if let Some(ref subtitle) = built.labels.subtitle {
230 let (sx, anchor) = Self::hjust_pos(&theme.subtitle, &title_ref);
231 let subtitle_y = plot_area.y - 2.0 - legend_lift;
232 let family = if theme.subtitle.family.is_empty() {
233 None
234 } else {
235 Some(theme.subtitle.family.clone())
236 };
237 backend.draw_text(
238 subtitle,
239 (sx, subtitle_y.max(theme.title.size + theme.subtitle.size)),
240 &TextStyle {
241 color: theme.subtitle.color,
242 size: theme.subtitle.size,
243 anchor,
244 angle: 0.0,
245 family,
246 face: theme.subtitle.face,
247 bg_colour: None,
248 bg_r: 0.0,
249 },
250 )?;
251 }
252
253 legend::draw_legend_planned(
255 &built.scales,
256 theme,
257 &plot_area,
258 plot_area.width,
259 backend,
260 &built.guide_legend,
261 &built.suppressed_aes,
262 &built.legend_plans,
263 )?;
264
265 if let Some(ref caption) = built.labels.caption {
267 let (cx, anchor) = Self::hjust_pos(&theme.caption, &title_ref);
268 let caption_y = total_area.y + total_area.height - theme.caption.size * 0.5;
269 let family = if theme.caption.family.is_empty() {
270 None
271 } else {
272 Some(theme.caption.family.clone())
273 };
274 backend.draw_text(
275 caption,
276 (cx, caption_y),
277 &TextStyle {
278 color: theme.caption.color,
279 size: theme.caption.size,
280 anchor,
281 angle: 0.0,
282 family,
283 face: theme.caption.face,
284 bg_colour: None,
285 bg_r: 0.0,
286 },
287 )?;
288 }
289
290 Self::draw_tag(&built.labels, theme, &total_area, backend)?;
292
293 Ok(())
294 }
295
296 fn hjust_pos(
299 el: &crate::theme::elements::ElementText,
300 area: &crate::render::Rect,
301 ) -> (f64, TextAnchor) {
302 let x = area.x + el.hjust.clamp(0.0, 1.0) * area.width;
303 let anchor = if el.hjust <= 0.02 {
304 TextAnchor::Start
305 } else if el.hjust >= 0.98 {
306 TextAnchor::End
307 } else {
308 TextAnchor::Middle
309 };
310 (x, anchor)
311 }
312
313 fn title_ref_rect(
316 theme: &crate::theme::Theme,
317 plot_area: &crate::render::Rect,
318 total_area: &crate::render::Rect,
319 ) -> crate::render::Rect {
320 match theme.title_position {
321 crate::theme::TitlePosition::Panel => plot_area.clone(),
322 crate::theme::TitlePosition::Plot => crate::render::Rect {
323 x: total_area.x + theme.plot_margin.left,
324 y: plot_area.y,
325 width: (total_area.width - theme.plot_margin.left - theme.plot_margin.right)
326 .max(1.0),
327 height: plot_area.height,
328 },
329 }
330 }
331
332 fn draw_tag(
334 labels: &crate::plot::Labels,
335 theme: &crate::theme::Theme,
336 total_area: &crate::render::Rect,
337 backend: &mut dyn DrawBackend,
338 ) -> Result<(), RenderError> {
339 if let Some(ref tag) = labels.tag {
340 let family = if theme.title.family.is_empty() {
341 None
342 } else {
343 Some(theme.title.family.clone())
344 };
345 use crate::theme::TagPosition::*;
346 let pad = theme.title.size;
347 let (left, right) = (total_area.x + pad, total_area.x + total_area.width - pad);
348 let (top, bottom) = (total_area.y + pad, total_area.y + total_area.height - pad);
349 let (x, y, anchor) = match theme.tag_position {
350 TopLeft => (left, top, TextAnchor::Start),
351 TopRight => (right, top, TextAnchor::End),
352 BottomLeft => (left, bottom, TextAnchor::Start),
353 BottomRight => (right, bottom, TextAnchor::End),
354 };
355 backend.draw_text(
356 tag,
357 (x, y),
358 &TextStyle {
359 color: theme.title.color,
360 size: theme.title.size,
361 anchor,
362 angle: 0.0,
363 family,
364 face: theme.title.face,
365 bg_colour: None,
366 bg_r: 0.0,
367 },
368 )?;
369 }
370 Ok(())
371 }
372
373 fn render_faceted(built: &BuiltPlot, backend: &mut dyn DrawBackend) -> Result<(), RenderError> {
374 let theme = &built.theme;
375 let plot_area = backend.plot_area();
376 let total_area = backend.total_area();
377
378 if theme.plot_background.visible {
380 if let Some(fill) = theme.plot_background.fill {
381 backend.draw_rect(
382 (total_area.x, total_area.y),
383 (
384 total_area.x + total_area.width,
385 total_area.y + total_area.height,
386 ),
387 &RectStyle {
388 fill: Some(fill),
389 stroke: None,
390 stroke_width: 0.0,
391 alpha: 1.0,
392 clip: false,
393 corner_radius: 0.0,
394 },
395 )?;
396 }
397 }
398
399 let ncol = match &built.facet {
401 Facet::Wrap { ncol, .. } => {
402 ncol.unwrap_or_else(|| (built.panels.len() as f64).sqrt().ceil() as usize)
403 }
404 Facet::Grid { .. } => built.panels.iter().map(|p| p.col).max().unwrap_or(0) + 1,
405 Facet::None => 1,
406 };
407 let nrow = built.panels.len().div_ceil(ncol);
408
409 let strip_height = theme.strip_text.size + 8.0;
410 let gap_x = theme.get_panel_spacing_x();
411 let gap_y = theme.get_panel_spacing_y();
412
413 let space = match &built.facet {
416 Facet::Grid { space, .. } => space.clone(),
417 _ => crate::facet::FacetSpace::Fixed,
418 };
419 let extent = |pi: usize, col: &str| -> Option<f64> {
420 let (mut lo, mut hi) = (f64::INFINITY, f64::NEG_INFINITY);
421 for df in &built.panels_data[pi] {
422 if let Some(c) = df.column(col) {
423 for v in c {
424 if let Some(f) = v.as_f64() {
425 lo = lo.min(f);
426 hi = hi.max(f);
427 }
428 }
429 }
430 }
431 (lo <= hi).then_some(hi - lo)
432 };
433 let avail_w = plot_area.width - gap_x * (ncol as f64 - 1.0);
434 let col_widths: Vec<f64> = if space.free_x() && ncol > 0 {
435 let mut ranges = vec![0.0f64; ncol];
436 for (pi, panel) in built.panels.iter().enumerate() {
437 if let Some(r) = extent(pi, "x") {
438 ranges[panel.col] = ranges[panel.col].max(r);
439 }
440 }
441 let total: f64 = ranges.iter().sum();
442 if total > 0.0 {
443 ranges.iter().map(|r| avail_w * (r / total)).collect()
444 } else {
445 vec![avail_w / ncol as f64; ncol]
446 }
447 } else {
448 vec![avail_w / ncol.max(1) as f64; ncol.max(1)]
449 };
450 let mut col_x = vec![plot_area.x; ncol.max(1)];
451 {
452 let mut acc = plot_area.x;
453 for c in 0..ncol {
454 col_x[c] = acc;
455 acc += col_widths[c] + gap_x;
456 }
457 }
458
459 let avail_h = plot_area.height - gap_y * (nrow as f64 - 1.0) - strip_height * nrow as f64;
460 let row_heights: Vec<f64> = if space.free_y() && nrow > 0 {
461 let mut ranges = vec![0.0f64; nrow];
462 for (pi, panel) in built.panels.iter().enumerate() {
463 if let Some(r) = extent(pi, "y") {
464 ranges[panel.row] = ranges[panel.row].max(r);
465 }
466 }
467 let total: f64 = ranges.iter().sum();
468 if total > 0.0 {
469 ranges.iter().map(|r| avail_h * (r / total)).collect()
470 } else {
471 vec![avail_h / nrow as f64; nrow]
472 }
473 } else {
474 vec![avail_h / nrow.max(1) as f64; nrow.max(1)]
475 };
476 let mut row_y = vec![plot_area.y; nrow.max(1)];
477 {
478 let mut acc = plot_area.y;
479 for r in 0..nrow {
480 row_y[r] = acc;
481 acc += row_heights[r] + strip_height + gap_y;
482 }
483 }
484
485 for (pi, panel) in built.panels.iter().enumerate() {
486 let panel_width = col_widths[panel.col];
487 let panel_height = row_heights[panel.row];
488 let px = col_x[panel.col];
489 let py = row_y[panel.row];
490
491 let panel_rect = crate::render::Rect {
492 x: px,
493 y: py + strip_height,
494 width: panel_width,
495 height: panel_height,
496 };
497 backend.clear_corner_claims();
505
506 if theme.strip_background.visible {
508 backend.draw_rect(
509 (px, py),
510 (px + panel_width, py + strip_height),
511 &RectStyle {
512 fill: theme.strip_background.fill,
513 stroke: theme.strip_background.color,
514 stroke_width: theme.strip_background.width,
515 alpha: 1.0,
516 clip: false,
517 corner_radius: 0.0,
518 },
519 )?;
520 }
521
522 if theme.strip_text.visible {
524 let label = panel.col_label.as_deref().unwrap_or(&panel.label);
525 let family = if theme.strip_text.family.is_empty() {
526 None
527 } else {
528 Some(theme.strip_text.family.clone())
529 };
530 backend.draw_text(
531 label,
532 (px + panel_width / 2.0, py + strip_height / 2.0),
533 &TextStyle {
534 color: theme.strip_text.color,
535 size: theme.strip_text.size,
536 anchor: TextAnchor::Middle,
537 angle: 0.0,
538 family,
539 face: theme.strip_text.face,
540 bg_colour: None,
541 bg_r: 0.0,
542 },
543 )?;
544 }
545
546 if theme.panel_background.visible {
548 if let Some(fill) = theme.panel_background.fill {
549 backend.draw_rect(
550 (panel_rect.x, panel_rect.y),
551 (
552 panel_rect.x + panel_rect.width,
553 panel_rect.y + panel_rect.height,
554 ),
555 &RectStyle {
556 fill: Some(fill),
557 stroke: theme.panel_background.color,
558 stroke_width: theme.panel_background.width,
559 alpha: 1.0,
560 clip: false,
561 corner_radius: 0.0,
562 },
563 )?;
564 }
565 }
566
567 if theme.panel_border.visible {
569 let style = LineStyle {
570 color: theme.panel_border.color,
571 width: theme.panel_border.width,
572 alpha: 1.0,
573 linetype: Linetype::Solid,
574 ..Default::default()
575 };
576 let x0 = panel_rect.x;
577 let y0 = panel_rect.y;
578 let x1 = panel_rect.x + panel_rect.width;
579 let y1 = panel_rect.y + panel_rect.height;
580 backend.draw_line(&[(x0, y0), (x1, y0)], &style)?;
581 backend.draw_line(&[(x1, y0), (x1, y1)], &style)?;
582 backend.draw_line(&[(x1, y1), (x0, y1)], &style)?;
583 backend.draw_line(&[(x0, y1), (x0, y0)], &style)?;
584 }
585
586 let panel_scale_set = if pi < built.panel_scales.len() {
588 &built.panel_scales[pi]
589 } else {
590 &built.scales
591 };
592
593 let x_scale = panel_scale_set.get(&Aesthetic::X);
595 let y_scale = panel_scale_set.get(&Aesthetic::Y);
596
597 if let (Some(xs), Some(ys)) = (x_scale, y_scale) {
598 if built.coord.gridlines() {
599 axis::draw_gridlines(
600 xs,
601 ys,
602 built.coord.as_ref(),
603 theme,
604 &panel_rect,
605 backend,
606 )?;
607 }
608
609 if panel.row == nrow - 1 || pi + ncol >= built.panels.len() {
611 axis::draw_x_axis(
612 xs,
613 built.coord.as_ref(),
614 theme,
615 &panel_rect,
616 &total_area,
617 backend,
618 )?;
619 }
620
621 if panel.col == 0 {
623 axis::draw_y_axis(ys, built.coord.as_ref(), theme, &panel_rect, backend)?;
624 }
625 }
626
627 if pi < built.panels_data.len() {
629 for (li, layer_data) in built.panels_data[pi].iter().enumerate() {
630 if li < built.layers.len() && layer_data.nrows() > 0 {
631 let mut panel_backend = PanelBackendAdapter {
632 inner: backend,
633 panel_rect: panel_rect.clone(),
634 };
635 let geom = &built.layers[li].geom;
636 panel_backend.set_mark_geom(Some(geom.name()));
637 let drawn = geom.draw(
638 layer_data,
639 built.coord.as_ref(),
640 panel_scale_set,
641 theme,
642 &mut panel_backend,
643 );
644 panel_backend.set_mark_geom(None);
645 drawn?;
646 }
647 }
648 }
649 }
650
651 let title_ref = Self::title_ref_rect(theme, &plot_area, &total_area);
653 let legend_lift = legend::top_legend_lift(
656 theme,
657 &built.scales,
658 &built.suppressed_aes,
659 &built.legend_plans,
660 );
661 if let Some(ref title) = built.labels.title {
662 let (tx, anchor) = Self::hjust_pos(&theme.title, &title_ref);
663 let title_y = plot_area.y - theme.title.size * 0.9 - legend_lift;
664 let family = if theme.title.family.is_empty() {
665 None
666 } else {
667 Some(theme.title.family.clone())
668 };
669 backend.draw_text(
670 title,
671 (tx, title_y.max(theme.title.size)),
672 &TextStyle {
673 color: theme.title.color,
674 size: theme.title.size,
675 anchor,
676 angle: 0.0,
677 family,
678 face: theme.title.face,
679 bg_colour: None,
680 bg_r: 0.0,
681 },
682 )?;
683 }
684
685 if let Some(ref subtitle) = built.labels.subtitle {
687 let (sx, anchor) = Self::hjust_pos(&theme.subtitle, &title_ref);
688 let subtitle_y = plot_area.y - 2.0 - legend_lift;
689 let family = if theme.subtitle.family.is_empty() {
690 None
691 } else {
692 Some(theme.subtitle.family.clone())
693 };
694 backend.draw_text(
695 subtitle,
696 (sx, subtitle_y.max(theme.title.size + theme.subtitle.size)),
697 &TextStyle {
698 color: theme.subtitle.color,
699 size: theme.subtitle.size,
700 anchor,
701 angle: 0.0,
702 family,
703 face: theme.subtitle.face,
704 bg_colour: None,
705 bg_r: 0.0,
706 },
707 )?;
708 }
709
710 if let Some(ref caption) = built.labels.caption {
712 let (cx, anchor) = Self::hjust_pos(&theme.caption, &title_ref);
713 let caption_y = total_area.y + total_area.height - theme.caption.size * 0.5;
714 let family = if theme.caption.family.is_empty() {
715 None
716 } else {
717 Some(theme.caption.family.clone())
718 };
719 backend.draw_text(
720 caption,
721 (cx, caption_y),
722 &TextStyle {
723 color: theme.caption.color,
724 size: theme.caption.size,
725 anchor,
726 angle: 0.0,
727 family,
728 face: theme.caption.face,
729 bg_colour: None,
730 bg_r: 0.0,
731 },
732 )?;
733 }
734
735 Self::draw_annotations(
737 &built.annotations,
738 &built.scales,
739 built.coord.as_ref(),
740 &plot_area,
741 theme,
742 backend,
743 )?;
744
745 legend::draw_legend_planned(
747 &built.scales,
748 theme,
749 &plot_area,
750 plot_area.width,
751 backend,
752 &built.guide_legend,
753 &built.suppressed_aes,
754 &built.legend_plans,
755 )?;
756
757 Self::draw_tag(&built.labels, theme, &total_area, backend)?;
759
760 Ok(())
761 }
762
763 fn draw_annotations(
778 annotations: &[Annotation],
779 scales: &crate::scale::ScaleSet,
780 coord: &dyn crate::coord::Coord,
781 plot_area: &crate::render::Rect,
782 theme: &crate::theme::Theme,
783 backend: &mut dyn DrawBackend,
784 ) -> Result<(), RenderError> {
785 use crate::data::Value;
786
787 let x_scale = scales.get(&Aesthetic::X);
788 let y_scale = scales.get(&Aesthetic::Y);
789
790 for ann in annotations {
791 match ann {
792 Annotation::Text {
793 label,
794 x,
795 y,
796 size,
797 color,
798 } => {
799 let nx = x_scale.map(|s| s.map(&Value::Float(*x))).unwrap_or(0.0);
800 let ny = y_scale.map(|s| s.map(&Value::Float(*y))).unwrap_or(0.0);
801 let pos = coord.transform((nx, ny), plot_area);
802 backend.draw_text(
803 label,
804 pos,
805 &TextStyle {
806 color: annotation_ink(*color, theme),
807 size: *size,
808 anchor: TextAnchor::Middle,
809 angle: 0.0,
810 family: crate::geom::resolve_font_family("", theme),
811 face: crate::render::backend::FontFace::Plain,
812 bg_colour: None,
813 bg_r: 0.0,
814 },
815 )?;
816 }
817 Annotation::Rect {
818 xmin,
819 xmax,
820 ymin,
821 ymax,
822 fill,
823 alpha,
824 } => {
825 let nx0 = x_scale.map(|s| s.map(&Value::Float(*xmin))).unwrap_or(0.0);
826 let nx1 = x_scale.map(|s| s.map(&Value::Float(*xmax))).unwrap_or(1.0);
827 let ny0 = y_scale.map(|s| s.map(&Value::Float(*ymin))).unwrap_or(0.0);
828 let ny1 = y_scale.map(|s| s.map(&Value::Float(*ymax))).unwrap_or(1.0);
829 let tl = coord.transform((nx0, ny1), plot_area);
830 let br = coord.transform((nx1, ny0), plot_area);
831 backend.draw_rect(
832 tl,
833 br,
834 &RectStyle {
835 fill: Some(*fill),
836 stroke: None,
837 stroke_width: 0.0,
838 alpha: *alpha,
839 clip: false,
840 corner_radius: 0.0,
841 },
842 )?;
843 }
844 Annotation::Segment {
845 x,
846 y,
847 xend,
848 yend,
849 color,
850 width,
851 } => {
852 let nx0 = x_scale.map(|s| s.map(&Value::Float(*x))).unwrap_or(0.0);
853 let ny0 = y_scale.map(|s| s.map(&Value::Float(*y))).unwrap_or(0.0);
854 let nx1 = x_scale.map(|s| s.map(&Value::Float(*xend))).unwrap_or(1.0);
855 let ny1 = y_scale.map(|s| s.map(&Value::Float(*yend))).unwrap_or(1.0);
856 let p0 = coord.transform((nx0, ny0), plot_area);
857 let p1 = coord.transform((nx1, ny1), plot_area);
858 backend.draw_line(
859 &[p0, p1],
860 &LineStyle {
861 color: annotation_ink(*color, theme),
862 alpha: 1.0,
863 width: *width,
864 linetype: Linetype::Solid,
865 ..Default::default()
866 },
867 )?;
868 }
869 }
870 }
871 Ok(())
872 }
873}
874
875fn annotation_ink(color: (u8, u8, u8), theme: &crate::theme::Theme) -> (u8, u8, u8) {
881 match (color == (50, 50, 50), theme.geom.ink) {
882 (true, Some(ink)) => ink,
883 _ => color,
884 }
885}
886
887struct PanelBackendAdapter<'a> {
889 inner: &'a mut dyn DrawBackend,
890 panel_rect: crate::render::Rect,
891}
892
893impl<'a> DrawBackend for PanelBackendAdapter<'a> {
894 fn draw_circle(
895 &mut self,
896 center: (f64, f64),
897 radius: f64,
898 style: &crate::render::backend::PointStyle,
899 ) -> Result<(), RenderError> {
900 self.inner.draw_circle(center, radius, style)
901 }
902 fn draw_line(
903 &mut self,
904 points: &[(f64, f64)],
905 style: &crate::render::backend::LineStyle,
906 ) -> Result<(), RenderError> {
907 self.inner.draw_line(points, style)
908 }
909 fn draw_quadratic(
910 &mut self,
911 p0: (f64, f64),
912 ctrl: (f64, f64),
913 p1: (f64, f64),
914 style: &crate::render::backend::LineStyle,
915 ) -> Result<(), RenderError> {
916 self.inner.draw_quadratic(p0, ctrl, p1, style)
917 }
918 fn draw_rect(
919 &mut self,
920 top_left: (f64, f64),
921 bottom_right: (f64, f64),
922 style: &RectStyle,
923 ) -> Result<(), RenderError> {
924 self.inner.draw_rect(top_left, bottom_right, style)
925 }
926 fn draw_text(
927 &mut self,
928 text: &str,
929 pos: (f64, f64),
930 style: &TextStyle,
931 ) -> Result<(), RenderError> {
932 self.inner.draw_text(text, pos, style)
933 }
934 fn draw_polygon(
935 &mut self,
936 points: &[(f64, f64)],
937 style: &RectStyle,
938 ) -> Result<(), RenderError> {
939 self.inner.draw_polygon(points, style)
940 }
941 fn draw_polygon_rings(
942 &mut self,
943 rings: &[Vec<(f64, f64)>],
944 style: &RectStyle,
945 ) -> Result<(), RenderError> {
946 self.inner.draw_polygon_rings(rings, style)
947 }
948 fn draw_shape(
949 &mut self,
950 center: (f64, f64),
951 radius: f64,
952 style: &crate::render::backend::PointStyle,
953 ) -> Result<(), RenderError> {
954 self.inner.draw_shape(center, radius, style)
955 }
956 fn plot_area(&self) -> crate::render::Rect {
957 self.panel_rect.clone()
958 }
959 fn set_tooltip(&mut self, tooltip: Option<String>) {
960 self.inner.set_tooltip(tooltip)
961 }
962 fn set_mark_axis(&mut self, key: Option<String>) {
963 self.inner.set_mark_axis(key)
964 }
965 fn set_mark_series(&mut self, series: Option<String>) {
966 self.inner.set_mark_series(series)
967 }
968 fn set_mark_value(&mut self, value: Option<String>) {
969 self.inner.set_mark_value(value)
970 }
971 fn set_mark_geom(&mut self, geom: Option<&str>) {
972 self.inner.set_mark_geom(geom)
973 }
974 fn warn(&mut self, message: String) {
975 self.inner.warn(message)
976 }
977 fn set_mark_part(&mut self, part: Option<&str>) {
978 self.inner.set_mark_part(part)
979 }
980 fn set_mark_attrs(&mut self, attrs: Vec<(String, String)>) {
981 self.inner.set_mark_attrs(attrs)
982 }
983
984 fn set_mark_class(&mut self, class: Option<String>) {
985 self.inner.set_mark_class(class)
986 }
987
988 fn set_mark_group(&mut self, group: Option<(String, String)>) {
989 self.inner.set_mark_group(group)
990 }
991
992 fn set_layer_attrs(&mut self, attrs: Vec<(String, String)>) {
993 self.inner.set_layer_attrs(attrs)
994 }
995 fn wants_formatted_values(&self) -> bool {
996 self.inner.wants_formatted_values()
997 }
998 fn set_mark_fvalue(&mut self, value: Option<String>) {
999 self.inner.set_mark_fvalue(value)
1000 }
1001 fn set_mark_fx(&mut self, value: Option<String>) {
1002 self.inner.set_mark_fx(value)
1003 }
1004 fn total_area(&self) -> crate::render::Rect {
1005 self.inner.total_area()
1006 }
1007 fn corner_claims(&self) -> Vec<crate::render::Rect> {
1008 self.inner.corner_claims()
1009 }
1010 fn claim_corner_box(&mut self, rect: crate::render::Rect) {
1011 self.inner.claim_corner_box(rect)
1012 }
1013 fn clear_corner_claims(&mut self) {
1014 self.inner.clear_corner_claims()
1015 }
1016}
1017
1018#[cfg(test)]
1019mod tests {
1020 use super::*;
1021 use crate::render::backend::LineStyle;
1022 use crate::render::svg_backend::SvgBackend;
1023
1024 #[test]
1030 fn panel_backend_adapter_forwards_quadratic_and_mark_metadata() {
1031 let total = crate::render::Rect {
1032 x: 0.0,
1033 y: 0.0,
1034 width: 200.0,
1035 height: 200.0,
1036 };
1037 let panel_rect = crate::render::Rect {
1038 x: 10.0,
1039 y: 10.0,
1040 width: 80.0,
1041 height: 80.0,
1042 };
1043 let mut backend = SvgBackend::new(200, 200, total);
1044 let mut adapter = PanelBackendAdapter {
1045 inner: &mut backend,
1046 panel_rect: panel_rect.clone(),
1047 };
1048
1049 assert_eq!(adapter.plot_area().x, panel_rect.x);
1050 assert_eq!(adapter.plot_area().width, panel_rect.width);
1051
1052 adapter.set_mark_part(Some("arrow"));
1053 adapter.set_mark_attrs(vec![("data-x".to_string(), "v".to_string())]);
1054 adapter
1055 .draw_quadratic((0.0, 0.0), (5.0, 10.0), (10.0, 0.0), &LineStyle::default())
1056 .unwrap();
1057 adapter.set_mark_part(None);
1058 adapter.set_mark_attrs(Vec::new());
1059
1060 let svg = backend.finish();
1061 assert!(svg.contains("<path"));
1062 assert!(svg.contains("data-part=\"arrow\""));
1063 assert!(svg.contains("data-x=\"v\""));
1064 }
1065}