1use crate::aes::{mapping::apply_after_stat, mapping::resolve_mappings, Aes, Aesthetic};
2use crate::annotate::Annotation;
3use crate::coord::Coord;
4use crate::data::DataFrame;
5use crate::facet::{Facet, FacetScales, Panel};
6use crate::geom::Geom;
7use crate::plot::{GGError, GGPlot, Labels, Layer};
8use crate::position::PositionParams;
9use crate::scale::ScaleSet;
10use crate::theme::Theme;
11
12pub struct BuiltLayer {
14 pub data: DataFrame,
15 pub geom: Box<dyn Geom>,
16 pub show_legend: Option<bool>,
17}
18
19pub struct BuiltPlot {
21 pub layers: Vec<BuiltLayer>,
22 pub scales: ScaleSet,
23 pub coord: Box<dyn Coord>,
24 pub theme: Theme,
25 pub labels: Labels,
26 pub facet: Facet,
27 pub panels: Vec<Panel>,
28 pub panels_data: Vec<Vec<DataFrame>>,
30 pub annotations: Vec<Annotation>,
31 pub guide_legend: crate::guide::config::GuideLegend,
32 pub suppressed_aes: std::collections::HashSet<Aesthetic>,
34 pub panel_scales: Vec<ScaleSet>,
36 pub warnings: Vec<String>,
40}
41
42impl BuiltPlot {
43 pub fn warnings(&self) -> &[String] {
47 &self.warnings
48 }
49}
50
51const POSITION_COLS: &[&str] = &[
54 "x", "y", "xmin", "xmax", "ymin", "ymax", "xend", "yend", "open", "high", "low", "close",
55];
56
57const X_FAMILY: &[&str] = &["x", "xmin", "xmax", "xend"];
59const Y_FAMILY: &[&str] = &["y", "ymin", "ymax", "yend", "open", "high", "low", "close"];
60
61pub struct PlotBuilder;
63
64impl PlotBuilder {
65 pub fn build(plot: GGPlot) -> Result<BuiltPlot, GGError> {
66 let GGPlot {
67 data: plot_data,
68 mapping: plot_mapping,
69 layers,
70 scales: user_scales,
71 mut coord,
72 theme,
73 labels,
74 facet,
75 annotations,
76 guide_legend,
77 warnings: plot_warnings,
78 default_aspect_ratio,
79 } = plot;
80 let mut theme = theme;
81 if theme.aspect_ratio.is_none() {
82 theme.aspect_ratio = default_aspect_ratio;
83 }
84
85 plot_data.validate()?;
88 for layer in &layers {
89 if let Some(d) = &layer.data {
90 d.validate()?;
91 }
92 }
93
94 let mut scale_set = ScaleSet::new();
95
96 for s in user_scales {
98 scale_set.add(s);
99 }
100
101 let mut built_layers = Vec::new();
102 let mut warnings: Vec<String> = plot_warnings;
103
104 let facet_vars = Self::facet_vars(&facet);
107
108 for layer in layers {
109 let built = Self::build_layer(
110 layer,
111 &plot_data,
112 &plot_mapping,
113 &mut scale_set,
114 theme.primary,
115 &facet_vars,
116 &mut warnings,
117 )?;
118 built_layers.push(built);
119 }
120
121 for bl in &built_layers {
123 scale_set.train_layer(&bl.data);
124 }
125
126 if built_layers.iter().all(|bl| bl.data.nrows() == 0) {
130 for aes in [Aesthetic::X, Aesthetic::Y] {
131 if scale_set.get(&aes).is_none() {
132 scale_set.add(Box::new(
133 crate::scale::continuous::ScaleContinuous::new().for_aesthetic(aes),
134 ));
135 }
136 }
137 }
138
139 if let Some((min, max)) = coord.zoom_x() {
141 scale_set.set_limits(&Aesthetic::X, min, max);
142 }
143 if let Some((min, max)) = coord.zoom_y() {
144 scale_set.set_limits(&Aesthetic::Y, min, max);
145 }
146
147 coord.train_scales(&mut scale_set);
149
150 let axis_span = |aes: &Aesthetic| {
154 scale_set.get(aes).and_then(|s| {
155 s.domain().map(|(min, max)| crate::coord::AxisSpan {
156 min,
157 max,
158 pmin: s.map(&crate::data::Value::Float(min)),
159 pmax: s.map(&crate::data::Value::Float(max)),
160 })
161 })
162 };
163 let x_span = axis_span(&Aesthetic::X);
164 let y_span = axis_span(&Aesthetic::Y);
165 coord.set_domains(x_span, y_span);
166
167 for spec in &plot_mapping.after_scale {
172 if let Some(src_scale) = scale_set.get(&spec.source) {
173 let modified = crate::scale::modified::ScaleColorModified::new(
174 src_scale.clone_box(),
175 spec.target.clone(),
176 spec.lightness,
177 );
178 let (src_col, tgt_col) = (spec.source.col_name(), spec.target.col_name());
179 for bl in &mut built_layers {
180 if !bl.data.has_column(tgt_col) {
181 if let Some(vals) = bl.data.column(src_col) {
182 let vals = vals.to_vec();
183 bl.data.add_column(tgt_col.to_string(), vals);
184 }
185 }
186 }
187 scale_set.add(Box::new(modified));
188 }
189 }
190
191 let (panels, panels_data) = Self::compute_facets(&facet, &built_layers, &plot_data);
193
194 let suppressed_aes = Self::compute_suppressed_aes(&built_layers);
196
197 let facet_scales_mode = match &facet {
199 Facet::Wrap { scales, .. } => scales.clone(),
200 Facet::Grid { scales, .. } => scales.clone(),
201 Facet::None => FacetScales::Fixed,
202 };
203 let panel_scales = Self::compute_panel_scales(&facet_scales_mode, &panels_data, &scale_set);
204
205 Ok(BuiltPlot {
206 layers: built_layers,
207 scales: scale_set,
208 coord,
209 theme,
210 labels,
211 facet,
212 panels,
213 panels_data,
214 annotations,
215 guide_legend,
216 suppressed_aes,
217 panel_scales,
218 warnings,
219 })
220 }
221
222 fn retain_rows(data: &mut DataFrame, keep: &[bool]) {
224 if keep.iter().all(|&k| k) {
225 return;
226 }
227 let mut result = DataFrame::new();
228 for col_name in data.column_names() {
229 if let Some(src) = data.column(col_name) {
230 let vals: Vec<_> = src
231 .iter()
232 .zip(keep)
233 .filter(|(_, &k)| k)
234 .map(|(v, _)| v.clone())
235 .collect();
236 result.add_column(col_name.to_string(), vals);
237 }
238 }
239 *data = result;
240 }
241
242 fn drop_non_finite(data: &mut DataFrame, allow_infinite: bool) -> usize {
246 let n = data.nrows();
247 let mut keep = vec![true; n];
248 for col in POSITION_COLS {
249 if let Some(values) = data.column(col) {
250 for (i, v) in values.iter().enumerate() {
251 if let crate::data::Value::Float(f) = v {
252 let bad = f.is_nan() || (f.is_infinite() && !allow_infinite);
253 if bad {
254 keep[i] = false;
255 }
256 }
257 }
258 }
259 }
260 let removed = keep.iter().filter(|&&k| !k).count();
261 if removed > 0 {
262 Self::retain_rows(data, &keep);
263 }
264 removed
265 }
266
267 fn facet_vars(facet: &Facet) -> Vec<String> {
269 match facet {
270 Facet::None => vec![],
271 Facet::Wrap { var, .. } => vec![var.clone()],
272 Facet::Grid {
273 row_var, col_var, ..
274 } => row_var.iter().chain(col_var.iter()).cloned().collect(),
275 }
276 }
277
278 fn compute_facets(
279 facet: &Facet,
280 built_layers: &[BuiltLayer],
281 _plot_data: &DataFrame,
282 ) -> (Vec<Panel>, Vec<Vec<DataFrame>>) {
283 match facet {
284 Facet::None => (vec![], vec![]),
285 Facet::Wrap {
286 var,
287 ncol,
288 labeller,
289 ..
290 } => {
291 let mut levels: Vec<String> = Vec::new();
293 for bl in built_layers {
294 if let Some(col) = bl.data.column(var) {
295 for v in col {
296 let key = v.to_group_key();
297 if !levels.contains(&key) {
298 levels.push(key);
299 }
300 }
301 }
302 }
303
304 let panels: Vec<Panel> = levels
306 .iter()
307 .enumerate()
308 .map(|(i, value)| {
309 let ncols =
310 ncol.unwrap_or_else(|| (levels.len() as f64).sqrt().ceil() as usize);
311 let formatted = labeller.format(var, value);
312 Panel {
313 row: i / ncols.max(1),
314 col: i % ncols.max(1),
315 label: formatted.clone(),
316 row_label: None,
317 col_label: Some(formatted),
318 rect: crate::render::Rect {
319 x: 0.0,
320 y: 0.0,
321 width: 0.0,
322 height: 0.0,
323 },
324 }
325 })
326 .collect();
327
328 let panels_data: Vec<Vec<DataFrame>> = levels
330 .iter()
331 .map(|level| {
332 built_layers
333 .iter()
334 .map(|bl| Self::filter_data_by_var(&bl.data, var, level))
335 .collect()
336 })
337 .collect();
338
339 (panels, panels_data)
340 }
341 Facet::Grid {
342 row_var,
343 col_var,
344 labeller,
345 ..
346 } => {
347 let mut row_levels: Vec<String> = Vec::new();
348 let mut col_levels: Vec<String> = Vec::new();
349
350 for bl in built_layers {
351 if let Some(rv) = row_var {
352 if let Some(col) = bl.data.column(rv) {
353 for v in col {
354 let key = v.to_group_key();
355 if !row_levels.contains(&key) {
356 row_levels.push(key);
357 }
358 }
359 }
360 }
361 if let Some(cv) = col_var {
362 if let Some(col) = bl.data.column(cv) {
363 for v in col {
364 let key = v.to_group_key();
365 if !col_levels.contains(&key) {
366 col_levels.push(key);
367 }
368 }
369 }
370 }
371 }
372
373 if row_levels.is_empty() {
374 row_levels.push("".to_string());
375 }
376 if col_levels.is_empty() {
377 col_levels.push("".to_string());
378 }
379
380 let mut panels = Vec::new();
381 let mut panels_data = Vec::new();
382
383 for (ri, rl) in row_levels.iter().enumerate() {
384 for (ci, cl) in col_levels.iter().enumerate() {
385 let row_fmt = if rl.is_empty() {
386 None
387 } else {
388 let rv = row_var.as_deref().unwrap_or("");
389 Some(labeller.format(rv, rl))
390 };
391 let col_fmt = if cl.is_empty() {
392 None
393 } else {
394 let cv = col_var.as_deref().unwrap_or("");
395 Some(labeller.format(cv, cl))
396 };
397 let label = match (&row_fmt, &col_fmt) {
398 (Some(r), Some(c)) => format!("{r} | {c}"),
399 (Some(r), None) => r.clone(),
400 (None, Some(c)) => c.clone(),
401 (None, None) => String::new(),
402 };
403 panels.push(Panel {
404 row: ri,
405 col: ci,
406 label,
407 row_label: row_fmt,
408 col_label: col_fmt,
409 rect: crate::render::Rect {
410 x: 0.0,
411 y: 0.0,
412 width: 0.0,
413 height: 0.0,
414 },
415 });
416
417 let layer_data: Vec<DataFrame> = built_layers
418 .iter()
419 .map(|bl| {
420 let mut data = bl.data.clone();
421 if let Some(rv) = row_var {
422 if !rl.is_empty() {
423 data = Self::filter_data_by_var(&data, rv, rl);
424 }
425 }
426 if let Some(cv) = col_var {
427 if !cl.is_empty() {
428 data = Self::filter_data_by_var(&data, cv, cl);
429 }
430 }
431 data
432 })
433 .collect();
434 panels_data.push(layer_data);
435 }
436 }
437
438 (panels, panels_data)
439 }
440 }
441 }
442
443 fn filter_data_by_var(data: &DataFrame, var: &str, level: &str) -> DataFrame {
444 if let Some(col) = data.column(var) {
445 let indices: Vec<usize> = col
446 .iter()
447 .enumerate()
448 .filter(|(_, v)| v.to_group_key() == level)
449 .map(|(i, _)| i)
450 .collect();
451
452 let mut result = DataFrame::new();
453 for col_name in data.column_names() {
454 if let Some(src) = data.column(col_name) {
455 let vals: Vec<_> = indices.iter().map(|&i| src[i].clone()).collect();
456 result.add_column(col_name.to_string(), vals);
457 }
458 }
459 result
460 } else {
461 data.clone()
462 }
463 }
464
465 fn build_layer(
466 layer: Layer,
467 plot_data: &DataFrame,
468 plot_mapping: &Aes,
469 scale_set: &mut ScaleSet,
470 primary: Option<(u8, u8, u8)>,
471 facet_vars: &[String],
472 warnings: &mut Vec<String>,
473 ) -> Result<BuiltLayer, GGError> {
474 let Layer {
475 data: layer_data,
476 mapping: layer_mapping,
477 mut geom,
478 stat,
479 position,
480 params: _,
481 show_legend,
482 explicit_style,
483 } = layer;
484
485 let source_data = layer_data.as_ref().unwrap_or(plot_data);
489
490 let merged_mapping = plot_mapping.merge(&layer_mapping);
492
493 if let Some(color) = primary.filter(|_| !explicit_style) {
497 let has_color = merged_mapping.get_mapping(&Aesthetic::Color).is_some();
498 let has_fill = merged_mapping.get_mapping(&Aesthetic::Fill).is_some();
499 if !has_color && !has_fill {
500 geom.set_series_color(color);
501 }
502 }
503
504 let mut working_data = resolve_mappings(source_data, &merged_mapping);
506
507 let pre_stat_columns: Vec<String> = working_data
512 .column_names()
513 .iter()
514 .map(|s| s.to_string())
515 .collect();
516
517 for m in &merged_mapping.mappings {
519 scale_set.ensure_scale(&m.aesthetic, &working_data);
520 }
521
522 for scale in scale_set.iter() {
525 let aes = scale.aesthetic();
526 let cols: Vec<&str> = match aes {
527 Aesthetic::X => X_FAMILY.to_vec(),
528 Aesthetic::Y => Y_FAMILY.to_vec(),
529 _ => vec![aes.col_name()],
530 };
531 for col_name in cols {
532 let Some(col) = working_data.column(col_name) else {
533 continue;
534 };
535 let transformed: Vec<_> = col
539 .iter()
540 .map(|v| match (scale.transform(v), v.as_f64()) {
541 (crate::data::Value::Na, Some(_)) => crate::data::Value::Float(f64::NAN),
542 (t, _) => t,
543 })
544 .collect();
545 let any_changed = transformed.iter().zip(col.iter()).any(|(t, o)| {
546 match (t.as_f64(), o.as_f64()) {
547 (Some(a), Some(b)) => a.is_nan() || (a - b).abs() > f64::EPSILON,
548 _ => false,
549 }
550 });
551 if any_changed {
552 if let Some(col_mut) = working_data.column_mut(col_name) {
553 *col_mut = transformed;
554 }
555 }
556 }
557 }
558
559 Self::filter_oob_data(&mut working_data, scale_set);
561
562 let geom_label = format!("geom_{}", geom.name());
566 let removed = Self::drop_non_finite(&mut working_data, geom.allows_infinite());
567 if removed > 0 {
568 warnings.push(format!(
569 "{geom_label}: removed {removed} row{} containing non-finite values",
570 if removed == 1 { "" } else { "s" }
571 ));
572 }
573 let input_rows = working_data.nrows();
574
575 let mut group_cols = if stat.panelwise() {
582 Vec::new()
583 } else {
584 Self::detect_group_columns(&working_data)
585 };
586 for fv in facet_vars {
587 if working_data.has_column(fv) && !group_cols.contains(fv) {
588 group_cols.push(fv.clone());
589 }
590 }
591
592 working_data = if !group_cols.is_empty() {
593 let groups =
594 working_data.group_by(&group_cols.iter().map(|s| s.as_str()).collect::<Vec<_>>());
595 let mut result = DataFrame::new();
596 for group in groups {
597 let mut computed = stat.compute_group(&group, scale_set);
598 let n = computed.nrows();
599 if n > 0 {
600 for fv in facet_vars {
601 if !computed.has_column(fv) {
602 if let Some(val) = group.column(fv).and_then(|c| c.first()).cloned() {
603 computed.add_column(fv.clone(), vec![val; n]);
604 }
605 }
606 }
607 }
608 result.vstack(&computed);
609 }
610 result
611 } else {
612 stat.compute_group(&working_data, scale_set)
613 };
614
615 apply_after_stat(&mut working_data, &merged_mapping);
617
618 let removed = Self::drop_non_finite(&mut working_data, geom.allows_infinite());
621 if removed > 0 {
622 warnings.push(format!(
623 "{geom_label}: removed {removed} row{} containing missing values (stat_{} output)",
624 if removed == 1 { "" } else { "s" },
625 stat.name()
626 ));
627 }
628
629 if working_data.nrows() == 0 {
634 if input_rows > 0 {
635 for aes in &stat.required_aes() {
636 let col_name = aes.col_name();
637 if !pre_stat_columns.iter().any(|c| c == col_name) {
638 return Err(GGError::ValidationError(format!(
639 "stat_{} requires aesthetic '{}' but it was not provided",
640 stat.name(),
641 col_name
642 )));
643 }
644 }
645 warnings.push(format!(
646 "{geom_label}: stat_{} produced no data from {input_rows} row{} \
647 (too few or degenerate values); layer skipped",
648 stat.name(),
649 if input_rows == 1 { "" } else { "s" }
650 ));
651 }
652 return Ok(BuiltLayer {
653 data: DataFrame::new(),
654 geom,
655 show_legend,
656 });
657 }
658
659 for aes in &geom.required_aes() {
665 let col_name = aes.col_name();
666 let supplied = pre_stat_columns.iter().any(|c| c == col_name);
667 if !supplied && !working_data.has_column(col_name) {
668 return Err(GGError::ValidationError(format!(
669 "geom_{} requires aesthetic '{}' but it was not provided",
670 geom.name(),
671 col_name
672 )));
673 }
674 }
675
676 let stat_aes = [
678 ("x", Aesthetic::X),
679 ("y", Aesthetic::Y),
680 ("xmin", Aesthetic::X),
681 ("xmax", Aesthetic::X),
682 ("ymin", Aesthetic::Y),
683 ("ymax", Aesthetic::Y),
684 ];
685 for (col, aes) in &stat_aes {
686 if working_data.has_column(col) {
687 scale_set.ensure_scale(aes, &working_data);
688 }
689 }
690
691 let y_is_user_mapped = merged_mapping.get_mapping(&Aesthetic::Y).is_some();
695 if (!y_is_user_mapped || geom.include_zero_baseline()) && working_data.has_column("y") {
696 if let Some(y_scale) = scale_set.get_mut(&Aesthetic::Y) {
697 y_scale.train(&[crate::data::Value::Float(0.0)]);
698 }
699 }
700
701 let params = PositionParams::default();
703 position.compute(&mut working_data, ¶ms);
704
705 geom.setup_data(&mut working_data);
708 for (col, aes) in &stat_aes {
709 if working_data.has_column(col) {
710 scale_set.ensure_scale(aes, &working_data);
711 }
712 }
713
714 scale_set.train_layer(&working_data);
716
717 for (col, aes) in &stat_aes {
721 if let Some(values) = working_data.column(col) {
722 if let Some(scale) = scale_set.get_mut(aes) {
723 scale.train(values);
724 }
725 }
726 }
727
728 Ok(BuiltLayer {
729 data: working_data,
730 geom,
731 show_legend,
732 })
733 }
734
735 fn filter_oob_data(data: &mut DataFrame, scale_set: &ScaleSet) {
737 let x_limits = scale_set.get(&Aesthetic::X).and_then(|s| s.filter_limits());
738 let y_limits = scale_set.get(&Aesthetic::Y).and_then(|s| s.filter_limits());
739
740 if x_limits.is_none() && y_limits.is_none() {
741 return;
742 }
743
744 let nrows = data.nrows();
745 let mut keep = vec![true; nrows];
746
747 if let Some((min, max)) = x_limits {
748 if let Some(col) = data.column("x") {
749 for (i, v) in col.iter().enumerate() {
750 if let Some(f) = v.as_f64() {
751 if f < min || f > max {
752 keep[i] = false;
753 }
754 }
755 }
756 }
757 }
758
759 if let Some((min, max)) = y_limits {
760 if let Some(col) = data.column("y") {
761 for (i, v) in col.iter().enumerate() {
762 if let Some(f) = v.as_f64() {
763 if f < min || f > max {
764 keep[i] = false;
765 }
766 }
767 }
768 }
769 }
770
771 if keep.iter().all(|&k| k) {
773 return;
774 }
775
776 let indices: Vec<usize> = keep
777 .iter()
778 .enumerate()
779 .filter(|(_, &k)| k)
780 .map(|(i, _)| i)
781 .collect();
782
783 let mut result = DataFrame::new();
784 for col_name in data.column_names() {
785 if let Some(src) = data.column(col_name) {
786 let vals: Vec<_> = indices.iter().map(|&i| src[i].clone()).collect();
787 result.add_column(col_name.to_string(), vals);
788 }
789 }
790 *data = result;
791 }
792
793 fn compute_panel_scales(
796 facet_scales: &FacetScales,
797 panels_data: &[Vec<DataFrame>],
798 base_scales: &ScaleSet,
799 ) -> Vec<ScaleSet> {
800 if matches!(facet_scales, FacetScales::Fixed) || panels_data.is_empty() {
801 return vec![];
802 }
803
804 let free_x = matches!(facet_scales, FacetScales::FreeX | FacetScales::Free);
805 let free_y = matches!(facet_scales, FacetScales::FreeY | FacetScales::Free);
806
807 panels_data
808 .iter()
809 .map(|panel_layers| {
810 let mut panel_set = base_scales.clone();
811
812 if free_x {
814 if let Some(s) = panel_set.get_mut(&Aesthetic::X) {
815 s.reset_training();
816 }
817 }
818 if free_y {
819 if let Some(s) = panel_set.get_mut(&Aesthetic::Y) {
820 s.reset_training();
821 }
822 }
823
824 for layer_data in panel_layers {
827 panel_set.train_layer(layer_data);
828 for (cols, aes) in [
829 (["xmin", "xmax"], Aesthetic::X),
830 (["ymin", "ymax"], Aesthetic::Y),
831 ] {
832 let freed = match aes {
833 Aesthetic::X => free_x,
834 _ => free_y,
835 };
836 if !freed {
837 continue;
838 }
839 for c in cols {
840 if let (Some(vals), Some(s)) =
841 (layer_data.column(c), panel_set.get_mut(&aes))
842 {
843 s.train(vals);
844 }
845 }
846 }
847 }
848
849 panel_set
850 })
851 .collect()
852 }
853
854 fn compute_suppressed_aes(built_layers: &[BuiltLayer]) -> std::collections::HashSet<Aesthetic> {
858 use std::collections::HashSet;
859 let legend_aes = [
860 Aesthetic::Color,
861 Aesthetic::Fill,
862 Aesthetic::Shape,
863 Aesthetic::Linetype,
864 Aesthetic::Size,
865 Aesthetic::Alpha,
866 ];
867 let mut suppressed = HashSet::new();
868 for aes in &legend_aes {
869 let col_name = aes.col_name();
870 let mut any_has = false;
871 let mut all_hidden = true;
872 for bl in built_layers {
873 if bl.data.has_column(col_name) {
874 any_has = true;
875 match bl.show_legend {
876 Some(false) => {} _ => {
878 all_hidden = false;
879 break;
880 }
881 }
882 }
883 }
884 if any_has && all_hidden {
885 suppressed.insert(aes.clone());
886 }
887 }
888 suppressed
889 }
890
891 fn detect_group_columns(data: &DataFrame) -> Vec<String> {
894 let candidates = ["group", "color", "fill", "x"];
895 let mut group_cols = Vec::new();
896 for &col in &candidates {
897 if data.has_column(col) {
898 if let Some(values) = data.column(col) {
899 let is_discrete = values
900 .iter()
901 .any(|v| matches!(v, crate::data::Value::Str(_)));
902 if is_discrete {
903 group_cols.push(col.to_string());
904 }
905 }
906 }
907 }
908 group_cols
909 }
910}