Skip to main content

ggplot_rs/guide/
key.rs

1//! Legend key glyphs (#410): ggplot2's `draw_key_*` — which picture a
2//! layer contributes to a legend key — chosen per geom (`Geom::key_glyph`)
3//! or per layer (`GGPlot::key_glyph`), and several layers drawn into one key.
4
5use crate::aes::Aesthetic;
6use crate::render::backend::{
7    DrawBackend, LineStyle, Linetype, PointShape, PointStyle, RectStyle, TextAnchor, TextStyle,
8};
9use crate::render::RenderError;
10
11/// The picture a layer draws in a legend key (ggplot2's `key_glyph =`).
12#[derive(Clone, Copy, Debug, PartialEq, Eq)]
13pub enum KeyGlyph {
14    /// A point (`draw_key_point`).
15    Point,
16    /// A horizontal line (`draw_key_path`).
17    Path,
18    /// A filled square (`draw_key_rect`).
19    Rect,
20    /// A filled square with an outline (`draw_key_polygon`).
21    Polygon,
22    /// A small boxplot (`draw_key_boxplot`).
23    Boxplot,
24    /// A crossbar (`draw_key_crossbar`).
25    Crossbar,
26    /// A vertical line with a point (`draw_key_pointrange`).
27    Pointrange,
28    /// A vertical line (`draw_key_linerange`).
29    Linerange,
30    /// A line over a light ribbon (`draw_key_smooth`).
31    Smooth,
32    /// The letter "a" (`draw_key_text`).
33    Text,
34    /// "a" on a box (`draw_key_label`).
35    Label,
36    /// A vertical line (`draw_key_vline`).
37    Vline,
38    /// A vertical path (`draw_key_vpath`).
39    Vpath,
40    /// A diagonal line (`draw_key_abline`).
41    Abline,
42    /// A zig-zag line (`draw_key_timeseries`).
43    Timeseries,
44    /// A filled dot (`draw_key_dotplot`).
45    Dotplot,
46    /// Nothing (`draw_key_blank`).
47    Blank,
48}
49
50impl KeyGlyph {
51    /// Parse ggplot2's glyph names (`"point"`, `"path"`, `"boxplot"`, …).
52    pub fn parse(s: &str) -> Option<Self> {
53        Some(match s {
54            "point" => KeyGlyph::Point,
55            "path" => KeyGlyph::Path,
56            "rect" => KeyGlyph::Rect,
57            "polygon" => KeyGlyph::Polygon,
58            "boxplot" => KeyGlyph::Boxplot,
59            "crossbar" => KeyGlyph::Crossbar,
60            "pointrange" => KeyGlyph::Pointrange,
61            "linerange" => KeyGlyph::Linerange,
62            "smooth" => KeyGlyph::Smooth,
63            "text" => KeyGlyph::Text,
64            "label" => KeyGlyph::Label,
65            "vline" => KeyGlyph::Vline,
66            "vpath" => KeyGlyph::Vpath,
67            "abline" => KeyGlyph::Abline,
68            "timeseries" => KeyGlyph::Timeseries,
69            "dotplot" => KeyGlyph::Dotplot,
70            "blank" => KeyGlyph::Blank,
71            _ => return None,
72        })
73    }
74}
75
76/// One layer's contribution to the legends: its glyph (`None` = the
77/// pre-#410 per-aesthetic swatch) and the legend aesthetics it maps and shows.
78#[derive(Clone, Debug, PartialEq)]
79pub struct LayerKey {
80    pub glyph: Option<KeyGlyph>,
81    pub aesthetics: Vec<Aesthetic>,
82    /// The layer's constant fill and alpha ([`Geom::key_fill`](crate::geom::Geom::key_fill), #473).
83    pub fill: Option<((u8, u8, u8), f64)>,
84}
85
86/// The mapped values a key is drawn with (each `None` = not mapped in this
87/// legend → the glyph's default).
88#[derive(Clone, Debug, Default)]
89pub(crate) struct KeyProps {
90    pub color: Option<(u8, u8, u8)>,
91    pub fill: Option<(u8, u8, u8)>,
92    pub shape: Option<PointShape>,
93    pub linetype: Option<Linetype>,
94    pub size: Option<f64>,
95    pub alpha: Option<f64>,
96    pub linewidth: Option<f64>,
97}
98
99/// Draw `glyph` into the key square at `(x, y)` of side `s`.
100pub(crate) fn draw_key(
101    backend: &mut dyn DrawBackend,
102    glyph: KeyGlyph,
103    props: &KeyProps,
104    ink: (u8, u8, u8),
105    (x, y, s): (f64, f64, f64),
106) -> Result<(), RenderError> {
107    let color = props.color.unwrap_or(ink);
108    // A rect key is coloured by fill, else colour; a polygon key fills with
109    // its fill (default grey35, as ggplot2's bars) and outlines in colour.
110    let fill = props.fill.or(props.color).unwrap_or((89, 89, 89));
111    let poly_fill = props.fill.unwrap_or((89, 89, 89));
112    let alpha = props.alpha.unwrap_or(1.0);
113    let lw = props.linewidth.unwrap_or(1.0).min(s);
114    let lt = props.linetype.clone().unwrap_or(Linetype::Solid);
115    let line = |w: f64| LineStyle {
116        color,
117        width: w,
118        alpha,
119        linetype: lt.clone(),
120        ..Default::default()
121    };
122    let (cx, cy) = (x + s / 2.0, y + s / 2.0);
123    let at = |fx: f64, fy: f64| (x + fx * s, y + fy * s);
124    let rect = |fill: Option<(u8, u8, u8)>, stroke: Option<(u8, u8, u8)>| RectStyle {
125        fill,
126        stroke,
127        stroke_width: lw.max(0.5),
128        alpha,
129        clip: false,
130        corner_radius: 0.0,
131    };
132    let point = |backend: &mut dyn DrawBackend, r: f64| {
133        let shape = props.shape.unwrap_or(PointShape::Circle);
134        backend.draw_shape(
135            (cx, cy),
136            r,
137            &PointStyle {
138                color,
139                alpha,
140                filled: true,
141                shape,
142            },
143        )
144    };
145    match glyph {
146        KeyGlyph::Point => point(backend, props.size.unwrap_or(s / 5.0).min(s / 2.0))?,
147        KeyGlyph::Dotplot => point(backend, s / 3.0)?,
148        KeyGlyph::Path => backend.draw_line(&[at(0.1, 0.5), at(0.9, 0.5)], &line(lw))?,
149        KeyGlyph::Vline | KeyGlyph::Linerange | KeyGlyph::Vpath => {
150            backend.draw_line(&[at(0.5, 0.1), at(0.5, 0.9)], &line(lw))?
151        }
152        KeyGlyph::Abline => backend.draw_line(&[at(0.0, 1.0), at(1.0, 0.0)], &line(lw))?,
153        KeyGlyph::Timeseries => backend.draw_line(
154            &[at(0.0, 0.9), at(0.4, 0.1), at(0.6, 0.6), at(1.0, 0.1)],
155            &line(lw),
156        )?,
157        KeyGlyph::Rect => backend.draw_rect(at(0.0, 0.0), at(1.0, 1.0), &rect(Some(fill), None))?,
158        KeyGlyph::Polygon => backend.draw_rect(
159            at(0.0, 0.0),
160            at(1.0, 1.0),
161            &rect(Some(poly_fill), props.color),
162        )?,
163        KeyGlyph::Smooth => {
164            let band = props.fill.or(props.color).unwrap_or((153, 153, 153));
165            backend.draw_rect(
166                at(0.0, 0.0),
167                at(1.0, 1.0),
168                &RectStyle {
169                    alpha: 0.4,
170                    ..rect(Some(band), None)
171                },
172            )?;
173            backend.draw_line(&[at(0.1, 0.5), at(0.9, 0.5)], &line(lw.max(1.0)))?;
174        }
175        KeyGlyph::Boxplot => {
176            let fill = props.fill.unwrap_or((255, 255, 255));
177            backend.draw_line(&[at(0.5, 0.1), at(0.5, 0.25)], &line(0.5))?;
178            backend.draw_line(&[at(0.5, 0.75), at(0.5, 0.9)], &line(0.5))?;
179            backend.draw_rect(
180                at(0.125, 0.25),
181                at(0.875, 0.75),
182                &rect(Some(fill), Some(color)),
183            )?;
184            backend.draw_line(&[at(0.125, 0.5), at(0.875, 0.5)], &line(1.0))?;
185        }
186        KeyGlyph::Crossbar => {
187            let fill = props.fill.unwrap_or((255, 255, 255));
188            backend.draw_rect(
189                at(0.125, 0.25),
190                at(0.875, 0.75),
191                &rect(Some(fill), Some(color)),
192            )?;
193            backend.draw_line(&[at(0.125, 0.5), at(0.875, 0.5)], &line(1.0))?;
194        }
195        KeyGlyph::Pointrange => {
196            backend.draw_line(&[at(0.5, 0.1), at(0.5, 0.9)], &line(lw))?;
197            point(backend, props.size.unwrap_or(s / 6.0).min(s / 3.0))?;
198        }
199        KeyGlyph::Text | KeyGlyph::Label => {
200            if glyph == KeyGlyph::Label {
201                backend.draw_rect(
202                    at(0.1, 0.15),
203                    at(0.9, 0.85),
204                    &RectStyle {
205                        fill: Some((255, 255, 255)),
206                        stroke: Some(color),
207                        stroke_width: 0.5,
208                        alpha,
209                        clip: false,
210                        corner_radius: 1.5,
211                    },
212                )?;
213            }
214            backend.draw_text(
215                "a",
216                (cx, cy + s * 0.18),
217                &TextStyle {
218                    color,
219                    size: s * 0.6,
220                    anchor: TextAnchor::Middle,
221                    ..Default::default()
222                },
223            )?;
224        }
225        KeyGlyph::Blank => {}
226    }
227    Ok(())
228}