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