1use crate::aes::Aesthetic;
7use crate::coord::Coord;
8use crate::data::{DataFrame, Value};
9use crate::position::identity::PositionIdentity;
10use crate::position::Position;
11use crate::render::backend::{DrawBackend, FontFace, RectStyle, TextAnchor, TextStyle};
12use crate::render::RenderError;
13use crate::scale::ScaleSet;
14use crate::stat::marimekko::StatMarimekko;
15use crate::stat::Stat;
16use crate::theme::Theme;
17
18use super::treemap::{contrast_text, fit_size};
19use super::{Geom, GeomParams};
20
21pub struct GeomMarimekko {
24 pub mosaic: bool,
26 pub gap: f64,
28 pub fill: (u8, u8, u8),
30 pub border: (u8, u8, u8),
31 pub alpha: f64,
32 pub labels: bool,
34 pub label_size: f64,
35}
36
37impl GeomMarimekko {
38 pub fn variwide() -> Self {
40 GeomMarimekko {
41 mosaic: false,
42 gap: 0.0,
43 fill: (97, 156, 255),
44 border: (255, 255, 255),
45 alpha: 1.0,
46 labels: false,
47 label_size: 9.0,
48 }
49 }
50
51 pub fn marimekko() -> Self {
53 GeomMarimekko {
54 mosaic: true,
55 gap: 0.006,
56 labels: true,
57 ..Self::variwide()
58 }
59 }
60}
61
62impl Geom for GeomMarimekko {
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 (x0c, x1c, y0c, y1c) = (col("xmin")?, col("xmax")?, col("ymin")?, col("ymax")?);
76 let (column, value, share) = (col("column")?, col("value")?, col("share")?);
77 let (fill_col, y_col) = (data.column("fill"), data.column("y"));
78 let area = backend.plot_area();
79 let m = |aes: Aesthetic, v: &Value| scales.get(&aes).map(|s| s.map(v)).unwrap_or(0.0);
80 let family = super::resolve_font_family("", theme);
81 let pct = |v: &Value| {
82 v.as_f64()
83 .map(|s| {
84 format!(
85 "{}%",
86 super::tip_value(&Value::Float((s * 1000.0).round() / 10.0))
87 )
88 })
89 .unwrap_or_default()
90 };
91 let mut labels = Vec::new();
92 for i in 0..data.nrows() {
93 let a = coord.transform((m(Aesthetic::X, &x0c[i]), m(Aesthetic::Y, &y1c[i])), &area);
94 let b = coord.transform((m(Aesthetic::X, &x1c[i]), m(Aesthetic::Y, &y0c[i])), &area);
95 let ((l, t), (r, btm)) = ((a.0.min(b.0), a.1.min(b.1)), (a.0.max(b.0), a.1.max(b.1)));
96 if ![l, t, r, btm].iter().all(|v| v.is_finite()) {
97 continue;
98 }
99 let fill = fill_col
100 .and_then(|c| scales.map_color(&Aesthetic::Fill, &c[i]))
101 .unwrap_or(self.fill);
102 let name = super::tip_value(&column[i]);
103 let tip = if self.mosaic {
104 let seg = fill_col
105 .map(|c| super::tip_value(&c[i]))
106 .unwrap_or_default();
107 format!(
108 "{name} / {seg}: {} ({})",
109 super::tip_value(&value[i]),
110 pct(&share[i])
111 )
112 } else {
113 format!(
114 "{name}: {} (width {})",
115 y_col.map(|c| super::tip_value(&c[i])).unwrap_or_default(),
116 super::tip_value(&value[i])
117 )
118 };
119 super::set_mark(
120 backend,
121 Some(tip),
122 Some(name),
123 super::series_key(data, i),
124 super::raw_value(&value[i]),
125 );
126 super::set_row_extras(backend, data, i);
127 backend.draw_rect(
128 (l, t),
129 (r, btm),
130 &RectStyle {
131 fill: Some(fill),
132 stroke: Some(self.border),
133 stroke_width: 0.8,
134 alpha: self.alpha,
135 clip: true,
136 corner_radius: 0.0,
137 },
138 )?;
139 if self.labels {
140 let text = pct(&share[i]);
141 if let Some(size) = fit_size(&text, r - l, btm - t, self.label_size, 6.0) {
142 labels.push((
143 text,
144 ((l + r) / 2.0, (t + btm) / 2.0),
145 size,
146 contrast_text(fill),
147 ));
148 }
149 }
150 }
151 super::clear_mark(backend);
152 super::clear_row_extras(backend);
153 for (text, pos, size, color) in labels {
154 backend.draw_text(
155 &text,
156 pos,
157 &TextStyle {
158 color,
159 size,
160 anchor: TextAnchor::Middle,
161 angle: 0.0,
162 family: family.clone(),
163 face: FontFace::Plain,
164 bg_colour: None,
165 bg_r: 0.0,
166 },
167 )?;
168 }
169 Ok(())
170 }
171
172 fn required_aes(&self) -> Vec<Aesthetic> {
173 vec![Aesthetic::X]
174 }
175
176 fn default_stat(&self) -> Box<dyn Stat> {
177 Box::new(StatMarimekko {
178 mosaic: self.mosaic,
179 gap: self.gap,
180 })
181 }
182
183 fn default_position(&self) -> Box<dyn Position> {
184 Box::new(PositionIdentity)
185 }
186
187 fn default_params(&self) -> GeomParams {
188 GeomParams::default()
189 }
190
191 fn name(&self) -> &str {
192 if self.mosaic {
193 "marimekko"
194 } else {
195 "variwide"
196 }
197 }
198
199 fn consumes_tooltips(&self) -> bool {
200 true
201 }
202
203 fn include_zero_baseline(&self) -> bool {
204 true
205 }
206
207 fn apply_element_geom(&mut self, el: &crate::theme::elements::ElementGeom) {
208 if let (true, Some(paper)) = (self.border == (255, 255, 255), el.paper) {
209 self.border = paper;
210 }
211 }
212
213 fn set_series_color(&mut self, color: (u8, u8, u8)) {
214 self.fill = color;
215 }
216}