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