1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
/// Tooltip component for Sankey nodes and links
use leptos::prelude::*;
/// Floating tooltip for Sankey diagram hover events.
#[component]
pub fn SankeyTooltip(
/// Hovered label (node label or "A → B" for links). None = hidden.
label: Signal<Option<String>>,
/// Hovered value. None = hidden.
value: Signal<Option<f64>>,
/// Mouse X position in chart-inner coordinates
x: Signal<f64>,
/// Mouse Y position in chart-inner coordinates
y: Signal<f64>,
/// Inner width of the chart area (for edge-flipping)
inner_width: Signal<f64>,
/// Inner height of the chart area (for edge-flipping)
inner_height: Signal<f64>,
/// Text color
#[prop(into, optional)]
tooltip_text: Option<Signal<String>>,
/// Tooltip background color
#[prop(into, optional)]
tooltip_bg: Option<Signal<String>>,
) -> impl IntoView {
let tooltip_bg = move || {
tooltip_bg
.map(|s| s.get())
.unwrap_or_else(|| "rgba(0,0,0,0.85)".to_string())
};
let tooltip_text = move || {
tooltip_text
.map(|s| s.get())
.unwrap_or_else(|| "#ffffff".to_string())
};
view! {
{move || {
let lbl = label.get()?;
let val = value.get()?;
let mx = x.get();
let my = y.get();
let w = inner_width.get();
let h = inner_height.get();
let tc = tooltip_text();
let val_str = format!("{val:.1}");
let max_len = lbl.len().max(val_str.len() + 8);
let box_w = 12.0 + max_len as f64 * 6.5;
let box_h = 38.0;
let bx = if mx + box_w + 12.0 > w { mx - box_w - 12.0 } else { mx + 12.0 };
let by = if my + box_h + 8.0 > h { my - box_h - 8.0 } else { my + 8.0 };
Some(
view! {
<g class="sankey-tooltip" style="pointer-events: none;">
<rect
x=format!("{bx:.2}")
y=format!("{by:.2}")
width=format!("{box_w:.2}")
height=box_h
rx="4"
fill=tooltip_bg()
/>
<text
x=format!("{:.2}", bx + 8.0)
y=format!("{:.2}", by + 15.0)
font-size="11"
fill=tc.clone()
font-family="'JetBrains Mono', monospace"
font-weight="bold"
>
{lbl.clone()}
</text>
<text
x=format!("{:.2}", bx + 8.0)
y=format!("{:.2}", by + 30.0)
font-size="11"
fill=tc.clone()
font-family="'JetBrains Mono', monospace"
>
{format!("Value: {}", val_str)}
</text>
</g>
},
)
}}
}
}