Skip to main content

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