scirs2_metrics/visualization/backends/
mod.rs

1//! Backend adapters for visualization
2//!
3//! This module provides adapters for different plotting backends like plotters, plotly, etc.
4//! It allows metrics visualizations to be rendered using different plotting libraries.
5
6use std::error::Error;
7use std::path::Path;
8
9use crate::visualization::{VisualizationData, VisualizationMetadata, VisualizationOptions};
10
11#[cfg(feature = "plotly_backend")]
12mod plotly;
13#[cfg(feature = "plotly_backend")]
14mod plotly_interactive;
15#[cfg(feature = "plotters_backend")]
16mod plotters;
17
18#[cfg(feature = "plotly_backend")]
19pub use self::plotly::PlotlyBackend;
20#[cfg(feature = "plotly_backend")]
21pub use self::plotly_interactive::{PlotlyInteractiveBackend, PlotlyInteractiveBackendInterface};
22#[cfg(feature = "plotters_backend")]
23pub use self::plotters::PlottersBackend;
24
25/// A trait for plotting backends
26///
27/// This trait provides a common interface for rendering visualizations using different
28/// plotting libraries. It allows metrics visualizations to be rendered using the most
29/// appropriate backend for a given application.
30pub trait PlottingBackend {
31    /// Save a visualization to a file
32    ///
33    /// # Arguments
34    ///
35    /// * `data` - The visualization data to render
36    /// * `metadata` - The visualization metadata (title, labels, etc.)
37    /// * `options` - Options for the visualization (size, dpi, etc.)
38    /// * `path` - The output file path
39    ///
40    /// # Returns
41    ///
42    /// * `Result<(), Box<dyn Error>>` - Ok if the visualization was successfully saved,
43    ///   or an error if something went wrong
44    fn save_to_file(
45        &self,
46        data: &VisualizationData,
47        metadata: &VisualizationMetadata,
48        options: &VisualizationOptions,
49        path: impl AsRef<Path>,
50    ) -> Result<(), Box<dyn Error>>;
51
52    /// Render a visualization to a byte array as SVG
53    ///
54    /// # Arguments
55    ///
56    /// * `data` - The visualization data to render
57    /// * `metadata` - The visualization metadata (title, labels, etc.)
58    /// * `options` - Options for the visualization (size, dpi, etc.)
59    ///
60    /// # Returns
61    ///
62    /// * `Result<Vec<u8>, Box<dyn Error>>` - A byte array containing the SVG representation
63    ///   of the visualization
64    fn render_svg(
65        &self,
66        data: &VisualizationData,
67        metadata: &VisualizationMetadata,
68        options: &VisualizationOptions,
69    ) -> Result<Vec<u8>, Box<dyn Error>>;
70
71    /// Render a visualization to a byte array as PNG
72    ///
73    /// # Arguments
74    ///
75    /// * `data` - The visualization data to render
76    /// * `metadata` - The visualization metadata (title, labels, etc.)
77    /// * `options` - Options for the visualization (size, dpi, etc.)
78    ///
79    /// # Returns
80    ///
81    /// * `Result<Vec<u8>, Box<dyn Error>>` - A byte array containing the PNG representation
82    ///   of the visualization
83    fn render_png(
84        &self,
85        data: &VisualizationData,
86        metadata: &VisualizationMetadata,
87        options: &VisualizationOptions,
88    ) -> Result<Vec<u8>, Box<dyn Error>>;
89}
90
91/// Create the default plotting backend
92///
93/// This function returns the default plotting backend for the current configuration.
94/// The default backend is determined by the available feature flags.
95///
96/// # Example
97///
98/// ```
99/// use scirs2_metrics::visualization::backends;
100///
101/// let backend = backends::default_backend();
102/// ```
103#[allow(dead_code)]
104pub fn default_backend() -> impl PlottingBackend {
105    #[cfg(feature = "plotly_backend")]
106    {
107        PlotlyBackend::new()
108    }
109    #[cfg(not(feature = "plotly_backend"))]
110    #[cfg(feature = "plotters_backend")]
111    {
112        PlottersBackend::new()
113    }
114    #[cfg(not(feature = "plotly_backend"))]
115    #[cfg(not(feature = "plotters_backend"))]
116    {
117        // Fallback implementation that does nothing
118        struct NoopBackend;
119
120        impl PlottingBackend for NoopBackend {
121            fn save_to_file(
122                &self,
123                _data: &VisualizationData,
124                _metadata: &VisualizationMetadata,
125                _options: &VisualizationOptions,
126                _path: impl AsRef<Path>,
127            ) -> Result<(), Box<dyn Error>> {
128                Err("No visualization backend available. Enable either 'plotly_backend' or 'plotters_backend' feature.".into())
129            }
130
131            fn render_svg(
132                &self,
133                _data: &VisualizationData,
134                _metadata: &VisualizationMetadata,
135                _options: &VisualizationOptions,
136            ) -> Result<Vec<u8>, Box<dyn Error>> {
137                Err("No visualization backend available. Enable either 'plotly_backend' or 'plotters_backend' feature.".into())
138            }
139
140            fn render_png(
141                &self,
142                _data: &VisualizationData,
143                _metadata: &VisualizationMetadata,
144                _options: &VisualizationOptions,
145            ) -> Result<Vec<u8>, Box<dyn Error>> {
146                Err("No visualization backend available. Enable either 'plotly_backend' or 'plotters_backend' feature.".into())
147            }
148        }
149
150        NoopBackend
151    }
152}
153
154/// Create the default interactive plotting backend
155///
156/// This function returns the default interactive plotting backend.
157/// Currently, only Plotly is supported for interactive visualizations.
158///
159/// # Example
160///
161/// ```
162/// use scirs2_metrics::visualization::backends;
163///
164/// let backend = backends::default_interactive_backend();
165/// ```
166#[cfg(feature = "plotly_backend")]
167#[allow(dead_code)]
168pub fn default_interactive_backend() -> PlotlyInteractiveBackend {
169    PlotlyInteractiveBackend::new()
170}
171
172/// Enhance a visualization data structure with additional data
173///
174/// This function adds additional data to a visualization data structure,
175/// such as computed averages, confidence intervals, etc.
176///
177/// # Arguments
178///
179/// * `data` - The visualization data to enhance
180/// * `metadata` - The visualization metadata
181///
182/// # Returns
183///
184/// * `VisualizationData` - The enhanced visualization data
185#[allow(dead_code)]
186pub fn enhance_visualization(
187    data: &VisualizationData,
188    metadata: &VisualizationMetadata,
189) -> VisualizationData {
190    // Create a copy of the original data
191    let mut enhanced = data.clone();
192
193    // Enhance based on plot type
194    match metadata.plot_type {
195        crate::visualization::PlotType::Line => {
196            // Add a trend line for line plots
197            if data.x.len() > 5 && data.y.len() > 5 {
198                // Simple linear regression
199                let n = data.x.len() as f64;
200                let sum_x: f64 = data.x.iter().sum();
201                let sum_y: f64 = data.y.iter().sum();
202                let sum_xy: f64 = data.x.iter().zip(data.y.iter()).map(|(&x, &y)| x * y).sum();
203                let sum_xx: f64 = data.x.iter().map(|&x| x * x).sum();
204
205                // Calculate slope and intercept
206                let slope = (n * sum_xy - sum_x * sum_y) / (n * sum_xx - sum_x * sum_x);
207                let intercept = (sum_y - slope * sum_x) / n;
208
209                // Add trend line data
210                let trend_line: Vec<f64> = data.x.iter().map(|&x| slope * x + intercept).collect();
211
212                // Store trend line in the enhanced data
213                enhanced
214                    .auxiliary_data
215                    .insert("trend_line".to_string(), trend_line);
216                enhanced
217                    .auxiliary_metadata
218                    .insert("trend_slope".to_string(), slope.to_string());
219                enhanced
220                    .auxiliary_metadata
221                    .insert("trend_intercept".to_string(), intercept.to_string());
222            }
223        }
224        crate::visualization::PlotType::Scatter => {
225            // Add a center of mass for scatter plots
226            if data.x.len() > 0 && data.y.len() > 0 {
227                let center_x = data.x.iter().sum::<f64>() / data.x.len() as f64;
228                let center_y = data.y.iter().sum::<f64>() / data.y.len() as f64;
229
230                enhanced
231                    .auxiliary_data
232                    .insert("center_x".to_string(), vec![center_x]);
233                enhanced
234                    .auxiliary_data
235                    .insert("center_y".to_string(), vec![center_y]);
236            }
237        }
238        _ => {
239            // No enhancement for other plot types yet
240        }
241    }
242
243    enhanced
244}