Skip to content

Commit

Permalink
beta version
Browse files Browse the repository at this point in the history
  • Loading branch information
elonaire committed Jun 28, 2024
1 parent 84353ff commit 7eb5e81
Show file tree
Hide file tree
Showing 7 changed files with 391 additions and 216 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "visualize-yew"
version = "0.20.0-alpha.4"
version = "0.20.0-beta.1"
edition = "2021"
description = "A simple data visualization library for Yew"
license = "MIT OR Apache-2.0"
Expand Down
40 changes: 25 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,30 @@

This is a simple crate to help you visualize your data in the browser using Yew. It is a wrapper around the yew crate that provides a simple API to create charts.

This crate is still in development and is not yet ready for production use. The API is subject to change.
**Note**: This crate is **NOW** available for use, all charts are customizable to your liking.

**New/Upcoming Features:**
- [x] Customizable colors for all charts
- [x] Customizable labels for all charts
- [x] Customizable legend for all charts
- [x] Customizable stroke width for line chart
- [ ] Customizable tooltip for all charts

This crate is built using the [Yew](https://yew.rs/docs/0.20/getting-started/introduction) framework and uses HTML5 canvas to render the charts.

## Features
- [x] PieChart

<img src="https://imagedelivery.net/fa3SWf5GIAHiTnHQyqU8IQ/4401284a-6498-4e19-d2c8-865dc95e9f00/public" width="200">
<img src="https://imagedelivery.net/fa3SWf5GIAHiTnHQyqU8IQ/2fd337b3-4c77-45b8-8195-0ac85496e700/public" width="200">
- [x] LineChart

<img src="https://imagedelivery.net/fa3SWf5GIAHiTnHQyqU8IQ/d33bc074-207d-417d-18b6-af93594d0700/public" width="200">
<img src="https://imagedelivery.net/fa3SWf5GIAHiTnHQyqU8IQ/509533f0-8ab1-4333-2b22-408c2b8d1e00/public" width="200">
- [x] BarChart

<img src="https://imagedelivery.net/fa3SWf5GIAHiTnHQyqU8IQ/194517a9-7a9b-4248-3acf-436dcb3fc700/public" width="200">
<img src="https://imagedelivery.net/fa3SWf5GIAHiTnHQyqU8IQ/ff0d07c0-681e-43a8-349c-571c1d389b00/public" width="200">
- [x] DoughnutChart

<img src="https://imagedelivery.net/fa3SWf5GIAHiTnHQyqU8IQ/6c66a389-9f66-4cc3-a8d3-fbfe9e9e9400/public" width="200">
<img src="https://imagedelivery.net/fa3SWf5GIAHiTnHQyqU8IQ/f30b8b58-668c-45ce-3923-5e9840abe400/public" width="200">

## Usage
Add the following to your `Cargo.toml`:
Expand All @@ -34,33 +41,36 @@ use visualize_yew::pie_chart::{DataPoint as PieChartData, PieChart};

#[function_component]
fn Home() -> Html {
let pie_chart_data: Vec<PieChartData> = vec![
PieChartData {
let mut pie_chart_config = PieChartConfig::default();
pie_chart_config.show_legend = true;

let pie_data = vec![
PieDataPoint {
name: "A".to_string(),
value: 10,
color: "#F47489".to_string(),
},
PieChartData {
PieDataPoint {
name: "B".to_string(),
value: 20,
color: "#43bc7e".to_string(),
},
PieChartData {
PieDataPoint {
name: "C".to_string(),
value: 30,
color: "#1ECBE1".to_string(),
},
PieChartData {
PieDataPoint {
name: "D".to_string(),
value: 40,
},
PieChartData {
name: "E".to_string(),
value: 50,
color: "#8900ef".to_string(),
},
];

html! {
// Chart will take the full width of the parent container
<div>
<PieChart data={pie_chart_data} />
<PieChart data={pie_chart_data} config={pie_chart_config} />
</div>
}
}
Expand Down
45 changes: 15 additions & 30 deletions src/charts/bar_chart/bar_chart.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,12 @@ use gloo::events::EventListener;

#[derive(Clone, Debug, PartialEq, Eq, Properties, Default)]
pub struct BarChartConfig {
#[prop_or(Some("#1ECBE1".to_string()))]
pub bar_color: Option<String>,
pub grid_color: Option<String>,
pub axis_color: Option<String>,
pub axis_label_color: Option<String>,
pub axis_label_font: Option<String>,
pub axis_label_font_size: Option<i32>,
pub axis_label_font_weight: Option<String>,
pub axis_label_padding: Option<i32>,
pub axis_label_rotation: Option<i32>,
pub axis_label_x_offset: Option<i32>,
pub axis_label_y_offset: Option<i32>,
pub axis_tick_length: Option<i32>,
pub axis_tick_width: Option<i32>,
pub axis_width: Option<i32>,
pub bar_spacing: Option<i32>,
pub grid_line_width: Option<i32>,
pub grid_num_lines: Option<usize>,
pub margin_bottom: Option<i32>,
pub margin_left: Option<i32>,
pub margin_right: Option<i32>,
pub margin_top: Option<i32>,
pub x_axis_label: Option<String>,
pub y_axis_label: Option<String>,
#[prop_or_default]
pub bar_color: String,
#[prop_or_default]
pub grid_color: String,
#[prop_or_default]
pub axis_color: String,
}

#[derive(Clone, Debug, PartialEq, Eq, Properties)]
Expand All @@ -40,7 +22,7 @@ pub struct DataPoint {
pub struct BarChartProps {
pub data: Vec<DataPoint>,
#[prop_or(Default::default())]
pub config: Option<BarChartConfig>,
pub config: BarChartConfig,
}


Expand Down Expand Up @@ -94,7 +76,10 @@ pub fn BarChart(props: &BarChartProps) -> Html {
}

html! {
<canvas ref={canvas_ref} style="width: 100%; height: 100%;"></canvas>
// <div style="width: 100%; height: 100%;">

// </div>
<canvas ref={canvas_ref} style="width: 90%; height: 90%;"></canvas>
}
}

Expand All @@ -103,7 +88,7 @@ fn draw_bar_chart(context: &CanvasRenderingContext2d, width: f64, height: f64, p
let num_bars = (data.len() + 2) as f64; // Add 2 to account for spacing on the farthest right
let total_spacing = width * 0.1; // Reserve 10% of the width for spacing between bars
let total_bar_width = width - total_spacing;
let bar_width = total_bar_width / num_bars;
let bar_width = total_bar_width / (num_bars * 3.0); // Shrink thrice the size of the bar width
let bar_spacing = total_spacing / (num_bars - 1.0);
let axis_padding = 50.0;

Expand Down Expand Up @@ -136,8 +121,8 @@ fn draw_bar_chart(context: &CanvasRenderingContext2d, width: f64, height: f64, p
}

// Draw the bars
let bar_color = props.config.as_ref().and_then(|config| config.bar_color.as_deref()).unwrap_or("#1ECBE1");
context.set_fill_style(&JsValue::from_str(bar_color));
let bar_color = props.config.bar_color.clone();
context.set_fill_style(&JsValue::from_str(bar_color.as_str()));
for (i, &value) in data.iter().enumerate() {
let x = axis_padding + i as f64 * (bar_width + bar_spacing);
let y = height - axis_padding - value as f64 * ((height - axis_padding * 2.0) / max_value);
Expand Down Expand Up @@ -187,7 +172,7 @@ mod tests {

let props = BarChartProps {
data,
config: None,
config: BarChartConfig::default(),
};

draw_bar_chart(&context, width, height, &props);
Expand Down
69 changes: 50 additions & 19 deletions src/charts/doughnut_chart/doughnut_chart.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,17 @@ use yew::prelude::*;
use gloo::events::EventListener;
use std::f64::consts::PI;

#[derive(Clone, Debug, PartialEq, Eq, Properties)]
#[derive(Clone, Debug, PartialEq, Eq, Properties, Default)]
pub struct DoughnutChartConfigs {

#[prop_or(true)]
pub show_legend: bool,
}

#[derive(Clone, Properties, PartialEq, Debug, Eq)]
pub struct DoughnutChartProps {
pub data: Vec<(String, i32, String)>,
pub config: Option<DoughnutChartConfigs>,
#[prop_or_default]
pub config: DoughnutChartConfigs,
}
#[function_component]
pub fn DoughnutChart(props: &DoughnutChartProps) -> Html {
Expand Down Expand Up @@ -43,7 +45,12 @@ pub fn DoughnutChart(props: &DoughnutChartProps) -> Html {

// Set the canvas dimensions to match its parent's dimensions
canvas.set_width((width * device_pixel_ratio) as u32);
canvas.set_height((height * device_pixel_ratio) as u32);
if height < width {
canvas.set_height((height * device_pixel_ratio) as u32);
} else {
canvas.set_height((width * device_pixel_ratio) as u32);

}

// Scale the context to account for the device pixel ratio
context.scale(device_pixel_ratio, device_pixel_ratio).unwrap();
Expand All @@ -62,15 +69,37 @@ pub fn DoughnutChart(props: &DoughnutChartProps) -> Html {
}, ());
}

let legend_html = if props.config.show_legend {

html! {
<div style="display: flex; flex-direction: row; gap: 5px; margin-bottom: 1em;">
{ for props.data.iter().map(|(label, _value, color)| {
html! {
<div style="display: flex; flex-direction: row; align-items: center; gap: 2px;">
<span style="font-size: 10px;">{ &label }</span>
<div style={format!("background-color: {}; width: 10px; height: 10px; display: inline-block;", &color)}></div>
</div>
}
})}
</div>
}
} else {
html! {}
};

html! {
<canvas ref={canvas_ref} style="width: 100%; height: 100%;"></canvas>
<div>
// legend
{ legend_html }
<canvas ref={canvas_ref} style="width: 100%; height: 100%;"></canvas>
</div>
}
}

fn draw_doughnut_chart(context: &CanvasRenderingContext2d, width: f64, height: f64, props: &DoughnutChartProps) {
let center_x = width / 2.0;
let center_y = height / 2.0;
let radius = (width.min(height) / 3.0).min(150.0);
let radius = (width.min(height) / 2.0).min(150.0);
let inner_radius = radius * 0.5;

// Define the segments of the doughnut chart
Expand Down Expand Up @@ -115,22 +144,22 @@ fn draw_doughnut_chart(context: &CanvasRenderingContext2d, width: f64, height: f
}

// Add labels
start_angle = -PI / 2.0;
context.set_fill_style(&JsValue::from_str("black"));
context.set_text_align("center");
context.set_text_baseline("middle");
// start_angle = -PI / 2.0;
// context.set_fill_style(&JsValue::from_str("black"));
// context.set_text_align("center");
// context.set_text_baseline("middle");

for (label, value, _) in segments {
let sweep_angle = (*value as f64 / total) * 2.0 * PI;
let angle: f64 = start_angle + sweep_angle / 2.0;
// for (label, value, _) in segments {
// let sweep_angle = (*value as f64 / total) * 2.0 * PI;
// let angle: f64 = start_angle + sweep_angle / 2.0;

let x = center_x + (radius + 20.0) * angle.cos();
let y = center_y + (radius + 20.0) * angle.sin();
// let x = center_x + (radius + 20.0) * angle.cos();
// let y = center_y + (radius + 20.0) * angle.sin();

let _ignored_result = context.fill_text(label.as_str(), x, y);
// let _ignored_result = context.fill_text(label.as_str(), x, y);

start_angle += sweep_angle;
}
// start_angle += sweep_angle;
// }
}

#[cfg(test)]
Expand Down Expand Up @@ -160,7 +189,9 @@ mod tests {
("B".to_string(), 20, "#00ff00".to_string()),
("C".to_string(), 30, "#0000ff".to_string()),
],
config: None,
config: DoughnutChartConfigs {
show_legend: true,
}
};

draw_doughnut_chart(&context, 500.0, 500.0, &props);
Expand Down
Loading

0 comments on commit 7eb5e81

Please sign in to comment.