use dioxus::prelude::*;
use crate::grid::{Axis, Grid};
use crate::types::*;
#[allow(clippy::struct_excessive_bools)]
#[derive(PartialEq, Props)]
pub struct LineChartProps<'a> {
series: Series,
#[props(optional)]
labels: Option<Labels>,
#[props(optional)]
series_labels: Option<Labels>,
#[props(default = "100%")]
width: &'a str,
#[props(default = "100%")]
height: &'a str,
#[props(default = 600)]
viewbox_width: i32,
#[props(default = 400)]
viewbox_height: i32,
#[props(default)]
padding_top: i32,
#[props(default)]
padding_bottom: i32,
#[props(default)]
padding_left: i32,
#[props(default)]
padding_right: i32,
#[props(default = true)]
show_grid: bool,
#[props(default = true)]
show_dotted_grid: bool,
#[props(default = false)]
show_grid_ticks: bool,
#[props(default = true)]
show_labels: bool,
#[props(default = true)]
show_dots: bool,
#[props(default = true)]
show_lines: bool,
#[props(default = true)]
show_line_labels: bool,
#[props(default = "1%")]
line_width: &'a str,
#[props(default = "3%")]
dot_size: &'a str,
#[props(optional)]
label_interpolation: Option<fn(f32) -> String>,
#[props(optional)]
lowest: Option<f32>,
#[props(optional)]
highest: Option<f32>,
#[props(default = 8)]
max_ticks: i32,
#[props(default = "dx-chart-line")]
class_chart_line: &'a str,
#[props(default = "dx-line")]
class_line: &'a str,
#[props(default = "dx-line-path")]
class_line_path: &'a str,
#[props(default = "dx-line-dot")]
class_line_dot: &'a str,
#[props(default = "dx-line-label")]
class_line_label: &'a str,
#[props(default = "dx-grid")]
class_grid: &'a str,
#[props(default = "dx-grid-line")]
class_grid_line: &'a str,
#[props(default = "dx-grid-label")]
class_grid_label: &'a str,
#[props(default = "dx-grid-labels")]
class_grid_labels: &'a str,
}
#[allow(non_snake_case)]
pub fn LineChart<'a>(cx: Scope<'a, LineChartProps<'a>>) -> Element {
for series in cx.props.series.iter() {
if series.is_empty() {
return cx.render(rsx!("Pie chart error: empty series"));
}
}
let view = Rect::new(
cx.props.padding_left as f32,
cx.props.padding_top as f32,
(cx.props.viewbox_width - cx.props.padding_right) as f32,
(cx.props.viewbox_height - cx.props.padding_bottom) as f32,
);
let max_ticks = cx.props.max_ticks.max(3);
let axis_x = Axis::builder()
.with_view(view)
.with_grid_ticks(cx.props.show_grid_ticks)
.with_labels(cx.props.labels.as_ref());
let axis_y = Axis::builder()
.with_view(view)
.with_max_ticks(max_ticks)
.with_grid_ticks(cx.props.show_grid_ticks)
.with_series(&cx.props.series)
.with_label_interpolation(cx.props.label_interpolation)
.with_highest(cx.props.highest)
.with_lowest(cx.props.lowest);
let grid = Grid::new(axis_x, axis_y);
let lines = grid.lines();
let generated_labels = grid.y.generated_labels();
let grid_labels = if cx.props.show_labels {
if let Some(labels) = cx.props.labels.as_ref() {
Some(
grid.text_data(Some(labels.len()), Some(generated_labels.len()))
.into_iter()
.zip(labels.iter().chain(generated_labels.iter()))
.collect::<Vec<(TextData, &String)>>(),
)
} else {
Some(
grid.y
.text_data(generated_labels.len())
.into_iter()
.zip(generated_labels.iter())
.collect::<Vec<(TextData, &String)>>(),
)
}
} else {
None
};
let mut color_var = 255.0;
let dotted_stroke = if cx.props.show_dotted_grid {
&"2px"
} else {
&"0px"
};
cx.render(rsx! {
div {
svg {
xmlns: "http://www.w3.org/2000/svg",
width: "{cx.props.width}",
height: "{cx.props.height}",
class: "{cx.props.class_chart_line}",
preserveAspectRatio: "xMidYMid meet",
view_box: "0 0 {cx.props.viewbox_width} {cx.props.viewbox_height}",
cx.props.show_grid.then(|| rsx! {
g {
class: "{cx.props.class_grid}",
lines.iter().map(|line| {
rsx! {
line {
key: "{line}",
x1: "{line.min.x}",
y1: "{line.min.y}",
x2: "{line.max.x}",
y2: "{line.max.y}",
class: "{cx.props.class_grid_line}",
stroke: "rgba(20, 20, 20, 0.8)",
stroke_dasharray: "{dotted_stroke}",
}
}
}),
}
}),
grid_labels.map(|labels| rsx! {
g {
class: "{cx.props.class_grid_labels}",
labels.iter().map(|(text, label)| rsx! {
text {
key: "{label}",
dx: "{text.x}",
dy: "{text.y}",
text_anchor: "{text.anchor}",
class: "{cx.props.class_grid_label}",
alignment_baseline: "{text.baseline}",
[label.as_str()]
}
})
}
}),
cx.props.series
.iter()
.enumerate()
.zip(cx.props.series_labels
.as_ref()
.unwrap_or(&vec!())
.iter()
.chain(std::iter::repeat(&"".to_owned())))
.map(|((i, a), label)| {
let mut commands = Vec::<String>::with_capacity(a.len());
let mut dots = Vec::<Rect>::with_capacity(a.len());
let mut text_point: Option<Point> = None;
color_var -= 75.0 * (1.0 / (i + 1) as f32);
for (index, v) in a.iter().enumerate() {
let point = grid.world_to_view(index as f32, *v, false);
if index == 0 {
commands.push(format!("M{},{}", point.x, point.y));
} else {
commands.push(format!("L{},{}", point.x, point.y));
}
if cx.props.show_dots {
dots.push(Rect::new(point.x, point.y, point.x + 0.1, point.y));
}
if !label.is_empty() && index == (a.len() - 1) {
text_point = Some(point);
}
}
let commands = commands.join(" ");
rsx! {
g {
key: "{label}",
class: "{cx.props.class_line}-{i}",
path {
key: "{cx.props.class_line_path}",
d: "{commands}",
class: "{cx.props.class_line_path}",
stroke: "rgb({color_var}, 40, 40)",
stroke_width: "{cx.props.line_width}",
stroke_linecap: "round",
fill: "transparent",
},
dots.iter().map(|d| {
rsx! {
line {
key: "{i}",
x1: "{d.min.x}",
y1: "{d.min.y}",
x2: "{d.max.x}",
y2: "{d.max.y}",
class: "{cx.props.class_line_dot}",
stroke: "rgb({color_var}, 40, 40)",
stroke_width: "{cx.props.dot_size}",
stroke_linecap: "round",
}
}
}),
text_point.map(|point| {
rsx! {cx,
text {
dx: format_args!("{}", point.x + 10.0),
dy: "{point.y}",
text_anchor: "start",
color: "rgb({color_var}, 40, 40)",
class: "{cx.props.class_line_label}",
[label.as_str()]
}
}
}),
}
}
}),
}
}
})
}