lc_render/view/
vertical_bar.rs

1use 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/// VerticalBarView represents a chart view with vertical bars.
12#[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    /// Create a new VerticalBarView.
23    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    /// Configure label visibility for bars.
34    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    /// Configure label position for bars.
40    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    /// Set values for bars.
46    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        // Populate a map of category to tuples of (value, fill_color, stroke_color).
52        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        // Create vector of bars from the bars_categories map.
68        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    /// Get bar view SVG representation.
111    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}