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}