1use crate::color::{COLOR_HEX_BLUE_1, COLOR_HEX_BLUE_2};
2use crate::math::linear::range;
3use crate::render::svg::*;
4use crate::Orientation;
5use svg::Node;
6
7const DEFAULT_FONT_SIZE: &str = "14px";
8const DEFAULT_LABEL_HORIZONTAL_OFFSET: f32 = 12_f32;
9const DEFAULT_LABEL_VERTICAL_OFFSET: f32 = 16_f32;
10const DEFAULT_LABEL_VISIBLE: bool = true;
11const DEFAULT_LABEL_POSITION: BarLabelPosition = BarLabelPosition::Center;
12
13#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
15pub enum BarLabelPosition {
16 StartOutside,
17 StartInside,
18 Center,
19 EndInside,
20 EndOutside,
21}
22
23#[derive(Clone)]
25pub struct Bar {
26 start: f32,
27 end: f32,
28 size: f32,
29 width: f32,
30 offset: f32,
31 orientation: Orientation,
32 fill_color: String,
33 stroke_color: String,
34 stroke_width: i32,
35 label_visible: bool,
36 label_position: BarLabelPosition,
37 label_text_anchor: String,
38 label_x_attr: f32,
39}
40
41impl Bar {
42 pub fn new(
44 start: f32,
45 end: f32,
46 size: f32,
47 width: f32,
48 offset: f32,
49 orientation: Orientation,
50 ) -> Self {
51 Bar {
52 start,
53 end,
54 size,
55 width,
56 offset,
57 orientation,
58 fill_color: COLOR_HEX_BLUE_2.to_string(),
59 stroke_color: COLOR_HEX_BLUE_1.to_string(),
60 stroke_width: DEFAULT_STROKE_WIDTH,
61 label_visible: DEFAULT_LABEL_VISIBLE,
62 label_position: DEFAULT_LABEL_POSITION,
63 label_text_anchor: Self::label_text_anchor(DEFAULT_LABEL_POSITION, orientation),
64 label_x_attr: Self::label_x_attr(start, end, DEFAULT_LABEL_POSITION, orientation),
65 }
66 }
67
68 pub fn set_fill_color(mut self, fill_color: &str) -> Self {
70 self.fill_color = fill_color.to_string();
71 self
72 }
73
74 pub fn set_stroke_color(mut self, stroke_color: &str) -> Self {
76 self.stroke_color = stroke_color.to_string();
77 self
78 }
79
80 pub fn set_label_visible(mut self, label_visible: bool) -> Self {
82 self.label_visible = label_visible;
83 self
84 }
85
86 pub fn set_label_position(mut self, label_position: BarLabelPosition) -> Self {
88 self.label_position = label_position;
89 self.label_text_anchor = Self::label_text_anchor(label_position, self.orientation);
90 self.label_x_attr =
91 Self::label_x_attr(self.start, self.end, label_position, self.orientation);
92 self
93 }
94
95 fn label_text_anchor(label_position: BarLabelPosition, orientation: Orientation) -> String {
96 match label_position {
97 BarLabelPosition::StartOutside => {
98 if orientation == Orientation::Horizontal {
99 return TEXT_ANCHOR_END.to_string();
100 }
101 TEXT_ANCHOR_MIDDLE.to_string()
102 }
103 BarLabelPosition::StartInside => {
104 if orientation == Orientation::Horizontal {
105 return TEXT_ANCHOR_START.to_string();
106 }
107 TEXT_ANCHOR_MIDDLE.to_string()
108 }
109 BarLabelPosition::Center => TEXT_ANCHOR_MIDDLE.to_string(),
110 BarLabelPosition::EndInside => {
111 if orientation == Orientation::Horizontal {
112 return TEXT_ANCHOR_END.to_string();
113 }
114 TEXT_ANCHOR_MIDDLE.to_string()
115 }
116 BarLabelPosition::EndOutside => {
117 if orientation == Orientation::Horizontal {
118 return TEXT_ANCHOR_START.to_string();
119 }
120 TEXT_ANCHOR_MIDDLE.to_string()
121 }
122 }
123 }
124
125 fn label_x_attr(
126 start: f32,
127 end: f32,
128 label_position: BarLabelPosition,
129 orientation: Orientation,
130 ) -> f32 {
131 match label_position {
132 BarLabelPosition::StartOutside => {
133 if orientation == Orientation::Horizontal {
134 return start - DEFAULT_LABEL_HORIZONTAL_OFFSET;
135 }
136 end + DEFAULT_LABEL_VERTICAL_OFFSET
137 }
138 BarLabelPosition::StartInside => {
139 if orientation == Orientation::Horizontal {
140 return start + DEFAULT_LABEL_HORIZONTAL_OFFSET;
141 }
142 end - DEFAULT_LABEL_VERTICAL_OFFSET
143 }
144 BarLabelPosition::Center => start + (range(start, end) / 2.0),
145 BarLabelPosition::EndInside => {
146 if orientation == Orientation::Horizontal {
147 return end - DEFAULT_LABEL_VERTICAL_OFFSET;
148 }
149 start + DEFAULT_LABEL_HORIZONTAL_OFFSET
150 }
151 BarLabelPosition::EndOutside => {
152 if orientation == Orientation::Horizontal {
153 return end + DEFAULT_LABEL_VERTICAL_OFFSET;
154 }
155 start - DEFAULT_LABEL_HORIZONTAL_OFFSET
156 }
157 }
158 }
159
160 pub fn to_svg(&self) -> svg::node::element::Group {
162 let x_attr = match self.orientation {
163 Orientation::Horizontal => (X_ATTR),
164 Orientation::Vertical => (Y_ATTR),
165 };
166 let y_attr = match self.orientation {
167 Orientation::Horizontal => (Y_ATTR),
168 Orientation::Vertical => (X_ATTR),
169 };
170 let w_attr = match self.orientation {
171 Orientation::Horizontal => (WIDTH_ATTR),
172 Orientation::Vertical => (HEIGHT_ATTR),
173 };
174 let h_attr = match self.orientation {
175 Orientation::Horizontal => (HEIGHT_ATTR),
176 Orientation::Vertical => (WIDTH_ATTR),
177 };
178 let rect = svg::node::element::Rectangle::new()
179 .set(x_attr, self.start)
180 .set(y_attr, START)
181 .set(w_attr, range(self.start, self.end))
182 .set(h_attr, self.width)
183 .set(SHAPE_RENDERING_ATTR, SHAPE_RENDERING_CRISP_EDGES)
184 .set(FILL_ATTR, self.fill_color.to_string())
185 .set(STROKE_WIDTH_ATTR, self.stroke_width)
186 .set(STROKE_ATTR, self.stroke_color.to_string());
187
188 let offset_x = match self.orientation {
189 Orientation::Horizontal => 0.0,
190 Orientation::Vertical => self.offset,
191 };
192 let offset_y = match self.orientation {
193 Orientation::Horizontal => self.offset,
194 Orientation::Vertical => 0.0,
195 };
196 let mut group = svg::node::element::Group::new()
197 .set(TRANSFORM_ATTR, translate_x_y(offset_x, offset_y))
198 .set(CLASS_ATTR, CLASS_BAR);
199 group.append(rect);
200
201 if !self.label_visible {
202 return group;
203 }
204
205 let label = svg::node::element::Text::new()
206 .set(x_attr, self.label_x_attr)
207 .set(y_attr, self.width / 2.0) .set(TEXT_ANCHOR_ATTR, self.label_text_anchor.to_owned())
209 .set(DY_ATTR, DEFAULT_DY)
210 .set(FONT_FAMILY_ATTR, DEFAULT_FONT_FAMILY)
211 .set(FILL_ATTR, DEFAULT_FONT_COLOR)
212 .set(FONT_SIZE_ATTR, DEFAULT_FONT_SIZE)
213 .add(svg::node::Text::new(self.size.to_string()));
214 group.append(label);
215
216 group
217 }
218}
219
220#[cfg(test)]
221mod tests {
222 use super::*;
223 use crate::color::{COLOR_HEX_GREEN_2, COLOR_HEX_GREEN_4};
224
225 #[test]
226 fn bar_basic() {
227 let expected_svg_group = r##"<g class="bar" transform="translate(5,0)">
228<rect fill="#117401" height="10" shape-rendering="crispEdges" stroke="#00400e" stroke-width="1" width="40" x="0" y="10"/>
229<text dy=".35em" fill="#080808" font-family="sans-serif" font-size="14px" text-anchor="middle" x="20" y="36">
23030
231</text>
232</g>"##;
233
234 let bar_svg = Bar::new(10_f32, 20_f32, 30_f32, 40_f32, 5_f32, Orientation::Vertical)
235 .set_fill_color(COLOR_HEX_GREEN_4)
236 .set_stroke_color(COLOR_HEX_GREEN_2)
237 .set_label_visible(true)
238 .set_label_position(BarLabelPosition::StartOutside)
239 .to_svg();
240
241 assert_eq!(bar_svg.to_string(), expected_svg_group);
242 }
243}