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