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