Skip to main content

ggplot_rs/
plot.rs

1use plotters::prelude::IntoDrawingArea;
2
3use crate::aes::Aes;
4use crate::annotate::Annotation;
5use crate::build::PlotBuilder;
6use crate::coord::cartesian::CoordCartesian;
7use crate::coord::fixed::CoordFixed;
8use crate::coord::flip::CoordFlip;
9use crate::coord::polar::CoordPolar;
10use crate::coord::Coord;
11use crate::data::{DataFrame, GGData};
12use crate::facet::{Facet, FacetLabeller, FacetScales};
13use crate::geom::area::GeomArea;
14use crate::geom::bar::GeomBar;
15use crate::geom::bin2d::GeomBin2d;
16use crate::geom::blank::GeomBlank;
17use crate::geom::boxplot::GeomBoxplot;
18use crate::geom::col::GeomCol;
19use crate::geom::contour::GeomContour;
20use crate::geom::count::GeomCount;
21use crate::geom::crossbar::GeomCrossbar;
22use crate::geom::curve::GeomCurve;
23use crate::geom::density::GeomDensity;
24use crate::geom::density2d::GeomDensity2d;
25use crate::geom::dotplot::GeomDotplot;
26use crate::geom::errorbar::GeomErrorbar;
27use crate::geom::freqpoly::GeomFreqpoly;
28use crate::geom::hex::GeomHex;
29use crate::geom::histogram::GeomHistogram;
30use crate::geom::jitter::GeomJitter;
31use crate::geom::line::GeomLine;
32use crate::geom::linerange::GeomLinerange;
33use crate::geom::path::GeomPath;
34use crate::geom::point::GeomPoint;
35use crate::geom::pointrange::GeomPointrange;
36use crate::geom::polygon::GeomPolygon;
37use crate::geom::qq::{GeomQQ, GeomQQLine};
38use crate::geom::rect::GeomRect;
39use crate::geom::refline::{GeomAbline, GeomHline, GeomVline};
40use crate::geom::ribbon::GeomRibbon;
41use crate::geom::rug::GeomRug;
42use crate::geom::segment::GeomSegment;
43use crate::geom::smooth::GeomSmooth;
44use crate::geom::spoke::GeomSpoke;
45use crate::geom::step::GeomStep;
46use crate::geom::text::{GeomLabel, GeomText};
47use crate::geom::tile::GeomTile;
48use crate::geom::violin::GeomViolin;
49use crate::geom::{Geom, GeomParams};
50use crate::position::Position;
51use crate::render::layout::PlotLayout;
52use crate::render::plotters_backend::PlottersAdapter;
53use crate::render::renderer::PlotRenderer;
54use crate::render::RenderError;
55use crate::scale::continuous::ScaleContinuous;
56use crate::scale::transform::ScaleTransform;
57use crate::scale::Scale;
58use crate::stat::Stat;
59use crate::theme::Theme;
60
61/// Labels for the plot.
62#[derive(Clone, Debug, Default)]
63pub struct Labels {
64    pub title: Option<String>,
65    pub subtitle: Option<String>,
66    pub x: Option<String>,
67    pub y: Option<String>,
68    pub caption: Option<String>,
69}
70
71/// A single layer in the plot.
72pub struct Layer {
73    pub data: Option<DataFrame>,
74    pub mapping: Aes,
75    pub geom: Box<dyn Geom>,
76    pub stat: Box<dyn Stat>,
77    pub position: Box<dyn Position>,
78    pub params: GeomParams,
79    pub show_legend: Option<bool>,
80}
81
82/// The top-level plot specification — builder pattern.
83pub struct GGPlot {
84    pub(crate) data: DataFrame,
85    pub(crate) mapping: Aes,
86    pub(crate) layers: Vec<Layer>,
87    pub(crate) scales: Vec<Box<dyn Scale>>,
88    pub(crate) coord: Box<dyn Coord>,
89    pub(crate) theme: Theme,
90    pub(crate) labels: Labels,
91    pub(crate) facet: Facet,
92    pub(crate) annotations: Vec<Annotation>,
93    pub(crate) guide_legend: crate::guide::config::GuideLegend,
94}
95
96impl GGPlot {
97    /// Create a new plot with the given data source.
98    pub fn new(data: impl GGData) -> Self {
99        GGPlot {
100            data: data.into_dataframe(),
101            mapping: Aes::default(),
102            layers: Vec::new(),
103            scales: Vec::new(),
104            coord: Box::new(CoordCartesian::new()),
105            theme: Theme::default(),
106            labels: Labels::default(),
107            facet: Facet::default(),
108            annotations: Vec::new(),
109            guide_legend: crate::guide::config::GuideLegend::default(),
110        }
111    }
112
113    /// Set the plot-level aesthetic mapping.
114    pub fn aes(mut self, mapping: Aes) -> Self {
115        self.mapping = mapping;
116        self
117    }
118
119    // ─── Geom shortcuts ──────────────────────────────────────────
120
121    pub fn geom_point(self) -> Self {
122        self.add_geom(GeomPoint::default())
123    }
124
125    pub fn geom_point_with(self, geom: GeomPoint) -> Self {
126        self.add_geom(geom)
127    }
128
129    pub fn geom_line(self) -> Self {
130        self.add_geom(GeomLine::default())
131    }
132
133    pub fn geom_line_with(self, geom: GeomLine) -> Self {
134        self.add_geom(geom)
135    }
136
137    pub fn geom_bar(self) -> Self {
138        self.add_geom(GeomBar::default())
139    }
140
141    pub fn geom_bar_with(self, geom: GeomBar) -> Self {
142        self.add_geom(geom)
143    }
144
145    pub fn geom_histogram(self) -> Self {
146        self.add_geom(GeomHistogram::default())
147    }
148
149    pub fn geom_histogram_with(self, geom: GeomHistogram) -> Self {
150        self.add_geom(geom)
151    }
152
153    pub fn geom_boxplot(self) -> Self {
154        self.add_geom(GeomBoxplot::default())
155    }
156
157    pub fn geom_boxplot_with(self, geom: GeomBoxplot) -> Self {
158        self.add_geom(geom)
159    }
160
161    pub fn geom_smooth(self) -> Self {
162        self.add_geom(GeomSmooth::default())
163    }
164
165    pub fn geom_smooth_with(self, geom: GeomSmooth) -> Self {
166        self.add_geom(geom)
167    }
168
169    pub fn geom_col(self) -> Self {
170        self.add_geom(GeomCol::default())
171    }
172
173    pub fn geom_col_with(self, geom: GeomCol) -> Self {
174        self.add_geom(geom)
175    }
176
177    pub fn geom_hline(self, yintercept: f64) -> Self {
178        self.add_geom(GeomHline::new(yintercept))
179    }
180
181    /// Add a horizontal reference line with custom styling (color/linetype/width).
182    pub fn geom_hline_with(self, geom: GeomHline) -> Self {
183        self.add_geom(geom)
184    }
185
186    pub fn geom_vline(self, xintercept: f64) -> Self {
187        self.add_geom(GeomVline::new(xintercept))
188    }
189
190    /// Add a vertical reference line with custom styling (color/linetype/width).
191    pub fn geom_vline_with(self, geom: GeomVline) -> Self {
192        self.add_geom(geom)
193    }
194
195    pub fn geom_abline(self, slope: f64, intercept: f64) -> Self {
196        self.add_geom(GeomAbline::new(slope, intercept))
197    }
198
199    /// Add a slope/intercept reference line with custom styling.
200    pub fn geom_abline_with(self, geom: GeomAbline) -> Self {
201        self.add_geom(geom)
202    }
203
204    pub fn geom_text(self) -> Self {
205        self.add_geom(GeomText::default())
206    }
207
208    pub fn geom_text_with(self, geom: GeomText) -> Self {
209        self.add_geom(geom)
210    }
211
212    pub fn geom_label(self) -> Self {
213        self.add_geom(GeomLabel::default())
214    }
215
216    pub fn geom_label_with(self, geom: GeomLabel) -> Self {
217        self.add_geom(geom)
218    }
219
220    pub fn geom_area(self) -> Self {
221        self.add_geom(GeomArea::default())
222    }
223
224    pub fn geom_area_with(self, geom: GeomArea) -> Self {
225        self.add_geom(geom)
226    }
227
228    pub fn geom_ribbon(self) -> Self {
229        self.add_geom(GeomRibbon::default())
230    }
231
232    pub fn geom_ribbon_with(self, geom: GeomRibbon) -> Self {
233        self.add_geom(geom)
234    }
235
236    pub fn geom_errorbar(self) -> Self {
237        self.add_geom(GeomErrorbar::default())
238    }
239
240    pub fn geom_errorbar_with(self, geom: GeomErrorbar) -> Self {
241        self.add_geom(geom)
242    }
243
244    pub fn geom_segment(self) -> Self {
245        self.add_geom(GeomSegment::default())
246    }
247
248    pub fn geom_segment_with(self, geom: GeomSegment) -> Self {
249        self.add_geom(geom)
250    }
251
252    pub fn geom_density(self) -> Self {
253        self.add_geom(GeomDensity::default())
254    }
255
256    pub fn geom_density_with(self, geom: GeomDensity) -> Self {
257        self.add_geom(geom)
258    }
259
260    pub fn geom_rug(self) -> Self {
261        self.add_geom(GeomRug::default())
262    }
263
264    pub fn geom_rug_with(self, geom: GeomRug) -> Self {
265        self.add_geom(geom)
266    }
267
268    pub fn geom_jitter(self) -> Self {
269        self.add_geom(GeomJitter::default())
270    }
271
272    pub fn geom_jitter_with(self, geom: GeomJitter) -> Self {
273        self.add_geom(geom)
274    }
275
276    pub fn geom_path(self) -> Self {
277        self.add_geom(GeomPath::default())
278    }
279
280    pub fn geom_path_with(self, geom: GeomPath) -> Self {
281        self.add_geom(geom)
282    }
283
284    /// Add a confidence-ellipse layer (default 95%) as a path per group.
285    pub fn stat_ellipse(self) -> Self {
286        self.geom_path()
287            .stat(crate::stat::ellipse::StatEllipse::default())
288    }
289
290    /// Add a confidence-ellipse layer at the given level (0, 1).
291    pub fn stat_ellipse_level(self, level: f64) -> Self {
292        self.geom_path()
293            .stat(crate::stat::ellipse::StatEllipse::new(level))
294    }
295
296    /// Add a quantile-regression line for each `tau` as a separate path layer
297    /// (R's `stat_quantile`). Backed by anofox-regression (feature `regression`).
298    #[cfg(feature = "regression")]
299    pub fn stat_quantile(mut self, taus: &[f64]) -> Self {
300        for &tau in taus {
301            self = self
302                .geom_path()
303                .stat(crate::stat::quantile::StatQuantile::new(tau));
304        }
305        self
306    }
307
308    /// Quantile-regression lines at the quartiles (0.25, 0.5, 0.75).
309    #[cfg(feature = "regression")]
310    pub fn geom_quantile(self) -> Self {
311        self.stat_quantile(&[0.25, 0.5, 0.75])
312    }
313
314    pub fn geom_step(self) -> Self {
315        self.add_geom(GeomStep::default())
316    }
317
318    pub fn geom_step_with(self, geom: GeomStep) -> Self {
319        self.add_geom(geom)
320    }
321
322    pub fn geom_freqpoly(self) -> Self {
323        self.add_geom(GeomFreqpoly::default())
324    }
325
326    pub fn geom_freqpoly_with(self, geom: GeomFreqpoly) -> Self {
327        self.add_geom(geom)
328    }
329
330    pub fn geom_linerange(self) -> Self {
331        self.add_geom(GeomLinerange::default())
332    }
333
334    pub fn geom_linerange_with(self, geom: GeomLinerange) -> Self {
335        self.add_geom(geom)
336    }
337
338    pub fn geom_pointrange(self) -> Self {
339        self.add_geom(GeomPointrange::default())
340    }
341
342    pub fn geom_pointrange_with(self, geom: GeomPointrange) -> Self {
343        self.add_geom(geom)
344    }
345
346    pub fn geom_crossbar(self) -> Self {
347        self.add_geom(GeomCrossbar::default())
348    }
349
350    pub fn geom_crossbar_with(self, geom: GeomCrossbar) -> Self {
351        self.add_geom(geom)
352    }
353
354    pub fn geom_spoke(self) -> Self {
355        self.add_geom(GeomSpoke::default())
356    }
357
358    pub fn geom_spoke_with(self, geom: GeomSpoke) -> Self {
359        self.add_geom(geom)
360    }
361
362    pub fn geom_rect(self) -> Self {
363        self.add_geom(GeomRect::default())
364    }
365
366    pub fn geom_rect_with(self, geom: GeomRect) -> Self {
367        self.add_geom(geom)
368    }
369
370    pub fn geom_tile(self) -> Self {
371        self.add_geom(GeomTile::default())
372    }
373
374    pub fn geom_tile_with(self, geom: GeomTile) -> Self {
375        self.add_geom(geom)
376    }
377
378    /// Dense regular grid of filled cells (heatmap/raster) from x, y, fill.
379    pub fn geom_raster(self) -> Self {
380        self.add_geom(crate::geom::raster::GeomRaster::default())
381    }
382
383    pub fn geom_raster_with(self, geom: crate::geom::raster::GeomRaster) -> Self {
384        self.add_geom(geom)
385    }
386
387    pub fn geom_polygon(self) -> Self {
388        self.add_geom(GeomPolygon::default())
389    }
390
391    pub fn geom_polygon_with(self, geom: GeomPolygon) -> Self {
392        self.add_geom(geom)
393    }
394
395    pub fn geom_curve(self) -> Self {
396        self.add_geom(GeomCurve::default())
397    }
398
399    pub fn geom_curve_with(self, geom: GeomCurve) -> Self {
400        self.add_geom(geom)
401    }
402
403    pub fn geom_violin(self) -> Self {
404        self.add_geom(GeomViolin::default())
405    }
406
407    pub fn geom_violin_with(self, geom: GeomViolin) -> Self {
408        self.add_geom(geom)
409    }
410
411    pub fn geom_dotplot(self) -> Self {
412        self.add_geom(GeomDotplot::default())
413    }
414
415    pub fn geom_dotplot_with(self, geom: GeomDotplot) -> Self {
416        self.add_geom(geom)
417    }
418
419    pub fn geom_qq(self) -> Self {
420        self.add_geom(GeomQQ::default())
421    }
422
423    pub fn geom_qq_with(self, geom: GeomQQ) -> Self {
424        self.add_geom(geom)
425    }
426
427    pub fn geom_qq_line(self) -> Self {
428        self.add_geom(GeomQQLine::default())
429    }
430
431    pub fn geom_qq_line_with(self, geom: GeomQQLine) -> Self {
432        self.add_geom(geom)
433    }
434
435    pub fn geom_bin2d(self) -> Self {
436        self.add_geom(GeomBin2d::default())
437    }
438
439    pub fn geom_bin2d_with(self, geom: GeomBin2d) -> Self {
440        self.add_geom(geom)
441    }
442
443    pub fn geom_hex(self) -> Self {
444        self.add_geom(GeomHex::default())
445    }
446
447    pub fn geom_hex_with(self, geom: GeomHex) -> Self {
448        self.add_geom(geom)
449    }
450
451    pub fn geom_count(self) -> Self {
452        self.add_geom(GeomCount::default())
453    }
454
455    pub fn geom_count_with(self, geom: GeomCount) -> Self {
456        self.add_geom(geom)
457    }
458
459    pub fn geom_contour(self) -> Self {
460        self.add_geom(GeomContour::default())
461    }
462
463    pub fn geom_contour_with(self, geom: GeomContour) -> Self {
464        self.add_geom(geom)
465    }
466
467    /// Filled contour bands from gridded (x, y, z) data — draws polygons filled by
468    /// band level. Pair with a continuous fill scale (e.g. `scale_fill_viridis_c`).
469    pub fn geom_contour_filled(self) -> Self {
470        self.add_geom(GeomPolygon {
471            line_width: 0.0,
472            alpha: 1.0,
473            ..GeomPolygon::default()
474        })
475        .stat(crate::stat::contour_filled::StatContourFilled::default())
476    }
477
478    pub fn geom_density2d(self) -> Self {
479        self.add_geom(GeomDensity2d::default())
480    }
481
482    pub fn geom_density2d_with(self, geom: GeomDensity2d) -> Self {
483        self.add_geom(geom)
484    }
485
486    pub fn geom_blank(self) -> Self {
487        self.add_geom(GeomBlank)
488    }
489
490    fn add_geom(mut self, geom: impl Geom + 'static) -> Self {
491        let stat = geom.default_stat();
492        let position = geom.default_position();
493        let params = geom.default_params();
494        self.layers.push(Layer {
495            data: None,
496            mapping: Aes::default(),
497            geom: Box::new(geom),
498            stat,
499            position,
500            params,
501            show_legend: None,
502        });
503        self
504    }
505
506    // ─── Layer-level overrides ──────────────────────────────────
507
508    /// Override the stat for the most recently added layer.
509    pub fn stat(mut self, stat: impl Stat + 'static) -> Self {
510        if let Some(layer) = self.layers.last_mut() {
511            layer.stat = Box::new(stat);
512        }
513        self
514    }
515
516    /// Override the position for the most recently added layer.
517    pub fn position(mut self, pos: impl Position + 'static) -> Self {
518        if let Some(layer) = self.layers.last_mut() {
519            layer.position = Box::new(pos);
520        }
521        self
522    }
523
524    /// Override the data for the most recently added layer.
525    pub fn layer_data(mut self, data: impl GGData) -> Self {
526        if let Some(layer) = self.layers.last_mut() {
527            layer.data = Some(data.into_dataframe());
528        }
529        self
530    }
531
532    /// Override the aesthetic mapping for the most recently added layer.
533    pub fn layer_aes(mut self, mapping: Aes) -> Self {
534        if let Some(layer) = self.layers.last_mut() {
535            layer.mapping = mapping;
536        }
537        self
538    }
539
540    /// Control whether the most recently added layer contributes to the legend.
541    /// `true` = always show, `false` = always hide, default (None) = auto.
542    pub fn show_legend(mut self, show: bool) -> Self {
543        if let Some(layer) = self.layers.last_mut() {
544            layer.show_legend = Some(show);
545        }
546        self
547    }
548
549    // ─── Scales ──────────────────────────────────────────────────
550
551    pub fn scale_x_continuous(mut self, s: ScaleContinuous) -> Self {
552        let s = s.for_aesthetic(crate::aes::Aesthetic::X);
553        self.scales.push(Box::new(s));
554        self
555    }
556
557    pub fn scale_y_continuous(mut self, s: ScaleContinuous) -> Self {
558        let s = s.for_aesthetic(crate::aes::Aesthetic::Y);
559        self.scales.push(Box::new(s));
560        self
561    }
562
563    pub fn scale_x_discrete(mut self, s: crate::scale::discrete::ScaleDiscrete) -> Self {
564        let s = s.for_aesthetic(crate::aes::Aesthetic::X);
565        self.scales.push(Box::new(s));
566        self
567    }
568
569    pub fn scale_y_discrete(mut self, s: crate::scale::discrete::ScaleDiscrete) -> Self {
570        let s = s.for_aesthetic(crate::aes::Aesthetic::Y);
571        self.scales.push(Box::new(s));
572        self
573    }
574
575    pub fn scale_color(mut self, s: impl Scale + 'static) -> Self {
576        self.scales.push(Box::new(s));
577        self
578    }
579
580    pub fn scale_fill(mut self, s: impl Scale + 'static) -> Self {
581        self.scales.push(Box::new(s));
582        self
583    }
584
585    pub fn scale_color_manual(self, values: Vec<(&str, crate::scale::color::RGBAColor)>) -> Self {
586        let s = crate::scale::manual::ScaleManual::new(crate::aes::Aesthetic::Color, values);
587        self.scale_color(s)
588    }
589
590    pub fn scale_fill_manual(self, values: Vec<(&str, crate::scale::color::RGBAColor)>) -> Self {
591        let s = crate::scale::manual::ScaleManual::new(crate::aes::Aesthetic::Fill, values);
592        self.scale_fill(s)
593    }
594
595    pub fn scale_color_viridis(self) -> Self {
596        use crate::scale::color::ScaleColorDiscrete;
597        use crate::scale::palettes::PaletteName;
598        let s = ScaleColorDiscrete::new(crate::aes::Aesthetic::Color)
599            .with_named_palette(&PaletteName::Viridis);
600        self.scale_color(s)
601    }
602
603    pub fn scale_color_brewer(self, name: crate::scale::palettes::PaletteName) -> Self {
604        use crate::scale::color::ScaleColorDiscrete;
605        let s = ScaleColorDiscrete::new(crate::aes::Aesthetic::Color).with_named_palette(&name);
606        self.scale_color(s)
607    }
608
609    pub fn scale_color_gradient(
610        self,
611        low: crate::scale::color::RGBAColor,
612        high: crate::scale::color::RGBAColor,
613    ) -> Self {
614        use crate::scale::color::ScaleColorContinuous;
615        let s = ScaleColorContinuous::new(crate::aes::Aesthetic::Color).with_colors(low, high);
616        self.scale_color(s)
617    }
618
619    pub fn scale_fill_gradient(
620        self,
621        low: crate::scale::color::RGBAColor,
622        high: crate::scale::color::RGBAColor,
623    ) -> Self {
624        use crate::scale::color::ScaleColorContinuous;
625        let s = ScaleColorContinuous::new(crate::aes::Aesthetic::Fill).with_colors(low, high);
626        self.scale_fill(s)
627    }
628
629    pub fn scale_color_gradient2(
630        self,
631        low: crate::scale::color::RGBAColor,
632        mid: crate::scale::color::RGBAColor,
633        high: crate::scale::color::RGBAColor,
634    ) -> Self {
635        use crate::scale::gradient::ScaleColorGradient2;
636        let s = ScaleColorGradient2::new(crate::aes::Aesthetic::Color).with_colors(low, mid, high);
637        self.scale_color(s)
638    }
639
640    pub fn scale_fill_gradient2(
641        self,
642        low: crate::scale::color::RGBAColor,
643        mid: crate::scale::color::RGBAColor,
644        high: crate::scale::color::RGBAColor,
645    ) -> Self {
646        use crate::scale::gradient::ScaleColorGradient2;
647        let s = ScaleColorGradient2::new(crate::aes::Aesthetic::Fill).with_colors(low, mid, high);
648        self.scale_fill(s)
649    }
650
651    pub fn scale_fill_viridis(self) -> Self {
652        use crate::scale::color::ScaleColorDiscrete;
653        use crate::scale::palettes::PaletteName;
654        let s = ScaleColorDiscrete::new(crate::aes::Aesthetic::Fill)
655            .with_named_palette(&PaletteName::Viridis);
656        self.scale_fill(s)
657    }
658
659    /// Continuous viridis color scale (for numeric data).
660    pub fn scale_color_viridis_c(self) -> Self {
661        use crate::scale::gradient_n::ScaleColorGradientN;
662        let s = ScaleColorGradientN::viridis(crate::aes::Aesthetic::Color);
663        self.scale_color(s)
664    }
665
666    /// Continuous viridis fill scale (for numeric data).
667    pub fn scale_fill_viridis_c(self) -> Self {
668        use crate::scale::gradient_n::ScaleColorGradientN;
669        let s = ScaleColorGradientN::viridis(crate::aes::Aesthetic::Fill);
670        self.scale_fill(s)
671    }
672
673    /// N-stop continuous color gradient.
674    pub fn scale_color_gradientn(self, stops: Vec<(f64, crate::scale::color::RGBAColor)>) -> Self {
675        use crate::scale::gradient_n::ScaleColorGradientN;
676        let s = ScaleColorGradientN::new(crate::aes::Aesthetic::Color, stops);
677        self.scale_color(s)
678    }
679
680    /// N-stop continuous fill gradient.
681    pub fn scale_fill_gradientn(self, stops: Vec<(f64, crate::scale::color::RGBAColor)>) -> Self {
682        use crate::scale::gradient_n::ScaleColorGradientN;
683        let s = ScaleColorGradientN::new(crate::aes::Aesthetic::Fill, stops);
684        self.scale_fill(s)
685    }
686
687    /// Binned (stepped) two-colour continuous colour scale — buckets the mapped
688    /// variable into `n_bins` bins, each a discrete colour, with a stepped legend.
689    pub fn scale_color_steps(
690        self,
691        low: crate::scale::color::RGBAColor,
692        high: crate::scale::color::RGBAColor,
693        n_bins: usize,
694    ) -> Self {
695        let s = crate::scale::steps::ScaleColorSteps::two(
696            crate::aes::Aesthetic::Color,
697            (low.r, low.g, low.b),
698            (high.r, high.g, high.b),
699            n_bins,
700        );
701        self.scale_color(s)
702    }
703
704    /// Binned N-stop continuous colour scale.
705    pub fn scale_color_stepsn(
706        self,
707        stops: Vec<crate::scale::color::RGBAColor>,
708        n_bins: usize,
709    ) -> Self {
710        let s = crate::scale::steps::ScaleColorSteps::new(
711            crate::aes::Aesthetic::Color,
712            stops.iter().map(|c| (c.r, c.g, c.b)).collect(),
713            n_bins,
714        );
715        self.scale_color(s)
716    }
717
718    /// Binned ColorBrewer colour scale (R's `scale_color_fermenter`).
719    pub fn scale_color_fermenter(
720        self,
721        name: crate::scale::palettes::PaletteName,
722        n_bins: usize,
723    ) -> Self {
724        let stops = crate::scale::palettes::palette(&name)
725            .iter()
726            .map(|c| (c.r, c.g, c.b))
727            .collect();
728        let s =
729            crate::scale::steps::ScaleColorSteps::new(crate::aes::Aesthetic::Color, stops, n_bins);
730        self.scale_color(s)
731    }
732
733    /// Binned (stepped) two-colour continuous fill scale.
734    pub fn scale_fill_steps(
735        self,
736        low: crate::scale::color::RGBAColor,
737        high: crate::scale::color::RGBAColor,
738        n_bins: usize,
739    ) -> Self {
740        let s = crate::scale::steps::ScaleColorSteps::two(
741            crate::aes::Aesthetic::Fill,
742            (low.r, low.g, low.b),
743            (high.r, high.g, high.b),
744            n_bins,
745        );
746        self.scale_fill(s)
747    }
748
749    /// Binned ColorBrewer fill scale.
750    pub fn scale_fill_fermenter(
751        self,
752        name: crate::scale::palettes::PaletteName,
753        n_bins: usize,
754    ) -> Self {
755        let stops = crate::scale::palettes::palette(&name)
756            .iter()
757            .map(|c| (c.r, c.g, c.b))
758            .collect();
759        let s =
760            crate::scale::steps::ScaleColorSteps::new(crate::aes::Aesthetic::Fill, stops, n_bins);
761        self.scale_fill(s)
762    }
763
764    pub fn scale_fill_brewer(self, name: crate::scale::palettes::PaletteName) -> Self {
765        use crate::scale::color::ScaleColorDiscrete;
766        let s = ScaleColorDiscrete::new(crate::aes::Aesthetic::Fill).with_named_palette(&name);
767        self.scale_fill(s)
768    }
769
770    pub fn scale_linetype_manual(
771        self,
772        values: Vec<(&str, crate::render::backend::Linetype)>,
773    ) -> Self {
774        let s = crate::scale::linetype_manual::ScaleLinetypeManual::new(values);
775        self.scale_color(s)
776    }
777
778    pub fn scale_shape_manual(
779        self,
780        values: Vec<(&str, crate::render::backend::PointShape)>,
781    ) -> Self {
782        let s = crate::scale::shape_manual::ScaleShapeManual::new(values);
783        self.scale_color(s)
784    }
785
786    pub fn scale_color_grey(self) -> Self {
787        let s = crate::scale::grey::ScaleColorGrey::new(crate::aes::Aesthetic::Color);
788        self.scale_color(s)
789    }
790
791    pub fn scale_fill_grey(self) -> Self {
792        let s = crate::scale::grey::ScaleColorGrey::new(crate::aes::Aesthetic::Fill);
793        self.scale_fill(s)
794    }
795
796    pub fn scale_color_grey_with(self, s: crate::scale::grey::ScaleColorGrey) -> Self {
797        self.scale_color(s)
798    }
799
800    pub fn scale_fill_grey_with(self, s: crate::scale::grey::ScaleColorGrey) -> Self {
801        self.scale_fill(s)
802    }
803
804    pub fn scale_x_reverse(self) -> Self {
805        self.scale_x_continuous(ScaleContinuous::new().with_transform(ScaleTransform::Reverse))
806    }
807
808    pub fn scale_y_reverse(self) -> Self {
809        self.scale_y_continuous(ScaleContinuous::new().with_transform(ScaleTransform::Reverse))
810    }
811
812    pub fn scale_x_datetime(mut self, s: crate::scale::datetime::ScaleDateTime) -> Self {
813        let s = s.for_aesthetic(crate::aes::Aesthetic::X);
814        self.scales.push(Box::new(s));
815        self
816    }
817
818    pub fn scale_y_datetime(mut self, s: crate::scale::datetime::ScaleDateTime) -> Self {
819        let s = s.for_aesthetic(crate::aes::Aesthetic::Y);
820        self.scales.push(Box::new(s));
821        self
822    }
823
824    pub fn scale_size(mut self, s: crate::scale::size::ScaleSizeContinuous) -> Self {
825        self.scales.push(Box::new(s));
826        self
827    }
828
829    pub fn scale_alpha(mut self, s: crate::scale::alpha::ScaleAlphaContinuous) -> Self {
830        self.scales.push(Box::new(s));
831        self
832    }
833
834    pub fn xlim(self, min: f64, max: f64) -> Self {
835        self.scale_x_continuous(ScaleContinuous::new().with_limits(min, max))
836    }
837
838    pub fn ylim(self, min: f64, max: f64) -> Self {
839        self.scale_y_continuous(ScaleContinuous::new().with_limits(min, max))
840    }
841
842    pub fn scale_x_log10(self) -> Self {
843        self.scale_x_continuous(ScaleContinuous::new().with_transform(ScaleTransform::Log10))
844    }
845
846    pub fn scale_y_log10(self) -> Self {
847        self.scale_y_continuous(ScaleContinuous::new().with_transform(ScaleTransform::Log10))
848    }
849
850    pub fn scale_x_sqrt(self) -> Self {
851        self.scale_x_continuous(ScaleContinuous::new().with_transform(ScaleTransform::Sqrt))
852    }
853
854    pub fn scale_y_sqrt(self) -> Self {
855        self.scale_y_continuous(ScaleContinuous::new().with_transform(ScaleTransform::Sqrt))
856    }
857
858    pub fn scale_x_log2(self) -> Self {
859        self.scale_x_continuous(ScaleContinuous::new().with_transform(ScaleTransform::Log2))
860    }
861
862    pub fn scale_y_log2(self) -> Self {
863        self.scale_y_continuous(ScaleContinuous::new().with_transform(ScaleTransform::Log2))
864    }
865
866    pub fn scale_x_ln(self) -> Self {
867        self.scale_x_continuous(ScaleContinuous::new().with_transform(ScaleTransform::Ln))
868    }
869
870    pub fn scale_y_ln(self) -> Self {
871        self.scale_y_continuous(ScaleContinuous::new().with_transform(ScaleTransform::Ln))
872    }
873
874    /// Logit-transformed x axis (for proportions in (0, 1)).
875    pub fn scale_x_logit(self) -> Self {
876        self.scale_x_continuous(ScaleContinuous::new().with_transform(ScaleTransform::Logit))
877    }
878
879    pub fn scale_y_logit(self) -> Self {
880        self.scale_y_continuous(ScaleContinuous::new().with_transform(ScaleTransform::Logit))
881    }
882
883    /// Probit-transformed x axis (inverse normal CDF, for proportions in (0, 1)).
884    pub fn scale_x_probit(self) -> Self {
885        self.scale_x_continuous(ScaleContinuous::new().with_transform(ScaleTransform::Probit))
886    }
887
888    pub fn scale_y_probit(self) -> Self {
889        self.scale_y_continuous(ScaleContinuous::new().with_transform(ScaleTransform::Probit))
890    }
891
892    /// Sign-preserving pseudo-log x axis (handles zero and negative values).
893    pub fn scale_x_pseudo_log(self) -> Self {
894        self.scale_x_continuous(ScaleContinuous::new().with_transform(ScaleTransform::PseudoLog))
895    }
896
897    pub fn scale_y_pseudo_log(self) -> Self {
898        self.scale_y_continuous(ScaleContinuous::new().with_transform(ScaleTransform::PseudoLog))
899    }
900
901    /// Reciprocal (1/x) x axis.
902    pub fn scale_x_reciprocal(self) -> Self {
903        self.scale_x_continuous(ScaleContinuous::new().with_transform(ScaleTransform::Reciprocal))
904    }
905
906    pub fn scale_y_reciprocal(self) -> Self {
907        self.scale_y_continuous(ScaleContinuous::new().with_transform(ScaleTransform::Reciprocal))
908    }
909
910    /// Exponential x axis (labels spaced logarithmically).
911    pub fn scale_x_exp(self) -> Self {
912        self.scale_x_continuous(ScaleContinuous::new().with_transform(ScaleTransform::Exp))
913    }
914
915    pub fn scale_y_exp(self) -> Self {
916        self.scale_y_continuous(ScaleContinuous::new().with_transform(ScaleTransform::Exp))
917    }
918
919    /// Box–Cox x axis with the given lambda (x > 0).
920    pub fn scale_x_boxcox(self, lambda: f64) -> Self {
921        self.scale_x_continuous(
922            ScaleContinuous::new().with_transform(ScaleTransform::BoxCox(lambda)),
923        )
924    }
925
926    pub fn scale_y_boxcox(self, lambda: f64) -> Self {
927        self.scale_y_continuous(
928            ScaleContinuous::new().with_transform(ScaleTransform::BoxCox(lambda)),
929        )
930    }
931
932    // ─── Faceting ─────────────────────────────────────────────────
933
934    pub fn facet_wrap(mut self, var: &str, ncol: Option<usize>) -> Self {
935        self.facet = Facet::Wrap {
936            var: var.to_string(),
937            ncol,
938            scales: FacetScales::Fixed,
939            labeller: FacetLabeller::default(),
940        };
941        self
942    }
943
944    pub fn facet_wrap_free(mut self, var: &str, ncol: Option<usize>, scales: FacetScales) -> Self {
945        self.facet = Facet::Wrap {
946            var: var.to_string(),
947            ncol,
948            scales,
949            labeller: FacetLabeller::default(),
950        };
951        self
952    }
953
954    pub fn facet_wrap_labeller(
955        mut self,
956        var: &str,
957        ncol: Option<usize>,
958        labeller: FacetLabeller,
959    ) -> Self {
960        self.facet = Facet::Wrap {
961            var: var.to_string(),
962            ncol,
963            scales: FacetScales::Fixed,
964            labeller,
965        };
966        self
967    }
968
969    pub fn facet_grid(mut self, row: Option<&str>, col: Option<&str>) -> Self {
970        self.facet = Facet::Grid {
971            row_var: row.map(String::from),
972            col_var: col.map(String::from),
973            scales: FacetScales::Fixed,
974            labeller: FacetLabeller::default(),
975        };
976        self
977    }
978
979    pub fn facet_grid_free(
980        mut self,
981        row: Option<&str>,
982        col: Option<&str>,
983        scales: FacetScales,
984    ) -> Self {
985        self.facet = Facet::Grid {
986            row_var: row.map(String::from),
987            col_var: col.map(String::from),
988            scales,
989            labeller: FacetLabeller::default(),
990        };
991        self
992    }
993
994    pub fn facet_grid_labeller(
995        mut self,
996        row: Option<&str>,
997        col: Option<&str>,
998        labeller: FacetLabeller,
999    ) -> Self {
1000        self.facet = Facet::Grid {
1001            row_var: row.map(String::from),
1002            col_var: col.map(String::from),
1003            scales: FacetScales::Fixed,
1004            labeller,
1005        };
1006        self
1007    }
1008
1009    // ─── Coordinates ─────────────────────────────────────────────
1010
1011    pub fn coord_flip(mut self) -> Self {
1012        self.coord = Box::new(CoordFlip);
1013        self
1014    }
1015
1016    pub fn coord_fixed(mut self, ratio: f64) -> Self {
1017        self.coord = Box::new(CoordFixed::new(ratio));
1018        self
1019    }
1020
1021    /// Transform the coordinate space at draw time (R's `coord_trans`) — stats are
1022    /// computed on raw data but drawn on non-linear axes. Pass a per-axis
1023    /// [`ScaleTransform`] (e.g. `Some(ScaleTransform::Log10)`), `None` to leave an
1024    /// axis linear.
1025    pub fn coord_trans(mut self, x: Option<ScaleTransform>, y: Option<ScaleTransform>) -> Self {
1026        self.coord = Box::new(crate::coord::trans::CoordTrans::new(x, y));
1027        self
1028    }
1029
1030    /// `coord_trans` on the y-axis only.
1031    pub fn coord_trans_y(self, y: ScaleTransform) -> Self {
1032        self.coord_trans(None, Some(y))
1033    }
1034
1035    /// `coord_trans` on the x-axis only.
1036    pub fn coord_trans_x(self, x: ScaleTransform) -> Self {
1037        self.coord_trans(Some(x), None)
1038    }
1039
1040    /// Zoom into a region without filtering data (unlike xlim/ylim which filter).
1041    pub fn coord_cartesian_zoom(
1042        mut self,
1043        xlim: Option<(f64, f64)>,
1044        ylim: Option<(f64, f64)>,
1045    ) -> Self {
1046        let mut c = CoordCartesian::new();
1047        if let Some((min, max)) = xlim {
1048            c = c.xlim(min, max);
1049        }
1050        if let Some((min, max)) = ylim {
1051            c = c.ylim(min, max);
1052        }
1053        self.coord = Box::new(c);
1054        self
1055    }
1056
1057    pub fn coord_polar(mut self) -> Self {
1058        self.coord = Box::new(CoordPolar::new());
1059        self
1060    }
1061
1062    pub fn coord_polar_with(mut self, coord: CoordPolar) -> Self {
1063        self.coord = Box::new(coord);
1064        self
1065    }
1066
1067    // ─── Theme ───────────────────────────────────────────────────
1068
1069    pub fn theme(mut self, theme: Theme) -> Self {
1070        self.theme = theme;
1071        self
1072    }
1073
1074    /// Rotate the x-axis tick labels by `degrees` (R's
1075    /// `guides(x = guide_axis(angle = ...))` / `axis.text.x = element_text(angle)`).
1076    /// Useful for long category labels. Call after any `theme_*()` preset.
1077    pub fn axis_text_x_angle(mut self, degrees: f64) -> Self {
1078        self.theme.axis_text_x.angle = degrees;
1079        self
1080    }
1081
1082    /// Rotate the y-axis tick labels by `degrees`.
1083    pub fn axis_text_y_angle(mut self, degrees: f64) -> Self {
1084        self.theme.axis_text_y.angle = degrees;
1085        self
1086    }
1087
1088    /// Set the brand/primary color used as the default for single-series geoms
1089    /// that have no color/fill aesthetic mapped. Composes with any theme — one
1090    /// render process can serve different tenants' brands at render time.
1091    /// Place the legend inside the panel at panel-relative coordinates
1092    /// (0..1, 0..1) — `(0,0)` bottom-left, `(1,1)` top-right (R's
1093    /// `legend.position = c(x, y)`).
1094    pub fn legend_position_inside(mut self, x: f64, y: f64) -> Self {
1095        self.theme.legend_position = crate::theme::LegendPosition::Inside(x, y);
1096        self
1097    }
1098
1099    pub fn primary_color(mut self, color: (u8, u8, u8)) -> Self {
1100        self.theme.primary = Some(color);
1101        self
1102    }
1103
1104    pub fn theme_minimal(mut self) -> Self {
1105        self.theme = crate::theme::presets::theme_minimal();
1106        self
1107    }
1108
1109    pub fn theme_bw(mut self) -> Self {
1110        self.theme = crate::theme::presets::theme_bw();
1111        self
1112    }
1113
1114    pub fn theme_gray(mut self) -> Self {
1115        self.theme = crate::theme::presets::theme_gray();
1116        self
1117    }
1118
1119    pub fn theme_classic(mut self) -> Self {
1120        self.theme = crate::theme::presets::theme_classic();
1121        self
1122    }
1123
1124    pub fn theme_linedraw(mut self) -> Self {
1125        self.theme = crate::theme::presets::theme_linedraw();
1126        self
1127    }
1128
1129    pub fn theme_light(mut self) -> Self {
1130        self.theme = crate::theme::presets::theme_light();
1131        self
1132    }
1133
1134    pub fn theme_dark(mut self) -> Self {
1135        self.theme = crate::theme::presets::theme_dark();
1136        self
1137    }
1138
1139    pub fn theme_void(mut self) -> Self {
1140        self.theme = crate::theme::presets::theme_void();
1141        self
1142    }
1143
1144    /// Apply incremental theme modifications on top of the current theme.
1145    /// Like R's `+ theme(axis.text.x = element_text(...))`.
1146    pub fn theme_update(mut self, update: crate::theme::ThemeUpdate) -> Self {
1147        self.theme = self.theme.update(update);
1148        self
1149    }
1150
1151    // ─── Guides ──────────────────────────────────────────────────
1152
1153    /// Configure legend guide (title, ncol, reverse).
1154    pub fn guides(mut self, guide: crate::guide::config::GuideLegend) -> Self {
1155        self.guide_legend = guide;
1156        self
1157    }
1158
1159    // ─── Labels ──────────────────────────────────────────────────
1160
1161    pub fn labs(mut self, labels: Labels) -> Self {
1162        if labels.title.is_some() {
1163            self.labels.title = labels.title;
1164        }
1165        if labels.subtitle.is_some() {
1166            self.labels.subtitle = labels.subtitle;
1167        }
1168        if labels.x.is_some() {
1169            self.labels.x = labels.x;
1170        }
1171        if labels.y.is_some() {
1172            self.labels.y = labels.y;
1173        }
1174        if labels.caption.is_some() {
1175            self.labels.caption = labels.caption;
1176        }
1177        self
1178    }
1179
1180    pub fn title(mut self, title: &str) -> Self {
1181        self.labels.title = Some(title.to_string());
1182        self
1183    }
1184
1185    pub fn subtitle(mut self, subtitle: &str) -> Self {
1186        self.labels.subtitle = Some(subtitle.to_string());
1187        self
1188    }
1189
1190    pub fn xlab(mut self, label: &str) -> Self {
1191        self.labels.x = Some(label.to_string());
1192        self
1193    }
1194
1195    pub fn ylab(mut self, label: &str) -> Self {
1196        self.labels.y = Some(label.to_string());
1197        self
1198    }
1199
1200    pub fn caption(mut self, caption: &str) -> Self {
1201        self.labels.caption = Some(caption.to_string());
1202        self
1203    }
1204
1205    // ─── Annotations ──────────────────────────────────────────────
1206
1207    /// Add an annotation to the plot.
1208    pub fn annotate(mut self, annotation: Annotation) -> Self {
1209        self.annotations.push(annotation);
1210        self
1211    }
1212
1213    /// Add a text annotation at data coordinates.
1214    pub fn annotate_text(self, label: &str, x: f64, y: f64) -> Self {
1215        self.annotate(Annotation::text(label, x, y))
1216    }
1217
1218    /// Add a rectangle annotation at data coordinates.
1219    pub fn annotate_rect(self, xmin: f64, xmax: f64, ymin: f64, ymax: f64) -> Self {
1220        self.annotate(Annotation::rect(xmin, xmax, ymin, ymax))
1221    }
1222
1223    /// Add a segment annotation between data coordinates.
1224    pub fn annotate_segment(self, x: f64, y: f64, xend: f64, yend: f64) -> Self {
1225        self.annotate(Annotation::segment(x, y, xend, yend))
1226    }
1227
1228    // ─── Build and Render ────────────────────────────────────────
1229
1230    /// Build the plot without rendering, returning errors on validation failure.
1231    pub fn try_build(self) -> Result<crate::build::BuiltPlot, GGError> {
1232        PlotBuilder::build(self)
1233    }
1234
1235    /// Build the plot without rendering (analogous to R's ggplot_build()).
1236    /// Returns the fully computed BuiltPlot with layer data ready for inspection.
1237    /// Panics on validation errors — use `try_build()` for error handling.
1238    pub fn build(self) -> crate::build::BuiltPlot {
1239        self.try_build().expect("plot build failed")
1240    }
1241
1242    /// Build and save the plot to a file. Format determined by extension.
1243    pub fn save(self, path: &str) -> Result<(), GGError> {
1244        self.save_with_size(path, 800, 600)
1245    }
1246
1247    /// Build and save with custom dimensions.
1248    pub fn save_with_size(self, path: &str, w: u32, h: u32) -> Result<(), GGError> {
1249        let (built, layout) = self.prepare(w, h)?;
1250
1251        // Determine backend from file extension
1252        let ext = path.rsplit('.').next().unwrap_or("svg").to_lowercase();
1253
1254        match ext.as_str() {
1255            "svg" => {
1256                let backend = plotters::prelude::SVGBackend::new(path, (w, h));
1257                Self::render_into(backend.into_drawing_area(), &built, &layout)?;
1258            }
1259            "png" | "bmp" | "gif" | "jpeg" | "jpg" | "tiff" => {
1260                let backend = plotters::prelude::BitMapBackend::new(path, (w, h));
1261                Self::render_into(backend.into_drawing_area(), &built, &layout)?;
1262            }
1263            _ => {
1264                return Err(GGError::UnsupportedFormat(ext));
1265            }
1266        }
1267
1268        Ok(())
1269    }
1270
1271    /// Render the plot to an in-memory SVG document (default 800x600).
1272    ///
1273    /// Unlike [`save`](Self::save), this writes nothing to disk — handy for
1274    /// serving charts from a web/MCP service.
1275    pub fn render_svg(self) -> Result<String, GGError> {
1276        self.render_svg_with_size(800, 600)
1277    }
1278
1279    /// Render the plot to an in-memory SVG document with custom dimensions.
1280    pub fn render_svg_with_size(self, w: u32, h: u32) -> Result<String, GGError> {
1281        let (built, layout) = self.prepare(w, h)?;
1282        let mut buf = String::new();
1283        {
1284            let backend = plotters::prelude::SVGBackend::with_string(&mut buf, (w, h));
1285            Self::render_into(backend.into_drawing_area(), &built, &layout)?;
1286        }
1287        Ok(buf)
1288    }
1289
1290    /// Render the plot to in-memory PNG bytes (default 800x600).
1291    ///
1292    /// Returns a fully-encoded PNG, ready to write to an HTTP response or
1293    /// embed as a data URI — no temp files involved.
1294    pub fn render_png(self) -> Result<Vec<u8>, GGError> {
1295        self.render_png_with_size(800, 600)
1296    }
1297
1298    /// Render the plot to in-memory PNG bytes with custom dimensions.
1299    pub fn render_png_with_size(self, w: u32, h: u32) -> Result<Vec<u8>, GGError> {
1300        let (built, layout) = self.prepare(w, h)?;
1301
1302        // plotters' BitMapBackend draws into a raw RGB buffer; we then encode
1303        // that buffer to PNG via the `image` crate.
1304        let mut rgb = vec![0u8; (w as usize) * (h as usize) * 3];
1305        {
1306            let backend = plotters::prelude::BitMapBackend::with_buffer(&mut rgb, (w, h));
1307            Self::render_into(backend.into_drawing_area(), &built, &layout)?;
1308        }
1309
1310        let img = image::RgbImage::from_raw(w, h, rgb).ok_or_else(|| {
1311            GGError::Render(RenderError::BackendError(
1312                "PNG buffer size mismatch".to_string(),
1313            ))
1314        })?;
1315        let mut out = std::io::Cursor::new(Vec::new());
1316        img.write_to(&mut out, image::ImageOutputFormat::Png)
1317            .map_err(|e| GGError::Render(RenderError::BackendError(format!("{:?}", e))))?;
1318        Ok(out.into_inner())
1319    }
1320
1321    /// Shared pipeline: build the plot, apply label overrides, compute layout.
1322    fn prepare(self, w: u32, h: u32) -> Result<(crate::build::BuiltPlot, PlotLayout), GGError> {
1323        let plot = self;
1324
1325        let has_title = plot.labels.title.is_some();
1326        let has_subtitle = plot.labels.subtitle.is_some();
1327        let has_caption = plot.labels.caption.is_some();
1328        let has_legend = plot.has_legend_mapping();
1329        let x_label = plot.labels.x.clone();
1330        let y_label = plot.labels.y.clone();
1331
1332        let mut built = PlotBuilder::build(plot)?;
1333
1334        // Apply user label overrides to scales
1335        if let Some(ref label) = x_label {
1336            if let Some(s) = built.scales.get_mut(&crate::aes::Aesthetic::X) {
1337                s.set_name(label);
1338            }
1339        }
1340        if let Some(ref label) = y_label {
1341            if let Some(s) = built.scales.get_mut(&crate::aes::Aesthetic::Y) {
1342                s.set_name(label);
1343            }
1344        }
1345
1346        let layout = PlotLayout::compute_full(
1347            w as f64,
1348            h as f64,
1349            &built.theme,
1350            has_title,
1351            has_subtitle,
1352            has_caption,
1353            has_legend,
1354        );
1355
1356        Ok((built, layout))
1357    }
1358
1359    /// Fill the background, render the built plot, and flush — for any backend.
1360    fn render_into<DB>(
1361        area: plotters::drawing::DrawingArea<DB, plotters::coord::Shift>,
1362        built: &crate::build::BuiltPlot,
1363        layout: &PlotLayout,
1364    ) -> Result<(), GGError>
1365    where
1366        DB: plotters::prelude::DrawingBackend,
1367        DB::ErrorType: 'static,
1368    {
1369        area.fill(&plotters::prelude::WHITE)
1370            .map_err(|e| GGError::Render(RenderError::BackendError(format!("{:?}", e))))?;
1371        let mut adapter = PlottersAdapter::new(&area, layout.plot_area.clone());
1372        PlotRenderer::render(built, &mut adapter).map_err(GGError::Render)?;
1373        area.present()
1374            .map_err(|e| GGError::Render(RenderError::BackendError(format!("{:?}", e))))?;
1375        Ok(())
1376    }
1377
1378    /// Save with physical dimensions (inches) and DPI.
1379    pub fn ggsave(
1380        self,
1381        path: &str,
1382        width_inches: f64,
1383        height_inches: f64,
1384        dpi: f64,
1385    ) -> Result<(), GGError> {
1386        let w = (width_inches * dpi) as u32;
1387        let h = (height_inches * dpi) as u32;
1388        self.save_with_size(path, w, h)
1389    }
1390
1391    fn has_legend_mapping(&self) -> bool {
1392        self.mapping.mappings.iter().any(|m| {
1393            matches!(
1394                m.aesthetic,
1395                crate::aes::Aesthetic::Color
1396                    | crate::aes::Aesthetic::Fill
1397                    | crate::aes::Aesthetic::Shape
1398                    | crate::aes::Aesthetic::Linetype
1399                    | crate::aes::Aesthetic::Size
1400                    | crate::aes::Aesthetic::Alpha
1401            )
1402        })
1403    }
1404}
1405
1406/// Top-level error type.
1407#[derive(Debug)]
1408pub enum GGError {
1409    Render(RenderError),
1410    UnsupportedFormat(String),
1411    Io(std::io::Error),
1412    ValidationError(String),
1413}
1414
1415impl std::fmt::Display for GGError {
1416    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1417        match self {
1418            GGError::Render(e) => write!(f, "Render error: {e}"),
1419            GGError::UnsupportedFormat(ext) => write!(f, "Unsupported output format: {ext}"),
1420            GGError::Io(e) => write!(f, "IO error: {e}"),
1421            GGError::ValidationError(msg) => write!(f, "Validation error: {msg}"),
1422        }
1423    }
1424}
1425
1426impl std::error::Error for GGError {}