1#[derive(Clone, Debug, Default)]
4pub struct GuideLegend {
5 pub title: Option<String>,
7 pub ncol: Option<usize>,
9 pub nrow: Option<usize>,
11 pub reverse: bool,
13 pub byrow: bool,
16 pub max_rows: Option<usize>,
22 pub hidden: Vec<crate::aes::Aesthetic>,
24 pub titles: Vec<(crate::aes::Aesthetic, String)>,
26 pub overrides: Vec<(crate::aes::Aesthetic, KeyOverride)>,
29 pub reversed: Vec<crate::aes::Aesthetic>,
32 pub colourbars: Vec<(crate::aes::Aesthetic, GuideColourbar)>,
35 pub bins: Vec<(crate::aes::Aesthetic, GuideBins)>,
37 pub positions: Vec<(crate::aes::Aesthetic, crate::theme::LegendPosition)>,
41 pub custom: Vec<GuideCustom>,
44 pub plain_keys: Vec<crate::aes::Aesthetic>,
47 #[doc(hidden)]
50 pub layer_keys: Vec<crate::guide::key::LayerKey>,
51}
52
53impl GuideLegend {
54 pub fn new() -> Self {
55 Self::default()
56 }
57
58 pub(crate) fn colourbar_for(&self, aes: &crate::aes::Aesthetic) -> GuideColourbar {
60 self.colourbars
61 .iter()
62 .find(|(a, _)| a == aes)
63 .map(|(_, c)| c.clone())
64 .unwrap_or_default()
65 }
66
67 pub(crate) fn bins_for(
71 &self,
72 aes: &crate::aes::Aesthetic,
73 scale: &dyn crate::scale::Scale,
74 ) -> Option<GuideBins> {
75 scale.bin_edge_labels()?;
76 if self.plain_keys.contains(aes) {
77 return None;
78 }
79 Some(
80 self.bins
81 .iter()
82 .find(|(a, _)| a == aes)
83 .map(|(_, b)| b.clone())
84 .unwrap_or_default(),
85 )
86 }
87
88 pub(crate) fn position_for<'a>(
90 &'a self,
91 aes: &crate::aes::Aesthetic,
92 main: &'a crate::theme::LegendPosition,
93 ) -> &'a crate::theme::LegendPosition {
94 self.positions
95 .iter()
96 .find(|(a, _)| a == aes)
97 .map_or(main, |(_, p)| p)
98 }
99
100 pub(crate) fn is_reversed(&self, aes: &crate::aes::Aesthetic) -> bool {
102 self.reverse != self.reversed.contains(aes)
103 }
104
105 pub fn with_title(mut self, title: &str) -> Self {
106 self.title = Some(title.to_string());
107 self
108 }
109
110 pub fn with_ncol(mut self, ncol: usize) -> Self {
111 self.ncol = Some(ncol);
112 self
113 }
114
115 pub fn with_nrow(mut self, nrow: usize) -> Self {
116 self.nrow = Some(nrow);
117 self
118 }
119
120 pub fn reverse(mut self) -> Self {
121 self.reverse = true;
122 self
123 }
124
125 pub fn with_byrow(mut self, byrow: bool) -> Self {
128 self.byrow = byrow;
129 self
130 }
131
132 pub fn with_max_rows(mut self, max_rows: usize) -> Self {
134 self.max_rows = Some(max_rows);
135 self
136 }
137
138 pub(crate) fn validated(&self, n: usize, warnings: &mut Vec<String>) -> GuideLegend {
144 let mut out = self.clone();
145
146 if out.nrow == Some(0) {
147 warnings.push("guide_legend: nrow(0) is invalid; using the default".to_string());
148 out.nrow = None;
149 }
150 if out.ncol == Some(0) {
151 warnings.push("guide_legend: ncol(0) is invalid; using the default".to_string());
152 out.ncol = None;
153 }
154 if let (Some(nrow), Some(ncol)) = (out.nrow, out.ncol) {
155 if nrow.saturating_mul(ncol) < n {
156 warnings.push(format!(
157 "guide_legend: nrow({nrow}) x ncol({ncol}) = {} is smaller than {n} keys; dropping ncol",
158 nrow.saturating_mul(ncol)
159 ));
160 out.ncol = None;
161 }
162 }
163 if out.max_rows == Some(0) {
164 warnings.push("guide_legend: max_rows(0) is invalid; using 1".to_string());
165 out.max_rows = Some(1);
166 }
167 out
168 }
169}
170
171pub type CustomGuideDraw = std::sync::Arc<
173 dyn Fn(
174 &mut dyn crate::render::backend::DrawBackend,
175 &crate::render::Rect,
176 ) -> Result<(), crate::render::RenderError>
177 + Send
178 + Sync,
179>;
180
181#[derive(Clone)]
187pub struct GuideCustom {
188 pub title: Option<String>,
189 pub width: f64,
190 pub height: f64,
191 pub draw: CustomGuideDraw,
192}
193
194impl GuideCustom {
195 pub fn new<F>(width: f64, height: f64, draw: F) -> Self
197 where
198 F: Fn(
199 &mut dyn crate::render::backend::DrawBackend,
200 &crate::render::Rect,
201 ) -> Result<(), crate::render::RenderError>
202 + Send
203 + Sync
204 + 'static,
205 {
206 GuideCustom {
207 title: None,
208 width: width.max(0.0),
209 height: height.max(0.0),
210 draw: std::sync::Arc::new(draw),
211 }
212 }
213
214 pub fn with_title(mut self, title: &str) -> Self {
216 self.title = Some(title.to_string());
217 self
218 }
219
220 pub(crate) fn title_offset(&self, theme: &crate::theme::Theme) -> f64 {
222 match &self.title {
223 Some(t) if !t.is_empty() => theme.legend_title.size + 4.0,
224 _ => 0.0,
225 }
226 }
227}
228
229impl std::fmt::Debug for GuideCustom {
230 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
231 f.debug_struct("GuideCustom")
232 .field("title", &self.title)
233 .field("width", &self.width)
234 .field("height", &self.height)
235 .finish_non_exhaustive()
236 }
237}
238
239#[derive(Clone, Debug, PartialEq)]
243pub struct GuideBins {
244 pub show_limits: bool,
246 pub axis: bool,
248}
249
250impl Default for GuideBins {
251 fn default() -> Self {
252 GuideBins {
253 show_limits: false,
254 axis: true,
255 }
256 }
257}
258
259impl GuideBins {
260 pub fn new() -> Self {
261 Self::default()
262 }
263
264 pub fn with_show_limits(mut self, on: bool) -> Self {
266 self.show_limits = on;
267 self
268 }
269
270 pub fn with_axis(mut self, on: bool) -> Self {
272 self.axis = on;
273 self
274 }
275}
276
277#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
279pub enum ColourbarDisplay {
280 #[default]
282 Gradient,
283 Rectangles,
285}
286
287#[derive(Clone, Debug, PartialEq)]
291pub struct GuideColourbar {
292 pub bar_width: Option<f64>,
295 pub bar_height: Option<f64>,
297 pub nbin: usize,
299 pub display: ColourbarDisplay,
301 pub frame: Option<bool>,
304 pub ticks: bool,
306 pub steps: bool,
308 pub even_steps: bool,
311 pub show_limits: bool,
313}
314
315impl Default for GuideColourbar {
316 fn default() -> Self {
317 GuideColourbar {
318 bar_width: None,
319 bar_height: None,
320 nbin: 50,
321 display: ColourbarDisplay::Gradient,
322 frame: None,
323 ticks: true,
324 steps: false,
325 even_steps: true,
326 show_limits: false,
327 }
328 }
329}
330
331impl GuideColourbar {
332 pub fn new() -> Self {
334 Self::default()
335 }
336
337 pub fn steps() -> Self {
339 GuideColourbar {
340 steps: true,
341 ticks: false,
342 ..Self::default()
343 }
344 }
345
346 pub fn with_bar_width(mut self, px: f64) -> Self {
347 self.bar_width = Some(px);
348 self
349 }
350
351 pub fn with_bar_height(mut self, px: f64) -> Self {
352 self.bar_height = Some(px);
353 self
354 }
355
356 pub fn with_nbin(mut self, nbin: usize) -> Self {
357 self.nbin = nbin.max(1);
358 self
359 }
360
361 pub fn with_display(mut self, display: ColourbarDisplay) -> Self {
362 self.display = display;
363 self
364 }
365
366 pub fn with_frame(mut self, frame: bool) -> Self {
367 self.frame = Some(frame);
368 self
369 }
370
371 pub fn with_ticks(mut self, ticks: bool) -> Self {
372 self.ticks = ticks;
373 self
374 }
375
376 pub fn with_even_steps(mut self, even: bool) -> Self {
377 self.even_steps = even;
378 self
379 }
380
381 pub fn with_show_limits(mut self, show: bool) -> Self {
382 self.show_limits = show;
383 self
384 }
385}
386
387#[derive(Clone, Debug, Default, PartialEq)]
392pub struct KeyOverride {
393 pub color: Option<(u8, u8, u8)>,
394 pub fill: Option<(u8, u8, u8)>,
395 pub shape: Option<crate::render::backend::PointShape>,
396 pub linetype: Option<crate::render::backend::Linetype>,
397 pub size: Option<f64>,
398 pub alpha: Option<f64>,
399 pub linewidth: Option<f64>,
400}
401
402impl KeyOverride {
403 pub fn new() -> Self {
404 Self::default()
405 }
406 pub fn color(mut self, c: (u8, u8, u8)) -> Self {
407 self.color = Some(c);
408 self
409 }
410 pub fn fill(mut self, c: (u8, u8, u8)) -> Self {
411 self.fill = Some(c);
412 self
413 }
414 pub fn shape(mut self, s: crate::render::backend::PointShape) -> Self {
415 self.shape = Some(s);
416 self
417 }
418 pub fn linetype(mut self, l: crate::render::backend::Linetype) -> Self {
419 self.linetype = Some(l);
420 self
421 }
422 pub fn size(mut self, v: f64) -> Self {
423 self.size = Some(v);
424 self
425 }
426 pub fn alpha(mut self, v: f64) -> Self {
427 self.alpha = Some(v.clamp(0.0, 1.0));
428 self
429 }
430 pub fn linewidth(mut self, v: f64) -> Self {
431 self.linewidth = Some(v);
432 self
433 }
434}