runmat_plot/export/
web.rs1use runmat_time::unix_timestamp_us;
6use std::collections::HashMap;
7use std::path::Path;
8
9pub struct WebExporter {
11 settings: WebExportSettings,
13 widgets: HashMap<String, WidgetInstance>,
15}
16
17#[derive(Debug, Clone)]
19pub struct WebExportSettings {
20 pub width: u32,
22 pub height: u32,
24 pub enable_webassembly: bool,
26 pub enable_webgl: bool,
28 pub update_fps: u32,
30 pub enable_controls: bool,
32 pub include_styles: bool,
34}
35
36#[derive(Debug, Clone)]
38pub struct WidgetInstance {
39 pub id: String,
41 pub render_data: Vec<SerializedRenderData>,
43}
44
45#[derive(Debug, Clone, Serialize, Deserialize)]
47pub struct SerializedRenderData {
48 pub vertices: Vec<f32>,
50 pub indices: Vec<u32>,
52 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
70use serde::{Deserialize, Serialize};
72
73impl WebExporter {
74 pub fn new() -> Self {
76 Self {
77 settings: WebExportSettings::default(),
78 widgets: HashMap::new(),
79 }
80 }
81
82 pub fn with_settings(settings: WebExportSettings) -> Self {
84 Self {
85 settings,
86 widgets: HashMap::new(),
87 }
88 }
89
90 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 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 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 format!("runmat_{timestamp}_{inc}")
166 }
167
168 pub fn set_settings(&mut self, settings: WebExportSettings) {
170 self.settings = settings;
171 }
172
173 pub fn settings(&self) -> &WebExportSettings {
175 &self.settings
176 }
177
178 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}