1use esoc_gfx::canvas::Canvas;
5use esoc_gfx::color::Color;
6use esoc_gfx::element::{DrawElement, Element};
7use esoc_gfx::geom::Rect;
8use esoc_gfx::layer::Layer;
9use esoc_gfx::style::{Fill, Stroke};
10use esoc_gfx::transform::CoordinateTransform;
11
12use crate::series::{DataBounds, SeriesRenderer};
13use crate::theme::Theme;
14
15#[derive(Clone, Debug)]
17pub struct BarSeries {
18 pub x: Vec<f64>,
20 pub heights: Vec<f64>,
22 pub label: Option<String>,
24 pub color: Option<Color>,
26 pub bar_width: f64,
28 pub horizontal: bool,
30}
31
32impl BarSeries {
33 pub fn new(x: &[f64], heights: &[f64]) -> Self {
35 Self {
36 x: x.to_vec(),
37 heights: heights.to_vec(),
38 label: None,
39 color: None,
40 bar_width: 0.8,
41 horizontal: false,
42 }
43 }
44}
45
46impl SeriesRenderer for BarSeries {
47 fn data_bounds(&self) -> DataBounds {
48 if self.horizontal {
49 let y_min = self.x.iter().copied().fold(f64::INFINITY, f64::min) - self.bar_width / 2.0;
50 let y_max =
51 self.x.iter().copied().fold(f64::NEG_INFINITY, f64::max) + self.bar_width / 2.0;
52 let x_max = self.heights.iter().copied().fold(0.0_f64, f64::max);
53 DataBounds::new(0.0, x_max, y_min, y_max)
54 } else {
55 let x_min = self.x.iter().copied().fold(f64::INFINITY, f64::min) - self.bar_width / 2.0;
56 let x_max =
57 self.x.iter().copied().fold(f64::NEG_INFINITY, f64::max) + self.bar_width / 2.0;
58 let y_max = self.heights.iter().copied().fold(0.0_f64, f64::max);
59 DataBounds::new(x_min, x_max, 0.0, y_max)
60 }
61 }
62
63 fn render(
64 &self,
65 canvas: &mut Canvas,
66 transform: &CoordinateTransform,
67 theme: &Theme,
68 series_index: usize,
69 ) {
70 let color = self
71 .color
72 .unwrap_or_else(|| theme.palette.get(series_index));
73
74 for (&x, &h) in self.x.iter().zip(self.heights.iter()) {
75 if self.horizontal {
76 let p_start = transform.to_pixel(0.0, x - self.bar_width / 2.0);
77 let p_end = transform.to_pixel(h, x + self.bar_width / 2.0);
78 let rx = p_start.x.min(p_end.x);
79 let ry = p_start.y.min(p_end.y);
80 let rw = (p_end.x - p_start.x).abs();
81 let rh = (p_end.y - p_start.y).abs();
82 canvas.add(DrawElement::new(
83 Element::Rect {
84 rect: Rect::new(rx, ry, rw, rh),
85 fill: Fill::Solid(color),
86 stroke: Some(Stroke::solid(color.with_alpha(0.8), 0.5)),
87 rx: 0.0,
88 },
89 Layer::Data,
90 ));
91 } else {
92 let p_top = transform.to_pixel(x - self.bar_width / 2.0, h);
93 let p_bottom = transform.to_pixel(x + self.bar_width / 2.0, 0.0);
94 let rx = p_top.x.min(p_bottom.x);
95 let ry = p_top.y.min(p_bottom.y);
96 let rw = (p_bottom.x - p_top.x).abs();
97 let rh = (p_bottom.y - p_top.y).abs();
98 canvas.add(DrawElement::new(
99 Element::Rect {
100 rect: Rect::new(rx, ry, rw, rh),
101 fill: Fill::Solid(color),
102 stroke: Some(Stroke::solid(color.with_alpha(0.8), 0.5)),
103 rx: 0.0,
104 },
105 Layer::Data,
106 ));
107 }
108 }
109 }
110
111 fn label(&self) -> Option<&str> {
112 self.label.as_deref()
113 }
114}