Skip to main content

ggplot_rs/render/
renderer.rs

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
9/// Orchestrates rendering of a built plot.
10pub 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        // 1. Draw plot background
27        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        // 2. Draw panel background
47        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        // 2b. Draw panel border
67        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            };
74            let x0 = plot_area.x;
75            let y0 = plot_area.y;
76            let x1 = plot_area.x + plot_area.width;
77            let y1 = plot_area.y + plot_area.height;
78            backend.draw_line(&[(x0, y0), (x1, y0)], &style)?;
79            backend.draw_line(&[(x1, y0), (x1, y1)], &style)?;
80            backend.draw_line(&[(x1, y1), (x0, y1)], &style)?;
81            backend.draw_line(&[(x0, y1), (x0, y0)], &style)?;
82        }
83
84        // 3. Draw gridlines
85        let x_scale = built.scales.get(&Aesthetic::X);
86        let y_scale = built.scales.get(&Aesthetic::Y);
87
88        let (h_scale, v_scale) = if built.coord.is_flipped() {
89            (y_scale, x_scale)
90        } else {
91            (x_scale, y_scale)
92        };
93
94        if let (true, Some(xs), Some(ys)) = (built.coord.is_radar(), x_scale, y_scale) {
95            // Radar: rings + spokes + labels replace cartesian grid/axes.
96            crate::guide::polar::draw_radar_guides(
97                xs,
98                ys,
99                built.coord.as_ref(),
100                theme,
101                &plot_area,
102                backend,
103            )?;
104        } else if let (Some(hs), Some(vs)) = (h_scale, v_scale) {
105            // Gridlines under the data, unless panel.ontop draws them over it.
106            if built.coord.gridlines() && !theme.panel_ontop {
107                axis::draw_gridlines(hs, vs, built.coord.as_ref(), theme, &plot_area, backend)?;
108            }
109
110            // 4. Draw axes
111            axis::draw_x_axis(hs, built.coord.as_ref(), theme, &plot_area, backend)?;
112            axis::draw_y_axis(vs, built.coord.as_ref(), theme, &plot_area, backend)?;
113
114            // 4b. Draw secondary y axis if present
115            if let Some(sec) = built.scales.sec_axis(&Aesthetic::Y) {
116                axis::draw_sec_y_axis(vs, sec, built.coord.as_ref(), theme, &plot_area, backend)?;
117            }
118        }
119
120        // 5. Draw each layer's geometry
121        // A layer whose data is empty (no input rows, or a stat that produced
122        // nothing) simply draws nothing.
123        for layer in built.layers.iter().filter(|l| l.data.nrows() > 0) {
124            layer.geom.draw(
125                &layer.data,
126                built.coord.as_ref(),
127                &built.scales,
128                theme,
129                backend,
130            )?;
131        }
132
133        // 5b. Gridlines on top of the data (R's panel.ontop).
134        if theme.panel_ontop && built.coord.gridlines() {
135            if let (Some(hs), Some(vs)) = (h_scale, v_scale) {
136                axis::draw_gridlines(hs, vs, built.coord.as_ref(), theme, &plot_area, backend)?;
137            }
138        }
139
140        // 6. Draw annotations
141        Self::draw_annotations(
142            &built.annotations,
143            &built.scales,
144            built.coord.as_ref(),
145            &plot_area,
146            backend,
147        )?;
148
149        // 7. Draw title
150        let title_ref = Self::title_ref_rect(theme, &plot_area, &total_area);
151        if let Some(ref title) = built.labels.title {
152            let (tx, anchor) = Self::hjust_pos(&theme.title, &title_ref);
153            // A top x-axis occupies the space just above the panel — lift the
154            // title above it so they don't overlap.
155            let x_axis_top = built
156                .scales
157                .get(&Aesthetic::X)
158                .map(|s| s.axis_position_opposite())
159                .unwrap_or(false);
160            let x_axis_lift = if x_axis_top {
161                theme.axis_ticks_length + theme.axis_text_x.size + theme.legend_spacing
162            } else {
163                0.0
164            };
165            let title_y = plot_area.y - theme.title.size * 0.9 - x_axis_lift;
166            let family = if theme.title.family.is_empty() {
167                None
168            } else {
169                Some(theme.title.family.clone())
170            };
171            backend.draw_text(
172                title,
173                (tx, title_y.max(theme.title.size)),
174                &TextStyle {
175                    color: theme.title.color,
176                    size: theme.title.size,
177                    anchor,
178                    angle: 0.0,
179                    family,
180                    face: theme.title.face,
181                },
182            )?;
183        }
184
185        // 8. Draw subtitle
186        if let Some(ref subtitle) = built.labels.subtitle {
187            let (sx, anchor) = Self::hjust_pos(&theme.subtitle, &title_ref);
188            let subtitle_y = plot_area.y - 2.0;
189            let family = if theme.subtitle.family.is_empty() {
190                None
191            } else {
192                Some(theme.subtitle.family.clone())
193            };
194            backend.draw_text(
195                subtitle,
196                (sx, subtitle_y.max(theme.title.size + theme.subtitle.size)),
197                &TextStyle {
198                    color: theme.subtitle.color,
199                    size: theme.subtitle.size,
200                    anchor,
201                    angle: 0.0,
202                    family,
203                    face: theme.subtitle.face,
204                },
205            )?;
206        }
207
208        // 9. Draw legend
209        legend::draw_legend(
210            &built.scales,
211            theme,
212            &plot_area,
213            backend,
214            &built.guide_legend,
215            &built.suppressed_aes,
216        )?;
217
218        // 10. Draw caption
219        if let Some(ref caption) = built.labels.caption {
220            let (cx, anchor) = Self::hjust_pos(&theme.caption, &title_ref);
221            let caption_y = total_area.y + total_area.height - theme.caption.size * 0.5;
222            let family = if theme.caption.family.is_empty() {
223                None
224            } else {
225                Some(theme.caption.family.clone())
226            };
227            backend.draw_text(
228                caption,
229                (cx, caption_y),
230                &TextStyle {
231                    color: theme.caption.color,
232                    size: theme.caption.size,
233                    anchor,
234                    angle: 0.0,
235                    family,
236                    face: theme.caption.face,
237                },
238            )?;
239        }
240
241        // 11. Draw corner tag (labs(tag = ...))
242        Self::draw_tag(&built.labels, theme, &total_area, backend)?;
243
244        Ok(())
245    }
246
247    /// Horizontal position + anchor for a theme text element, from its `hjust`
248    /// (0 = left, 0.5 = center, 1 = right), relative to `area`.
249    fn hjust_pos(
250        el: &crate::theme::elements::ElementText,
251        area: &crate::render::Rect,
252    ) -> (f64, TextAnchor) {
253        let x = area.x + el.hjust.clamp(0.0, 1.0) * area.width;
254        let anchor = if el.hjust <= 0.02 {
255            TextAnchor::Start
256        } else if el.hjust >= 0.98 {
257            TextAnchor::End
258        } else {
259            TextAnchor::Middle
260        };
261        (x, anchor)
262    }
263
264    /// Horizontal reference rect for titles: the panel or the whole plot width
265    /// (R's `plot.title.position`).
266    fn title_ref_rect(
267        theme: &crate::theme::Theme,
268        plot_area: &crate::render::Rect,
269        total_area: &crate::render::Rect,
270    ) -> crate::render::Rect {
271        match theme.title_position {
272            crate::theme::TitlePosition::Panel => plot_area.clone(),
273            crate::theme::TitlePosition::Plot => crate::render::Rect {
274                x: total_area.x + theme.plot_margin.left,
275                y: plot_area.y,
276                width: (total_area.width - theme.plot_margin.left - theme.plot_margin.right)
277                    .max(1.0),
278                height: plot_area.height,
279            },
280        }
281    }
282
283    /// Draw the corner tag label (position from `plot.tag.position`).
284    fn draw_tag(
285        labels: &crate::plot::Labels,
286        theme: &crate::theme::Theme,
287        total_area: &crate::render::Rect,
288        backend: &mut dyn DrawBackend,
289    ) -> Result<(), RenderError> {
290        if let Some(ref tag) = labels.tag {
291            let family = if theme.title.family.is_empty() {
292                None
293            } else {
294                Some(theme.title.family.clone())
295            };
296            use crate::theme::TagPosition::*;
297            let pad = theme.title.size;
298            let (left, right) = (total_area.x + pad, total_area.x + total_area.width - pad);
299            let (top, bottom) = (total_area.y + pad, total_area.y + total_area.height - pad);
300            let (x, y, anchor) = match theme.tag_position {
301                TopLeft => (left, top, TextAnchor::Start),
302                TopRight => (right, top, TextAnchor::End),
303                BottomLeft => (left, bottom, TextAnchor::Start),
304                BottomRight => (right, bottom, TextAnchor::End),
305            };
306            backend.draw_text(
307                tag,
308                (x, y),
309                &TextStyle {
310                    color: theme.title.color,
311                    size: theme.title.size,
312                    anchor,
313                    angle: 0.0,
314                    family,
315                    face: theme.title.face,
316                },
317            )?;
318        }
319        Ok(())
320    }
321
322    fn render_faceted(built: &BuiltPlot, backend: &mut dyn DrawBackend) -> Result<(), RenderError> {
323        let theme = &built.theme;
324        let plot_area = backend.plot_area();
325        let total_area = backend.total_area();
326
327        // Draw plot background
328        if theme.plot_background.visible {
329            if let Some(fill) = theme.plot_background.fill {
330                backend.draw_rect(
331                    (total_area.x, total_area.y),
332                    (
333                        total_area.x + total_area.width,
334                        total_area.y + total_area.height,
335                    ),
336                    &RectStyle {
337                        fill: Some(fill),
338                        stroke: None,
339                        stroke_width: 0.0,
340                        alpha: 1.0,
341                        clip: false,
342                    },
343                )?;
344            }
345        }
346
347        // Compute panel grid dimensions
348        let ncol = match &built.facet {
349            Facet::Wrap { ncol, .. } => {
350                ncol.unwrap_or_else(|| (built.panels.len() as f64).sqrt().ceil() as usize)
351            }
352            Facet::Grid { .. } => built.panels.iter().map(|p| p.col).max().unwrap_or(0) + 1,
353            Facet::None => 1,
354        };
355        let nrow = built.panels.len().div_ceil(ncol);
356
357        let strip_height = theme.strip_text.size + 8.0;
358        let gap_x = theme.get_panel_spacing_x();
359        let gap_y = theme.get_panel_spacing_y();
360
361        // Proportional panel sizing (R's `space =`): free_x sizes columns to
362        // their data x-range, free_y sizes rows to their y-range. Fixed = equal.
363        let space = match &built.facet {
364            Facet::Grid { space, .. } => space.clone(),
365            _ => crate::facet::FacetSpace::Fixed,
366        };
367        let extent = |pi: usize, col: &str| -> Option<f64> {
368            let (mut lo, mut hi) = (f64::INFINITY, f64::NEG_INFINITY);
369            for df in &built.panels_data[pi] {
370                if let Some(c) = df.column(col) {
371                    for v in c {
372                        if let Some(f) = v.as_f64() {
373                            lo = lo.min(f);
374                            hi = hi.max(f);
375                        }
376                    }
377                }
378            }
379            (lo <= hi).then_some(hi - lo)
380        };
381        let avail_w = plot_area.width - gap_x * (ncol as f64 - 1.0);
382        let col_widths: Vec<f64> = if space.free_x() && ncol > 0 {
383            let mut ranges = vec![0.0f64; ncol];
384            for (pi, panel) in built.panels.iter().enumerate() {
385                if let Some(r) = extent(pi, "x") {
386                    ranges[panel.col] = ranges[panel.col].max(r);
387                }
388            }
389            let total: f64 = ranges.iter().sum();
390            if total > 0.0 {
391                ranges.iter().map(|r| avail_w * (r / total)).collect()
392            } else {
393                vec![avail_w / ncol as f64; ncol]
394            }
395        } else {
396            vec![avail_w / ncol.max(1) as f64; ncol.max(1)]
397        };
398        let mut col_x = vec![plot_area.x; ncol.max(1)];
399        {
400            let mut acc = plot_area.x;
401            for c in 0..ncol {
402                col_x[c] = acc;
403                acc += col_widths[c] + gap_x;
404            }
405        }
406
407        let avail_h = plot_area.height - gap_y * (nrow as f64 - 1.0) - strip_height * nrow as f64;
408        let row_heights: Vec<f64> = if space.free_y() && nrow > 0 {
409            let mut ranges = vec![0.0f64; nrow];
410            for (pi, panel) in built.panels.iter().enumerate() {
411                if let Some(r) = extent(pi, "y") {
412                    ranges[panel.row] = ranges[panel.row].max(r);
413                }
414            }
415            let total: f64 = ranges.iter().sum();
416            if total > 0.0 {
417                ranges.iter().map(|r| avail_h * (r / total)).collect()
418            } else {
419                vec![avail_h / nrow as f64; nrow]
420            }
421        } else {
422            vec![avail_h / nrow.max(1) as f64; nrow.max(1)]
423        };
424        let mut row_y = vec![plot_area.y; nrow.max(1)];
425        {
426            let mut acc = plot_area.y;
427            for r in 0..nrow {
428                row_y[r] = acc;
429                acc += row_heights[r] + strip_height + gap_y;
430            }
431        }
432
433        for (pi, panel) in built.panels.iter().enumerate() {
434            let panel_width = col_widths[panel.col];
435            let panel_height = row_heights[panel.row];
436            let px = col_x[panel.col];
437            let py = row_y[panel.row];
438
439            let panel_rect = crate::render::Rect {
440                x: px,
441                y: py + strip_height,
442                width: panel_width,
443                height: panel_height,
444            };
445
446            // Strip label background
447            if theme.strip_background.visible {
448                backend.draw_rect(
449                    (px, py),
450                    (px + panel_width, py + strip_height),
451                    &RectStyle {
452                        fill: theme.strip_background.fill,
453                        stroke: theme.strip_background.color,
454                        stroke_width: theme.strip_background.width,
455                        alpha: 1.0,
456                        clip: false,
457                    },
458                )?;
459            }
460
461            // Strip label text
462            if theme.strip_text.visible {
463                let label = panel.col_label.as_deref().unwrap_or(&panel.label);
464                let family = if theme.strip_text.family.is_empty() {
465                    None
466                } else {
467                    Some(theme.strip_text.family.clone())
468                };
469                backend.draw_text(
470                    label,
471                    (px + panel_width / 2.0, py + strip_height / 2.0),
472                    &TextStyle {
473                        color: theme.strip_text.color,
474                        size: theme.strip_text.size,
475                        anchor: TextAnchor::Middle,
476                        angle: 0.0,
477                        family,
478                        face: theme.strip_text.face,
479                    },
480                )?;
481            }
482
483            // Panel background
484            if theme.panel_background.visible {
485                if let Some(fill) = theme.panel_background.fill {
486                    backend.draw_rect(
487                        (panel_rect.x, panel_rect.y),
488                        (
489                            panel_rect.x + panel_rect.width,
490                            panel_rect.y + panel_rect.height,
491                        ),
492                        &RectStyle {
493                            fill: Some(fill),
494                            stroke: theme.panel_background.color,
495                            stroke_width: theme.panel_background.width,
496                            alpha: 1.0,
497                            clip: false,
498                        },
499                    )?;
500                }
501            }
502
503            // Panel border
504            if theme.panel_border.visible {
505                let style = LineStyle {
506                    color: theme.panel_border.color,
507                    width: theme.panel_border.width,
508                    alpha: 1.0,
509                    linetype: Linetype::Solid,
510                };
511                let x0 = panel_rect.x;
512                let y0 = panel_rect.y;
513                let x1 = panel_rect.x + panel_rect.width;
514                let y1 = panel_rect.y + panel_rect.height;
515                backend.draw_line(&[(x0, y0), (x1, y0)], &style)?;
516                backend.draw_line(&[(x1, y0), (x1, y1)], &style)?;
517                backend.draw_line(&[(x1, y1), (x0, y1)], &style)?;
518                backend.draw_line(&[(x0, y1), (x0, y0)], &style)?;
519            }
520
521            // Use per-panel scales if free facets, otherwise global scales
522            let panel_scale_set = if pi < built.panel_scales.len() {
523                &built.panel_scales[pi]
524            } else {
525                &built.scales
526            };
527
528            // Gridlines + axes for edge panels
529            let x_scale = panel_scale_set.get(&Aesthetic::X);
530            let y_scale = panel_scale_set.get(&Aesthetic::Y);
531
532            if let (Some(xs), Some(ys)) = (x_scale, y_scale) {
533                if built.coord.gridlines() {
534                    axis::draw_gridlines(
535                        xs,
536                        ys,
537                        built.coord.as_ref(),
538                        theme,
539                        &panel_rect,
540                        backend,
541                    )?;
542                }
543
544                // Bottom row gets x axis
545                if panel.row == nrow - 1 || pi + ncol >= built.panels.len() {
546                    axis::draw_x_axis(xs, built.coord.as_ref(), theme, &panel_rect, backend)?;
547                }
548
549                // Left column gets y axis
550                if panel.col == 0 {
551                    axis::draw_y_axis(ys, built.coord.as_ref(), theme, &panel_rect, backend)?;
552                }
553            }
554
555            // Draw layers for this panel
556            if pi < built.panels_data.len() {
557                for (li, layer_data) in built.panels_data[pi].iter().enumerate() {
558                    if li < built.layers.len() && layer_data.nrows() > 0 {
559                        let mut panel_backend = PanelBackendAdapter {
560                            inner: backend,
561                            panel_rect: panel_rect.clone(),
562                        };
563                        built.layers[li].geom.draw(
564                            layer_data,
565                            built.coord.as_ref(),
566                            panel_scale_set,
567                            theme,
568                            &mut panel_backend,
569                        )?;
570                    }
571                }
572            }
573        }
574
575        // Draw title
576        let title_ref = Self::title_ref_rect(theme, &plot_area, &total_area);
577        if let Some(ref title) = built.labels.title {
578            let (tx, anchor) = Self::hjust_pos(&theme.title, &title_ref);
579            let title_y = plot_area.y - theme.title.size * 0.9;
580            let family = if theme.title.family.is_empty() {
581                None
582            } else {
583                Some(theme.title.family.clone())
584            };
585            backend.draw_text(
586                title,
587                (tx, title_y.max(theme.title.size)),
588                &TextStyle {
589                    color: theme.title.color,
590                    size: theme.title.size,
591                    anchor,
592                    angle: 0.0,
593                    family,
594                    face: theme.title.face,
595                },
596            )?;
597        }
598
599        // Draw subtitle
600        if let Some(ref subtitle) = built.labels.subtitle {
601            let (sx, anchor) = Self::hjust_pos(&theme.subtitle, &title_ref);
602            let subtitle_y = plot_area.y - 2.0;
603            let family = if theme.subtitle.family.is_empty() {
604                None
605            } else {
606                Some(theme.subtitle.family.clone())
607            };
608            backend.draw_text(
609                subtitle,
610                (sx, subtitle_y.max(theme.title.size + theme.subtitle.size)),
611                &TextStyle {
612                    color: theme.subtitle.color,
613                    size: theme.subtitle.size,
614                    anchor,
615                    angle: 0.0,
616                    family,
617                    face: theme.subtitle.face,
618                },
619            )?;
620        }
621
622        // Draw caption
623        if let Some(ref caption) = built.labels.caption {
624            let (cx, anchor) = Self::hjust_pos(&theme.caption, &title_ref);
625            let caption_y = total_area.y + total_area.height - theme.caption.size * 0.5;
626            let family = if theme.caption.family.is_empty() {
627                None
628            } else {
629                Some(theme.caption.family.clone())
630            };
631            backend.draw_text(
632                caption,
633                (cx, caption_y),
634                &TextStyle {
635                    color: theme.caption.color,
636                    size: theme.caption.size,
637                    anchor,
638                    angle: 0.0,
639                    family,
640                    face: theme.caption.face,
641                },
642            )?;
643        }
644
645        // Draw annotations
646        Self::draw_annotations(
647            &built.annotations,
648            &built.scales,
649            built.coord.as_ref(),
650            &plot_area,
651            backend,
652        )?;
653
654        // Draw legend
655        legend::draw_legend(
656            &built.scales,
657            theme,
658            &plot_area,
659            backend,
660            &built.guide_legend,
661            &built.suppressed_aes,
662        )?;
663
664        // Draw corner tag (labs(tag = ...))
665        Self::draw_tag(&built.labels, theme, &total_area, backend)?;
666
667        Ok(())
668    }
669
670    fn draw_annotations(
671        annotations: &[Annotation],
672        scales: &crate::scale::ScaleSet,
673        coord: &dyn crate::coord::Coord,
674        plot_area: &crate::render::Rect,
675        backend: &mut dyn DrawBackend,
676    ) -> Result<(), RenderError> {
677        use crate::data::Value;
678
679        let x_scale = scales.get(&Aesthetic::X);
680        let y_scale = scales.get(&Aesthetic::Y);
681
682        for ann in annotations {
683            match ann {
684                Annotation::Text {
685                    label,
686                    x,
687                    y,
688                    size,
689                    color,
690                } => {
691                    let nx = x_scale.map(|s| s.map(&Value::Float(*x))).unwrap_or(0.0);
692                    let ny = y_scale.map(|s| s.map(&Value::Float(*y))).unwrap_or(0.0);
693                    let pos = coord.transform((nx, ny), plot_area);
694                    backend.draw_text(
695                        label,
696                        pos,
697                        &TextStyle {
698                            color: *color,
699                            size: *size,
700                            anchor: TextAnchor::Middle,
701                            angle: 0.0,
702                            family: None,
703                            face: crate::render::backend::FontFace::Plain,
704                        },
705                    )?;
706                }
707                Annotation::Rect {
708                    xmin,
709                    xmax,
710                    ymin,
711                    ymax,
712                    fill,
713                    alpha,
714                } => {
715                    let nx0 = x_scale.map(|s| s.map(&Value::Float(*xmin))).unwrap_or(0.0);
716                    let nx1 = x_scale.map(|s| s.map(&Value::Float(*xmax))).unwrap_or(1.0);
717                    let ny0 = y_scale.map(|s| s.map(&Value::Float(*ymin))).unwrap_or(0.0);
718                    let ny1 = y_scale.map(|s| s.map(&Value::Float(*ymax))).unwrap_or(1.0);
719                    let tl = coord.transform((nx0, ny1), plot_area);
720                    let br = coord.transform((nx1, ny0), plot_area);
721                    backend.draw_rect(
722                        tl,
723                        br,
724                        &RectStyle {
725                            fill: Some(*fill),
726                            stroke: None,
727                            stroke_width: 0.0,
728                            alpha: *alpha,
729                            clip: false,
730                        },
731                    )?;
732                }
733                Annotation::Segment {
734                    x,
735                    y,
736                    xend,
737                    yend,
738                    color,
739                    width,
740                } => {
741                    let nx0 = x_scale.map(|s| s.map(&Value::Float(*x))).unwrap_or(0.0);
742                    let ny0 = y_scale.map(|s| s.map(&Value::Float(*y))).unwrap_or(0.0);
743                    let nx1 = x_scale.map(|s| s.map(&Value::Float(*xend))).unwrap_or(1.0);
744                    let ny1 = y_scale.map(|s| s.map(&Value::Float(*yend))).unwrap_or(1.0);
745                    let p0 = coord.transform((nx0, ny0), plot_area);
746                    let p1 = coord.transform((nx1, ny1), plot_area);
747                    backend.draw_line(
748                        &[p0, p1],
749                        &LineStyle {
750                            color: *color,
751                            alpha: 1.0,
752                            width: *width,
753                            linetype: Linetype::Solid,
754                        },
755                    )?;
756                }
757            }
758        }
759        Ok(())
760    }
761}
762
763/// Wrapper that overrides plot_area() to return the panel rect.
764struct PanelBackendAdapter<'a> {
765    inner: &'a mut dyn DrawBackend,
766    panel_rect: crate::render::Rect,
767}
768
769impl<'a> DrawBackend for PanelBackendAdapter<'a> {
770    fn draw_circle(
771        &mut self,
772        center: (f64, f64),
773        radius: f64,
774        style: &crate::render::backend::PointStyle,
775    ) -> Result<(), RenderError> {
776        self.inner.draw_circle(center, radius, style)
777    }
778    fn draw_line(
779        &mut self,
780        points: &[(f64, f64)],
781        style: &crate::render::backend::LineStyle,
782    ) -> Result<(), RenderError> {
783        self.inner.draw_line(points, style)
784    }
785    fn draw_rect(
786        &mut self,
787        top_left: (f64, f64),
788        bottom_right: (f64, f64),
789        style: &RectStyle,
790    ) -> Result<(), RenderError> {
791        self.inner.draw_rect(top_left, bottom_right, style)
792    }
793    fn draw_text(
794        &mut self,
795        text: &str,
796        pos: (f64, f64),
797        style: &TextStyle,
798    ) -> Result<(), RenderError> {
799        self.inner.draw_text(text, pos, style)
800    }
801    fn draw_polygon(
802        &mut self,
803        points: &[(f64, f64)],
804        style: &RectStyle,
805    ) -> Result<(), RenderError> {
806        self.inner.draw_polygon(points, style)
807    }
808    fn draw_shape(
809        &mut self,
810        center: (f64, f64),
811        radius: f64,
812        style: &crate::render::backend::PointStyle,
813    ) -> Result<(), RenderError> {
814        self.inner.draw_shape(center, radius, style)
815    }
816    fn plot_area(&self) -> crate::render::Rect {
817        self.panel_rect.clone()
818    }
819    fn set_tooltip(&mut self, tooltip: Option<String>) {
820        self.inner.set_tooltip(tooltip)
821    }
822    fn set_mark_axis(&mut self, key: Option<String>) {
823        self.inner.set_mark_axis(key)
824    }
825    fn set_mark_series(&mut self, series: Option<String>) {
826        self.inner.set_mark_series(series)
827    }
828    fn set_mark_value(&mut self, value: Option<String>) {
829        self.inner.set_mark_value(value)
830    }
831    fn total_area(&self) -> crate::render::Rect {
832        self.inner.total_area()
833    }
834}