Skip to main content

flow_plots/plots/
density.rs

1use crate::contour::calculate_contours;
2use crate::PlotBytes;
3use crate::density_calc::calculate_plot_pixels;
4use crate::options::{DensityPlotOptions, PlotOptions};
5use crate::plots::traits::Plot;
6use crate::plots::PlotType;
7use crate::render::RenderConfig;
8use crate::render::plotters_backend::{render_contour, render_pixels};
9use crate::scatter_data::ScatterPlotData;
10use anyhow::Result;
11
12/// Density plot implementation
13///
14/// Creates a 2D density plot from (x, y) coordinate pairs by binning
15/// data points into pixels and coloring by density.
16///
17/// # Example
18///
19/// ```rust,no_run
20/// use flow_plots::plots::density::DensityPlot;
21/// use flow_plots::plots::traits::Plot;
22/// use flow_plots::options::{DensityPlotOptions, BasePlotOptions};
23/// use flow_plots::render::RenderConfig;
24///
25/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
26/// let plot = DensityPlot::new();
27/// let options = DensityPlotOptions::new()
28///     .base(BasePlotOptions::new().width(800u32).height(600u32).build()?)
29///     .build()?;
30/// let data: Vec<(f32, f32)> = vec![(100.0, 200.0), (150.0, 250.0)];
31/// let mut render_config = RenderConfig::default();
32/// let bytes = plot.render(data.into(), &options, &mut render_config)?;
33/// # Ok(())
34/// # }
35/// ```
36pub struct DensityPlot;
37
38impl DensityPlot {
39    /// Create a new DensityPlot instance
40    pub fn new() -> Self {
41        Self
42    }
43
44    /// Render multiple density plots in batch
45    ///
46    /// High-level convenience method that handles both density calculation
47    /// and rendering. For apps that want to orchestrate rendering themselves,
48    /// use `calculate_density_per_pixel_batch` instead.
49    ///
50    /// # Arguments
51    /// * `requests` - Vector of (data, options) tuples
52    /// * `render_config` - Rendering configuration (shared across all plots)
53    ///
54    /// # Returns
55    /// Vector of plot bytes, one per request
56    pub fn render_batch(
57        &self,
58        requests: &[(ScatterPlotData, DensityPlotOptions)],
59        render_config: &mut RenderConfig,
60    ) -> Result<Vec<PlotBytes>> {
61        let mut results = Vec::with_capacity(requests.len());
62        for (data, options) in requests.iter() {
63            let bytes = match options.plot_type.canonical() {
64                PlotType::Contour | PlotType::ContourOverlay => {
65                    let xy = data.xy();
66                    let x_range = (*options.x_axis.range.start(), *options.x_axis.range.end());
67                    let y_range = (*options.y_axis.range.start(), *options.y_axis.range.end());
68                    let contour_data = calculate_contours(
69                        xy,
70                        options.contour_level_count,
71                        options.contour_smoothing,
72                        options.draw_outliers,
73                        x_range,
74                        y_range,
75                    )?;
76                    render_contour(contour_data, options, render_config)?
77                }
78                _ => {
79                    let base = options.base();
80                    let raw_pixels = calculate_plot_pixels(
81                        data,
82                        base.width as usize,
83                        base.height as usize,
84                        options,
85                    );
86                    render_pixels(raw_pixels, options, render_config)?
87                }
88            };
89            results.push(bytes);
90        }
91        Ok(results)
92    }
93}
94
95impl Plot for DensityPlot {
96    type Options = DensityPlotOptions;
97    type Data = ScatterPlotData;
98
99    fn render(
100        &self,
101        data: Self::Data,
102        options: &Self::Options,
103        render_config: &mut RenderConfig,
104    ) -> Result<PlotBytes> {
105        let plot_start = std::time::Instant::now();
106
107        match options.plot_type.canonical() {
108            PlotType::Contour | PlotType::ContourOverlay => {
109                let xy = data.xy();
110                let x_range = (*options.x_axis.range.start(), *options.x_axis.range.end());
111                let y_range = (*options.y_axis.range.start(), *options.y_axis.range.end());
112                let contour_data = calculate_contours(
113                    xy,
114                    options.contour_level_count,
115                    options.contour_smoothing,
116                    options.draw_outliers,
117                    x_range,
118                    y_range,
119                )?;
120                eprintln!(
121                    "  ├─ Contour calculation: {:?} ({} paths, {} outliers)",
122                    plot_start.elapsed(),
123                    contour_data.contours.len(),
124                    contour_data.outliers.len()
125                );
126                let draw_start = std::time::Instant::now();
127                let result = render_contour(contour_data, options, render_config);
128                eprintln!("  └─ Draw + encode: {:?}", draw_start.elapsed());
129                result
130            }
131            _ => {
132                let base = options.base();
133                let raw_pixels = calculate_plot_pixels(
134                    &data,
135                    base.width as usize,
136                    base.height as usize,
137                    options,
138                );
139                eprintln!(
140                    "  ├─ Plot calculation: {:?} ({} pixels at {}x{})",
141                    plot_start.elapsed(),
142                    raw_pixels.len(),
143                    base.width,
144                    base.height
145                );
146                let draw_start = std::time::Instant::now();
147                let result = render_pixels(raw_pixels, options, render_config);
148                eprintln!("  └─ Draw + encode: {:?}", draw_start.elapsed());
149                result
150            }
151        }
152    }
153}