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 fn top_axis_lift(built: &BuiltPlot, theme: &crate::theme::Theme) -> f64 {
16 let Some(xs) = built.scales.get(&Aesthetic::X) else {
17 return 0.0;
18 };
19 if xs.axis_position_opposite() {
20 theme.axis_ticks_length + theme.axis_text_x.size + theme.legend_spacing
21 } else if let Some(sec) = xs.sec_axis().filter(|_| built.facet.is_none()) {
22 axis::sec_x_axis_height(theme, sec)
23 } else {
24 0.0
25 }
26 }
27
28 pub fn render(built: &BuiltPlot, backend: &mut dyn DrawBackend) -> Result<(), RenderError> {
29 backend.enable_element_classes(built.svg_classes);
31 let drawn = if !built.facet.is_none() && !built.panels.is_empty() {
32 Self::render_faceted(built, backend)
33 } else {
34 Self::render_single(built, backend)
35 };
36 backend.set_element_class(None);
37 backend.enable_element_classes(false);
38 drawn
39 }
40
41 fn render_single(built: &BuiltPlot, backend: &mut dyn DrawBackend) -> Result<(), RenderError> {
42 let theme = &built.theme;
43 let plot_area = backend.plot_area();
44 let total_area = backend.total_area();
45 backend.clear_corner_claims();
51 if let Some(r) = built.coord.data_rect(&plot_area) {
52 backend.set_viewport(&r);
53 }
54 if let Some(rect) = legend::inside_legend_box(
55 &built.scales,
56 theme,
57 &plot_area,
58 &total_area,
59 &built.guide_legend,
60 &built.suppressed_aes,
61 &built.legend_plans,
62 ) {
63 backend.claim_corner_box(rect);
64 }
65
66 if theme.plot_background.visible {
68 if let Some(fill) = theme.plot_background.fill {
69 backend.set_element_class(Some("gg-plot-background"));
70 backend.draw_rect(
71 (total_area.x, total_area.y),
72 (
73 total_area.x + total_area.width,
74 total_area.y + total_area.height,
75 ),
76 &RectStyle {
77 fill: Some(fill),
78 stroke: None,
79 stroke_width: 0.0,
80 alpha: 1.0,
81 clip: false,
82 corner_radius: 0.0,
83 },
84 )?;
85 }
86 }
87
88 if theme.panel_background.visible {
90 if let Some(fill) = theme.panel_background.fill {
91 backend.set_element_class(Some("gg-panel-background"));
92 backend.draw_rect(
93 (plot_area.x, plot_area.y),
94 (
95 plot_area.x + plot_area.width,
96 plot_area.y + plot_area.height,
97 ),
98 &RectStyle {
99 fill: Some(fill),
100 stroke: theme.panel_background.color,
101 stroke_width: theme.panel_background.width,
102 alpha: 1.0,
103 clip: false,
104 corner_radius: 0.0,
105 },
106 )?;
107 }
108 }
109
110 if theme.panel_border.visible {
112 let style = LineStyle {
113 color: theme.panel_border.color,
114 width: theme.panel_border.width,
115 alpha: 1.0,
116 linetype: Linetype::Solid,
117 ..Default::default()
118 };
119 let x0 = plot_area.x;
120 let y0 = plot_area.y;
121 let x1 = plot_area.x + plot_area.width;
122 let y1 = plot_area.y + plot_area.height;
123 backend.set_element_class(Some("gg-panel-border"));
124 backend.draw_line(&[(x0, y0), (x1, y0)], &style)?;
125 backend.draw_line(&[(x1, y0), (x1, y1)], &style)?;
126 backend.draw_line(&[(x1, y1), (x0, y1)], &style)?;
127 backend.draw_line(&[(x0, y1), (x0, y0)], &style)?;
128 }
129
130 let x_scale = built.scales.get(&Aesthetic::X);
132 let y_scale = built.scales.get(&Aesthetic::Y);
133
134 let (h_scale, v_scale) = if built.coord.is_flipped() {
135 (y_scale, x_scale)
136 } else {
137 (x_scale, y_scale)
138 };
139
140 if let (true, Some(xs), Some(ys)) = (built.coord.is_radar(), x_scale, y_scale) {
141 crate::guide::polar::draw_radar_guides(
143 xs,
144 ys,
145 built.coord.as_ref(),
146 theme,
147 &plot_area,
148 backend,
149 )?;
150 } else if let (true, Some(xs), Some(ys)) = (built.coord.is_polar(), x_scale, y_scale) {
151 crate::guide::polar::draw_polar_guides(
154 xs,
155 ys,
156 built.coord.as_ref(),
157 theme,
158 &plot_area,
159 backend,
160 )?;
161 axis::draw_x_axis_title(xs, theme, &plot_area, backend)?;
162 let reserve = axis::y_label_reserve(theme, &ys.breaks());
163 axis::draw_y_axis_title(ys, theme, &plot_area, reserve, backend)?;
164 } else if let (Some(hs), Some(vs)) = (h_scale, v_scale) {
165 if built.coord.gridlines() && !theme.panel_ontop {
167 axis::draw_gridlines(hs, vs, built.coord.as_ref(), theme, &plot_area, backend)?;
168 }
169
170 axis::draw_x_axis(
172 hs,
173 built.coord.as_ref(),
174 theme,
175 &plot_area,
176 &total_area,
177 backend,
178 )?;
179 axis::draw_y_axis(vs, built.coord.as_ref(), theme, &plot_area, backend)?;
180
181 if let Some(sec) = built.scales.sec_axis(&Aesthetic::Y) {
183 axis::draw_sec_y_axis(vs, sec, built.coord.as_ref(), theme, &plot_area, backend)?;
184 }
185 if let Some(sec) = hs.sec_axis().filter(|_| !hs.axis_position_opposite()) {
188 axis::draw_sec_x_axis(hs, sec, built.coord.as_ref(), theme, &plot_area, backend)?;
189 }
190 }
191
192 backend.set_element_class(None);
198 let clip_group = built.clip
199 && built
200 .layers
201 .iter()
202 .any(|l| layer_spills(&l.data, &built.scales, built.coord.as_ref(), &plot_area));
203 if clip_group {
204 backend.begin_clip(&plot_area);
205 }
206 if !built.clip {
207 backend.set_clipping(false);
208 }
209 for layer in built.layers.iter().filter(|l| l.data.nrows() > 0) {
210 backend.set_mark_geom(Some(layer.geom.name()));
211 let drawn = layer.geom.draw(
212 &layer.data,
213 built.coord.as_ref(),
214 &built.scales,
215 theme,
216 backend,
217 );
218 backend.set_mark_geom(None);
219 drawn?;
220 }
221 if !built.clip {
222 backend.set_clipping(true);
223 }
224 if clip_group {
225 backend.end_clip();
226 }
227
228 if theme.panel_ontop && built.coord.gridlines() {
230 if let (Some(hs), Some(vs)) = (h_scale, v_scale) {
231 axis::draw_gridlines(hs, vs, built.coord.as_ref(), theme, &plot_area, backend)?;
232 }
233 }
234
235 backend.set_element_class(None);
236 Self::draw_annotations(
238 &built.annotations,
239 &built.scales,
240 built.coord.as_ref(),
241 &plot_area,
242 theme,
243 backend,
244 )?;
245
246 let title_ref = Self::title_ref_rect(theme, &plot_area, &total_area);
248 let caption_ref = Self::ref_rect(
249 theme.caption_position.unwrap_or(theme.title_position),
250 theme,
251 &plot_area,
252 &total_area,
253 );
254 let legend_lift = legend::top_legend_lift(
259 theme,
260 &built.scales,
261 &built.suppressed_aes,
262 &built.legend_plans,
263 &built.guide_legend,
264 );
265 if let Some(ref title) = built.labels.title {
266 let (tx, anchor) = Self::hjust_pos(&theme.title, &title_ref);
267 let x_axis_lift = Self::top_axis_lift(built, theme);
270 let (title_y, _) = Self::title_subtitle_y(
271 theme,
272 plot_area.y,
273 x_axis_lift + legend_lift,
274 built.labels.subtitle.is_some(),
275 );
276 let family = if theme.title.family.is_empty() {
277 None
278 } else {
279 Some(theme.title.family.clone())
280 };
281 backend.set_element_class(Some("gg-title"));
282 crate::render::rich::draw(
283 backend,
284 theme.rich_text,
285 title,
286 (tx, title_y),
287 &TextStyle {
288 color: theme.title.color,
289 size: theme.title.size,
290 anchor,
291 angle: 0.0,
292 family,
293 face: theme.title.face,
294 bg_colour: None,
295 bg_r: 0.0,
296 },
297 )?;
298 }
299
300 if let Some(ref subtitle) = built.labels.subtitle {
302 let (sx, anchor) = Self::hjust_pos(&theme.subtitle, &title_ref);
303 let x_axis_lift = Self::top_axis_lift(built, theme);
305 let (_, subtitle_y) =
306 Self::title_subtitle_y(theme, plot_area.y, x_axis_lift + legend_lift, true);
307 let family = if theme.subtitle.family.is_empty() {
308 None
309 } else {
310 Some(theme.subtitle.family.clone())
311 };
312 backend.set_element_class(Some("gg-subtitle"));
313 crate::render::rich::draw(
314 backend,
315 theme.rich_text,
316 subtitle,
317 (sx, subtitle_y),
318 &TextStyle {
319 color: theme.subtitle.color,
320 size: theme.subtitle.size,
321 anchor,
322 angle: 0.0,
323 family,
324 face: theme.subtitle.face,
325 bg_colour: None,
326 bg_r: 0.0,
327 },
328 )?;
329 }
330
331 legend::draw_legend_planned(
333 &built.scales,
334 theme,
335 &plot_area,
336 plot_area.width,
337 backend,
338 &built.guide_legend,
339 &built.suppressed_aes,
340 &built.legend_plans,
341 )?;
342
343 if let Some(ref caption) = built.labels.caption {
345 let (cx, anchor) = Self::hjust_pos(&theme.caption, &caption_ref);
346 let caption_y = total_area.y + total_area.height
349 - theme.plot_margin.bottom.max(0.0)
350 - theme.caption.size * 0.5;
351 let family = if theme.caption.family.is_empty() {
352 None
353 } else {
354 Some(theme.caption.family.clone())
355 };
356 backend.set_element_class(Some("gg-caption"));
357 crate::render::rich::draw(
358 backend,
359 theme.rich_text,
360 caption,
361 (cx, caption_y),
362 &TextStyle {
363 color: theme.caption.color,
364 size: theme.caption.size,
365 anchor,
366 angle: 0.0,
367 family,
368 face: theme.caption.face,
369 bg_colour: None,
370 bg_r: 0.0,
371 },
372 )?;
373 }
374
375 let tag_area = if theme.tag_location_panel {
379 &plot_area
380 } else {
381 &total_area
382 };
383 Self::draw_tag(&built.labels, theme, tag_area, backend)?;
384
385 Ok(())
386 }
387
388 fn hjust_pos(
391 el: &crate::theme::elements::ElementText,
392 area: &crate::render::Rect,
393 ) -> (f64, TextAnchor) {
394 let x = area.x + el.hjust.clamp(0.0, 1.0) * area.width;
395 let anchor = if el.hjust <= 0.02 {
396 TextAnchor::Start
397 } else if el.hjust >= 0.98 {
398 TextAnchor::End
399 } else {
400 TextAnchor::Middle
401 };
402 (x, anchor)
403 }
404
405 fn title_subtitle_y(
414 theme: &crate::theme::Theme,
415 panel_top: f64,
416 lift: f64,
417 has_subtitle: bool,
418 ) -> (f64, f64) {
419 let base = panel_top - lift;
420 let subtitle_band = if has_subtitle {
421 theme.subtitle.size * 1.5
422 } else {
423 0.0
424 };
425 let subtitle_y = base - subtitle_band / 2.0;
426 let title_y = (base - subtitle_band - theme.title.size * 0.9).max(theme.title.size);
427 (title_y, subtitle_y)
428 }
429
430 fn title_ref_rect(
433 theme: &crate::theme::Theme,
434 plot_area: &crate::render::Rect,
435 total_area: &crate::render::Rect,
436 ) -> crate::render::Rect {
437 Self::ref_rect(theme.title_position, theme, plot_area, total_area)
438 }
439
440 fn ref_rect(
443 pos: crate::theme::TitlePosition,
444 theme: &crate::theme::Theme,
445 plot_area: &crate::render::Rect,
446 total_area: &crate::render::Rect,
447 ) -> crate::render::Rect {
448 match pos {
449 crate::theme::TitlePosition::Panel => plot_area.clone(),
450 crate::theme::TitlePosition::Plot => crate::render::Rect {
451 x: total_area.x + theme.plot_margin.left,
452 y: plot_area.y,
453 width: (total_area.width - theme.plot_margin.left - theme.plot_margin.right)
454 .max(1.0),
455 height: plot_area.height,
456 },
457 }
458 }
459
460 pub(crate) fn draw_cell_tag(
463 labels: &crate::plot::Labels,
464 theme: &crate::theme::Theme,
465 area: &crate::render::Rect,
466 backend: &mut dyn DrawBackend,
467 ) -> Result<(), RenderError> {
468 Self::draw_tag(labels, theme, area, backend)
469 }
470
471 fn draw_tag(
472 labels: &crate::plot::Labels,
473 theme: &crate::theme::Theme,
474 total_area: &crate::render::Rect,
475 backend: &mut dyn DrawBackend,
476 ) -> Result<(), RenderError> {
477 if let Some(ref tag) = labels.tag {
478 let family = if theme.title.family.is_empty() {
479 None
480 } else {
481 Some(theme.title.family.clone())
482 };
483 use crate::theme::TagPosition::*;
484 let pad = theme.title.size;
485 let (left, right) = (total_area.x + pad, total_area.x + total_area.width - pad);
486 let (top, bottom) = (total_area.y + pad, total_area.y + total_area.height - pad);
487 let (x, y, anchor) = match theme.tag_position {
488 TopLeft => (left, top, TextAnchor::Start),
489 TopRight => (right, top, TextAnchor::End),
490 BottomLeft => (left, bottom, TextAnchor::Start),
491 BottomRight => (right, bottom, TextAnchor::End),
492 };
493 backend.set_element_class(Some("gg-tag"));
494 backend.draw_text(
495 tag,
496 (x, y),
497 &TextStyle {
498 color: theme.title.color,
499 size: theme.title.size,
500 anchor,
501 angle: 0.0,
502 family,
503 face: theme.title.face,
504 bg_colour: None,
505 bg_r: 0.0,
506 },
507 )?;
508 }
509 Ok(())
510 }
511
512 fn render_faceted(built: &BuiltPlot, backend: &mut dyn DrawBackend) -> Result<(), RenderError> {
513 let theme = &built.theme;
514 let plot_area = backend.plot_area();
515 let total_area = backend.total_area();
516
517 if theme.plot_background.visible {
519 if let Some(fill) = theme.plot_background.fill {
520 backend.set_element_class(Some("gg-plot-background"));
521 backend.draw_rect(
522 (total_area.x, total_area.y),
523 (
524 total_area.x + total_area.width,
525 total_area.y + total_area.height,
526 ),
527 &RectStyle {
528 fill: Some(fill),
529 stroke: None,
530 stroke_width: 0.0,
531 alpha: 1.0,
532 clip: false,
533 corner_radius: 0.0,
534 },
535 )?;
536 }
537 }
538
539 let ncol = match &built.facet {
541 Facet::Wrap { ncol, .. } => {
542 ncol.unwrap_or_else(|| (built.panels.len() as f64).sqrt().ceil() as usize)
543 }
544 Facet::Grid { .. } => built.panels.iter().map(|p| p.col).max().unwrap_or(0) + 1,
545 Facet::None => 1,
546 };
547 let mut nrow = built.panels.len().div_ceil(ncol);
548
549 let strip_lines = |label: &dyn Fn(&crate::facet::Panel) -> Option<String>| {
552 built
553 .panels
554 .iter()
555 .filter_map(|p| label(p).map(|l| l.lines().count()))
556 .max()
557 .unwrap_or(1)
558 .max(1)
559 };
560 let size_x = theme
563 .strip_text_x
564 .as_ref()
565 .unwrap_or(&theme.strip_text)
566 .size;
567 let size_y = theme
568 .strip_text_y
569 .as_ref()
570 .unwrap_or(&theme.strip_text)
571 .size;
572 let thickness = |lines: usize, size: f64| size * (1.0 + 1.2 * (lines - 1) as f64) + 8.0;
573 let is_grid = matches!(built.facet, Facet::Grid { .. });
577 let col_strip = |p: &crate::facet::Panel| {
578 if is_grid {
579 p.col_label.clone()
580 } else {
581 Some(p.label.clone())
582 }
583 };
584 let row_strip = |p: &crate::facet::Panel| {
585 if is_grid {
586 p.row_label.clone()
587 } else {
588 None
589 }
590 };
591 let row_strip_width = thickness(strip_lines(&row_strip), size_y);
592 let (wrap_side, switch) = match &built.facet {
593 Facet::Wrap { strip_position, .. } => {
594 (*strip_position, crate::facet::FacetSwitch::None)
595 }
596 Facet::Grid { switch, .. } => (crate::facet::StripPosition::Top, *switch),
597 Facet::None => (
598 crate::facet::StripPosition::Top,
599 crate::facet::FacetSwitch::None,
600 ),
601 };
602 let wrap_vertical = matches!(
604 wrap_side,
605 crate::facet::StripPosition::Left | crate::facet::StripPosition::Right
606 );
607 let (free_x, free_y) = match &built.facet {
610 Facet::Wrap { scales, .. } => (
611 matches!(
612 scales,
613 crate::facet::FacetScales::FreeX | crate::facet::FacetScales::Free
614 ),
615 matches!(
616 scales,
617 crate::facet::FacetScales::FreeY | crate::facet::FacetScales::Free
618 ),
619 ),
620 _ => (false, false),
621 };
622 let flipped = built.coord.is_flipped();
626 let (h_aes, v_aes) = if flipped {
627 (Aesthetic::Y, Aesthetic::X)
628 } else {
629 (Aesthetic::X, Aesthetic::Y)
630 };
631 let (free_x, free_y) = if flipped {
632 (free_y, free_x)
633 } else {
634 (free_x, free_y)
635 };
636 let all_x = built.facet_axes.all_x || free_x;
637 let all_y = built.facet_axes.all_y || free_y;
638 let inner_labels_x = !built.facet_axes.margin_labels_x || free_x;
640 let inner_labels_y = !built.facet_axes.margin_labels_y || free_y;
641 let unlabelled = |x: bool| {
642 let mut t = theme.clone();
643 if x {
644 t.axis_text_x.visible = false;
645 } else {
646 t.axis_text_y.visible = false;
647 }
648 t
649 };
650 let theme_no_x_labels = unlabelled(true);
651 let theme_no_y_labels = unlabelled(false);
652 let gap_x = theme.get_panel_spacing_x()
653 + if all_y && built.panels.len() > 1 {
654 theme.axis_ticks_length
655 + if inner_labels_y && theme.axis_text_y.visible {
656 built
657 .panel_scales
658 .iter()
659 .chain(std::iter::once(&built.scales))
660 .filter_map(|s| s.get(&v_aes))
661 .map(|ys| axis::y_label_reserve(theme, &ys.breaks()))
662 .fold(0.0_f64, f64::max)
663 } else {
664 0.0
665 }
666 } else {
667 0.0
668 };
669 let gap_y = theme.get_panel_spacing_y()
670 + if all_x && built.panels.len() > 1 {
671 if inner_labels_x {
672 super::layout::PlotLayout::x_axis_band(theme, None)
673 } else {
674 theme.axis_ticks_length
675 }
676 } else {
677 0.0
678 };
679 let strip_height = thickness(
680 strip_lines(&col_strip),
681 if wrap_vertical { size_y } else { size_x },
682 );
683
684 let is_figure = |li: usize| {
687 built.layers[li]
688 .geom
689 .figure_band_height(plot_area.width, theme)
690 .is_some()
691 };
692 let band_h: f64 = built
693 .layers
694 .iter()
695 .filter_map(|l| l.geom.figure_band_height(plot_area.width, theme))
696 .sum::<f64>()
697 .min(plot_area.height * 0.5);
698 let band_rect = crate::render::Rect {
699 x: plot_area.x,
700 y: plot_area.y + plot_area.height - band_h,
701 width: plot_area.width,
702 height: band_h,
703 };
704 let plot_area = crate::render::Rect {
705 height: plot_area.height - band_h,
706 ..plot_area
707 };
708
709 let fixed_grid = match (&built.facet, built.coord.fixed_aspect()) {
714 (Facet::Wrap { ncol: want, .. }, Some(aspect)) => Some(crate::facet::wrap::fit_grid(
715 built.panels.len(),
716 *want,
717 aspect,
718 plot_area.width
719 - if wrap_vertical {
720 strip_height * want.unwrap_or(1) as f64
721 } else {
722 0.0
723 },
724 plot_area.height,
725 if wrap_vertical { 0.0 } else { strip_height },
726 gap_x,
727 gap_y,
728 )),
729 _ => None,
730 };
731 let ncol = if let Some(g) = fixed_grid {
732 nrow = g.nrow;
733 g.ncol
734 } else {
735 ncol
736 };
737 let cell: Vec<(usize, usize)> = built
740 .panels
741 .iter()
742 .enumerate()
743 .map(|(i, p)| {
744 if fixed_grid.is_some() {
745 match &built.facet {
746 Facet::Wrap {
747 vertical, as_table, ..
748 } => crate::facet::wrap::wrap_cell(
749 i,
750 built.panels.len(),
751 ncol,
752 *vertical,
753 *as_table,
754 ),
755 _ => (i / ncol, i % ncol),
756 }
757 } else {
758 (p.row, p.col)
759 }
760 })
761 .collect();
762
763 use crate::facet::StripPosition as Side;
767 let outside = theme.strip_placement_outside;
770 let x_axis_off =
771 super::layout::PlotLayout::x_axis_band(theme, None) + theme.strip_switch_pad;
772 let y_axis_off = theme.axis_ticks_length
773 + built
774 .panel_scales
775 .iter()
776 .chain(std::iter::once(&built.scales))
777 .filter_map(|s| s.get(&v_aes))
778 .map(|ys| axis::y_label_reserve(theme, &ys.breaks()))
779 .fold(0.0_f64, f64::max)
780 + theme.strip_switch_pad;
781 let n_panels = built.panels.len();
782 let draws_x = |pi: usize, r: usize| r == nrow - 1 || pi + ncol >= n_panels || all_x;
783 let draws_y = |c: usize| c == 0 || all_y;
784 let off = |side: Side, pi: usize, r: usize, c: usize| match side {
785 Side::Bottom if outside && draws_x(pi, r) => x_axis_off,
786 Side::Left if outside && draws_y(c) => y_axis_off,
787 _ => 0.0,
788 };
789 let strips: Vec<Vec<(Side, String, f64)>> = built
790 .panels
791 .iter()
792 .enumerate()
793 .map(|(pi, p)| {
794 let (r, c) = cell[pi];
795 let mut v = Vec::new();
796 if is_grid {
797 if let Some(l) = &p.col_label {
798 if switch.x() && r == nrow - 1 {
799 v.push((Side::Bottom, l.clone(), strip_height));
800 } else if !switch.x() && r == 0 {
801 v.push((Side::Top, l.clone(), strip_height));
802 }
803 }
804 if let Some(l) = &p.row_label {
805 if switch.y() && c == 0 {
806 v.push((Side::Left, l.clone(), row_strip_width));
807 } else if !switch.y() && c == ncol - 1 {
808 v.push((Side::Right, l.clone(), row_strip_width));
809 }
810 }
811 } else {
812 v.push((wrap_side, p.label.clone(), strip_height));
813 }
814 v
815 })
816 .collect();
817 let mut band_top = vec![0.0_f64; nrow.max(1)];
818 let mut band_bottom = vec![0.0_f64; nrow.max(1)];
819 let mut band_left = vec![0.0_f64; ncol.max(1)];
820 let mut band_right = vec![0.0_f64; ncol.max(1)];
821 for (pi, list) in strips.iter().enumerate() {
822 let (r, c) = cell[pi];
823 for (side, _, t) in list {
824 let o = off(*side, pi, r, c);
825 let b = match side {
826 Side::Top => &mut band_top[r],
827 Side::Bottom => &mut band_bottom[r],
828 Side::Left => &mut band_left[c],
829 Side::Right => &mut band_right[c],
830 };
831 *b = b.max(*t + o);
832 }
833 }
834 if !is_grid {
837 match wrap_side {
838 Side::Top => band_top.fill(strip_height),
839 Side::Bottom => band_bottom
840 .iter_mut()
841 .for_each(|b| *b = b.max(strip_height)),
842 Side::Left => band_left.iter_mut().for_each(|b| *b = b.max(strip_height)),
843 Side::Right => band_right.fill(strip_height),
844 }
845 }
846
847 let space = match &built.facet {
850 Facet::Grid { space, .. } => space.clone(),
851 Facet::Wrap { space, .. } if fixed_grid.is_none() => {
853 use crate::facet::FacetSpace as S;
854 match (space.free_x() && nrow == 1, space.free_y() && ncol == 1) {
855 (true, true) => S::Free,
856 (true, false) => S::FreeX,
857 (false, true) => S::FreeY,
858 _ => S::Fixed,
859 }
860 }
861 _ => crate::facet::FacetSpace::Fixed,
862 };
863 let extent = |pi: usize, col: &str| -> Option<f64> {
864 let (mut lo, mut hi) = (f64::INFINITY, f64::NEG_INFINITY);
865 for df in &built.panels_data[pi] {
866 if let Some(c) = df.column(col) {
867 for v in c {
868 if let Some(f) = v.as_f64() {
869 lo = lo.min(f);
870 hi = hi.max(f);
871 }
872 }
873 }
874 }
875 (lo <= hi).then_some(hi - lo)
876 };
877 let avail_w = plot_area.width
878 - gap_x * (ncol as f64 - 1.0)
879 - band_left.iter().sum::<f64>()
880 - band_right.iter().sum::<f64>();
881 let col_widths: Vec<f64> = if let Some(g) = fixed_grid {
882 vec![g.panel_w; ncol]
883 } else if space.free_x() && ncol > 0 {
884 let mut ranges = vec![0.0f64; ncol];
885 for (pi, panel) in built.panels.iter().enumerate() {
886 if let Some(r) = extent(pi, "x") {
887 ranges[panel.col] = ranges[panel.col].max(r);
888 }
889 }
890 let total: f64 = ranges.iter().sum();
891 if total > 0.0 {
892 ranges.iter().map(|r| avail_w * (r / total)).collect()
893 } else {
894 vec![avail_w / ncol as f64; ncol]
895 }
896 } else {
897 vec![avail_w / ncol.max(1) as f64; ncol.max(1)]
898 };
899 let mut col_x = vec![plot_area.x; ncol.max(1)];
900 {
901 let used_w: f64 = col_widths.iter().sum::<f64>()
902 + gap_x * (ncol as f64 - 1.0)
903 + band_left.iter().sum::<f64>()
904 + band_right.iter().sum::<f64>();
905 let mut acc = if fixed_grid.is_some() {
906 plot_area.x + ((plot_area.width - used_w) / 2.0).max(0.0)
907 } else {
908 plot_area.x
909 };
910 for c in 0..ncol {
911 col_x[c] = acc + band_left[c];
912 acc += band_left[c] + col_widths[c] + band_right[c] + gap_x;
913 }
914 }
915
916 let avail_h = plot_area.height
917 - gap_y * (nrow as f64 - 1.0)
918 - band_top.iter().sum::<f64>()
919 - band_bottom.iter().sum::<f64>();
920 let row_heights: Vec<f64> = if let Some(g) = fixed_grid {
921 vec![g.panel_h; nrow]
922 } else if space.free_y() && nrow > 0 {
923 let mut ranges = vec![0.0f64; nrow];
924 for (pi, panel) in built.panels.iter().enumerate() {
925 if let Some(r) = extent(pi, "y") {
926 ranges[panel.row] = ranges[panel.row].max(r);
927 }
928 }
929 let total: f64 = ranges.iter().sum();
930 if total > 0.0 {
931 ranges.iter().map(|r| avail_h * (r / total)).collect()
932 } else {
933 vec![avail_h / nrow as f64; nrow]
934 }
935 } else {
936 vec![avail_h / nrow.max(1) as f64; nrow.max(1)]
937 };
938 let mut row_y = vec![plot_area.y; nrow.max(1)];
939 {
940 let used_h: f64 = row_heights.iter().sum::<f64>()
941 + gap_y * (nrow as f64 - 1.0)
942 + band_top.iter().sum::<f64>()
943 + band_bottom.iter().sum::<f64>();
944 let mut acc = if fixed_grid.is_some() {
945 plot_area.y + ((plot_area.height - used_h) / 2.0).max(0.0)
946 } else {
947 plot_area.y
948 };
949 for r in 0..nrow {
950 row_y[r] = acc + band_top[r];
952 acc += band_top[r] + row_heights[r] + band_bottom[r] + gap_y;
953 }
954 }
955
956 let mut x_title_area: Option<super::Rect> = None;
960 let mut y_title_area: Option<(super::Rect, f64)> = None;
961 let union = |a: &Option<super::Rect>, b: &super::Rect| match a {
962 None => b.clone(),
963 Some(a) => {
964 let (x0, y0) = (a.x.min(b.x), a.y.min(b.y));
965 let x1 = (a.x + a.width).max(b.x + b.width);
966 let y1 = (a.y + a.height).max(b.y + b.height);
967 super::Rect {
968 x: x0,
969 y: y0,
970 width: x1 - x0,
971 height: y1 - y0,
972 }
973 }
974 };
975
976 for (pi, panel) in built.panels.iter().enumerate() {
977 let (prow, pcol) = cell[pi];
978 let panel_width = col_widths[pcol];
979 let panel_height = row_heights[prow];
980 let px = col_x[pcol];
981 let py = row_y[prow];
982
983 let panel_rect = crate::render::Rect {
984 x: px,
985 y: py,
986 width: panel_width,
987 height: panel_height,
988 };
989 backend.clear_corner_claims();
997
998 let mut bottom_strip = 0.0_f64;
1003 let mut left_strip = 0.0_f64;
1004 let mut bottom_title = 0.0_f64;
1005 let mut left_title = 0.0_f64;
1006 for (side, label, t) in &strips[pi] {
1007 let o = off(*side, pi, prow, pcol);
1008 let (rect, angle) = match side {
1009 Side::Top => ((px, py - t, panel_width, *t), 0.0),
1010 Side::Bottom => {
1011 if o > 0.0 {
1012 bottom_title = *t + o;
1013 } else {
1014 bottom_strip = *t;
1015 }
1016 ((px, py + panel_height + o, panel_width, *t), 0.0)
1017 }
1018 Side::Left => {
1019 if o > 0.0 {
1020 left_title = *t + o;
1021 } else {
1022 left_strip = *t;
1023 }
1024 ((px - t - o, py, *t, panel_height), 270.0)
1025 }
1026 Side::Right => ((px + panel_width, py, *t, panel_height), 90.0),
1027 };
1028 draw_strip(backend, theme, rect, label, angle)?;
1029 }
1030
1031 if theme.panel_background.visible {
1033 if let Some(fill) = theme.panel_background.fill {
1034 backend.set_element_class(Some("gg-panel-background"));
1035 backend.draw_rect(
1036 (panel_rect.x, panel_rect.y),
1037 (
1038 panel_rect.x + panel_rect.width,
1039 panel_rect.y + panel_rect.height,
1040 ),
1041 &RectStyle {
1042 fill: Some(fill),
1043 stroke: theme.panel_background.color,
1044 stroke_width: theme.panel_background.width,
1045 alpha: 1.0,
1046 clip: false,
1047 corner_radius: 0.0,
1048 },
1049 )?;
1050 }
1051 }
1052
1053 if theme.panel_border.visible {
1055 let style = LineStyle {
1056 color: theme.panel_border.color,
1057 width: theme.panel_border.width,
1058 alpha: 1.0,
1059 linetype: Linetype::Solid,
1060 ..Default::default()
1061 };
1062 let x0 = panel_rect.x;
1063 let y0 = panel_rect.y;
1064 let x1 = panel_rect.x + panel_rect.width;
1065 let y1 = panel_rect.y + panel_rect.height;
1066 backend.set_element_class(Some("gg-panel-border"));
1067 backend.draw_line(&[(x0, y0), (x1, y0)], &style)?;
1068 backend.draw_line(&[(x1, y0), (x1, y1)], &style)?;
1069 backend.draw_line(&[(x1, y1), (x0, y1)], &style)?;
1070 backend.draw_line(&[(x0, y1), (x0, y0)], &style)?;
1071 }
1072
1073 let panel_scale_set = if pi < built.panel_scales.len() {
1075 &built.panel_scales[pi]
1076 } else {
1077 &built.scales
1078 };
1079
1080 let x_scale = panel_scale_set.get(&h_aes);
1082 let y_scale = panel_scale_set.get(&v_aes);
1083
1084 if let (true, Some(xs), Some(ys)) = (
1085 built.coord.is_polar() && !built.coord.is_radar(),
1086 x_scale,
1087 y_scale,
1088 ) {
1089 crate::guide::polar::draw_polar_guides(
1091 xs,
1092 ys,
1093 built.coord.as_ref(),
1094 theme,
1095 &panel_rect,
1096 backend,
1097 )?;
1098 } else if let (Some(xs), Some(ys)) = (x_scale, y_scale) {
1099 if built.coord.gridlines() {
1100 axis::draw_gridlines(
1101 xs,
1102 ys,
1103 built.coord.as_ref(),
1104 theme,
1105 &panel_rect,
1106 backend,
1107 )?;
1108 }
1109
1110 let bottom_edge = prow == nrow - 1 || pi + ncol >= built.panels.len();
1114 if bottom_edge || all_x {
1115 let axis_rect = crate::render::Rect {
1116 height: panel_rect.height + bottom_strip,
1117 ..panel_rect.clone()
1118 };
1119 let t = if bottom_edge || inner_labels_x {
1120 theme
1121 } else {
1122 &theme_no_x_labels
1123 };
1124 axis::draw_x_axis_with(
1125 xs,
1126 built.coord.as_ref(),
1127 t,
1128 &axis_rect,
1129 &total_area,
1130 false,
1131 backend,
1132 )?;
1133 if bottom_edge {
1134 let title_rect = crate::render::Rect {
1135 height: axis_rect.height + bottom_title,
1136 ..axis_rect.clone()
1137 };
1138 x_title_area = Some(union(&x_title_area, &title_rect));
1139 }
1140 }
1141
1142 if pcol == 0 || all_y {
1144 let axis_rect = crate::render::Rect {
1145 x: panel_rect.x - left_strip,
1146 width: panel_rect.width + left_strip,
1147 ..panel_rect.clone()
1148 };
1149 let t = if pcol == 0 || inner_labels_y {
1150 theme
1151 } else {
1152 &theme_no_y_labels
1153 };
1154 axis::draw_y_axis_with(
1155 ys,
1156 built.coord.as_ref(),
1157 t,
1158 &axis_rect,
1159 false,
1160 backend,
1161 )?;
1162 if pcol == 0 {
1163 let reserve = axis::y_label_reserve(theme, &ys.breaks());
1164 let prev = y_title_area.as_ref().map(|(r, _)| r.clone());
1165 let widest = y_title_area.as_ref().map_or(0.0, |(_, w)| *w);
1166 let title_rect = crate::render::Rect {
1167 x: axis_rect.x - left_title,
1168 width: axis_rect.width + left_title,
1169 ..axis_rect.clone()
1170 };
1171 y_title_area = Some((union(&prev, &title_rect), widest.max(reserve)));
1172 }
1173 }
1174 }
1175
1176 if fixed_grid.is_some() {
1180 let data = built
1181 .coord
1182 .data_rect(&panel_rect)
1183 .unwrap_or_else(|| panel_rect.clone());
1184 backend.begin_panel(pi, &panel.label, &panel_rect, &data);
1185 }
1186 backend.set_element_class(None);
1190 let clip_group = built.clip
1191 && fixed_grid.is_none()
1192 && built.panels_data.get(pi).is_some_and(|layers| {
1193 layers.iter().any(|d| {
1194 layer_spills(d, panel_scale_set, built.coord.as_ref(), &panel_rect)
1195 })
1196 });
1197 if clip_group {
1198 backend.begin_clip(&panel_rect);
1199 }
1200 if !built.clip {
1201 backend.set_clipping(false);
1202 }
1203 if pi < built.panels_data.len() {
1204 for (li, layer_data) in built.panels_data[pi].iter().enumerate() {
1205 if li < built.layers.len() && layer_data.nrows() > 0 && !is_figure(li) {
1206 let mut panel_backend = PanelBackendAdapter {
1207 inner: backend,
1208 panel_rect: panel_rect.clone(),
1209 };
1210 let geom = &built.layers[li].geom;
1211 panel_backend.set_mark_geom(Some(geom.name()));
1212 let drawn = geom.draw(
1213 layer_data,
1214 built.coord.as_ref(),
1215 panel_scale_set,
1216 theme,
1217 &mut panel_backend,
1218 );
1219 panel_backend.set_mark_geom(None);
1220 drawn?;
1221 }
1222 }
1223 }
1224 if !built.clip {
1225 backend.set_clipping(true);
1226 }
1227 if clip_group {
1228 backend.end_clip();
1229 }
1230 if fixed_grid.is_some() {
1231 backend.end_panel();
1232 }
1233 }
1234
1235 let title_scales = built.panel_scales.first().unwrap_or(&built.scales);
1238 if let (Some(area), Some(xs)) = (&x_title_area, title_scales.get(&h_aes)) {
1239 axis::draw_x_axis_title(xs, theme, area, backend)?;
1240 }
1241 if let (Some((area, reserve)), Some(ys)) = (&y_title_area, title_scales.get(&v_aes)) {
1242 axis::draw_y_axis_title(ys, theme, area, *reserve, backend)?;
1243 }
1244
1245 if band_h > 0.0 {
1247 backend.clear_corner_claims();
1248 for li in (0..built.layers.len()).filter(|&li| is_figure(li)) {
1249 let empty = crate::data::DataFrame::new();
1250 let data = built
1251 .panels_data
1252 .iter()
1253 .filter_map(|p| p.get(li))
1254 .find(|d| d.nrows() > 0)
1255 .unwrap_or(&empty);
1256 let mut band_backend = PanelBackendAdapter {
1257 inner: backend,
1258 panel_rect: band_rect.clone(),
1259 };
1260 let geom = &built.layers[li].geom;
1261 band_backend.set_mark_geom(Some(geom.name()));
1262 let drawn = geom.draw(
1263 data,
1264 built.coord.as_ref(),
1265 &built.scales,
1266 theme,
1267 &mut band_backend,
1268 );
1269 band_backend.set_mark_geom(None);
1270 drawn?;
1271 }
1272 }
1273
1274 let title_ref = Self::title_ref_rect(theme, &plot_area, &total_area);
1276 let caption_ref = Self::ref_rect(
1277 theme.caption_position.unwrap_or(theme.title_position),
1278 theme,
1279 &plot_area,
1280 &total_area,
1281 );
1282 let legend_lift = legend::top_legend_lift(
1285 theme,
1286 &built.scales,
1287 &built.suppressed_aes,
1288 &built.legend_plans,
1289 &built.guide_legend,
1290 );
1291 if let Some(ref title) = built.labels.title {
1292 let (tx, anchor) = Self::hjust_pos(&theme.title, &title_ref);
1293 let (title_y, _) = Self::title_subtitle_y(
1294 theme,
1295 plot_area.y,
1296 legend_lift,
1297 built.labels.subtitle.is_some(),
1298 );
1299 let family = if theme.title.family.is_empty() {
1300 None
1301 } else {
1302 Some(theme.title.family.clone())
1303 };
1304 backend.set_element_class(Some("gg-title"));
1305 crate::render::rich::draw(
1306 backend,
1307 theme.rich_text,
1308 title,
1309 (tx, title_y),
1310 &TextStyle {
1311 color: theme.title.color,
1312 size: theme.title.size,
1313 anchor,
1314 angle: 0.0,
1315 family,
1316 face: theme.title.face,
1317 bg_colour: None,
1318 bg_r: 0.0,
1319 },
1320 )?;
1321 }
1322
1323 if let Some(ref subtitle) = built.labels.subtitle {
1325 let (sx, anchor) = Self::hjust_pos(&theme.subtitle, &title_ref);
1326 let (_, subtitle_y) = Self::title_subtitle_y(theme, plot_area.y, legend_lift, true);
1327 let family = if theme.subtitle.family.is_empty() {
1328 None
1329 } else {
1330 Some(theme.subtitle.family.clone())
1331 };
1332 backend.set_element_class(Some("gg-subtitle"));
1333 crate::render::rich::draw(
1334 backend,
1335 theme.rich_text,
1336 subtitle,
1337 (sx, subtitle_y),
1338 &TextStyle {
1339 color: theme.subtitle.color,
1340 size: theme.subtitle.size,
1341 anchor,
1342 angle: 0.0,
1343 family,
1344 face: theme.subtitle.face,
1345 bg_colour: None,
1346 bg_r: 0.0,
1347 },
1348 )?;
1349 }
1350
1351 if let Some(ref caption) = built.labels.caption {
1353 let (cx, anchor) = Self::hjust_pos(&theme.caption, &caption_ref);
1354 let caption_y = total_area.y + total_area.height
1357 - theme.plot_margin.bottom.max(0.0)
1358 - theme.caption.size * 0.5;
1359 let family = if theme.caption.family.is_empty() {
1360 None
1361 } else {
1362 Some(theme.caption.family.clone())
1363 };
1364 backend.set_element_class(Some("gg-caption"));
1365 crate::render::rich::draw(
1366 backend,
1367 theme.rich_text,
1368 caption,
1369 (cx, caption_y),
1370 &TextStyle {
1371 color: theme.caption.color,
1372 size: theme.caption.size,
1373 anchor,
1374 angle: 0.0,
1375 family,
1376 face: theme.caption.face,
1377 bg_colour: None,
1378 bg_r: 0.0,
1379 },
1380 )?;
1381 }
1382
1383 backend.set_element_class(None);
1384 Self::draw_annotations(
1386 &built.annotations,
1387 &built.scales,
1388 built.coord.as_ref(),
1389 &plot_area,
1390 theme,
1391 backend,
1392 )?;
1393
1394 legend::draw_legend_planned(
1396 &built.scales,
1397 theme,
1398 &plot_area,
1399 plot_area.width,
1400 backend,
1401 &built.guide_legend,
1402 &built.suppressed_aes,
1403 &built.legend_plans,
1404 )?;
1405
1406 let tag_area = if theme.tag_location_panel {
1410 &plot_area
1411 } else {
1412 &total_area
1413 };
1414 Self::draw_tag(&built.labels, theme, tag_area, backend)?;
1415
1416 Ok(())
1417 }
1418
1419 fn draw_annotations(
1434 annotations: &[Annotation],
1435 scales: &crate::scale::ScaleSet,
1436 coord: &dyn crate::coord::Coord,
1437 plot_area: &crate::render::Rect,
1438 theme: &crate::theme::Theme,
1439 backend: &mut dyn DrawBackend,
1440 ) -> Result<(), RenderError> {
1441 use crate::data::Value;
1442
1443 let x_scale = scales.get(&Aesthetic::X);
1444 let y_scale = scales.get(&Aesthetic::Y);
1445
1446 for ann in annotations {
1447 match ann {
1448 Annotation::Text {
1449 label,
1450 x,
1451 y,
1452 size,
1453 color,
1454 } => {
1455 let nx = x_scale.map(|s| s.map(&Value::Float(*x))).unwrap_or(0.0);
1456 let ny = y_scale.map(|s| s.map(&Value::Float(*y))).unwrap_or(0.0);
1457 let pos = coord.transform((nx, ny), plot_area);
1458 backend.draw_text(
1459 label,
1460 pos,
1461 &TextStyle {
1462 color: annotation_ink(*color, theme),
1463 size: *size,
1464 anchor: TextAnchor::Middle,
1465 angle: 0.0,
1466 family: crate::geom::resolve_font_family("", theme),
1467 face: crate::render::backend::FontFace::Plain,
1468 bg_colour: None,
1469 bg_r: 0.0,
1470 },
1471 )?;
1472 }
1473 Annotation::Rect {
1474 xmin,
1475 xmax,
1476 ymin,
1477 ymax,
1478 fill,
1479 alpha,
1480 } => {
1481 let nx0 = x_scale.map(|s| s.map(&Value::Float(*xmin))).unwrap_or(0.0);
1482 let nx1 = x_scale.map(|s| s.map(&Value::Float(*xmax))).unwrap_or(1.0);
1483 let ny0 = y_scale.map(|s| s.map(&Value::Float(*ymin))).unwrap_or(0.0);
1484 let ny1 = y_scale.map(|s| s.map(&Value::Float(*ymax))).unwrap_or(1.0);
1485 let tl = coord.transform((nx0, ny1), plot_area);
1486 let br = coord.transform((nx1, ny0), plot_area);
1487 backend.draw_rect(
1488 tl,
1489 br,
1490 &RectStyle {
1491 fill: Some(*fill),
1492 stroke: None,
1493 stroke_width: 0.0,
1494 alpha: *alpha,
1495 clip: false,
1496 corner_radius: 0.0,
1497 },
1498 )?;
1499 }
1500 Annotation::Segment {
1501 x,
1502 y,
1503 xend,
1504 yend,
1505 color,
1506 width,
1507 } => {
1508 let nx0 = x_scale.map(|s| s.map(&Value::Float(*x))).unwrap_or(0.0);
1509 let ny0 = y_scale.map(|s| s.map(&Value::Float(*y))).unwrap_or(0.0);
1510 let nx1 = x_scale.map(|s| s.map(&Value::Float(*xend))).unwrap_or(1.0);
1511 let ny1 = y_scale.map(|s| s.map(&Value::Float(*yend))).unwrap_or(1.0);
1512 let p0 = coord.transform((nx0, ny0), plot_area);
1513 let p1 = coord.transform((nx1, ny1), plot_area);
1514 backend.draw_line(
1515 &[p0, p1],
1516 &LineStyle {
1517 color: annotation_ink(*color, theme),
1518 alpha: 1.0,
1519 width: *width,
1520 linetype: Linetype::Solid,
1521 ..Default::default()
1522 },
1523 )?;
1524 }
1525 Annotation::LogTicks {
1526 sides,
1527 outside,
1528 lengths,
1529 color,
1530 width,
1531 } => {
1532 let style = LineStyle {
1533 color: annotation_ink(*color, theme),
1534 alpha: 1.0,
1535 width: *width,
1536 linetype: Linetype::Solid,
1537 ..Default::default()
1538 };
1539 let dir = if *outside { -1.0 } else { 1.0 };
1540 for side in sides.chars() {
1541 let horizontal = matches!(side, 'b' | 't');
1542 let scale = if horizontal { x_scale } else { y_scale };
1543 let Some((lo, hi)) = scale.and_then(|s| s.expanded_domain()) else {
1544 continue;
1545 };
1546 for (t, kind) in log_ticks(lo, hi) {
1547 let n = scale.map(|s| s.map(&Value::Float(t))).unwrap_or(0.0);
1548 let len = dir
1549 * match kind {
1550 0 => lengths.2,
1551 1 => lengths.1,
1552 _ => lengths.0,
1553 };
1554 let (edge, step) = match side {
1556 'b' => ((n, 0.0), (0.0, -len)),
1557 't' => ((n, 1.0), (0.0, len)),
1558 'l' => ((0.0, n), (len, 0.0)),
1559 'r' => ((1.0, n), (-len, 0.0)),
1560 _ => continue,
1561 };
1562 let p0 = coord.transform(edge, plot_area);
1563 let p1 = (p0.0 + step.0, p0.1 + step.1);
1564 backend.draw_line(&[p0, p1], &style)?;
1565 }
1566 }
1567 }
1568 Annotation::Raster {
1569 image,
1570 xmin,
1571 xmax,
1572 ymin,
1573 ymax,
1574 alpha,
1575 } => {
1576 let rect = data_rect(
1577 x_scale,
1578 y_scale,
1579 coord,
1580 plot_area,
1581 [*xmin, *xmax, *ymin, *ymax],
1582 );
1583 backend.draw_image(image, &rect, plot_area, *alpha)?;
1584 }
1585 Annotation::Inset {
1586 plot,
1587 xmin,
1588 xmax,
1589 ymin,
1590 ymax,
1591 } => {
1592 let rect = data_rect(
1593 x_scale,
1594 y_scale,
1595 coord,
1596 plot_area,
1597 [*xmin, *xmax, *ymin, *ymax],
1598 );
1599 let (w, h) = (rect.width.round().max(1.0), rect.height.round().max(1.0));
1600 let mut guard = plot.0.lock().unwrap_or_else(|e| e.into_inner());
1601 match &mut *guard {
1602 Ok((built, meta)) => {
1603 let layout =
1604 crate::GGPlot::layout_built(built, meta, w as u32, h as u32);
1605 let mut off = crate::render::offset::OffsetBackend::new(
1606 backend,
1607 rect.x,
1608 rect.y,
1609 layout.plot_area.clone(),
1610 layout.total.clone(),
1611 );
1612 Self::render(built, &mut off)?;
1613 }
1614 Err(e) => {
1615 backend.warn(format!("annotation_custom: inset plot failed: {e}"))
1616 }
1617 }
1618 }
1619 }
1620 }
1621 Ok(())
1622 }
1623}
1624
1625fn draw_strip(
1631 backend: &mut dyn DrawBackend,
1632 theme: &crate::theme::Theme,
1633 (x, y, w, h): (f64, f64, f64, f64),
1634 label: &str,
1635 angle: f64,
1636) -> Result<(), RenderError> {
1637 let horizontal = angle == 0.0;
1639 let (bg, text) = if horizontal {
1640 (&theme.strip_background_x, &theme.strip_text_x)
1641 } else {
1642 (&theme.strip_background_y, &theme.strip_text_y)
1643 };
1644 let bg = bg.as_ref().unwrap_or(&theme.strip_background);
1645 let text = text.as_ref().unwrap_or(&theme.strip_text);
1646 if bg.visible {
1647 backend.set_element_class(Some("gg-strip gg-strip-background"));
1648 backend.draw_rect(
1649 (x, y),
1650 (x + w, y + h),
1651 &RectStyle {
1652 fill: bg.fill,
1653 stroke: bg.color,
1654 stroke_width: bg.width,
1655 alpha: 1.0,
1656 clip: false,
1657 corner_radius: 0.0,
1658 },
1659 )?;
1660 }
1661 if !text.visible {
1662 return Ok(());
1663 }
1664 let family = if text.family.is_empty() {
1665 None
1666 } else {
1667 Some(text.family.clone())
1668 };
1669 let lines: Vec<&str> = label.lines().collect();
1670 let n = lines.len().max(1) as f64;
1671 for (k, line) in lines.iter().enumerate() {
1672 let d = (k as f64 - (n - 1.0) / 2.0) * text.size * 1.2;
1673 let (dx, dy) = if angle == 90.0 {
1676 (-d, 0.0)
1677 } else if angle == 270.0 {
1678 (d, 0.0)
1679 } else {
1680 (0.0, d)
1681 };
1682 backend.set_element_class(Some("gg-strip gg-strip-text"));
1683 crate::render::rich::draw(
1684 backend,
1685 theme.rich_text,
1686 line,
1687 (x + w / 2.0 + dx, y + h / 2.0 + dy),
1688 &TextStyle {
1689 color: text.color,
1690 size: text.size,
1691 anchor: TextAnchor::Middle,
1692 angle,
1693 family: family.clone(),
1694 face: text.face,
1695 bg_colour: None,
1696 bg_r: 0.0,
1697 },
1698 )?;
1699 }
1700 Ok(())
1701}
1702
1703fn log_ticks(lo: f64, hi: f64) -> Vec<(f64, u8)> {
1704 let mut out = Vec::new();
1705 if !(lo.is_finite() && hi.is_finite()) || hi - lo > 300.0 {
1706 return out;
1707 }
1708 for k in (lo.floor() as i64)..=(hi.ceil() as i64) {
1709 for m in 1..=9 {
1710 let t = k as f64 + (m as f64).log10();
1711 if t >= lo - 1e-9 && t <= hi + 1e-9 {
1712 out.push((
1713 t,
1714 if m == 1 {
1715 0
1716 } else if m == 5 {
1717 1
1718 } else {
1719 2
1720 },
1721 ));
1722 }
1723 }
1724 }
1725 out
1726}
1727
1728fn data_rect(
1731 x_scale: Option<&dyn crate::scale::Scale>,
1732 y_scale: Option<&dyn crate::scale::Scale>,
1733 coord: &dyn crate::coord::Coord,
1734 plot_area: &crate::render::Rect,
1735 [xmin, xmax, ymin, ymax]: [f64; 4],
1736) -> crate::render::Rect {
1737 use crate::data::Value;
1738 let m = |s: Option<&dyn crate::scale::Scale>, v: f64, edge: f64| {
1739 if v.is_finite() {
1740 s.map(|s| s.map(&Value::Float(v))).unwrap_or(edge)
1741 } else if v > 0.0 {
1742 1.0
1743 } else {
1744 0.0
1745 }
1746 };
1747 let a = coord.transform((m(x_scale, xmin, 0.0), m(y_scale, ymax, 1.0)), plot_area);
1748 let b = coord.transform((m(x_scale, xmax, 1.0), m(y_scale, ymin, 0.0)), plot_area);
1749 crate::render::Rect {
1750 x: a.0.min(b.0),
1751 y: a.1.min(b.1),
1752 width: (a.0 - b.0).abs(),
1753 height: (a.1 - b.1).abs(),
1754 }
1755}
1756
1757fn annotation_ink(color: (u8, u8, u8), theme: &crate::theme::Theme) -> (u8, u8, u8) {
1763 match (color == (50, 50, 50), theme.geom.ink) {
1764 (true, Some(ink)) => ink,
1765 _ => color,
1766 }
1767}
1768
1769fn layer_spills(
1774 data: &crate::data::DataFrame,
1775 scales: &crate::scale::ScaleSet,
1776 coord: &dyn crate::coord::Coord,
1777 area: &crate::render::Rect,
1778) -> bool {
1779 if coord.is_polar() || data.nrows() == 0 {
1780 return false;
1781 }
1782 const SLACK: f64 = 0.5;
1783 let outside = |(px, py): (f64, f64)| {
1784 px < area.x - SLACK
1785 || px > area.x + area.width + SLACK
1786 || py < area.y - SLACK
1787 || py > area.y + area.height + SLACK
1788 };
1789 let axes = [
1790 (Aesthetic::X, ["x", "xmin", "xmax", "xend"], true),
1791 (Aesthetic::Y, ["y", "ymin", "ymax", "yend"], false),
1792 ];
1793 for (aes, cols, is_x) in axes {
1794 let Some(scale) = scales.get(&aes) else {
1795 continue;
1796 };
1797 for col in cols {
1798 let Some(values) = data.column(col) else {
1799 continue;
1800 };
1801 for v in values {
1802 if !v.as_f64().is_some_and(f64::is_finite) {
1803 continue;
1804 }
1805 let n = scale.map(v);
1806 if !n.is_finite() {
1807 continue;
1808 }
1809 let p = if is_x { (n, 0.5) } else { (0.5, n) };
1810 if outside(coord.transform(p, area)) {
1811 return true;
1812 }
1813 }
1814 }
1815 }
1816 false
1817}
1818
1819struct PanelBackendAdapter<'a> {
1821 inner: &'a mut dyn DrawBackend,
1822 panel_rect: crate::render::Rect,
1823}
1824
1825impl<'a> DrawBackend for PanelBackendAdapter<'a> {
1826 fn set_clipping(&mut self, on: bool) {
1827 self.inner.set_clipping(on)
1828 }
1829 fn begin_clip(&mut self, rect: &crate::render::Rect) {
1830 self.inner.begin_clip(rect)
1831 }
1832 fn enable_element_classes(&mut self, on: bool) {
1833 self.inner.enable_element_classes(on)
1834 }
1835 fn draw_rich_text(
1836 &mut self,
1837 runs: &[crate::render::rich::RichRun],
1838 pos: (f64, f64),
1839 style: &crate::render::backend::TextStyle,
1840 ) -> Result<(), RenderError> {
1841 self.inner.draw_rich_text(runs, pos, style)
1842 }
1843 fn set_element_class(&mut self, class: Option<&'static str>) {
1844 self.inner.set_element_class(class)
1845 }
1846 fn end_clip(&mut self) {
1847 self.inner.end_clip()
1848 }
1849 fn draw_image(
1850 &mut self,
1851 bytes: &[u8],
1852 rect: &crate::render::Rect,
1853 clip: &crate::render::Rect,
1854 alpha: f64,
1855 ) -> Result<(), RenderError> {
1856 self.inner.draw_image(bytes, rect, clip, alpha)
1857 }
1858 fn draw_circle(
1859 &mut self,
1860 center: (f64, f64),
1861 radius: f64,
1862 style: &crate::render::backend::PointStyle,
1863 ) -> Result<(), RenderError> {
1864 self.inner.draw_circle(center, radius, style)
1865 }
1866 fn draw_line(
1867 &mut self,
1868 points: &[(f64, f64)],
1869 style: &crate::render::backend::LineStyle,
1870 ) -> Result<(), RenderError> {
1871 self.inner.draw_line(points, style)
1872 }
1873 fn draw_quadratic(
1874 &mut self,
1875 p0: (f64, f64),
1876 ctrl: (f64, f64),
1877 p1: (f64, f64),
1878 style: &crate::render::backend::LineStyle,
1879 ) -> Result<(), RenderError> {
1880 self.inner.draw_quadratic(p0, ctrl, p1, style)
1881 }
1882 fn draw_rect(
1883 &mut self,
1884 top_left: (f64, f64),
1885 bottom_right: (f64, f64),
1886 style: &RectStyle,
1887 ) -> Result<(), RenderError> {
1888 self.inner.draw_rect(top_left, bottom_right, style)
1889 }
1890 fn draw_text(
1891 &mut self,
1892 text: &str,
1893 pos: (f64, f64),
1894 style: &TextStyle,
1895 ) -> Result<(), RenderError> {
1896 self.inner.draw_text(text, pos, style)
1897 }
1898 fn draw_polygon(
1899 &mut self,
1900 points: &[(f64, f64)],
1901 style: &RectStyle,
1902 ) -> Result<(), RenderError> {
1903 self.inner.draw_polygon(points, style)
1904 }
1905 fn draw_polygon_rings(
1906 &mut self,
1907 rings: &[Vec<(f64, f64)>],
1908 style: &RectStyle,
1909 ) -> Result<(), RenderError> {
1910 self.inner.draw_polygon_rings(rings, style)
1911 }
1912 fn draw_shape(
1913 &mut self,
1914 center: (f64, f64),
1915 radius: f64,
1916 style: &crate::render::backend::PointStyle,
1917 ) -> Result<(), RenderError> {
1918 self.inner.draw_shape(center, radius, style)
1919 }
1920 fn plot_area(&self) -> crate::render::Rect {
1921 self.panel_rect.clone()
1922 }
1923 fn set_tooltip(&mut self, tooltip: Option<String>) {
1924 self.inner.set_tooltip(tooltip)
1925 }
1926 fn set_mark_axis(&mut self, key: Option<String>) {
1927 self.inner.set_mark_axis(key)
1928 }
1929 fn set_mark_series(&mut self, series: Option<String>) {
1930 self.inner.set_mark_series(series)
1931 }
1932 fn set_mark_value(&mut self, value: Option<String>) {
1933 self.inner.set_mark_value(value)
1934 }
1935 fn set_mark_geom(&mut self, geom: Option<&str>) {
1936 self.inner.set_mark_geom(geom)
1937 }
1938 fn warn(&mut self, message: String) {
1939 self.inner.warn(message)
1940 }
1941 fn set_mark_part(&mut self, part: Option<&str>) {
1942 self.inner.set_mark_part(part)
1943 }
1944 fn set_mark_attrs(&mut self, attrs: Vec<(String, String)>) {
1945 self.inner.set_mark_attrs(attrs)
1946 }
1947
1948 fn set_mark_class(&mut self, class: Option<String>) {
1949 self.inner.set_mark_class(class)
1950 }
1951
1952 fn set_mark_group(&mut self, group: Option<(String, String)>) {
1953 self.inner.set_mark_group(group)
1954 }
1955
1956 fn set_layer_attrs(&mut self, attrs: Vec<(String, String)>) {
1957 self.inner.set_layer_attrs(attrs)
1958 }
1959 fn wants_formatted_values(&self) -> bool {
1960 self.inner.wants_formatted_values()
1961 }
1962 fn set_mark_fvalue(&mut self, value: Option<String>) {
1963 self.inner.set_mark_fvalue(value)
1964 }
1965 fn set_mark_fx(&mut self, value: Option<String>) {
1966 self.inner.set_mark_fx(value)
1967 }
1968 fn total_area(&self) -> crate::render::Rect {
1969 self.inner.total_area()
1970 }
1971 fn corner_claims(&self) -> Vec<crate::render::Rect> {
1972 self.inner.corner_claims()
1973 }
1974 fn claim_corner_box(&mut self, rect: crate::render::Rect) {
1975 self.inner.claim_corner_box(rect)
1976 }
1977 fn clear_corner_claims(&mut self) {
1978 self.inner.clear_corner_claims()
1979 }
1980}
1981
1982#[cfg(test)]
1983mod tests {
1984 use super::*;
1985 use crate::render::backend::LineStyle;
1986 use crate::render::svg_backend::SvgBackend;
1987
1988 #[test]
1994 fn panel_backend_adapter_forwards_quadratic_and_mark_metadata() {
1995 let total = crate::render::Rect {
1996 x: 0.0,
1997 y: 0.0,
1998 width: 200.0,
1999 height: 200.0,
2000 };
2001 let panel_rect = crate::render::Rect {
2002 x: 10.0,
2003 y: 10.0,
2004 width: 80.0,
2005 height: 80.0,
2006 };
2007 let mut backend = SvgBackend::new(200, 200, total);
2008 let mut adapter = PanelBackendAdapter {
2009 inner: &mut backend,
2010 panel_rect: panel_rect.clone(),
2011 };
2012
2013 assert_eq!(adapter.plot_area().x, panel_rect.x);
2014 assert_eq!(adapter.plot_area().width, panel_rect.width);
2015
2016 adapter.set_mark_part(Some("arrow"));
2017 adapter.set_mark_attrs(vec![("data-x".to_string(), "v".to_string())]);
2018 adapter
2019 .draw_quadratic((0.0, 0.0), (5.0, 10.0), (10.0, 0.0), &LineStyle::default())
2020 .unwrap();
2021 adapter.set_mark_part(None);
2022 adapter.set_mark_attrs(Vec::new());
2023
2024 let svg = backend.finish();
2025 assert!(svg.contains("<path"));
2026 assert!(svg.contains("data-part=\"arrow\""));
2027 assert!(svg.contains("data-x=\"v\""));
2028 }
2029}