splot/
text.rs

1// text.rs
2//
3// Copyright (c) 2021-2025  Douglas P Lau
4//
5use crate::rect::{Edge, Rect};
6use hatmil::Value;
7
8/// Vertical offset relative to point
9#[derive(Copy, Clone, Debug, PartialEq)]
10pub enum VerticalOffset {
11    /// Label below point
12    Below,
13    /// Label at point
14    At,
15    /// Label above point
16    Above,
17}
18
19/// Text anchor
20#[derive(Copy, Clone, Debug, PartialEq)]
21pub enum Anchor {
22    /// Anchor at start of text
23    Start,
24    /// Anchor at middle of text
25    Middle,
26    /// Anchor at end of text
27    End,
28}
29
30/// Chart label
31#[derive(Clone, Debug, PartialEq)]
32pub struct Label {
33    offset: VerticalOffset,
34    anchor: Anchor,
35    rounding_precision: Option<usize>,
36}
37
38/// Tick marks for axis labels
39#[derive(Debug, PartialEq)]
40pub struct Tick {
41    value: f32,
42    text: String,
43}
44
45impl From<Anchor> for Value<'_> {
46    fn from(anchor: Anchor) -> Self {
47        Value::from(match anchor {
48            Anchor::Start => "start",
49            Anchor::Middle => "middle",
50            Anchor::End => "end",
51        })
52    }
53}
54
55impl Default for Label {
56    fn default() -> Self {
57        Label {
58            offset: VerticalOffset::At,
59            anchor: Anchor::Middle,
60            rounding_precision: None,
61        }
62    }
63}
64
65#[allow(dead_code)]
66impl Label {
67    /// Create a new label
68    pub fn new() -> Self {
69        Self::default()
70    }
71
72    /// Get the vertical offset
73    pub fn vertical_offset(&self) -> f32 {
74        match self.offset {
75            VerticalOffset::Above => -1.0,
76            VerticalOffset::At => 0.0,
77            VerticalOffset::Below => 1.0,
78        }
79    }
80
81    /// Make label above
82    pub fn above(mut self) -> Self {
83        self.offset = VerticalOffset::Above;
84        self
85    }
86
87    /// Make label below
88    pub fn below(mut self) -> Self {
89        self.offset = VerticalOffset::Below;
90        self
91    }
92
93    /// Set anchor to start
94    pub fn start(mut self) -> Self {
95        self.anchor = Anchor::Start;
96        self
97    }
98
99    /// Set anchor to end
100    pub fn end(mut self) -> Self {
101        self.anchor = Anchor::End;
102        self
103    }
104
105    /// Get rounded value
106    pub fn rounded(&self, value: f32) -> String {
107        match self.rounding_precision {
108            Some(digits) => format!("{value:.0$}", digits),
109            None => value.to_string(),
110        }
111    }
112}
113
114impl Tick {
115    pub const LEN: i32 = 20;
116    pub const HLEN: i32 = Tick::LEN + 8;
117    pub const VLEN: i32 = Tick::LEN * 2;
118
119    /// Create a new tick
120    pub fn new<T>(value: f32, text: T) -> Self
121    where
122        T: Into<String>,
123    {
124        let text = text.into();
125        Tick { value, text }
126    }
127
128    /// Get text
129    pub fn text(&self) -> &str {
130        &self.text
131    }
132
133    /// Get X
134    pub fn x(&self, edge: Edge, rect: Rect, len: i32) -> i32 {
135        match edge {
136            Edge::Left => rect.right() - len,
137            Edge::Right => rect.x + len,
138            _ => rect.x + (self.value * rect.width as f32).round() as i32,
139        }
140    }
141
142    /// Get Y
143    pub fn y(&self, edge: Edge, rect: Rect, len: i32) -> i32 {
144        match edge {
145            Edge::Top => rect.bottom() - len,
146            Edge::Bottom => rect.y + len,
147            _ => rect.y + (self.value * rect.height as f32).round() as i32,
148        }
149    }
150}