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 Annotation::LogTicks {
1104 sides,
1105 outside,
1106 lengths,
1107 color,
1108 width,
1109 } => {
1110 let style = LineStyle {
1111 color: annotation_ink(*color, theme),
1112 alpha: 1.0,
1113 width: *width,
1114 linetype: Linetype::Solid,
1115 ..Default::default()
1116 };
1117 let dir = if *outside { -1.0 } else { 1.0 };
1118 for side in sides.chars() {
1119 let horizontal = matches!(side, 'b' | 't');
1120 let scale = if horizontal { x_scale } else { y_scale };
1121 let Some((lo, hi)) = scale.and_then(|s| s.expanded_domain()) else {
1122 continue;
1123 };
1124 for (t, kind) in log_ticks(lo, hi) {
1125 let n = scale.map(|s| s.map(&Value::Float(t))).unwrap_or(0.0);
1126 let len = dir
1127 * match kind {
1128 0 => lengths.2,
1129 1 => lengths.1,
1130 _ => lengths.0,
1131 };
1132 let (edge, step) = match side {
1134 'b' => ((n, 0.0), (0.0, -len)),
1135 't' => ((n, 1.0), (0.0, len)),
1136 'l' => ((0.0, n), (len, 0.0)),
1137 'r' => ((1.0, n), (-len, 0.0)),
1138 _ => continue,
1139 };
1140 let p0 = coord.transform(edge, plot_area);
1141 let p1 = (p0.0 + step.0, p0.1 + step.1);
1142 backend.draw_line(&[p0, p1], &style)?;
1143 }
1144 }
1145 }
1146 Annotation::Raster {
1147 image,
1148 xmin,
1149 xmax,
1150 ymin,
1151 ymax,
1152 alpha,
1153 } => {
1154 let rect = data_rect(
1155 x_scale,
1156 y_scale,
1157 coord,
1158 plot_area,
1159 [*xmin, *xmax, *ymin, *ymax],
1160 );
1161 backend.draw_image(image, &rect, plot_area, *alpha)?;
1162 }
1163 Annotation::Inset {
1164 plot,
1165 xmin,
1166 xmax,
1167 ymin,
1168 ymax,
1169 } => {
1170 let rect = data_rect(
1171 x_scale,
1172 y_scale,
1173 coord,
1174 plot_area,
1175 [*xmin, *xmax, *ymin, *ymax],
1176 );
1177 let (w, h) = (rect.width.round().max(1.0), rect.height.round().max(1.0));
1178 let mut guard = plot.0.lock().unwrap_or_else(|e| e.into_inner());
1179 match &mut *guard {
1180 Ok((built, meta)) => {
1181 let layout =
1182 crate::GGPlot::layout_built(built, meta, w as u32, h as u32);
1183 let mut off = crate::render::offset::OffsetBackend::new(
1184 backend,
1185 rect.x,
1186 rect.y,
1187 layout.plot_area.clone(),
1188 layout.total.clone(),
1189 );
1190 Self::render(built, &mut off)?;
1191 }
1192 Err(e) => {
1193 backend.warn(format!("annotation_custom: inset plot failed: {e}"))
1194 }
1195 }
1196 }
1197 }
1198 }
1199 Ok(())
1200 }
1201}
1202
1203fn log_ticks(lo: f64, hi: f64) -> Vec<(f64, u8)> {
1206 let mut out = Vec::new();
1207 if !(lo.is_finite() && hi.is_finite()) || hi - lo > 300.0 {
1208 return out;
1209 }
1210 for k in (lo.floor() as i64)..=(hi.ceil() as i64) {
1211 for m in 1..=9 {
1212 let t = k as f64 + (m as f64).log10();
1213 if t >= lo - 1e-9 && t <= hi + 1e-9 {
1214 out.push((
1215 t,
1216 if m == 1 {
1217 0
1218 } else if m == 5 {
1219 1
1220 } else {
1221 2
1222 },
1223 ));
1224 }
1225 }
1226 }
1227 out
1228}
1229
1230fn data_rect(
1233 x_scale: Option<&dyn crate::scale::Scale>,
1234 y_scale: Option<&dyn crate::scale::Scale>,
1235 coord: &dyn crate::coord::Coord,
1236 plot_area: &crate::render::Rect,
1237 [xmin, xmax, ymin, ymax]: [f64; 4],
1238) -> crate::render::Rect {
1239 use crate::data::Value;
1240 let m = |s: Option<&dyn crate::scale::Scale>, v: f64, edge: f64| {
1241 if v.is_finite() {
1242 s.map(|s| s.map(&Value::Float(v))).unwrap_or(edge)
1243 } else if v > 0.0 {
1244 1.0
1245 } else {
1246 0.0
1247 }
1248 };
1249 let a = coord.transform((m(x_scale, xmin, 0.0), m(y_scale, ymax, 1.0)), plot_area);
1250 let b = coord.transform((m(x_scale, xmax, 1.0), m(y_scale, ymin, 0.0)), plot_area);
1251 crate::render::Rect {
1252 x: a.0.min(b.0),
1253 y: a.1.min(b.1),
1254 width: (a.0 - b.0).abs(),
1255 height: (a.1 - b.1).abs(),
1256 }
1257}
1258
1259fn annotation_ink(color: (u8, u8, u8), theme: &crate::theme::Theme) -> (u8, u8, u8) {
1265 match (color == (50, 50, 50), theme.geom.ink) {
1266 (true, Some(ink)) => ink,
1267 _ => color,
1268 }
1269}
1270
1271struct PanelBackendAdapter<'a> {
1273 inner: &'a mut dyn DrawBackend,
1274 panel_rect: crate::render::Rect,
1275}
1276
1277impl<'a> DrawBackend for PanelBackendAdapter<'a> {
1278 fn set_clipping(&mut self, on: bool) {
1279 self.inner.set_clipping(on)
1280 }
1281 fn draw_image(
1282 &mut self,
1283 bytes: &[u8],
1284 rect: &crate::render::Rect,
1285 clip: &crate::render::Rect,
1286 alpha: f64,
1287 ) -> Result<(), RenderError> {
1288 self.inner.draw_image(bytes, rect, clip, alpha)
1289 }
1290 fn draw_circle(
1291 &mut self,
1292 center: (f64, f64),
1293 radius: f64,
1294 style: &crate::render::backend::PointStyle,
1295 ) -> Result<(), RenderError> {
1296 self.inner.draw_circle(center, radius, style)
1297 }
1298 fn draw_line(
1299 &mut self,
1300 points: &[(f64, f64)],
1301 style: &crate::render::backend::LineStyle,
1302 ) -> Result<(), RenderError> {
1303 self.inner.draw_line(points, style)
1304 }
1305 fn draw_quadratic(
1306 &mut self,
1307 p0: (f64, f64),
1308 ctrl: (f64, f64),
1309 p1: (f64, f64),
1310 style: &crate::render::backend::LineStyle,
1311 ) -> Result<(), RenderError> {
1312 self.inner.draw_quadratic(p0, ctrl, p1, style)
1313 }
1314 fn draw_rect(
1315 &mut self,
1316 top_left: (f64, f64),
1317 bottom_right: (f64, f64),
1318 style: &RectStyle,
1319 ) -> Result<(), RenderError> {
1320 self.inner.draw_rect(top_left, bottom_right, style)
1321 }
1322 fn draw_text(
1323 &mut self,
1324 text: &str,
1325 pos: (f64, f64),
1326 style: &TextStyle,
1327 ) -> Result<(), RenderError> {
1328 self.inner.draw_text(text, pos, style)
1329 }
1330 fn draw_polygon(
1331 &mut self,
1332 points: &[(f64, f64)],
1333 style: &RectStyle,
1334 ) -> Result<(), RenderError> {
1335 self.inner.draw_polygon(points, style)
1336 }
1337 fn draw_polygon_rings(
1338 &mut self,
1339 rings: &[Vec<(f64, f64)>],
1340 style: &RectStyle,
1341 ) -> Result<(), RenderError> {
1342 self.inner.draw_polygon_rings(rings, style)
1343 }
1344 fn draw_shape(
1345 &mut self,
1346 center: (f64, f64),
1347 radius: f64,
1348 style: &crate::render::backend::PointStyle,
1349 ) -> Result<(), RenderError> {
1350 self.inner.draw_shape(center, radius, style)
1351 }
1352 fn plot_area(&self) -> crate::render::Rect {
1353 self.panel_rect.clone()
1354 }
1355 fn set_tooltip(&mut self, tooltip: Option<String>) {
1356 self.inner.set_tooltip(tooltip)
1357 }
1358 fn set_mark_axis(&mut self, key: Option<String>) {
1359 self.inner.set_mark_axis(key)
1360 }
1361 fn set_mark_series(&mut self, series: Option<String>) {
1362 self.inner.set_mark_series(series)
1363 }
1364 fn set_mark_value(&mut self, value: Option<String>) {
1365 self.inner.set_mark_value(value)
1366 }
1367 fn set_mark_geom(&mut self, geom: Option<&str>) {
1368 self.inner.set_mark_geom(geom)
1369 }
1370 fn warn(&mut self, message: String) {
1371 self.inner.warn(message)
1372 }
1373 fn set_mark_part(&mut self, part: Option<&str>) {
1374 self.inner.set_mark_part(part)
1375 }
1376 fn set_mark_attrs(&mut self, attrs: Vec<(String, String)>) {
1377 self.inner.set_mark_attrs(attrs)
1378 }
1379
1380 fn set_mark_class(&mut self, class: Option<String>) {
1381 self.inner.set_mark_class(class)
1382 }
1383
1384 fn set_mark_group(&mut self, group: Option<(String, String)>) {
1385 self.inner.set_mark_group(group)
1386 }
1387
1388 fn set_layer_attrs(&mut self, attrs: Vec<(String, String)>) {
1389 self.inner.set_layer_attrs(attrs)
1390 }
1391 fn wants_formatted_values(&self) -> bool {
1392 self.inner.wants_formatted_values()
1393 }
1394 fn set_mark_fvalue(&mut self, value: Option<String>) {
1395 self.inner.set_mark_fvalue(value)
1396 }
1397 fn set_mark_fx(&mut self, value: Option<String>) {
1398 self.inner.set_mark_fx(value)
1399 }
1400 fn total_area(&self) -> crate::render::Rect {
1401 self.inner.total_area()
1402 }
1403 fn corner_claims(&self) -> Vec<crate::render::Rect> {
1404 self.inner.corner_claims()
1405 }
1406 fn claim_corner_box(&mut self, rect: crate::render::Rect) {
1407 self.inner.claim_corner_box(rect)
1408 }
1409 fn clear_corner_claims(&mut self) {
1410 self.inner.clear_corner_claims()
1411 }
1412}
1413
1414#[cfg(test)]
1415mod tests {
1416 use super::*;
1417 use crate::render::backend::LineStyle;
1418 use crate::render::svg_backend::SvgBackend;
1419
1420 #[test]
1426 fn panel_backend_adapter_forwards_quadratic_and_mark_metadata() {
1427 let total = crate::render::Rect {
1428 x: 0.0,
1429 y: 0.0,
1430 width: 200.0,
1431 height: 200.0,
1432 };
1433 let panel_rect = crate::render::Rect {
1434 x: 10.0,
1435 y: 10.0,
1436 width: 80.0,
1437 height: 80.0,
1438 };
1439 let mut backend = SvgBackend::new(200, 200, total);
1440 let mut adapter = PanelBackendAdapter {
1441 inner: &mut backend,
1442 panel_rect: panel_rect.clone(),
1443 };
1444
1445 assert_eq!(adapter.plot_area().x, panel_rect.x);
1446 assert_eq!(adapter.plot_area().width, panel_rect.width);
1447
1448 adapter.set_mark_part(Some("arrow"));
1449 adapter.set_mark_attrs(vec![("data-x".to_string(), "v".to_string())]);
1450 adapter
1451 .draw_quadratic((0.0, 0.0), (5.0, 10.0), (10.0, 0.0), &LineStyle::default())
1452 .unwrap();
1453 adapter.set_mark_part(None);
1454 adapter.set_mark_attrs(Vec::new());
1455
1456 let svg = backend.finish();
1457 assert!(svg.contains("<path"));
1458 assert!(svg.contains("data-part=\"arrow\""));
1459 assert!(svg.contains("data-x=\"v\""));
1460 }
1461}