esoc_chart/chart/
errorbar.rs1use esoc_gfx::canvas::Canvas;
5use esoc_gfx::color::Color;
6use esoc_gfx::element::DrawElement;
7use esoc_gfx::layer::Layer;
8use esoc_gfx::style::{Fill, Stroke};
9use esoc_gfx::transform::CoordinateTransform;
10
11use crate::series::{DataBounds, SeriesRenderer};
12use crate::theme::Theme;
13
14#[derive(Clone, Debug)]
16pub struct ErrorBarSeries {
17 pub x: Vec<f64>,
19 pub y: Vec<f64>,
21 pub err: Vec<f64>,
23 pub err_neg: Option<Vec<f64>>,
25 pub label: Option<String>,
27 pub color: Option<Color>,
29 pub cap_width: f64,
31}
32
33impl ErrorBarSeries {
34 pub fn new(x: &[f64], y: &[f64], err: &[f64]) -> Self {
36 Self {
37 x: x.to_vec(),
38 y: y.to_vec(),
39 err: err.to_vec(),
40 err_neg: None,
41 label: None,
42 color: None,
43 cap_width: 6.0,
44 }
45 }
46
47 pub fn asymmetric(x: &[f64], y: &[f64], err_pos: &[f64], err_neg: &[f64]) -> Self {
49 Self {
50 x: x.to_vec(),
51 y: y.to_vec(),
52 err: err_pos.to_vec(),
53 err_neg: Some(err_neg.to_vec()),
54 label: None,
55 color: None,
56 cap_width: 6.0,
57 }
58 }
59}
60
61impl SeriesRenderer for ErrorBarSeries {
62 fn data_bounds(&self) -> DataBounds {
63 let x_min = self.x.iter().copied().fold(f64::INFINITY, f64::min);
64 let x_max = self.x.iter().copied().fold(f64::NEG_INFINITY, f64::max);
65
66 let mut y_min = f64::INFINITY;
67 let mut y_max = f64::NEG_INFINITY;
68 for i in 0..self.y.len() {
69 let lo = self.y[i] - self.err_neg.as_ref().map_or(self.err[i], |en| en[i]);
70 let hi = self.y[i] + self.err[i];
71 y_min = y_min.min(lo);
72 y_max = y_max.max(hi);
73 }
74
75 DataBounds::new(x_min, x_max, y_min, y_max)
76 }
77
78 fn render(
79 &self,
80 canvas: &mut Canvas,
81 transform: &CoordinateTransform,
82 theme: &Theme,
83 series_index: usize,
84 ) {
85 let color = self
86 .color
87 .unwrap_or_else(|| theme.palette.get(series_index));
88 let stroke = Stroke::solid(color, 1.5);
89
90 for i in 0..self.x.len() {
91 let x = self.x[i];
92 let y = self.y[i];
93 let err_pos = self.err[i];
94 let err_neg = self.err_neg.as_ref().map_or(err_pos, |en| en[i]);
95
96 let p_center = transform.to_pixel(x, y);
97 let p_top = transform.to_pixel(x, y + err_pos);
98 let p_bot = transform.to_pixel(x, y - err_neg);
99
100 canvas.add(DrawElement::line(
102 p_top.x,
103 p_top.y,
104 p_bot.x,
105 p_bot.y,
106 stroke.clone(),
107 Layer::Data,
108 ));
109
110 canvas.add(DrawElement::line(
112 p_top.x - self.cap_width / 2.0,
113 p_top.y,
114 p_top.x + self.cap_width / 2.0,
115 p_top.y,
116 stroke.clone(),
117 Layer::Data,
118 ));
119
120 canvas.add(DrawElement::line(
122 p_bot.x - self.cap_width / 2.0,
123 p_bot.y,
124 p_bot.x + self.cap_width / 2.0,
125 p_bot.y,
126 stroke.clone(),
127 Layer::Data,
128 ));
129
130 canvas.add(DrawElement::circle(
132 p_center.x,
133 p_center.y,
134 3.0,
135 Fill::Solid(color),
136 Layer::Data,
137 ));
138 }
139 }
140
141 fn label(&self) -> Option<&str> {
142 self.label.as_deref()
143 }
144}