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 ..Default::default()
74 };
75 let x0 = plot_area.x;
76 let y0 = plot_area.y;
77 let x1 = plot_area.x + plot_area.width;
78 let y1 = plot_area.y + plot_area.height;
79 backend.draw_line(&[(x0, y0), (x1, y0)], &style)?;
80 backend.draw_line(&[(x1, y0), (x1, y1)], &style)?;
81 backend.draw_line(&[(x1, y1), (x0, y1)], &style)?;
82 backend.draw_line(&[(x0, y1), (x0, y0)], &style)?;
83 }
84
85 let x_scale = built.scales.get(&Aesthetic::X);
87 let y_scale = built.scales.get(&Aesthetic::Y);
88
89 let (h_scale, v_scale) = if built.coord.is_flipped() {
90 (y_scale, x_scale)
91 } else {
92 (x_scale, y_scale)
93 };
94
95 if let (true, Some(xs), Some(ys)) = (built.coord.is_radar(), x_scale, y_scale) {
96 crate::guide::polar::draw_radar_guides(
98 xs,
99 ys,
100 built.coord.as_ref(),
101 theme,
102 &plot_area,
103 backend,
104 )?;
105 } else if let (Some(hs), Some(vs)) = (h_scale, v_scale) {
106 if built.coord.gridlines() && !theme.panel_ontop {
108 axis::draw_gridlines(hs, vs, built.coord.as_ref(), theme, &plot_area, backend)?;
109 }
110
111 axis::draw_x_axis(hs, built.coord.as_ref(), theme, &plot_area, backend)?;
113 axis::draw_y_axis(vs, built.coord.as_ref(), theme, &plot_area, backend)?;
114
115 if let Some(sec) = built.scales.sec_axis(&Aesthetic::Y) {
117 axis::draw_sec_y_axis(vs, sec, built.coord.as_ref(), theme, &plot_area, backend)?;
118 }
119 }
120
121 for layer in built.layers.iter().filter(|l| l.data.nrows() > 0) {
125 backend.set_mark_geom(Some(layer.geom.name()));
126 let drawn = layer.geom.draw(
127 &layer.data,
128 built.coord.as_ref(),
129 &built.scales,
130 theme,
131 backend,
132 );
133 backend.set_mark_geom(None);
134 drawn?;
135 }
136
137 if theme.panel_ontop && built.coord.gridlines() {
139 if let (Some(hs), Some(vs)) = (h_scale, v_scale) {
140 axis::draw_gridlines(hs, vs, built.coord.as_ref(), theme, &plot_area, backend)?;
141 }
142 }
143
144 Self::draw_annotations(
146 &built.annotations,
147 &built.scales,
148 built.coord.as_ref(),
149 &plot_area,
150 theme,
151 backend,
152 )?;
153
154 let title_ref = Self::title_ref_rect(theme, &plot_area, &total_area);
156 if let Some(ref title) = built.labels.title {
157 let (tx, anchor) = Self::hjust_pos(&theme.title, &title_ref);
158 let x_axis_top = built
161 .scales
162 .get(&Aesthetic::X)
163 .map(|s| s.axis_position_opposite())
164 .unwrap_or(false);
165 let x_axis_lift = if x_axis_top {
166 theme.axis_ticks_length + theme.axis_text_x.size + theme.legend_spacing
167 } else {
168 0.0
169 };
170 let title_y = plot_area.y - theme.title.size * 0.9 - x_axis_lift;
171 let family = if theme.title.family.is_empty() {
172 None
173 } else {
174 Some(theme.title.family.clone())
175 };
176 backend.draw_text(
177 title,
178 (tx, title_y.max(theme.title.size)),
179 &TextStyle {
180 color: theme.title.color,
181 size: theme.title.size,
182 anchor,
183 angle: 0.0,
184 family,
185 face: theme.title.face,
186 },
187 )?;
188 }
189
190 if let Some(ref subtitle) = built.labels.subtitle {
192 let (sx, anchor) = Self::hjust_pos(&theme.subtitle, &title_ref);
193 let subtitle_y = plot_area.y - 2.0;
194 let family = if theme.subtitle.family.is_empty() {
195 None
196 } else {
197 Some(theme.subtitle.family.clone())
198 };
199 backend.draw_text(
200 subtitle,
201 (sx, subtitle_y.max(theme.title.size + theme.subtitle.size)),
202 &TextStyle {
203 color: theme.subtitle.color,
204 size: theme.subtitle.size,
205 anchor,
206 angle: 0.0,
207 family,
208 face: theme.subtitle.face,
209 },
210 )?;
211 }
212
213 legend::draw_legend(
215 &built.scales,
216 theme,
217 &plot_area,
218 backend,
219 &built.guide_legend,
220 &built.suppressed_aes,
221 )?;
222
223 if let Some(ref caption) = built.labels.caption {
225 let (cx, anchor) = Self::hjust_pos(&theme.caption, &title_ref);
226 let caption_y = total_area.y + total_area.height - theme.caption.size * 0.5;
227 let family = if theme.caption.family.is_empty() {
228 None
229 } else {
230 Some(theme.caption.family.clone())
231 };
232 backend.draw_text(
233 caption,
234 (cx, caption_y),
235 &TextStyle {
236 color: theme.caption.color,
237 size: theme.caption.size,
238 anchor,
239 angle: 0.0,
240 family,
241 face: theme.caption.face,
242 },
243 )?;
244 }
245
246 Self::draw_tag(&built.labels, theme, &total_area, backend)?;
248
249 Ok(())
250 }
251
252 fn hjust_pos(
255 el: &crate::theme::elements::ElementText,
256 area: &crate::render::Rect,
257 ) -> (f64, TextAnchor) {
258 let x = area.x + el.hjust.clamp(0.0, 1.0) * area.width;
259 let anchor = if el.hjust <= 0.02 {
260 TextAnchor::Start
261 } else if el.hjust >= 0.98 {
262 TextAnchor::End
263 } else {
264 TextAnchor::Middle
265 };
266 (x, anchor)
267 }
268
269 fn title_ref_rect(
272 theme: &crate::theme::Theme,
273 plot_area: &crate::render::Rect,
274 total_area: &crate::render::Rect,
275 ) -> crate::render::Rect {
276 match theme.title_position {
277 crate::theme::TitlePosition::Panel => plot_area.clone(),
278 crate::theme::TitlePosition::Plot => crate::render::Rect {
279 x: total_area.x + theme.plot_margin.left,
280 y: plot_area.y,
281 width: (total_area.width - theme.plot_margin.left - theme.plot_margin.right)
282 .max(1.0),
283 height: plot_area.height,
284 },
285 }
286 }
287
288 fn draw_tag(
290 labels: &crate::plot::Labels,
291 theme: &crate::theme::Theme,
292 total_area: &crate::render::Rect,
293 backend: &mut dyn DrawBackend,
294 ) -> Result<(), RenderError> {
295 if let Some(ref tag) = labels.tag {
296 let family = if theme.title.family.is_empty() {
297 None
298 } else {
299 Some(theme.title.family.clone())
300 };
301 use crate::theme::TagPosition::*;
302 let pad = theme.title.size;
303 let (left, right) = (total_area.x + pad, total_area.x + total_area.width - pad);
304 let (top, bottom) = (total_area.y + pad, total_area.y + total_area.height - pad);
305 let (x, y, anchor) = match theme.tag_position {
306 TopLeft => (left, top, TextAnchor::Start),
307 TopRight => (right, top, TextAnchor::End),
308 BottomLeft => (left, bottom, TextAnchor::Start),
309 BottomRight => (right, bottom, TextAnchor::End),
310 };
311 backend.draw_text(
312 tag,
313 (x, y),
314 &TextStyle {
315 color: theme.title.color,
316 size: theme.title.size,
317 anchor,
318 angle: 0.0,
319 family,
320 face: theme.title.face,
321 },
322 )?;
323 }
324 Ok(())
325 }
326
327 fn render_faceted(built: &BuiltPlot, backend: &mut dyn DrawBackend) -> Result<(), RenderError> {
328 let theme = &built.theme;
329 let plot_area = backend.plot_area();
330 let total_area = backend.total_area();
331
332 if theme.plot_background.visible {
334 if let Some(fill) = theme.plot_background.fill {
335 backend.draw_rect(
336 (total_area.x, total_area.y),
337 (
338 total_area.x + total_area.width,
339 total_area.y + total_area.height,
340 ),
341 &RectStyle {
342 fill: Some(fill),
343 stroke: None,
344 stroke_width: 0.0,
345 alpha: 1.0,
346 clip: false,
347 },
348 )?;
349 }
350 }
351
352 let ncol = match &built.facet {
354 Facet::Wrap { ncol, .. } => {
355 ncol.unwrap_or_else(|| (built.panels.len() as f64).sqrt().ceil() as usize)
356 }
357 Facet::Grid { .. } => built.panels.iter().map(|p| p.col).max().unwrap_or(0) + 1,
358 Facet::None => 1,
359 };
360 let nrow = built.panels.len().div_ceil(ncol);
361
362 let strip_height = theme.strip_text.size + 8.0;
363 let gap_x = theme.get_panel_spacing_x();
364 let gap_y = theme.get_panel_spacing_y();
365
366 let space = match &built.facet {
369 Facet::Grid { space, .. } => space.clone(),
370 _ => crate::facet::FacetSpace::Fixed,
371 };
372 let extent = |pi: usize, col: &str| -> Option<f64> {
373 let (mut lo, mut hi) = (f64::INFINITY, f64::NEG_INFINITY);
374 for df in &built.panels_data[pi] {
375 if let Some(c) = df.column(col) {
376 for v in c {
377 if let Some(f) = v.as_f64() {
378 lo = lo.min(f);
379 hi = hi.max(f);
380 }
381 }
382 }
383 }
384 (lo <= hi).then_some(hi - lo)
385 };
386 let avail_w = plot_area.width - gap_x * (ncol as f64 - 1.0);
387 let col_widths: Vec<f64> = if space.free_x() && ncol > 0 {
388 let mut ranges = vec![0.0f64; ncol];
389 for (pi, panel) in built.panels.iter().enumerate() {
390 if let Some(r) = extent(pi, "x") {
391 ranges[panel.col] = ranges[panel.col].max(r);
392 }
393 }
394 let total: f64 = ranges.iter().sum();
395 if total > 0.0 {
396 ranges.iter().map(|r| avail_w * (r / total)).collect()
397 } else {
398 vec![avail_w / ncol as f64; ncol]
399 }
400 } else {
401 vec![avail_w / ncol.max(1) as f64; ncol.max(1)]
402 };
403 let mut col_x = vec![plot_area.x; ncol.max(1)];
404 {
405 let mut acc = plot_area.x;
406 for c in 0..ncol {
407 col_x[c] = acc;
408 acc += col_widths[c] + gap_x;
409 }
410 }
411
412 let avail_h = plot_area.height - gap_y * (nrow as f64 - 1.0) - strip_height * nrow as f64;
413 let row_heights: Vec<f64> = if space.free_y() && nrow > 0 {
414 let mut ranges = vec![0.0f64; nrow];
415 for (pi, panel) in built.panels.iter().enumerate() {
416 if let Some(r) = extent(pi, "y") {
417 ranges[panel.row] = ranges[panel.row].max(r);
418 }
419 }
420 let total: f64 = ranges.iter().sum();
421 if total > 0.0 {
422 ranges.iter().map(|r| avail_h * (r / total)).collect()
423 } else {
424 vec![avail_h / nrow as f64; nrow]
425 }
426 } else {
427 vec![avail_h / nrow.max(1) as f64; nrow.max(1)]
428 };
429 let mut row_y = vec![plot_area.y; nrow.max(1)];
430 {
431 let mut acc = plot_area.y;
432 for r in 0..nrow {
433 row_y[r] = acc;
434 acc += row_heights[r] + strip_height + gap_y;
435 }
436 }
437
438 for (pi, panel) in built.panels.iter().enumerate() {
439 let panel_width = col_widths[panel.col];
440 let panel_height = row_heights[panel.row];
441 let px = col_x[panel.col];
442 let py = row_y[panel.row];
443
444 let panel_rect = crate::render::Rect {
445 x: px,
446 y: py + strip_height,
447 width: panel_width,
448 height: panel_height,
449 };
450
451 if theme.strip_background.visible {
453 backend.draw_rect(
454 (px, py),
455 (px + panel_width, py + strip_height),
456 &RectStyle {
457 fill: theme.strip_background.fill,
458 stroke: theme.strip_background.color,
459 stroke_width: theme.strip_background.width,
460 alpha: 1.0,
461 clip: false,
462 },
463 )?;
464 }
465
466 if theme.strip_text.visible {
468 let label = panel.col_label.as_deref().unwrap_or(&panel.label);
469 let family = if theme.strip_text.family.is_empty() {
470 None
471 } else {
472 Some(theme.strip_text.family.clone())
473 };
474 backend.draw_text(
475 label,
476 (px + panel_width / 2.0, py + strip_height / 2.0),
477 &TextStyle {
478 color: theme.strip_text.color,
479 size: theme.strip_text.size,
480 anchor: TextAnchor::Middle,
481 angle: 0.0,
482 family,
483 face: theme.strip_text.face,
484 },
485 )?;
486 }
487
488 if theme.panel_background.visible {
490 if let Some(fill) = theme.panel_background.fill {
491 backend.draw_rect(
492 (panel_rect.x, panel_rect.y),
493 (
494 panel_rect.x + panel_rect.width,
495 panel_rect.y + panel_rect.height,
496 ),
497 &RectStyle {
498 fill: Some(fill),
499 stroke: theme.panel_background.color,
500 stroke_width: theme.panel_background.width,
501 alpha: 1.0,
502 clip: false,
503 },
504 )?;
505 }
506 }
507
508 if theme.panel_border.visible {
510 let style = LineStyle {
511 color: theme.panel_border.color,
512 width: theme.panel_border.width,
513 alpha: 1.0,
514 linetype: Linetype::Solid,
515 ..Default::default()
516 };
517 let x0 = panel_rect.x;
518 let y0 = panel_rect.y;
519 let x1 = panel_rect.x + panel_rect.width;
520 let y1 = panel_rect.y + panel_rect.height;
521 backend.draw_line(&[(x0, y0), (x1, y0)], &style)?;
522 backend.draw_line(&[(x1, y0), (x1, y1)], &style)?;
523 backend.draw_line(&[(x1, y1), (x0, y1)], &style)?;
524 backend.draw_line(&[(x0, y1), (x0, y0)], &style)?;
525 }
526
527 let panel_scale_set = if pi < built.panel_scales.len() {
529 &built.panel_scales[pi]
530 } else {
531 &built.scales
532 };
533
534 let x_scale = panel_scale_set.get(&Aesthetic::X);
536 let y_scale = panel_scale_set.get(&Aesthetic::Y);
537
538 if let (Some(xs), Some(ys)) = (x_scale, y_scale) {
539 if built.coord.gridlines() {
540 axis::draw_gridlines(
541 xs,
542 ys,
543 built.coord.as_ref(),
544 theme,
545 &panel_rect,
546 backend,
547 )?;
548 }
549
550 if panel.row == nrow - 1 || pi + ncol >= built.panels.len() {
552 axis::draw_x_axis(xs, built.coord.as_ref(), theme, &panel_rect, backend)?;
553 }
554
555 if panel.col == 0 {
557 axis::draw_y_axis(ys, built.coord.as_ref(), theme, &panel_rect, backend)?;
558 }
559 }
560
561 if pi < built.panels_data.len() {
563 for (li, layer_data) in built.panels_data[pi].iter().enumerate() {
564 if li < built.layers.len() && layer_data.nrows() > 0 {
565 let mut panel_backend = PanelBackendAdapter {
566 inner: backend,
567 panel_rect: panel_rect.clone(),
568 };
569 let geom = &built.layers[li].geom;
570 panel_backend.set_mark_geom(Some(geom.name()));
571 let drawn = geom.draw(
572 layer_data,
573 built.coord.as_ref(),
574 panel_scale_set,
575 theme,
576 &mut panel_backend,
577 );
578 panel_backend.set_mark_geom(None);
579 drawn?;
580 }
581 }
582 }
583 }
584
585 let title_ref = Self::title_ref_rect(theme, &plot_area, &total_area);
587 if let Some(ref title) = built.labels.title {
588 let (tx, anchor) = Self::hjust_pos(&theme.title, &title_ref);
589 let title_y = plot_area.y - theme.title.size * 0.9;
590 let family = if theme.title.family.is_empty() {
591 None
592 } else {
593 Some(theme.title.family.clone())
594 };
595 backend.draw_text(
596 title,
597 (tx, title_y.max(theme.title.size)),
598 &TextStyle {
599 color: theme.title.color,
600 size: theme.title.size,
601 anchor,
602 angle: 0.0,
603 family,
604 face: theme.title.face,
605 },
606 )?;
607 }
608
609 if let Some(ref subtitle) = built.labels.subtitle {
611 let (sx, anchor) = Self::hjust_pos(&theme.subtitle, &title_ref);
612 let subtitle_y = plot_area.y - 2.0;
613 let family = if theme.subtitle.family.is_empty() {
614 None
615 } else {
616 Some(theme.subtitle.family.clone())
617 };
618 backend.draw_text(
619 subtitle,
620 (sx, subtitle_y.max(theme.title.size + theme.subtitle.size)),
621 &TextStyle {
622 color: theme.subtitle.color,
623 size: theme.subtitle.size,
624 anchor,
625 angle: 0.0,
626 family,
627 face: theme.subtitle.face,
628 },
629 )?;
630 }
631
632 if let Some(ref caption) = built.labels.caption {
634 let (cx, anchor) = Self::hjust_pos(&theme.caption, &title_ref);
635 let caption_y = total_area.y + total_area.height - theme.caption.size * 0.5;
636 let family = if theme.caption.family.is_empty() {
637 None
638 } else {
639 Some(theme.caption.family.clone())
640 };
641 backend.draw_text(
642 caption,
643 (cx, caption_y),
644 &TextStyle {
645 color: theme.caption.color,
646 size: theme.caption.size,
647 anchor,
648 angle: 0.0,
649 family,
650 face: theme.caption.face,
651 },
652 )?;
653 }
654
655 Self::draw_annotations(
657 &built.annotations,
658 &built.scales,
659 built.coord.as_ref(),
660 &plot_area,
661 theme,
662 backend,
663 )?;
664
665 legend::draw_legend(
667 &built.scales,
668 theme,
669 &plot_area,
670 backend,
671 &built.guide_legend,
672 &built.suppressed_aes,
673 )?;
674
675 Self::draw_tag(&built.labels, theme, &total_area, backend)?;
677
678 Ok(())
679 }
680
681 fn draw_annotations(
682 annotations: &[Annotation],
683 scales: &crate::scale::ScaleSet,
684 coord: &dyn crate::coord::Coord,
685 plot_area: &crate::render::Rect,
686 theme: &crate::theme::Theme,
687 backend: &mut dyn DrawBackend,
688 ) -> Result<(), RenderError> {
689 use crate::data::Value;
690
691 let x_scale = scales.get(&Aesthetic::X);
692 let y_scale = scales.get(&Aesthetic::Y);
693
694 for ann in annotations {
695 match ann {
696 Annotation::Text {
697 label,
698 x,
699 y,
700 size,
701 color,
702 } => {
703 let nx = x_scale.map(|s| s.map(&Value::Float(*x))).unwrap_or(0.0);
704 let ny = y_scale.map(|s| s.map(&Value::Float(*y))).unwrap_or(0.0);
705 let pos = coord.transform((nx, ny), plot_area);
706 backend.draw_text(
707 label,
708 pos,
709 &TextStyle {
710 color: *color,
711 size: *size,
712 anchor: TextAnchor::Middle,
713 angle: 0.0,
714 family: crate::geom::resolve_font_family("", theme),
715 face: crate::render::backend::FontFace::Plain,
716 },
717 )?;
718 }
719 Annotation::Rect {
720 xmin,
721 xmax,
722 ymin,
723 ymax,
724 fill,
725 alpha,
726 } => {
727 let nx0 = x_scale.map(|s| s.map(&Value::Float(*xmin))).unwrap_or(0.0);
728 let nx1 = x_scale.map(|s| s.map(&Value::Float(*xmax))).unwrap_or(1.0);
729 let ny0 = y_scale.map(|s| s.map(&Value::Float(*ymin))).unwrap_or(0.0);
730 let ny1 = y_scale.map(|s| s.map(&Value::Float(*ymax))).unwrap_or(1.0);
731 let tl = coord.transform((nx0, ny1), plot_area);
732 let br = coord.transform((nx1, ny0), plot_area);
733 backend.draw_rect(
734 tl,
735 br,
736 &RectStyle {
737 fill: Some(*fill),
738 stroke: None,
739 stroke_width: 0.0,
740 alpha: *alpha,
741 clip: false,
742 },
743 )?;
744 }
745 Annotation::Segment {
746 x,
747 y,
748 xend,
749 yend,
750 color,
751 width,
752 } => {
753 let nx0 = x_scale.map(|s| s.map(&Value::Float(*x))).unwrap_or(0.0);
754 let ny0 = y_scale.map(|s| s.map(&Value::Float(*y))).unwrap_or(0.0);
755 let nx1 = x_scale.map(|s| s.map(&Value::Float(*xend))).unwrap_or(1.0);
756 let ny1 = y_scale.map(|s| s.map(&Value::Float(*yend))).unwrap_or(1.0);
757 let p0 = coord.transform((nx0, ny0), plot_area);
758 let p1 = coord.transform((nx1, ny1), plot_area);
759 backend.draw_line(
760 &[p0, p1],
761 &LineStyle {
762 color: *color,
763 alpha: 1.0,
764 width: *width,
765 linetype: Linetype::Solid,
766 ..Default::default()
767 },
768 )?;
769 }
770 }
771 }
772 Ok(())
773 }
774}
775
776struct PanelBackendAdapter<'a> {
778 inner: &'a mut dyn DrawBackend,
779 panel_rect: crate::render::Rect,
780}
781
782impl<'a> DrawBackend for PanelBackendAdapter<'a> {
783 fn draw_circle(
784 &mut self,
785 center: (f64, f64),
786 radius: f64,
787 style: &crate::render::backend::PointStyle,
788 ) -> Result<(), RenderError> {
789 self.inner.draw_circle(center, radius, style)
790 }
791 fn draw_line(
792 &mut self,
793 points: &[(f64, f64)],
794 style: &crate::render::backend::LineStyle,
795 ) -> Result<(), RenderError> {
796 self.inner.draw_line(points, style)
797 }
798 fn draw_quadratic(
799 &mut self,
800 p0: (f64, f64),
801 ctrl: (f64, f64),
802 p1: (f64, f64),
803 style: &crate::render::backend::LineStyle,
804 ) -> Result<(), RenderError> {
805 self.inner.draw_quadratic(p0, ctrl, p1, style)
806 }
807 fn draw_rect(
808 &mut self,
809 top_left: (f64, f64),
810 bottom_right: (f64, f64),
811 style: &RectStyle,
812 ) -> Result<(), RenderError> {
813 self.inner.draw_rect(top_left, bottom_right, style)
814 }
815 fn draw_text(
816 &mut self,
817 text: &str,
818 pos: (f64, f64),
819 style: &TextStyle,
820 ) -> Result<(), RenderError> {
821 self.inner.draw_text(text, pos, style)
822 }
823 fn draw_polygon(
824 &mut self,
825 points: &[(f64, f64)],
826 style: &RectStyle,
827 ) -> Result<(), RenderError> {
828 self.inner.draw_polygon(points, style)
829 }
830 fn draw_shape(
831 &mut self,
832 center: (f64, f64),
833 radius: f64,
834 style: &crate::render::backend::PointStyle,
835 ) -> Result<(), RenderError> {
836 self.inner.draw_shape(center, radius, style)
837 }
838 fn plot_area(&self) -> crate::render::Rect {
839 self.panel_rect.clone()
840 }
841 fn set_tooltip(&mut self, tooltip: Option<String>) {
842 self.inner.set_tooltip(tooltip)
843 }
844 fn set_mark_axis(&mut self, key: Option<String>) {
845 self.inner.set_mark_axis(key)
846 }
847 fn set_mark_series(&mut self, series: Option<String>) {
848 self.inner.set_mark_series(series)
849 }
850 fn set_mark_value(&mut self, value: Option<String>) {
851 self.inner.set_mark_value(value)
852 }
853 fn set_mark_geom(&mut self, geom: Option<&str>) {
854 self.inner.set_mark_geom(geom)
855 }
856 fn warn(&mut self, message: String) {
857 self.inner.warn(message)
858 }
859 fn set_mark_part(&mut self, part: Option<&str>) {
860 self.inner.set_mark_part(part)
861 }
862 fn set_mark_attrs(&mut self, attrs: Vec<(String, String)>) {
863 self.inner.set_mark_attrs(attrs)
864 }
865 fn wants_formatted_values(&self) -> bool {
866 self.inner.wants_formatted_values()
867 }
868 fn set_mark_fvalue(&mut self, value: Option<String>) {
869 self.inner.set_mark_fvalue(value)
870 }
871 fn set_mark_fx(&mut self, value: Option<String>) {
872 self.inner.set_mark_fx(value)
873 }
874 fn total_area(&self) -> crate::render::Rect {
875 self.inner.total_area()
876 }
877}
878
879#[cfg(test)]
880mod tests {
881 use super::*;
882 use crate::render::backend::LineStyle;
883 use crate::render::svg_backend::SvgBackend;
884
885 #[test]
891 fn panel_backend_adapter_forwards_quadratic_and_mark_metadata() {
892 let total = crate::render::Rect {
893 x: 0.0,
894 y: 0.0,
895 width: 200.0,
896 height: 200.0,
897 };
898 let panel_rect = crate::render::Rect {
899 x: 10.0,
900 y: 10.0,
901 width: 80.0,
902 height: 80.0,
903 };
904 let mut backend = SvgBackend::new(200, 200, total);
905 let mut adapter = PanelBackendAdapter {
906 inner: &mut backend,
907 panel_rect: panel_rect.clone(),
908 };
909
910 assert_eq!(adapter.plot_area().x, panel_rect.x);
911 assert_eq!(adapter.plot_area().width, panel_rect.width);
912
913 adapter.set_mark_part(Some("arrow"));
914 adapter.set_mark_attrs(vec![("data-x".to_string(), "v".to_string())]);
915 adapter
916 .draw_quadratic((0.0, 0.0), (5.0, 10.0), (10.0, 0.0), &LineStyle::default())
917 .unwrap();
918 adapter.set_mark_part(None);
919 adapter.set_mark_attrs(Vec::new());
920
921 let svg = backend.finish();
922 assert!(svg.contains("<path"));
923 assert!(svg.contains("data-part=\"arrow\""));
924 assert!(svg.contains("data-x=\"v\""));
925 }
926}