use crate ::prelude :: *;
use std ::path ::Path;
type Result< T > = std ::result ::Result< T, Box<dyn std ::error ::Error >>;
#[ cfg(feature = "visualization") ]
use plotters ::prelude :: *;
#[ derive(Debug, Clone) ]
pub struct ChartConfig
{
pub title: String,
pub width: u32,
pub height: u32,
pub x_label: String,
pub y_label: String,
pub format: ChartFormat,
}
impl Default for ChartConfig
{
fn default() -> Self
{
Self
{
title: "Benchmark Results".to_string(),
width: 800,
height: 600,
x_label: "Input Size".to_string(),
y_label: "Operations/sec".to_string(),
format: ChartFormat ::SVG,
}
}
}
#[ derive(Debug, Clone) ]
pub enum ChartFormat
{
SVG,
PNG,
HTML,
}
impl ChartFormat
{
pub fn extension( &self ) -> &str
{
match self
{
ChartFormat ::SVG => "svg",
ChartFormat ::PNG => "png",
ChartFormat ::HTML => "html",
}
}
}
#[ cfg(feature = "visualization") ]
#[ derive(Debug) ]
pub struct ScalingChart
{
config: ChartConfig,
data_series: Vec< (String, Vec<(f64, f64) >)>, }
#[ cfg(feature = "visualization") ]
impl ScalingChart
{
pub fn new(config: ChartConfig) -> Self
{
Self
{
config,
data_series: Vec ::new(),
}
}
pub fn add_series(&mut self, name: &str, data_points: Vec< (f64, f64) >)
{
self.data_series.push((name.to_string(), data_points));
}
pub fn add_scaling_results(&mut self, name: &str, scaling_results: &[ (usize, BenchmarkResult)])
{
let data_points: Vec< (f64, f64) > = scaling_results
.iter()
.map(|(scale, result)| (*scale as f64, result.operations_per_second()))
.collect();
self.add_series(name, data_points);
}
pub fn data_series_count( &self ) -> usize
{
self.data_series.len()
}
pub fn data_points_count(&self, series_index: usize) -> Option< usize >
{
self.data_series.get( series_index ).map( | ( _, points ) | points.len() )
}
pub fn generate(&self, output_path: &Path) -> Result< () >
{
match self.config.format
{
ChartFormat ::SVG => self.generate_svg(output_path),
ChartFormat ::PNG => self.generate_png(output_path),
ChartFormat ::HTML => self.generate_html(output_path),
}
}
fn generate_svg(&self, output_path: &Path) -> Result< () >
{
let root = SVGBackend ::new(output_path, (self.config.width, self.config.height))
.into_drawing_area();
self.draw_chart(root)
}
fn generate_png(&self, output_path: &Path) -> Result< () >
{
let svg_path = output_path.with_extension("svg");
self.generate_svg(&svg_path)?;
println!("⚠️ PNG support requires image library - generated SVG instead: {:?}", svg_path);
Ok(())
}
fn generate_html(&self, output_path: &Path) -> Result< () >
{
let svg_content = self.generate_svg_string()?;
let html_content = format!(
r#"< !DOCTYPE html >
< html >
< head >
< title >{}< /title >
< style >
body {{ font-family: Arial, sans-serif; margin: 40px; }}
.chart-container {{ text-align: center; }}
< /style >
< /head >
< body >
< div class="chart-container" >
< h1 >{}< /h1 >
{}
< /div >
< /body >
< /html >"#,
self.config.title, self.config.title, svg_content
);
std ::fs ::write(output_path, html_content)?;
Ok(())
}
fn generate_svg_string( &self ) -> Result< String >
{
let mut buffer = String ::new();
{
let root = SVGBackend ::with_string(&mut buffer, (self.config.width, self.config.height))
.into_drawing_area();
self.draw_chart(root)?;
}
Ok(buffer)
}
fn draw_chart< DB: DrawingBackend >(&self, root: DrawingArea< DB, plotters ::coord ::Shift >) -> Result< () >
where
DB ::ErrorType: 'static,
{
root.fill(&WHITE)?;
if self.data_series.is_empty()
{
return Ok(());
}
let (x_min, x_max, y_min, y_max) = self.find_data_ranges();
let mut chart = ChartBuilder ::on(&root)
.caption(&self.config.title, ("sans-serif", 30))
.margin(20)
.x_label_area_size(40)
.y_label_area_size(50)
.build_cartesian_2d(x_min..x_max, y_min..y_max)?;
chart
.configure_mesh()
.x_desc(&self.config.x_label)
.y_desc(&self.config.y_label)
.draw()?;
let colors = [&RED, &BLUE, &GREEN, &MAGENTA, &CYAN];
for (i, (name, data_points)) in self.data_series.iter().enumerate()
{
let color = colors[i % colors.len()];
chart
.draw_series(LineSeries ::new(data_points.iter().cloned(), color))?
.label(name)
.legend(move |(x, y)| PathElement ::new(vec![(x, y), (x + 10, y)], color));
chart.draw_series(
data_points.iter().map(|(x, y)| Circle ::new((*x, *y), 3, color.filled()))
)?;
}
chart.configure_series_labels().draw()?;
root.present()?;
Ok(())
}
fn find_data_ranges( &self ) -> (f64, f64, f64, f64)
{
let mut x_min = f64 ::INFINITY;
let mut x_max = f64 ::NEG_INFINITY;
let mut y_min = f64 ::INFINITY;
let mut y_max = f64 ::NEG_INFINITY;
for (_, data_points) in &self.data_series
{
for (x, y) in data_points
{
x_min = x_min.min(*x);
x_max = x_max.max(*x);
y_min = y_min.min(*y);
y_max = y_max.max(*y);
}
}
let x_range = x_max - x_min;
let y_range = y_max - y_min;
(
x_min - x_range * 0.05,
x_max + x_range * 0.05,
y_min - y_range * 0.05,
y_max + y_range * 0.05,
)
}
}
#[ cfg(feature = "visualization") ]
#[ derive(Debug) ]
pub struct ComparisonChart
{
config: ChartConfig,
data: Vec< (String, f64) >, }
#[ cfg(feature = "visualization") ]
impl ComparisonChart
{
pub fn new(config: ChartConfig) -> Self
{
Self
{
config,
data: Vec ::new(),
}
}
pub fn add_framework(&mut self, name: &str, ops_per_second: f64)
{
self.data.push((name.to_string(), ops_per_second));
}
pub fn add_benchmark_results(&mut self, results: &[ (String, BenchmarkResult)])
{
for (name, result) in results
{
self.add_framework(name, result.operations_per_second());
}
}
pub fn data_count( &self ) -> usize
{
self.data.len()
}
pub fn ops_per_second(&self, index: usize) -> Option< f64 >
{
self.data.get( index ).map( | ( _, ops ) | *ops )
}
pub fn generate(&self, output_path: &Path) -> Result< () >
{
match self.config.format
{
ChartFormat ::SVG => self.generate_svg(output_path),
ChartFormat ::PNG => self.generate_png(output_path),
ChartFormat ::HTML => self.generate_html(output_path),
}
}
fn generate_svg(&self, output_path: &Path) -> Result< () >
{
let root = SVGBackend ::new(output_path, (self.config.width, self.config.height))
.into_drawing_area();
self.draw_chart(root)
}
fn generate_png(&self, output_path: &Path) -> Result< () >
{
let svg_path = output_path.with_extension("svg");
self.generate_svg(&svg_path)?;
println!("⚠️ PNG support requires image library - generated SVG instead: {:?}", svg_path);
Ok(())
}
fn generate_html(&self, output_path: &Path) -> Result< () >
{
let svg_content = self.generate_svg_string()?;
let html_content = format!(
r#"< !DOCTYPE html >
< html >
< head >
< title >{}< /title >
< style >
body {{ font-family: Arial, sans-serif; margin: 40px; }}
.chart-container {{ text-align: center; }}
< /style >
< /head >
< body >
< div class="chart-container" >
< h1 >{}< /h1 >
{}
< /div >
< /body >
< /html >"#,
self.config.title, self.config.title, svg_content
);
std ::fs ::write(output_path, html_content)?;
Ok(())
}
fn generate_svg_string( &self ) -> Result< String >
{
let mut buffer = String ::new();
{
let root = SVGBackend ::with_string(&mut buffer, (self.config.width, self.config.height))
.into_drawing_area();
self.draw_chart(root)?;
}
Ok(buffer)
}
fn draw_chart< DB: DrawingBackend >(&self, root: DrawingArea< DB, plotters ::coord ::Shift >) -> Result< () >
where
DB ::ErrorType: 'static,
{
root.fill(&WHITE)?;
if self.data.is_empty()
{
return Ok(());
}
let y_max = self.data.iter().map(|(_, ops)| *ops).fold(0.0f64, f64 ::max) * 1.1;
let mut chart = ChartBuilder ::on(&root)
.caption(&self.config.title, ("sans-serif", 30))
.margin(20)
.x_label_area_size(60)
.y_label_area_size(50)
.build_cartesian_2d(0f32..self.data.len() as f32, 0f64..y_max)?;
chart
.configure_mesh()
.y_desc(&self.config.y_label)
.x_desc(&self.config.x_label)
.x_label_formatter(&|x| {
let index = *x as usize;
if index < self.data.len()
{
self.data[index].0.clone()
} else {
String ::new()
}
})
.draw()?;
chart.draw_series(
self.data.iter().enumerate().map(|(i, (_name, ops))| {
Rectangle ::new([(i as f32 - 0.4, 0.0), (i as f32 + 0.4, *ops)], BLUE.filled())
})
)?
.label("Operations/sec")
.legend(|(x, y)| PathElement ::new(vec![(x, y), (x + 10, y)], &BLUE));
chart.configure_series_labels().draw()?;
root.present()?;
Ok(())
}
}
#[ cfg(feature = "visualization") ]
pub mod plots
{
use super :: *;
pub fn scaling_analysis_chart(
suite_results: &[ (usize, BenchmarkResult)],
title: &str,
output_path: &Path,
) -> Result< () >
{
let config = ChartConfig
{
title: title.to_string(),
x_label: "Input Size".to_string(),
y_label: "Operations/sec".to_string(),
..Default ::default()
};
let mut chart = ScalingChart ::new(config);
chart.add_scaling_results("Performance", suite_results);
chart.generate(output_path)
}
pub fn framework_comparison_chart(
framework_results: &[ (String, BenchmarkResult)],
title: &str,
output_path: &Path,
) -> Result< () >
{
let config = ChartConfig
{
title: title.to_string(),
x_label: "Framework".to_string(),
y_label: "Operations/sec".to_string(),
..Default ::default()
};
let mut chart = ComparisonChart ::new(config);
chart.add_benchmark_results(framework_results);
chart.generate(output_path)
}
pub fn performance_trend_chart(
historical_data: &[ (String, f64)], title: &str,
output_path: &Path,
) -> Result< () >
{
let config = ChartConfig
{
title: title.to_string(),
x_label: "Time".to_string(),
y_label: "Operations/sec".to_string(),
..Default ::default()
};
let mut chart = ScalingChart ::new(config);
let data_points: Vec< (f64, f64) > = historical_data
.iter()
.enumerate()
.map(|(i, (_, ops))| (i as f64, *ops))
.collect();
chart.add_series("Performance Trend", data_points);
chart.generate(output_path)
}
}
#[ cfg(not(feature = "visualization")) ]
pub mod plots
{
use super :: *;
pub fn scaling_analysis_chart(
_suite_results: &[ (usize, BenchmarkResult)],
_title: &str,
_output_path: &Path,
) -> Result< () >
{
println!("⚠️ Visualization disabled - enable 'visualization' feature for charts");
Ok(())
}
pub fn framework_comparison_chart(
_framework_results: &[ (String, BenchmarkResult)],
_title: &str,
_output_path: &Path,
) -> Result< () >
{
println!("⚠️ Visualization disabled - enable 'visualization' feature for charts");
Ok(())
}
pub fn performance_trend_chart(
_historical_data: &[ (String, f64)],
_title: &str,
_output_path: &Path,
) -> Result< () >
{
println!("⚠️ Visualization disabled - enable 'visualization' feature for charts");
Ok(())
}
}