plotters/style/
size.rs

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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
use crate::coord::CoordTranslate;
use crate::drawing::DrawingArea;
use plotters_backend::DrawingBackend;

/// The trait indicates that the type has a dimensional data.
/// This is the abstraction for the relative sizing model.
/// A relative sizing value is able to be converted into a concrete size
/// when coupling with a type with `HasDimension` type.
pub trait HasDimension {
    /// Get the dimensional data for this object
    fn dim(&self) -> (u32, u32);
}

impl<D: DrawingBackend, C: CoordTranslate> HasDimension for DrawingArea<D, C> {
    fn dim(&self) -> (u32, u32) {
        self.dim_in_pixel()
    }
}

impl HasDimension for (u32, u32) {
    fn dim(&self) -> (u32, u32) {
        *self
    }
}

/// The trait that describes a size, it may be a relative size which the
/// size is determined by the parent size, e.g., 10% of the parent width
pub trait SizeDesc {
    /// Convert the size into the number of pixels
    ///
    /// - `parent`: The reference to the parent container of this size
    /// - **returns**: The number of pixels
    fn in_pixels<T: HasDimension>(&self, parent: &T) -> i32;
}

impl SizeDesc for i32 {
    fn in_pixels<D: HasDimension>(&self, _parent: &D) -> i32 {
        *self
    }
}

impl SizeDesc for u32 {
    fn in_pixels<D: HasDimension>(&self, _parent: &D) -> i32 {
        *self as i32
    }
}

impl SizeDesc for f32 {
    fn in_pixels<D: HasDimension>(&self, _parent: &D) -> i32 {
        *self as i32
    }
}

impl SizeDesc for f64 {
    fn in_pixels<D: HasDimension>(&self, _parent: &D) -> i32 {
        *self as i32
    }
}

/// Describes a relative size, might be
///     1. portion of height
///     2. portion of width
///     3. portion of the minimal of height and weight
pub enum RelativeSize {
    /// Percentage height
    Height(f64),
    /// Percentage width
    Width(f64),
    /// Percentage of either height or width, which is smaller
    Smaller(f64),
}

impl RelativeSize {
    /// Set the lower bound of the relative size.
    ///
    /// - `min_sz`: The minimal size the relative size can be in pixels
    /// - **returns**: The relative size with the bound
    pub fn min(self, min_sz: i32) -> RelativeSizeWithBound {
        RelativeSizeWithBound {
            size: self,
            min: Some(min_sz),
            max: None,
        }
    }

    /// Set the upper bound of the relative size
    ///
    /// - `max_size`: The maximum size in pixels for this relative size
    /// - **returns** The relative size with the upper bound
    pub fn max(self, max_sz: i32) -> RelativeSizeWithBound {
        RelativeSizeWithBound {
            size: self,
            max: Some(max_sz),
            min: None,
        }
    }
}

impl SizeDesc for RelativeSize {
    fn in_pixels<D: HasDimension>(&self, parent: &D) -> i32 {
        let (w, h) = parent.dim();
        match self {
            RelativeSize::Width(p) => *p * f64::from(w),
            RelativeSize::Height(p) => *p * f64::from(h),
            RelativeSize::Smaller(p) => *p * f64::from(w.min(h)),
        }
        .round() as i32
    }
}

/// Allows a value turns into a relative size
pub trait AsRelative: Into<f64> {
    /// Make the value a relative size of percentage of width
    fn percent_width(self) -> RelativeSize {
        RelativeSize::Width(self.into() / 100.0)
    }
    /// Make the value a relative size of percentage of height
    fn percent_height(self) -> RelativeSize {
        RelativeSize::Height(self.into() / 100.0)
    }
    /// Make the value a relative size of percentage of minimal of height and width
    fn percent(self) -> RelativeSize {
        RelativeSize::Smaller(self.into() / 100.0)
    }
}

impl<T: Into<f64>> AsRelative for T {}

/// The struct describes a relative size with upper bound and lower bound
pub struct RelativeSizeWithBound {
    size: RelativeSize,
    min: Option<i32>,
    max: Option<i32>,
}

impl RelativeSizeWithBound {
    /// Set the lower bound of the bounded relative size
    ///
    /// - `min_sz`: The lower bound of this size description
    /// - **returns**: The newly created size description with the bound
    pub fn min(mut self, min_sz: i32) -> RelativeSizeWithBound {
        self.min = Some(min_sz);
        self
    }

    /// Set the upper bound of the bounded relative size
    ///
    /// - `min_sz`: The upper bound of this size description
    /// - **returns**: The newly created size description with the bound
    pub fn max(mut self, max_sz: i32) -> RelativeSizeWithBound {
        self.max = Some(max_sz);
        self
    }
}

impl SizeDesc for RelativeSizeWithBound {
    fn in_pixels<D: HasDimension>(&self, parent: &D) -> i32 {
        let size = self.size.in_pixels(parent);
        let size_lower_capped = self.min.map_or(size, |x| x.max(size));
        self.max.map_or(size_lower_capped, |x| x.min(size))
    }
}

#[cfg(test)]
mod test {
    use super::*;
    #[test]
    fn test_relative_size() {
        let size = (10).percent_height();
        assert_eq!(size.in_pixels(&(100, 200)), 20);

        let size = (10).percent_width();
        assert_eq!(size.in_pixels(&(100, 200)), 10);

        let size = (-10).percent_width();
        assert_eq!(size.in_pixels(&(100, 200)), -10);

        let size = (10).percent_width().min(30);
        assert_eq!(size.in_pixels(&(100, 200)), 30);
        assert_eq!(size.in_pixels(&(400, 200)), 40);

        let size = (10).percent();
        assert_eq!(size.in_pixels(&(100, 200)), 10);
        assert_eq!(size.in_pixels(&(400, 200)), 20);
    }
}