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 title_y = plot_area.y - theme.title.size * 0.9;
133 let family = if theme.title.family.is_empty() {
134 None
135 } else {
136 Some(theme.title.family.clone())
137 };
138 backend.draw_text(
139 title,
140 (center_x, title_y.max(theme.title.size)),
141 &TextStyle {
142 color: theme.title.color,
143 size: theme.title.size,
144 anchor: TextAnchor::Middle,
145 angle: 0.0,
146 family,
147 },
148 )?;
149 }
150
151 if let Some(ref subtitle) = built.labels.subtitle {
153 let center_x = plot_area.x + plot_area.width / 2.0;
154 let subtitle_y = plot_area.y - 2.0;
155 let family = if theme.subtitle.family.is_empty() {
156 None
157 } else {
158 Some(theme.subtitle.family.clone())
159 };
160 backend.draw_text(
161 subtitle,
162 (
163 center_x,
164 subtitle_y.max(theme.title.size + theme.subtitle.size),
165 ),
166 &TextStyle {
167 color: theme.subtitle.color,
168 size: theme.subtitle.size,
169 anchor: TextAnchor::Middle,
170 angle: 0.0,
171 family,
172 },
173 )?;
174 }
175
176 legend::draw_legend(
178 &built.scales,
179 theme,
180 &plot_area,
181 backend,
182 &built.guide_legend,
183 &built.suppressed_aes,
184 )?;
185
186 if let Some(ref caption) = built.labels.caption {
188 let right_x = plot_area.x + plot_area.width;
189 let caption_y = total_area.y + total_area.height - theme.caption.size * 0.5;
190 let family = if theme.caption.family.is_empty() {
191 None
192 } else {
193 Some(theme.caption.family.clone())
194 };
195 backend.draw_text(
196 caption,
197 (right_x, caption_y),
198 &TextStyle {
199 color: theme.caption.color,
200 size: theme.caption.size,
201 anchor: TextAnchor::End,
202 angle: 0.0,
203 family,
204 },
205 )?;
206 }
207
208 Ok(())
209 }
210
211 fn render_faceted(built: &BuiltPlot, backend: &mut dyn DrawBackend) -> Result<(), RenderError> {
212 let theme = &built.theme;
213 let plot_area = backend.plot_area();
214 let total_area = backend.total_area();
215
216 if theme.plot_background.visible {
218 if let Some(fill) = theme.plot_background.fill {
219 backend.draw_rect(
220 (total_area.x, total_area.y),
221 (
222 total_area.x + total_area.width,
223 total_area.y + total_area.height,
224 ),
225 &RectStyle {
226 fill: Some(fill),
227 stroke: None,
228 stroke_width: 0.0,
229 alpha: 1.0,
230 clip: false,
231 },
232 )?;
233 }
234 }
235
236 let ncol = match &built.facet {
238 Facet::Wrap { ncol, .. } => {
239 ncol.unwrap_or_else(|| (built.panels.len() as f64).sqrt().ceil() as usize)
240 }
241 Facet::Grid { .. } => built.panels.iter().map(|p| p.col).max().unwrap_or(0) + 1,
242 Facet::None => 1,
243 };
244 let nrow = built.panels.len().div_ceil(ncol);
245
246 let strip_height = theme.strip_text.size + 8.0;
247 let gap_x = theme.get_panel_spacing_x();
248 let gap_y = theme.get_panel_spacing_y();
249 let panel_width = (plot_area.width - gap_x * (ncol as f64 - 1.0)) / ncol as f64;
250 let panel_height =
251 (plot_area.height - gap_y * (nrow as f64 - 1.0) - strip_height * nrow as f64)
252 / nrow as f64;
253
254 for (pi, panel) in built.panels.iter().enumerate() {
255 let px = plot_area.x + panel.col as f64 * (panel_width + gap_x);
256 let py = plot_area.y + panel.row as f64 * (panel_height + strip_height + gap_y);
257
258 let panel_rect = crate::render::Rect {
259 x: px,
260 y: py + strip_height,
261 width: panel_width,
262 height: panel_height,
263 };
264
265 if theme.strip_background.visible {
267 backend.draw_rect(
268 (px, py),
269 (px + panel_width, py + strip_height),
270 &RectStyle {
271 fill: theme.strip_background.fill,
272 stroke: theme.strip_background.color,
273 stroke_width: theme.strip_background.width,
274 alpha: 1.0,
275 clip: false,
276 },
277 )?;
278 }
279
280 if theme.strip_text.visible {
282 let label = panel.col_label.as_deref().unwrap_or(&panel.label);
283 let family = if theme.strip_text.family.is_empty() {
284 None
285 } else {
286 Some(theme.strip_text.family.clone())
287 };
288 backend.draw_text(
289 label,
290 (px + panel_width / 2.0, py + strip_height / 2.0),
291 &TextStyle {
292 color: theme.strip_text.color,
293 size: theme.strip_text.size,
294 anchor: TextAnchor::Middle,
295 angle: 0.0,
296 family,
297 },
298 )?;
299 }
300
301 if theme.panel_background.visible {
303 if let Some(fill) = theme.panel_background.fill {
304 backend.draw_rect(
305 (panel_rect.x, panel_rect.y),
306 (
307 panel_rect.x + panel_rect.width,
308 panel_rect.y + panel_rect.height,
309 ),
310 &RectStyle {
311 fill: Some(fill),
312 stroke: theme.panel_background.color,
313 stroke_width: theme.panel_background.width,
314 alpha: 1.0,
315 clip: false,
316 },
317 )?;
318 }
319 }
320
321 if theme.panel_border.visible {
323 let style = LineStyle {
324 color: theme.panel_border.color,
325 width: theme.panel_border.width,
326 alpha: 1.0,
327 linetype: Linetype::Solid,
328 };
329 let x0 = panel_rect.x;
330 let y0 = panel_rect.y;
331 let x1 = panel_rect.x + panel_rect.width;
332 let y1 = panel_rect.y + panel_rect.height;
333 backend.draw_line(&[(x0, y0), (x1, y0)], &style)?;
334 backend.draw_line(&[(x1, y0), (x1, y1)], &style)?;
335 backend.draw_line(&[(x1, y1), (x0, y1)], &style)?;
336 backend.draw_line(&[(x0, y1), (x0, y0)], &style)?;
337 }
338
339 let panel_scale_set = if pi < built.panel_scales.len() {
341 &built.panel_scales[pi]
342 } else {
343 &built.scales
344 };
345
346 let x_scale = panel_scale_set.get(&Aesthetic::X);
348 let y_scale = panel_scale_set.get(&Aesthetic::Y);
349
350 if let (Some(xs), Some(ys)) = (x_scale, y_scale) {
351 if built.coord.gridlines() {
352 axis::draw_gridlines(
353 xs,
354 ys,
355 built.coord.as_ref(),
356 theme,
357 &panel_rect,
358 backend,
359 )?;
360 }
361
362 if panel.row == nrow - 1 || pi + ncol >= built.panels.len() {
364 axis::draw_x_axis(xs, built.coord.as_ref(), theme, &panel_rect, backend)?;
365 }
366
367 if panel.col == 0 {
369 axis::draw_y_axis(ys, built.coord.as_ref(), theme, &panel_rect, backend)?;
370 }
371 }
372
373 if pi < built.panels_data.len() {
375 for (li, layer_data) in built.panels_data[pi].iter().enumerate() {
376 if li < built.layers.len() && layer_data.nrows() > 0 {
377 let mut panel_backend = PanelBackendAdapter {
378 inner: backend,
379 panel_rect: panel_rect.clone(),
380 };
381 built.layers[li].geom.draw(
382 layer_data,
383 built.coord.as_ref(),
384 panel_scale_set,
385 theme,
386 &mut panel_backend,
387 )?;
388 }
389 }
390 }
391 }
392
393 if let Some(ref title) = built.labels.title {
395 let center_x = plot_area.x + plot_area.width / 2.0;
396 let title_y = plot_area.y - theme.title.size * 0.9;
397 let family = if theme.title.family.is_empty() {
398 None
399 } else {
400 Some(theme.title.family.clone())
401 };
402 backend.draw_text(
403 title,
404 (center_x, title_y.max(theme.title.size)),
405 &TextStyle {
406 color: theme.title.color,
407 size: theme.title.size,
408 anchor: TextAnchor::Middle,
409 angle: 0.0,
410 family,
411 },
412 )?;
413 }
414
415 if let Some(ref subtitle) = built.labels.subtitle {
417 let center_x = plot_area.x + plot_area.width / 2.0;
418 let subtitle_y = plot_area.y - 2.0;
419 let family = if theme.subtitle.family.is_empty() {
420 None
421 } else {
422 Some(theme.subtitle.family.clone())
423 };
424 backend.draw_text(
425 subtitle,
426 (
427 center_x,
428 subtitle_y.max(theme.title.size + theme.subtitle.size),
429 ),
430 &TextStyle {
431 color: theme.subtitle.color,
432 size: theme.subtitle.size,
433 anchor: TextAnchor::Middle,
434 angle: 0.0,
435 family,
436 },
437 )?;
438 }
439
440 if let Some(ref caption) = built.labels.caption {
442 let right_x = plot_area.x + plot_area.width;
443 let caption_y = total_area.y + total_area.height - theme.caption.size * 0.5;
444 let family = if theme.caption.family.is_empty() {
445 None
446 } else {
447 Some(theme.caption.family.clone())
448 };
449 backend.draw_text(
450 caption,
451 (right_x, caption_y),
452 &TextStyle {
453 color: theme.caption.color,
454 size: theme.caption.size,
455 anchor: TextAnchor::End,
456 angle: 0.0,
457 family,
458 },
459 )?;
460 }
461
462 Self::draw_annotations(
464 &built.annotations,
465 &built.scales,
466 built.coord.as_ref(),
467 &plot_area,
468 backend,
469 )?;
470
471 legend::draw_legend(
473 &built.scales,
474 theme,
475 &plot_area,
476 backend,
477 &built.guide_legend,
478 &built.suppressed_aes,
479 )?;
480
481 Ok(())
482 }
483
484 fn draw_annotations(
485 annotations: &[Annotation],
486 scales: &crate::scale::ScaleSet,
487 coord: &dyn crate::coord::Coord,
488 plot_area: &crate::render::Rect,
489 backend: &mut dyn DrawBackend,
490 ) -> Result<(), RenderError> {
491 use crate::data::Value;
492
493 let x_scale = scales.get(&Aesthetic::X);
494 let y_scale = scales.get(&Aesthetic::Y);
495
496 for ann in annotations {
497 match ann {
498 Annotation::Text {
499 label,
500 x,
501 y,
502 size,
503 color,
504 } => {
505 let nx = x_scale.map(|s| s.map(&Value::Float(*x))).unwrap_or(0.0);
506 let ny = y_scale.map(|s| s.map(&Value::Float(*y))).unwrap_or(0.0);
507 let pos = coord.transform((nx, ny), plot_area);
508 backend.draw_text(
509 label,
510 pos,
511 &TextStyle {
512 color: *color,
513 size: *size,
514 anchor: TextAnchor::Middle,
515 angle: 0.0,
516 family: None,
517 },
518 )?;
519 }
520 Annotation::Rect {
521 xmin,
522 xmax,
523 ymin,
524 ymax,
525 fill,
526 alpha,
527 } => {
528 let nx0 = x_scale.map(|s| s.map(&Value::Float(*xmin))).unwrap_or(0.0);
529 let nx1 = x_scale.map(|s| s.map(&Value::Float(*xmax))).unwrap_or(1.0);
530 let ny0 = y_scale.map(|s| s.map(&Value::Float(*ymin))).unwrap_or(0.0);
531 let ny1 = y_scale.map(|s| s.map(&Value::Float(*ymax))).unwrap_or(1.0);
532 let tl = coord.transform((nx0, ny1), plot_area);
533 let br = coord.transform((nx1, ny0), plot_area);
534 backend.draw_rect(
535 tl,
536 br,
537 &RectStyle {
538 fill: Some(*fill),
539 stroke: None,
540 stroke_width: 0.0,
541 alpha: *alpha,
542 clip: false,
543 },
544 )?;
545 }
546 Annotation::Segment {
547 x,
548 y,
549 xend,
550 yend,
551 color,
552 width,
553 } => {
554 let nx0 = x_scale.map(|s| s.map(&Value::Float(*x))).unwrap_or(0.0);
555 let ny0 = y_scale.map(|s| s.map(&Value::Float(*y))).unwrap_or(0.0);
556 let nx1 = x_scale.map(|s| s.map(&Value::Float(*xend))).unwrap_or(1.0);
557 let ny1 = y_scale.map(|s| s.map(&Value::Float(*yend))).unwrap_or(1.0);
558 let p0 = coord.transform((nx0, ny0), plot_area);
559 let p1 = coord.transform((nx1, ny1), plot_area);
560 backend.draw_line(
561 &[p0, p1],
562 &LineStyle {
563 color: *color,
564 alpha: 1.0,
565 width: *width,
566 linetype: Linetype::Solid,
567 },
568 )?;
569 }
570 }
571 }
572 Ok(())
573 }
574}
575
576struct PanelBackendAdapter<'a> {
578 inner: &'a mut dyn DrawBackend,
579 panel_rect: crate::render::Rect,
580}
581
582impl<'a> DrawBackend for PanelBackendAdapter<'a> {
583 fn draw_circle(
584 &mut self,
585 center: (f64, f64),
586 radius: f64,
587 style: &crate::render::backend::PointStyle,
588 ) -> Result<(), RenderError> {
589 self.inner.draw_circle(center, radius, style)
590 }
591 fn draw_line(
592 &mut self,
593 points: &[(f64, f64)],
594 style: &crate::render::backend::LineStyle,
595 ) -> Result<(), RenderError> {
596 self.inner.draw_line(points, style)
597 }
598 fn draw_rect(
599 &mut self,
600 top_left: (f64, f64),
601 bottom_right: (f64, f64),
602 style: &RectStyle,
603 ) -> Result<(), RenderError> {
604 self.inner.draw_rect(top_left, bottom_right, style)
605 }
606 fn draw_text(
607 &mut self,
608 text: &str,
609 pos: (f64, f64),
610 style: &TextStyle,
611 ) -> Result<(), RenderError> {
612 self.inner.draw_text(text, pos, style)
613 }
614 fn draw_polygon(
615 &mut self,
616 points: &[(f64, f64)],
617 style: &RectStyle,
618 ) -> Result<(), RenderError> {
619 self.inner.draw_polygon(points, style)
620 }
621 fn draw_shape(
622 &mut self,
623 center: (f64, f64),
624 radius: f64,
625 style: &crate::render::backend::PointStyle,
626 ) -> Result<(), RenderError> {
627 self.inner.draw_shape(center, radius, style)
628 }
629 fn plot_area(&self) -> crate::render::Rect {
630 self.panel_rect.clone()
631 }
632 fn total_area(&self) -> crate::render::Rect {
633 self.inner.total_area()
634 }
635}