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