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::partition::StatPartition;
15use crate::stat::Stat;
16use crate::theme::Theme;
17
18use super::treemap::{contrast_text, fit_size};
19use super::{Geom, GeomParams};
20
21pub struct GeomPartition {
25 pub levels: Vec<String>,
27 pub hole: f64,
30 pub sort: bool,
32 pub downward: bool,
34 pub fill: (u8, u8, u8),
36 pub lighten_per_level: f64,
38 pub border: (u8, u8, u8),
39 pub border_width: f64,
40 pub alpha: f64,
41 pub label_size: f64,
43 pub min_label_size: f64,
44}
45
46impl GeomPartition {
47 pub fn sunburst<S: Into<String>>(levels: impl IntoIterator<Item = S>) -> Self {
50 GeomPartition {
51 levels: levels.into_iter().map(Into::into).collect(),
52 hole: 0.6,
53 sort: true,
54 downward: false,
55 fill: (97, 156, 255),
56 lighten_per_level: 0.18,
57 border: (255, 255, 255),
58 border_width: 1.0,
59 alpha: 1.0,
60 label_size: 10.0,
61 min_label_size: 6.5,
62 }
63 }
64
65 pub fn icicle<S: Into<String>>(levels: impl IntoIterator<Item = S>) -> Self {
67 GeomPartition {
68 hole: 0.0,
69 downward: true,
70 ..Self::sunburst(levels)
71 }
72 }
73}
74
75fn lighten(c: (u8, u8, u8), t: f64) -> (u8, u8, u8) {
76 let t = t.clamp(0.0, 1.0);
77 let m = |v: u8| (v as f64 + (255.0 - v as f64) * t).round() as u8;
78 (m(c.0), m(c.1), m(c.2))
79}
80
81impl Geom for GeomPartition {
82 fn draw(
83 &self,
84 data: &DataFrame,
85 coord: &dyn Coord,
86 scales: &ScaleSet,
87 theme: &Theme,
88 backend: &mut dyn DrawBackend,
89 ) -> Result<(), RenderError> {
90 let col = |n: &str| {
91 data.column(n)
92 .ok_or_else(|| RenderError::MissingAesthetic(n.into()))
93 };
94 let (x0c, x1c, y0c, y1c) = (col("xmin")?, col("xmax")?, col("ymin")?, col("ymax")?);
95 let (node, path, value) = (col("node")?, col("path")?, col("value")?);
96 let (depth, share) = (data.column("depth"), data.column("share"));
97 let fill_col = data.column("fill");
98 let area = backend.plot_area();
99 let nx = |v: &Value| scales.get(&Aesthetic::X).map(|s| s.map(v)).unwrap_or(0.0);
100 let ny = |v: &Value| scales.get(&Aesthetic::Y).map(|s| s.map(v)).unwrap_or(0.0);
101 let dist = |a: (f64, f64), b: (f64, f64)| (a.0 - b.0).hypot(a.1 - b.1);
102 let family = super::resolve_font_family("", theme);
103
104 let mut labels = Vec::new();
105 for i in 0..data.nrows() {
106 let (x0, x1, y0, y1) = (nx(&x0c[i]), nx(&x1c[i]), ny(&y0c[i]), ny(&y1c[i]));
107 if ![x0, x1, y0, y1].iter().all(|v| v.is_finite()) || x1 <= x0 {
108 continue;
109 }
110 let base = fill_col
111 .and_then(|c| scales.map_color(&Aesthetic::Fill, &c[i]))
112 .unwrap_or(self.fill);
113 let d = depth.and_then(|c| c[i].as_f64()).unwrap_or(0.0);
114 let fill = lighten(base, d * self.lighten_per_level);
115 let pct = share
116 .and_then(|c| c[i].as_f64())
117 .map(|s| {
118 format!(
119 " ({}%)",
120 super::tip_value(&Value::Float((s * 1000.0).round() / 10.0))
121 )
122 })
123 .unwrap_or_default();
124 super::set_mark(
125 backend,
126 Some(format!(
127 "{}: {}{pct}",
128 super::tip_value(&path[i]),
129 super::tip_value(&value[i])
130 )),
131 Some(super::tip_value(&path[i])),
132 super::series_key(data, i),
133 super::raw_value(&value[i]),
134 );
135 let style = RectStyle {
136 fill: Some(fill),
137 stroke: Some(self.border),
138 stroke_width: self.border_width,
139 alpha: self.alpha,
140 clip: !coord.is_polar(),
141 corner_radius: 0.0,
142 };
143 if coord.is_polar() {
144 let pts = super::col::polar_sector(coord, &area, x0, x1, y0, y1);
145 backend.draw_polygon(&pts, &style)?;
146 } else {
147 let (a, b) = (
148 coord.transform((x0, y1), &area),
149 coord.transform((x1, y0), &area),
150 );
151 backend.draw_rect(
152 (a.0.min(b.0), a.1.min(b.1)),
153 (a.0.max(b.0), a.1.max(b.1)),
154 &style,
155 )?;
156 }
157 if self.label_size > 0.0 {
158 let (xm, ym) = ((x0 + x1) / 2.0, (y0 + y1) / 2.0);
159 let centre = coord.transform((xm, ym), &area);
160 let along = dist(
162 coord.transform((x0, ym), &area),
163 coord.transform((x1, ym), &area),
164 );
165 let across = dist(
166 coord.transform((xm, y0), &area),
167 coord.transform((xm, y1), &area),
168 );
169 let name = super::tip_value(&node[i]);
170 if let Some(size) =
171 fit_size(&name, along, across, self.label_size, self.min_label_size)
172 {
173 labels.push((name, centre, size, contrast_text(fill)));
174 }
175 }
176 }
177 super::clear_mark(backend);
178 for (text, pos, size, color) in labels {
179 backend.draw_text(
180 &text,
181 pos,
182 &TextStyle {
183 color,
184 size,
185 anchor: TextAnchor::Middle,
186 angle: 0.0,
187 family: family.clone(),
188 face: FontFace::Plain,
189 bg_colour: None,
190 bg_r: 0.0,
191 },
192 )?;
193 }
194 Ok(())
195 }
196
197 fn required_aes(&self) -> Vec<Aesthetic> {
198 Vec::new()
199 }
200
201 fn default_stat(&self) -> Box<dyn Stat> {
202 Box::new(StatPartition {
203 levels: self.levels.clone(),
204 sort: self.sort,
205 downward: self.downward,
206 })
207 }
208
209 fn default_position(&self) -> Box<dyn Position> {
210 Box::new(PositionIdentity)
211 }
212
213 fn default_params(&self) -> GeomParams {
214 GeomParams::default()
215 }
216
217 fn name(&self) -> &str {
218 "partition"
219 }
220
221 fn apply_element_geom(&mut self, el: &crate::theme::elements::ElementGeom) {
222 if let (true, Some(paper)) = (self.border == (255, 255, 255), el.paper) {
223 self.border = paper;
224 }
225 }
226}