use super::{ApplyUseSeries, GetYValue, IntoUseBar, SeriesAcc, UseY};
use crate::{state::State, Colour, Tick};
use leptos::prelude::*;
use std::sync::Arc;
pub const BAR_GAP: f64 = 0.1;
pub const BAR_GAP_INNER: f64 = 0.05;
#[non_exhaustive]
pub struct Bar<T, Y> {
get_y: Arc<dyn GetYValue<T, Y>>,
pub name: RwSignal<String>,
pub colour: RwSignal<Option<Colour>>,
pub placement: RwSignal<BarPlacement>,
pub gap: RwSignal<f64>,
pub group_gap: RwSignal<f64>,
}
#[derive(Copy, Clone, Debug, Default, PartialEq)]
#[non_exhaustive]
pub enum BarPlacement {
#[default]
Zero,
Edge,
}
#[derive(Clone, Debug, PartialEq)]
pub struct UseBar {
group_id: usize,
colour: Signal<Colour>,
placement: RwSignal<BarPlacement>,
gap: RwSignal<f64>,
group_gap: RwSignal<f64>,
}
impl<T, Y> Bar<T, Y> {
pub fn new(get_y: impl Fn(&T) -> Y + Send + Sync + 'static) -> Self
where
Y: Tick,
{
Self {
get_y: Arc::new(get_y),
name: RwSignal::default(),
colour: RwSignal::default(),
placement: RwSignal::default(),
gap: RwSignal::new(BAR_GAP),
group_gap: RwSignal::new(BAR_GAP_INNER),
}
}
pub fn with_name(self, name: impl Into<String>) -> Self {
self.name.set(name.into());
self
}
pub fn with_colour(self, colour: impl Into<Option<Colour>>) -> Self {
self.colour.set(colour.into());
self
}
pub fn with_placement(self, placement: impl Into<BarPlacement>) -> Self {
self.placement.set(placement.into());
self
}
pub fn with_gap(self, gap: f64) -> Self {
self.gap.set(gap);
self
}
pub fn with_group_gap(self, group_gap: f64) -> Self {
self.group_gap.set(group_gap);
self
}
}
impl<T, Y> Clone for Bar<T, Y> {
fn clone(&self) -> Self {
Self {
get_y: self.get_y.clone(),
placement: self.placement,
gap: self.gap,
group_gap: self.group_gap,
name: self.name,
colour: self.colour,
}
}
}
impl<T, Y: Tick, F: Fn(&T) -> Y + Send + Sync + 'static> From<F> for Bar<T, Y> {
fn from(f: F) -> Self {
Self::new(f)
}
}
impl<T, Y> ApplyUseSeries<T, Y> for Bar<T, Y> {
fn apply_use_series(self: Arc<Self>, series: &mut SeriesAcc<T, Y>) {
let colour = series.next_colour();
_ = series.push_bar(colour, (*self).clone());
}
}
impl<T, Y> IntoUseBar<T, Y> for Bar<T, Y> {
fn into_use_bar(
self,
id: usize,
group_id: usize,
colour: Memo<Colour>,
) -> (UseY, Arc<dyn GetYValue<T, Y>>) {
let override_colour = self.colour;
let colour = Signal::derive(move || override_colour.get().unwrap_or(colour.get()));
let bar = UseY::new_bar(
id,
self.name,
UseBar {
group_id,
colour,
placement: self.placement,
gap: self.gap,
group_gap: self.group_gap,
},
);
(bar, self.get_y.clone())
}
}
#[component]
pub fn RenderBar<X: Tick, Y: Tick>(
bar: UseBar,
state: State<X, Y>,
positions: Signal<Vec<(f64, f64)>>,
) -> impl IntoView {
let bars = Memo::new(move |_| {
state
.pre
.data
.series
.get()
.iter()
.filter_map(|series| series.bar().map(|bar| (series.clone(), bar.clone())))
.collect::<Vec<_>>()
.len()
});
let rects = move || {
positions.with(|positions| {
let bottom_y = match bar.placement.get() {
BarPlacement::Zero => state.svg_zero.get().1,
BarPlacement::Edge => state.layout.inner.get().bottom_y(),
};
let gap = bar.gap.get().clamp(0.0, 1.0);
let width = state.layout.x_width.get() * (1.0 - gap);
let group_gap = bar.group_gap.get().clamp(0.0, 1.0);
let group_width = width / bars.get() as f64;
let group_width_inner = group_width * (1.0 - group_gap);
let group_gap = group_width * group_gap;
let offset = group_gap / 2.0 - width / 2.0;
positions
.iter()
.map(|&(x, y)| {
let height = bottom_y - y;
let (y, height) = if height.is_sign_positive() {
(y, height)
} else {
(bottom_y, -height) };
view! {
<rect
x=x + group_width * bar.group_id as f64 + offset
y=y
width=group_width_inner
height=height />
}
.into_any()
})
.collect_view()
.into_any()
})
};
view! {
<g
class="_chartistry_bar"
fill=move || bar.colour.get().to_string()>
{rects}
</g>
}
}