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
use crate::shape::bar::Bar;
use crate::{
    BandScale, BarLabelPosition, BarsValues, Error, LinearScale, Orientation, Scale, View,
};
use std::collections::HashMap;
use svg::node::Node;

const DEFAULT_BAR_LABEL_VISIBLE: bool = true;
const DEFAULT_BAR_LABEL_POSITION: BarLabelPosition = BarLabelPosition::Center;

/// HorizontalBarView represents a chart view with horizontal bars.
#[derive(Clone)]
pub struct HorizontalBarView {
    x_scale: LinearScale,
    y_scale: BandScale,
    bars: Vec<Bar>,
    bar_label_visible: bool,
    bar_label_position: BarLabelPosition,
}

impl HorizontalBarView {
    /// Create a new HorizontalBarView.
    pub fn new(x_scale: LinearScale, y_scale: BandScale) -> Self {
        Self {
            x_scale,
            y_scale,
            bars: Vec::new(),
            bar_label_visible: DEFAULT_BAR_LABEL_VISIBLE,
            bar_label_position: DEFAULT_BAR_LABEL_POSITION,
        }
    }

    /// Configure label visibility for bars.
    pub fn set_bar_label_visible(mut self, bar_label_visible: bool) -> Self {
        self.bar_label_visible = bar_label_visible;
        self
    }

    /// Configure label position for bars.
    pub fn set_bar_label_position(mut self, bar_label_position: BarLabelPosition) -> Self {
        self.bar_label_position = bar_label_position;
        self
    }

    /// Set values for bars.
    pub fn set_data(mut self, bars_values: &[BarsValues]) -> Result<Self, Error> {
        if bars_values.is_empty() {
            return Err(Error::DataIsEmpty);
        }

        // Populate a map of category to tuples of (value, fill_color, stroke_color).
        let y_scale_domain = self.y_scale.ticks();
        let mut bars_categories = HashMap::new();
        for bv_opts in bars_values.iter() {
            if bv_opts.values().len() > self.y_scale.ticks().len() {
                return Err(Error::CategoriesCountIsLess);
            }
            for (i, value) in bv_opts.values().iter().enumerate() {
                let category = &y_scale_domain[i];
                bars_categories.entry(category).or_insert_with(Vec::new);
                if let Some(category_entries) = bars_categories.get_mut(&category) {
                    category_entries.push((value, bv_opts.fill_color(), bv_opts.stroke_color()));
                };
            }
        }

        // Create vector of bars from the bars_categories map.
        let mut bars = Vec::new();
        for (category, category_entries) in bars_categories.iter() {
            let mut value_acc = 0_f32;
            let mut start = self.x_scale.scale(&value_acc);
            let mut end = start;

            for category_entry in category_entries.iter() {
                let value = category_entry.0;
                let fill_color = category_entry.1;
                let stroke_color = category_entry.2;

                value_acc += value;
                if self.x_scale.is_range_reversed() {
                    end = start;
                    start = self.x_scale.scale(&value_acc);
                } else {
                    start = end;
                    end = self.x_scale.scale(&value_acc);
                }

                let bar = Bar::new(
                    start,
                    end,
                    *value,
                    self.y_scale.bandwidth(),
                    self.y_scale.scale(&category.to_string()),
                    Orientation::Horizontal,
                )
                .set_fill_color(fill_color)
                .set_stroke_color(stroke_color)
                .set_label_visible(self.bar_label_visible)
                .set_label_position(self.bar_label_position);
                bars.push(bar);
            }
        }
        self.bars.extend(bars);

        Ok(self)
    }
}

impl View for HorizontalBarView {
    /// Get bar view SVG representation.
    fn to_svg(&self) -> svg::node::element::Group {
        let mut res = svg::node::element::Group::new();

        for bar in self.bars.iter() {
            res.append(bar.to_svg());
        }

        res
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::color::{COLOR_HEX_GREEN_2, COLOR_HEX_GREEN_4};
    use crate::Color;

    #[test]
    fn horizontal_bar_basic() {
        let expected_svg_group = r##"<g>
<g class="bar" transform="translate(0,3.2258034)">
<rect fill="#117401" height="29.032257" shape-rendering="crispEdges" stroke="#00400e" stroke-width="1" width="66" x="34" y="0"/>
<text dy=".35em" fill="#080808" font-family="sans-serif" font-size="14px" text-anchor="middle" x="67" y="14.516129">
66
</text>
</g>
</g>"##;

        let x_scale = LinearScale::new(0_f32, 100_f32, 100, 0);
        let y_scale = BandScale::new(
            vec!["A".to_string(), "B".to_string(), "C".to_string()],
            0,
            100,
        );
        let data = vec![BarsValues::new(vec![66_f32])
            .set_fill_color(Color::new_from_hex(COLOR_HEX_GREEN_4))
            .set_stroke_color(Color::new_from_hex(COLOR_HEX_GREEN_2))];
        let horizontal_bar = HorizontalBarView::new(x_scale, y_scale)
            .set_data(&data)
            .expect("unable to set data");
        let horizontal_bar_svg = horizontal_bar.to_svg();
        assert_eq!(horizontal_bar_svg.to_string(), expected_svg_group);
    }
}