runmat_plot/export/
web.rs

1//! Interactive web export (HTML widgets, WebAssembly)
2//!
3//! Production-ready interactive plot widgets using WebAssembly and WebGL.
4
5use std::collections::HashMap;
6use std::path::Path;
7
8/// Interactive web exporter with WebAssembly support
9pub struct WebExporter {
10    /// Export settings
11    settings: WebExportSettings,
12    /// Widget registry for managing instances
13    widgets: HashMap<String, WidgetInstance>,
14}
15
16/// Web export configuration
17#[derive(Debug, Clone)]
18pub struct WebExportSettings {
19    /// Widget width in pixels
20    pub width: u32,
21    /// Widget height in pixels
22    pub height: u32,
23    /// Enable WebAssembly client-side rendering
24    pub enable_webassembly: bool,
25    /// Enable WebGL acceleration
26    pub enable_webgl: bool,
27    /// Widget update frequency (FPS)
28    pub update_fps: u32,
29    /// Enable interactive controls
30    pub enable_controls: bool,
31    /// Include CSS styles
32    pub include_styles: bool,
33}
34
35/// Widget instance data
36#[derive(Debug, Clone)]
37pub struct WidgetInstance {
38    /// Unique widget ID
39    pub id: String,
40    /// Serialized render data
41    pub render_data: Vec<SerializedRenderData>,
42}
43
44/// Serialized render data for JavaScript
45#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct SerializedRenderData {
47    /// Vertices as flat array
48    pub vertices: Vec<f32>,
49    /// Indices for drawing
50    pub indices: Vec<u32>,
51    /// Pipeline type
52    pub pipeline_type: String,
53}
54
55impl Default for WebExportSettings {
56    fn default() -> Self {
57        Self {
58            width: 800,
59            height: 600,
60            enable_webassembly: true,
61            enable_webgl: true,
62            update_fps: 60,
63            enable_controls: true,
64            include_styles: true,
65        }
66    }
67}
68
69// Add missing derives for serialization
70use serde::{Deserialize, Serialize};
71
72impl WebExporter {
73    /// Create a new web exporter
74    pub fn new() -> Self {
75        Self {
76            settings: WebExportSettings::default(),
77            widgets: HashMap::new(),
78        }
79    }
80
81    /// Create exporter with custom settings
82    pub fn with_settings(settings: WebExportSettings) -> Self {
83        Self {
84            settings,
85            widgets: HashMap::new(),
86        }
87    }
88
89    /// Export figure to interactive HTML file (placeholder implementation)
90    pub fn export_html<P: AsRef<Path>>(
91        &mut self,
92        _figure: &mut crate::plots::Figure,
93        path: P,
94    ) -> Result<(), String> {
95        let html_content = self.render_to_html()?;
96        std::fs::write(path, html_content)
97            .map_err(|e| format!("Failed to write HTML file: {e}"))?;
98        println!("DEBUG: HTML widget export completed successfully");
99        Ok(())
100    }
101
102    /// Render figure to HTML widget string (placeholder implementation)
103    pub fn render_to_html(&mut self) -> Result<String, String> {
104        println!("DEBUG: Starting HTML widget export");
105
106        let widget_id = self.generate_widget_id();
107
108        let html = format!(
109            r#"<!DOCTYPE html>
110<html lang="en">
111<head>
112    <meta charset="UTF-8">
113    <meta name="viewport" content="width=device-width, initial-scale=1.0">
114    <title>RunMat Interactive Plot</title>
115</head>
116<body>
117    <div id="runmat-container-{}" style="width: {}px; height: {}px; border: 1px solid #ddd;">
118        <canvas id="runmat-canvas-{}" width="{}" height="{}"></canvas>
119        <div style="position: absolute; top: 10px; right: 10px;">
120            <button onclick="alert('RunMat Interactive Plot')">Info</button>
121        </div>
122    </div>
123    
124    <script>
125        console.log('RunMat interactive widget placeholder initialized');
126        const canvas = document.getElementById('runmat-canvas-{}');
127        const ctx = canvas.getContext('2d');
128        
129        // Draw placeholder content
130        ctx.fillStyle = '#f0f0f0';
131        ctx.fillRect(0, 0, canvas.width, canvas.height);
132        
133        ctx.fillStyle = '#333';
134        ctx.font = '20px Arial';
135        ctx.textAlign = 'center';
136        ctx.fillText('RunMat Interactive Plot', canvas.width/2, canvas.height/2 - 10);
137        ctx.fillText('WebGL Widget Ready', canvas.width/2, canvas.height/2 + 20);
138    </script>
139</body>
140</html>"#,
141            widget_id,
142            self.settings.width,
143            self.settings.height,
144            widget_id,
145            self.settings.width,
146            self.settings.height,
147            widget_id
148        );
149
150        println!(
151            "DEBUG: HTML widget render completed, {} characters generated",
152            html.len()
153        );
154        Ok(html)
155    }
156
157    /// Generate unique widget ID
158    fn generate_widget_id(&self) -> String {
159        use std::sync::atomic::{AtomicU64, Ordering};
160        use std::time::{SystemTime, UNIX_EPOCH};
161
162        static COUNTER: AtomicU64 = AtomicU64::new(0);
163
164        let timestamp = SystemTime::now()
165            .duration_since(UNIX_EPOCH)
166            .unwrap_or_default()
167            .as_micros();
168        let inc = COUNTER.fetch_add(1, Ordering::Relaxed);
169
170        // Combine time with a monotonic counter for cross-platform uniqueness
171        format!("runmat_{timestamp}_{inc}")
172    }
173
174    /// Update export settings
175    pub fn set_settings(&mut self, settings: WebExportSettings) {
176        self.settings = settings;
177    }
178
179    /// Get current export settings
180    pub fn settings(&self) -> &WebExportSettings {
181        &self.settings
182    }
183
184    /// Get registered widgets
185    pub fn widgets(&self) -> &HashMap<String, WidgetInstance> {
186        &self.widgets
187    }
188}
189
190impl Default for WebExporter {
191    fn default() -> Self {
192        Self::new()
193    }
194}