Skip to main content

ggplot_rs/
plot.rs

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