1use crate::aes::Aesthetic;
2use crate::annotate::Annotation;
3use crate::build::BuiltPlot;
4use crate::facet::Facet;
5use crate::guide::{axis, legend};
6use crate::render::backend::{DrawBackend, LineStyle, Linetype, RectStyle, TextAnchor, TextStyle};
7use crate::render::RenderError;
8
9pub struct PlotRenderer;
11
12impl PlotRenderer {
13 pub fn render(built: &BuiltPlot, backend: &mut dyn DrawBackend) -> Result<(), RenderError> {
14 if !built.facet.is_none() && !built.panels.is_empty() {
15 Self::render_faceted(built, backend)
16 } else {
17 Self::render_single(built, backend)
18 }
19 }
20
21 fn render_single(built: &BuiltPlot, backend: &mut dyn DrawBackend) -> Result<(), RenderError> {
22 let theme = &built.theme;
23 let plot_area = backend.plot_area();
24 let total_area = backend.total_area();
25
26 if theme.plot_background.visible {
28 if let Some(fill) = theme.plot_background.fill {
29 backend.draw_rect(
30 (total_area.x, total_area.y),
31 (
32 total_area.x + total_area.width,
33 total_area.y + total_area.height,
34 ),
35 &RectStyle {
36 fill: Some(fill),
37 stroke: None,
38 stroke_width: 0.0,
39 alpha: 1.0,
40 clip: false,
41 },
42 )?;
43 }
44 }
45
46 if theme.panel_background.visible {
48 if let Some(fill) = theme.panel_background.fill {
49 backend.draw_rect(
50 (plot_area.x, plot_area.y),
51 (
52 plot_area.x + plot_area.width,
53 plot_area.y + plot_area.height,
54 ),
55 &RectStyle {
56 fill: Some(fill),
57 stroke: theme.panel_background.color,
58 stroke_width: theme.panel_background.width,
59 alpha: 1.0,
60 clip: false,
61 },
62 )?;
63 }
64 }
65
66 if theme.panel_border.visible {
68 let style = LineStyle {
69 color: theme.panel_border.color,
70 width: theme.panel_border.width,
71 alpha: 1.0,
72 linetype: Linetype::Solid,
73 };
74 let x0 = plot_area.x;
75 let y0 = plot_area.y;
76 let x1 = plot_area.x + plot_area.width;
77 let y1 = plot_area.y + plot_area.height;
78 backend.draw_line(&[(x0, y0), (x1, y0)], &style)?;
79 backend.draw_line(&[(x1, y0), (x1, y1)], &style)?;
80 backend.draw_line(&[(x1, y1), (x0, y1)], &style)?;
81 backend.draw_line(&[(x0, y1), (x0, y0)], &style)?;
82 }
83
84 let x_scale = built.scales.get(&Aesthetic::X);
86 let y_scale = built.scales.get(&Aesthetic::Y);
87
88 let (h_scale, v_scale) = if built.coord.is_flipped() {
89 (y_scale, x_scale)
90 } else {
91 (x_scale, y_scale)
92 };
93
94 if let (Some(hs), Some(vs)) = (h_scale, v_scale) {
95 if built.coord.gridlines() {
96 axis::draw_gridlines(hs, vs, built.coord.as_ref(), theme, &plot_area, backend)?;
97 }
98
99 axis::draw_x_axis(hs, built.coord.as_ref(), theme, &plot_area, backend)?;
101 axis::draw_y_axis(vs, built.coord.as_ref(), theme, &plot_area, backend)?;
102
103 if let Some(sec) = built.scales.sec_axis(&Aesthetic::Y) {
105 axis::draw_sec_y_axis(vs, sec, built.coord.as_ref(), theme, &plot_area, backend)?;
106 }
107 }
108
109 for layer in &built.layers {
111 layer.geom.draw(
112 &layer.data,
113 built.coord.as_ref(),
114 &built.scales,
115 theme,
116 backend,
117 )?;
118 }
119
120 Self::draw_annotations(
122 &built.annotations,
123 &built.scales,
124 built.coord.as_ref(),
125 &plot_area,
126 backend,
127 )?;
128
129 if let Some(ref title) = built.labels.title {
131 let center_x = plot_area.x + plot_area.width / 2.0;
132 let x_axis_top = built
135 .scales
136 .get(&Aesthetic::X)
137 .map(|s| s.axis_position_opposite())
138 .unwrap_or(false);
139 let x_axis_lift = if x_axis_top {
140 theme.axis_ticks_length + theme.axis_text_x.size + theme.legend_spacing
141 } else {
142 0.0
143 };
144 let title_y = plot_area.y - theme.title.size * 0.9 - x_axis_lift;
145 let family = if theme.title.family.is_empty() {
146 None
147 } else {
148 Some(theme.title.family.clone())
149 };
150 backend.draw_text(
151 title,
152 (center_x, title_y.max(theme.title.size)),
153 &TextStyle {
154 color: theme.title.color,
155 size: theme.title.size,
156 anchor: TextAnchor::Middle,
157 angle: 0.0,
158 family,
159 },
160 )?;
161 }
162
163 if let Some(ref subtitle) = built.labels.subtitle {
165 let center_x = plot_area.x + plot_area.width / 2.0;
166 let subtitle_y = plot_area.y - 2.0;
167 let family = if theme.subtitle.family.is_empty() {
168 None
169 } else {
170 Some(theme.subtitle.family.clone())
171 };
172 backend.draw_text(
173 subtitle,
174 (
175 center_x,
176 subtitle_y.max(theme.title.size + theme.subtitle.size),
177 ),
178 &TextStyle {
179 color: theme.subtitle.color,
180 size: theme.subtitle.size,
181 anchor: TextAnchor::Middle,
182 angle: 0.0,
183 family,
184 },
185 )?;
186 }
187
188 legend::draw_legend(
190 &built.scales,
191 theme,
192 &plot_area,
193 backend,
194 &built.guide_legend,
195 &built.suppressed_aes,
196 )?;
197
198 if let Some(ref caption) = built.labels.caption {
200 let right_x = plot_area.x + plot_area.width;
201 let caption_y = total_area.y + total_area.height - theme.caption.size * 0.5;
202 let family = if theme.caption.family.is_empty() {
203 None
204 } else {
205 Some(theme.caption.family.clone())
206 };
207 backend.draw_text(
208 caption,
209 (right_x, caption_y),
210 &TextStyle {
211 color: theme.caption.color,
212 size: theme.caption.size,
213 anchor: TextAnchor::End,
214 angle: 0.0,
215 family,
216 },
217 )?;
218 }
219
220 Self::draw_tag(&built.labels, theme, &total_area, backend)?;
222
223 Ok(())
224 }
225
226 fn draw_tag(
228 labels: &crate::plot::Labels,
229 theme: &crate::theme::Theme,
230 total_area: &crate::render::Rect,
231 backend: &mut dyn DrawBackend,
232 ) -> Result<(), RenderError> {
233 if let Some(ref tag) = labels.tag {
234 let family = if theme.title.family.is_empty() {
235 None
236 } else {
237 Some(theme.title.family.clone())
238 };
239 backend.draw_text(
240 tag,
241 (
242 total_area.x + theme.title.size,
243 total_area.y + theme.title.size,
244 ),
245 &TextStyle {
246 color: theme.title.color,
247 size: theme.title.size,
248 anchor: TextAnchor::Start,
249 angle: 0.0,
250 family,
251 },
252 )?;
253 }
254 Ok(())
255 }
256
257 fn render_faceted(built: &BuiltPlot, backend: &mut dyn DrawBackend) -> Result<(), RenderError> {
258 let theme = &built.theme;
259 let plot_area = backend.plot_area();
260 let total_area = backend.total_area();
261
262 if theme.plot_background.visible {
264 if let Some(fill) = theme.plot_background.fill {
265 backend.draw_rect(
266 (total_area.x, total_area.y),
267 (
268 total_area.x + total_area.width,
269 total_area.y + total_area.height,
270 ),
271 &RectStyle {
272 fill: Some(fill),
273 stroke: None,
274 stroke_width: 0.0,
275 alpha: 1.0,
276 clip: false,
277 },
278 )?;
279 }
280 }
281
282 let ncol = match &built.facet {
284 Facet::Wrap { ncol, .. } => {
285 ncol.unwrap_or_else(|| (built.panels.len() as f64).sqrt().ceil() as usize)
286 }
287 Facet::Grid { .. } => built.panels.iter().map(|p| p.col).max().unwrap_or(0) + 1,
288 Facet::None => 1,
289 };
290 let nrow = built.panels.len().div_ceil(ncol);
291
292 let strip_height = theme.strip_text.size + 8.0;
293 let gap_x = theme.get_panel_spacing_x();
294 let gap_y = theme.get_panel_spacing_y();
295
296 let space = match &built.facet {
299 Facet::Grid { space, .. } => space.clone(),
300 _ => crate::facet::FacetSpace::Fixed,
301 };
302 let extent = |pi: usize, col: &str| -> Option<f64> {
303 let (mut lo, mut hi) = (f64::INFINITY, f64::NEG_INFINITY);
304 for df in &built.panels_data[pi] {
305 if let Some(c) = df.column(col) {
306 for v in c {
307 if let Some(f) = v.as_f64() {
308 lo = lo.min(f);
309 hi = hi.max(f);
310 }
311 }
312 }
313 }
314 (lo <= hi).then_some(hi - lo)
315 };
316 let avail_w = plot_area.width - gap_x * (ncol as f64 - 1.0);
317 let col_widths: Vec<f64> = if space.free_x() && ncol > 0 {
318 let mut ranges = vec![0.0f64; ncol];
319 for (pi, panel) in built.panels.iter().enumerate() {
320 if let Some(r) = extent(pi, "x") {
321 ranges[panel.col] = ranges[panel.col].max(r);
322 }
323 }
324 let total: f64 = ranges.iter().sum();
325 if total > 0.0 {
326 ranges.iter().map(|r| avail_w * (r / total)).collect()
327 } else {
328 vec![avail_w / ncol as f64; ncol]
329 }
330 } else {
331 vec![avail_w / ncol.max(1) as f64; ncol.max(1)]
332 };
333 let mut col_x = vec![plot_area.x; ncol.max(1)];
334 {
335 let mut acc = plot_area.x;
336 for c in 0..ncol {
337 col_x[c] = acc;
338 acc += col_widths[c] + gap_x;
339 }
340 }
341
342 let avail_h = plot_area.height - gap_y * (nrow as f64 - 1.0) - strip_height * nrow as f64;
343 let row_heights: Vec<f64> = if space.free_y() && nrow > 0 {
344 let mut ranges = vec![0.0f64; nrow];
345 for (pi, panel) in built.panels.iter().enumerate() {
346 if let Some(r) = extent(pi, "y") {
347 ranges[panel.row] = ranges[panel.row].max(r);
348 }
349 }
350 let total: f64 = ranges.iter().sum();
351 if total > 0.0 {
352 ranges.iter().map(|r| avail_h * (r / total)).collect()
353 } else {
354 vec![avail_h / nrow as f64; nrow]
355 }
356 } else {
357 vec![avail_h / nrow.max(1) as f64; nrow.max(1)]
358 };
359 let mut row_y = vec![plot_area.y; nrow.max(1)];
360 {
361 let mut acc = plot_area.y;
362 for r in 0..nrow {
363 row_y[r] = acc;
364 acc += row_heights[r] + strip_height + gap_y;
365 }
366 }
367
368 for (pi, panel) in built.panels.iter().enumerate() {
369 let panel_width = col_widths[panel.col];
370 let panel_height = row_heights[panel.row];
371 let px = col_x[panel.col];
372 let py = row_y[panel.row];
373
374 let panel_rect = crate::render::Rect {
375 x: px,
376 y: py + strip_height,
377 width: panel_width,
378 height: panel_height,
379 };
380
381 if theme.strip_background.visible {
383 backend.draw_rect(
384 (px, py),
385 (px + panel_width, py + strip_height),
386 &RectStyle {
387 fill: theme.strip_background.fill,
388 stroke: theme.strip_background.color,
389 stroke_width: theme.strip_background.width,
390 alpha: 1.0,
391 clip: false,
392 },
393 )?;
394 }
395
396 if theme.strip_text.visible {
398 let label = panel.col_label.as_deref().unwrap_or(&panel.label);
399 let family = if theme.strip_text.family.is_empty() {
400 None
401 } else {
402 Some(theme.strip_text.family.clone())
403 };
404 backend.draw_text(
405 label,
406 (px + panel_width / 2.0, py + strip_height / 2.0),
407 &TextStyle {
408 color: theme.strip_text.color,
409 size: theme.strip_text.size,
410 anchor: TextAnchor::Middle,
411 angle: 0.0,
412 family,
413 },
414 )?;
415 }
416
417 if theme.panel_background.visible {
419 if let Some(fill) = theme.panel_background.fill {
420 backend.draw_rect(
421 (panel_rect.x, panel_rect.y),
422 (
423 panel_rect.x + panel_rect.width,
424 panel_rect.y + panel_rect.height,
425 ),
426 &RectStyle {
427 fill: Some(fill),
428 stroke: theme.panel_background.color,
429 stroke_width: theme.panel_background.width,
430 alpha: 1.0,
431 clip: false,
432 },
433 )?;
434 }
435 }
436
437 if theme.panel_border.visible {
439 let style = LineStyle {
440 color: theme.panel_border.color,
441 width: theme.panel_border.width,
442 alpha: 1.0,
443 linetype: Linetype::Solid,
444 };
445 let x0 = panel_rect.x;
446 let y0 = panel_rect.y;
447 let x1 = panel_rect.x + panel_rect.width;
448 let y1 = panel_rect.y + panel_rect.height;
449 backend.draw_line(&[(x0, y0), (x1, y0)], &style)?;
450 backend.draw_line(&[(x1, y0), (x1, y1)], &style)?;
451 backend.draw_line(&[(x1, y1), (x0, y1)], &style)?;
452 backend.draw_line(&[(x0, y1), (x0, y0)], &style)?;
453 }
454
455 let panel_scale_set = if pi < built.panel_scales.len() {
457 &built.panel_scales[pi]
458 } else {
459 &built.scales
460 };
461
462 let x_scale = panel_scale_set.get(&Aesthetic::X);
464 let y_scale = panel_scale_set.get(&Aesthetic::Y);
465
466 if let (Some(xs), Some(ys)) = (x_scale, y_scale) {
467 if built.coord.gridlines() {
468 axis::draw_gridlines(
469 xs,
470 ys,
471 built.coord.as_ref(),
472 theme,
473 &panel_rect,
474 backend,
475 )?;
476 }
477
478 if panel.row == nrow - 1 || pi + ncol >= built.panels.len() {
480 axis::draw_x_axis(xs, built.coord.as_ref(), theme, &panel_rect, backend)?;
481 }
482
483 if panel.col == 0 {
485 axis::draw_y_axis(ys, built.coord.as_ref(), theme, &panel_rect, backend)?;
486 }
487 }
488
489 if pi < built.panels_data.len() {
491 for (li, layer_data) in built.panels_data[pi].iter().enumerate() {
492 if li < built.layers.len() && layer_data.nrows() > 0 {
493 let mut panel_backend = PanelBackendAdapter {
494 inner: backend,
495 panel_rect: panel_rect.clone(),
496 };
497 built.layers[li].geom.draw(
498 layer_data,
499 built.coord.as_ref(),
500 panel_scale_set,
501 theme,
502 &mut panel_backend,
503 )?;
504 }
505 }
506 }
507 }
508
509 if let Some(ref title) = built.labels.title {
511 let center_x = plot_area.x + plot_area.width / 2.0;
512 let title_y = plot_area.y - theme.title.size * 0.9;
513 let family = if theme.title.family.is_empty() {
514 None
515 } else {
516 Some(theme.title.family.clone())
517 };
518 backend.draw_text(
519 title,
520 (center_x, title_y.max(theme.title.size)),
521 &TextStyle {
522 color: theme.title.color,
523 size: theme.title.size,
524 anchor: TextAnchor::Middle,
525 angle: 0.0,
526 family,
527 },
528 )?;
529 }
530
531 if let Some(ref subtitle) = built.labels.subtitle {
533 let center_x = plot_area.x + plot_area.width / 2.0;
534 let subtitle_y = plot_area.y - 2.0;
535 let family = if theme.subtitle.family.is_empty() {
536 None
537 } else {
538 Some(theme.subtitle.family.clone())
539 };
540 backend.draw_text(
541 subtitle,
542 (
543 center_x,
544 subtitle_y.max(theme.title.size + theme.subtitle.size),
545 ),
546 &TextStyle {
547 color: theme.subtitle.color,
548 size: theme.subtitle.size,
549 anchor: TextAnchor::Middle,
550 angle: 0.0,
551 family,
552 },
553 )?;
554 }
555
556 if let Some(ref caption) = built.labels.caption {
558 let right_x = plot_area.x + plot_area.width;
559 let caption_y = total_area.y + total_area.height - theme.caption.size * 0.5;
560 let family = if theme.caption.family.is_empty() {
561 None
562 } else {
563 Some(theme.caption.family.clone())
564 };
565 backend.draw_text(
566 caption,
567 (right_x, caption_y),
568 &TextStyle {
569 color: theme.caption.color,
570 size: theme.caption.size,
571 anchor: TextAnchor::End,
572 angle: 0.0,
573 family,
574 },
575 )?;
576 }
577
578 Self::draw_annotations(
580 &built.annotations,
581 &built.scales,
582 built.coord.as_ref(),
583 &plot_area,
584 backend,
585 )?;
586
587 legend::draw_legend(
589 &built.scales,
590 theme,
591 &plot_area,
592 backend,
593 &built.guide_legend,
594 &built.suppressed_aes,
595 )?;
596
597 Self::draw_tag(&built.labels, theme, &total_area, backend)?;
599
600 Ok(())
601 }
602
603 fn draw_annotations(
604 annotations: &[Annotation],
605 scales: &crate::scale::ScaleSet,
606 coord: &dyn crate::coord::Coord,
607 plot_area: &crate::render::Rect,
608 backend: &mut dyn DrawBackend,
609 ) -> Result<(), RenderError> {
610 use crate::data::Value;
611
612 let x_scale = scales.get(&Aesthetic::X);
613 let y_scale = scales.get(&Aesthetic::Y);
614
615 for ann in annotations {
616 match ann {
617 Annotation::Text {
618 label,
619 x,
620 y,
621 size,
622 color,
623 } => {
624 let nx = x_scale.map(|s| s.map(&Value::Float(*x))).unwrap_or(0.0);
625 let ny = y_scale.map(|s| s.map(&Value::Float(*y))).unwrap_or(0.0);
626 let pos = coord.transform((nx, ny), plot_area);
627 backend.draw_text(
628 label,
629 pos,
630 &TextStyle {
631 color: *color,
632 size: *size,
633 anchor: TextAnchor::Middle,
634 angle: 0.0,
635 family: None,
636 },
637 )?;
638 }
639 Annotation::Rect {
640 xmin,
641 xmax,
642 ymin,
643 ymax,
644 fill,
645 alpha,
646 } => {
647 let nx0 = x_scale.map(|s| s.map(&Value::Float(*xmin))).unwrap_or(0.0);
648 let nx1 = x_scale.map(|s| s.map(&Value::Float(*xmax))).unwrap_or(1.0);
649 let ny0 = y_scale.map(|s| s.map(&Value::Float(*ymin))).unwrap_or(0.0);
650 let ny1 = y_scale.map(|s| s.map(&Value::Float(*ymax))).unwrap_or(1.0);
651 let tl = coord.transform((nx0, ny1), plot_area);
652 let br = coord.transform((nx1, ny0), plot_area);
653 backend.draw_rect(
654 tl,
655 br,
656 &RectStyle {
657 fill: Some(*fill),
658 stroke: None,
659 stroke_width: 0.0,
660 alpha: *alpha,
661 clip: false,
662 },
663 )?;
664 }
665 Annotation::Segment {
666 x,
667 y,
668 xend,
669 yend,
670 color,
671 width,
672 } => {
673 let nx0 = x_scale.map(|s| s.map(&Value::Float(*x))).unwrap_or(0.0);
674 let ny0 = y_scale.map(|s| s.map(&Value::Float(*y))).unwrap_or(0.0);
675 let nx1 = x_scale.map(|s| s.map(&Value::Float(*xend))).unwrap_or(1.0);
676 let ny1 = y_scale.map(|s| s.map(&Value::Float(*yend))).unwrap_or(1.0);
677 let p0 = coord.transform((nx0, ny0), plot_area);
678 let p1 = coord.transform((nx1, ny1), plot_area);
679 backend.draw_line(
680 &[p0, p1],
681 &LineStyle {
682 color: *color,
683 alpha: 1.0,
684 width: *width,
685 linetype: Linetype::Solid,
686 },
687 )?;
688 }
689 }
690 }
691 Ok(())
692 }
693}
694
695struct PanelBackendAdapter<'a> {
697 inner: &'a mut dyn DrawBackend,
698 panel_rect: crate::render::Rect,
699}
700
701impl<'a> DrawBackend for PanelBackendAdapter<'a> {
702 fn draw_circle(
703 &mut self,
704 center: (f64, f64),
705 radius: f64,
706 style: &crate::render::backend::PointStyle,
707 ) -> Result<(), RenderError> {
708 self.inner.draw_circle(center, radius, style)
709 }
710 fn draw_line(
711 &mut self,
712 points: &[(f64, f64)],
713 style: &crate::render::backend::LineStyle,
714 ) -> Result<(), RenderError> {
715 self.inner.draw_line(points, style)
716 }
717 fn draw_rect(
718 &mut self,
719 top_left: (f64, f64),
720 bottom_right: (f64, f64),
721 style: &RectStyle,
722 ) -> Result<(), RenderError> {
723 self.inner.draw_rect(top_left, bottom_right, style)
724 }
725 fn draw_text(
726 &mut self,
727 text: &str,
728 pos: (f64, f64),
729 style: &TextStyle,
730 ) -> Result<(), RenderError> {
731 self.inner.draw_text(text, pos, style)
732 }
733 fn draw_polygon(
734 &mut self,
735 points: &[(f64, f64)],
736 style: &RectStyle,
737 ) -> Result<(), RenderError> {
738 self.inner.draw_polygon(points, style)
739 }
740 fn draw_shape(
741 &mut self,
742 center: (f64, f64),
743 radius: f64,
744 style: &crate::render::backend::PointStyle,
745 ) -> Result<(), RenderError> {
746 self.inner.draw_shape(center, radius, style)
747 }
748 fn plot_area(&self) -> crate::render::Rect {
749 self.panel_rect.clone()
750 }
751 fn total_area(&self) -> crate::render::Rect {
752 self.inner.total_area()
753 }
754}