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