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