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