1use crate::point::{IntoPoint, Point};
6use crate::rect::{Edge, Rect};
7use std::fmt;
8
9#[derive(Copy, Clone, Debug, PartialEq)]
11pub enum VerticalOffset {
12 Below,
14 At,
16 Above,
18}
19
20#[derive(Copy, Clone, Debug, PartialEq)]
22pub enum Anchor {
23 Start,
25 Middle,
27 End,
29}
30
31#[derive(Clone, Debug, PartialEq)]
32pub struct Label {
33 offset: VerticalOffset,
34 anchor: Anchor,
35 rounding_precision: Option<usize>,
36}
37
38pub struct Text<'a> {
39 edge: Edge,
40 anchor: Anchor,
41 rect: Option<Rect>,
42 dy: Option<f32>,
43 class_name: Option<&'a str>,
44}
45
46pub struct Tspan<'a> {
47 text: &'a str,
48 x: Option<i32>,
49 y: Option<i32>,
50 dy: Option<f32>,
51}
52
53#[derive(Debug, PartialEq)]
55pub struct Tick {
56 value: f32,
57 text: String,
58}
59
60impl fmt::Display for Anchor {
61 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
62 match self {
63 Anchor::Start => write!(f, " text-anchor='start'"),
64 Anchor::Middle => write!(f, " text-anchor='middle'"),
65 Anchor::End => write!(f, " text-anchor='end'"),
66 }
67 }
68}
69
70impl Default for Label {
71 fn default() -> Self {
72 Label {
73 offset: VerticalOffset::At,
74 anchor: Anchor::Middle,
75 rounding_precision: None,
76 }
77 }
78}
79
80#[allow(dead_code)]
81impl Label {
82 pub fn new() -> Self {
83 Self::default()
84 }
85
86 pub fn vertical_offset(&self) -> f32 {
87 match self.offset {
88 VerticalOffset::Above => -1.0,
89 VerticalOffset::At => 0.0,
90 VerticalOffset::Below => 1.0,
91 }
92 }
93
94 pub fn above(mut self) -> Self {
95 self.offset = VerticalOffset::Above;
96 self
97 }
98
99 pub fn below(mut self) -> Self {
100 self.offset = VerticalOffset::Below;
101 self
102 }
103
104 pub fn start(mut self) -> Self {
105 self.anchor = Anchor::Start;
106 self
107 }
108
109 pub fn end(mut self) -> Self {
110 self.anchor = Anchor::End;
111 self
112 }
113
114 pub fn rounded(&self, value: f32) -> String {
115 match self.rounding_precision {
116 None => value.to_string(),
117 Some(digits) => format!("{:.1$}", value, digits),
118 }
119 }
120
121 pub fn display<P>(
122 &self,
123 f: &mut fmt::Formatter,
124 x: i32,
125 y: i32,
126 pt: P,
127 ) -> fmt::Result
128 where
129 P: IntoPoint,
130 {
131 let pt: Point = pt.into();
132 let lbl = format!("({} {})", pt.x, pt.y);
133 let tspan = Tspan::new(&lbl).x(x).y(y).dy(-0.66);
134 write!(f, "{tspan}")
135 }
136}
137
138impl<'a> Text<'a> {
139 pub fn new(edge: Edge) -> Self {
140 Text {
141 edge,
142 anchor: Anchor::Middle,
143 rect: None,
144 dy: None,
145 class_name: None,
146 }
147 }
148
149 pub fn anchor(mut self, anchor: Anchor) -> Self {
150 self.anchor = anchor;
151 self
152 }
153
154 #[allow(dead_code)]
155 pub fn dy(mut self, dy: f32) -> Self {
156 self.dy = Some(dy);
157 self
158 }
159
160 pub fn rect(mut self, rect: Rect) -> Self {
161 self.rect = Some(rect);
162 self
163 }
164
165 pub fn class_name(mut self, class_name: &'a str) -> Self {
166 self.class_name = Some(class_name);
167 self
168 }
169
170 pub fn display(&self, f: &mut fmt::Formatter) -> fmt::Result {
171 write!(f, "<text")?;
172 if let Some(class_name) = self.class_name {
173 write!(f, " class='{}'", class_name)?;
174 }
175 if let Some(rect) = self.rect {
176 self.transform(f, rect)?;
177 }
178 if let Some(dy) = self.dy {
179 write!(f, " dy='{dy}em'")?;
180 }
181 writeln!(f, "{}>", self.anchor)
182 }
183
184 pub fn display_done(&self, f: &mut fmt::Formatter) -> fmt::Result {
185 writeln!(f, "</text>")
186 }
187
188 fn transform(&self, f: &mut fmt::Formatter, rect: Rect) -> fmt::Result {
189 let x = match (self.edge, self.anchor) {
190 (Edge::Top, Anchor::Start) | (Edge::Bottom, Anchor::Start) => {
191 rect.x
192 }
193 (Edge::Top, Anchor::End) | (Edge::Bottom, Anchor::End) => {
194 rect.right()
195 }
196 _ => rect.x + i32::from(rect.width) / 2,
197 };
198 let y = match (self.edge, self.anchor) {
199 (Edge::Left, Anchor::End) | (Edge::Right, Anchor::Start) => rect.y,
200 (Edge::Left, Anchor::Start) | (Edge::Right, Anchor::End) => {
201 rect.bottom()
202 }
203 _ => rect.y + i32::from(rect.height) / 2,
204 };
205 write!(f, " transform='translate({x} {y})")?;
206 match self.edge {
207 Edge::Left => write!(f, " rotate(-90)")?,
208 Edge::Right => write!(f, " rotate(90)")?,
209 _ => (),
210 }
211 write!(f, "'")
212 }
213}
214
215impl fmt::Display for Tspan<'_> {
216 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
217 write!(f, "<tspan")?;
218 if let Some(x) = self.x {
219 write!(f, " x='{x}'")?;
220 }
221 if let Some(y) = self.y {
222 write!(f, " y='{y}'")?;
223 }
224 if let Some(dy) = self.dy {
225 write!(f, " dy='{dy}em'")?;
226 }
227 write!(f, ">{}", &self.text)?;
228 writeln!(f, "</tspan>")
229 }
230}
231
232impl<'a> Tspan<'a> {
233 pub fn new(text: &'a str) -> Self {
234 Tspan {
235 text,
236 x: None,
237 y: None,
238 dy: None,
239 }
240 }
241
242 pub fn x(mut self, x: i32) -> Self {
243 self.x = Some(x);
244 self
245 }
246
247 pub fn y(mut self, y: i32) -> Self {
248 self.y = Some(y);
249 self
250 }
251
252 pub fn dy(mut self, dy: f32) -> Self {
253 self.dy = Some(dy);
254 self
255 }
256}
257
258impl Tick {
259 pub const LEN: i32 = 20;
260 pub const HLEN: i32 = Tick::LEN + 8;
261 pub const VLEN: i32 = Tick::LEN * 2;
262
263 pub fn new<T>(value: f32, text: T) -> Self
264 where
265 T: Into<String>,
266 {
267 let text = text.into();
268 Tick { value, text }
269 }
270
271 pub fn text(&self) -> &str {
272 &self.text
273 }
274
275 pub fn x(&self, edge: Edge, rect: Rect, len: i32) -> i32 {
276 match edge {
277 Edge::Left => rect.right() - len,
278 Edge::Right => rect.x + len,
279 _ => rect.x + (self.value * rect.width as f32).round() as i32,
280 }
281 }
282
283 pub fn y(&self, edge: Edge, rect: Rect, len: i32) -> i32 {
284 match edge {
285 Edge::Top => rect.bottom() - len,
286 Edge::Bottom => rect.y + len,
287 _ => rect.y + (self.value * rect.height as f32).round() as i32,
288 }
289 }
290
291 pub fn tspan(&self, edge: Edge, rect: Rect) -> Tspan {
292 let x = self.x(edge, rect, Tick::HLEN);
293 let y = self.y(edge, rect, Tick::VLEN);
294 Tspan::new(self.text()).x(x).y(y).dy(0.33)
295 }
296}