ggplot_rs/geom/
histogram.rs1use 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 backend.set_tooltip(Some(tip));
106
107 backend.draw_rect(
108 (left_px, top_px.min(bottom_px)),
109 (right_px, top_px.max(bottom_px)),
110 &RectStyle {
111 fill: Some(self.fill),
112 stroke: Some(self.color),
113 stroke_width: 0.5,
114 alpha: self.alpha,
115 clip: true,
116 },
117 )?;
118 }
119 backend.set_tooltip(None);
120
121 Ok(())
122 }
123
124 fn required_aes(&self) -> Vec<Aesthetic> {
125 vec![Aesthetic::X]
126 }
127
128 fn default_stat(&self) -> Box<dyn Stat> {
129 let mut stat = StatBin {
130 bins: self.bins,
131 binwidth: self.binwidth,
132 };
133 if let Some(bw) = self.binwidth {
134 stat = stat.with_binwidth(bw);
135 }
136 Box::new(stat)
137 }
138
139 fn default_position(&self) -> Box<dyn Position> {
140 Box::new(PositionIdentity)
141 }
142
143 fn default_params(&self) -> GeomParams {
144 GeomParams::default()
145 }
146
147 fn name(&self) -> &str {
148 "histogram"
149 }
150
151 fn include_zero_baseline(&self) -> bool {
152 true
153 }
154
155 fn set_series_color(&mut self, color: (u8, u8, u8)) {
156 self.fill = color;
157 }
158}