leptos_chartistry/layout/
rotated_label.rs1use super::{UseLayout, UseVerticalLayout};
2use crate::{
3 bounds::Bounds,
4 debug::DebugRect,
5 edge::Edge,
6 state::{PreState, State},
7 Tick,
8};
9use leptos::prelude::*;
10use std::str::FromStr;
11
12#[derive(Copy, Clone, Debug, PartialEq)]
14#[non_exhaustive]
15pub enum Anchor {
16 Start,
18 Middle,
20 End,
22}
23
24#[derive(Clone, Debug, PartialEq)]
28#[non_exhaustive]
29pub struct RotatedLabel {
30 pub text: RwSignal<String>,
32 pub anchor: RwSignal<Anchor>,
34}
35
36impl RotatedLabel {
37 fn new(anchor: Anchor, text: String) -> Self {
38 Self {
39 text: RwSignal::new(text),
40 anchor: RwSignal::new(anchor),
41 }
42 }
43
44 pub fn start(text: impl Into<String>) -> Self {
46 Self::new(Anchor::Start, text.into())
47 }
48 pub fn middle(text: impl Into<String>) -> Self {
50 Self::new(Anchor::Middle, text.into())
51 }
52 pub fn end(text: impl Into<String>) -> Self {
54 Self::new(Anchor::End, text.into())
55 }
56
57 fn size<X: Tick, Y: Tick>(&self, state: &PreState<X, Y>) -> Signal<f64> {
58 let text = self.text;
59 let font_height = state.font_height;
60 let padding = state.padding;
61 Signal::derive(move || {
62 if text.with(|t| t.is_empty()) {
63 0.0
64 } else {
65 font_height.get() + padding.get().height()
66 }
67 })
68 }
69
70 pub(super) fn fixed_height<X: Tick, Y: Tick>(&self, state: &PreState<X, Y>) -> Signal<f64> {
71 self.size(state)
72 }
73
74 pub(super) fn to_horizontal_use(&self) -> UseLayout {
75 UseLayout::RotatedLabel(self.clone())
76 }
77
78 pub(super) fn to_vertical_use<X: Tick, Y: Tick>(
79 &self,
80 state: &PreState<X, Y>,
81 ) -> UseVerticalLayout {
82 UseVerticalLayout {
84 width: self.size(state),
85 layout: UseLayout::RotatedLabel(self.clone()),
86 }
87 }
88}
89
90impl Anchor {
91 fn to_svg_attr(self) -> String {
92 self.to_string()
93 }
94
95 fn map_points(&self, left: f64, middle: f64, right: f64) -> f64 {
96 match self {
97 Anchor::Start => left,
98 Anchor::Middle => middle,
99 Anchor::End => right,
100 }
101 }
102
103 pub(crate) fn css_justify_content(&self) -> &'static str {
104 match self {
105 Anchor::Start => "flex-start",
106 Anchor::Middle => "center",
107 Anchor::End => "flex-end",
108 }
109 }
110}
111
112impl FromStr for Anchor {
113 type Err = String;
114
115 fn from_str(s: &str) -> Result<Self, Self::Err> {
116 match s.to_lowercase().as_str() {
117 "start" => Ok(Anchor::Start),
118 "middle" => Ok(Anchor::Middle),
119 "end" => Ok(Anchor::End),
120 _ => Err(format!("unknown anchor: `{}`", s)),
121 }
122 }
123}
124
125impl std::fmt::Display for Anchor {
126 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
127 match self {
128 Anchor::Start => write!(f, "start"),
129 Anchor::Middle => write!(f, "middle"),
130 Anchor::End => write!(f, "end"),
131 }
132 }
133}
134
135#[component]
136pub(super) fn RotatedLabel<X: Tick, Y: Tick>(
137 label: RotatedLabel,
138 edge: Edge,
139 bounds: Memo<Bounds>,
140 state: State<X, Y>,
141) -> impl IntoView {
142 let RotatedLabel { text, anchor } = label;
143 let debug = state.pre.debug;
144 let font_height = state.pre.font_height;
145 let padding = state.pre.padding;
146
147 let content = Signal::derive(move || padding.get().apply(bounds.get()));
148 let position = Memo::new(move |_| {
149 let c = content.get();
150 let (top, right, bottom, left) = (c.top_y(), c.right_x(), c.bottom_y(), c.left_x());
151 let (centre_x, centre_y) = (c.centre_x(), c.centre_y());
152
153 let anchor = anchor.get();
154 match edge {
155 Edge::Top | Edge::Bottom => (0, anchor.map_points(left, centre_x, right), centre_y),
156 Edge::Left => (270, centre_x, anchor.map_points(bottom, centre_y, top)),
157 Edge::Right => (90, centre_x, anchor.map_points(top, centre_y, bottom)),
159 }
160 });
161
162 view! {
163 <g
164 class="_chartistry_rotated_label"
165 font-family="monospace">
166 <DebugRect label="RotatedLabel" debug=debug bounds=vec![bounds.into(), content] />
167 <text
168 x=move || position.with(|(_, x, _)| x.to_string())
169 y=move || position.with(|(_, _, y)| y.to_string())
170 transform=move || position.with(|(rotate, x, y)| format!("rotate({rotate}, {x}, {y})"))
171 dominant-baseline="middle"
172 text-anchor=move || anchor.get().to_svg_attr()
173 font-size=move || format!("{}px", font_height.get())>
174 {text}
175 </text>
176 </g>
177 }
178}