Skip to main content

ggplot_rs/
annotate.rs

1/// Annotation types for adding static marks to a plot.
2#[derive(Clone, Debug)]
3pub enum Annotation {
4    /// Text annotation at a data position.
5    Text {
6        label: String,
7        x: f64,
8        y: f64,
9        size: f64,
10        color: (u8, u8, u8),
11    },
12    /// Rectangle annotation between two data positions.
13    Rect {
14        xmin: f64,
15        xmax: f64,
16        ymin: f64,
17        ymax: f64,
18        fill: (u8, u8, u8),
19        alpha: f64,
20    },
21    /// Line segment annotation between two data positions.
22    Segment {
23        x: f64,
24        y: f64,
25        xend: f64,
26        yend: f64,
27        color: (u8, u8, u8),
28        width: f64,
29    },
30    /// Log-10 tick marks on panel edges (ggplot2's `annotation_logticks`):
31    /// long ticks at each power of ten, mid at 5×, short at 2–4× and 6–9×.
32    /// Positions are read in the axis' (log10-)transformed units, so it suits
33    /// `scale_x_log10()` / `scale_y_log10()` or data already in log10.
34    LogTicks {
35        /// Panel sides to draw on: any of `b`, `l`, `t`, `r`.
36        sides: String,
37        /// Draw outward from the panel instead of inward.
38        outside: bool,
39        /// Tick lengths in px (short, mid, long).
40        lengths: (f64, f64, f64),
41        color: (u8, u8, u8),
42        width: f64,
43    },
44    /// An encoded image (PNG or JPEG bytes) stretched over a data rectangle
45    /// (ggplot2's `annotation_raster`). `±INFINITY` bounds mean the panel edge.
46    Raster {
47        image: std::sync::Arc<Vec<u8>>,
48        xmin: f64,
49        xmax: f64,
50        ymin: f64,
51        ymax: f64,
52        alpha: f64,
53    },
54    /// A whole plot drawn into a data rectangle (ggplot2's `annotation_custom`
55    /// with a ggplot grob — an inset). `±INFINITY` bounds mean the panel edge.
56    Inset {
57        plot: InsetPlot,
58        xmin: f64,
59        xmax: f64,
60        ymin: f64,
61        ymax: f64,
62    },
63}
64
65/// A plot built for drawing as an [`Annotation::Inset`]: built once when the
66/// annotation is created (a `GGPlot` itself can't be cloned), laid out for
67/// its rectangle at draw time.
68#[derive(Clone)]
69pub struct InsetPlot(
70    pub(crate)  std::sync::Arc<
71        std::sync::Mutex<Result<(crate::build::BuiltPlot, crate::plot::RenderMeta), String>>,
72    >,
73);
74
75impl std::fmt::Debug for InsetPlot {
76    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77        f.write_str("InsetPlot")
78    }
79}
80
81impl InsetPlot {
82    pub fn new(plot: crate::GGPlot) -> Self {
83        InsetPlot(std::sync::Arc::new(std::sync::Mutex::new(
84            plot.build_for_render().map_err(|e| format!("{e:?}")),
85        )))
86    }
87}
88
89impl Annotation {
90    /// Create a text annotation.
91    pub fn text(label: &str, x: f64, y: f64) -> Self {
92        Annotation::Text {
93            label: label.to_string(),
94            x,
95            y,
96            size: 12.0,
97            color: (50, 50, 50),
98        }
99    }
100
101    /// Create a rectangle annotation.
102    pub fn rect(xmin: f64, xmax: f64, ymin: f64, ymax: f64) -> Self {
103        Annotation::Rect {
104            xmin,
105            xmax,
106            ymin,
107            ymax,
108            fill: (200, 200, 200),
109            alpha: 0.3,
110        }
111    }
112
113    /// Log-10 tick marks on `sides` (`"bl"` by default in ggplot2).
114    pub fn logticks(sides: &str) -> Self {
115        Annotation::LogTicks {
116            sides: sides.to_string(),
117            outside: false,
118            // ggplot2: 0.1 / 0.2 / 0.3 cm.
119            lengths: (3.8, 7.6, 11.3),
120            color: (50, 50, 50),
121            width: 0.5,
122        }
123    }
124
125    /// `outside = TRUE` for [`Annotation::LogTicks`] (no-op otherwise).
126    pub fn outside(mut self) -> Self {
127        if let Annotation::LogTicks { outside, .. } = &mut self {
128            *outside = true;
129        }
130        self
131    }
132
133    /// An encoded PNG / JPEG image over the data rectangle.
134    pub fn raster(image: Vec<u8>, xmin: f64, xmax: f64, ymin: f64, ymax: f64) -> Self {
135        Annotation::Raster {
136            image: std::sync::Arc::new(image),
137            xmin,
138            xmax,
139            ymin,
140            ymax,
141            alpha: 1.0,
142        }
143    }
144
145    /// `plot` drawn as an inset into the data rectangle.
146    pub fn inset(plot: crate::GGPlot, xmin: f64, xmax: f64, ymin: f64, ymax: f64) -> Self {
147        Annotation::Inset {
148            plot: InsetPlot::new(plot),
149            xmin,
150            xmax,
151            ymin,
152            ymax,
153        }
154    }
155
156    /// Create a segment annotation.
157    pub fn segment(x: f64, y: f64, xend: f64, yend: f64) -> Self {
158        Annotation::Segment {
159            x,
160            y,
161            xend,
162            yend,
163            color: (50, 50, 50),
164            width: 1.0,
165        }
166    }
167}