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 all_x = built.facet_axes.all_x || free_x;
623 let all_y = built.facet_axes.all_y || free_y;
624 let inner_labels_x = !built.facet_axes.margin_labels_x || free_x;
626 let inner_labels_y = !built.facet_axes.margin_labels_y || free_y;
627 let unlabelled = |x: bool| {
628 let mut t = theme.clone();
629 if x {
630 t.axis_text_x.visible = false;
631 } else {
632 t.axis_text_y.visible = false;
633 }
634 t
635 };
636 let theme_no_x_labels = unlabelled(true);
637 let theme_no_y_labels = unlabelled(false);
638 let gap_x = theme.get_panel_spacing_x()
639 + if all_y && built.panels.len() > 1 {
640 theme.axis_ticks_length
641 + if inner_labels_y && theme.axis_text_y.visible {
642 built
643 .panel_scales
644 .iter()
645 .chain(std::iter::once(&built.scales))
646 .filter_map(|s| s.get(&Aesthetic::Y))
647 .map(|ys| axis::y_label_reserve(theme, &ys.breaks()))
648 .fold(0.0_f64, f64::max)
649 } else {
650 0.0
651 }
652 } else {
653 0.0
654 };
655 let gap_y = theme.get_panel_spacing_y()
656 + if all_x && built.panels.len() > 1 {
657 if inner_labels_x {
658 super::layout::PlotLayout::x_axis_band(theme, None)
659 } else {
660 theme.axis_ticks_length
661 }
662 } else {
663 0.0
664 };
665 let strip_height = thickness(
666 strip_lines(&col_strip),
667 if wrap_vertical { size_y } else { size_x },
668 );
669
670 let is_figure = |li: usize| {
673 built.layers[li]
674 .geom
675 .figure_band_height(plot_area.width, theme)
676 .is_some()
677 };
678 let band_h: f64 = built
679 .layers
680 .iter()
681 .filter_map(|l| l.geom.figure_band_height(plot_area.width, theme))
682 .sum::<f64>()
683 .min(plot_area.height * 0.5);
684 let band_rect = crate::render::Rect {
685 x: plot_area.x,
686 y: plot_area.y + plot_area.height - band_h,
687 width: plot_area.width,
688 height: band_h,
689 };
690 let plot_area = crate::render::Rect {
691 height: plot_area.height - band_h,
692 ..plot_area
693 };
694
695 let fixed_grid = match (&built.facet, built.coord.fixed_aspect()) {
700 (Facet::Wrap { ncol: want, .. }, Some(aspect)) => Some(crate::facet::wrap::fit_grid(
701 built.panels.len(),
702 *want,
703 aspect,
704 plot_area.width
705 - if wrap_vertical {
706 strip_height * want.unwrap_or(1) as f64
707 } else {
708 0.0
709 },
710 plot_area.height,
711 if wrap_vertical { 0.0 } else { strip_height },
712 gap_x,
713 gap_y,
714 )),
715 _ => None,
716 };
717 let ncol = if let Some(g) = fixed_grid {
718 nrow = g.nrow;
719 g.ncol
720 } else {
721 ncol
722 };
723 let cell: Vec<(usize, usize)> = built
726 .panels
727 .iter()
728 .enumerate()
729 .map(|(i, p)| {
730 if fixed_grid.is_some() {
731 match &built.facet {
732 Facet::Wrap {
733 vertical, as_table, ..
734 } => crate::facet::wrap::wrap_cell(
735 i,
736 built.panels.len(),
737 ncol,
738 *vertical,
739 *as_table,
740 ),
741 _ => (i / ncol, i % ncol),
742 }
743 } else {
744 (p.row, p.col)
745 }
746 })
747 .collect();
748
749 use crate::facet::StripPosition as Side;
753 let outside = theme.strip_placement_outside;
756 let x_axis_off =
757 super::layout::PlotLayout::x_axis_band(theme, None) + theme.strip_switch_pad;
758 let y_axis_off = theme.axis_ticks_length
759 + built
760 .panel_scales
761 .iter()
762 .chain(std::iter::once(&built.scales))
763 .filter_map(|s| s.get(&Aesthetic::Y))
764 .map(|ys| axis::y_label_reserve(theme, &ys.breaks()))
765 .fold(0.0_f64, f64::max)
766 + theme.strip_switch_pad;
767 let n_panels = built.panels.len();
768 let draws_x = |pi: usize, r: usize| r == nrow - 1 || pi + ncol >= n_panels || all_x;
769 let draws_y = |c: usize| c == 0 || all_y;
770 let off = |side: Side, pi: usize, r: usize, c: usize| match side {
771 Side::Bottom if outside && draws_x(pi, r) => x_axis_off,
772 Side::Left if outside && draws_y(c) => y_axis_off,
773 _ => 0.0,
774 };
775 let strips: Vec<Vec<(Side, String, f64)>> = built
776 .panels
777 .iter()
778 .enumerate()
779 .map(|(pi, p)| {
780 let (r, c) = cell[pi];
781 let mut v = Vec::new();
782 if is_grid {
783 if let Some(l) = &p.col_label {
784 if switch.x() && r == nrow - 1 {
785 v.push((Side::Bottom, l.clone(), strip_height));
786 } else if !switch.x() && r == 0 {
787 v.push((Side::Top, l.clone(), strip_height));
788 }
789 }
790 if let Some(l) = &p.row_label {
791 if switch.y() && c == 0 {
792 v.push((Side::Left, l.clone(), row_strip_width));
793 } else if !switch.y() && c == ncol - 1 {
794 v.push((Side::Right, l.clone(), row_strip_width));
795 }
796 }
797 } else {
798 v.push((wrap_side, p.label.clone(), strip_height));
799 }
800 v
801 })
802 .collect();
803 let mut band_top = vec![0.0_f64; nrow.max(1)];
804 let mut band_bottom = vec![0.0_f64; nrow.max(1)];
805 let mut band_left = vec![0.0_f64; ncol.max(1)];
806 let mut band_right = vec![0.0_f64; ncol.max(1)];
807 for (pi, list) in strips.iter().enumerate() {
808 let (r, c) = cell[pi];
809 for (side, _, t) in list {
810 let o = off(*side, pi, r, c);
811 let b = match side {
812 Side::Top => &mut band_top[r],
813 Side::Bottom => &mut band_bottom[r],
814 Side::Left => &mut band_left[c],
815 Side::Right => &mut band_right[c],
816 };
817 *b = b.max(*t + o);
818 }
819 }
820 if !is_grid {
823 match wrap_side {
824 Side::Top => band_top.fill(strip_height),
825 Side::Bottom => band_bottom
826 .iter_mut()
827 .for_each(|b| *b = b.max(strip_height)),
828 Side::Left => band_left.iter_mut().for_each(|b| *b = b.max(strip_height)),
829 Side::Right => band_right.fill(strip_height),
830 }
831 }
832
833 let space = match &built.facet {
836 Facet::Grid { space, .. } => space.clone(),
837 Facet::Wrap { space, .. } if fixed_grid.is_none() => {
839 use crate::facet::FacetSpace as S;
840 match (space.free_x() && nrow == 1, space.free_y() && ncol == 1) {
841 (true, true) => S::Free,
842 (true, false) => S::FreeX,
843 (false, true) => S::FreeY,
844 _ => S::Fixed,
845 }
846 }
847 _ => crate::facet::FacetSpace::Fixed,
848 };
849 let extent = |pi: usize, col: &str| -> Option<f64> {
850 let (mut lo, mut hi) = (f64::INFINITY, f64::NEG_INFINITY);
851 for df in &built.panels_data[pi] {
852 if let Some(c) = df.column(col) {
853 for v in c {
854 if let Some(f) = v.as_f64() {
855 lo = lo.min(f);
856 hi = hi.max(f);
857 }
858 }
859 }
860 }
861 (lo <= hi).then_some(hi - lo)
862 };
863 let avail_w = plot_area.width
864 - gap_x * (ncol as f64 - 1.0)
865 - band_left.iter().sum::<f64>()
866 - band_right.iter().sum::<f64>();
867 let col_widths: Vec<f64> = if let Some(g) = fixed_grid {
868 vec![g.panel_w; ncol]
869 } else if space.free_x() && ncol > 0 {
870 let mut ranges = vec![0.0f64; ncol];
871 for (pi, panel) in built.panels.iter().enumerate() {
872 if let Some(r) = extent(pi, "x") {
873 ranges[panel.col] = ranges[panel.col].max(r);
874 }
875 }
876 let total: f64 = ranges.iter().sum();
877 if total > 0.0 {
878 ranges.iter().map(|r| avail_w * (r / total)).collect()
879 } else {
880 vec![avail_w / ncol as f64; ncol]
881 }
882 } else {
883 vec![avail_w / ncol.max(1) as f64; ncol.max(1)]
884 };
885 let mut col_x = vec![plot_area.x; ncol.max(1)];
886 {
887 let used_w: f64 = col_widths.iter().sum::<f64>()
888 + gap_x * (ncol as f64 - 1.0)
889 + band_left.iter().sum::<f64>()
890 + band_right.iter().sum::<f64>();
891 let mut acc = if fixed_grid.is_some() {
892 plot_area.x + ((plot_area.width - used_w) / 2.0).max(0.0)
893 } else {
894 plot_area.x
895 };
896 for c in 0..ncol {
897 col_x[c] = acc + band_left[c];
898 acc += band_left[c] + col_widths[c] + band_right[c] + gap_x;
899 }
900 }
901
902 let avail_h = plot_area.height
903 - gap_y * (nrow as f64 - 1.0)
904 - band_top.iter().sum::<f64>()
905 - band_bottom.iter().sum::<f64>();
906 let row_heights: Vec<f64> = if let Some(g) = fixed_grid {
907 vec![g.panel_h; nrow]
908 } else if space.free_y() && nrow > 0 {
909 let mut ranges = vec![0.0f64; nrow];
910 for (pi, panel) in built.panels.iter().enumerate() {
911 if let Some(r) = extent(pi, "y") {
912 ranges[panel.row] = ranges[panel.row].max(r);
913 }
914 }
915 let total: f64 = ranges.iter().sum();
916 if total > 0.0 {
917 ranges.iter().map(|r| avail_h * (r / total)).collect()
918 } else {
919 vec![avail_h / nrow as f64; nrow]
920 }
921 } else {
922 vec![avail_h / nrow.max(1) as f64; nrow.max(1)]
923 };
924 let mut row_y = vec![plot_area.y; nrow.max(1)];
925 {
926 let used_h: f64 = row_heights.iter().sum::<f64>()
927 + gap_y * (nrow as f64 - 1.0)
928 + band_top.iter().sum::<f64>()
929 + band_bottom.iter().sum::<f64>();
930 let mut acc = if fixed_grid.is_some() {
931 plot_area.y + ((plot_area.height - used_h) / 2.0).max(0.0)
932 } else {
933 plot_area.y
934 };
935 for r in 0..nrow {
936 row_y[r] = acc + band_top[r];
938 acc += band_top[r] + row_heights[r] + band_bottom[r] + gap_y;
939 }
940 }
941
942 let mut x_title_area: Option<super::Rect> = None;
946 let mut y_title_area: Option<(super::Rect, f64)> = None;
947 let union = |a: &Option<super::Rect>, b: &super::Rect| match a {
948 None => b.clone(),
949 Some(a) => {
950 let (x0, y0) = (a.x.min(b.x), a.y.min(b.y));
951 let x1 = (a.x + a.width).max(b.x + b.width);
952 let y1 = (a.y + a.height).max(b.y + b.height);
953 super::Rect {
954 x: x0,
955 y: y0,
956 width: x1 - x0,
957 height: y1 - y0,
958 }
959 }
960 };
961
962 for (pi, panel) in built.panels.iter().enumerate() {
963 let (prow, pcol) = cell[pi];
964 let panel_width = col_widths[pcol];
965 let panel_height = row_heights[prow];
966 let px = col_x[pcol];
967 let py = row_y[prow];
968
969 let panel_rect = crate::render::Rect {
970 x: px,
971 y: py,
972 width: panel_width,
973 height: panel_height,
974 };
975 backend.clear_corner_claims();
983
984 let mut bottom_strip = 0.0_f64;
989 let mut left_strip = 0.0_f64;
990 let mut bottom_title = 0.0_f64;
991 let mut left_title = 0.0_f64;
992 for (side, label, t) in &strips[pi] {
993 let o = off(*side, pi, prow, pcol);
994 let (rect, angle) = match side {
995 Side::Top => ((px, py - t, panel_width, *t), 0.0),
996 Side::Bottom => {
997 if o > 0.0 {
998 bottom_title = *t + o;
999 } else {
1000 bottom_strip = *t;
1001 }
1002 ((px, py + panel_height + o, panel_width, *t), 0.0)
1003 }
1004 Side::Left => {
1005 if o > 0.0 {
1006 left_title = *t + o;
1007 } else {
1008 left_strip = *t;
1009 }
1010 ((px - t - o, py, *t, panel_height), 270.0)
1011 }
1012 Side::Right => ((px + panel_width, py, *t, panel_height), 90.0),
1013 };
1014 draw_strip(backend, theme, rect, label, angle)?;
1015 }
1016
1017 if theme.panel_background.visible {
1019 if let Some(fill) = theme.panel_background.fill {
1020 backend.set_element_class(Some("gg-panel-background"));
1021 backend.draw_rect(
1022 (panel_rect.x, panel_rect.y),
1023 (
1024 panel_rect.x + panel_rect.width,
1025 panel_rect.y + panel_rect.height,
1026 ),
1027 &RectStyle {
1028 fill: Some(fill),
1029 stroke: theme.panel_background.color,
1030 stroke_width: theme.panel_background.width,
1031 alpha: 1.0,
1032 clip: false,
1033 corner_radius: 0.0,
1034 },
1035 )?;
1036 }
1037 }
1038
1039 if theme.panel_border.visible {
1041 let style = LineStyle {
1042 color: theme.panel_border.color,
1043 width: theme.panel_border.width,
1044 alpha: 1.0,
1045 linetype: Linetype::Solid,
1046 ..Default::default()
1047 };
1048 let x0 = panel_rect.x;
1049 let y0 = panel_rect.y;
1050 let x1 = panel_rect.x + panel_rect.width;
1051 let y1 = panel_rect.y + panel_rect.height;
1052 backend.set_element_class(Some("gg-panel-border"));
1053 backend.draw_line(&[(x0, y0), (x1, y0)], &style)?;
1054 backend.draw_line(&[(x1, y0), (x1, y1)], &style)?;
1055 backend.draw_line(&[(x1, y1), (x0, y1)], &style)?;
1056 backend.draw_line(&[(x0, y1), (x0, y0)], &style)?;
1057 }
1058
1059 let panel_scale_set = if pi < built.panel_scales.len() {
1061 &built.panel_scales[pi]
1062 } else {
1063 &built.scales
1064 };
1065
1066 let x_scale = panel_scale_set.get(&Aesthetic::X);
1068 let y_scale = panel_scale_set.get(&Aesthetic::Y);
1069
1070 if let (true, Some(xs), Some(ys)) = (
1071 built.coord.is_polar() && !built.coord.is_radar(),
1072 x_scale,
1073 y_scale,
1074 ) {
1075 crate::guide::polar::draw_polar_guides(
1077 xs,
1078 ys,
1079 built.coord.as_ref(),
1080 theme,
1081 &panel_rect,
1082 backend,
1083 )?;
1084 } else if let (Some(xs), Some(ys)) = (x_scale, y_scale) {
1085 if built.coord.gridlines() {
1086 axis::draw_gridlines(
1087 xs,
1088 ys,
1089 built.coord.as_ref(),
1090 theme,
1091 &panel_rect,
1092 backend,
1093 )?;
1094 }
1095
1096 let bottom_edge = prow == nrow - 1 || pi + ncol >= built.panels.len();
1100 if bottom_edge || all_x {
1101 let axis_rect = crate::render::Rect {
1102 height: panel_rect.height + bottom_strip,
1103 ..panel_rect.clone()
1104 };
1105 let t = if bottom_edge || inner_labels_x {
1106 theme
1107 } else {
1108 &theme_no_x_labels
1109 };
1110 axis::draw_x_axis_with(
1111 xs,
1112 built.coord.as_ref(),
1113 t,
1114 &axis_rect,
1115 &total_area,
1116 false,
1117 backend,
1118 )?;
1119 if bottom_edge {
1120 let title_rect = crate::render::Rect {
1121 height: axis_rect.height + bottom_title,
1122 ..axis_rect.clone()
1123 };
1124 x_title_area = Some(union(&x_title_area, &title_rect));
1125 }
1126 }
1127
1128 if pcol == 0 || all_y {
1130 let axis_rect = crate::render::Rect {
1131 x: panel_rect.x - left_strip,
1132 width: panel_rect.width + left_strip,
1133 ..panel_rect.clone()
1134 };
1135 let t = if pcol == 0 || inner_labels_y {
1136 theme
1137 } else {
1138 &theme_no_y_labels
1139 };
1140 axis::draw_y_axis_with(
1141 ys,
1142 built.coord.as_ref(),
1143 t,
1144 &axis_rect,
1145 false,
1146 backend,
1147 )?;
1148 if pcol == 0 {
1149 let reserve = axis::y_label_reserve(theme, &ys.breaks());
1150 let prev = y_title_area.as_ref().map(|(r, _)| r.clone());
1151 let widest = y_title_area.as_ref().map_or(0.0, |(_, w)| *w);
1152 let title_rect = crate::render::Rect {
1153 x: axis_rect.x - left_title,
1154 width: axis_rect.width + left_title,
1155 ..axis_rect.clone()
1156 };
1157 y_title_area = Some((union(&prev, &title_rect), widest.max(reserve)));
1158 }
1159 }
1160 }
1161
1162 if fixed_grid.is_some() {
1166 let data = built
1167 .coord
1168 .data_rect(&panel_rect)
1169 .unwrap_or_else(|| panel_rect.clone());
1170 backend.begin_panel(pi, &panel.label, &panel_rect, &data);
1171 }
1172 backend.set_element_class(None);
1176 let clip_group = built.clip
1177 && fixed_grid.is_none()
1178 && built.panels_data.get(pi).is_some_and(|layers| {
1179 layers.iter().any(|d| {
1180 layer_spills(d, panel_scale_set, built.coord.as_ref(), &panel_rect)
1181 })
1182 });
1183 if clip_group {
1184 backend.begin_clip(&panel_rect);
1185 }
1186 if !built.clip {
1187 backend.set_clipping(false);
1188 }
1189 if pi < built.panels_data.len() {
1190 for (li, layer_data) in built.panels_data[pi].iter().enumerate() {
1191 if li < built.layers.len() && layer_data.nrows() > 0 && !is_figure(li) {
1192 let mut panel_backend = PanelBackendAdapter {
1193 inner: backend,
1194 panel_rect: panel_rect.clone(),
1195 };
1196 let geom = &built.layers[li].geom;
1197 panel_backend.set_mark_geom(Some(geom.name()));
1198 let drawn = geom.draw(
1199 layer_data,
1200 built.coord.as_ref(),
1201 panel_scale_set,
1202 theme,
1203 &mut panel_backend,
1204 );
1205 panel_backend.set_mark_geom(None);
1206 drawn?;
1207 }
1208 }
1209 }
1210 if !built.clip {
1211 backend.set_clipping(true);
1212 }
1213 if clip_group {
1214 backend.end_clip();
1215 }
1216 if fixed_grid.is_some() {
1217 backend.end_panel();
1218 }
1219 }
1220
1221 let title_scales = built.panel_scales.first().unwrap_or(&built.scales);
1224 if let (Some(area), Some(xs)) = (&x_title_area, title_scales.get(&Aesthetic::X)) {
1225 axis::draw_x_axis_title(xs, theme, area, backend)?;
1226 }
1227 if let (Some((area, reserve)), Some(ys)) = (&y_title_area, title_scales.get(&Aesthetic::Y))
1228 {
1229 axis::draw_y_axis_title(ys, theme, area, *reserve, backend)?;
1230 }
1231
1232 if band_h > 0.0 {
1234 backend.clear_corner_claims();
1235 for li in (0..built.layers.len()).filter(|&li| is_figure(li)) {
1236 let empty = crate::data::DataFrame::new();
1237 let data = built
1238 .panels_data
1239 .iter()
1240 .filter_map(|p| p.get(li))
1241 .find(|d| d.nrows() > 0)
1242 .unwrap_or(&empty);
1243 let mut band_backend = PanelBackendAdapter {
1244 inner: backend,
1245 panel_rect: band_rect.clone(),
1246 };
1247 let geom = &built.layers[li].geom;
1248 band_backend.set_mark_geom(Some(geom.name()));
1249 let drawn = geom.draw(
1250 data,
1251 built.coord.as_ref(),
1252 &built.scales,
1253 theme,
1254 &mut band_backend,
1255 );
1256 band_backend.set_mark_geom(None);
1257 drawn?;
1258 }
1259 }
1260
1261 let title_ref = Self::title_ref_rect(theme, &plot_area, &total_area);
1263 let caption_ref = Self::ref_rect(
1264 theme.caption_position.unwrap_or(theme.title_position),
1265 theme,
1266 &plot_area,
1267 &total_area,
1268 );
1269 let legend_lift = legend::top_legend_lift(
1272 theme,
1273 &built.scales,
1274 &built.suppressed_aes,
1275 &built.legend_plans,
1276 &built.guide_legend,
1277 );
1278 if let Some(ref title) = built.labels.title {
1279 let (tx, anchor) = Self::hjust_pos(&theme.title, &title_ref);
1280 let (title_y, _) = Self::title_subtitle_y(
1281 theme,
1282 plot_area.y,
1283 legend_lift,
1284 built.labels.subtitle.is_some(),
1285 );
1286 let family = if theme.title.family.is_empty() {
1287 None
1288 } else {
1289 Some(theme.title.family.clone())
1290 };
1291 backend.set_element_class(Some("gg-title"));
1292 crate::render::rich::draw(
1293 backend,
1294 theme.rich_text,
1295 title,
1296 (tx, title_y),
1297 &TextStyle {
1298 color: theme.title.color,
1299 size: theme.title.size,
1300 anchor,
1301 angle: 0.0,
1302 family,
1303 face: theme.title.face,
1304 bg_colour: None,
1305 bg_r: 0.0,
1306 },
1307 )?;
1308 }
1309
1310 if let Some(ref subtitle) = built.labels.subtitle {
1312 let (sx, anchor) = Self::hjust_pos(&theme.subtitle, &title_ref);
1313 let (_, subtitle_y) = Self::title_subtitle_y(theme, plot_area.y, legend_lift, true);
1314 let family = if theme.subtitle.family.is_empty() {
1315 None
1316 } else {
1317 Some(theme.subtitle.family.clone())
1318 };
1319 backend.set_element_class(Some("gg-subtitle"));
1320 crate::render::rich::draw(
1321 backend,
1322 theme.rich_text,
1323 subtitle,
1324 (sx, subtitle_y),
1325 &TextStyle {
1326 color: theme.subtitle.color,
1327 size: theme.subtitle.size,
1328 anchor,
1329 angle: 0.0,
1330 family,
1331 face: theme.subtitle.face,
1332 bg_colour: None,
1333 bg_r: 0.0,
1334 },
1335 )?;
1336 }
1337
1338 if let Some(ref caption) = built.labels.caption {
1340 let (cx, anchor) = Self::hjust_pos(&theme.caption, &caption_ref);
1341 let caption_y = total_area.y + total_area.height
1344 - theme.plot_margin.bottom.max(0.0)
1345 - theme.caption.size * 0.5;
1346 let family = if theme.caption.family.is_empty() {
1347 None
1348 } else {
1349 Some(theme.caption.family.clone())
1350 };
1351 backend.set_element_class(Some("gg-caption"));
1352 crate::render::rich::draw(
1353 backend,
1354 theme.rich_text,
1355 caption,
1356 (cx, caption_y),
1357 &TextStyle {
1358 color: theme.caption.color,
1359 size: theme.caption.size,
1360 anchor,
1361 angle: 0.0,
1362 family,
1363 face: theme.caption.face,
1364 bg_colour: None,
1365 bg_r: 0.0,
1366 },
1367 )?;
1368 }
1369
1370 backend.set_element_class(None);
1371 Self::draw_annotations(
1373 &built.annotations,
1374 &built.scales,
1375 built.coord.as_ref(),
1376 &plot_area,
1377 theme,
1378 backend,
1379 )?;
1380
1381 legend::draw_legend_planned(
1383 &built.scales,
1384 theme,
1385 &plot_area,
1386 plot_area.width,
1387 backend,
1388 &built.guide_legend,
1389 &built.suppressed_aes,
1390 &built.legend_plans,
1391 )?;
1392
1393 let tag_area = if theme.tag_location_panel {
1397 &plot_area
1398 } else {
1399 &total_area
1400 };
1401 Self::draw_tag(&built.labels, theme, tag_area, backend)?;
1402
1403 Ok(())
1404 }
1405
1406 fn draw_annotations(
1421 annotations: &[Annotation],
1422 scales: &crate::scale::ScaleSet,
1423 coord: &dyn crate::coord::Coord,
1424 plot_area: &crate::render::Rect,
1425 theme: &crate::theme::Theme,
1426 backend: &mut dyn DrawBackend,
1427 ) -> Result<(), RenderError> {
1428 use crate::data::Value;
1429
1430 let x_scale = scales.get(&Aesthetic::X);
1431 let y_scale = scales.get(&Aesthetic::Y);
1432
1433 for ann in annotations {
1434 match ann {
1435 Annotation::Text {
1436 label,
1437 x,
1438 y,
1439 size,
1440 color,
1441 } => {
1442 let nx = x_scale.map(|s| s.map(&Value::Float(*x))).unwrap_or(0.0);
1443 let ny = y_scale.map(|s| s.map(&Value::Float(*y))).unwrap_or(0.0);
1444 let pos = coord.transform((nx, ny), plot_area);
1445 backend.draw_text(
1446 label,
1447 pos,
1448 &TextStyle {
1449 color: annotation_ink(*color, theme),
1450 size: *size,
1451 anchor: TextAnchor::Middle,
1452 angle: 0.0,
1453 family: crate::geom::resolve_font_family("", theme),
1454 face: crate::render::backend::FontFace::Plain,
1455 bg_colour: None,
1456 bg_r: 0.0,
1457 },
1458 )?;
1459 }
1460 Annotation::Rect {
1461 xmin,
1462 xmax,
1463 ymin,
1464 ymax,
1465 fill,
1466 alpha,
1467 } => {
1468 let nx0 = x_scale.map(|s| s.map(&Value::Float(*xmin))).unwrap_or(0.0);
1469 let nx1 = x_scale.map(|s| s.map(&Value::Float(*xmax))).unwrap_or(1.0);
1470 let ny0 = y_scale.map(|s| s.map(&Value::Float(*ymin))).unwrap_or(0.0);
1471 let ny1 = y_scale.map(|s| s.map(&Value::Float(*ymax))).unwrap_or(1.0);
1472 let tl = coord.transform((nx0, ny1), plot_area);
1473 let br = coord.transform((nx1, ny0), plot_area);
1474 backend.draw_rect(
1475 tl,
1476 br,
1477 &RectStyle {
1478 fill: Some(*fill),
1479 stroke: None,
1480 stroke_width: 0.0,
1481 alpha: *alpha,
1482 clip: false,
1483 corner_radius: 0.0,
1484 },
1485 )?;
1486 }
1487 Annotation::Segment {
1488 x,
1489 y,
1490 xend,
1491 yend,
1492 color,
1493 width,
1494 } => {
1495 let nx0 = x_scale.map(|s| s.map(&Value::Float(*x))).unwrap_or(0.0);
1496 let ny0 = y_scale.map(|s| s.map(&Value::Float(*y))).unwrap_or(0.0);
1497 let nx1 = x_scale.map(|s| s.map(&Value::Float(*xend))).unwrap_or(1.0);
1498 let ny1 = y_scale.map(|s| s.map(&Value::Float(*yend))).unwrap_or(1.0);
1499 let p0 = coord.transform((nx0, ny0), plot_area);
1500 let p1 = coord.transform((nx1, ny1), plot_area);
1501 backend.draw_line(
1502 &[p0, p1],
1503 &LineStyle {
1504 color: annotation_ink(*color, theme),
1505 alpha: 1.0,
1506 width: *width,
1507 linetype: Linetype::Solid,
1508 ..Default::default()
1509 },
1510 )?;
1511 }
1512 Annotation::LogTicks {
1513 sides,
1514 outside,
1515 lengths,
1516 color,
1517 width,
1518 } => {
1519 let style = LineStyle {
1520 color: annotation_ink(*color, theme),
1521 alpha: 1.0,
1522 width: *width,
1523 linetype: Linetype::Solid,
1524 ..Default::default()
1525 };
1526 let dir = if *outside { -1.0 } else { 1.0 };
1527 for side in sides.chars() {
1528 let horizontal = matches!(side, 'b' | 't');
1529 let scale = if horizontal { x_scale } else { y_scale };
1530 let Some((lo, hi)) = scale.and_then(|s| s.expanded_domain()) else {
1531 continue;
1532 };
1533 for (t, kind) in log_ticks(lo, hi) {
1534 let n = scale.map(|s| s.map(&Value::Float(t))).unwrap_or(0.0);
1535 let len = dir
1536 * match kind {
1537 0 => lengths.2,
1538 1 => lengths.1,
1539 _ => lengths.0,
1540 };
1541 let (edge, step) = match side {
1543 'b' => ((n, 0.0), (0.0, -len)),
1544 't' => ((n, 1.0), (0.0, len)),
1545 'l' => ((0.0, n), (len, 0.0)),
1546 'r' => ((1.0, n), (-len, 0.0)),
1547 _ => continue,
1548 };
1549 let p0 = coord.transform(edge, plot_area);
1550 let p1 = (p0.0 + step.0, p0.1 + step.1);
1551 backend.draw_line(&[p0, p1], &style)?;
1552 }
1553 }
1554 }
1555 Annotation::Raster {
1556 image,
1557 xmin,
1558 xmax,
1559 ymin,
1560 ymax,
1561 alpha,
1562 } => {
1563 let rect = data_rect(
1564 x_scale,
1565 y_scale,
1566 coord,
1567 plot_area,
1568 [*xmin, *xmax, *ymin, *ymax],
1569 );
1570 backend.draw_image(image, &rect, plot_area, *alpha)?;
1571 }
1572 Annotation::Inset {
1573 plot,
1574 xmin,
1575 xmax,
1576 ymin,
1577 ymax,
1578 } => {
1579 let rect = data_rect(
1580 x_scale,
1581 y_scale,
1582 coord,
1583 plot_area,
1584 [*xmin, *xmax, *ymin, *ymax],
1585 );
1586 let (w, h) = (rect.width.round().max(1.0), rect.height.round().max(1.0));
1587 let mut guard = plot.0.lock().unwrap_or_else(|e| e.into_inner());
1588 match &mut *guard {
1589 Ok((built, meta)) => {
1590 let layout =
1591 crate::GGPlot::layout_built(built, meta, w as u32, h as u32);
1592 let mut off = crate::render::offset::OffsetBackend::new(
1593 backend,
1594 rect.x,
1595 rect.y,
1596 layout.plot_area.clone(),
1597 layout.total.clone(),
1598 );
1599 Self::render(built, &mut off)?;
1600 }
1601 Err(e) => {
1602 backend.warn(format!("annotation_custom: inset plot failed: {e}"))
1603 }
1604 }
1605 }
1606 }
1607 }
1608 Ok(())
1609 }
1610}
1611
1612fn draw_strip(
1618 backend: &mut dyn DrawBackend,
1619 theme: &crate::theme::Theme,
1620 (x, y, w, h): (f64, f64, f64, f64),
1621 label: &str,
1622 angle: f64,
1623) -> Result<(), RenderError> {
1624 let horizontal = angle == 0.0;
1626 let (bg, text) = if horizontal {
1627 (&theme.strip_background_x, &theme.strip_text_x)
1628 } else {
1629 (&theme.strip_background_y, &theme.strip_text_y)
1630 };
1631 let bg = bg.as_ref().unwrap_or(&theme.strip_background);
1632 let text = text.as_ref().unwrap_or(&theme.strip_text);
1633 if bg.visible {
1634 backend.set_element_class(Some("gg-strip gg-strip-background"));
1635 backend.draw_rect(
1636 (x, y),
1637 (x + w, y + h),
1638 &RectStyle {
1639 fill: bg.fill,
1640 stroke: bg.color,
1641 stroke_width: bg.width,
1642 alpha: 1.0,
1643 clip: false,
1644 corner_radius: 0.0,
1645 },
1646 )?;
1647 }
1648 if !text.visible {
1649 return Ok(());
1650 }
1651 let family = if text.family.is_empty() {
1652 None
1653 } else {
1654 Some(text.family.clone())
1655 };
1656 let lines: Vec<&str> = label.lines().collect();
1657 let n = lines.len().max(1) as f64;
1658 for (k, line) in lines.iter().enumerate() {
1659 let d = (k as f64 - (n - 1.0) / 2.0) * text.size * 1.2;
1660 let (dx, dy) = if angle == 90.0 {
1663 (-d, 0.0)
1664 } else if angle == 270.0 {
1665 (d, 0.0)
1666 } else {
1667 (0.0, d)
1668 };
1669 backend.set_element_class(Some("gg-strip gg-strip-text"));
1670 crate::render::rich::draw(
1671 backend,
1672 theme.rich_text,
1673 line,
1674 (x + w / 2.0 + dx, y + h / 2.0 + dy),
1675 &TextStyle {
1676 color: text.color,
1677 size: text.size,
1678 anchor: TextAnchor::Middle,
1679 angle,
1680 family: family.clone(),
1681 face: text.face,
1682 bg_colour: None,
1683 bg_r: 0.0,
1684 },
1685 )?;
1686 }
1687 Ok(())
1688}
1689
1690fn log_ticks(lo: f64, hi: f64) -> Vec<(f64, u8)> {
1691 let mut out = Vec::new();
1692 if !(lo.is_finite() && hi.is_finite()) || hi - lo > 300.0 {
1693 return out;
1694 }
1695 for k in (lo.floor() as i64)..=(hi.ceil() as i64) {
1696 for m in 1..=9 {
1697 let t = k as f64 + (m as f64).log10();
1698 if t >= lo - 1e-9 && t <= hi + 1e-9 {
1699 out.push((
1700 t,
1701 if m == 1 {
1702 0
1703 } else if m == 5 {
1704 1
1705 } else {
1706 2
1707 },
1708 ));
1709 }
1710 }
1711 }
1712 out
1713}
1714
1715fn data_rect(
1718 x_scale: Option<&dyn crate::scale::Scale>,
1719 y_scale: Option<&dyn crate::scale::Scale>,
1720 coord: &dyn crate::coord::Coord,
1721 plot_area: &crate::render::Rect,
1722 [xmin, xmax, ymin, ymax]: [f64; 4],
1723) -> crate::render::Rect {
1724 use crate::data::Value;
1725 let m = |s: Option<&dyn crate::scale::Scale>, v: f64, edge: f64| {
1726 if v.is_finite() {
1727 s.map(|s| s.map(&Value::Float(v))).unwrap_or(edge)
1728 } else if v > 0.0 {
1729 1.0
1730 } else {
1731 0.0
1732 }
1733 };
1734 let a = coord.transform((m(x_scale, xmin, 0.0), m(y_scale, ymax, 1.0)), plot_area);
1735 let b = coord.transform((m(x_scale, xmax, 1.0), m(y_scale, ymin, 0.0)), plot_area);
1736 crate::render::Rect {
1737 x: a.0.min(b.0),
1738 y: a.1.min(b.1),
1739 width: (a.0 - b.0).abs(),
1740 height: (a.1 - b.1).abs(),
1741 }
1742}
1743
1744fn annotation_ink(color: (u8, u8, u8), theme: &crate::theme::Theme) -> (u8, u8, u8) {
1750 match (color == (50, 50, 50), theme.geom.ink) {
1751 (true, Some(ink)) => ink,
1752 _ => color,
1753 }
1754}
1755
1756fn layer_spills(
1761 data: &crate::data::DataFrame,
1762 scales: &crate::scale::ScaleSet,
1763 coord: &dyn crate::coord::Coord,
1764 area: &crate::render::Rect,
1765) -> bool {
1766 if coord.is_polar() || data.nrows() == 0 {
1767 return false;
1768 }
1769 const SLACK: f64 = 0.5;
1770 let outside = |(px, py): (f64, f64)| {
1771 px < area.x - SLACK
1772 || px > area.x + area.width + SLACK
1773 || py < area.y - SLACK
1774 || py > area.y + area.height + SLACK
1775 };
1776 let axes = [
1777 (Aesthetic::X, ["x", "xmin", "xmax", "xend"], true),
1778 (Aesthetic::Y, ["y", "ymin", "ymax", "yend"], false),
1779 ];
1780 for (aes, cols, is_x) in axes {
1781 let Some(scale) = scales.get(&aes) else {
1782 continue;
1783 };
1784 for col in cols {
1785 let Some(values) = data.column(col) else {
1786 continue;
1787 };
1788 for v in values {
1789 if !v.as_f64().is_some_and(f64::is_finite) {
1790 continue;
1791 }
1792 let n = scale.map(v);
1793 if !n.is_finite() {
1794 continue;
1795 }
1796 let p = if is_x { (n, 0.5) } else { (0.5, n) };
1797 if outside(coord.transform(p, area)) {
1798 return true;
1799 }
1800 }
1801 }
1802 }
1803 false
1804}
1805
1806struct PanelBackendAdapter<'a> {
1808 inner: &'a mut dyn DrawBackend,
1809 panel_rect: crate::render::Rect,
1810}
1811
1812impl<'a> DrawBackend for PanelBackendAdapter<'a> {
1813 fn set_clipping(&mut self, on: bool) {
1814 self.inner.set_clipping(on)
1815 }
1816 fn begin_clip(&mut self, rect: &crate::render::Rect) {
1817 self.inner.begin_clip(rect)
1818 }
1819 fn enable_element_classes(&mut self, on: bool) {
1820 self.inner.enable_element_classes(on)
1821 }
1822 fn draw_rich_text(
1823 &mut self,
1824 runs: &[crate::render::rich::RichRun],
1825 pos: (f64, f64),
1826 style: &crate::render::backend::TextStyle,
1827 ) -> Result<(), RenderError> {
1828 self.inner.draw_rich_text(runs, pos, style)
1829 }
1830 fn set_element_class(&mut self, class: Option<&'static str>) {
1831 self.inner.set_element_class(class)
1832 }
1833 fn end_clip(&mut self) {
1834 self.inner.end_clip()
1835 }
1836 fn draw_image(
1837 &mut self,
1838 bytes: &[u8],
1839 rect: &crate::render::Rect,
1840 clip: &crate::render::Rect,
1841 alpha: f64,
1842 ) -> Result<(), RenderError> {
1843 self.inner.draw_image(bytes, rect, clip, alpha)
1844 }
1845 fn draw_circle(
1846 &mut self,
1847 center: (f64, f64),
1848 radius: f64,
1849 style: &crate::render::backend::PointStyle,
1850 ) -> Result<(), RenderError> {
1851 self.inner.draw_circle(center, radius, style)
1852 }
1853 fn draw_line(
1854 &mut self,
1855 points: &[(f64, f64)],
1856 style: &crate::render::backend::LineStyle,
1857 ) -> Result<(), RenderError> {
1858 self.inner.draw_line(points, style)
1859 }
1860 fn draw_quadratic(
1861 &mut self,
1862 p0: (f64, f64),
1863 ctrl: (f64, f64),
1864 p1: (f64, f64),
1865 style: &crate::render::backend::LineStyle,
1866 ) -> Result<(), RenderError> {
1867 self.inner.draw_quadratic(p0, ctrl, p1, style)
1868 }
1869 fn draw_rect(
1870 &mut self,
1871 top_left: (f64, f64),
1872 bottom_right: (f64, f64),
1873 style: &RectStyle,
1874 ) -> Result<(), RenderError> {
1875 self.inner.draw_rect(top_left, bottom_right, style)
1876 }
1877 fn draw_text(
1878 &mut self,
1879 text: &str,
1880 pos: (f64, f64),
1881 style: &TextStyle,
1882 ) -> Result<(), RenderError> {
1883 self.inner.draw_text(text, pos, style)
1884 }
1885 fn draw_polygon(
1886 &mut self,
1887 points: &[(f64, f64)],
1888 style: &RectStyle,
1889 ) -> Result<(), RenderError> {
1890 self.inner.draw_polygon(points, style)
1891 }
1892 fn draw_polygon_rings(
1893 &mut self,
1894 rings: &[Vec<(f64, f64)>],
1895 style: &RectStyle,
1896 ) -> Result<(), RenderError> {
1897 self.inner.draw_polygon_rings(rings, style)
1898 }
1899 fn draw_shape(
1900 &mut self,
1901 center: (f64, f64),
1902 radius: f64,
1903 style: &crate::render::backend::PointStyle,
1904 ) -> Result<(), RenderError> {
1905 self.inner.draw_shape(center, radius, style)
1906 }
1907 fn plot_area(&self) -> crate::render::Rect {
1908 self.panel_rect.clone()
1909 }
1910 fn set_tooltip(&mut self, tooltip: Option<String>) {
1911 self.inner.set_tooltip(tooltip)
1912 }
1913 fn set_mark_axis(&mut self, key: Option<String>) {
1914 self.inner.set_mark_axis(key)
1915 }
1916 fn set_mark_series(&mut self, series: Option<String>) {
1917 self.inner.set_mark_series(series)
1918 }
1919 fn set_mark_value(&mut self, value: Option<String>) {
1920 self.inner.set_mark_value(value)
1921 }
1922 fn set_mark_geom(&mut self, geom: Option<&str>) {
1923 self.inner.set_mark_geom(geom)
1924 }
1925 fn warn(&mut self, message: String) {
1926 self.inner.warn(message)
1927 }
1928 fn set_mark_part(&mut self, part: Option<&str>) {
1929 self.inner.set_mark_part(part)
1930 }
1931 fn set_mark_attrs(&mut self, attrs: Vec<(String, String)>) {
1932 self.inner.set_mark_attrs(attrs)
1933 }
1934
1935 fn set_mark_class(&mut self, class: Option<String>) {
1936 self.inner.set_mark_class(class)
1937 }
1938
1939 fn set_mark_group(&mut self, group: Option<(String, String)>) {
1940 self.inner.set_mark_group(group)
1941 }
1942
1943 fn set_layer_attrs(&mut self, attrs: Vec<(String, String)>) {
1944 self.inner.set_layer_attrs(attrs)
1945 }
1946 fn wants_formatted_values(&self) -> bool {
1947 self.inner.wants_formatted_values()
1948 }
1949 fn set_mark_fvalue(&mut self, value: Option<String>) {
1950 self.inner.set_mark_fvalue(value)
1951 }
1952 fn set_mark_fx(&mut self, value: Option<String>) {
1953 self.inner.set_mark_fx(value)
1954 }
1955 fn total_area(&self) -> crate::render::Rect {
1956 self.inner.total_area()
1957 }
1958 fn corner_claims(&self) -> Vec<crate::render::Rect> {
1959 self.inner.corner_claims()
1960 }
1961 fn claim_corner_box(&mut self, rect: crate::render::Rect) {
1962 self.inner.claim_corner_box(rect)
1963 }
1964 fn clear_corner_claims(&mut self) {
1965 self.inner.clear_corner_claims()
1966 }
1967}
1968
1969#[cfg(test)]
1970mod tests {
1971 use super::*;
1972 use crate::render::backend::LineStyle;
1973 use crate::render::svg_backend::SvgBackend;
1974
1975 #[test]
1981 fn panel_backend_adapter_forwards_quadratic_and_mark_metadata() {
1982 let total = crate::render::Rect {
1983 x: 0.0,
1984 y: 0.0,
1985 width: 200.0,
1986 height: 200.0,
1987 };
1988 let panel_rect = crate::render::Rect {
1989 x: 10.0,
1990 y: 10.0,
1991 width: 80.0,
1992 height: 80.0,
1993 };
1994 let mut backend = SvgBackend::new(200, 200, total);
1995 let mut adapter = PanelBackendAdapter {
1996 inner: &mut backend,
1997 panel_rect: panel_rect.clone(),
1998 };
1999
2000 assert_eq!(adapter.plot_area().x, panel_rect.x);
2001 assert_eq!(adapter.plot_area().width, panel_rect.width);
2002
2003 adapter.set_mark_part(Some("arrow"));
2004 adapter.set_mark_attrs(vec![("data-x".to_string(), "v".to_string())]);
2005 adapter
2006 .draw_quadratic((0.0, 0.0), (5.0, 10.0), (10.0, 0.0), &LineStyle::default())
2007 .unwrap();
2008 adapter.set_mark_part(None);
2009 adapter.set_mark_attrs(Vec::new());
2010
2011 let svg = backend.finish();
2012 assert!(svg.contains("<path"));
2013 assert!(svg.contains("data-part=\"arrow\""));
2014 assert!(svg.contains("data-x=\"v\""));
2015 }
2016}