lc_render/view/
vertical_bar.rs1use crate::shape::bar::Bar;
2use crate::{
3 BandScale, BarLabelPosition, BarsValues, Error, LinearScale, Orientation, Scale, View,
4};
5use std::collections::HashMap;
6use svg::node::Node;
7
8const DEFAULT_BAR_LABEL_VISIBLE: bool = true;
9const DEFAULT_BAR_LABEL_POSITION: BarLabelPosition = BarLabelPosition::Center;
10
11#[derive(Clone)]
13pub struct VerticalBarView {
14 x_scale: BandScale,
15 y_scale: LinearScale,
16 bars: Vec<Bar>,
17 bar_label_visible: bool,
18 bar_label_position: BarLabelPosition,
19}
20
21impl VerticalBarView {
22 pub fn new(x_scale: BandScale, y_scale: LinearScale) -> Self {
24 Self {
25 x_scale,
26 y_scale,
27 bars: Vec::new(),
28 bar_label_visible: DEFAULT_BAR_LABEL_VISIBLE,
29 bar_label_position: DEFAULT_BAR_LABEL_POSITION,
30 }
31 }
32
33 pub fn set_bar_label_visible(mut self, bar_label_visible: bool) -> Self {
35 self.bar_label_visible = bar_label_visible;
36 self
37 }
38
39 pub fn set_bar_label_position(mut self, bar_label_position: BarLabelPosition) -> Self {
41 self.bar_label_position = bar_label_position;
42 self
43 }
44
45 pub fn set_data(mut self, bars_values: &[BarsValues]) -> Result<Self, Error> {
47 if bars_values.is_empty() {
48 return Err(Error::DataIsEmpty);
49 }
50
51 let x_scale_domain = self.x_scale.ticks();
53 let mut bars_categories = HashMap::new();
54 for bv_opts in bars_values.iter() {
55 if bv_opts.values().len() > self.x_scale.ticks().len() {
56 return Err(Error::CategoriesCountIsLess);
57 }
58 for (i, value) in bv_opts.values().iter().enumerate() {
59 let category = &x_scale_domain[i];
60 bars_categories.entry(category).or_insert_with(Vec::new);
61 if let Some(category_entries) = bars_categories.get_mut(&category) {
62 category_entries.push((value, bv_opts.fill_color(), bv_opts.stroke_color()));
63 };
64 }
65 }
66
67 let mut bars = Vec::new();
69 for (category, category_entries) in bars_categories.iter() {
70 let mut value_acc = 0_f32;
71 let mut start = self.y_scale.scale(&value_acc);
72 let mut end = start;
73
74 for category_entry in category_entries.iter() {
75 let value = category_entry.0;
76 let fill_color = category_entry.1;
77 let stroke_color = category_entry.2;
78
79 value_acc += value;
80 if self.y_scale.is_range_reversed() {
81 end = start;
82 start = self.y_scale.scale(&value_acc);
83 } else {
84 start = end;
85 end = self.y_scale.scale(&value_acc);
86 }
87
88 let bar = Bar::new(
89 start,
90 end,
91 *value,
92 self.x_scale.bandwidth(),
93 self.x_scale.scale(&category.to_string()),
94 Orientation::Vertical,
95 )
96 .set_fill_color(fill_color)
97 .set_stroke_color(stroke_color)
98 .set_label_visible(self.bar_label_visible)
99 .set_label_position(self.bar_label_position);
100 bars.push(bar);
101 }
102 }
103 self.bars = bars;
104
105 Ok(self)
106 }
107}
108
109impl View for VerticalBarView {
110 fn to_svg(&self) -> svg::node::element::Group {
112 let mut res = svg::node::element::Group::new();
113
114 for bar in self.bars.iter() {
115 res.append(bar.to_svg());
116 }
117
118 res
119 }
120}
121
122#[cfg(test)]
123mod tests {
124 use super::*;
125 use crate::color::{COLOR_HEX_BLUE_2, COLOR_HEX_BLUE_4};
126 use crate::Color;
127
128 #[test]
129 fn vertical_bar_basic() {
130 let expected_svg_group = r##"<g>
131<g class="bar" transform="translate(3.2258034,0)">
132<rect fill="#5095e5" height="66" shape-rendering="crispEdges" stroke="#1960b2" stroke-width="1" width="29.032257" x="0" y="34"/>
133<text dy=".35em" fill="#080808" font-family="sans-serif" font-size="14px" text-anchor="middle" x="14.516129" y="67">
13466
135</text>
136</g>
137</g>"##;
138
139 let x_scale = BandScale::new(
140 vec!["A".to_string(), "B".to_string(), "C".to_string()],
141 0,
142 100,
143 );
144 let y_scale = LinearScale::new(0_f32, 100_f32, 100, 0);
145 let data = vec![BarsValues::new(vec![66_f32])
146 .set_fill_color(Color::new_from_hex(COLOR_HEX_BLUE_4))
147 .set_stroke_color(Color::new_from_hex(COLOR_HEX_BLUE_2))];
148 let vertical_bar = VerticalBarView::new(x_scale, y_scale)
149 .set_data(&data)
150 .expect("unable to set data");
151 let vertical_bar_svg = vertical_bar.to_svg();
152 assert_eq!(vertical_bar_svg.to_string(), expected_svg_group);
153 }
154}