leptos_helios_components/
components.rs1use leptos::prelude::*;
4use leptos_helios::chart::*;
5#[derive(Debug, Clone, Copy, PartialEq)]
8pub enum DataSourceType {
9 DataFrame,
10 Url,
11 Json,
12 Csv,
13 Api,
14 WebSocket,
15}
16use leptos_helios::DataFrame;
17use std::sync::Arc;
18
19#[derive(Debug, Clone)]
21pub struct HeliosChart {
22 spec: ChartSpec,
23 data_version: u32,
24 updated: bool,
25 canvas_id: Option<String>,
26 canvas_mounted: bool,
27 canvas_element: Option<String>,
28 accessibility_config: Option<AccessibilityConfig>,
29 connected_data_loaders: Vec<Arc<DataLoader>>,
30}
31
32impl HeliosChart {
33 pub fn new(spec: ChartSpec) -> Result<Self, ComponentError> {
35 Ok(Self {
51 spec,
52 data_version: 1,
53 updated: false,
54 canvas_id: None,
55 canvas_mounted: false,
56 canvas_element: None,
57 accessibility_config: None,
58 connected_data_loaders: vec![],
59 })
60 }
61
62 pub fn new_with_signal(spec_signal: ReadSignal<ChartSpec>) -> Result<Self, ComponentError> {
64 let spec = spec_signal.get();
65 Self::new(spec)
66 }
67
68 pub fn spec(&self) -> &ChartSpec {
70 &self.spec
71 }
72
73 pub fn chart_type(&self) -> ChartType {
75 match self.spec.mark {
76 MarkType::Point { .. } => ChartType::Scatter,
77 MarkType::Scatter { .. } => ChartType::Scatter,
78 MarkType::Line { .. } => ChartType::Line,
79 MarkType::Bar { .. } => ChartType::Bar,
80 MarkType::Area { .. } => ChartType::Area,
81 _ => ChartType::Scatter,
82 }
83 }
84
85 pub fn is_updated(&self) -> bool {
87 self.updated
88 }
89
90 pub fn data_version(&self) -> u32 {
92 self.data_version
93 }
94
95 pub fn connect_data_loader(&mut self, loader: &DataLoader) {
97 self.connected_data_loaders.push(Arc::new(loader.clone()));
98 }
99
100 pub fn create_canvas(&mut self) -> Result<String, ComponentError> {
102 let canvas_id = format!("helios-canvas-{}", uuid::Uuid::new_v4());
103 self.canvas_id = Some(canvas_id.clone());
104 Ok(canvas_id)
105 }
106
107 pub fn mount_canvas(&mut self, canvas_id: &str) {
109 self.canvas_mounted = true;
110 self.canvas_element = Some(format!("canvas-{}", canvas_id));
111 }
112
113 pub fn unmount_canvas(&mut self) {
115 self.canvas_mounted = false;
116 self.canvas_element = None;
117 }
118
119 pub fn is_canvas_mounted(&self) -> bool {
121 self.canvas_mounted
122 }
123
124 pub fn canvas_element(&self) -> Option<&str> {
126 self.canvas_element.as_deref()
127 }
128
129 pub fn set_accessibility_config(&mut self, config: AccessibilityConfig) {
131 self.accessibility_config = Some(config);
132 }
133
134 pub fn has_screen_reader_support(&self) -> bool {
136 self.accessibility_config
137 .as_ref()
138 .is_some_and(|c| c.screen_reader_support)
139 }
140
141 pub fn has_keyboard_navigation(&self) -> bool {
143 self.accessibility_config
144 .as_ref()
145 .is_some_and(|c| c.keyboard_navigation)
146 }
147
148 pub fn is_high_contrast_mode(&self) -> bool {
150 self.accessibility_config
151 .as_ref()
152 .is_some_and(|c| c.high_contrast_mode)
153 }
154
155 pub fn is_reduced_motion(&self) -> bool {
157 self.accessibility_config
158 .as_ref()
159 .is_some_and(|c| c.reduced_motion)
160 }
161
162 pub fn get_aria_label(&self) -> String {
164 format!(
165 "Chart: {}",
166 self.spec.config.title.as_str()
167 )
168 }
169
170 pub fn get_aria_description(&self) -> String {
172 format!(
173 "Data visualization with {} encoding",
174 if self.spec.encoding.x.is_some() {
175 "x"
176 } else {
177 "no x"
178 }
179 )
180 }
181
182 pub fn trigger_update(&mut self) {
184 self.updated = true;
185 self.data_version += 1;
186 }
187}
188
189#[derive(Debug, Clone)]
191pub struct DataLoader {
192 #[allow(dead_code)]
193 source_type: DataSourceType,
194 loading: bool,
195 error: Option<String>,
196 data: Option<DataFrame>,
197 connected_charts: Vec<Arc<HeliosChart>>,
198}
199
200impl DataLoader {
201 pub fn new(source_type: DataSourceType) -> Result<Self, ComponentError> {
203
204 Ok(Self {
205 source_type,
206 loading: true,
207 error: None,
208 data: None,
209 connected_charts: vec![],
210 })
211 }
212
213 pub fn new_with_signal(data_signal: ReadSignal<DataFrame>) -> Result<Self, ComponentError> {
215 let _data = data_signal.get();
216 let source_type = DataSourceType::DataFrame;
217 Self::new(source_type)
218 }
219
220 pub fn source_type(&self) -> DataSourceType {
222 self.source_type
223 }
224
225 pub fn is_loading(&self) -> bool {
227 self.loading
228 }
229
230 pub fn has_error(&self) -> bool {
232 self.error.is_some()
233 }
234
235 pub fn data(&self) -> Option<&DataFrame> {
237 self.data.as_ref()
238 }
239
240 pub fn complete_loading(&mut self) {
242 self.loading = false;
243 self.data = Some(DataFrame::empty());
245 }
246
247 pub fn start_loading(&mut self) {
249 self.loading = true;
250 self.error = Some("Failed to load data".to_string());
251 }
252
253 pub fn has_connected_charts(&self) -> bool {
255 !self.connected_charts.is_empty()
256 }
257}
258
259#[derive(Debug, Clone)]
261pub struct VisualizationDashboard {
262 charts: Vec<ChartSpec>,
263 layout: DashboardLayout,
264 chart_count: usize,
265}
266
267impl VisualizationDashboard {
268 pub fn new(charts: Vec<ChartSpec>) -> Result<Self, ComponentError> {
270 Ok(Self {
271 chart_count: charts.len(),
272 charts,
273 layout: DashboardLayout::Grid(2, 2),
274 })
275 }
276
277 pub fn new_with_layout(
279 charts: Vec<ChartSpec>,
280 layout: DashboardLayout,
281 ) -> Result<Self, ComponentError> {
282 Ok(Self {
283 chart_count: charts.len(),
284 charts,
285 layout,
286 })
287 }
288
289 pub fn chart_count(&self) -> usize {
291 self.chart_count
292 }
293
294 pub fn layout(&self) -> DashboardLayout {
296 self.layout.clone()
297 }
298
299 pub fn add_chart(&mut self, chart: ChartSpec) {
301 self.charts.push(chart);
302 self.chart_count += 1;
303 }
304}
305
306pub async fn process_data_on_server(data: DataFrame) -> Result<DataFrame, ComponentError> {
308 Ok(data)
310}
311
312pub async fn render_chart_on_server(_spec: ChartSpec) -> Result<Vec<u8>, ComponentError> {
313 Ok(vec![1, 2, 3, 4, 5])
315}
316
317#[derive(Debug, thiserror::Error)]
319pub enum ComponentError {
320 #[error("Validation error: {0}")]
321 Validation(String),
322
323 #[error("Rendering error: {0}")]
324 Rendering(String),
325
326 #[error("Data error: {0}")]
327 Data(String),
328}
329
330#[derive(Debug, Clone)]
332pub struct AccessibilityConfig {
333 pub screen_reader_support: bool,
334 pub keyboard_navigation: bool,
335 pub high_contrast_mode: bool,
336 pub reduced_motion: bool,
337}
338
339
340#[derive(Debug, Clone, PartialEq)]
342pub enum DashboardLayout {
343 Grid(u32, u32),
344 Flex(FlexDirection),
345 Custom(String),
346}
347
348#[derive(Debug, Clone, PartialEq)]
350pub enum FlexDirection {
351 Row,
352 Column,
353}
354
355#[derive(Debug, Clone, PartialEq)]
357pub enum ChartType {
358 Line,
359 Scatter,
360 Bar,
361 Area,
362 Pie,
363}