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