orbital_charts/shared/layers/
reference_line.rs1use leptos::prelude::*;
4
5use crate::context::{use_drawing_area, use_x_scale, use_y_scale};
6use crate::{ChartScale, ReferenceLineLabelAlign, ReferenceLineStyle};
7
8#[component]
10pub fn ReferenceLine(
11 #[prop(optional)]
13 x: Option<f64>,
14 #[prop(optional)]
16 y: Option<f64>,
17 #[prop(optional, into)]
19 axis_id: Option<String>,
20 #[prop(optional, into)]
22 label: Option<String>,
23 #[prop(default = ReferenceLineLabelAlign::Middle)]
25 label_align: ReferenceLineLabelAlign,
26 #[prop(optional)]
28 line_style: Option<ReferenceLineStyle>,
29) -> impl IntoView {
30 let area = use_drawing_area();
31 let x_scale = use_x_scale(axis_id.clone().unwrap_or_else(|| "x".into()));
32 let y_scale = use_y_scale(axis_id.clone().unwrap_or_else(|| "y".into()));
33 let style = line_style.unwrap_or_default();
34
35 let stroke = style
36 .color
37 .unwrap_or_else(|| "var(--orb-color-accent-primary, currentColor)".into());
38 let stroke_width = style.stroke_width.unwrap_or(1.5);
39 let dash = style.dash_array.unwrap_or_else(|| "6 4".into());
40 let opacity = style.opacity.unwrap_or(0.7);
41
42 view! {
43 <g class="orb-reference-line" aria-hidden="true">
44 {x.map(|xv| {
45 let px = scale_x(&x_scale, xv);
46 view! {
47 <line
48 class="orb-reference-line__line orb-reference-line__line--vertical"
49 x1=px
50 y1=0.0
51 x2=px
52 y2=area.plot_height
53 stroke=stroke.clone()
54 stroke-width=stroke_width
55 stroke-dasharray=dash.clone()
56 opacity=opacity
57 />
58 }
59 })}
60 {y.map(|yv| {
61 let py = scale_y(&y_scale, yv);
62 view! {
63 <line
64 class="orb-reference-line__line orb-reference-line__line--horizontal"
65 x1=0.0
66 y1=py
67 x2=area.plot_width
68 y2=py
69 stroke=stroke.clone()
70 stroke-width=stroke_width
71 stroke-dasharray=dash.clone()
72 opacity=opacity
73 />
74 }
75 })}
76 {label.map(|text| {
77 let (lx, ly, anchor) = label_position(
78 x,
79 y,
80 &x_scale,
81 &y_scale,
82 &area,
83 label_align,
84 );
85 view! {
86 <text
87 class="orb-reference-line__label"
88 x=lx
89 y=ly
90 text-anchor=anchor
91 dominant-baseline="middle"
92 fill=stroke.clone()
93 font-size="12"
94 opacity=opacity
95 >
96 <title>{text.clone()}</title>
97 {text}
98 </text>
99 }
100 })}
101 </g>
102 }
103}
104
105fn scale_x(scale: &ChartScale, value: f64) -> f64 {
106 match scale {
107 ChartScale::Band(b) => b.scale(&value.to_string()).unwrap_or(0.0),
108 ChartScale::Linear(l) => l.scale(value),
109 }
110}
111
112fn scale_y(scale: &ChartScale, value: f64) -> f64 {
113 match scale {
114 ChartScale::Linear(l) => l.scale(value),
115 ChartScale::Band(b) => b.scale(&value.to_string()).unwrap_or(0.0),
116 }
117}
118
119fn label_position(
120 x: Option<f64>,
121 y: Option<f64>,
122 x_scale: &ChartScale,
123 y_scale: &ChartScale,
124 area: &crate::DrawingArea,
125 align: ReferenceLineLabelAlign,
126) -> (f64, f64, &'static str) {
127 let anchor = match align {
128 ReferenceLineLabelAlign::Start => "start",
129 ReferenceLineLabelAlign::Middle => "middle",
130 ReferenceLineLabelAlign::End => "end",
131 };
132
133 if let Some(yv) = y {
134 let py = scale_y(y_scale, yv);
135 let lx = match align {
136 ReferenceLineLabelAlign::Start => 4.0,
137 ReferenceLineLabelAlign::Middle => area.plot_width / 2.0,
138 ReferenceLineLabelAlign::End => area.plot_width - 4.0,
139 };
140 return (lx, py - 6.0, anchor);
141 }
142
143 if let Some(xv) = x {
144 let px = scale_x(x_scale, xv);
145 let ly = match align {
146 ReferenceLineLabelAlign::Start => 12.0,
147 ReferenceLineLabelAlign::Middle => area.plot_height / 2.0,
148 ReferenceLineLabelAlign::End => area.plot_height - 4.0,
149 };
150 return (px + 4.0, ly, anchor);
151 }
152
153 (0.0, 0.0, anchor)
154}