1use crate::aes::Aesthetic;
2use crate::coord::Coord;
3use crate::data::DataFrame;
4use crate::position::identity::PositionIdentity;
5use crate::position::Position;
6use crate::render::backend::{DrawBackend, RectStyle};
7use crate::render::RenderError;
8use crate::scale::ScaleSet;
9use crate::stat::bin::StatBin;
10use crate::stat::Stat;
11use crate::theme::Theme;
12
13use super::{Geom, GeomParams};
14
15pub struct GeomHistogram {
17 pub bins: usize,
18 pub binwidth: Option<f64>,
19 pub fill: (u8, u8, u8),
20 pub color: (u8, u8, u8),
21 pub alpha: f64,
22}
23
24impl GeomHistogram {
25 pub fn with_binwidth(mut self, width: f64) -> Self {
27 self.binwidth = Some(width);
28 self
29 }
30
31 pub fn with_bins(mut self, bins: usize) -> Self {
33 self.bins = bins;
34 self.binwidth = None;
35 self
36 }
37}
38
39impl Default for GeomHistogram {
40 fn default() -> Self {
41 GeomHistogram {
42 bins: 30,
43 binwidth: None,
44 fill: (97, 156, 255),
45 color: (50, 50, 50),
46 alpha: 1.0,
47 }
48 }
49}
50
51impl Geom for GeomHistogram {
52 fn draw(
53 &self,
54 data: &DataFrame,
55 coord: &dyn Coord,
56 scales: &ScaleSet,
57 _theme: &Theme,
58 backend: &mut dyn DrawBackend,
59 ) -> Result<(), RenderError> {
60 let xmin_col = data.column("xmin");
61 let xmax_col = data.column("xmax");
62 let y_col = data
63 .column("y")
64 .ok_or(RenderError::MissingAesthetic("y".into()))?;
65
66 let plot_area = backend.plot_area();
67 let x_scale = scales.get(&Aesthetic::X);
68 let y_scale = scales.get(&Aesthetic::Y);
69
70 for i in 0..data.nrows() {
71 let (left_n, right_n) = match (xmin_col, xmax_col) {
72 (Some(xmin), Some(xmax)) => {
73 let ln = x_scale.map(|s| s.map(&xmin[i])).unwrap_or(0.0);
74 let rn = x_scale.map(|s| s.map(&xmax[i])).unwrap_or(1.0);
75 (ln, rn)
76 }
77 _ => {
78 let x_col = data
80 .column("x")
81 .ok_or(RenderError::MissingAesthetic("x".into()))?;
82 let nx = x_scale.map(|s| s.map(&x_col[i])).unwrap_or(0.0);
83 (nx - 0.02, nx + 0.02)
84 }
85 };
86
87 let ny = y_scale.map(|s| s.map(&y_col[i])).unwrap_or(0.0);
88 let ny_base = y_scale
89 .map(|s| s.map(&crate::data::Value::Float(0.0)))
90 .unwrap_or(0.0);
91
92 let (left_px, top_px) = coord.transform((left_n, ny), &plot_area);
93 let (right_px, bottom_px) = coord.transform((right_n, ny_base), &plot_area);
94
95 let cnt = super::tip_value(&y_col[i]);
97 let tip = match (xmin_col, xmax_col) {
98 (Some(lo), Some(hi)) => format!(
99 "[{}, {}): {cnt}",
100 super::tip_value(&lo[i]),
101 super::tip_value(&hi[i])
102 ),
103 _ => format!("count: {cnt}"),
104 };
105 super::set_mark(
106 backend,
107 Some(tip),
108 data.column("x").map(|c| super::tip_value(&c[i])),
109 super::series_key(data, i),
110 super::measured_value(data, i),
111 );
112 super::set_mark_formatted(
113 backend,
114 scales,
115 data.column("x").map(|c| &c[i]),
116 &Aesthetic::Y,
117 super::measured_raw(data, i),
118 );
119 super::set_row_extras(backend, data, i);
120
121 backend.draw_rect(
122 (left_px, top_px.min(bottom_px)),
123 (right_px, top_px.max(bottom_px)),
124 &RectStyle {
125 fill: Some(self.fill),
126 stroke: Some(self.color),
127 stroke_width: 0.5,
128 alpha: self.alpha,
129 clip: true,
130 },
131 )?;
132 }
133 super::clear_mark(backend);
134 super::clear_row_extras(backend);
135
136 Ok(())
137 }
138
139 fn required_aes(&self) -> Vec<Aesthetic> {
140 vec![Aesthetic::X]
141 }
142
143 fn default_stat(&self) -> Box<dyn Stat> {
144 let mut stat = StatBin {
145 bins: self.bins,
146 binwidth: self.binwidth,
147 };
148 if let Some(bw) = self.binwidth {
149 stat = stat.with_binwidth(bw);
150 }
151 Box::new(stat)
152 }
153
154 fn default_position(&self) -> Box<dyn Position> {
155 Box::new(PositionIdentity)
156 }
157
158 fn default_params(&self) -> GeomParams {
159 GeomParams::default()
160 }
161
162 fn name(&self) -> &str {
163 "histogram"
164 }
165
166 fn apply_element_geom(&mut self, el: &crate::theme::elements::ElementGeom) {
167 if let (true, Some(ink)) = (self.color == (50, 50, 50), el.ink) {
168 self.color = ink;
169 }
170 }
171
172 fn consumes_tooltips(&self) -> bool {
173 true
174 }
175
176 fn include_zero_baseline(&self) -> bool {
177 true
178 }
179
180 fn set_series_color(&mut self, color: (u8, u8, u8)) {
181 self.fill = color;
182 }
183}
184
185#[cfg(test)]
186mod tests {
187 use crate::aes::{AesMapping, Aesthetic, MappingStage};
188 use crate::data::Value;
189 use crate::prelude::*;
190
191 #[test]
198 fn after_stat_data_attr_works_on_a_histogram() {
199 let n = 50;
200 let x: Vec<Value> = (0..n).map(|i| Value::Float(i as f64 * 0.2)).collect();
201 let mut aes = Aes::new().x("x");
202 aes.mappings.push(AesMapping {
203 column: "count".to_string(),
204 aesthetic: Aesthetic::DataAttr("data-n".to_string()),
205 stage: MappingStage::AfterStat,
206 });
207
208 let svg = GGPlot::new(vec![("x".into(), x)])
209 .aes(aes)
210 .geom_histogram()
211 .render_svg_native_with_size(400, 300)
212 .unwrap();
213
214 assert!(svg.contains("data-n=\""));
215 }
216}