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