#[cfg(feature = "plotting")]
use plotters::prelude::*;
use scirs2_core::numeric::Complex64;
use std::error::Error;
use std::path::Path;
#[derive(Debug, Clone)]
pub struct PlotConfig {
pub width: u32,
pub height: u32,
pub dpi: u32,
pub title: String,
pub x_label: String,
pub y_label: String,
pub show_grid: bool,
pub show_legend: bool,
pub color_scheme: ColorScheme,
}
impl Default for PlotConfig {
fn default() -> Self {
Self {
width: 800,
height: 600,
dpi: 100,
title: String::new(),
x_label: "x".to_string(),
y_label: "f(x)".to_string(),
show_grid: true,
show_legend: true,
color_scheme: ColorScheme::default(),
}
}
}
#[derive(Debug, Clone)]
pub enum ColorScheme {
Default,
Viridis,
Plasma,
Inferno,
Magma,
ColorBlind,
}
impl Default for ColorScheme {
fn default() -> Self {
ColorScheme::Default
}
}
pub trait Visualizable {
fn plot_2d(&self, config: &PlotConfig) -> Result<Vec<u8>, Box<dyn Error>>;
fn plot_3d(&self, config: &PlotConfig) -> Result<Vec<u8>, Box<dyn Error>>;
fn animate(&self, config: &PlotConfig) -> Result<Vec<Vec<u8>>, Box<dyn Error>>;
}
pub struct MultiPlot {
functions: Vec<Box<dyn Fn(f64) -> f64>>,
labels: Vec<String>,
x_range: (f64, f64),
config: PlotConfig,
}
impl MultiPlot {
pub fn new(config: PlotConfig) -> Self {
Self {
functions: Vec::new(),
labels: Vec::new(),
x_range: (-10.0, 10.0),
config,
}
}
pub fn add_function(mut self, f: Box<dyn Fn(f64) -> f64>, label: &str) -> Self {
self.functions.push(f);
self.labels.push(label.to_string());
self
}
pub fn set_x_range(mut self, min: f64, max: f64) -> Self {
self.x_range = (min, max);
self
}
#[cfg(feature = "plotting")]
pub fn plot<P: AsRef<Path>>(&self, path: P) -> Result<(), Box<dyn Error>> {
let root = BitMapBackend::new(path.as_ref(), (self.config.width, self.config.height))
.into_drawing_area();
root.fill(&WHITE)?;
let mut chart = ChartBuilder::on(&root)
.caption(&self.config.title, ("sans-serif", 40))
.margin(10)
.x_label_area_size(30)
.y_label_area_size(40)
.build_cartesian_2d(self.x_range.0..self.x_range.1, -2f64..2f64)?;
if self.config.show_grid {
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, (f, label)) in self.functions.iter().zip(&self.labels).enumerate() {
let color = colors[i % colors.len()];
let data: Vec<(f64, f64)> = ((self.x_range.0 * 100.0) as i32
..(self.x_range.1 * 100.0) as i32)
.map(|x| x as f64 / 100.0)
.map(|x| (x, f(x)))
.collect();
chart
.draw_series(LineSeries::new(data, color))?
.label(label)
.legend(move |(x, y)| PathElement::new(vec![(x, y), (x + 10, y)], color));
}
if self.config.show_legend {
chart
.configure_series_labels()
.background_style(&WHITE.mix(0.8))
.border_style(&BLACK)
.draw()?;
}
root.present()?;
Ok(())
}
}
pub mod gamma_plots {
use super::*;
use crate::{digamma, gamma, gammaln};
pub fn plot_gamma_family<P: AsRef<Path>>(path: P) -> Result<(), Box<dyn Error>> {
let config = PlotConfig {
title: "Gamma Function Family".to_string(),
x_label: "x".to_string(),
y_label: "f(x)".to_string(),
..Default::default()
};
MultiPlot::new(config)
.add_function(Box::new(|x| gamma(x)), "Γ(x)")
.add_function(Box::new(|x| gammaln(x)), "ln Γ(x)")
.add_function(Box::new(|x| digamma(x)), "ψ(x)")
.set_x_range(0.1, 5.0)
.plot(path)
}
#[cfg(feature = "plotting")]
pub fn plot_gamma_complex<P: AsRef<Path>>(path: P) -> Result<(), Box<dyn Error>> {
use crate::gamma::complex::gamma_complex;
let root = BitMapBackend::new(path.as_ref(), (800, 600)).into_drawing_area();
root.fill(&WHITE)?;
let mut chart = ChartBuilder::on(&root)
.caption("Complex Gamma Function |Γ(z)|", ("sans-serif", 40))
.margin(10)
.x_label_area_size(30)
.y_label_area_size(40)
.build_cartesian_2d(-5f64..5f64, -5f64..5f64)?;
chart
.configure_mesh()
.x_desc("Re(z)")
.y_desc("Im(z)")
.draw()?;
let n = 100;
let mut data = vec![];
for i in 0..n {
for j in 0..n {
let x = -5.0 + 10.0 * i as f64 / n as f64;
let y = -5.0 + 10.0 * j as f64 / n as f64;
let z = Complex64::new(x, y);
let gamma_z = gamma_complex(z);
let magnitude = gamma_z.norm().ln();
data.push(Rectangle::new(
[(x, y), (x + 0.1, y + 0.1)],
HSLColor(240.0 - magnitude * 30.0, 0.7, 0.5).filled(),
));
}
}
chart.draw_series(data)?;
root.present()?;
Ok(())
}
}
pub mod bessel_plots {
use super::*;
use crate::bessel::{j0, j1, jn};
pub fn plot_bessel_j<P: AsRef<Path>>(path: P) -> Result<(), Box<dyn Error>> {
let config = PlotConfig {
title: "Bessel Functions of the First Kind".to_string(),
..Default::default()
};
MultiPlot::new(config)
.add_function(Box::new(|x| j0(x)), "J₀(x)")
.add_function(Box::new(|x| j1(x)), "J₁(x)")
.add_function(Box::new(|x| jn(2, x)), "J₂(x)")
.add_function(Box::new(|x| jn(3, x)), "J₃(x)")
.set_x_range(0.0, 20.0)
.plot(path)
}
pub fn plot_bessel_zeros<P: AsRef<Path>>(path: P) -> Result<(), Box<dyn Error>> {
use crate::bessel_zeros::j0_zeros;
#[cfg(feature = "plotting")]
{
let root = BitMapBackend::new(path.as_ref(), (800, 600)).into_drawing_area();
root.fill(&WHITE)?;
let mut chart = ChartBuilder::on(&root)
.caption("Bessel Function Zeros", ("sans-serif", 40))
.margin(10)
.x_label_area_size(30)
.y_label_area_size(40)
.build_cartesian_2d(0f64..30f64, -0.5f64..1f64)?;
chart.configure_mesh().x_desc("x").y_desc("J_n(x)").draw()?;
let j0_data: Vec<(f64, f64)> = (0..3000)
.map(|i| i as f64 / 100.0)
.map(|x| (x, j0(x)))
.collect();
chart
.draw_series(LineSeries::new(j0_data, &BLUE))?
.label("J₀(x)")
.legend(|(x, y)| PathElement::new(vec![(x, y), (x + 10, y)], &BLUE));
for k in 1..=10 {
if let Ok(zero) = j0_zeros::<f64>(k) {
chart.draw_series(PointSeries::of_element(
vec![(zero, 0.0)],
5,
&RED,
&|c, s, st| {
return EmptyElement::at(c)
+ Circle::new((0, 0), s, st.filled())
+ Text::new(format!("{:.3}", zero), (10, 0), ("sans-serif", 15));
},
))?;
}
}
chart
.configure_series_labels()
.background_style(&WHITE.mix(0.8))
.border_style(&BLACK)
.draw()?;
root.present()?;
}
Ok(())
}
}
pub mod error_function_plots {
use super::*;
use crate::{erf, erfc, erfinv};
pub fn plot_error_functions<P: AsRef<Path>>(path: P) -> Result<(), Box<dyn Error>> {
let config = PlotConfig {
title: "Error Functions".to_string(),
..Default::default()
};
MultiPlot::new(config)
.add_function(Box::new(|x| erf(x)), "erf(x)")
.add_function(Box::new(|x| erfc(x)), "erfc(x)")
.add_function(
Box::new(|x| if x.abs() < 0.999 { erfinv(x) } else { f64::NAN }),
"erfinv(x)",
)
.set_x_range(-3.0, 3.0)
.plot(path)
}
}
pub mod polynomial_plots {
use super::*;
use crate::legendre;
pub fn plot_legendre<P: AsRef<Path>>(path: P, maxn: usize) -> Result<(), Box<dyn Error>> {
let config = PlotConfig {
title: format!("Legendre Polynomials P_n(x) for _n = 0..{}", maxn),
..Default::default()
};
let mut plot = MultiPlot::new(config).set_x_range(-1.0, 1.0);
for _n in 0..=maxn {
plot = plot.add_function(Box::new(move |x| legendre(_n, x)), &format!("P_{}", _n));
}
plot.plot(path)
}
pub fn animate_polynomials() -> Result<Vec<Vec<u8>>, Box<dyn Error>> {
Ok(vec![])
}
}
pub mod surface_plots {
use super::*;
#[cfg(feature = "plotting")]
pub fn plot_3d_surface<P, F>(path: P, f: F, title: &str) -> Result<(), Box<dyn Error>>
where
P: AsRef<Path>,
F: Fn(f64, f64) -> f64,
{
let root = BitMapBackend::new(path.as_ref(), (800, 600)).into_drawing_area();
root.fill(&WHITE)?;
let mut chart = ChartBuilder::on(&root)
.caption(title, ("sans-serif", 40))
.margin(10)
.x_label_area_size(30)
.y_label_area_size(40)
.build_cartesian_3d(-5.0..5.0, -5.0..5.0, -2.0..2.0)?;
chart.configure_axes().draw()?;
let n = 50;
let mut data = vec![];
for i in 0..n {
for j in 0..n {
let x = -5.0 + 10.0 * i as f64 / n as f64;
let y = -5.0 + 10.0 * j as f64 / n as f64;
let z = f(x, y);
if z.is_finite() {
data.push((x, y, z));
}
}
}
let x_range: Vec<f64> = (0..51).map(|i| -5.0 + i as f64 * 0.2).collect();
let y_range: Vec<f64> = (0..51).map(|i| -5.0 + i as f64 * 0.2).collect();
chart.draw_series(
SurfaceSeries::xoz(x_range.into_iter(), y_range.into_iter(), |x, y| f(x, y))
.style(&BLUE.mix(0.5)),
)?;
root.present()?;
Ok(())
}
}
#[cfg(feature = "interactive")]
pub mod interactive {
#[allow(unused_imports)]
use super::*;
pub struct InteractivePlotConfig {
pub enable_zoom: bool,
pub enable_pan: bool,
pub enable_tooltips: bool,
pub enable_export: bool,
}
pub fn create_interactive_plot<F>(
f: F,
config: InteractivePlotConfig,
x_range: (f64, f64),
function_name: &str,
) -> String
where
F: Fn(f64) -> f64,
{
let n_points = 1000;
let step = (x_range.1 - x_range.0) / n_points as f64;
let mut data_points = Vec::new();
for i in 0..=n_points {
let x = x_range.0 + i as f64 * step;
let y = f(x);
if y.is_finite() {
data_points.push(format!("[{}, {}]", x, y));
}
}
let mut x_values = Vec::new();
let mut y_values = Vec::new();
for i in 0..=n_points {
let x = x_range.0 + i as f64 * step;
let y = f(x);
if y.is_finite() {
x_values.push(x);
y_values.push(y);
}
}
let x_json = format!(
"[{}]",
x_values
.iter()
.map(|x| format!("{x}"))
.collect::<Vec<_>>()
.join(", ")
);
let y_json = format!(
"[{}]",
y_values
.iter()
.map(|y| format!("{y}"))
.collect::<Vec<_>>()
.join(", ")
);
format!(
r#"
<!DOCTYPE html>
<html>
<head>
<title>Interactive Plot - {}</title>
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
<style>
body {{
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
margin: 0;
padding: 20px;
background-color: #f5f5f5;
}}
.container {{
max-width: 1200px;
margin: 0 auto;
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}}
h1 {{
color: #333;
text-align: center;
margin-bottom: 30px;
}}
.controls {{
display: flex;
gap: 15px;
margin-bottom: 20px;
flex-wrap: wrap;
align-items: center;
}}
.control-group {{
display: flex;
flex-direction: column;
gap: 5px;
}}
label {{
font-weight: 600;
color: #555;
font-size: 14px;
}}
input, select, button {{
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}}
button {{
background-color: #007bff;
color: white;
border: none;
cursor: pointer;
transition: background-color 0.2s;
}}
button:hover {{
background-color: #0056b3;
}}
#plot {{
width: 100%;
height: 600px;
}}
.info-panel {{
margin-top: 20px;
padding: 15px;
background-color: #f8f9fa;
border-radius: 6px;
border-left: 4px solid #007bff;
}}
.tooltip-info {{
margin-top: 10px;
font-size: 14px;
color: #666;
}}
</style>
</head>
<body>
<div class="container">
<h1>Interactive Visualization: {}</h1>
<div class="controls">
<div class="control-group">
<label for="xMin">X Min:</label>
<input type="number" id="xMin" value="{}" step="0.1">
</div>
<div class="control-group">
<label for="xMax">X Max:</label>
<input type="number" id="xMax" value="{}" step="0.1">
</div>
<div class="control-group">
<label for="points">Points:</label>
<select id="points">
<option value="500">500</option>
<option value="1000" selected>1000</option>
<option value="2000">2000</option>
<option value="5000">5000</option>
</select>
</div>
<button onclick="updatePlot()">Update Plot</button>
<button onclick="resetZoom()">Reset Zoom</button>
<button onclick="exportData()">Export CSV</button>
{}
</div>
<div id="plot"></div>
<div class="info-panel">
<h3>Interactive Features:</h3>
<ul>
<li><strong>Zoom:</strong> Click and drag to zoom into a region</li>
<li><strong>Pan:</strong> Hold shift and drag to pan around</li>
<li><strong>Hover:</strong> Move mouse over the curve to see coordinates</li>
<li><strong>Double-click:</strong> Reset zoom to fit all data</li>
</ul>
<div class="tooltip-info" id="tooltip-info">
Hover over the plot to see coordinate information here.
</div>
</div>
</div>
<script>
let currentData = {};
// JavaScript implementations of special functions
function gamma(x) {{
if (x < 0) return NaN;
if (x === 0) return Infinity;
if (x === 1 || x === 2) return 1;
// Stirling's approximation for x > 1
if (x > 1) {{
return Math.sqrt(2 * Math.PI / x) * Math.pow(x / Math.E, x);
}}
return gamma(x + 1) / x;
}}
function besselJ0(x) {{
const ax = Math.abs(x);
if (ax < 8) {{
const y = x * x;
return ((-0.0000000000000000015 * y + 0.000000000000000176) * y +
(-0.0000000000000156) * y + 0.0000000000164) * y +
(-0.00000000106) * y + 0.000000421) * y +
(-0.0000103) * y + 0.00015625) * y +
(-0.015625) * y + 1;
}} else {{
const z = 8 / ax;
const y = z * z;
const xx = ax - 0.785398164;
return Math.sqrt(0.636619772 / ax) *
(Math.cos(xx) * (1 + y * (-0.0703125 + y * 0.1121520996)) +
z * Math.sin(xx) * (-0.0390625 + y * 0.0444479255));
}}
}}
function erf(x) {{
const a1 = 0.254829592;
const a2 = -0.284496736;
const a3 = 1.421413741;
const a4 = -1.453152027;
const a5 = 1.061405429;
const p = 0.3275911;
const sign = x >= 0 ? 1 : -1;
x = Math.abs(x);
const t = 1.0 / (1.0 + p * x);
const y = 1.0 - (((((a5 * t + a4) * t) + a3) * t + a2) * t + a1) * t * Math.exp(-x * x);
return sign * y;
}}
function airyAi(x) {{
// Simplified Airy Ai function approximation
if (x > 5) return 0; // Exponentially decaying for positive x
if (x < -5) {{
// Oscillatory behavior for negative x
const arg = (2/3) * Math.pow(Math.abs(x), 1.5);
return (1 / (Math.sqrt(Math.PI) * Math.pow(Math.abs(x), 0.25))) * Math.sin(arg + Math.PI/4);
}}
// Rough approximation for the intermediate region
return Math.exp(-Math.abs(x)) * Math.cos(x);
}}
function getSpecialFunction(functionName) {{
const _name = functionName.toLowerCase();
if (_name.includes('gamma')) return gamma;
if (_name.includes('bessel') && name.includes('j0')) return besselJ0;
if (_name.includes('error') || name.includes('erf')) return erf;
if (_name.includes('airy')) return airyAi;
// Default fallback - could add more functions as needed
return Math.sin;
}}
function initializePlot() {{
const data = [{{
x: {},
y: {},
type: 'scatter',
mode: 'lines',
_name: '{}',
line: {{
color: '#1f77b4',
width: 2
}},
hovertemplate: '<b>x:</b> %{{x:.6f}}<br><b>f(x):</b> %{{y:.6f}}<extra></extra>'
}}];
const layout = {{
title: {{
text: '{} Function',
font: {{ size: 20 }}
}},
xaxis: {{
title: 'x',
showgrid: true,
zeroline: true,
showspikes: true,
spikethickness: 1,
spikecolor: '#999',
spikemode: 'across'
}},
yaxis: {{
title: 'f(x)',
showgrid: true,
zeroline: true,
showspikes: true,
spikethickness: 1,
spikecolor: '#999',
spikemode: 'across'
}},
hovermode: 'closest',
showlegend: true,
plot_bgcolor: 'white',
paper_bgcolor: 'white'
}};
const plotConfig = {{
responsive: true,
displayModeBar: true,
modeBarButtonsToAdd: [
'pan2d',
'zoomin2d',
'zoomout2d',
'autoScale2d',
'hoverClosestCartesian',
'hoverCompareCartesian'
],
toImageButtonOptions: {{
format: 'png',
filename: '{}_plot',
height: 600,
width: 800,
scale: 1
}}
}};
Plotly.newPlot('plot', data, layout, plotConfig);
// Add hover event listener for tooltip info
document.getElementById('plot').on('plotly_hover', function(data) {{
const point = data.points[0];
document.getElementById('tooltip-info').innerHTML =
`<strong>Coordinates:</strong> x = ${{point.x.toFixed(6)}}, f(x) = ${{point.y.toFixed(6)}}`;
}});
currentData = {{ x: {}, y: {} }};
}}
function updatePlot() {{
const xMin = parseFloat(document.getElementById('xMin').value);
const xMax = parseFloat(document.getElementById('xMax').value);
const nPoints = parseInt(document.getElementById('points').value);
// Generate data points using actual special function implementations
const step = (xMax - xMin) / nPoints;
const x = [];
const y = [];
for (let i = 0; i <= nPoints; i++) {{
const xVal = xMin + i * step;
x.push(xVal);
// Use appropriate special function based on function _name
const func = getSpecialFunction('{}');
const yVal = func(xVal);
y.push(isFinite(yVal) ? yVal : NaN);
}}
Plotly.restyle('plot', {{'x': [x], 'y': [y]}});
currentData = {{ x: x, y: y }};
}}
function resetZoom() {{
Plotly.relayout('plot', {{
'xaxis.autorange': true,
'yaxis.autorange': true
}});
}}
function exportData() {{
let csv = 'x,f(x)\\n';
for (let i = 0; i < currentData.x.length; i++) {{
csv += `${{currentData.x[i]}},${{currentData.y[i]}}\\n`;
}}
const blob = new Blob([csv], {{ type: 'text/csv' }});
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.style.display = 'none';
a.href = url;
a.download = '{}_data.csv';
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
}}
// Initialize the plot when the page loads
window.onload = initializePlot;
</script>
</body>
</html>
"#,
function_name,
function_name,
x_range.0,
x_range.1,
if config.enable_tooltips {
r#"<button onclick="toggleTooltips()">Toggle Info</button>"#
} else {
""
},
x_json,
y_json,
function_name,
function_name,
function_name,
function_name, x_json,
y_json,
function_name,
function_name )
}
pub fn create_gamma_plot() -> String {
use crate::gamma::gamma;
let config = InteractivePlotConfig {
enable_zoom: true,
enable_pan: true,
enable_tooltips: true,
enable_export: true,
};
create_interactive_plot(gamma, config, (0.1, 5.0), "Gamma")
}
pub fn create_bessel_j0_plot() -> String {
use crate::bessel::j0;
let config = InteractivePlotConfig {
enable_zoom: true,
enable_pan: true,
enable_tooltips: true,
enable_export: true,
};
create_interactive_plot(j0, config, (-10.0, 10.0), "Bessel J0")
}
pub fn create_erf_plot() -> String {
use crate::erf::erf;
let config = InteractivePlotConfig {
enable_zoom: true,
enable_pan: true,
enable_tooltips: true,
enable_export: true,
};
create_interactive_plot(erf, config, (-3.0, 3.0), "Error Function")
}
pub fn create_comparison_plot() -> String {
let template = r#"
<!DOCTYPE html>
<html>
<head>
<title>Special Functions Comparison</title>
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
<style>
body { font-family: Arial, sans-serif; margin: 20px; background-color: #f8f9fa; }
.container { max-width: 1200px; margin: 0 auto; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
h1 { text-align: center; color: #333; }
#plot { width: 100%; height: 700px; }
.controls { margin-bottom: 20px; text-align: center; }
button { margin: 5px; padding: 10px 20px; border: none; border-radius: 4px; background-color: #007bff; color: white; cursor: pointer; }
button:hover { background-color: #0056b3; }
</style>
</head>
<body>
<div class="container">
<h1>Special Functions Comparison</h1>
<div class="controls">
<button onclick="showGamma()">Gamma Function</button>
<button onclick="showBessel()">Bessel J0</button>
<button onclick="showErf()">Error Function</button>
<button onclick="showAll()">Show All</button>
</div>
<div id="plot"></div>
</div>
<script>
function generateData(func, range, nPoints, name) {
const step = (range[1] - range[0]) / nPoints;
const x = [];
const y = [];
for (let i = 0; i <= nPoints; i++) {
const xVal = range[0] + i * step;
x.push(xVal);
y.push(func(xVal));
}
return {
x: x,
y: y,
type: 'scatter',
mode: 'lines',
name: name,
line: { width: 2 }
};
}
function gamma(x) {
// Simplified gamma function approximation for demo
if (x < 0) return NaN;
if (x === 0) return Infinity;
if (x === 1 || x === 2) return 1;
// Stirling's approximation for simplicity
if (x > 1) {
return Math.sqrt(2 * Math.PI / x) * Math.pow(x / Math.E, x);
}
return gamma(x + 1) / x;
}
function besselJ0(x) {
// Simplified Bessel J0 approximation
const ax = Math.abs(x);
if (ax < 8) {
const y = x * x;
return ((-0.0000000000000000015 * y + 0.000000000000000176) * y +
(-0.0000000000000156) * y + 0.0000000000164) * y +
(-0.00000000106) * y + 0.000000421) * y +
(-0.0000103) * y + 0.00015625) * y +
(-0.015625) * y + 1;
} else {
const z = 8 / ax;
const y = z * z;
const xx = ax - 0.785398164;
return Math.sqrt(0.636619772 / ax) *
(Math.cos(xx) * (1 + y * (-0.0703125 + y * 0.1121520996)) +
z * Math.sin(xx) * (-0.0390625 + y * 0.0444479255));
}
}
function erf(x) {
// Simplified error function approximation
const a1 = 0.254829592;
const a2 = -0.284496736;
const a3 = 1.421413741;
const a4 = -1.453152027;
const a5 = 1.061405429;
const p = 0.3275911;
const sign = x >= 0 ? 1 : -1;
x = Math.abs(x);
const t = 1.0 / (1.0 + p * x);
const y = 1.0 - (((((a5 * t + a4) * t) + a3) * t + a2) * t + a1) * t * Math.exp(-x * x);
return sign * y;
}
function showGamma() {
const data = [generateData(gamma, [0.1, 5], 1000, 'Gamma(x)')];
Plotly.newPlot('plot', data, {
title: 'Gamma Function',
xaxis: { title: 'x' },
yaxis: { title: 'Γ(x)' }
});
}
function showBessel() {
const data = [generateData(besselJ0, [-15, 15], 1000, 'J₀(x)')];
Plotly.newPlot('plot', data, {
title: 'Bessel Function of the First Kind (J₀)',
xaxis: { title: 'x' },
yaxis: { title: 'J₀(x)' }
});
}
function showErf() {
const data = [generateData(erf, [-3, 3], 1000, 'erf(x)')];
Plotly.newPlot('plot', data, {
title: 'Error Function',
xaxis: { title: 'x' },
yaxis: { title: 'erf(x)' }
});
}
function showAll() {
const data = [
generateData(x => gamma(x) / 10, [0.1, 3], 500, 'Γ(x)/10'),
generateData(besselJ0, [-10, 10], 500, 'J₀(x)'),
generateData(erf, [-3, 3], 500, 'erf(x)')
];
Plotly.newPlot('plot', data, {
title: 'Special Functions Comparison',
xaxis: { title: 'x' },
yaxis: { title: 'f(x)' }
});
}
// Initialize with gamma function
window.onload = showGamma;
</script>
</body>
</html>
"#;
template.to_string()
}
}
pub mod export {
use super::*;
pub enum ExportFormat {
PNG,
SVG,
PDF,
LaTeX,
CSV,
}
pub fn export_plot_data<F>(
f: F,
x_range: (f64, f64),
n_points: usize,
format: ExportFormat,
) -> Result<Vec<u8>, Box<dyn Error>>
where
F: Fn(f64) -> f64,
{
match format {
ExportFormat::CSV => {
let mut csv_data = String::from("x,y\n");
let step = (x_range.1 - x_range.0) / n_points as f64;
for i in 0..=n_points {
let x = x_range.0 + i as f64 * step;
let y = f(x);
csv_data.push_str(&format!("{},{}\n", x, y));
}
Ok(csv_data.into_bytes())
}
ExportFormat::LaTeX => {
let mut latex = String::from("\\begin{tikzpicture}\n\\begin{axis}[\n");
latex.push_str(" xlabel=$x$,\n ylabel=$f(x)$,\n]\n");
latex.push_str("\\addplot[blue,thick] coordinates {\n");
let step = (x_range.1 - x_range.0) / n_points as f64;
for i in 0..=n_points {
let x = x_range.0 + i as f64 * step;
let y = f(x);
if y.is_finite() {
latex.push_str(&format!(" ({},{})\n", x, y));
}
}
latex.push_str("};\n\\end{axis}\n\\end{tikzpicture}\n");
Ok(latex.into_bytes())
}
ExportFormat::PDF => {
Err("PDF export is not yet implemented".to_string().into())
}
ExportFormat::PNG => {
let mut png_data = Vec::new();
{
let backend =
plotters::backend::BitMapBackend::with_buffer(&mut png_data, (800, 600))
.into_drawing_area();
backend
.fill(&plotters::style::colors::WHITE)
.map_err(|e| format!("Failed to fill background: {}", e))?;
let mut chart = plotters::chart::ChartBuilder::on(&backend)
.caption("Special Function Plot", ("sans-serif", 30))
.margin(10)
.x_label_area_size(30)
.y_label_area_size(40)
.build_cartesian_2d(x_range.0..x_range.1, -2f64..2f64)
.map_err(|e| format!("Failed to build chart: {}", e))?;
chart
.configure_mesh()
.x_desc("x")
.y_desc("f(x)")
.draw()
.map_err(|e| format!("Failed to draw mesh: {}", e))?;
let data: Vec<(f64, f64)> = (0..=n_points)
.map(|i| {
let x =
x_range.0 + i as f64 * (x_range.1 - x_range.0) / n_points as f64;
let y = f(x);
(x, y)
})
.filter(|(_, y)| y.is_finite())
.collect();
chart
.draw_series(plotters::series::LineSeries::new(
data,
&plotters::style::colors::BLUE,
))
.map_err(|e| format!("Failed to draw series: {}", e))?;
backend
.present()
.map_err(|e| format!("Failed to present plot: {}", e))?;
}
Ok(png_data)
}
ExportFormat::SVG => {
let mut svg_data = String::new();
{
let backend =
plotters::backend::SVGBackend::with_string(&mut svg_data, (800, 600));
let root = backend.into_drawing_area();
root.fill(&plotters::style::colors::WHITE)
.map_err(|e| format!("Failed to fill background: {}", e))?;
let mut chart = plotters::chart::ChartBuilder::on(&root)
.caption("Special Function Plot", ("sans-serif", 30))
.margin(10)
.x_label_area_size(30)
.y_label_area_size(40)
.build_cartesian_2d(x_range.0..x_range.1, -2f64..2f64)
.map_err(|e| format!("Failed to build chart: {}", e))?;
chart
.configure_mesh()
.x_desc("x")
.y_desc("f(x)")
.draw()
.map_err(|e| format!("Failed to draw mesh: {}", e))?;
let data: Vec<(f64, f64)> = (0..=n_points)
.map(|i| {
let x =
x_range.0 + i as f64 * (x_range.1 - x_range.0) / n_points as f64;
let y = f(x);
(x, y)
})
.filter(|(_, y)| y.is_finite())
.collect();
chart
.draw_series(plotters::series::LineSeries::new(
data,
&plotters::style::colors::BLUE,
))
.map_err(|e| format!("Failed to draw series: {}", e))?;
root.present()
.map_err(|e| format!("Failed to present plot: {}", e))?;
}
Ok(svg_data.into_bytes())
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_plot_config() {
let config = PlotConfig::default();
assert_eq!(config.width, 800);
assert_eq!(config.height, 600);
assert!(config.show_grid);
}
#[test]
fn test_export_csv() {
let data = export::export_plot_data(|x| x * x, (0.0, 1.0), 10, export::ExportFormat::CSV)
.expect("Operation failed");
let csv = String::from_utf8(data).expect("Operation failed");
assert!(csv.contains("x,y\n"));
assert!(csv.contains("0,0\n"));
assert!(csv.contains("1,1\n"));
}
}