1use crate::aes::Aesthetic;
6use crate::coord::Coord;
7use crate::data::{DataFrame, Value};
8use crate::position::identity::PositionIdentity;
9use crate::position::Position;
10use crate::render::backend::{DrawBackend, PointStyle, RectStyle};
11use crate::render::RenderError;
12use crate::scale::ScaleSet;
13use crate::stat::waffle::{StatWaffle, UnitLayout};
14use crate::stat::Stat;
15use crate::theme::Theme;
16
17use super::{Geom, GeomParams};
18
19pub struct GeomWaffle {
22 pub layout: UnitLayout,
23 pub gap: f64,
25 pub roundness: f64,
27 pub fill: (u8, u8, u8),
29 pub alpha: f64,
30}
31
32impl Default for GeomWaffle {
33 fn default() -> Self {
34 GeomWaffle {
35 layout: UnitLayout::Waffle { rows: 10 },
36 gap: 0.12,
37 roundness: 0.0,
38 fill: (97, 156, 255),
39 alpha: 1.0,
40 }
41 }
42}
43
44impl GeomWaffle {
45 pub fn waffle(rows: usize) -> Self {
47 GeomWaffle {
48 layout: UnitLayout::Waffle { rows },
49 ..Default::default()
50 }
51 }
52
53 pub fn parliament(rows: usize) -> Self {
55 GeomWaffle {
56 layout: UnitLayout::Parliament { rows, inner: 0.4 },
57 ..Default::default()
58 }
59 }
60}
61
62impl Geom for GeomWaffle {
63 fn draw(
64 &self,
65 data: &DataFrame,
66 coord: &dyn Coord,
67 scales: &ScaleSet,
68 _theme: &Theme,
69 backend: &mut dyn DrawBackend,
70 ) -> Result<(), RenderError> {
71 let col = |n: &str| {
72 data.column(n)
73 .ok_or_else(|| RenderError::MissingAesthetic(n.into()))
74 };
75 let (xc, yc, uc) = (col("x")?, col("y")?, col("unit")?);
76 let fill_col = data.column("fill");
77 let area = backend.plot_area();
78 let px = |x: f64, y: f64| {
79 let sx = scales.get(&Aesthetic::X).map(|s| s.map(&Value::Float(x)));
80 let sy = scales.get(&Aesthetic::Y).map(|s| s.map(&Value::Float(y)));
81 coord.transform((sx.unwrap_or(0.5), sy.unwrap_or(0.5)), &area)
82 };
83 let unit_px = |x: f64, y: f64, u: f64| {
86 let c = px(x, y);
87 let (a, b) = (px(x + u, y), px(x, y + u));
88 ((a.0 - c.0).hypot(a.1 - c.1)).min((b.0 - c.0).hypot(b.1 - c.1))
89 };
90 let square = matches!(self.layout, UnitLayout::Waffle { .. });
91 for i in 0..data.nrows() {
92 let (Some(x), Some(y), Some(u)) = (xc[i].as_f64(), yc[i].as_f64(), uc[i].as_f64())
93 else {
94 continue;
95 };
96 let c = px(x, y);
97 let r = unit_px(x, y, u) * (1.0 - self.gap);
98 let fill = fill_col
99 .and_then(|f| scales.map_color(&Aesthetic::Fill, &f[i]))
100 .unwrap_or(self.fill);
101 let series = super::series_key(data, i);
102 super::set_mark(backend, series.clone(), None, series, None);
103 super::set_row_extras(backend, data, i);
104 if square {
105 backend.draw_rect(
106 (c.0 - r, c.1 - r),
107 (c.0 + r, c.1 + r),
108 &RectStyle {
109 fill: Some(fill),
110 stroke: None,
111 stroke_width: 0.0,
112 alpha: self.alpha,
113 clip: true,
114 corner_radius: 2.0 * r * self.roundness,
115 },
116 )?;
117 } else {
118 backend.draw_circle(
119 c,
120 r,
121 &PointStyle {
122 color: fill,
123 alpha: self.alpha,
124 filled: true,
125 ..Default::default()
126 },
127 )?;
128 }
129 }
130 super::clear_mark(backend);
131 super::clear_row_extras(backend);
132 Ok(())
133 }
134
135 fn required_aes(&self) -> Vec<Aesthetic> {
136 Vec::new()
137 }
138
139 fn default_stat(&self) -> Box<dyn Stat> {
140 Box::new(StatWaffle {
141 layout: self.layout,
142 })
143 }
144
145 fn default_position(&self) -> Box<dyn Position> {
146 Box::new(PositionIdentity)
147 }
148
149 fn default_params(&self) -> GeomParams {
150 GeomParams::default()
151 }
152
153 fn name(&self) -> &str {
154 "waffle"
155 }
156
157 fn consumes_tooltips(&self) -> bool {
158 true
159 }
160
161 fn set_series_color(&mut self, color: (u8, u8, u8)) {
162 self.fill = color;
163 }
164}