1use std::sync::Arc;
2
3use crate::plot::bar::BarPlot;
4use crate::plot::boxplot::BoxPlot;
5use crate::plot::brick::BrickPlot;
6use crate::plot::histogram::Histogram;
7use crate::plot::line::LinePlot;
8use crate::plot::scatter::{ScatterPlot, TrendLine};
9use crate::plot::violin::ViolinPlot;
10
11use crate::plot::band::BandPlot;
12use crate::plot::bump::BumpPlot;
13use crate::plot::calendar::CalendarPlot;
14use crate::plot::candlestick::CandlestickPlot;
15use crate::plot::chord::ChordPlot;
16use crate::plot::clustermap::Clustermap;
17use crate::plot::contour::ContourPlot;
18use crate::plot::density::DensityPlot;
19use crate::plot::diceplot::DicePlot;
20use crate::plot::dotplot::DotPlot;
21use crate::plot::ecdf::EcdfPlot;
22use crate::plot::forest::ForestPlot;
23use crate::plot::funnel::FunnelPlot;
24use crate::plot::gantt::GanttPlot;
25use crate::plot::hexbin::HexbinPlot;
26use crate::plot::horizon::HorizonPlot;
27use crate::plot::jointplot::JointPlot;
28use crate::plot::legend::ColorBarInfo;
29use crate::plot::legend_plot::LegendPlot;
30use crate::plot::lollipop::LollipopPlot;
31use crate::plot::manhattan::ManhattanPlot;
32use crate::plot::mosaic::MosaicPlot;
33use crate::plot::network::NetworkPlot;
34use crate::plot::parallel::ParallelPlot;
35use crate::plot::pareto::ParetoPlot;
36use crate::plot::phylo::PhyloTree;
37use crate::plot::polar::PolarPlot;
38use crate::plot::pr::PrPlot;
39use crate::plot::pyramid::PopulationPyramid;
40use crate::plot::qq::QQPlot;
41use crate::plot::quiver::QuiverPlot;
42use crate::plot::radar::RadarPlot;
43use crate::plot::raincloud::RaincloudPlot;
44use crate::plot::ridgeline::RidgelinePlot;
45use crate::plot::roc::RocPlot;
46use crate::plot::rose::RosePlot;
47use crate::plot::sankey::SankeyPlot;
48use crate::plot::scatter3d::Scatter3DPlot;
49use crate::plot::slope::SlopePlot;
50use crate::plot::stacked_area::StackedAreaPlot;
51use crate::plot::streamgraph::StreamgraphPlot;
52use crate::plot::strip::StripPlot;
53use crate::plot::sunburst::SunburstPlot;
54use crate::plot::surface3d::Surface3DPlot;
55use crate::plot::survival::SurvivalPlot;
56use crate::plot::synteny::SyntenyPlot;
57use crate::plot::ternary::TernaryPlot;
58use crate::plot::text::TextPlot;
59use crate::plot::treemap::TreemapPlot;
60use crate::plot::upset::UpSetPlot;
61use crate::plot::venn::VennPlot;
62use crate::plot::volcano::VolcanoPlot;
63use crate::plot::waffle::WafflePlot;
64use crate::plot::waterfall::{WaterfallKind, WaterfallPlot};
65use crate::plot::{Heatmap, Histogram2D, PiePlot, SeriesPlot};
66use crate::render::render_utils;
67
68pub enum Plot {
69 Scatter(ScatterPlot),
70 Line(LinePlot),
71 Bar(BarPlot),
72 Histogram(Histogram),
73 Histogram2d(Histogram2D),
74 Box(BoxPlot),
75 Violin(ViolinPlot),
76 Series(SeriesPlot),
77 Pie(PiePlot),
78 Heatmap(Heatmap),
79 Brick(BrickPlot),
80 Band(BandPlot),
81 Waterfall(WaterfallPlot),
82 Strip(StripPlot),
83 Volcano(VolcanoPlot),
84 Manhattan(ManhattanPlot),
85 DotPlot(DotPlot),
86 UpSet(UpSetPlot),
87 StackedArea(StackedAreaPlot),
88 Candlestick(CandlestickPlot),
89 Contour(ContourPlot),
90 Chord(ChordPlot),
91 Sankey(SankeyPlot),
92 PhyloTree(PhyloTree),
93 Synteny(SyntenyPlot),
94 Density(DensityPlot),
95 Ridgeline(RidgelinePlot),
96 Polar(PolarPlot),
97 Ternary(TernaryPlot),
98 DicePlot(DicePlot),
99 Forest(ForestPlot),
100 Scatter3D(Scatter3DPlot),
101 Surface3D(Surface3DPlot),
102 Clustermap(Clustermap),
103 Joint(JointPlot),
104 Raincloud(RaincloudPlot),
105 Lollipop(LollipopPlot),
106 Survival(SurvivalPlot),
107 Roc(RocPlot),
108 Pr(PrPlot),
109 Slope(SlopePlot),
110 Venn(VennPlot),
111 Parallel(ParallelPlot),
112 Pareto(ParetoPlot),
113 Mosaic(MosaicPlot),
114 Ecdf(EcdfPlot),
115 QQ(QQPlot),
116 Network(NetworkPlot),
117 Streamgraph(StreamgraphPlot),
118 Radar(RadarPlot),
119 Hexbin(HexbinPlot),
120 Treemap(TreemapPlot),
121 Sunburst(SunburstPlot),
122 Bump(BumpPlot),
123 Funnel(FunnelPlot),
124 Rose(RosePlot),
125 Calendar(CalendarPlot),
126 Pyramid(PopulationPyramid),
127 Waffle(WafflePlot),
128 Horizon(HorizonPlot),
129 Gantt(GanttPlot),
130 Text(TextPlot),
131 LegendPlot(LegendPlot),
132 Quiver(QuiverPlot),
133}
134
135impl From<ScatterPlot> for Plot {
136 fn from(p: ScatterPlot) -> Self {
137 Plot::Scatter(p)
138 }
139}
140impl From<LinePlot> for Plot {
141 fn from(p: LinePlot) -> Self {
142 Plot::Line(p)
143 }
144}
145impl From<BarPlot> for Plot {
146 fn from(p: BarPlot) -> Self {
147 Plot::Bar(p)
148 }
149}
150impl From<Histogram> for Plot {
151 fn from(p: Histogram) -> Self {
152 Plot::Histogram(p)
153 }
154}
155impl From<Histogram2D> for Plot {
156 fn from(p: Histogram2D) -> Self {
157 Plot::Histogram2d(p)
158 }
159}
160impl From<BoxPlot> for Plot {
161 fn from(p: BoxPlot) -> Self {
162 Plot::Box(p)
163 }
164}
165impl From<ViolinPlot> for Plot {
166 fn from(p: ViolinPlot) -> Self {
167 Plot::Violin(p)
168 }
169}
170impl From<SeriesPlot> for Plot {
171 fn from(p: SeriesPlot) -> Self {
172 Plot::Series(p)
173 }
174}
175impl From<PiePlot> for Plot {
176 fn from(p: PiePlot) -> Self {
177 Plot::Pie(p)
178 }
179}
180impl From<Heatmap> for Plot {
181 fn from(p: Heatmap) -> Self {
182 Plot::Heatmap(p)
183 }
184}
185impl From<BrickPlot> for Plot {
186 fn from(p: BrickPlot) -> Self {
187 Plot::Brick(p)
188 }
189}
190impl From<BandPlot> for Plot {
191 fn from(p: BandPlot) -> Self {
192 Plot::Band(p)
193 }
194}
195impl From<WaterfallPlot> for Plot {
196 fn from(p: WaterfallPlot) -> Self {
197 Plot::Waterfall(p)
198 }
199}
200impl From<StripPlot> for Plot {
201 fn from(p: StripPlot) -> Self {
202 Plot::Strip(p)
203 }
204}
205impl From<VolcanoPlot> for Plot {
206 fn from(p: VolcanoPlot) -> Self {
207 Plot::Volcano(p)
208 }
209}
210impl From<ManhattanPlot> for Plot {
211 fn from(p: ManhattanPlot) -> Self {
212 Plot::Manhattan(p)
213 }
214}
215impl From<DotPlot> for Plot {
216 fn from(p: DotPlot) -> Self {
217 Plot::DotPlot(p)
218 }
219}
220impl From<UpSetPlot> for Plot {
221 fn from(p: UpSetPlot) -> Self {
222 Plot::UpSet(p)
223 }
224}
225impl From<StackedAreaPlot> for Plot {
226 fn from(p: StackedAreaPlot) -> Self {
227 Plot::StackedArea(p)
228 }
229}
230impl From<CandlestickPlot> for Plot {
231 fn from(p: CandlestickPlot) -> Self {
232 Plot::Candlestick(p)
233 }
234}
235impl From<ContourPlot> for Plot {
236 fn from(p: ContourPlot) -> Self {
237 Plot::Contour(p)
238 }
239}
240impl From<ChordPlot> for Plot {
241 fn from(p: ChordPlot) -> Self {
242 Plot::Chord(p)
243 }
244}
245impl From<SankeyPlot> for Plot {
246 fn from(p: SankeyPlot) -> Self {
247 Plot::Sankey(p)
248 }
249}
250impl From<PhyloTree> for Plot {
251 fn from(p: PhyloTree) -> Self {
252 Plot::PhyloTree(p)
253 }
254}
255impl From<SyntenyPlot> for Plot {
256 fn from(p: SyntenyPlot) -> Self {
257 Plot::Synteny(p)
258 }
259}
260impl From<DensityPlot> for Plot {
261 fn from(p: DensityPlot) -> Self {
262 Plot::Density(p)
263 }
264}
265impl From<RidgelinePlot> for Plot {
266 fn from(p: RidgelinePlot) -> Self {
267 Plot::Ridgeline(p)
268 }
269}
270impl From<PolarPlot> for Plot {
271 fn from(p: PolarPlot) -> Self {
272 Plot::Polar(p)
273 }
274}
275impl From<TernaryPlot> for Plot {
276 fn from(p: TernaryPlot) -> Self {
277 Plot::Ternary(p)
278 }
279}
280impl From<DicePlot> for Plot {
281 fn from(p: DicePlot) -> Self {
282 Plot::DicePlot(p)
283 }
284}
285impl From<ForestPlot> for Plot {
286 fn from(p: ForestPlot) -> Self {
287 Plot::Forest(p)
288 }
289}
290impl From<Scatter3DPlot> for Plot {
291 fn from(p: Scatter3DPlot) -> Self {
292 Plot::Scatter3D(p)
293 }
294}
295impl From<Surface3DPlot> for Plot {
296 fn from(p: Surface3DPlot) -> Self {
297 Plot::Surface3D(p)
298 }
299}
300impl From<Clustermap> for Plot {
301 fn from(p: Clustermap) -> Self {
302 Plot::Clustermap(p)
303 }
304}
305impl From<JointPlot> for Plot {
306 fn from(p: JointPlot) -> Self {
307 Plot::Joint(p)
308 }
309}
310impl From<RaincloudPlot> for Plot {
311 fn from(p: RaincloudPlot) -> Self {
312 Plot::Raincloud(p)
313 }
314}
315impl From<LollipopPlot> for Plot {
316 fn from(p: LollipopPlot) -> Self {
317 Plot::Lollipop(p)
318 }
319}
320impl From<SurvivalPlot> for Plot {
321 fn from(p: SurvivalPlot) -> Self {
322 Plot::Survival(p)
323 }
324}
325impl From<RocPlot> for Plot {
326 fn from(p: RocPlot) -> Self {
327 Plot::Roc(p)
328 }
329}
330impl From<PrPlot> for Plot {
331 fn from(p: PrPlot) -> Self {
332 Plot::Pr(p)
333 }
334}
335impl From<SlopePlot> for Plot {
336 fn from(p: SlopePlot) -> Self {
337 Plot::Slope(p)
338 }
339}
340impl From<VennPlot> for Plot {
341 fn from(p: VennPlot) -> Self {
342 Plot::Venn(p)
343 }
344}
345impl From<ParallelPlot> for Plot {
346 fn from(p: ParallelPlot) -> Self {
347 Plot::Parallel(p)
348 }
349}
350impl From<ParetoPlot> for Plot {
351 fn from(p: ParetoPlot) -> Self {
352 Plot::Pareto(p)
353 }
354}
355impl From<MosaicPlot> for Plot {
356 fn from(p: MosaicPlot) -> Self {
357 Plot::Mosaic(p)
358 }
359}
360impl From<EcdfPlot> for Plot {
361 fn from(p: EcdfPlot) -> Self {
362 Plot::Ecdf(p)
363 }
364}
365impl From<QQPlot> for Plot {
366 fn from(p: QQPlot) -> Self {
367 Plot::QQ(p)
368 }
369}
370impl From<NetworkPlot> for Plot {
371 fn from(p: NetworkPlot) -> Self {
372 Plot::Network(p)
373 }
374}
375impl From<StreamgraphPlot> for Plot {
376 fn from(p: StreamgraphPlot) -> Self {
377 Plot::Streamgraph(p)
378 }
379}
380impl From<RadarPlot> for Plot {
381 fn from(p: RadarPlot) -> Self {
382 Plot::Radar(p)
383 }
384}
385impl From<HexbinPlot> for Plot {
386 fn from(p: HexbinPlot) -> Self {
387 Plot::Hexbin(p)
388 }
389}
390impl From<TreemapPlot> for Plot {
391 fn from(p: TreemapPlot) -> Self {
392 Plot::Treemap(p)
393 }
394}
395impl From<SunburstPlot> for Plot {
396 fn from(p: SunburstPlot) -> Self {
397 Plot::Sunburst(p)
398 }
399}
400impl From<BumpPlot> for Plot {
401 fn from(p: BumpPlot) -> Self {
402 Plot::Bump(p)
403 }
404}
405impl From<FunnelPlot> for Plot {
406 fn from(p: FunnelPlot) -> Self {
407 Plot::Funnel(p)
408 }
409}
410impl From<RosePlot> for Plot {
411 fn from(p: RosePlot) -> Self {
412 Plot::Rose(p)
413 }
414}
415impl From<CalendarPlot> for Plot {
416 fn from(p: CalendarPlot) -> Self {
417 Plot::Calendar(p)
418 }
419}
420impl From<PopulationPyramid> for Plot {
421 fn from(p: PopulationPyramid) -> Self {
422 Plot::Pyramid(p)
423 }
424}
425impl From<WafflePlot> for Plot {
426 fn from(p: WafflePlot) -> Self {
427 Plot::Waffle(p)
428 }
429}
430impl From<HorizonPlot> for Plot {
431 fn from(p: HorizonPlot) -> Self {
432 Plot::Horizon(p)
433 }
434}
435impl From<GanttPlot> for Plot {
436 fn from(p: GanttPlot) -> Self {
437 Plot::Gantt(p)
438 }
439}
440impl From<TextPlot> for Plot {
441 fn from(p: TextPlot) -> Self {
442 Plot::Text(p)
443 }
444}
445impl From<LegendPlot> for Plot {
446 fn from(p: LegendPlot) -> Self {
447 Plot::LegendPlot(p)
448 }
449}
450impl From<QuiverPlot> for Plot {
451 fn from(p: QuiverPlot) -> Self {
452 Plot::Quiver(p)
453 }
454}
455
456use crate::plot::colormap::ColorMap;
457use crate::plot::plot3d::DataRanges3D;
458
459fn colorbar_from_z(
460 cmap: &ColorMap,
461 ranges: DataRanges3D,
462 label: Option<String>,
463) -> Option<ColorBarInfo> {
464 let (z_min, z_max) = ranges.z;
465 if !z_min.is_finite() || !z_max.is_finite() {
466 return None;
467 }
468 colorbar_linear(cmap, z_min, z_max, label)
469}
470
471pub(crate) fn colorbar_linear(
474 cmap: &ColorMap,
475 min: f64,
476 max: f64,
477 label: Option<String>,
478) -> Option<ColorBarInfo> {
479 if !min.is_finite() || !max.is_finite() {
480 return None;
481 }
482 let cmap = cmap.clone();
483 Some(ColorBarInfo {
484 map_fn: Arc::new(move |t| {
485 let norm = (t - min) / (max - min + f64::EPSILON);
486 cmap.map(norm.clamp(0.0, 1.0))
487 }),
488 min_value: min,
489 max_value: max,
490 label,
491 tick_labels: None,
492 tick_values: None,
493 })
494}
495
496fn bounds_from_2d<I>(points: I) -> Option<((f64, f64), (f64, f64))>
497where
498 I: IntoIterator,
499 I::Item: Into<(f64, f64)>,
500{
501 let mut iter = points.into_iter().map(Into::into);
502 let (x0, y0) = iter.next()?;
503 let (mut x_min, mut x_max) = (x0, x0);
504 let (mut y_min, mut y_max) = (y0, y0);
505 for (x, y) in iter {
506 x_min = x_min.min(x);
507 x_max = x_max.max(x);
508 y_min = y_min.min(y);
509 y_max = y_max.max(y);
510 }
511 Some(((x_min, x_max), (y_min, y_max)))
512}
513
514fn _bounds_from_1d(points: &[f64]) -> Option<((f64, f64), (f64, f64))> {
515 if points.is_empty() {
516 return None;
517 }
518 let (mut min_val, mut max_val) = (points[0], points[0]);
519 for i in points {
520 min_val = min_val.min(*i);
521 max_val = max_val.max(*i);
522 }
523
524 Some(((0.0f64, points.len() as f64), (min_val, max_val)))
525}
526
527impl Plot {
528 pub fn set_color(&mut self, color: &str) {
531 match self {
532 Plot::Scatter(s) => s.color = color.into(),
533 Plot::Line(l) => l.color = color.into(),
534 Plot::Series(s) => s.color = color.into(),
535 Plot::Histogram(h) => h.color = color.into(),
536 Plot::Box(b) => b.color = color.into(),
537 Plot::Violin(v) => v.color = color.into(),
538 Plot::Band(b) => b.color = color.into(),
539 Plot::Strip(s) => s.color = color.into(),
540 Plot::Density(d) => d.color = color.into(),
541 Plot::Forest(f) => f.color = color.into(),
542 Plot::Scatter3D(s) => s.color = color.into(),
543 Plot::Surface3D(s) => s.color = color.into(),
544 Plot::Raincloud(r) => r.color = color.into(),
545 Plot::Lollipop(l) => l.color = color.into(),
546 Plot::Survival(s) => s.color = color.into(),
547 Plot::Roc(r) => r.color = color.into(),
548 Plot::Pr(r) => r.color = color.into(),
549 Plot::Slope(s) => s.color = color.into(),
550 Plot::Parallel(p) => p.color = color.into(),
551 Plot::Ecdf(e) => e.color = color.into(),
552 Plot::QQ(q) => q.color = color.into(),
553 Plot::Quiver(q) => q.color = color.into(),
554 _ => {} }
556 }
557
558 pub fn bounds(&self) -> Option<((f64, f64), (f64, f64))> {
559 match self {
560 Plot::Scatter(s) => {
561 let ((mut x_min, mut x_max), (mut y_min, mut y_max)) = bounds_from_2d(&s.data)?;
562
563 for point in &s.data {
565 let x_lo = point.x - point.x_err.map_or(0.0, |e| e.0);
566 let x_hi = point.x + point.x_err.map_or(0.0, |e| e.1);
567 let y_lo = point.y - point.y_err.map_or(0.0, |e| e.0);
568 let y_hi = point.y + point.y_err.map_or(0.0, |e| e.1);
569
570 x_min = x_min.min(x_lo);
571 x_max = x_max.max(x_hi);
572 y_min = y_min.min(y_lo);
573 y_max = y_max.max(y_hi);
574 }
575
576 if let Some(ref band) = s.band {
578 for &y in &band.y_lower {
579 y_min = y_min.min(y);
580 }
581 for &y in &band.y_upper {
582 y_max = y_max.max(y);
583 }
584 }
585
586 if let Some(trend) = s.trend {
588 let TrendLine::Linear = trend;
589 if let Some((slope, intercept, _)) = render_utils::linear_regression(&s.data) {
590 let y_start = slope * x_min + intercept;
591 let y_end = slope * x_max + intercept;
592
593 y_min = y_min.min(y_start).min(y_end);
594 y_max = y_max.max(y_start).max(y_end);
595 }
596 }
597
598 Some(((x_min, x_max), (y_min, y_max)))
599 }
600 Plot::Line(p) => {
601 let ((x_min, x_max), (mut y_min, mut y_max)) = bounds_from_2d(&p.data)?;
602 if let Some(ref band) = p.band {
603 for &y in &band.y_lower {
604 y_min = y_min.min(y);
605 }
606 for &y in &band.y_upper {
607 y_max = y_max.max(y);
608 }
609 }
610 Some(((x_min, x_max), (y_min, y_max)))
611 }
612 Plot::Series(sp) => {
614 if sp.values.is_empty() {
615 None
616 } else {
617 let x_min = 0.0;
618 let x_max = sp.values.len() as f64 - 1.0;
619
620 let mut y_min = f64::INFINITY;
621 let mut y_max = f64::NEG_INFINITY;
622
623 for &v in &sp.values {
624 y_min = y_min.min(v);
625 y_max = y_max.max(v);
626 }
627
628 Some(((x_min, x_max), (y_min, y_max)))
629 }
630 }
631 Plot::Bar(bp) => {
632 if bp.groups.is_empty() {
633 None
634 } else {
635 let cat_min = 0.5;
636 let cat_max = bp.groups.len() as f64 + 0.5;
637 let mut data_min: f64 = 0.0;
638
639 let mut data_max = f64::NEG_INFINITY;
640 let mut flat_i = 0usize;
641 if bp.stacked {
642 for group in &bp.groups {
643 let mut accum = 0.0;
644 for bar in &group.bars {
645 accum += bar.value;
646 let (lo, hi) = bp
647 .errors
648 .as_ref()
649 .and_then(|e| e.get(flat_i))
650 .copied()
651 .unwrap_or((0.0, 0.0));
652 data_max = data_max.max(accum + hi);
653 data_min = data_min.min(accum - lo);
654 flat_i += 1;
655 }
656 }
657 } else {
658 for group in &bp.groups {
659 for bar in &group.bars {
660 let (lo, hi) = bp
661 .errors
662 .as_ref()
663 .and_then(|e| e.get(flat_i))
664 .copied()
665 .unwrap_or((0.0, 0.0));
666 data_max = data_max.max(bar.value + hi);
667 data_min = data_min.min(bar.value - lo);
668 flat_i += 1;
669 }
670 }
671 }
672
673 if bp.horizontal {
674 Some(((data_min, data_max), (cat_min, cat_max)))
675 } else {
676 Some(((cat_min, cat_max), (data_min, data_max)))
677 }
678 }
679 }
680 Plot::Pareto(pp) => {
681 if pp.categories.is_empty() {
682 None
683 } else {
684 let bars = pp.render_bars();
685 let cat_min = 0.5;
686 let cat_max = bars.len() as f64 + 0.5;
687 let data_max = bars.iter().map(|b| b.value()).fold(0.0_f64, f64::max);
688 if pp.horizontal {
689 Some(((0.0, data_max), (cat_min, cat_max)))
690 } else {
691 Some(((cat_min, cat_max), (0.0, data_max)))
692 }
693 }
694 }
695 Plot::Histogram(h) => {
696 if let Some((edges, counts)) = &h.precomputed {
698 if edges.len() < 2 || counts.is_empty() {
699 return None;
700 }
701 let x_min = edges[0];
702 let x_max = *edges.last().unwrap();
703 let max_y = if h.normalize {
704 1.0
705 } else {
706 counts.iter().cloned().fold(0.0_f64, f64::max)
707 };
708 return Some(((x_min, x_max), (0.0, max_y)));
709 }
710 let range = h.range.unwrap_or_else(|| {
713 if h.data.is_empty() {
714 return (0.0, 1.0);
715 }
716 let min = h.data.iter().cloned().fold(f64::INFINITY, f64::min);
717 let max = h.data.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
718 (min, max)
719 });
720 let bins = h.bins;
721 let bin_width = (range.1 - range.0) / bins as f64;
722
723 let mut counts = vec![0usize; bins];
724 for &value in &h.data {
725 if value < range.0 || value > range.1 {
726 continue;
727 }
728 let bin = ((value - range.0) / bin_width).floor() as usize;
729 let bin = if bin == bins { bin - 1 } else { bin };
730 counts[bin] += 1;
731 }
732
733 let max_count = *counts.iter().max().unwrap_or(&1) as f64;
734 let mut max_y = if h.normalize { 1.0 } else { max_count };
735
736 if h.show_kde && h.data.len() >= 2 {
737 let bw = h
738 .kde_bandwidth
739 .unwrap_or_else(|| render_utils::silverman_bandwidth(&h.data));
740 let n = h.data.len() as f64;
741 let density_norm = 1.0 / (n * bw * (2.0 * std::f64::consts::PI).sqrt());
742 let kde = render_utils::simple_kde(&h.data, bw, h.kde_samples);
743 let peak_density = kde.iter().map(|(_, y)| *y).fold(0.0_f64, f64::max);
744 let scale_norm = if h.normalize { 1.0 / max_count } else { 1.0 };
745 let kde_peak_height = peak_density * density_norm * n * bin_width * scale_norm;
746 max_y = max_y.max(kde_peak_height);
747 }
748
749 Some((range, (0.0, max_y)))
750 }
751 Plot::Box(bp) => {
752 if bp.groups.is_empty() {
753 None
754 } else {
755 let cat_min = 0.5;
756 let cat_max = bp.groups.len() as f64 + 0.5;
757
758 let mut data_min = f64::INFINITY;
759 let mut data_max = f64::NEG_INFINITY;
760 for g in &bp.groups {
761 if g.values.is_empty() {
762 continue;
763 }
764 let mut vals = g.values.clone();
765 vals.sort_by(|a, b| a.total_cmp(b));
766 let q1 = render_utils::percentile(&vals, 25.0);
767 let q3 = render_utils::percentile(&vals, 75.0);
768 let iqr = q3 - q1;
769 let lo = q1 - 1.5 * iqr;
770 let hi = q3 + 1.5 * iqr;
771 data_min = data_min.min(lo);
772 data_max = data_max.max(hi);
773 }
774
775 if bp.horizontal {
776 Some(((data_min, data_max), (cat_min, cat_max)))
777 } else {
778 Some(((cat_min, cat_max), (data_min, data_max)))
779 }
780 }
781 }
782 Plot::Violin(vp) => {
783 if vp.groups.is_empty() {
784 None
785 } else {
786 let cat_min = 0.5;
787 let cat_max = vp.groups.len() as f64 + 0.5;
788
789 let mut data_min = f64::INFINITY;
790 let mut data_max = f64::NEG_INFINITY;
791
792 let groups_iter = vp.groups.iter().chain(vp.split_groups.iter());
793 for group in groups_iter {
794 if group.values.is_empty() {
795 continue;
796 }
797 let g_min = group.values.iter().cloned().fold(f64::INFINITY, f64::min);
798 let g_max = group
799 .values
800 .iter()
801 .cloned()
802 .fold(f64::NEG_INFINITY, f64::max);
803 let h = vp
804 .bandwidth
805 .unwrap_or_else(|| render_utils::silverman_bandwidth(&group.values));
806 data_min = data_min.min(g_min - 3.0 * h);
807 data_max = data_max.max(g_max + 3.0 * h);
808 }
809
810 if vp.horizontal {
811 Some(((data_min, data_max), (cat_min, cat_max)))
812 } else {
813 Some(((cat_min, cat_max), (data_min, data_max)))
814 }
815 }
816 }
817 Plot::Pie(_) => {
818 Some(((-1.0, 1.0), (-1.0, 1.0)))
820 }
821 Plot::Heatmap(hm) => {
822 let rows = hm.data.len();
823 let cols = hm.data.first().map_or(0, |row| row.len());
824 let x = hm.x_range.unwrap_or((0.5, cols as f64 + 0.5));
825 let y = hm.y_range.unwrap_or((0.5, rows as f64 + 0.5));
826 Some((x, y))
827 }
828 Plot::Histogram2d(h2d) => {
829 Some((
832 (h2d.x_range.0, h2d.x_range.1),
833 (h2d.y_range.0, h2d.y_range.1),
834 ))
835 }
836 Plot::Band(b) => {
837 if b.x.is_empty() {
838 return None;
839 }
840 let x_min = b.x.iter().cloned().fold(f64::INFINITY, f64::min);
841 let x_max = b.x.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
842 let y_min = b.y_lower.iter().cloned().fold(f64::INFINITY, f64::min);
843 let y_max = b.y_upper.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
844 Some(((x_min, x_max), (y_min, y_max)))
845 }
846 Plot::Waterfall(wp) => {
847 if wp.bars.is_empty() {
848 return None;
849 }
850 let x_min = 0.5;
851 let x_max = wp.bars.len() as f64 + 0.5;
852 let mut running = 0.0_f64;
853 let mut y_min = 0.0_f64;
854 let mut y_max = 0.0_f64;
855 for bar in &wp.bars {
856 match bar.kind {
857 WaterfallKind::Delta => {
858 let base = running;
859 running += bar.value;
860 y_min = y_min.min(base).min(running);
861 y_max = y_max.max(base).max(running);
862 }
863 WaterfallKind::Total => {
864 y_min = y_min.min(0.0).min(running);
865 y_max = y_max.max(0.0).max(running);
866 }
867 WaterfallKind::Difference { from, to } => {
868 y_min = y_min.min(from).min(to);
869 y_max = y_max.max(from).max(to);
870 }
871 }
872 }
873 Some(((x_min, x_max), (y_min, y_max)))
874 }
875 Plot::Strip(sp) => {
876 if sp.groups.is_empty() {
877 return None;
878 }
879 let x_min = 0.5;
880 let x_max = sp.groups.len() as f64 + 0.5;
881 let mut y_min = f64::INFINITY;
882 let mut y_max = f64::NEG_INFINITY;
883 for g in &sp.groups {
884 for &v in &g.values {
885 y_min = y_min.min(v);
886 y_max = y_max.max(v);
887 }
888 }
889 if y_min == f64::INFINITY {
890 return None;
891 }
892 Some(((x_min, x_max), (y_min, y_max)))
893 }
894 Plot::Volcano(vp) => {
895 if vp.points.is_empty() {
896 return None;
897 }
898 let floor = vp.floor();
899 let mut x_min = f64::INFINITY;
900 let mut x_max = f64::NEG_INFINITY;
901 let mut y_max = f64::NEG_INFINITY;
902 for p in &vp.points {
903 x_min = x_min.min(p.log2fc);
904 x_max = x_max.max(p.log2fc);
905 let y = -(p.pvalue.max(floor)).log10();
906 y_max = y_max.max(y);
907 }
908 Some(((x_min, x_max), (0.0, y_max)))
909 }
910 Plot::Manhattan(mp) => {
911 if mp.points.is_empty() {
912 return None;
913 }
914 let floor = mp.floor();
915 let x_min = mp
916 .spans
917 .iter()
918 .map(|s| s.x_start)
919 .fold(f64::INFINITY, f64::min);
920 let x_max = mp
921 .spans
922 .iter()
923 .map(|s| s.x_end)
924 .fold(f64::NEG_INFINITY, f64::max);
925 if !x_min.is_finite() {
926 return None;
927 }
928 let y_max = mp
930 .points
931 .iter()
932 .map(|p| -(p.pvalue.max(floor)).log10())
933 .fold(mp.genome_wide, f64::max);
934 Some(((x_min, x_max), (0.0, y_max)))
935 }
936 Plot::DotPlot(dp) => {
937 if dp.x_categories.is_empty() {
938 return None;
939 }
940 Some((
941 (0.5, dp.x_categories.len() as f64 + 0.5),
942 (0.5, dp.y_categories.len() as f64 + 0.5),
943 ))
944 }
945 Plot::DicePlot(dp) => {
946 if dp.x_categories.is_empty() {
947 return None;
948 }
949 Some((
950 (0.5, dp.x_categories.len() as f64 + 0.5),
951 (0.5, dp.y_categories.len() as f64 + 0.5),
952 ))
953 }
954 Plot::UpSet(_) => {
955 Some(((0.0, 1.0), (0.0, 1.0)))
957 }
958 Plot::StackedArea(sa) => {
959 if sa.x.is_empty() || sa.series.is_empty() {
960 return None;
961 }
962 let x_min = sa.x.iter().cloned().fold(f64::INFINITY, f64::min);
963 let x_max = sa.x.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
964 let n = sa.x.len();
965 let y_max = if sa.normalized {
966 100.0
967 } else {
968 (0..n)
969 .map(|i| {
970 sa.series
971 .iter()
972 .map(|s| s.get(i).copied().unwrap_or(0.0))
973 .sum::<f64>()
974 })
975 .fold(0.0_f64, f64::max)
976 };
977 Some(((x_min, x_max), (0.0, y_max)))
978 }
979 Plot::Candlestick(cp) => {
980 if cp.candles.is_empty() {
981 return None;
982 }
983 let continuous = cp.candles.iter().any(|c| c.x.is_some());
984 let (x_min, x_max) = if continuous {
985 (
986 cp.candles
987 .iter()
988 .filter_map(|c| c.x)
989 .fold(f64::INFINITY, f64::min),
990 cp.candles
991 .iter()
992 .filter_map(|c| c.x)
993 .fold(f64::NEG_INFINITY, f64::max),
994 )
995 } else {
996 (0.5, cp.candles.len() as f64 + 0.5)
997 };
998 let y_min = cp
999 .candles
1000 .iter()
1001 .map(|c| c.low)
1002 .fold(f64::INFINITY, f64::min);
1003 let y_max = cp
1004 .candles
1005 .iter()
1006 .map(|c| c.high)
1007 .fold(f64::NEG_INFINITY, f64::max);
1008 Some(((x_min, x_max), (y_min, y_max)))
1009 }
1010 Plot::Contour(cp) => {
1011 if cp.z.is_empty() {
1012 return None;
1013 }
1014 let x_min = cp.x_coords.iter().cloned().fold(f64::INFINITY, f64::min);
1015 let x_max = cp
1016 .x_coords
1017 .iter()
1018 .cloned()
1019 .fold(f64::NEG_INFINITY, f64::max);
1020 let y_min = cp.y_coords.iter().cloned().fold(f64::INFINITY, f64::min);
1021 let y_max = cp
1022 .y_coords
1023 .iter()
1024 .cloned()
1025 .fold(f64::NEG_INFINITY, f64::max);
1026 Some(((x_min, x_max), (y_min, y_max)))
1027 }
1028 Plot::Chord(_) => {
1029 Some(((0.0, 1.0), (0.0, 1.0)))
1031 }
1032 Plot::Sankey(_) => {
1033 Some(((0.0, 1.0), (0.0, 1.0)))
1035 }
1036 Plot::PhyloTree(_) => {
1037 Some(((0.0, 1.0), (0.0, 1.0)))
1039 }
1040 Plot::Synteny(_) => {
1041 Some(((0.0, 1.0), (0.0, 1.0)))
1043 }
1044 Plot::Density(dp) => {
1045 if let Some((xs, ys)) = &dp.precomputed {
1047 if xs.is_empty() {
1048 return None;
1049 }
1050 let x_min = xs.iter().cloned().fold(f64::INFINITY, f64::min);
1051 let x_max = xs.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
1052 let y_min = if dp.fit_y {
1053 ys.iter().cloned().fold(f64::INFINITY, f64::min)
1054 } else {
1055 0.0
1056 };
1057 let y_max = ys.iter().cloned().fold(0.0_f64, f64::max);
1058 return Some(((x_min, x_max), (y_min, y_max * 1.1)));
1059 }
1060 if dp.data.len() < 2 {
1061 return None;
1062 }
1063 let bw = dp
1064 .bandwidth
1065 .unwrap_or_else(|| render_utils::silverman_bandwidth(&dp.data));
1066 let data_min = dp.data.iter().cloned().fold(f64::INFINITY, f64::min);
1067 let data_max = dp.data.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
1068 let x_min = dp.x_lo.unwrap_or(data_min - 3.0 * bw);
1069 let x_max = dp.x_hi.unwrap_or(data_max + 3.0 * bw);
1070 let n = dp.data.len() as f64;
1073 let norm = 1.0 / (n * bw * (2.0 * std::f64::consts::PI).sqrt());
1074 let curve = if dp.x_lo.is_some() || dp.x_hi.is_some() {
1075 render_utils::simple_kde_reflect(
1076 &dp.data,
1077 bw,
1078 dp.kde_samples,
1079 x_min,
1080 x_max,
1081 dp.x_lo.is_some(),
1082 dp.x_hi.is_some(),
1083 )
1084 } else {
1085 render_utils::simple_kde(&dp.data, bw, dp.kde_samples)
1086 };
1087 let y_max_pdf = curve.iter().map(|(_, y)| y * norm).fold(0.0_f64, f64::max);
1088 Some(((x_min, x_max), (0.0, y_max_pdf * 1.1)))
1089 }
1090 Plot::Ridgeline(rp) => {
1091 if rp.groups.is_empty() {
1092 return None;
1093 }
1094 let n = rp.groups.len() as f64;
1095 let mut x_min = f64::INFINITY;
1096 let mut x_max = f64::NEG_INFINITY;
1097 for g in &rp.groups {
1098 if g.values.is_empty() {
1099 continue;
1100 }
1101 let bw = rp
1102 .bandwidth
1103 .unwrap_or_else(|| render_utils::silverman_bandwidth(&g.values));
1104 let gmin = g.values.iter().cloned().fold(f64::INFINITY, f64::min);
1105 let gmax = g.values.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
1106 x_min = x_min.min(gmin - 3.0 * bw);
1107 x_max = x_max.max(gmax + 3.0 * bw);
1108 }
1109 if !x_min.is_finite() {
1110 return None;
1111 }
1112 Some(((x_min, x_max), (0.5, n + 1.5 + rp.overlap)))
1116 }
1117 Plot::Polar(_) => {
1118 Some(((-1.0, 1.0), (-1.0, 1.0)))
1120 }
1121 Plot::Ternary(_) => {
1122 Some(((-1.0, 1.0), (-1.0, 1.0)))
1124 }
1125 Plot::Brick(bp) => {
1126 let rows = if let Some(ref exp) = bp.strigar_exp {
1127 exp.len()
1128 } else {
1129 bp.sequences.len()
1130 };
1131
1132 let n_rows = rows;
1133
1134 let str_width = |i: usize| -> f64 {
1136 if let Some(ref exp) = bp.strigar_exp {
1137 if let Some(ref ml) = bp.motif_lengths {
1138 exp.get(i)
1139 .map(|s| {
1140 s.chars()
1141 .map(|c| *ml.get(&c).unwrap_or(&1) as f64)
1142 .sum::<f64>()
1143 })
1144 .unwrap_or(0.0)
1145 } else {
1146 exp.get(i).map(|s| s.len() as f64).unwrap_or(0.0)
1147 }
1148 } else {
1149 bp.sequences.get(i).map(|s| s.len() as f64).unwrap_or(0.0)
1150 }
1151 };
1152 let left_len = |i: usize| -> f64 {
1153 bp.left_flanks
1154 .as_ref()
1155 .and_then(|f| f.get(i))
1156 .map(|s| s.chars().count() as f64)
1157 .unwrap_or(0.0)
1158 };
1159 let right_len = |i: usize| -> f64 {
1160 bp.right_flanks
1161 .as_ref()
1162 .and_then(|f| f.get(i))
1163 .map(|s| s.chars().count() as f64)
1164 .unwrap_or(0.0)
1165 };
1166
1167 use crate::plot::BrickAnchor;
1171 let right_edges: Vec<f64> =
1172 (0..n_rows).map(|i| str_width(i) + right_len(i)).collect();
1173 let max_right = right_edges.iter().cloned().fold(0.0_f64, f64::max);
1174 let ra_shift = |i: usize| -> f64 {
1175 if bp.anchor == BrickAnchor::Right {
1176 max_right - right_edges[i]
1177 } else {
1178 0.0
1179 }
1180 };
1181
1182 let row_base_off = |i: usize| -> f64 {
1183 let per_row = if let Some(ref offsets) = bp.x_offsets {
1184 offsets.get(i).copied().flatten().unwrap_or(bp.x_offset)
1185 } else {
1186 bp.x_offset
1187 };
1188 per_row + bp.x_origin
1189 };
1190
1191 let mut lo = f64::INFINITY;
1193 let mut hi = f64::NEG_INFINITY;
1194 for i in 0..n_rows {
1195 let eff_off = row_base_off(i) - ra_shift(i);
1196 lo = lo.min(-left_len(i) - eff_off);
1197 hi = hi.max(str_width(i) + right_len(i) - eff_off);
1198 }
1199 if !lo.is_finite() {
1200 lo = 0.0;
1201 }
1202 if !hi.is_finite() {
1203 hi = 1.0;
1204 }
1205
1206 Some(((lo, hi), (0.0, rows as f64)))
1207 }
1208 Plot::Forest(fp) => {
1209 if fp.rows.is_empty() {
1210 return None;
1211 }
1212 let n = fp.rows.len();
1213 let y_min = 0.5;
1214 let y_max = n as f64 + 0.5;
1215 let mut x_min = f64::INFINITY;
1216 let mut x_max = f64::NEG_INFINITY;
1217 for row in &fp.rows {
1218 x_min = x_min.min(row.ci_lower);
1219 x_max = x_max.max(row.ci_upper);
1220 }
1221 if let Some(nv) = fp.null_value {
1223 x_min = x_min.min(nv);
1224 x_max = x_max.max(nv);
1225 }
1226 if !x_min.is_finite() {
1227 return None;
1228 }
1229 Some(((x_min, x_max), (y_min, y_max)))
1230 }
1231 Plot::Scatter3D(_) | Plot::Surface3D(_) => Some(((-1.0, 1.0), (-1.0, 1.0))),
1232 Plot::Clustermap(_) => Some(((0.0, 1.0), (0.0, 1.0))),
1234 Plot::Joint(_) => Some(((-1.0, 1.0), (-1.0, 1.0))),
1236 Plot::Raincloud(r) => {
1237 let n = r.groups.len();
1238 if n == 0 {
1239 return None;
1240 }
1241 let all_vals: Vec<f64> = r
1242 .groups
1243 .iter()
1244 .flat_map(|g| g.values.iter().copied())
1245 .collect();
1246 if all_vals.is_empty() {
1247 return None;
1248 }
1249 let data_min = all_vals.iter().cloned().fold(f64::INFINITY, f64::min);
1250 let data_max = all_vals.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
1251 let pad = (data_max - data_min) * 0.05 + 0.5;
1252 if r.horizontal {
1253 Some(((data_min - pad, data_max + pad), (0.5, n as f64 + 0.5)))
1254 } else {
1255 Some(((0.5, n as f64 + 0.5), (data_min - pad, data_max + pad)))
1256 }
1257 }
1258 Plot::Survival(sp) => {
1259 if sp.groups.is_empty() {
1260 return None;
1261 }
1262 let t_max = sp
1263 .groups
1264 .iter()
1265 .flat_map(|g| g.times.iter().copied())
1266 .fold(0.0_f64, f64::max);
1267 if t_max <= 0.0 {
1268 return None;
1269 }
1270 Some(((0.0, t_max), (0.0, 1.0)))
1272 }
1273 Plot::Lollipop(lp) => {
1274 if lp.points.is_empty() {
1275 return None;
1276 }
1277 let mut x_min = f64::INFINITY;
1278 let mut x_max = f64::NEG_INFINITY;
1279 let mut y_min = lp.baseline;
1280 let mut y_max = lp.baseline;
1281 for p in &lp.points {
1282 x_min = x_min.min(p.x);
1283 x_max = x_max.max(p.x);
1284 y_min = y_min.min(p.y);
1285 y_max = y_max.max(p.y);
1286 }
1287 for d in &lp.domains {
1288 x_min = x_min.min(d.x_start);
1289 x_max = x_max.max(d.x_end);
1290 }
1291 if !lp.domains.is_empty() {
1292 y_min = y_min.min(lp.baseline - lp.domain_height);
1293 }
1294 if !x_min.is_finite() {
1295 return None;
1296 }
1297 let x_span = (x_max - x_min).max(1.0);
1299 x_min -= x_span * 0.04;
1300 x_max += x_span * 0.04;
1301 Some(((x_min, x_max), (y_min, y_max)))
1302 }
1303 Plot::Roc(_) => Some(((0.0, 1.0), (0.0, 1.0))),
1304 Plot::Pr(_) => Some(((0.0, 1.0), (0.0, 1.0))),
1305 Plot::Slope(s) => {
1306 let n = s.points.len();
1307 if n == 0 {
1308 return None;
1309 }
1310 let mut x_min = f64::INFINITY;
1311 let mut x_max = f64::NEG_INFINITY;
1312 for p in &s.points {
1313 x_min = x_min.min(p.before).min(p.after);
1314 x_max = x_max.max(p.before).max(p.after);
1315 }
1316 if !x_min.is_finite() {
1317 return None;
1318 }
1319 let pad = (x_max - x_min) * 0.08 + 1e-9;
1320 Some(((x_min - pad, x_max + pad), (0.5, n as f64 + 0.5)))
1321 }
1322 Plot::Venn(_) => Some(((-1.0, 1.0), (-1.0, 1.0))),
1324 Plot::Parallel(_) => Some(((-1.0, 1.0), (-1.0, 1.0))),
1326 Plot::Mosaic(_) => None,
1328 Plot::Ecdf(ep) => {
1329 if ep.groups.is_empty() {
1330 return None;
1331 }
1332 let mut x_min = f64::INFINITY;
1333 let mut x_max = f64::NEG_INFINITY;
1334 for group in &ep.groups {
1335 for &v in &group.data {
1336 x_min = x_min.min(v);
1337 x_max = x_max.max(v);
1338 }
1339 }
1340 if !x_min.is_finite() {
1341 return None;
1342 }
1343 Some(((x_min, x_max), (0.0, 1.0)))
1344 }
1345 Plot::QQ(qp) => {
1346 use crate::plot::qq::QQMode;
1347 use crate::render::render_utils::probit;
1348 if qp.groups.is_empty() {
1349 return None;
1350 }
1351 match qp.mode {
1352 QQMode::Normal => {
1353 let n_max = qp.groups.iter().map(|g| g.data.len()).max().unwrap_or(0);
1354 if n_max == 0 {
1355 return None;
1356 }
1357 let th_min = probit(0.5 / n_max as f64);
1358 let th_max = probit(1.0 - 0.5 / n_max as f64);
1359 let mut y_min = f64::INFINITY;
1360 let mut y_max = f64::NEG_INFINITY;
1361 for g in &qp.groups {
1362 for &v in &g.data {
1363 y_min = y_min.min(v);
1364 y_max = y_max.max(v);
1365 }
1366 }
1367 if !y_min.is_finite() {
1368 return None;
1369 }
1370 Some(((th_min, th_max), (y_min, y_max)))
1371 }
1372 QQMode::Genomic => {
1373 let n_max = qp.groups.iter().map(|g| g.data.len()).max().unwrap_or(0);
1374 if n_max == 0 {
1375 return None;
1376 }
1377 let x_max = (2.0 * n_max as f64).log10();
1378 let mut y_max: f64 = 0.0;
1379 for g in &qp.groups {
1380 for &p in &g.data {
1381 if p > 0.0 && p <= 1.0 {
1382 y_max = y_max.max(-p.log10());
1383 }
1384 }
1385 }
1386 Some(((0.0, x_max), (0.0, y_max)))
1387 }
1388 }
1389 }
1390 Plot::Hexbin(hb) => {
1391 if hb.x.is_empty() {
1392 return None;
1393 }
1394 let x0 = hb
1395 .x_range
1396 .map(|(lo, _)| lo)
1397 .unwrap_or_else(|| hb.x.iter().cloned().fold(f64::INFINITY, f64::min));
1398 let x1 = hb
1399 .x_range
1400 .map(|(_, hi)| hi)
1401 .unwrap_or_else(|| hb.x.iter().cloned().fold(f64::NEG_INFINITY, f64::max));
1402 let y0 = hb
1403 .y_range
1404 .map(|(lo, _)| lo)
1405 .unwrap_or_else(|| hb.y.iter().cloned().fold(f64::INFINITY, f64::min));
1406 let y1 = hb
1407 .y_range
1408 .map(|(_, hi)| hi)
1409 .unwrap_or_else(|| hb.y.iter().cloned().fold(f64::NEG_INFINITY, f64::max));
1410 Some(((x0, x1), (y0, y1)))
1411 }
1412 Plot::Treemap(_) => Some(((-1.0, 1.0), (-1.0, 1.0))),
1414 Plot::Sunburst(_) => Some(((-1.0, 1.0), (-1.0, 1.0))),
1415 Plot::Funnel(_) => Some(((-1.0, 1.0), (-1.0, 1.0))),
1416 Plot::Rose(_) => Some(((-1.0, 1.0), (-1.0, 1.0))),
1417 Plot::Calendar(_) => Some(((-1.0, 1.0), (-1.0, 1.0))),
1418 Plot::Bump(bp) => {
1419 let n = bp.total_series_count();
1420 let n_time = bp.n_time_points();
1421 if n == 0 || n_time == 0 {
1422 Some(((0.5, 1.5), (0.5, 1.5)))
1423 } else {
1424 Some(((0.5, n_time as f64 + 0.5), (0.5, n as f64 + 0.5)))
1425 }
1426 }
1427 Plot::Pyramid(pp) => {
1428 let n = pp.n_groups();
1429 if n == 0 {
1430 return None;
1431 }
1432 let max_val = pp.max_value();
1433 if max_val <= 0.0 {
1434 return None;
1435 }
1436 Some(((-max_val, max_val), (0.5, n as f64 + 0.5)))
1437 }
1438 Plot::Waffle(_) => Some(((-1.0, 1.0), (-1.0, 1.0))),
1439 Plot::Horizon(hp) => {
1440 let n = hp.series.len();
1441 if n == 0 {
1442 return None;
1443 }
1444 let (x_min, x_max) = hp.x_range()?;
1445 Some(((x_min, x_max), (0.5, n as f64 + 0.5)))
1446 }
1447 Plot::Gantt(gp) => {
1448 let rows = gp.ordered_display_rows();
1449 let n = rows.len();
1450 if n == 0 {
1451 return None;
1452 }
1453 let (x_min, x_max) = gp.x_bounds()?;
1454 Some(((x_min, x_max), (0.5, n as f64 + 0.5)))
1455 }
1456 Plot::LegendPlot(_) => None,
1458 Plot::Text(_) => Some(((0.0, 1.0), (0.0, 1.0))),
1460 Plot::Network(_) => Some(((0.0, 1.0), (0.0, 1.0))),
1461 Plot::Radar(_) => Some(((0.0, 1.0), (0.0, 1.0))),
1462 Plot::Quiver(q) => {
1463 if q.arrows.is_empty() {
1464 return None;
1465 }
1466 let (scale, x_min_d, x_max_d, y_min_d, y_max_d) =
1469 q.effective_scale_and_data_extent();
1470 if !x_min_d.is_finite() {
1471 return None;
1472 }
1473 if q.tight_bounds {
1474 return Some(((x_min_d, x_max_d), (y_min_d, y_max_d)));
1475 }
1476 let mut x_min = f64::INFINITY;
1477 let mut x_max = f64::NEG_INFINITY;
1478 let mut y_min = f64::INFINITY;
1479 let mut y_max = f64::NEG_INFINITY;
1480 for a in &q.arrows {
1481 let (tail, tip) = q.endpoints_with_scale(a, scale);
1482 x_min = x_min.min(tail.0).min(tip.0);
1483 x_max = x_max.max(tail.0).max(tip.0);
1484 y_min = y_min.min(tail.1).min(tip.1);
1485 y_max = y_max.max(tail.1).max(tip.1);
1486 }
1487 if !x_min.is_finite() {
1488 return None;
1489 }
1490 Some(((x_min, x_max), (y_min, y_max)))
1491 }
1492 Plot::Streamgraph(sg) => {
1493 let geom = sg.compute_geometry()?;
1494 let x_min = sg.x.iter().cloned().fold(f64::INFINITY, f64::min);
1495 let x_max = sg.x.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
1496 let y_min = geom.baseline.iter().cloned().fold(f64::INFINITY, f64::min);
1497 let y_max = geom
1498 .uppers
1499 .last()
1500 .map(|u| u.iter().cloned().fold(f64::NEG_INFINITY, f64::max))
1501 .unwrap_or(0.0);
1502 Some(((x_min, x_max), (y_min, y_max)))
1503 }
1504 }
1505 }
1506
1507 pub fn estimated_primitives(&self) -> usize {
1510 match self {
1511 Plot::Scatter(s) => {
1512 let n = s.data.len();
1513 let err = if s
1514 .data
1515 .iter()
1516 .any(|p| p.x_err.is_some() || p.y_err.is_some())
1517 {
1518 n * 3
1519 } else {
1520 0
1521 };
1522 n + err + 10
1523 }
1524 Plot::Line(l) => l.data.len() / 10 + 10,
1525 Plot::Series(s) => s.values.len() / 10 + 10,
1526 Plot::Manhattan(m) => m.points.len() + m.spans.len() * 2 + 30,
1527 Plot::Heatmap(h) => {
1528 let cells: usize = h.data.iter().map(|r| r.len()).sum();
1529 (if h.show_values { cells * 2 } else { cells }) + 10
1530 }
1531 Plot::Histogram2d(h) => h.bins.iter().map(|r| r.len()).sum::<usize>() + 10,
1532 Plot::Violin(v) => v.groups.len() * 20 + 10,
1533 Plot::Bar(b) => b.groups.iter().map(|g| g.bars.len()).sum::<usize>() * 2 + 10,
1534 Plot::Histogram(h) => h.bins * 2 + 10,
1535 Plot::Brick(b) => {
1536 let rows = if b.strigar_exp.is_some() {
1537 b.strigar_exp.as_ref().map_or(0, |e| e.len())
1538 } else {
1539 b.sequences.len()
1540 };
1541 let avg_cols = b.sequences.first().map_or(10, |s| s.len());
1542 rows * avg_cols + 10
1543 }
1544 Plot::Forest(f) => f.rows.len() * 4 + 5,
1545 Plot::Scatter3D(s) => s.data.len() + 70,
1546 Plot::Surface3D(s) => {
1547 let n = s.nrows().saturating_sub(1) * s.ncols().saturating_sub(1);
1548 n + 70
1549 }
1550 Plot::Clustermap(c) => {
1551 let cells: usize = c.data.iter().map(|r| r.len()).sum();
1552 cells + 500
1553 }
1554 Plot::Joint(jp) => {
1555 jp.groups
1556 .iter()
1557 .map(|g| g.scatter.data.len() * 3 + 200)
1558 .sum::<usize>()
1559 + 100
1560 }
1561 Plot::Raincloud(r) => {
1562 let total_pts: usize = r.groups.iter().map(|g| g.values.len()).sum();
1563 r.groups.len() * 30 + total_pts + 10
1564 }
1565 Plot::Survival(sp) => sp.groups.iter().map(|g| g.times.len() * 3 + 20).sum(),
1566 Plot::Lollipop(lp) => lp.points.len() * 2 + lp.domains.len() * 2 + 5,
1567 Plot::Roc(r) => {
1568 r.groups
1569 .iter()
1570 .map(|g| g.raw_predictions.as_ref().map(|p| p.len()).unwrap_or(100) * 2 + 50)
1571 .sum::<usize>()
1572 + 10
1573 }
1574 Plot::Pr(r) => {
1575 r.groups
1576 .iter()
1577 .map(|g| g.raw_predictions.as_ref().map(|p| p.len()).unwrap_or(100) * 2 + 50)
1578 .sum::<usize>()
1579 + 10
1580 }
1581 Plot::Slope(s) => s.points.len() * 5 + 10,
1582 Plot::Venn(v) => v.sets.len() * 10 + 50,
1583 Plot::Parallel(p) => p.rows.len() + p.axis_names.len() * 10 + 50,
1584 Plot::Mosaic(mp) => {
1585 let nc = mp.effective_col_order().len();
1586 let nr = mp.effective_row_order().len();
1587 nc * nr * 2 + nc + nr + 30
1588 }
1589 Plot::Ecdf(ep) => {
1590 let n: usize = ep.groups.iter().map(|g| g.data.len()).sum();
1591 let band = if ep.show_confidence_band { n * 4 } else { 0 };
1592 let rug = if ep.show_rug { n } else { 0 };
1593 ep.groups.len() * 2 + n * 2 + band + rug + 20
1594 }
1595 Plot::QQ(qp) => {
1596 let n: usize = qp.groups.iter().map(|g| g.data.len()).sum();
1597 let band = if qp.show_ci_band { n * 4 } else { 0 };
1598 qp.groups.len() * 2 + n + band + 20
1599 }
1600 Plot::Network(n) => n.nodes.len() * 2 + n.edges.len() * 3 + 20,
1601 Plot::Radar(r) => {
1602 r.series.len() * (r.axes.len() + 2) + r.grid_lines * r.axes.len() + 30
1603 }
1604 Plot::Streamgraph(sg) => sg.series.len() * (sg.x.len() * 3 + 2) + 20,
1605 Plot::Hexbin(hb) => hb.n_bins * hb.n_bins / 2,
1606 Plot::Treemap(tm) => tm.node_count() * 3 + 10,
1607 Plot::Sunburst(sb) => sb.node_count() * 2 + 10,
1608 Plot::Bump(bp) => bp.total_series_count() * bp.n_time_points() * 3 + 20,
1609 Plot::Funnel(fp) => fp.stage_count() * 6 + 20,
1610 Plot::Rose(rp) => {
1611 rp.n_sectors() * rp.series.len().max(1) * 2
1612 + rp.grid_lines * 2
1613 + rp.n_sectors()
1614 + 20
1615 }
1616 Plot::Calendar(cp) => cp.data.len() + 100,
1617 Plot::Pyramid(pp) => pp.series.len() * pp.n_groups() * 4 + 20,
1618 Plot::Waffle(wp) => wp.rows * wp.cols + 10,
1619 Plot::Horizon(hp) => {
1620 let n = hp.series.len();
1621 let pts_per_series = hp.series.first().map(|s| s.x.len()).unwrap_or(100);
1622 n * hp.n_bands * 2 * pts_per_series / 10 + 20
1623 }
1624 Plot::Gantt(gp) => gp.tasks.len() * 5 + 20,
1625 Plot::Text(tp) => tp.body.lines().count() * 2 + 10,
1626 Plot::Quiver(q) => q.arrows.len() * 2 + 10,
1627 _ => 100,
1628 }
1629 }
1630
1631 pub fn colorbar_info(&self, bw_mode: bool) -> Option<ColorBarInfo> {
1638 let cmap_of = |c: &ColorMap| -> ColorMap {
1639 if bw_mode {
1640 ColorMap::Grayscale
1641 } else {
1642 c.clone()
1643 }
1644 };
1645 match self {
1646 Plot::Heatmap(hm) => {
1647 let min = hm
1648 .data
1649 .iter()
1650 .flatten()
1651 .cloned()
1652 .fold(f64::INFINITY, f64::min);
1653 let max = hm
1654 .data
1655 .iter()
1656 .flatten()
1657 .cloned()
1658 .fold(f64::NEG_INFINITY, f64::max);
1659 colorbar_linear(&cmap_of(&hm.color_map), min, max, None)
1660 }
1661 Plot::Histogram2d(h2d) => {
1662 let max_count = h2d.bins.iter().flatten().copied().max().unwrap_or(1) as f64;
1663 let cmap = cmap_of(&h2d.color_map);
1664 let log_scale = h2d.log_count;
1665 if log_scale {
1666 let log_max = (max_count + 1.0).log10();
1671 let tick_values: Vec<(f64, f64)> = {
1672 let mut v = vec![(0.0_f64, 0.0_f64)];
1673 let mut k = 0u32;
1674 loop {
1675 let count = 10_f64.powi(k as i32);
1676 if count > max_count {
1677 break;
1678 }
1679 let pos = (count + 1.0).log10();
1680 v.push((pos, count));
1681 k += 1;
1682 }
1683 v.push((log_max, max_count));
1685 v.dedup_by(|a, b| (a.0 - b.0).abs() < 1e-9);
1686 v
1687 };
1688 Some(ColorBarInfo {
1689 map_fn: Arc::new(move |t| {
1690 cmap.map((t / log_max).clamp(0.0, 1.0))
1692 }),
1693 min_value: 0.0,
1694 max_value: log_max,
1695 label: Some("log\u{2081}\u{2080}(Count + 1)".to_string()),
1696 tick_labels: None,
1697 tick_values: Some(tick_values),
1698 })
1699 } else {
1700 Some(ColorBarInfo {
1701 map_fn: Arc::new(move |t| cmap.map((t / max_count).clamp(0.0, 1.0))),
1702 min_value: 0.0,
1703 max_value: max_count,
1704 label: Some("Count".to_string()),
1705 tick_labels: None,
1706 tick_values: None,
1707 })
1708 }
1709 }
1710 Plot::DotPlot(dp) => {
1711 let label = dp.color_legend_label.clone()?;
1712 let (min, max) = dp.color_range.unwrap_or_else(|| dp.color_extent());
1713 colorbar_linear(&cmap_of(&dp.color_map), min, max, Some(label))
1714 }
1715 Plot::DicePlot(dp) => {
1716 let label = dp.fill_legend_label.clone()?;
1717 let (min, max) = dp.fill_range.unwrap_or_else(|| dp.fill_extent());
1718 colorbar_linear(&cmap_of(&dp.color_map), min, max, Some(label))
1719 }
1720 Plot::Contour(cp) => {
1721 if !cp.filled {
1722 return None;
1723 }
1724 let (z_min, z_max) = cp.z_range();
1725 colorbar_linear(
1726 &cmap_of(&cp.color_map),
1727 z_min,
1728 z_max,
1729 cp.legend_label.clone(),
1730 )
1731 }
1732 Plot::Clustermap(cm) => {
1733 if cm.data.is_empty() || cm.data.iter().all(|r| r.is_empty()) {
1734 return None;
1735 }
1736 let min = cm
1737 .data
1738 .iter()
1739 .flatten()
1740 .cloned()
1741 .fold(f64::INFINITY, f64::min);
1742 let max = cm
1743 .data
1744 .iter()
1745 .flatten()
1746 .cloned()
1747 .fold(f64::NEG_INFINITY, f64::max);
1748 colorbar_linear(&cmap_of(&cm.color_map), min, max, cm.legend_label.clone())
1749 }
1750 Plot::Surface3D(s) => colorbar_from_z(
1751 &cmap_of(s.z_colormap.as_ref()?),
1752 s.data_ranges()?,
1753 s.box3d.z_label.clone(),
1754 ),
1755 Plot::Scatter3D(s) => {
1756 if bw_mode {
1757 return None;
1761 }
1762 colorbar_from_z(
1763 s.z_colormap.as_ref()?,
1764 s.data_ranges()?,
1765 s.box3d.z_label.clone(),
1766 )
1767 }
1768 Plot::Quiver(q) => {
1769 let cmap = cmap_of(q.color_map.as_ref()?);
1770 let (min, max) = q.color_range.unwrap_or_else(|| q.magnitude_extent());
1771 colorbar_linear(&cmap, min, max, q.color_legend_label.clone())
1772 }
1773 _ => None,
1778 }
1779 }
1780}