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