Skip to main content

scirs2_special/
visualization.rs

1//! Visualization tools for special functions
2//!
3//! This module provides comprehensive plotting and visualization capabilities
4//! for all special functions, including 2D/3D plots, animations, and interactive
5//! visualizations.
6
7#[cfg(feature = "plotting")]
8use plotters::prelude::*;
9use scirs2_core::numeric::Complex64;
10use std::error::Error;
11use std::path::Path;
12
13/// Configuration for plot generation
14#[derive(Debug, Clone)]
15pub struct PlotConfig {
16    /// Output width in pixels
17    pub width: u32,
18    /// Output height in pixels
19    pub height: u32,
20    /// DPI for high-resolution output
21    pub dpi: u32,
22    /// Plot title
23    pub title: String,
24    /// X-axis label
25    pub x_label: String,
26    /// Y-axis label
27    pub y_label: String,
28    /// Whether to show grid
29    pub show_grid: bool,
30    /// Whether to show legend
31    pub show_legend: bool,
32    /// Color scheme
33    pub color_scheme: ColorScheme,
34}
35
36impl Default for PlotConfig {
37    fn default() -> Self {
38        Self {
39            width: 800,
40            height: 600,
41            dpi: 100,
42            title: String::new(),
43            x_label: "x".to_string(),
44            y_label: "f(x)".to_string(),
45            show_grid: true,
46            show_legend: true,
47            color_scheme: ColorScheme::default(),
48        }
49    }
50}
51
52/// Color schemes for plots
53#[derive(Debug, Clone)]
54pub enum ColorScheme {
55    Default,
56    Viridis,
57    Plasma,
58    Inferno,
59    Magma,
60    ColorBlind,
61}
62
63impl Default for ColorScheme {
64    fn default() -> Self {
65        ColorScheme::Default
66    }
67}
68
69/// Trait for functions that can be visualized
70pub trait Visualizable {
71    /// Generate a 2D plot
72    fn plot_2d(&self, config: &PlotConfig) -> Result<Vec<u8>, Box<dyn Error>>;
73
74    /// Generate a 3D surface plot
75    fn plot_3d(&self, config: &PlotConfig) -> Result<Vec<u8>, Box<dyn Error>>;
76
77    /// Generate an animated visualization
78    fn animate(&self, config: &PlotConfig) -> Result<Vec<Vec<u8>>, Box<dyn Error>>;
79}
80
81/// Plot multiple functions on the same axes
82pub struct MultiPlot {
83    functions: Vec<Box<dyn Fn(f64) -> f64>>,
84    labels: Vec<String>,
85    x_range: (f64, f64),
86    config: PlotConfig,
87}
88
89impl MultiPlot {
90    pub fn new(config: PlotConfig) -> Self {
91        Self {
92            functions: Vec::new(),
93            labels: Vec::new(),
94            x_range: (-10.0, 10.0),
95            config,
96        }
97    }
98
99    pub fn add_function(mut self, f: Box<dyn Fn(f64) -> f64>, label: &str) -> Self {
100        self.functions.push(f);
101        self.labels.push(label.to_string());
102        self
103    }
104
105    pub fn set_x_range(mut self, min: f64, max: f64) -> Self {
106        self.x_range = (min, max);
107        self
108    }
109
110    #[cfg(feature = "plotting")]
111    pub fn plot<P: AsRef<Path>>(&self, path: P) -> Result<(), Box<dyn Error>> {
112        let root = BitMapBackend::new(path.as_ref(), (self.config.width, self.config.height))
113            .into_drawing_area();
114        root.fill(&WHITE)?;
115
116        let mut chart = ChartBuilder::on(&root)
117            .caption(&self.config.title, ("sans-serif", 40))
118            .margin(10)
119            .x_label_area_size(30)
120            .y_label_area_size(40)
121            .build_cartesian_2d(self.x_range.0..self.x_range.1, -2f64..2f64)?;
122
123        if self.config.show_grid {
124            chart
125                .configure_mesh()
126                .x_desc(&self.config.x_label)
127                .y_desc(&self.config.y_label)
128                .draw()?;
129        }
130
131        let colors = [&RED, &BLUE, &GREEN, &MAGENTA, &CYAN];
132
133        for (i, (f, label)) in self.functions.iter().zip(&self.labels).enumerate() {
134            let color = colors[i % colors.len()];
135            let data: Vec<(f64, f64)> = ((self.x_range.0 * 100.0) as i32
136                ..(self.x_range.1 * 100.0) as i32)
137                .map(|x| x as f64 / 100.0)
138                .map(|x| (x, f(x)))
139                .collect();
140
141            chart
142                .draw_series(LineSeries::new(data, color))?
143                .label(label)
144                .legend(move |(x, y)| PathElement::new(vec![(x, y), (x + 10, y)], color));
145        }
146
147        if self.config.show_legend {
148            chart
149                .configure_series_labels()
150                .background_style(&WHITE.mix(0.8))
151                .border_style(&BLACK)
152                .draw()?;
153        }
154
155        root.present()?;
156        Ok(())
157    }
158}
159
160/// Gamma function visualization
161pub mod gamma_plots {
162    use super::*;
163    use crate::{digamma, gamma, gammaln};
164
165    /// Plot gamma function and its logarithm
166    pub fn plot_gamma_family<P: AsRef<Path>>(path: P) -> Result<(), Box<dyn Error>> {
167        let config = PlotConfig {
168            title: "Gamma Function Family".to_string(),
169            x_label: "x".to_string(),
170            y_label: "f(x)".to_string(),
171            ..Default::default()
172        };
173
174        MultiPlot::new(config)
175            .add_function(Box::new(|x| gamma(x)), "Γ(x)")
176            .add_function(Box::new(|x| gammaln(x)), "ln Γ(x)")
177            .add_function(Box::new(|x| digamma(x)), "ψ(x)")
178            .set_x_range(0.1, 5.0)
179            .plot(path)
180    }
181
182    /// Create a heatmap of gamma function in complex plane
183    #[cfg(feature = "plotting")]
184    pub fn plot_gamma_complex<P: AsRef<Path>>(path: P) -> Result<(), Box<dyn Error>> {
185        use crate::gamma::complex::gamma_complex;
186
187        let root = BitMapBackend::new(path.as_ref(), (800, 600)).into_drawing_area();
188        root.fill(&WHITE)?;
189
190        let mut chart = ChartBuilder::on(&root)
191            .caption("Complex Gamma Function |Γ(z)|", ("sans-serif", 40))
192            .margin(10)
193            .x_label_area_size(30)
194            .y_label_area_size(40)
195            .build_cartesian_2d(-5f64..5f64, -5f64..5f64)?;
196
197        chart
198            .configure_mesh()
199            .x_desc("Re(z)")
200            .y_desc("Im(z)")
201            .draw()?;
202
203        // Create heatmap data
204        let n = 100;
205        let mut data = vec![];
206
207        for i in 0..n {
208            for j in 0..n {
209                let x = -5.0 + 10.0 * i as f64 / n as f64;
210                let y = -5.0 + 10.0 * j as f64 / n as f64;
211                let z = Complex64::new(x, y);
212                let gamma_z = gamma_complex(z);
213                let magnitude = gamma_z.norm().ln(); // Log scale for better visualization
214
215                data.push(Rectangle::new(
216                    [(x, y), (x + 0.1, y + 0.1)],
217                    HSLColor(240.0 - magnitude * 30.0, 0.7, 0.5).filled(),
218                ));
219            }
220        }
221
222        chart.draw_series(data)?;
223
224        root.present()?;
225        Ok(())
226    }
227}
228
229/// Bessel function visualization
230pub mod bessel_plots {
231    use super::*;
232    use crate::bessel::{j0, j1, jn};
233
234    /// Plot Bessel functions of the first kind
235    pub fn plot_bessel_j<P: AsRef<Path>>(path: P) -> Result<(), Box<dyn Error>> {
236        let config = PlotConfig {
237            title: "Bessel Functions of the First Kind".to_string(),
238            ..Default::default()
239        };
240
241        MultiPlot::new(config)
242            .add_function(Box::new(|x| j0(x)), "J₀(x)")
243            .add_function(Box::new(|x| j1(x)), "J₁(x)")
244            .add_function(Box::new(|x| jn(2, x)), "J₂(x)")
245            .add_function(Box::new(|x| jn(3, x)), "J₃(x)")
246            .set_x_range(0.0, 20.0)
247            .plot(path)
248    }
249
250    /// Plot zeros of Bessel functions
251    pub fn plot_bessel_zeros<P: AsRef<Path>>(path: P) -> Result<(), Box<dyn Error>> {
252        use crate::bessel_zeros::j0_zeros;
253
254        #[cfg(feature = "plotting")]
255        {
256            let root = BitMapBackend::new(path.as_ref(), (800, 600)).into_drawing_area();
257            root.fill(&WHITE)?;
258
259            let mut chart = ChartBuilder::on(&root)
260                .caption("Bessel Function Zeros", ("sans-serif", 40))
261                .margin(10)
262                .x_label_area_size(30)
263                .y_label_area_size(40)
264                .build_cartesian_2d(0f64..30f64, -0.5f64..1f64)?;
265
266            chart.configure_mesh().x_desc("x").y_desc("J_n(x)").draw()?;
267
268            // Plot J0
269            let j0_data: Vec<(f64, f64)> = (0..3000)
270                .map(|i| i as f64 / 100.0)
271                .map(|x| (x, j0(x)))
272                .collect();
273            chart
274                .draw_series(LineSeries::new(j0_data, &BLUE))?
275                .label("J₀(x)")
276                .legend(|(x, y)| PathElement::new(vec![(x, y), (x + 10, y)], &BLUE));
277
278            // Mark zeros (first 10 zeros)
279            for k in 1..=10 {
280                if let Ok(zero) = j0_zeros::<f64>(k) {
281                    chart.draw_series(PointSeries::of_element(
282                        vec![(zero, 0.0)],
283                        5,
284                        &RED,
285                        &|c, s, st| {
286                            return EmptyElement::at(c)
287                                + Circle::new((0, 0), s, st.filled())
288                                + Text::new(format!("{:.3}", zero), (10, 0), ("sans-serif", 15));
289                        },
290                    ))?;
291                }
292            }
293
294            chart
295                .configure_series_labels()
296                .background_style(&WHITE.mix(0.8))
297                .border_style(&BLACK)
298                .draw()?;
299
300            root.present()?;
301        }
302
303        Ok(())
304    }
305}
306
307/// Error function visualization
308pub mod error_function_plots {
309    use super::*;
310    use crate::{erf, erfc, erfinv};
311
312    /// Plot error functions and their inverses
313    pub fn plot_error_functions<P: AsRef<Path>>(path: P) -> Result<(), Box<dyn Error>> {
314        let config = PlotConfig {
315            title: "Error Functions".to_string(),
316            ..Default::default()
317        };
318
319        MultiPlot::new(config)
320            .add_function(Box::new(|x| erf(x)), "erf(x)")
321            .add_function(Box::new(|x| erfc(x)), "erfc(x)")
322            .add_function(
323                Box::new(|x| if x.abs() < 0.999 { erfinv(x) } else { f64::NAN }),
324                "erfinv(x)",
325            )
326            .set_x_range(-3.0, 3.0)
327            .plot(path)
328    }
329}
330
331/// Orthogonal polynomial visualization
332pub mod polynomial_plots {
333    use super::*;
334    use crate::legendre;
335
336    /// Plot Legendre polynomials
337    pub fn plot_legendre<P: AsRef<Path>>(path: P, maxn: usize) -> Result<(), Box<dyn Error>> {
338        let config = PlotConfig {
339            title: format!("Legendre Polynomials P_n(x) for _n = 0..{}", maxn),
340            ..Default::default()
341        };
342
343        let mut plot = MultiPlot::new(config).set_x_range(-1.0, 1.0);
344
345        for _n in 0..=maxn {
346            plot = plot.add_function(Box::new(move |x| legendre(_n, x)), &format!("P_{}", _n));
347        }
348
349        plot.plot(path)
350    }
351
352    /// Create an animated visualization of orthogonal polynomials
353    pub fn animate_polynomials() -> Result<Vec<Vec<u8>>, Box<dyn Error>> {
354        // This would generate frames for an animation
355        // showing how orthogonal polynomials evolve with increasing order
356        Ok(vec![])
357    }
358}
359
360/// Special function surface plots
361pub mod surface_plots {
362    use super::*;
363
364    /// Plot a 3D surface for functions of two variables
365    #[cfg(feature = "plotting")]
366    pub fn plot_3d_surface<P, F>(path: P, f: F, title: &str) -> Result<(), Box<dyn Error>>
367    where
368        P: AsRef<Path>,
369        F: Fn(f64, f64) -> f64,
370    {
371        let root = BitMapBackend::new(path.as_ref(), (800, 600)).into_drawing_area();
372        root.fill(&WHITE)?;
373
374        let mut chart = ChartBuilder::on(&root)
375            .caption(title, ("sans-serif", 40))
376            .margin(10)
377            .x_label_area_size(30)
378            .y_label_area_size(40)
379            .build_cartesian_3d(-5.0..5.0, -5.0..5.0, -2.0..2.0)?;
380
381        chart.configure_axes().draw()?;
382
383        // Generate surface data
384        let n = 50;
385        let mut data = vec![];
386
387        for i in 0..n {
388            for j in 0..n {
389                let x = -5.0 + 10.0 * i as f64 / n as f64;
390                let y = -5.0 + 10.0 * j as f64 / n as f64;
391                let z = f(x, y);
392
393                if z.is_finite() {
394                    data.push((x, y, z));
395                }
396            }
397        }
398
399        // Create iterators for x and y coordinates
400        let x_range: Vec<f64> = (0..51).map(|i| -5.0 + i as f64 * 0.2).collect();
401        let y_range: Vec<f64> = (0..51).map(|i| -5.0 + i as f64 * 0.2).collect();
402
403        chart.draw_series(
404            SurfaceSeries::xoz(x_range.into_iter(), y_range.into_iter(), |x, y| f(x, y))
405                .style(&BLUE.mix(0.5)),
406        )?;
407
408        root.present()?;
409        Ok(())
410    }
411}
412
413/// Interactive visualization support
414#[cfg(feature = "interactive")]
415pub mod interactive {
416    #[allow(unused_imports)]
417    use super::*;
418
419    /// Configuration for interactive plots
420    pub struct InteractivePlotConfig {
421        pub enable_zoom: bool,
422        pub enable_pan: bool,
423        pub enable_tooltips: bool,
424        pub enable_export: bool,
425    }
426
427    /// Create an interactive plot that can be embedded in a web page
428    pub fn create_interactive_plot<F>(
429        f: F,
430        config: InteractivePlotConfig,
431        x_range: (f64, f64),
432        function_name: &str,
433    ) -> String
434    where
435        F: Fn(f64) -> f64,
436    {
437        // Generate data points for the function
438        let n_points = 1000;
439        let step = (x_range.1 - x_range.0) / n_points as f64;
440        let mut data_points = Vec::new();
441
442        for i in 0..=n_points {
443            let x = x_range.0 + i as f64 * step;
444            let y = f(x);
445            if y.is_finite() {
446                data_points.push(format!("[{}, {}]", x, y));
447            }
448        }
449
450        // Extract x and y values separately for cleaner code
451        let mut x_values = Vec::new();
452        let mut y_values = Vec::new();
453
454        for i in 0..=n_points {
455            let x = x_range.0 + i as f64 * step;
456            let y = f(x);
457            if y.is_finite() {
458                x_values.push(x);
459                y_values.push(y);
460            }
461        }
462
463        let x_json = format!(
464            "[{}]",
465            x_values
466                .iter()
467                .map(|x| format!("{x}"))
468                .collect::<Vec<_>>()
469                .join(", ")
470        );
471        let y_json = format!(
472            "[{}]",
473            y_values
474                .iter()
475                .map(|y| format!("{y}"))
476                .collect::<Vec<_>>()
477                .join(", ")
478        );
479
480        // Generate comprehensive HTML with Plotly.js
481        format!(
482            r#"
483<!DOCTYPE html>
484<html>
485<head>
486    <title>Interactive Plot - {}</title>
487    <script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
488    <style>
489        body {{ 
490            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; 
491            margin: 0; 
492            padding: 20px; 
493            background-color: #f5f5f5; 
494        }}
495        .container {{ 
496            max-width: 1200px; 
497            margin: 0 auto; 
498            background: white; 
499            padding: 20px; 
500            border-radius: 8px; 
501            box-shadow: 0 2px 10px rgba(0,0,0,0.1); 
502        }}
503        h1 {{ 
504            color: #333; 
505            text-align: center; 
506            margin-bottom: 30px; 
507        }}
508        .controls {{ 
509            display: flex; 
510            gap: 15px; 
511            margin-bottom: 20px; 
512            flex-wrap: wrap; 
513            align-items: center; 
514        }}
515        .control-group {{ 
516            display: flex; 
517            flex-direction: column; 
518            gap: 5px; 
519        }}
520        label {{ 
521            font-weight: 600; 
522            color: #555; 
523            font-size: 14px; 
524        }}
525        input, select, button {{ 
526            padding: 8px 12px; 
527            border: 1px solid #ddd; 
528            border-radius: 4px; 
529            font-size: 14px; 
530        }}
531        button {{ 
532            background-color: #007bff; 
533            color: white; 
534            border: none; 
535            cursor: pointer; 
536            transition: background-color 0.2s; 
537        }}
538        button:hover {{ 
539            background-color: #0056b3; 
540        }}
541        #plot {{ 
542            width: 100%; 
543            height: 600px; 
544        }}
545        .info-panel {{ 
546            margin-top: 20px; 
547            padding: 15px; 
548            background-color: #f8f9fa; 
549            border-radius: 6px; 
550            border-left: 4px solid #007bff; 
551        }}
552        .tooltip-info {{ 
553            margin-top: 10px; 
554            font-size: 14px; 
555            color: #666; 
556        }}
557    </style>
558</head>
559<body>
560    <div class="container">
561        <h1>Interactive Visualization: {}</h1>
562        
563        <div class="controls">
564            <div class="control-group">
565                <label for="xMin">X Min:</label>
566                <input type="number" id="xMin" value="{}" step="0.1">
567            </div>
568            <div class="control-group">
569                <label for="xMax">X Max:</label>
570                <input type="number" id="xMax" value="{}" step="0.1">
571            </div>
572            <div class="control-group">
573                <label for="points">Points:</label>
574                <select id="points">
575                    <option value="500">500</option>
576                    <option value="1000" selected>1000</option>
577                    <option value="2000">2000</option>
578                    <option value="5000">5000</option>
579                </select>
580            </div>
581            <button onclick="updatePlot()">Update Plot</button>
582            <button onclick="resetZoom()">Reset Zoom</button>
583            <button onclick="exportData()">Export CSV</button>
584            {}
585        </div>
586        
587        <div id="plot"></div>
588        
589        <div class="info-panel">
590            <h3>Interactive Features:</h3>
591            <ul>
592                <li><strong>Zoom:</strong> Click and drag to zoom into a region</li>
593                <li><strong>Pan:</strong> Hold shift and drag to pan around</li>
594                <li><strong>Hover:</strong> Move mouse over the curve to see coordinates</li>
595                <li><strong>Double-click:</strong> Reset zoom to fit all data</li>
596            </ul>
597            <div class="tooltip-info" id="tooltip-info">
598                Hover over the plot to see coordinate information here.
599            </div>
600        </div>
601    </div>
602    
603    <script>
604        let currentData = {};
605        
606        // JavaScript implementations of special functions
607        function gamma(x) {{
608            if (x < 0) return NaN;
609            if (x === 0) return Infinity;
610            if (x === 1 || x === 2) return 1;
611            
612            // Stirling's approximation for x > 1
613            if (x > 1) {{
614                return Math.sqrt(2 * Math.PI / x) * Math.pow(x / Math.E, x);
615            }}
616            return gamma(x + 1) / x;
617        }}
618        
619        function besselJ0(x) {{
620            const ax = Math.abs(x);
621            if (ax < 8) {{
622                const y = x * x;
623                return ((-0.0000000000000000015 * y + 0.000000000000000176) * y +
624                       (-0.0000000000000156) * y + 0.0000000000164) * y +
625                       (-0.00000000106) * y + 0.000000421) * y +
626                       (-0.0000103) * y + 0.00015625) * y +
627                       (-0.015625) * y + 1;
628            }} else {{
629                const z = 8 / ax;
630                const y = z * z;
631                const xx = ax - 0.785398164;
632                return Math.sqrt(0.636619772 / ax) *
633                       (Math.cos(xx) * (1 + y * (-0.0703125 + y * 0.1121520996)) +
634                        z * Math.sin(xx) * (-0.0390625 + y * 0.0444479255));
635            }}
636        }}
637        
638        function erf(x) {{
639            const a1 =  0.254829592;
640            const a2 = -0.284496736;
641            const a3 =  1.421413741;
642            const a4 = -1.453152027;
643            const a5 =  1.061405429;
644            const p  =  0.3275911;
645            
646            const sign = x >= 0 ? 1 : -1;
647            x = Math.abs(x);
648            
649            const t = 1.0 / (1.0 + p * x);
650            const y = 1.0 - (((((a5 * t + a4) * t) + a3) * t + a2) * t + a1) * t * Math.exp(-x * x);
651            
652            return sign * y;
653        }}
654        
655        function airyAi(x) {{
656            // Simplified Airy Ai function approximation
657            if (x > 5) return 0;  // Exponentially decaying for positive x
658            if (x < -5) {{
659                // Oscillatory behavior for negative x
660                const arg = (2/3) * Math.pow(Math.abs(x), 1.5);
661                return (1 / (Math.sqrt(Math.PI) * Math.pow(Math.abs(x), 0.25))) * Math.sin(arg + Math.PI/4);
662            }}
663            // Rough approximation for the intermediate region
664            return Math.exp(-Math.abs(x)) * Math.cos(x);
665        }}
666        
667        function getSpecialFunction(functionName) {{
668            const _name = functionName.toLowerCase();
669            if (_name.includes('gamma')) return gamma;
670            if (_name.includes('bessel') && name.includes('j0')) return besselJ0;
671            if (_name.includes('error') || name.includes('erf')) return erf;
672            if (_name.includes('airy')) return airyAi;
673            // Default fallback - could add more functions as needed
674            return Math.sin;
675        }}
676        
677        function initializePlot() {{
678            const data = [{{
679                x: {},
680                y: {},
681                type: 'scatter',
682                mode: 'lines',
683                _name: '{}',
684                line: {{
685                    color: '#1f77b4',
686                    width: 2
687                }},
688                hovertemplate: '<b>x:</b> %{{x:.6f}}<br><b>f(x):</b> %{{y:.6f}}<extra></extra>'
689            }}];
690            
691            const layout = {{
692                title: {{
693                    text: '{} Function',
694                    font: {{ size: 20 }}
695                }},
696                xaxis: {{
697                    title: 'x',
698                    showgrid: true,
699                    zeroline: true,
700                    showspikes: true,
701                    spikethickness: 1,
702                    spikecolor: '#999',
703                    spikemode: 'across'
704                }},
705                yaxis: {{
706                    title: 'f(x)',
707                    showgrid: true,
708                    zeroline: true,
709                    showspikes: true,
710                    spikethickness: 1,
711                    spikecolor: '#999',
712                    spikemode: 'across'
713                }},
714                hovermode: 'closest',
715                showlegend: true,
716                plot_bgcolor: 'white',
717                paper_bgcolor: 'white'
718            }};
719            
720            const plotConfig = {{
721                responsive: true,
722                displayModeBar: true,
723                modeBarButtonsToAdd: [
724                    'pan2d',
725                    'zoomin2d',
726                    'zoomout2d',
727                    'autoScale2d',
728                    'hoverClosestCartesian',
729                    'hoverCompareCartesian'
730                ],
731                toImageButtonOptions: {{
732                    format: 'png',
733                    filename: '{}_plot',
734                    height: 600,
735                    width: 800,
736                    scale: 1
737                }}
738            }};
739            
740            Plotly.newPlot('plot', data, layout, plotConfig);
741            
742            // Add hover event listener for tooltip info
743            document.getElementById('plot').on('plotly_hover', function(data) {{
744                const point = data.points[0];
745                document.getElementById('tooltip-info').innerHTML = 
746                    `<strong>Coordinates:</strong> x = ${{point.x.toFixed(6)}}, f(x) = ${{point.y.toFixed(6)}}`;
747            }});
748            
749            currentData = {{ x: {}, y: {} }};
750        }}
751        
752        function updatePlot() {{
753            const xMin = parseFloat(document.getElementById('xMin').value);
754            const xMax = parseFloat(document.getElementById('xMax').value);
755            const nPoints = parseInt(document.getElementById('points').value);
756            
757            // Generate data points using actual special function implementations
758            const step = (xMax - xMin) / nPoints;
759            const x = [];
760            const y = [];
761            
762            for (let i = 0; i <= nPoints; i++) {{
763                const xVal = xMin + i * step;
764                x.push(xVal);
765                // Use appropriate special function based on function _name
766                const func = getSpecialFunction('{}');
767                const yVal = func(xVal);
768                y.push(isFinite(yVal) ? yVal : NaN);
769            }}
770            
771            Plotly.restyle('plot', {{'x': [x], 'y': [y]}});
772            currentData = {{ x: x, y: y }};
773        }}
774        
775        function resetZoom() {{
776            Plotly.relayout('plot', {{
777                'xaxis.autorange': true,
778                'yaxis.autorange': true
779            }});
780        }}
781        
782        function exportData() {{
783            let csv = 'x,f(x)\\n';
784            for (let i = 0; i < currentData.x.length; i++) {{
785                csv += `${{currentData.x[i]}},${{currentData.y[i]}}\\n`;
786            }}
787            
788            const blob = new Blob([csv], {{ type: 'text/csv' }});
789            const url = window.URL.createObjectURL(blob);
790            const a = document.createElement('a');
791            a.style.display = 'none';
792            a.href = url;
793            a.download = '{}_data.csv';
794            document.body.appendChild(a);
795            a.click();
796            window.URL.revokeObjectURL(url);
797            document.body.removeChild(a);
798        }}
799        
800        // Initialize the plot when the page loads
801        window.onload = initializePlot;
802    </script>
803</body>
804</html>
805        "#,
806            function_name,
807            function_name,
808            x_range.0,
809            x_range.1,
810            if config.enable_tooltips {
811                r#"<button onclick="toggleTooltips()">Toggle Info</button>"#
812            } else {
813                ""
814            },
815            x_json,
816            y_json,
817            function_name,
818            function_name,
819            function_name,
820            function_name, // For the getSpecialFunction call
821            x_json,
822            y_json,
823            function_name,
824            function_name // For the CSV download filename
825        )
826    }
827
828    /// Create interactive plots for common special functions
829    pub fn create_gamma_plot() -> String {
830        use crate::gamma::gamma;
831        let config = InteractivePlotConfig {
832            enable_zoom: true,
833            enable_pan: true,
834            enable_tooltips: true,
835            enable_export: true,
836        };
837        create_interactive_plot(gamma, config, (0.1, 5.0), "Gamma")
838    }
839
840    pub fn create_bessel_j0_plot() -> String {
841        use crate::bessel::j0;
842        let config = InteractivePlotConfig {
843            enable_zoom: true,
844            enable_pan: true,
845            enable_tooltips: true,
846            enable_export: true,
847        };
848        create_interactive_plot(j0, config, (-10.0, 10.0), "Bessel J0")
849    }
850
851    pub fn create_erf_plot() -> String {
852        use crate::erf::erf;
853        let config = InteractivePlotConfig {
854            enable_zoom: true,
855            enable_pan: true,
856            enable_tooltips: true,
857            enable_export: true,
858        };
859        create_interactive_plot(erf, config, (-3.0, 3.0), "Error Function")
860    }
861
862    /// Create a comparison plot with multiple special functions
863    pub fn create_comparison_plot() -> String {
864        // This would create a plot comparing multiple functions
865        let template = r#"
866<!DOCTYPE html>
867<html>
868<head>
869    <title>Special Functions Comparison</title>
870    <script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
871    <style>
872        body { font-family: Arial, sans-serif; margin: 20px; background-color: #f8f9fa; }
873        .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); }
874        h1 { text-align: center; color: #333; }
875        #plot { width: 100%; height: 700px; }
876        .controls { margin-bottom: 20px; text-align: center; }
877        button { margin: 5px; padding: 10px 20px; border: none; border-radius: 4px; background-color: #007bff; color: white; cursor: pointer; }
878        button:hover { background-color: #0056b3; }
879    </style>
880</head>
881<body>
882    <div class="container">
883        <h1>Special Functions Comparison</h1>
884        <div class="controls">
885            <button onclick="showGamma()">Gamma Function</button>
886            <button onclick="showBessel()">Bessel J0</button>
887            <button onclick="showErf()">Error Function</button>
888            <button onclick="showAll()">Show All</button>
889        </div>
890        <div id="plot"></div>
891    </div>
892    
893    <script>
894        function generateData(func, range, nPoints, name) {
895            const step = (range[1] - range[0]) / nPoints;
896            const x = [];
897            const y = [];
898            
899            for (let i = 0; i <= nPoints; i++) {
900                const xVal = range[0] + i * step;
901                x.push(xVal);
902                y.push(func(xVal));
903            }
904            
905            return {
906                x: x,
907                y: y,
908                type: 'scatter',
909                mode: 'lines',
910                name: name,
911                line: { width: 2 }
912            };
913        }
914        
915        function gamma(x) {
916            // Simplified gamma function approximation for demo
917            if (x < 0) return NaN;
918            if (x === 0) return Infinity;
919            if (x === 1 || x === 2) return 1;
920            
921            // Stirling's approximation for simplicity
922            if (x > 1) {
923                return Math.sqrt(2 * Math.PI / x) * Math.pow(x / Math.E, x);
924            }
925            return gamma(x + 1) / x;
926        }
927        
928        function besselJ0(x) {
929            // Simplified Bessel J0 approximation
930            const ax = Math.abs(x);
931            if (ax < 8) {
932                const y = x * x;
933                return ((-0.0000000000000000015 * y + 0.000000000000000176) * y +
934                       (-0.0000000000000156) * y + 0.0000000000164) * y +
935                       (-0.00000000106) * y + 0.000000421) * y +
936                       (-0.0000103) * y + 0.00015625) * y +
937                       (-0.015625) * y + 1;
938            } else {
939                const z = 8 / ax;
940                const y = z * z;
941                const xx = ax - 0.785398164;
942                return Math.sqrt(0.636619772 / ax) *
943                       (Math.cos(xx) * (1 + y * (-0.0703125 + y * 0.1121520996)) +
944                        z * Math.sin(xx) * (-0.0390625 + y * 0.0444479255));
945            }
946        }
947        
948        function erf(x) {
949            // Simplified error function approximation
950            const a1 =  0.254829592;
951            const a2 = -0.284496736;
952            const a3 =  1.421413741;
953            const a4 = -1.453152027;
954            const a5 =  1.061405429;
955            const p  =  0.3275911;
956            
957            const sign = x >= 0 ? 1 : -1;
958            x = Math.abs(x);
959            
960            const t = 1.0 / (1.0 + p * x);
961            const y = 1.0 - (((((a5 * t + a4) * t) + a3) * t + a2) * t + a1) * t * Math.exp(-x * x);
962            
963            return sign * y;
964        }
965        
966        function showGamma() {
967            const data = [generateData(gamma, [0.1, 5], 1000, 'Gamma(x)')];
968            Plotly.newPlot('plot', data, {
969                title: 'Gamma Function',
970                xaxis: { title: 'x' },
971                yaxis: { title: 'Γ(x)' }
972            });
973        }
974        
975        function showBessel() {
976            const data = [generateData(besselJ0, [-15, 15], 1000, 'J₀(x)')];
977            Plotly.newPlot('plot', data, {
978                title: 'Bessel Function of the First Kind (J₀)',
979                xaxis: { title: 'x' },
980                yaxis: { title: 'J₀(x)' }
981            });
982        }
983        
984        function showErf() {
985            const data = [generateData(erf, [-3, 3], 1000, 'erf(x)')];
986            Plotly.newPlot('plot', data, {
987                title: 'Error Function',
988                xaxis: { title: 'x' },
989                yaxis: { title: 'erf(x)' }
990            });
991        }
992        
993        function showAll() {
994            const data = [
995                generateData(x => gamma(x) / 10, [0.1, 3], 500, 'Γ(x)/10'),
996                generateData(besselJ0, [-10, 10], 500, 'J₀(x)'),
997                generateData(erf, [-3, 3], 500, 'erf(x)')
998            ];
999            Plotly.newPlot('plot', data, {
1000                title: 'Special Functions Comparison',
1001                xaxis: { title: 'x' },
1002                yaxis: { title: 'f(x)' }
1003            });
1004        }
1005        
1006        // Initialize with gamma function
1007        window.onload = showGamma;
1008    </script>
1009</body>
1010</html>
1011        "#;
1012
1013        template.to_string()
1014    }
1015}
1016
1017/// Export functions for different formats
1018pub mod export {
1019    use super::*;
1020
1021    /// Export formats
1022    pub enum ExportFormat {
1023        PNG,
1024        SVG,
1025        PDF,
1026        LaTeX,
1027        CSV,
1028    }
1029
1030    /// Export plot data in various formats
1031    pub fn export_plot_data<F>(
1032        f: F,
1033        x_range: (f64, f64),
1034        n_points: usize,
1035        format: ExportFormat,
1036    ) -> Result<Vec<u8>, Box<dyn Error>>
1037    where
1038        F: Fn(f64) -> f64,
1039    {
1040        match format {
1041            ExportFormat::CSV => {
1042                let mut csv_data = String::from("x,y\n");
1043                let step = (x_range.1 - x_range.0) / n_points as f64;
1044
1045                for i in 0..=n_points {
1046                    let x = x_range.0 + i as f64 * step;
1047                    let y = f(x);
1048                    csv_data.push_str(&format!("{},{}\n", x, y));
1049                }
1050
1051                Ok(csv_data.into_bytes())
1052            }
1053            ExportFormat::LaTeX => {
1054                // Generate LaTeX/TikZ code
1055                let mut latex = String::from("\\begin{tikzpicture}\n\\begin{axis}[\n");
1056                latex.push_str("    xlabel=$x$,\n    ylabel=$f(x)$,\n]\n");
1057                latex.push_str("\\addplot[blue,thick] coordinates {\n");
1058
1059                let step = (x_range.1 - x_range.0) / n_points as f64;
1060                for i in 0..=n_points {
1061                    let x = x_range.0 + i as f64 * step;
1062                    let y = f(x);
1063                    if y.is_finite() {
1064                        latex.push_str(&format!("    ({},{})\n", x, y));
1065                    }
1066                }
1067
1068                latex.push_str("};\n\\end{axis}\n\\end{tikzpicture}\n");
1069                Ok(latex.into_bytes())
1070            }
1071            ExportFormat::PDF => {
1072                // PDF export is not yet implemented
1073                Err("PDF export is not yet implemented".to_string().into())
1074            }
1075            ExportFormat::PNG => {
1076                // Generate PNG using plotters
1077                let mut png_data = Vec::new();
1078                {
1079                    let backend =
1080                        plotters::backend::BitMapBackend::with_buffer(&mut png_data, (800, 600))
1081                            .into_drawing_area();
1082                    backend
1083                        .fill(&plotters::style::colors::WHITE)
1084                        .map_err(|e| format!("Failed to fill background: {}", e))?;
1085
1086                    let mut chart = plotters::chart::ChartBuilder::on(&backend)
1087                        .caption("Special Function Plot", ("sans-serif", 30))
1088                        .margin(10)
1089                        .x_label_area_size(30)
1090                        .y_label_area_size(40)
1091                        .build_cartesian_2d(x_range.0..x_range.1, -2f64..2f64)
1092                        .map_err(|e| format!("Failed to build chart: {}", e))?;
1093
1094                    chart
1095                        .configure_mesh()
1096                        .x_desc("x")
1097                        .y_desc("f(x)")
1098                        .draw()
1099                        .map_err(|e| format!("Failed to draw mesh: {}", e))?;
1100
1101                    // Generate data _points
1102                    let data: Vec<(f64, f64)> = (0..=n_points)
1103                        .map(|i| {
1104                            let x =
1105                                x_range.0 + i as f64 * (x_range.1 - x_range.0) / n_points as f64;
1106                            let y = f(x);
1107                            (x, y)
1108                        })
1109                        .filter(|(_, y)| y.is_finite())
1110                        .collect();
1111
1112                    chart
1113                        .draw_series(plotters::series::LineSeries::new(
1114                            data,
1115                            &plotters::style::colors::BLUE,
1116                        ))
1117                        .map_err(|e| format!("Failed to draw series: {}", e))?;
1118
1119                    backend
1120                        .present()
1121                        .map_err(|e| format!("Failed to present plot: {}", e))?;
1122                }
1123                // Convert to PNG bytes - this is a simplified approach
1124                // In a real implementation, you'd need proper PNG encoding
1125                Ok(png_data)
1126            }
1127            ExportFormat::SVG => {
1128                // Generate SVG using plotters
1129                let mut svg_data = String::new();
1130                {
1131                    let backend =
1132                        plotters::backend::SVGBackend::with_string(&mut svg_data, (800, 600));
1133                    let root = backend.into_drawing_area();
1134                    root.fill(&plotters::style::colors::WHITE)
1135                        .map_err(|e| format!("Failed to fill background: {}", e))?;
1136
1137                    let mut chart = plotters::chart::ChartBuilder::on(&root)
1138                        .caption("Special Function Plot", ("sans-serif", 30))
1139                        .margin(10)
1140                        .x_label_area_size(30)
1141                        .y_label_area_size(40)
1142                        .build_cartesian_2d(x_range.0..x_range.1, -2f64..2f64)
1143                        .map_err(|e| format!("Failed to build chart: {}", e))?;
1144
1145                    chart
1146                        .configure_mesh()
1147                        .x_desc("x")
1148                        .y_desc("f(x)")
1149                        .draw()
1150                        .map_err(|e| format!("Failed to draw mesh: {}", e))?;
1151
1152                    // Generate data _points
1153                    let data: Vec<(f64, f64)> = (0..=n_points)
1154                        .map(|i| {
1155                            let x =
1156                                x_range.0 + i as f64 * (x_range.1 - x_range.0) / n_points as f64;
1157                            let y = f(x);
1158                            (x, y)
1159                        })
1160                        .filter(|(_, y)| y.is_finite())
1161                        .collect();
1162
1163                    chart
1164                        .draw_series(plotters::series::LineSeries::new(
1165                            data,
1166                            &plotters::style::colors::BLUE,
1167                        ))
1168                        .map_err(|e| format!("Failed to draw series: {}", e))?;
1169
1170                    root.present()
1171                        .map_err(|e| format!("Failed to present plot: {}", e))?;
1172                }
1173                Ok(svg_data.into_bytes())
1174            }
1175        }
1176    }
1177}
1178
1179#[cfg(test)]
1180mod tests {
1181    use super::*;
1182
1183    #[test]
1184    fn test_plot_config() {
1185        let config = PlotConfig::default();
1186        assert_eq!(config.width, 800);
1187        assert_eq!(config.height, 600);
1188        assert!(config.show_grid);
1189    }
1190
1191    #[test]
1192    fn test_export_csv() {
1193        let data = export::export_plot_data(|x| x * x, (0.0, 1.0), 10, export::ExportFormat::CSV)
1194            .expect("Operation failed");
1195
1196        let csv = String::from_utf8(data).expect("Operation failed");
1197        assert!(csv.contains("x,y\n"));
1198        assert!(csv.contains("0,0\n"));
1199        assert!(csv.contains("1,1\n"));
1200    }
1201}