use leptos::callback::Callback;
use leptos::prelude::*;
use crate::context::{
set_hovered_item, use_chart_context, use_highlighted_item, ChartInteractionContext,
};
use crate::{
BarLabelConfig, ChartItemId, ChartOrientation, FadeMode, HighlightMode, HighlightScope,
};
#[component]
pub fn BarMark(
x: f64,
y: f64,
width: f64,
height: f64,
#[prop(into)]
fill: String,
#[prop(default = 0.0)]
corner_radius: f64,
#[prop(default = ChartOrientation::Vertical)]
orientation: ChartOrientation,
#[prop(into)]
series_id: String,
data_index: usize,
value: f64,
#[prop(default = None)]
bar_label: Option<BarLabelConfig>,
#[prop(default = None)]
highlight_scope: Option<HighlightScope>,
#[prop(default = None)]
on_item_click: Option<Callback<(ChartItemId,), ()>>,
) -> impl IntoView {
let label_text = bar_label_text(value, data_index, bar_label.as_ref());
let show_label = label_text.is_some();
let label = label_text.unwrap_or_default();
let origin_class = match orientation {
ChartOrientation::Vertical => "orb-bar-mark--vertical",
ChartOrientation::Horizontal => "orb-bar-mark--horizontal",
};
let label_x = x + width / 2.0;
let label_y = if orientation == ChartOrientation::Vertical {
y - 4.0
} else {
y + height / 2.0
};
let highlighted = use_highlighted_item();
let item_id = ChartItemId {
series_id: series_id.clone(),
data_index,
};
let scope = highlight_scope.unwrap_or(HighlightScope {
highlight: HighlightMode::Item,
fade: FadeMode::Global,
});
let item_id_for_class = item_id.clone();
let mark_class = move || {
let mut classes = vec!["orb-bar-mark".to_string(), origin_class.to_string()];
let active = highlighted.get();
let is_active = active.as_ref() == Some(&item_id_for_class);
if scope.highlight != HighlightMode::None && active.is_some() {
if is_active {
classes.push("orb-bar-mark-highlighted".into());
} else if scope.fade == FadeMode::Global {
classes.push("orb-bar-mark-faded".into());
}
}
classes.join(" ")
};
let item_for_hover = item_id.clone();
let series_for_click = series_id.clone();
let plot_left = use_chart_context().drawing_area.left;
let plot_top = use_chart_context().drawing_area.top;
let pointer_x = x + width / 2.0;
let pointer_y = y + height / 2.0;
let interaction = expect_context::<ChartInteractionContext>();
view! {
<g class="orb-bar-mark-group" style="pointer-events: all;">
<rect
class=mark_class
x=x
y=y
width=width
height=height
rx=corner_radius
ry=corner_radius
fill=fill
on:mouseenter=move |_| {
interaction
.pointer_plot
.set(Some((plot_left + pointer_x, plot_top + pointer_y)));
set_hovered_item(Some(item_for_hover.clone()));
}
on:mouseleave=move |_| {
interaction.pointer_plot.set(None);
set_hovered_item(None);
}
on:click=move |ev| {
ev.stop_propagation();
if let Some(cb) = &on_item_click {
cb.run((ChartItemId {
series_id: series_for_click.clone(),
data_index,
},));
}
}
/>
{show_label.then(|| view! {
<text
class="orb-bar-label"
x=label_x
y=label_y
text-anchor="middle"
dominant-baseline="auto"
>
{label}
</text>
})}
</g>
}
}
fn bar_label_text(
value: f64,
data_index: usize,
config: Option<&BarLabelConfig>,
) -> Option<String> {
let config = config?;
if config.show == Some(false) {
return None;
}
if let Some(fmt) = &config.formatter {
return Some(fmt.run((value, data_index)));
}
Some(format!("{value:.0}"))
}