use js_sys::{Array, Function, Object, Reflect};
use plotters::drawing::IntoDrawingArea;
use plotters_canvas::CanvasBackend;
use std::cell::RefCell;
use std::rc::Rc;
use wasm_bindgen::prelude::*;
use web_sys::{CanvasRenderingContext2d, Document, HtmlCanvasElement, Window};
use crate::error::{Error, PandRSError, Result};
use crate::vis::plotters_ext::{PlotKind, PlotSettings};
use crate::DataFrame;
use crate::Series;
type WebResult<T> = std::result::Result<T, JsValue>;
fn to_js_error<E: std::fmt::Display>(err: E) -> JsValue {
JsValue::from_str(&err.to_string())
}
#[wasm_bindgen]
#[derive(Debug, Clone, Copy)]
pub enum ColorTheme {
Default,
Dark,
Light,
Pastel,
Vibrant,
}
#[wasm_bindgen]
#[derive(Debug, Clone, Copy)]
pub enum VisualizationType {
Line,
Bar,
Scatter,
Area,
Pie,
Histogram,
BoxPlot,
HeatMap,
}
#[wasm_bindgen]
#[derive(Debug, Clone)]
pub struct WebVisualizationConfig {
canvas_id: String,
title: String,
viz_type: VisualizationType,
theme: ColorTheme,
width: u32,
height: u32,
show_legend: bool,
show_tooltips: bool,
animate: bool,
}
#[wasm_bindgen]
impl WebVisualizationConfig {
#[wasm_bindgen(constructor)]
pub fn new(canvas_id: &str) -> Self {
WebVisualizationConfig {
canvas_id: canvas_id.to_string(),
title: "Chart".to_string(),
viz_type: VisualizationType::Line,
theme: ColorTheme::Default,
width: 800,
height: 600,
show_legend: true,
show_tooltips: true,
animate: true,
}
}
#[wasm_bindgen]
pub fn set_title(&mut self, title: &str) -> Self {
self.title = title.to_string();
self.clone()
}
#[wasm_bindgen]
pub fn set_type(&mut self, viz_type: VisualizationType) -> Self {
self.viz_type = viz_type;
self.clone()
}
#[wasm_bindgen]
pub fn set_theme(&mut self, theme: ColorTheme) -> Self {
self.theme = theme;
self.clone()
}
#[wasm_bindgen]
pub fn set_dimensions(&mut self, width: u32, height: u32) -> Self {
self.width = width;
self.height = height;
self.clone()
}
#[wasm_bindgen]
pub fn show_legend(&mut self, show: bool) -> Self {
self.show_legend = show;
self.clone()
}
#[wasm_bindgen]
pub fn show_tooltips(&mut self, show: bool) -> Self {
self.show_tooltips = show;
self.clone()
}
#[wasm_bindgen]
pub fn animate(&mut self, animate: bool) -> Self {
self.animate = animate;
self.clone()
}
}
#[wasm_bindgen]
pub struct WebVisualization {
config: WebVisualizationConfig,
canvas: HtmlCanvasElement,
context: CanvasRenderingContext2d,
data: Option<Rc<RefCell<DataFrame>>>,
event_listeners: Vec<(String, Closure<dyn FnMut(web_sys::MouseEvent)>)>,
}
#[wasm_bindgen]
impl WebVisualization {
#[wasm_bindgen(constructor)]
pub fn new(config: WebVisualizationConfig) -> WebResult<WebVisualization> {
let window = web_sys::window().ok_or_else(|| to_js_error("No window object available"))?;
let document = window
.document()
.ok_or_else(|| to_js_error("No document object available"))?;
let canvas = document
.get_element_by_id(&config.canvas_id)
.ok_or_else(|| {
to_js_error(format!(
"Canvas element with ID '{}' not found",
config.canvas_id
))
})?;
let canvas = canvas
.dyn_into::<HtmlCanvasElement>()
.map_err(|_| to_js_error("Element is not a canvas"))?;
canvas.set_width(config.width);
canvas.set_height(config.height);
let context = canvas
.get_context("2d")
.map_err(|_| to_js_error("Failed to get canvas context"))?
.ok_or_else(|| to_js_error("Canvas context is null"))?
.dyn_into::<CanvasRenderingContext2d>()
.map_err(|_| to_js_error("Failed to convert to CanvasRenderingContext2d"))?;
Ok(WebVisualization {
config,
canvas,
context,
data: None,
event_listeners: Vec::new(),
})
}
#[wasm_bindgen]
pub fn set_data(&mut self, data_json: &str) -> WebResult<()> {
let df = DataFrame::from_json(data_json).map_err(|e| to_js_error(e))?;
self.data = Some(Rc::new(RefCell::new(df)));
Ok(())
}
#[wasm_bindgen]
pub fn render(&mut self) -> WebResult<()> {
{
let df = match &self.data {
Some(df) => df.borrow(),
None => return Err(to_js_error("No data available to visualize")),
};
self.context.clear_rect(
0.0,
0.0,
self.config.width as f64,
self.config.height as f64,
);
let columns = df.column_names();
if columns.is_empty() {
return Err(to_js_error("DataFrame has no columns"));
}
let backend = CanvasBackend::with_canvas_object(self.canvas.clone())
.ok_or_else(|| to_js_error("Failed to create canvas backend"))?;
let settings = self.create_plot_settings();
match self.config.viz_type {
VisualizationType::Line => self.render_line_chart(&df, backend, &settings)?,
VisualizationType::Bar => self.render_bar_chart(&df, backend, &settings)?,
VisualizationType::Scatter => self.render_scatter_chart(&df, backend, &settings)?,
VisualizationType::Area => self.render_area_chart(&df, backend, &settings)?,
VisualizationType::Histogram => self.render_histogram(&df, backend, &settings)?,
VisualizationType::BoxPlot => self.render_boxplot(&df, backend, &settings)?,
VisualizationType::Pie => self.render_pie_chart(&df, backend, &settings)?,
VisualizationType::HeatMap => self.render_heatmap(&df, backend, &settings)?,
}
}
if self.config.show_tooltips {
self.setup_tooltip_listeners()?;
}
Ok(())
}
fn create_plot_settings(&self) -> PlotSettings {
let mut settings = PlotSettings::default();
settings.title = self.config.title.clone();
settings.width = self.config.width;
settings.height = self.config.height;
settings.show_legend = self.config.show_legend;
settings.plot_kind = match self.config.viz_type {
VisualizationType::Line => PlotKind::Line,
VisualizationType::Bar => PlotKind::Bar,
VisualizationType::Scatter => PlotKind::Scatter,
VisualizationType::Area => PlotKind::Area,
VisualizationType::Histogram => PlotKind::Histogram,
VisualizationType::BoxPlot => PlotKind::BoxPlot,
_ => PlotKind::Line,
};
settings.color_palette = match self.config.theme {
ColorTheme::Default => vec![
(0, 123, 255), (255, 99, 71), (46, 204, 113), (255, 193, 7), (142, 68, 173), ],
ColorTheme::Dark => vec![
(41, 98, 255), (221, 65, 36), (11, 156, 49), (255, 147, 0), (116, 0, 184), ],
ColorTheme::Light => vec![
(99, 179, 237), (255, 161, 145), (134, 226, 173), (255, 230, 140), (198, 163, 229), ],
ColorTheme::Pastel => vec![
(174, 198, 242), (255, 179, 186), (186, 241, 191), (255, 239, 186), (220, 198, 239), ],
ColorTheme::Vibrant => vec![
(0, 116, 217), (255, 65, 54), (46, 204, 64), (255, 220, 0), (177, 13, 201), ],
};
settings
}
fn render_line_chart(
&self,
df: &DataFrame,
backend: CanvasBackend,
settings: &PlotSettings,
) -> Result<()> {
let mut numeric_columns = Vec::new();
for col_name in df.column_names() {
if df.is_numeric_column(&col_name) {
numeric_columns.push(col_name);
}
}
if numeric_columns.is_empty() {
return Err(Error::InvalidInput(
"No numeric columns available for line chart".to_string(),
));
}
let columns_to_plot = if numeric_columns.len() > 5 {
numeric_columns[0..5].to_vec()
} else {
numeric_columns
};
let columns_str: Vec<&str> = columns_to_plot.iter().map(|s| s.as_str()).collect();
let drawing_area = backend.into_drawing_area();
drawing_area
.fill(&plotters::style::colors::WHITE)
.map_err(|e| Error::Visualization(format!("Failed to fill drawing area: {}", e)))?;
let mut plot_settings = settings.clone();
plot_settings.plot_kind = PlotKind::Line;
plotters_ext_web::plot_multi_series_for_web(
df,
&columns_str,
drawing_area,
&plot_settings,
)?;
Ok(())
}
fn render_bar_chart(
&self,
df: &DataFrame,
backend: CanvasBackend,
settings: &PlotSettings,
) -> Result<()> {
let mut numeric_columns = Vec::new();
for col_name in df.column_names() {
if df.is_numeric_column(&col_name) {
numeric_columns.push(col_name);
}
}
if numeric_columns.is_empty() {
return Err(Error::InvalidInput(
"No numeric columns available for bar chart".to_string(),
));
}
let column = &numeric_columns[0];
let drawing_area = backend.into_drawing_area();
drawing_area
.fill(&plotters::style::colors::WHITE)
.map_err(|e| Error::Visualization(format!("Failed to fill drawing area: {}", e)))?;
let mut plot_settings = settings.clone();
plot_settings.plot_kind = PlotKind::Bar;
plotters_ext_web::plot_column_for_web(df, column, drawing_area, &plot_settings)?;
Ok(())
}
fn render_scatter_chart(
&self,
df: &DataFrame,
backend: CanvasBackend,
settings: &PlotSettings,
) -> Result<()> {
let mut numeric_columns = Vec::new();
for col_name in df.column_names() {
if df.is_numeric_column(&col_name) {
numeric_columns.push(col_name);
}
}
if numeric_columns.len() < 2 {
return Err(Error::InvalidInput(
"Need at least two numeric columns for scatter plot".to_string(),
));
}
let x_column = &numeric_columns[0];
let y_column = &numeric_columns[1];
let drawing_area = backend.into_drawing_area();
drawing_area
.fill(&plotters::style::colors::WHITE)
.map_err(|e| Error::Visualization(format!("Failed to fill drawing area: {}", e)))?;
let mut plot_settings = settings.clone();
plot_settings.plot_kind = PlotKind::Scatter;
plotters_ext_web::plot_scatter_for_web(
df,
x_column,
y_column,
drawing_area,
&plot_settings,
)?;
Ok(())
}
fn render_area_chart(
&self,
df: &DataFrame,
backend: CanvasBackend,
settings: &PlotSettings,
) -> Result<()> {
let mut numeric_columns = Vec::new();
for col_name in df.column_names() {
if df.is_numeric_column(&col_name) {
numeric_columns.push(col_name);
}
}
if numeric_columns.is_empty() {
return Err(Error::InvalidInput(
"No numeric columns available for area chart".to_string(),
));
}
let column = &numeric_columns[0];
let drawing_area = backend.into_drawing_area();
drawing_area
.fill(&plotters::style::colors::WHITE)
.map_err(|e| Error::Visualization(format!("Failed to fill drawing area: {}", e)))?;
let mut plot_settings = settings.clone();
plot_settings.plot_kind = PlotKind::Area;
plotters_ext_web::plot_column_for_web(df, column, drawing_area, &plot_settings)?;
Ok(())
}
fn render_histogram(
&self,
df: &DataFrame,
backend: CanvasBackend,
settings: &PlotSettings,
) -> Result<()> {
let mut numeric_columns = Vec::new();
for col_name in df.column_names() {
if df.is_numeric_column(&col_name) {
numeric_columns.push(col_name);
break; }
}
if numeric_columns.is_empty() {
return Err(Error::InvalidInput(
"No numeric columns available for histogram".to_string(),
));
}
let column = &numeric_columns[0];
let drawing_area = backend.into_drawing_area();
drawing_area
.fill(&plotters::style::colors::WHITE)
.map_err(|e| Error::Visualization(format!("Failed to fill drawing area: {}", e)))?;
let mut plot_settings = settings.clone();
plot_settings.plot_kind = PlotKind::Histogram;
plotters_ext_web::plot_histogram_for_web(
df,
column,
10, drawing_area,
&plot_settings,
)?;
Ok(())
}
fn render_boxplot(
&self,
df: &DataFrame,
backend: CanvasBackend,
settings: &PlotSettings,
) -> Result<()> {
let mut numeric_columns = Vec::new();
let mut categorical_columns = Vec::new();
for col_name in df.column_names() {
if df.is_numeric_column(&col_name) {
numeric_columns.push(col_name);
} else if df.is_categorical(&col_name) || !df.is_numeric_column(&col_name) {
categorical_columns.push(col_name);
}
}
if numeric_columns.is_empty() || categorical_columns.is_empty() {
return Err(Error::InvalidInput(
"Need at least one numeric and one categorical column for boxplot".to_string(),
));
}
let value_column = &numeric_columns[0];
let category_column = &categorical_columns[0];
let drawing_area = backend.into_drawing_area();
drawing_area
.fill(&plotters::style::colors::WHITE)
.map_err(|e| Error::Visualization(format!("Failed to fill drawing area: {}", e)))?;
let mut plot_settings = settings.clone();
plot_settings.plot_kind = PlotKind::BoxPlot;
plotters_ext_web::plot_boxplot_for_web(
df,
category_column,
value_column,
drawing_area,
&plot_settings,
)?;
Ok(())
}
fn render_pie_chart(
&self,
df: &DataFrame,
backend: CanvasBackend,
settings: &PlotSettings,
) -> Result<()> {
let mut numeric_columns = Vec::new();
let mut categorical_columns = Vec::new();
for col_name in df.column_names() {
if df.is_numeric_column(&col_name) {
numeric_columns.push(col_name);
} else if df.is_categorical(&col_name) || !df.is_numeric_column(&col_name) {
categorical_columns.push(col_name);
}
}
if numeric_columns.is_empty() || categorical_columns.is_empty() {
return Err(Error::InvalidInput(
"Need at least one numeric and one categorical column for pie chart".to_string(),
));
}
let value_column = &numeric_columns[0];
let category_column = &categorical_columns[0];
let drawing_area = backend.into_drawing_area();
drawing_area
.fill(&plotters::style::colors::WHITE)
.map_err(|e| Error::Visualization(format!("Failed to fill drawing area: {}", e)))?;
let plot_settings = settings.clone();
self.draw_pie_chart(df, category_column, value_column, &plot_settings)?;
Ok(())
}
fn render_heatmap(
&self,
df: &DataFrame,
backend: CanvasBackend,
settings: &PlotSettings,
) -> Result<()> {
let mut numeric_columns = Vec::new();
for col_name in df.column_names() {
if df.is_numeric_column(&col_name) {
numeric_columns.push(col_name);
}
}
if numeric_columns.len() < 2 {
return Err(Error::InvalidInput(
"Need at least two numeric columns for heatmap".to_string(),
));
}
let drawing_area = backend.into_drawing_area();
drawing_area
.fill(&plotters::style::colors::WHITE)
.map_err(|e| Error::Visualization(format!("Failed to fill drawing area: {}", e)))?;
let plot_settings = settings.clone();
self.draw_heatmap(df, &numeric_columns, &plot_settings)?;
Ok(())
}
fn draw_pie_chart(
&self,
df: &DataFrame,
category_col: &str,
value_col: &str,
settings: &PlotSettings,
) -> Result<()> {
let categories = df.get_column_string_values(category_col)?;
let values = df.get_column_numeric_values(value_col)?;
if categories.len() != values.len() {
return Err(Error::DimensionMismatch(
"Category and value columns must have the same length".to_string(),
));
}
let mut category_values = std::collections::HashMap::new();
for (cat, val) in categories.iter().zip(values.iter()) {
*category_values.entry(cat.clone()).or_insert(0.0) += *val as f64;
}
let total: f64 = category_values.values().sum();
let cx = (self.config.width as f64) / 2.0;
let cy = (self.config.height as f64) / 2.0;
let radius = (self.config.width.min(self.config.height) as f64) * 0.4;
self.context.set_font("16px sans-serif");
self.context.set_text_align("center");
self.context.set_fill_style_str("black");
self.context
.fill_text(&settings.title, cx, 30.0)
.map_err(|_| to_js_error("Failed to render text"))
.expect("operation should succeed");
let mut start_angle = 0.0;
let mut i = 0;
let colors = settings.color_palette.clone();
for (category, value) in &category_values {
let slice_angle = 2.0 * std::f64::consts::PI * (value / total);
let color_idx = i % colors.len();
let (r, g, b) = colors[color_idx];
let color = format!("rgb({}, {}, {})", r, g, b);
self.context.begin_path();
self.context.move_to(cx, cy);
self.context
.arc(cx, cy, radius, start_angle, start_angle + slice_angle)
.map_err(|_| to_js_error("Failed to draw arc"))
.expect("operation should succeed");
self.context.close_path();
self.context.set_fill_style_str(&color);
self.context.fill();
self.context.set_stroke_style_str("white");
self.context.set_line_width(1.0);
self.context.stroke();
let label_angle = start_angle + slice_angle / 2.0;
let label_x = cx + radius * 0.7 * label_angle.cos();
let label_y = cy + radius * 0.7 * label_angle.sin();
let percentage = (value / total * 100.0).round() / 10.0 * 10.0;
self.context.set_font("12px sans-serif");
self.context.set_fill_style_str("white");
self.context.set_text_align("center");
if percentage >= 5.0 {
self.context
.fill_text(&format!("{}%", percentage), label_x, label_y)
.map_err(|_| to_js_error("Failed to render text"))
.expect("operation should succeed");
}
start_angle += slice_angle;
i += 1;
}
if settings.show_legend {
let legend_x = self.config.width as f64 - 150.0;
let legend_y = 50.0;
let mut y_offset = 0.0;
i = 0;
for (category, _) in &category_values {
let color_idx = i % colors.len();
let (r, g, b) = colors[color_idx];
let color = format!("rgb({}, {}, {})", r, g, b);
self.context.set_fill_style_str(&color);
self.context
.fill_rect(legend_x, legend_y + y_offset, 15.0, 15.0);
self.context.set_font("12px sans-serif");
self.context.set_fill_style_str("black");
self.context.set_text_align("left");
let display_cat = if category.len() > 15 {
format!("{}...", &category[0..12])
} else {
category.clone()
};
self.context
.fill_text(&display_cat, legend_x + 20.0, legend_y + y_offset + 12.0)
.map_err(|_| to_js_error("Failed to render text"))
.expect("operation should succeed");
y_offset += 20.0;
i += 1;
}
}
Ok(())
}
fn draw_heatmap(
&self,
df: &DataFrame,
columns: &[String],
settings: &PlotSettings,
) -> Result<()> {
let mut data_matrix = Vec::new();
for col in columns {
let values = df.get_column_numeric_values(col)?;
data_matrix.push(values);
}
let rows = data_matrix.len();
if rows == 0 {
return Err(Error::InvalidInput("No data for heatmap".to_string()));
}
let cols = data_matrix[0].len();
if cols == 0 {
return Err(Error::InvalidInput("Empty columns for heatmap".to_string()));
}
let mut min_val = f64::MAX;
let mut max_val = f64::MIN;
for row in &data_matrix {
for &val in row {
let val = val as f64;
if val < min_val {
min_val = val;
}
if val > max_val {
max_val = val;
}
}
}
self.context.set_font("16px sans-serif");
self.context.set_text_align("center");
self.context.set_fill_style_str("black");
self.context
.fill_text(&settings.title, (self.config.width as f64) / 2.0, 30.0)
.map_err(|_| to_js_error("Failed to render text"))
.expect("operation should succeed");
let margin = 70.0;
let chart_width = self.config.width as f64 - 2.0 * margin;
let chart_height = self.config.height as f64 - 2.0 * margin;
let cell_width = chart_width / cols as f64;
let cell_height = chart_height / rows as f64;
for i in 0..rows {
for j in 0..cols {
let x = margin + j as f64 * cell_width;
let y = margin + i as f64 * cell_height;
let val = data_matrix[i][j] as f64;
let normalized = if max_val > min_val {
(val - min_val) / (max_val - min_val)
} else {
0.5 };
let r = (normalized * 255.0) as u8;
let b = (255.0 - normalized * 255.0) as u8;
let color = format!("rgb({}, 0, {})", r, b);
self.context.set_fill_style_str(&color);
self.context.fill_rect(x, y, cell_width, cell_height);
self.context.set_stroke_style_str("rgba(255,255,255,0.2)");
self.context.set_line_width(0.5);
self.context.stroke_rect(x, y, cell_width, cell_height);
if cell_width > 30.0 && cell_height > 20.0 {
self.context.set_font("10px sans-serif");
self.context.set_fill_style_str("white");
self.context.set_text_align("center");
self.context
.fill_text(
&format!("{:.1}", val),
x + cell_width / 2.0,
y + cell_height / 2.0 + 3.0,
)
.map_err(|_| to_js_error("Failed to render text"))
.expect("operation should succeed");
}
}
}
self.context.set_font("12px sans-serif");
self.context.set_fill_style_str("black");
self.context.set_text_align("center");
for (j, col) in columns.iter().enumerate().take(cols) {
let x = margin + j as f64 * cell_width + cell_width / 2.0;
let y = margin - 10.0;
let display_name = if col.len() > 10 {
format!("{}...", &col[0..7])
} else {
col.clone()
};
self.context
.fill_text(&display_name, x, y)
.map_err(|_| to_js_error("Failed to render text"))
.expect("operation should succeed");
}
self.context.set_text_align("right");
for i in 0..rows {
let x = margin - 10.0;
let y = margin + i as f64 * cell_height + cell_height / 2.0 + 5.0;
self.context
.fill_text(&format!("Row {}", i + 1), x, y)
.map_err(|_| to_js_error("Failed to render text"))
.expect("operation should succeed");
}
let scale_width = 20.0;
let scale_height = chart_height * 0.7;
let scale_x = self.config.width as f64 - margin / 2.0;
let scale_y = margin + (chart_height - scale_height) / 2.0;
for i in 0..100 {
let normalized = i as f64 / 100.0;
let r = (normalized * 255.0) as u8;
let b = (255.0 - normalized * 255.0) as u8;
let color = format!("rgb({}, 0, {})", r, b);
self.context.set_fill_style_str(&color);
self.context.fill_rect(
scale_x - scale_width / 2.0,
scale_y + (1.0 - normalized) * scale_height,
scale_width,
scale_height / 100.0,
);
}
self.context.set_font("10px sans-serif");
self.context.set_fill_style_str("black");
self.context.set_text_align("center");
self.context
.fill_text(&format!("{:.1}", max_val), scale_x, scale_y - 5.0)
.map_err(|_| to_js_error("Failed to render text"))
.expect("operation should succeed");
self.context
.fill_text(
&format!("{:.1}", min_val),
scale_x,
scale_y + scale_height + 15.0,
)
.map_err(|_| to_js_error("Failed to render text"))
.expect("operation should succeed");
Ok(())
}
fn setup_tooltip_listeners(&mut self) -> Result<()> {
for (event, listener) in self.event_listeners.drain(..) {
self.canvas
.remove_event_listener_with_callback(&event, listener.as_ref().unchecked_ref())
.map_err(|_| Error::InvalidInput("Failed to remove event listener".to_string()))?;
}
let canvas_clone = self.canvas.clone();
let context_clone = self.context.clone();
let config_clone = self.config.clone();
let data_clone = self.data.clone();
let mousemove_callback = Closure::wrap(Box::new(move |event: web_sys::MouseEvent| {
let rect = canvas_clone.get_bounding_client_rect();
let x = event.client_x() as f64 - rect.left();
let y = event.client_y() as f64 - rect.top();
if let Some(data) = &data_clone {
let df = data.borrow();
context_clone.clear_rect(
0.0,
0.0,
config_clone.width as f64,
config_clone.height as f64,
);
context_clone.set_fill_style_str("rgba(0,0,0,0.7)");
context_clone.fill_rect(x + 10.0, y - 20.0, 100.0, 40.0);
context_clone.set_font("12px sans-serif");
context_clone.set_fill_style_str("white");
context_clone
.fill_text("Tooltip", x + 15.0, y)
.expect("operation should succeed");
}
}) as Box<dyn FnMut(_)>);
self.canvas
.add_event_listener_with_callback(
"mousemove",
mousemove_callback.as_ref().unchecked_ref(),
)
.map_err(|_| Error::InvalidInput("Failed to add event listener".to_string()))?;
self.event_listeners
.push(("mousemove".to_string(), mousemove_callback));
Ok(())
}
#[wasm_bindgen]
pub fn export_image(&self) -> WebResult<String> {
self.canvas
.to_data_url()
.map_err(|_| to_js_error("Failed to export image"))
}
#[wasm_bindgen]
pub fn update_title(&mut self, title: &str) -> WebResult<()> {
self.config.title = title.to_string();
self.render()
}
#[wasm_bindgen]
pub fn change_type(&mut self, viz_type: VisualizationType) -> WebResult<()> {
self.config.viz_type = viz_type;
self.render()
}
#[wasm_bindgen]
pub fn update_theme(&mut self, theme: ColorTheme) -> WebResult<()> {
self.config.theme = theme;
self.render()
}
}
mod plotters_ext_web {
use super::*;
use crate::vis::plotters_ext::PlotSettings;
use plotters::prelude::*;
pub fn plot_multi_series_for_web(
df: &DataFrame,
columns: &[&str],
drawing_area: DrawingArea<plotters_canvas::CanvasBackend, plotters::coord::Shift>,
settings: &PlotSettings,
) -> Result<()> {
Ok(())
}
pub fn plot_column_for_web(
df: &DataFrame,
column: &str,
drawing_area: DrawingArea<plotters_canvas::CanvasBackend, plotters::coord::Shift>,
settings: &PlotSettings,
) -> Result<()> {
Ok(())
}
pub fn plot_scatter_for_web(
df: &DataFrame,
x_column: &str,
y_column: &str,
drawing_area: DrawingArea<plotters_canvas::CanvasBackend, plotters::coord::Shift>,
settings: &PlotSettings,
) -> Result<()> {
Ok(())
}
pub fn plot_histogram_for_web(
df: &DataFrame,
column: &str,
bins: usize,
drawing_area: DrawingArea<plotters_canvas::CanvasBackend, plotters::coord::Shift>,
settings: &PlotSettings,
) -> Result<()> {
Ok(())
}
pub fn plot_boxplot_for_web(
df: &DataFrame,
category_column: &str,
value_column: &str,
drawing_area: DrawingArea<plotters_canvas::CanvasBackend, plotters::coord::Shift>,
settings: &PlotSettings,
) -> Result<()> {
Ok(())
}
}