Skip to main content

ggplot_rs/geom/
venn.rs

1//! Venn / Euler diagrams: `geom_venn`. Translucent circles with area ∝
2//! set size, overlapping by the pairwise intersections; set names and
3//! intersection sizes labelled inside their regions. Layout by [`StatVenn`].
4
5use std::f64::consts::TAU;
6
7use crate::aes::Aesthetic;
8use crate::coord::Coord;
9use crate::data::{DataFrame, Value};
10use crate::position::identity::PositionIdentity;
11use crate::position::Position;
12use crate::render::backend::{
13    DrawBackend, FontFace, LineStyle, Linetype, RectStyle, TextAnchor, TextStyle,
14};
15use crate::render::RenderError;
16use crate::scale::ScaleSet;
17use crate::stat::venn::StatVenn;
18use crate::stat::Stat;
19use crate::theme::Theme;
20
21use super::{Geom, GeomParams};
22
23/// Venn diagram. Map `sets` (`"A"`, `"A&B"`, …) and `weight` (size).
24pub struct GeomVenn {
25    /// Fill when no fill scale applies.
26    pub fill: (u8, u8, u8),
27    pub alpha: f64,
28    pub outline_width: f64,
29    pub label_size: f64,
30    pub label_color: (u8, u8, u8),
31}
32
33impl Default for GeomVenn {
34    fn default() -> Self {
35        GeomVenn {
36            fill: (97, 156, 255),
37            alpha: 0.3,
38            outline_width: 1.5,
39            label_size: 11.0,
40            label_color: (30, 30, 30),
41        }
42    }
43}
44
45impl Geom for GeomVenn {
46    fn draw(
47        &self,
48        data: &DataFrame,
49        coord: &dyn Coord,
50        scales: &ScaleSet,
51        theme: &Theme,
52        backend: &mut dyn DrawBackend,
53    ) -> Result<(), RenderError> {
54        let col = |n: &str| {
55            data.column(n)
56                .ok_or_else(|| RenderError::MissingAesthetic(n.into()))
57        };
58        let (part, xc, yc, rc) = (col("part")?, col("x")?, col("y")?, col("r")?);
59        let (set, text, size) = (col("set")?, col("text")?, col("size")?);
60        let fill_col = data.column("fill");
61        let area = backend.plot_area();
62        let px = |x: f64, y: f64| {
63            let sx = scales.get(&Aesthetic::X).map(|s| s.map(&Value::Float(x)));
64            let sy = scales.get(&Aesthetic::Y).map(|s| s.map(&Value::Float(y)));
65            coord.transform((sx.unwrap_or(0.5), sy.unwrap_or(0.5)), &area)
66        };
67        for i in (0..data.nrows()).filter(|&i| part[i].key_str() == "circle") {
68            let (Some(x), Some(y), Some(r)) = (xc[i].as_f64(), yc[i].as_f64(), rc[i].as_f64())
69            else {
70                continue;
71            };
72            let pts: Vec<(f64, f64)> = (0..=96)
73                .map(|k| {
74                    let a = TAU * k as f64 / 96.0;
75                    px(x + r * a.cos(), y + r * a.sin())
76                })
77                .collect();
78            let fill = fill_col
79                .and_then(|f| scales.map_color(&Aesthetic::Fill, &f[i]))
80                .unwrap_or(self.fill);
81            let name = super::tip_value(&set[i]);
82            super::set_mark(
83                backend,
84                Some(format!("{name}: {}", super::tip_value(&size[i]))),
85                Some(name.clone()),
86                Some(name),
87                super::raw_value(&size[i]),
88            );
89            backend.draw_polygon(
90                &pts,
91                &RectStyle {
92                    fill: Some(fill),
93                    stroke: None,
94                    stroke_width: 0.0,
95                    alpha: self.alpha,
96                    clip: true,
97                    corner_radius: 0.0,
98                },
99            )?;
100            backend.draw_line(
101                &pts,
102                &LineStyle {
103                    color: fill,
104                    alpha: 1.0,
105                    width: self.outline_width,
106                    linetype: Linetype::Solid,
107                    ..Default::default()
108                },
109            )?;
110        }
111        super::clear_mark(backend);
112        let family = super::resolve_font_family("", theme);
113        for i in (0..data.nrows()).filter(|&i| part[i].key_str() == "label") {
114            let (Some(x), Some(y)) = (xc[i].as_f64(), yc[i].as_f64()) else {
115                continue;
116            };
117            backend.draw_text(
118                &super::tip_value(&text[i]),
119                px(x, y),
120                &TextStyle {
121                    color: self.label_color,
122                    size: self.label_size,
123                    anchor: TextAnchor::Middle,
124                    angle: 0.0,
125                    family: family.clone(),
126                    face: FontFace::Plain,
127                    bg_colour: None,
128                    bg_r: 0.0,
129                },
130            )?;
131        }
132        Ok(())
133    }
134
135    fn required_aes(&self) -> Vec<Aesthetic> {
136        vec![Aesthetic::Sets]
137    }
138    fn default_stat(&self) -> Box<dyn Stat> {
139        Box::new(StatVenn)
140    }
141    fn default_position(&self) -> Box<dyn Position> {
142        Box::new(PositionIdentity)
143    }
144    fn default_params(&self) -> GeomParams {
145        GeomParams::default()
146    }
147    fn name(&self) -> &str {
148        "venn"
149    }
150    fn apply_element_geom(&mut self, el: &crate::theme::elements::ElementGeom) {
151        if let Some(ink) = el.ink {
152            self.label_color = ink;
153        }
154    }
155}