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