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, _) = Self::title_subtitle_y(
211 theme,
212 plot_area.y,
213 x_axis_lift + legend_lift,
214 built.labels.subtitle.is_some(),
215 );
216 let family = if theme.title.family.is_empty() {
217 None
218 } else {
219 Some(theme.title.family.clone())
220 };
221 backend.draw_text(
222 title,
223 (tx, title_y),
224 &TextStyle {
225 color: theme.title.color,
226 size: theme.title.size,
227 anchor,
228 angle: 0.0,
229 family,
230 face: theme.title.face,
231 bg_colour: None,
232 bg_r: 0.0,
233 },
234 )?;
235 }
236
237 if let Some(ref subtitle) = built.labels.subtitle {
239 let (sx, anchor) = Self::hjust_pos(&theme.subtitle, &title_ref);
240 let x_axis_top = built
242 .scales
243 .get(&Aesthetic::X)
244 .map(|s| s.axis_position_opposite())
245 .unwrap_or(false);
246 let x_axis_lift = if x_axis_top {
247 theme.axis_ticks_length + theme.axis_text_x.size + theme.legend_spacing
248 } else {
249 0.0
250 };
251 let (_, subtitle_y) =
252 Self::title_subtitle_y(theme, plot_area.y, x_axis_lift + legend_lift, true);
253 let family = if theme.subtitle.family.is_empty() {
254 None
255 } else {
256 Some(theme.subtitle.family.clone())
257 };
258 backend.draw_text(
259 subtitle,
260 (sx, subtitle_y),
261 &TextStyle {
262 color: theme.subtitle.color,
263 size: theme.subtitle.size,
264 anchor,
265 angle: 0.0,
266 family,
267 face: theme.subtitle.face,
268 bg_colour: None,
269 bg_r: 0.0,
270 },
271 )?;
272 }
273
274 legend::draw_legend_planned(
276 &built.scales,
277 theme,
278 &plot_area,
279 plot_area.width,
280 backend,
281 &built.guide_legend,
282 &built.suppressed_aes,
283 &built.legend_plans,
284 )?;
285
286 if let Some(ref caption) = built.labels.caption {
288 let (cx, anchor) = Self::hjust_pos(&theme.caption, &title_ref);
289 let caption_y = total_area.y + total_area.height
292 - theme.plot_margin.bottom.max(0.0)
293 - theme.caption.size * 0.5;
294 let family = if theme.caption.family.is_empty() {
295 None
296 } else {
297 Some(theme.caption.family.clone())
298 };
299 backend.draw_text(
300 caption,
301 (cx, caption_y),
302 &TextStyle {
303 color: theme.caption.color,
304 size: theme.caption.size,
305 anchor,
306 angle: 0.0,
307 family,
308 face: theme.caption.face,
309 bg_colour: None,
310 bg_r: 0.0,
311 },
312 )?;
313 }
314
315 Self::draw_tag(&built.labels, theme, &total_area, backend)?;
317
318 Ok(())
319 }
320
321 fn hjust_pos(
324 el: &crate::theme::elements::ElementText,
325 area: &crate::render::Rect,
326 ) -> (f64, TextAnchor) {
327 let x = area.x + el.hjust.clamp(0.0, 1.0) * area.width;
328 let anchor = if el.hjust <= 0.02 {
329 TextAnchor::Start
330 } else if el.hjust >= 0.98 {
331 TextAnchor::End
332 } else {
333 TextAnchor::Middle
334 };
335 (x, anchor)
336 }
337
338 fn title_subtitle_y(
347 theme: &crate::theme::Theme,
348 panel_top: f64,
349 lift: f64,
350 has_subtitle: bool,
351 ) -> (f64, f64) {
352 let base = panel_top - lift;
353 let subtitle_band = if has_subtitle {
354 theme.subtitle.size * 1.5
355 } else {
356 0.0
357 };
358 let subtitle_y = base - subtitle_band / 2.0;
359 let title_y = (base - subtitle_band - theme.title.size * 0.9).max(theme.title.size);
360 (title_y, subtitle_y)
361 }
362
363 fn title_ref_rect(
366 theme: &crate::theme::Theme,
367 plot_area: &crate::render::Rect,
368 total_area: &crate::render::Rect,
369 ) -> crate::render::Rect {
370 match theme.title_position {
371 crate::theme::TitlePosition::Panel => plot_area.clone(),
372 crate::theme::TitlePosition::Plot => crate::render::Rect {
373 x: total_area.x + theme.plot_margin.left,
374 y: plot_area.y,
375 width: (total_area.width - theme.plot_margin.left - theme.plot_margin.right)
376 .max(1.0),
377 height: plot_area.height,
378 },
379 }
380 }
381
382 pub(crate) fn draw_cell_tag(
385 labels: &crate::plot::Labels,
386 theme: &crate::theme::Theme,
387 area: &crate::render::Rect,
388 backend: &mut dyn DrawBackend,
389 ) -> Result<(), RenderError> {
390 Self::draw_tag(labels, theme, area, backend)
391 }
392
393 fn draw_tag(
394 labels: &crate::plot::Labels,
395 theme: &crate::theme::Theme,
396 total_area: &crate::render::Rect,
397 backend: &mut dyn DrawBackend,
398 ) -> Result<(), RenderError> {
399 if let Some(ref tag) = labels.tag {
400 let family = if theme.title.family.is_empty() {
401 None
402 } else {
403 Some(theme.title.family.clone())
404 };
405 use crate::theme::TagPosition::*;
406 let pad = theme.title.size;
407 let (left, right) = (total_area.x + pad, total_area.x + total_area.width - pad);
408 let (top, bottom) = (total_area.y + pad, total_area.y + total_area.height - pad);
409 let (x, y, anchor) = match theme.tag_position {
410 TopLeft => (left, top, TextAnchor::Start),
411 TopRight => (right, top, TextAnchor::End),
412 BottomLeft => (left, bottom, TextAnchor::Start),
413 BottomRight => (right, bottom, TextAnchor::End),
414 };
415 backend.draw_text(
416 tag,
417 (x, y),
418 &TextStyle {
419 color: theme.title.color,
420 size: theme.title.size,
421 anchor,
422 angle: 0.0,
423 family,
424 face: theme.title.face,
425 bg_colour: None,
426 bg_r: 0.0,
427 },
428 )?;
429 }
430 Ok(())
431 }
432
433 fn render_faceted(built: &BuiltPlot, backend: &mut dyn DrawBackend) -> Result<(), RenderError> {
434 let theme = &built.theme;
435 let plot_area = backend.plot_area();
436 let total_area = backend.total_area();
437
438 if theme.plot_background.visible {
440 if let Some(fill) = theme.plot_background.fill {
441 backend.draw_rect(
442 (total_area.x, total_area.y),
443 (
444 total_area.x + total_area.width,
445 total_area.y + total_area.height,
446 ),
447 &RectStyle {
448 fill: Some(fill),
449 stroke: None,
450 stroke_width: 0.0,
451 alpha: 1.0,
452 clip: false,
453 corner_radius: 0.0,
454 },
455 )?;
456 }
457 }
458
459 let ncol = match &built.facet {
461 Facet::Wrap { ncol, .. } => {
462 ncol.unwrap_or_else(|| (built.panels.len() as f64).sqrt().ceil() as usize)
463 }
464 Facet::Grid { .. } => built.panels.iter().map(|p| p.col).max().unwrap_or(0) + 1,
465 Facet::None => 1,
466 };
467 let mut nrow = built.panels.len().div_ceil(ncol);
468
469 let strip_height = theme.strip_text.size + 8.0;
470 let gap_x = theme.get_panel_spacing_x();
471 let gap_y = theme.get_panel_spacing_y();
472
473 let is_figure = |li: usize| {
476 built.layers[li]
477 .geom
478 .figure_band_height(plot_area.width, theme)
479 .is_some()
480 };
481 let band_h: f64 = built
482 .layers
483 .iter()
484 .filter_map(|l| l.geom.figure_band_height(plot_area.width, theme))
485 .sum::<f64>()
486 .min(plot_area.height * 0.5);
487 let band_rect = crate::render::Rect {
488 x: plot_area.x,
489 y: plot_area.y + plot_area.height - band_h,
490 width: plot_area.width,
491 height: band_h,
492 };
493 let plot_area = crate::render::Rect {
494 height: plot_area.height - band_h,
495 ..plot_area
496 };
497
498 let fixed_grid = match (&built.facet, built.coord.fixed_aspect()) {
503 (Facet::Wrap { ncol: want, .. }, Some(aspect)) => Some(crate::facet::wrap::fit_grid(
504 built.panels.len(),
505 *want,
506 aspect,
507 plot_area.width,
508 plot_area.height,
509 strip_height,
510 gap_x,
511 gap_y,
512 )),
513 _ => None,
514 };
515 let ncol = if let Some(g) = fixed_grid {
516 nrow = g.nrow;
517 g.ncol
518 } else {
519 ncol
520 };
521 let cell: Vec<(usize, usize)> = built
524 .panels
525 .iter()
526 .enumerate()
527 .map(|(i, p)| {
528 if fixed_grid.is_some() {
529 (i / ncol, i % ncol)
530 } else {
531 (p.row, p.col)
532 }
533 })
534 .collect();
535
536 let space = match &built.facet {
539 Facet::Grid { space, .. } => space.clone(),
540 _ => crate::facet::FacetSpace::Fixed,
541 };
542 let extent = |pi: usize, col: &str| -> Option<f64> {
543 let (mut lo, mut hi) = (f64::INFINITY, f64::NEG_INFINITY);
544 for df in &built.panels_data[pi] {
545 if let Some(c) = df.column(col) {
546 for v in c {
547 if let Some(f) = v.as_f64() {
548 lo = lo.min(f);
549 hi = hi.max(f);
550 }
551 }
552 }
553 }
554 (lo <= hi).then_some(hi - lo)
555 };
556 let avail_w = plot_area.width - gap_x * (ncol as f64 - 1.0);
557 let col_widths: Vec<f64> = if let Some(g) = fixed_grid {
558 vec![g.panel_w; ncol]
559 } else if space.free_x() && ncol > 0 {
560 let mut ranges = vec![0.0f64; ncol];
561 for (pi, panel) in built.panels.iter().enumerate() {
562 if let Some(r) = extent(pi, "x") {
563 ranges[panel.col] = ranges[panel.col].max(r);
564 }
565 }
566 let total: f64 = ranges.iter().sum();
567 if total > 0.0 {
568 ranges.iter().map(|r| avail_w * (r / total)).collect()
569 } else {
570 vec![avail_w / ncol as f64; ncol]
571 }
572 } else {
573 vec![avail_w / ncol.max(1) as f64; ncol.max(1)]
574 };
575 let mut col_x = vec![plot_area.x; ncol.max(1)];
576 {
577 let used_w: f64 = col_widths.iter().sum::<f64>() + gap_x * (ncol as f64 - 1.0);
578 let mut acc = if fixed_grid.is_some() {
579 plot_area.x + ((plot_area.width - used_w) / 2.0).max(0.0)
580 } else {
581 plot_area.x
582 };
583 for c in 0..ncol {
584 col_x[c] = acc;
585 acc += col_widths[c] + gap_x;
586 }
587 }
588
589 let avail_h = plot_area.height - gap_y * (nrow as f64 - 1.0) - strip_height * nrow as f64;
590 let row_heights: Vec<f64> = if let Some(g) = fixed_grid {
591 vec![g.panel_h; nrow]
592 } else if space.free_y() && nrow > 0 {
593 let mut ranges = vec![0.0f64; nrow];
594 for (pi, panel) in built.panels.iter().enumerate() {
595 if let Some(r) = extent(pi, "y") {
596 ranges[panel.row] = ranges[panel.row].max(r);
597 }
598 }
599 let total: f64 = ranges.iter().sum();
600 if total > 0.0 {
601 ranges.iter().map(|r| avail_h * (r / total)).collect()
602 } else {
603 vec![avail_h / nrow as f64; nrow]
604 }
605 } else {
606 vec![avail_h / nrow.max(1) as f64; nrow.max(1)]
607 };
608 let mut row_y = vec![plot_area.y; nrow.max(1)];
609 {
610 let used_h: f64 =
611 row_heights.iter().sum::<f64>() + (strip_height + gap_y) * nrow as f64 - gap_y;
612 let mut acc = if fixed_grid.is_some() {
613 plot_area.y + ((plot_area.height - used_h) / 2.0).max(0.0)
614 } else {
615 plot_area.y
616 };
617 for r in 0..nrow {
618 row_y[r] = acc;
619 acc += row_heights[r] + strip_height + gap_y;
620 }
621 }
622
623 let mut x_title_area: Option<super::Rect> = None;
627 let mut y_title_area: Option<(super::Rect, f64)> = None;
628 let union = |a: &Option<super::Rect>, b: &super::Rect| match a {
629 None => b.clone(),
630 Some(a) => {
631 let (x0, y0) = (a.x.min(b.x), a.y.min(b.y));
632 let x1 = (a.x + a.width).max(b.x + b.width);
633 let y1 = (a.y + a.height).max(b.y + b.height);
634 super::Rect {
635 x: x0,
636 y: y0,
637 width: x1 - x0,
638 height: y1 - y0,
639 }
640 }
641 };
642
643 for (pi, panel) in built.panels.iter().enumerate() {
644 let (prow, pcol) = cell[pi];
645 let panel_width = col_widths[pcol];
646 let panel_height = row_heights[prow];
647 let px = col_x[pcol];
648 let py = row_y[prow];
649
650 let panel_rect = crate::render::Rect {
651 x: px,
652 y: py + strip_height,
653 width: panel_width,
654 height: panel_height,
655 };
656 backend.clear_corner_claims();
664
665 if theme.strip_background.visible {
667 backend.draw_rect(
668 (px, py),
669 (px + panel_width, py + strip_height),
670 &RectStyle {
671 fill: theme.strip_background.fill,
672 stroke: theme.strip_background.color,
673 stroke_width: theme.strip_background.width,
674 alpha: 1.0,
675 clip: false,
676 corner_radius: 0.0,
677 },
678 )?;
679 }
680
681 if theme.strip_text.visible {
683 let label = panel.col_label.as_deref().unwrap_or(&panel.label);
684 let family = if theme.strip_text.family.is_empty() {
685 None
686 } else {
687 Some(theme.strip_text.family.clone())
688 };
689 backend.draw_text(
690 label,
691 (px + panel_width / 2.0, py + strip_height / 2.0),
692 &TextStyle {
693 color: theme.strip_text.color,
694 size: theme.strip_text.size,
695 anchor: TextAnchor::Middle,
696 angle: 0.0,
697 family,
698 face: theme.strip_text.face,
699 bg_colour: None,
700 bg_r: 0.0,
701 },
702 )?;
703 }
704
705 if theme.panel_background.visible {
707 if let Some(fill) = theme.panel_background.fill {
708 backend.draw_rect(
709 (panel_rect.x, panel_rect.y),
710 (
711 panel_rect.x + panel_rect.width,
712 panel_rect.y + panel_rect.height,
713 ),
714 &RectStyle {
715 fill: Some(fill),
716 stroke: theme.panel_background.color,
717 stroke_width: theme.panel_background.width,
718 alpha: 1.0,
719 clip: false,
720 corner_radius: 0.0,
721 },
722 )?;
723 }
724 }
725
726 if theme.panel_border.visible {
728 let style = LineStyle {
729 color: theme.panel_border.color,
730 width: theme.panel_border.width,
731 alpha: 1.0,
732 linetype: Linetype::Solid,
733 ..Default::default()
734 };
735 let x0 = panel_rect.x;
736 let y0 = panel_rect.y;
737 let x1 = panel_rect.x + panel_rect.width;
738 let y1 = panel_rect.y + panel_rect.height;
739 backend.draw_line(&[(x0, y0), (x1, y0)], &style)?;
740 backend.draw_line(&[(x1, y0), (x1, y1)], &style)?;
741 backend.draw_line(&[(x1, y1), (x0, y1)], &style)?;
742 backend.draw_line(&[(x0, y1), (x0, y0)], &style)?;
743 }
744
745 let panel_scale_set = if pi < built.panel_scales.len() {
747 &built.panel_scales[pi]
748 } else {
749 &built.scales
750 };
751
752 let x_scale = panel_scale_set.get(&Aesthetic::X);
754 let y_scale = panel_scale_set.get(&Aesthetic::Y);
755
756 if let (Some(xs), Some(ys)) = (x_scale, y_scale) {
757 if built.coord.gridlines() {
758 axis::draw_gridlines(
759 xs,
760 ys,
761 built.coord.as_ref(),
762 theme,
763 &panel_rect,
764 backend,
765 )?;
766 }
767
768 if prow == nrow - 1 || pi + ncol >= built.panels.len() {
770 axis::draw_x_axis_with(
771 xs,
772 built.coord.as_ref(),
773 theme,
774 &panel_rect,
775 &total_area,
776 false,
777 backend,
778 )?;
779 x_title_area = Some(union(&x_title_area, &panel_rect));
780 }
781
782 if pcol == 0 {
784 axis::draw_y_axis_with(
785 ys,
786 built.coord.as_ref(),
787 theme,
788 &panel_rect,
789 false,
790 backend,
791 )?;
792 let reserve = axis::y_label_reserve(theme, &ys.breaks());
793 let prev = y_title_area.as_ref().map(|(r, _)| r.clone());
794 let widest = y_title_area.as_ref().map_or(0.0, |(_, w)| *w);
795 y_title_area = Some((union(&prev, &panel_rect), widest.max(reserve)));
796 }
797 }
798
799 if fixed_grid.is_some() {
803 let data = built
804 .coord
805 .data_rect(&panel_rect)
806 .unwrap_or_else(|| panel_rect.clone());
807 backend.begin_panel(pi, &panel.label, &panel_rect, &data);
808 }
809 if pi < built.panels_data.len() {
810 for (li, layer_data) in built.panels_data[pi].iter().enumerate() {
811 if li < built.layers.len() && layer_data.nrows() > 0 && !is_figure(li) {
812 let mut panel_backend = PanelBackendAdapter {
813 inner: backend,
814 panel_rect: panel_rect.clone(),
815 };
816 let geom = &built.layers[li].geom;
817 panel_backend.set_mark_geom(Some(geom.name()));
818 let drawn = geom.draw(
819 layer_data,
820 built.coord.as_ref(),
821 panel_scale_set,
822 theme,
823 &mut panel_backend,
824 );
825 panel_backend.set_mark_geom(None);
826 drawn?;
827 }
828 }
829 }
830 if fixed_grid.is_some() {
831 backend.end_panel();
832 }
833 }
834
835 let title_scales = built.panel_scales.first().unwrap_or(&built.scales);
838 if let (Some(area), Some(xs)) = (&x_title_area, title_scales.get(&Aesthetic::X)) {
839 axis::draw_x_axis_title(xs, theme, area, backend)?;
840 }
841 if let (Some((area, reserve)), Some(ys)) = (&y_title_area, title_scales.get(&Aesthetic::Y))
842 {
843 axis::draw_y_axis_title(ys, theme, area, *reserve, backend)?;
844 }
845
846 if band_h > 0.0 {
848 backend.clear_corner_claims();
849 for li in (0..built.layers.len()).filter(|&li| is_figure(li)) {
850 let empty = crate::data::DataFrame::new();
851 let data = built
852 .panels_data
853 .iter()
854 .filter_map(|p| p.get(li))
855 .find(|d| d.nrows() > 0)
856 .unwrap_or(&empty);
857 let mut band_backend = PanelBackendAdapter {
858 inner: backend,
859 panel_rect: band_rect.clone(),
860 };
861 let geom = &built.layers[li].geom;
862 band_backend.set_mark_geom(Some(geom.name()));
863 let drawn = geom.draw(
864 data,
865 built.coord.as_ref(),
866 &built.scales,
867 theme,
868 &mut band_backend,
869 );
870 band_backend.set_mark_geom(None);
871 drawn?;
872 }
873 }
874
875 let title_ref = Self::title_ref_rect(theme, &plot_area, &total_area);
877 let legend_lift = legend::top_legend_lift(
880 theme,
881 &built.scales,
882 &built.suppressed_aes,
883 &built.legend_plans,
884 &built.guide_legend,
885 );
886 if let Some(ref title) = built.labels.title {
887 let (tx, anchor) = Self::hjust_pos(&theme.title, &title_ref);
888 let (title_y, _) = Self::title_subtitle_y(
889 theme,
890 plot_area.y,
891 legend_lift,
892 built.labels.subtitle.is_some(),
893 );
894 let family = if theme.title.family.is_empty() {
895 None
896 } else {
897 Some(theme.title.family.clone())
898 };
899 backend.draw_text(
900 title,
901 (tx, title_y),
902 &TextStyle {
903 color: theme.title.color,
904 size: theme.title.size,
905 anchor,
906 angle: 0.0,
907 family,
908 face: theme.title.face,
909 bg_colour: None,
910 bg_r: 0.0,
911 },
912 )?;
913 }
914
915 if let Some(ref subtitle) = built.labels.subtitle {
917 let (sx, anchor) = Self::hjust_pos(&theme.subtitle, &title_ref);
918 let (_, subtitle_y) = Self::title_subtitle_y(theme, plot_area.y, legend_lift, true);
919 let family = if theme.subtitle.family.is_empty() {
920 None
921 } else {
922 Some(theme.subtitle.family.clone())
923 };
924 backend.draw_text(
925 subtitle,
926 (sx, subtitle_y),
927 &TextStyle {
928 color: theme.subtitle.color,
929 size: theme.subtitle.size,
930 anchor,
931 angle: 0.0,
932 family,
933 face: theme.subtitle.face,
934 bg_colour: None,
935 bg_r: 0.0,
936 },
937 )?;
938 }
939
940 if let Some(ref caption) = built.labels.caption {
942 let (cx, anchor) = Self::hjust_pos(&theme.caption, &title_ref);
943 let caption_y = total_area.y + total_area.height
946 - theme.plot_margin.bottom.max(0.0)
947 - theme.caption.size * 0.5;
948 let family = if theme.caption.family.is_empty() {
949 None
950 } else {
951 Some(theme.caption.family.clone())
952 };
953 backend.draw_text(
954 caption,
955 (cx, caption_y),
956 &TextStyle {
957 color: theme.caption.color,
958 size: theme.caption.size,
959 anchor,
960 angle: 0.0,
961 family,
962 face: theme.caption.face,
963 bg_colour: None,
964 bg_r: 0.0,
965 },
966 )?;
967 }
968
969 Self::draw_annotations(
971 &built.annotations,
972 &built.scales,
973 built.coord.as_ref(),
974 &plot_area,
975 theme,
976 backend,
977 )?;
978
979 legend::draw_legend_planned(
981 &built.scales,
982 theme,
983 &plot_area,
984 plot_area.width,
985 backend,
986 &built.guide_legend,
987 &built.suppressed_aes,
988 &built.legend_plans,
989 )?;
990
991 Self::draw_tag(&built.labels, theme, &total_area, backend)?;
993
994 Ok(())
995 }
996
997 fn draw_annotations(
1012 annotations: &[Annotation],
1013 scales: &crate::scale::ScaleSet,
1014 coord: &dyn crate::coord::Coord,
1015 plot_area: &crate::render::Rect,
1016 theme: &crate::theme::Theme,
1017 backend: &mut dyn DrawBackend,
1018 ) -> Result<(), RenderError> {
1019 use crate::data::Value;
1020
1021 let x_scale = scales.get(&Aesthetic::X);
1022 let y_scale = scales.get(&Aesthetic::Y);
1023
1024 for ann in annotations {
1025 match ann {
1026 Annotation::Text {
1027 label,
1028 x,
1029 y,
1030 size,
1031 color,
1032 } => {
1033 let nx = x_scale.map(|s| s.map(&Value::Float(*x))).unwrap_or(0.0);
1034 let ny = y_scale.map(|s| s.map(&Value::Float(*y))).unwrap_or(0.0);
1035 let pos = coord.transform((nx, ny), plot_area);
1036 backend.draw_text(
1037 label,
1038 pos,
1039 &TextStyle {
1040 color: annotation_ink(*color, theme),
1041 size: *size,
1042 anchor: TextAnchor::Middle,
1043 angle: 0.0,
1044 family: crate::geom::resolve_font_family("", theme),
1045 face: crate::render::backend::FontFace::Plain,
1046 bg_colour: None,
1047 bg_r: 0.0,
1048 },
1049 )?;
1050 }
1051 Annotation::Rect {
1052 xmin,
1053 xmax,
1054 ymin,
1055 ymax,
1056 fill,
1057 alpha,
1058 } => {
1059 let nx0 = x_scale.map(|s| s.map(&Value::Float(*xmin))).unwrap_or(0.0);
1060 let nx1 = x_scale.map(|s| s.map(&Value::Float(*xmax))).unwrap_or(1.0);
1061 let ny0 = y_scale.map(|s| s.map(&Value::Float(*ymin))).unwrap_or(0.0);
1062 let ny1 = y_scale.map(|s| s.map(&Value::Float(*ymax))).unwrap_or(1.0);
1063 let tl = coord.transform((nx0, ny1), plot_area);
1064 let br = coord.transform((nx1, ny0), plot_area);
1065 backend.draw_rect(
1066 tl,
1067 br,
1068 &RectStyle {
1069 fill: Some(*fill),
1070 stroke: None,
1071 stroke_width: 0.0,
1072 alpha: *alpha,
1073 clip: false,
1074 corner_radius: 0.0,
1075 },
1076 )?;
1077 }
1078 Annotation::Segment {
1079 x,
1080 y,
1081 xend,
1082 yend,
1083 color,
1084 width,
1085 } => {
1086 let nx0 = x_scale.map(|s| s.map(&Value::Float(*x))).unwrap_or(0.0);
1087 let ny0 = y_scale.map(|s| s.map(&Value::Float(*y))).unwrap_or(0.0);
1088 let nx1 = x_scale.map(|s| s.map(&Value::Float(*xend))).unwrap_or(1.0);
1089 let ny1 = y_scale.map(|s| s.map(&Value::Float(*yend))).unwrap_or(1.0);
1090 let p0 = coord.transform((nx0, ny0), plot_area);
1091 let p1 = coord.transform((nx1, ny1), plot_area);
1092 backend.draw_line(
1093 &[p0, p1],
1094 &LineStyle {
1095 color: annotation_ink(*color, theme),
1096 alpha: 1.0,
1097 width: *width,
1098 linetype: Linetype::Solid,
1099 ..Default::default()
1100 },
1101 )?;
1102 }
1103 }
1104 }
1105 Ok(())
1106 }
1107}
1108
1109fn annotation_ink(color: (u8, u8, u8), theme: &crate::theme::Theme) -> (u8, u8, u8) {
1115 match (color == (50, 50, 50), theme.geom.ink) {
1116 (true, Some(ink)) => ink,
1117 _ => color,
1118 }
1119}
1120
1121struct PanelBackendAdapter<'a> {
1123 inner: &'a mut dyn DrawBackend,
1124 panel_rect: crate::render::Rect,
1125}
1126
1127impl<'a> DrawBackend for PanelBackendAdapter<'a> {
1128 fn draw_image(
1129 &mut self,
1130 bytes: &[u8],
1131 rect: &crate::render::Rect,
1132 clip: &crate::render::Rect,
1133 alpha: f64,
1134 ) -> Result<(), RenderError> {
1135 self.inner.draw_image(bytes, rect, clip, alpha)
1136 }
1137 fn draw_circle(
1138 &mut self,
1139 center: (f64, f64),
1140 radius: f64,
1141 style: &crate::render::backend::PointStyle,
1142 ) -> Result<(), RenderError> {
1143 self.inner.draw_circle(center, radius, style)
1144 }
1145 fn draw_line(
1146 &mut self,
1147 points: &[(f64, f64)],
1148 style: &crate::render::backend::LineStyle,
1149 ) -> Result<(), RenderError> {
1150 self.inner.draw_line(points, style)
1151 }
1152 fn draw_quadratic(
1153 &mut self,
1154 p0: (f64, f64),
1155 ctrl: (f64, f64),
1156 p1: (f64, f64),
1157 style: &crate::render::backend::LineStyle,
1158 ) -> Result<(), RenderError> {
1159 self.inner.draw_quadratic(p0, ctrl, p1, style)
1160 }
1161 fn draw_rect(
1162 &mut self,
1163 top_left: (f64, f64),
1164 bottom_right: (f64, f64),
1165 style: &RectStyle,
1166 ) -> Result<(), RenderError> {
1167 self.inner.draw_rect(top_left, bottom_right, style)
1168 }
1169 fn draw_text(
1170 &mut self,
1171 text: &str,
1172 pos: (f64, f64),
1173 style: &TextStyle,
1174 ) -> Result<(), RenderError> {
1175 self.inner.draw_text(text, pos, style)
1176 }
1177 fn draw_polygon(
1178 &mut self,
1179 points: &[(f64, f64)],
1180 style: &RectStyle,
1181 ) -> Result<(), RenderError> {
1182 self.inner.draw_polygon(points, style)
1183 }
1184 fn draw_polygon_rings(
1185 &mut self,
1186 rings: &[Vec<(f64, f64)>],
1187 style: &RectStyle,
1188 ) -> Result<(), RenderError> {
1189 self.inner.draw_polygon_rings(rings, style)
1190 }
1191 fn draw_shape(
1192 &mut self,
1193 center: (f64, f64),
1194 radius: f64,
1195 style: &crate::render::backend::PointStyle,
1196 ) -> Result<(), RenderError> {
1197 self.inner.draw_shape(center, radius, style)
1198 }
1199 fn plot_area(&self) -> crate::render::Rect {
1200 self.panel_rect.clone()
1201 }
1202 fn set_tooltip(&mut self, tooltip: Option<String>) {
1203 self.inner.set_tooltip(tooltip)
1204 }
1205 fn set_mark_axis(&mut self, key: Option<String>) {
1206 self.inner.set_mark_axis(key)
1207 }
1208 fn set_mark_series(&mut self, series: Option<String>) {
1209 self.inner.set_mark_series(series)
1210 }
1211 fn set_mark_value(&mut self, value: Option<String>) {
1212 self.inner.set_mark_value(value)
1213 }
1214 fn set_mark_geom(&mut self, geom: Option<&str>) {
1215 self.inner.set_mark_geom(geom)
1216 }
1217 fn warn(&mut self, message: String) {
1218 self.inner.warn(message)
1219 }
1220 fn set_mark_part(&mut self, part: Option<&str>) {
1221 self.inner.set_mark_part(part)
1222 }
1223 fn set_mark_attrs(&mut self, attrs: Vec<(String, String)>) {
1224 self.inner.set_mark_attrs(attrs)
1225 }
1226
1227 fn set_mark_class(&mut self, class: Option<String>) {
1228 self.inner.set_mark_class(class)
1229 }
1230
1231 fn set_mark_group(&mut self, group: Option<(String, String)>) {
1232 self.inner.set_mark_group(group)
1233 }
1234
1235 fn set_layer_attrs(&mut self, attrs: Vec<(String, String)>) {
1236 self.inner.set_layer_attrs(attrs)
1237 }
1238 fn wants_formatted_values(&self) -> bool {
1239 self.inner.wants_formatted_values()
1240 }
1241 fn set_mark_fvalue(&mut self, value: Option<String>) {
1242 self.inner.set_mark_fvalue(value)
1243 }
1244 fn set_mark_fx(&mut self, value: Option<String>) {
1245 self.inner.set_mark_fx(value)
1246 }
1247 fn total_area(&self) -> crate::render::Rect {
1248 self.inner.total_area()
1249 }
1250 fn corner_claims(&self) -> Vec<crate::render::Rect> {
1251 self.inner.corner_claims()
1252 }
1253 fn claim_corner_box(&mut self, rect: crate::render::Rect) {
1254 self.inner.claim_corner_box(rect)
1255 }
1256 fn clear_corner_claims(&mut self) {
1257 self.inner.clear_corner_claims()
1258 }
1259}
1260
1261#[cfg(test)]
1262mod tests {
1263 use super::*;
1264 use crate::render::backend::LineStyle;
1265 use crate::render::svg_backend::SvgBackend;
1266
1267 #[test]
1273 fn panel_backend_adapter_forwards_quadratic_and_mark_metadata() {
1274 let total = crate::render::Rect {
1275 x: 0.0,
1276 y: 0.0,
1277 width: 200.0,
1278 height: 200.0,
1279 };
1280 let panel_rect = crate::render::Rect {
1281 x: 10.0,
1282 y: 10.0,
1283 width: 80.0,
1284 height: 80.0,
1285 };
1286 let mut backend = SvgBackend::new(200, 200, total);
1287 let mut adapter = PanelBackendAdapter {
1288 inner: &mut backend,
1289 panel_rect: panel_rect.clone(),
1290 };
1291
1292 assert_eq!(adapter.plot_area().x, panel_rect.x);
1293 assert_eq!(adapter.plot_area().width, panel_rect.width);
1294
1295 adapter.set_mark_part(Some("arrow"));
1296 adapter.set_mark_attrs(vec![("data-x".to_string(), "v".to_string())]);
1297 adapter
1298 .draw_quadratic((0.0, 0.0), (5.0, 10.0), (10.0, 0.0), &LineStyle::default())
1299 .unwrap();
1300 adapter.set_mark_part(None);
1301 adapter.set_mark_attrs(Vec::new());
1302
1303 let svg = backend.finish();
1304 assert!(svg.contains("<path"));
1305 assert!(svg.contains("data-part=\"arrow\""));
1306 assert!(svg.contains("data-x=\"v\""));
1307 }
1308}