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(r) = built.coord.data_rect(&plot_area) {
32 backend.set_viewport(&r);
33 }
34 if let Some(rect) = legend::inside_legend_box(
35 &built.scales,
36 theme,
37 &plot_area,
38 &total_area,
39 &built.guide_legend,
40 &built.suppressed_aes,
41 &built.legend_plans,
42 ) {
43 backend.claim_corner_box(rect);
44 }
45
46 if theme.plot_background.visible {
48 if let Some(fill) = theme.plot_background.fill {
49 backend.draw_rect(
50 (total_area.x, total_area.y),
51 (
52 total_area.x + total_area.width,
53 total_area.y + total_area.height,
54 ),
55 &RectStyle {
56 fill: Some(fill),
57 stroke: None,
58 stroke_width: 0.0,
59 alpha: 1.0,
60 clip: false,
61 corner_radius: 0.0,
62 },
63 )?;
64 }
65 }
66
67 if theme.panel_background.visible {
69 if let Some(fill) = theme.panel_background.fill {
70 backend.draw_rect(
71 (plot_area.x, plot_area.y),
72 (
73 plot_area.x + plot_area.width,
74 plot_area.y + plot_area.height,
75 ),
76 &RectStyle {
77 fill: Some(fill),
78 stroke: theme.panel_background.color,
79 stroke_width: theme.panel_background.width,
80 alpha: 1.0,
81 clip: false,
82 corner_radius: 0.0,
83 },
84 )?;
85 }
86 }
87
88 if theme.panel_border.visible {
90 let style = LineStyle {
91 color: theme.panel_border.color,
92 width: theme.panel_border.width,
93 alpha: 1.0,
94 linetype: Linetype::Solid,
95 ..Default::default()
96 };
97 let x0 = plot_area.x;
98 let y0 = plot_area.y;
99 let x1 = plot_area.x + plot_area.width;
100 let y1 = plot_area.y + plot_area.height;
101 backend.draw_line(&[(x0, y0), (x1, y0)], &style)?;
102 backend.draw_line(&[(x1, y0), (x1, y1)], &style)?;
103 backend.draw_line(&[(x1, y1), (x0, y1)], &style)?;
104 backend.draw_line(&[(x0, y1), (x0, y0)], &style)?;
105 }
106
107 let x_scale = built.scales.get(&Aesthetic::X);
109 let y_scale = built.scales.get(&Aesthetic::Y);
110
111 let (h_scale, v_scale) = if built.coord.is_flipped() {
112 (y_scale, x_scale)
113 } else {
114 (x_scale, y_scale)
115 };
116
117 if let (true, Some(xs), Some(ys)) = (built.coord.is_radar(), x_scale, y_scale) {
118 crate::guide::polar::draw_radar_guides(
120 xs,
121 ys,
122 built.coord.as_ref(),
123 theme,
124 &plot_area,
125 backend,
126 )?;
127 } else if let (Some(hs), Some(vs)) = (h_scale, v_scale) {
128 if built.coord.gridlines() && !theme.panel_ontop {
130 axis::draw_gridlines(hs, vs, built.coord.as_ref(), theme, &plot_area, backend)?;
131 }
132
133 axis::draw_x_axis(
135 hs,
136 built.coord.as_ref(),
137 theme,
138 &plot_area,
139 &total_area,
140 backend,
141 )?;
142 axis::draw_y_axis(vs, built.coord.as_ref(), theme, &plot_area, backend)?;
143
144 if let Some(sec) = built.scales.sec_axis(&Aesthetic::Y) {
146 axis::draw_sec_y_axis(vs, sec, built.coord.as_ref(), theme, &plot_area, backend)?;
147 }
148 }
149
150 for layer in built.layers.iter().filter(|l| l.data.nrows() > 0) {
154 backend.set_mark_geom(Some(layer.geom.name()));
155 let drawn = layer.geom.draw(
156 &layer.data,
157 built.coord.as_ref(),
158 &built.scales,
159 theme,
160 backend,
161 );
162 backend.set_mark_geom(None);
163 drawn?;
164 }
165
166 if theme.panel_ontop && built.coord.gridlines() {
168 if let (Some(hs), Some(vs)) = (h_scale, v_scale) {
169 axis::draw_gridlines(hs, vs, built.coord.as_ref(), theme, &plot_area, backend)?;
170 }
171 }
172
173 Self::draw_annotations(
175 &built.annotations,
176 &built.scales,
177 built.coord.as_ref(),
178 &plot_area,
179 theme,
180 backend,
181 )?;
182
183 let title_ref = Self::title_ref_rect(theme, &plot_area, &total_area);
185 let legend_lift = legend::top_legend_lift(
190 theme,
191 &built.scales,
192 &built.suppressed_aes,
193 &built.legend_plans,
194 &built.guide_legend,
195 );
196 if let Some(ref title) = built.labels.title {
197 let (tx, anchor) = Self::hjust_pos(&theme.title, &title_ref);
198 let x_axis_top = built
201 .scales
202 .get(&Aesthetic::X)
203 .map(|s| s.axis_position_opposite())
204 .unwrap_or(false);
205 let x_axis_lift = if x_axis_top {
206 theme.axis_ticks_length + theme.axis_text_x.size + theme.legend_spacing
207 } else {
208 0.0
209 };
210 let title_y = plot_area.y - theme.title.size * 0.9 - x_axis_lift - legend_lift;
211 let family = if theme.title.family.is_empty() {
212 None
213 } else {
214 Some(theme.title.family.clone())
215 };
216 backend.draw_text(
217 title,
218 (tx, title_y.max(theme.title.size)),
219 &TextStyle {
220 color: theme.title.color,
221 size: theme.title.size,
222 anchor,
223 angle: 0.0,
224 family,
225 face: theme.title.face,
226 bg_colour: None,
227 bg_r: 0.0,
228 },
229 )?;
230 }
231
232 if let Some(ref subtitle) = built.labels.subtitle {
234 let (sx, anchor) = Self::hjust_pos(&theme.subtitle, &title_ref);
235 let subtitle_y =
244 (plot_area.y - 2.0).max(theme.title.size + theme.subtitle.size) - legend_lift;
245 let family = if theme.subtitle.family.is_empty() {
246 None
247 } else {
248 Some(theme.subtitle.family.clone())
249 };
250 backend.draw_text(
251 subtitle,
252 (sx, subtitle_y),
253 &TextStyle {
254 color: theme.subtitle.color,
255 size: theme.subtitle.size,
256 anchor,
257 angle: 0.0,
258 family,
259 face: theme.subtitle.face,
260 bg_colour: None,
261 bg_r: 0.0,
262 },
263 )?;
264 }
265
266 legend::draw_legend_planned(
268 &built.scales,
269 theme,
270 &plot_area,
271 plot_area.width,
272 backend,
273 &built.guide_legend,
274 &built.suppressed_aes,
275 &built.legend_plans,
276 )?;
277
278 if let Some(ref caption) = built.labels.caption {
280 let (cx, anchor) = Self::hjust_pos(&theme.caption, &title_ref);
281 let caption_y = total_area.y + total_area.height - theme.caption.size * 0.5;
282 let family = if theme.caption.family.is_empty() {
283 None
284 } else {
285 Some(theme.caption.family.clone())
286 };
287 backend.draw_text(
288 caption,
289 (cx, caption_y),
290 &TextStyle {
291 color: theme.caption.color,
292 size: theme.caption.size,
293 anchor,
294 angle: 0.0,
295 family,
296 face: theme.caption.face,
297 bg_colour: None,
298 bg_r: 0.0,
299 },
300 )?;
301 }
302
303 Self::draw_tag(&built.labels, theme, &total_area, backend)?;
305
306 Ok(())
307 }
308
309 fn hjust_pos(
312 el: &crate::theme::elements::ElementText,
313 area: &crate::render::Rect,
314 ) -> (f64, TextAnchor) {
315 let x = area.x + el.hjust.clamp(0.0, 1.0) * area.width;
316 let anchor = if el.hjust <= 0.02 {
317 TextAnchor::Start
318 } else if el.hjust >= 0.98 {
319 TextAnchor::End
320 } else {
321 TextAnchor::Middle
322 };
323 (x, anchor)
324 }
325
326 fn title_ref_rect(
329 theme: &crate::theme::Theme,
330 plot_area: &crate::render::Rect,
331 total_area: &crate::render::Rect,
332 ) -> crate::render::Rect {
333 match theme.title_position {
334 crate::theme::TitlePosition::Panel => plot_area.clone(),
335 crate::theme::TitlePosition::Plot => crate::render::Rect {
336 x: total_area.x + theme.plot_margin.left,
337 y: plot_area.y,
338 width: (total_area.width - theme.plot_margin.left - theme.plot_margin.right)
339 .max(1.0),
340 height: plot_area.height,
341 },
342 }
343 }
344
345 fn draw_tag(
347 labels: &crate::plot::Labels,
348 theme: &crate::theme::Theme,
349 total_area: &crate::render::Rect,
350 backend: &mut dyn DrawBackend,
351 ) -> Result<(), RenderError> {
352 if let Some(ref tag) = labels.tag {
353 let family = if theme.title.family.is_empty() {
354 None
355 } else {
356 Some(theme.title.family.clone())
357 };
358 use crate::theme::TagPosition::*;
359 let pad = theme.title.size;
360 let (left, right) = (total_area.x + pad, total_area.x + total_area.width - pad);
361 let (top, bottom) = (total_area.y + pad, total_area.y + total_area.height - pad);
362 let (x, y, anchor) = match theme.tag_position {
363 TopLeft => (left, top, TextAnchor::Start),
364 TopRight => (right, top, TextAnchor::End),
365 BottomLeft => (left, bottom, TextAnchor::Start),
366 BottomRight => (right, bottom, TextAnchor::End),
367 };
368 backend.draw_text(
369 tag,
370 (x, y),
371 &TextStyle {
372 color: theme.title.color,
373 size: theme.title.size,
374 anchor,
375 angle: 0.0,
376 family,
377 face: theme.title.face,
378 bg_colour: None,
379 bg_r: 0.0,
380 },
381 )?;
382 }
383 Ok(())
384 }
385
386 fn render_faceted(built: &BuiltPlot, backend: &mut dyn DrawBackend) -> Result<(), RenderError> {
387 let theme = &built.theme;
388 let plot_area = backend.plot_area();
389 let total_area = backend.total_area();
390
391 if theme.plot_background.visible {
393 if let Some(fill) = theme.plot_background.fill {
394 backend.draw_rect(
395 (total_area.x, total_area.y),
396 (
397 total_area.x + total_area.width,
398 total_area.y + total_area.height,
399 ),
400 &RectStyle {
401 fill: Some(fill),
402 stroke: None,
403 stroke_width: 0.0,
404 alpha: 1.0,
405 clip: false,
406 corner_radius: 0.0,
407 },
408 )?;
409 }
410 }
411
412 let ncol = match &built.facet {
414 Facet::Wrap { ncol, .. } => {
415 ncol.unwrap_or_else(|| (built.panels.len() as f64).sqrt().ceil() as usize)
416 }
417 Facet::Grid { .. } => built.panels.iter().map(|p| p.col).max().unwrap_or(0) + 1,
418 Facet::None => 1,
419 };
420 let mut nrow = built.panels.len().div_ceil(ncol);
421
422 let strip_height = theme.strip_text.size + 8.0;
423 let gap_x = theme.get_panel_spacing_x();
424 let gap_y = theme.get_panel_spacing_y();
425
426 let is_figure = |li: usize| {
429 built.layers[li]
430 .geom
431 .figure_band_height(plot_area.width, theme)
432 .is_some()
433 };
434 let band_h: f64 = built
435 .layers
436 .iter()
437 .filter_map(|l| l.geom.figure_band_height(plot_area.width, theme))
438 .sum::<f64>()
439 .min(plot_area.height * 0.5);
440 let band_rect = crate::render::Rect {
441 x: plot_area.x,
442 y: plot_area.y + plot_area.height - band_h,
443 width: plot_area.width,
444 height: band_h,
445 };
446 let plot_area = crate::render::Rect {
447 height: plot_area.height - band_h,
448 ..plot_area
449 };
450
451 let fixed_grid = match (&built.facet, built.coord.fixed_aspect()) {
456 (Facet::Wrap { ncol: want, .. }, Some(aspect)) => Some(crate::facet::wrap::fit_grid(
457 built.panels.len(),
458 *want,
459 aspect,
460 plot_area.width,
461 plot_area.height,
462 strip_height,
463 gap_x,
464 gap_y,
465 )),
466 _ => None,
467 };
468 let ncol = if let Some(g) = fixed_grid {
469 nrow = g.nrow;
470 g.ncol
471 } else {
472 ncol
473 };
474 let cell: Vec<(usize, usize)> = built
477 .panels
478 .iter()
479 .enumerate()
480 .map(|(i, p)| {
481 if fixed_grid.is_some() {
482 (i / ncol, i % ncol)
483 } else {
484 (p.row, p.col)
485 }
486 })
487 .collect();
488
489 let space = match &built.facet {
492 Facet::Grid { space, .. } => space.clone(),
493 _ => crate::facet::FacetSpace::Fixed,
494 };
495 let extent = |pi: usize, col: &str| -> Option<f64> {
496 let (mut lo, mut hi) = (f64::INFINITY, f64::NEG_INFINITY);
497 for df in &built.panels_data[pi] {
498 if let Some(c) = df.column(col) {
499 for v in c {
500 if let Some(f) = v.as_f64() {
501 lo = lo.min(f);
502 hi = hi.max(f);
503 }
504 }
505 }
506 }
507 (lo <= hi).then_some(hi - lo)
508 };
509 let avail_w = plot_area.width - gap_x * (ncol as f64 - 1.0);
510 let col_widths: Vec<f64> = if let Some(g) = fixed_grid {
511 vec![g.panel_w; ncol]
512 } else if space.free_x() && ncol > 0 {
513 let mut ranges = vec![0.0f64; ncol];
514 for (pi, panel) in built.panels.iter().enumerate() {
515 if let Some(r) = extent(pi, "x") {
516 ranges[panel.col] = ranges[panel.col].max(r);
517 }
518 }
519 let total: f64 = ranges.iter().sum();
520 if total > 0.0 {
521 ranges.iter().map(|r| avail_w * (r / total)).collect()
522 } else {
523 vec![avail_w / ncol as f64; ncol]
524 }
525 } else {
526 vec![avail_w / ncol.max(1) as f64; ncol.max(1)]
527 };
528 let mut col_x = vec![plot_area.x; ncol.max(1)];
529 {
530 let used_w: f64 = col_widths.iter().sum::<f64>() + gap_x * (ncol as f64 - 1.0);
531 let mut acc = if fixed_grid.is_some() {
532 plot_area.x + ((plot_area.width - used_w) / 2.0).max(0.0)
533 } else {
534 plot_area.x
535 };
536 for c in 0..ncol {
537 col_x[c] = acc;
538 acc += col_widths[c] + gap_x;
539 }
540 }
541
542 let avail_h = plot_area.height - gap_y * (nrow as f64 - 1.0) - strip_height * nrow as f64;
543 let row_heights: Vec<f64> = if let Some(g) = fixed_grid {
544 vec![g.panel_h; nrow]
545 } else if space.free_y() && nrow > 0 {
546 let mut ranges = vec![0.0f64; nrow];
547 for (pi, panel) in built.panels.iter().enumerate() {
548 if let Some(r) = extent(pi, "y") {
549 ranges[panel.row] = ranges[panel.row].max(r);
550 }
551 }
552 let total: f64 = ranges.iter().sum();
553 if total > 0.0 {
554 ranges.iter().map(|r| avail_h * (r / total)).collect()
555 } else {
556 vec![avail_h / nrow as f64; nrow]
557 }
558 } else {
559 vec![avail_h / nrow.max(1) as f64; nrow.max(1)]
560 };
561 let mut row_y = vec![plot_area.y; nrow.max(1)];
562 {
563 let used_h: f64 =
564 row_heights.iter().sum::<f64>() + (strip_height + gap_y) * nrow as f64 - gap_y;
565 let mut acc = if fixed_grid.is_some() {
566 plot_area.y + ((plot_area.height - used_h) / 2.0).max(0.0)
567 } else {
568 plot_area.y
569 };
570 for r in 0..nrow {
571 row_y[r] = acc;
572 acc += row_heights[r] + strip_height + gap_y;
573 }
574 }
575
576 for (pi, panel) in built.panels.iter().enumerate() {
577 let (prow, pcol) = cell[pi];
578 let panel_width = col_widths[pcol];
579 let panel_height = row_heights[prow];
580 let px = col_x[pcol];
581 let py = row_y[prow];
582
583 let panel_rect = crate::render::Rect {
584 x: px,
585 y: py + strip_height,
586 width: panel_width,
587 height: panel_height,
588 };
589 backend.clear_corner_claims();
597
598 if theme.strip_background.visible {
600 backend.draw_rect(
601 (px, py),
602 (px + panel_width, py + strip_height),
603 &RectStyle {
604 fill: theme.strip_background.fill,
605 stroke: theme.strip_background.color,
606 stroke_width: theme.strip_background.width,
607 alpha: 1.0,
608 clip: false,
609 corner_radius: 0.0,
610 },
611 )?;
612 }
613
614 if theme.strip_text.visible {
616 let label = panel.col_label.as_deref().unwrap_or(&panel.label);
617 let family = if theme.strip_text.family.is_empty() {
618 None
619 } else {
620 Some(theme.strip_text.family.clone())
621 };
622 backend.draw_text(
623 label,
624 (px + panel_width / 2.0, py + strip_height / 2.0),
625 &TextStyle {
626 color: theme.strip_text.color,
627 size: theme.strip_text.size,
628 anchor: TextAnchor::Middle,
629 angle: 0.0,
630 family,
631 face: theme.strip_text.face,
632 bg_colour: None,
633 bg_r: 0.0,
634 },
635 )?;
636 }
637
638 if theme.panel_background.visible {
640 if let Some(fill) = theme.panel_background.fill {
641 backend.draw_rect(
642 (panel_rect.x, panel_rect.y),
643 (
644 panel_rect.x + panel_rect.width,
645 panel_rect.y + panel_rect.height,
646 ),
647 &RectStyle {
648 fill: Some(fill),
649 stroke: theme.panel_background.color,
650 stroke_width: theme.panel_background.width,
651 alpha: 1.0,
652 clip: false,
653 corner_radius: 0.0,
654 },
655 )?;
656 }
657 }
658
659 if theme.panel_border.visible {
661 let style = LineStyle {
662 color: theme.panel_border.color,
663 width: theme.panel_border.width,
664 alpha: 1.0,
665 linetype: Linetype::Solid,
666 ..Default::default()
667 };
668 let x0 = panel_rect.x;
669 let y0 = panel_rect.y;
670 let x1 = panel_rect.x + panel_rect.width;
671 let y1 = panel_rect.y + panel_rect.height;
672 backend.draw_line(&[(x0, y0), (x1, y0)], &style)?;
673 backend.draw_line(&[(x1, y0), (x1, y1)], &style)?;
674 backend.draw_line(&[(x1, y1), (x0, y1)], &style)?;
675 backend.draw_line(&[(x0, y1), (x0, y0)], &style)?;
676 }
677
678 let panel_scale_set = if pi < built.panel_scales.len() {
680 &built.panel_scales[pi]
681 } else {
682 &built.scales
683 };
684
685 let x_scale = panel_scale_set.get(&Aesthetic::X);
687 let y_scale = panel_scale_set.get(&Aesthetic::Y);
688
689 if let (Some(xs), Some(ys)) = (x_scale, y_scale) {
690 if built.coord.gridlines() {
691 axis::draw_gridlines(
692 xs,
693 ys,
694 built.coord.as_ref(),
695 theme,
696 &panel_rect,
697 backend,
698 )?;
699 }
700
701 if prow == nrow - 1 || pi + ncol >= built.panels.len() {
703 axis::draw_x_axis(
704 xs,
705 built.coord.as_ref(),
706 theme,
707 &panel_rect,
708 &total_area,
709 backend,
710 )?;
711 }
712
713 if pcol == 0 {
715 axis::draw_y_axis(ys, built.coord.as_ref(), theme, &panel_rect, backend)?;
716 }
717 }
718
719 if fixed_grid.is_some() {
723 let data = built
724 .coord
725 .data_rect(&panel_rect)
726 .unwrap_or_else(|| panel_rect.clone());
727 backend.begin_panel(pi, &panel.label, &panel_rect, &data);
728 }
729 if pi < built.panels_data.len() {
730 for (li, layer_data) in built.panels_data[pi].iter().enumerate() {
731 if li < built.layers.len() && layer_data.nrows() > 0 && !is_figure(li) {
732 let mut panel_backend = PanelBackendAdapter {
733 inner: backend,
734 panel_rect: panel_rect.clone(),
735 };
736 let geom = &built.layers[li].geom;
737 panel_backend.set_mark_geom(Some(geom.name()));
738 let drawn = geom.draw(
739 layer_data,
740 built.coord.as_ref(),
741 panel_scale_set,
742 theme,
743 &mut panel_backend,
744 );
745 panel_backend.set_mark_geom(None);
746 drawn?;
747 }
748 }
749 }
750 if fixed_grid.is_some() {
751 backend.end_panel();
752 }
753 }
754
755 if band_h > 0.0 {
757 backend.clear_corner_claims();
758 for li in (0..built.layers.len()).filter(|&li| is_figure(li)) {
759 let empty = crate::data::DataFrame::new();
760 let data = built
761 .panels_data
762 .iter()
763 .filter_map(|p| p.get(li))
764 .find(|d| d.nrows() > 0)
765 .unwrap_or(&empty);
766 let mut band_backend = PanelBackendAdapter {
767 inner: backend,
768 panel_rect: band_rect.clone(),
769 };
770 let geom = &built.layers[li].geom;
771 band_backend.set_mark_geom(Some(geom.name()));
772 let drawn = geom.draw(
773 data,
774 built.coord.as_ref(),
775 &built.scales,
776 theme,
777 &mut band_backend,
778 );
779 band_backend.set_mark_geom(None);
780 drawn?;
781 }
782 }
783
784 let title_ref = Self::title_ref_rect(theme, &plot_area, &total_area);
786 let legend_lift = legend::top_legend_lift(
789 theme,
790 &built.scales,
791 &built.suppressed_aes,
792 &built.legend_plans,
793 &built.guide_legend,
794 );
795 if let Some(ref title) = built.labels.title {
796 let (tx, anchor) = Self::hjust_pos(&theme.title, &title_ref);
797 let title_y = plot_area.y - theme.title.size * 0.9 - legend_lift;
798 let family = if theme.title.family.is_empty() {
799 None
800 } else {
801 Some(theme.title.family.clone())
802 };
803 backend.draw_text(
804 title,
805 (tx, title_y.max(theme.title.size)),
806 &TextStyle {
807 color: theme.title.color,
808 size: theme.title.size,
809 anchor,
810 angle: 0.0,
811 family,
812 face: theme.title.face,
813 bg_colour: None,
814 bg_r: 0.0,
815 },
816 )?;
817 }
818
819 if let Some(ref subtitle) = built.labels.subtitle {
821 let (sx, anchor) = Self::hjust_pos(&theme.subtitle, &title_ref);
822 let subtitle_y =
825 (plot_area.y - 2.0).max(theme.title.size + theme.subtitle.size) - legend_lift;
826 let family = if theme.subtitle.family.is_empty() {
827 None
828 } else {
829 Some(theme.subtitle.family.clone())
830 };
831 backend.draw_text(
832 subtitle,
833 (sx, subtitle_y),
834 &TextStyle {
835 color: theme.subtitle.color,
836 size: theme.subtitle.size,
837 anchor,
838 angle: 0.0,
839 family,
840 face: theme.subtitle.face,
841 bg_colour: None,
842 bg_r: 0.0,
843 },
844 )?;
845 }
846
847 if let Some(ref caption) = built.labels.caption {
849 let (cx, anchor) = Self::hjust_pos(&theme.caption, &title_ref);
850 let caption_y = total_area.y + total_area.height - theme.caption.size * 0.5;
851 let family = if theme.caption.family.is_empty() {
852 None
853 } else {
854 Some(theme.caption.family.clone())
855 };
856 backend.draw_text(
857 caption,
858 (cx, caption_y),
859 &TextStyle {
860 color: theme.caption.color,
861 size: theme.caption.size,
862 anchor,
863 angle: 0.0,
864 family,
865 face: theme.caption.face,
866 bg_colour: None,
867 bg_r: 0.0,
868 },
869 )?;
870 }
871
872 Self::draw_annotations(
874 &built.annotations,
875 &built.scales,
876 built.coord.as_ref(),
877 &plot_area,
878 theme,
879 backend,
880 )?;
881
882 legend::draw_legend_planned(
884 &built.scales,
885 theme,
886 &plot_area,
887 plot_area.width,
888 backend,
889 &built.guide_legend,
890 &built.suppressed_aes,
891 &built.legend_plans,
892 )?;
893
894 Self::draw_tag(&built.labels, theme, &total_area, backend)?;
896
897 Ok(())
898 }
899
900 fn draw_annotations(
915 annotations: &[Annotation],
916 scales: &crate::scale::ScaleSet,
917 coord: &dyn crate::coord::Coord,
918 plot_area: &crate::render::Rect,
919 theme: &crate::theme::Theme,
920 backend: &mut dyn DrawBackend,
921 ) -> Result<(), RenderError> {
922 use crate::data::Value;
923
924 let x_scale = scales.get(&Aesthetic::X);
925 let y_scale = scales.get(&Aesthetic::Y);
926
927 for ann in annotations {
928 match ann {
929 Annotation::Text {
930 label,
931 x,
932 y,
933 size,
934 color,
935 } => {
936 let nx = x_scale.map(|s| s.map(&Value::Float(*x))).unwrap_or(0.0);
937 let ny = y_scale.map(|s| s.map(&Value::Float(*y))).unwrap_or(0.0);
938 let pos = coord.transform((nx, ny), plot_area);
939 backend.draw_text(
940 label,
941 pos,
942 &TextStyle {
943 color: annotation_ink(*color, theme),
944 size: *size,
945 anchor: TextAnchor::Middle,
946 angle: 0.0,
947 family: crate::geom::resolve_font_family("", theme),
948 face: crate::render::backend::FontFace::Plain,
949 bg_colour: None,
950 bg_r: 0.0,
951 },
952 )?;
953 }
954 Annotation::Rect {
955 xmin,
956 xmax,
957 ymin,
958 ymax,
959 fill,
960 alpha,
961 } => {
962 let nx0 = x_scale.map(|s| s.map(&Value::Float(*xmin))).unwrap_or(0.0);
963 let nx1 = x_scale.map(|s| s.map(&Value::Float(*xmax))).unwrap_or(1.0);
964 let ny0 = y_scale.map(|s| s.map(&Value::Float(*ymin))).unwrap_or(0.0);
965 let ny1 = y_scale.map(|s| s.map(&Value::Float(*ymax))).unwrap_or(1.0);
966 let tl = coord.transform((nx0, ny1), plot_area);
967 let br = coord.transform((nx1, ny0), plot_area);
968 backend.draw_rect(
969 tl,
970 br,
971 &RectStyle {
972 fill: Some(*fill),
973 stroke: None,
974 stroke_width: 0.0,
975 alpha: *alpha,
976 clip: false,
977 corner_radius: 0.0,
978 },
979 )?;
980 }
981 Annotation::Segment {
982 x,
983 y,
984 xend,
985 yend,
986 color,
987 width,
988 } => {
989 let nx0 = x_scale.map(|s| s.map(&Value::Float(*x))).unwrap_or(0.0);
990 let ny0 = y_scale.map(|s| s.map(&Value::Float(*y))).unwrap_or(0.0);
991 let nx1 = x_scale.map(|s| s.map(&Value::Float(*xend))).unwrap_or(1.0);
992 let ny1 = y_scale.map(|s| s.map(&Value::Float(*yend))).unwrap_or(1.0);
993 let p0 = coord.transform((nx0, ny0), plot_area);
994 let p1 = coord.transform((nx1, ny1), plot_area);
995 backend.draw_line(
996 &[p0, p1],
997 &LineStyle {
998 color: annotation_ink(*color, theme),
999 alpha: 1.0,
1000 width: *width,
1001 linetype: Linetype::Solid,
1002 ..Default::default()
1003 },
1004 )?;
1005 }
1006 }
1007 }
1008 Ok(())
1009 }
1010}
1011
1012fn annotation_ink(color: (u8, u8, u8), theme: &crate::theme::Theme) -> (u8, u8, u8) {
1018 match (color == (50, 50, 50), theme.geom.ink) {
1019 (true, Some(ink)) => ink,
1020 _ => color,
1021 }
1022}
1023
1024struct PanelBackendAdapter<'a> {
1026 inner: &'a mut dyn DrawBackend,
1027 panel_rect: crate::render::Rect,
1028}
1029
1030impl<'a> DrawBackend for PanelBackendAdapter<'a> {
1031 fn draw_circle(
1032 &mut self,
1033 center: (f64, f64),
1034 radius: f64,
1035 style: &crate::render::backend::PointStyle,
1036 ) -> Result<(), RenderError> {
1037 self.inner.draw_circle(center, radius, style)
1038 }
1039 fn draw_line(
1040 &mut self,
1041 points: &[(f64, f64)],
1042 style: &crate::render::backend::LineStyle,
1043 ) -> Result<(), RenderError> {
1044 self.inner.draw_line(points, style)
1045 }
1046 fn draw_quadratic(
1047 &mut self,
1048 p0: (f64, f64),
1049 ctrl: (f64, f64),
1050 p1: (f64, f64),
1051 style: &crate::render::backend::LineStyle,
1052 ) -> Result<(), RenderError> {
1053 self.inner.draw_quadratic(p0, ctrl, p1, style)
1054 }
1055 fn draw_rect(
1056 &mut self,
1057 top_left: (f64, f64),
1058 bottom_right: (f64, f64),
1059 style: &RectStyle,
1060 ) -> Result<(), RenderError> {
1061 self.inner.draw_rect(top_left, bottom_right, style)
1062 }
1063 fn draw_text(
1064 &mut self,
1065 text: &str,
1066 pos: (f64, f64),
1067 style: &TextStyle,
1068 ) -> Result<(), RenderError> {
1069 self.inner.draw_text(text, pos, style)
1070 }
1071 fn draw_polygon(
1072 &mut self,
1073 points: &[(f64, f64)],
1074 style: &RectStyle,
1075 ) -> Result<(), RenderError> {
1076 self.inner.draw_polygon(points, style)
1077 }
1078 fn draw_polygon_rings(
1079 &mut self,
1080 rings: &[Vec<(f64, f64)>],
1081 style: &RectStyle,
1082 ) -> Result<(), RenderError> {
1083 self.inner.draw_polygon_rings(rings, style)
1084 }
1085 fn draw_shape(
1086 &mut self,
1087 center: (f64, f64),
1088 radius: f64,
1089 style: &crate::render::backend::PointStyle,
1090 ) -> Result<(), RenderError> {
1091 self.inner.draw_shape(center, radius, style)
1092 }
1093 fn plot_area(&self) -> crate::render::Rect {
1094 self.panel_rect.clone()
1095 }
1096 fn set_tooltip(&mut self, tooltip: Option<String>) {
1097 self.inner.set_tooltip(tooltip)
1098 }
1099 fn set_mark_axis(&mut self, key: Option<String>) {
1100 self.inner.set_mark_axis(key)
1101 }
1102 fn set_mark_series(&mut self, series: Option<String>) {
1103 self.inner.set_mark_series(series)
1104 }
1105 fn set_mark_value(&mut self, value: Option<String>) {
1106 self.inner.set_mark_value(value)
1107 }
1108 fn set_mark_geom(&mut self, geom: Option<&str>) {
1109 self.inner.set_mark_geom(geom)
1110 }
1111 fn warn(&mut self, message: String) {
1112 self.inner.warn(message)
1113 }
1114 fn set_mark_part(&mut self, part: Option<&str>) {
1115 self.inner.set_mark_part(part)
1116 }
1117 fn set_mark_attrs(&mut self, attrs: Vec<(String, String)>) {
1118 self.inner.set_mark_attrs(attrs)
1119 }
1120
1121 fn set_mark_class(&mut self, class: Option<String>) {
1122 self.inner.set_mark_class(class)
1123 }
1124
1125 fn set_mark_group(&mut self, group: Option<(String, String)>) {
1126 self.inner.set_mark_group(group)
1127 }
1128
1129 fn set_layer_attrs(&mut self, attrs: Vec<(String, String)>) {
1130 self.inner.set_layer_attrs(attrs)
1131 }
1132 fn wants_formatted_values(&self) -> bool {
1133 self.inner.wants_formatted_values()
1134 }
1135 fn set_mark_fvalue(&mut self, value: Option<String>) {
1136 self.inner.set_mark_fvalue(value)
1137 }
1138 fn set_mark_fx(&mut self, value: Option<String>) {
1139 self.inner.set_mark_fx(value)
1140 }
1141 fn total_area(&self) -> crate::render::Rect {
1142 self.inner.total_area()
1143 }
1144 fn corner_claims(&self) -> Vec<crate::render::Rect> {
1145 self.inner.corner_claims()
1146 }
1147 fn claim_corner_box(&mut self, rect: crate::render::Rect) {
1148 self.inner.claim_corner_box(rect)
1149 }
1150 fn clear_corner_claims(&mut self) {
1151 self.inner.clear_corner_claims()
1152 }
1153}
1154
1155#[cfg(test)]
1156mod tests {
1157 use super::*;
1158 use crate::render::backend::LineStyle;
1159 use crate::render::svg_backend::SvgBackend;
1160
1161 #[test]
1167 fn panel_backend_adapter_forwards_quadratic_and_mark_metadata() {
1168 let total = crate::render::Rect {
1169 x: 0.0,
1170 y: 0.0,
1171 width: 200.0,
1172 height: 200.0,
1173 };
1174 let panel_rect = crate::render::Rect {
1175 x: 10.0,
1176 y: 10.0,
1177 width: 80.0,
1178 height: 80.0,
1179 };
1180 let mut backend = SvgBackend::new(200, 200, total);
1181 let mut adapter = PanelBackendAdapter {
1182 inner: &mut backend,
1183 panel_rect: panel_rect.clone(),
1184 };
1185
1186 assert_eq!(adapter.plot_area().x, panel_rect.x);
1187 assert_eq!(adapter.plot_area().width, panel_rect.width);
1188
1189 adapter.set_mark_part(Some("arrow"));
1190 adapter.set_mark_attrs(vec![("data-x".to_string(), "v".to_string())]);
1191 adapter
1192 .draw_quadratic((0.0, 0.0), (5.0, 10.0), (10.0, 0.0), &LineStyle::default())
1193 .unwrap();
1194 adapter.set_mark_part(None);
1195 adapter.set_mark_attrs(Vec::new());
1196
1197 let svg = backend.finish();
1198 assert!(svg.contains("<path"));
1199 assert!(svg.contains("data-part=\"arrow\""));
1200 assert!(svg.contains("data-x=\"v\""));
1201 }
1202}