plotly/layout/
annotation.rs

1use plotly_derive::FieldSetter;
2use serde::{Serialize, Serializer};
3
4use crate::color::Color;
5use crate::common::{Anchor, Font, Label};
6use crate::layout::{HAlign, VAlign};
7use crate::private::NumOrString;
8
9#[derive(Serialize, Debug, Clone)]
10#[serde(rename_all = "lowercase")]
11pub enum ArrowSide {
12    End,
13    Start,
14    #[serde(rename = "end+start")]
15    StartEnd,
16    None,
17}
18
19#[derive(Debug, Clone)]
20pub enum ClickToShow {
21    False,
22    OnOff,
23    OnOut,
24}
25
26impl Serialize for ClickToShow {
27    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
28    where
29        S: Serializer,
30    {
31        match *self {
32            Self::False => serializer.serialize_bool(false),
33            Self::OnOff => serializer.serialize_str("onoff"),
34            Self::OnOut => serializer.serialize_str("onout"),
35        }
36    }
37}
38
39#[serde_with::skip_serializing_none]
40#[derive(Serialize, Debug, Clone, FieldSetter)]
41pub struct Annotation {
42    /// Determines whether or not this annotation is visible.
43    visible: Option<bool>,
44    /// Sets the text associated with this annotation. Plotly uses a subset of
45    /// HTML tags to do things like newline (<br>), bold (<b></b>), italics
46    /// (<i></i>), hyperlinks (<a href='...'></a>). Tags <em></em>, <sup></sup>,
47    /// <sub></sub> <span></span> are also supported.
48    text: Option<String>,
49    /// Sets the angle at which the `text` is drawn with respect to the
50    /// horizontal.
51    #[serde(rename = "textangle")]
52    text_angle: Option<f64>,
53    /// Sets the annotation text font.
54    font: Option<Font>,
55    /// Sets an explicit width for the text box. null (default) lets the text
56    /// set the box width. Wider text will be clipped. There is no automatic
57    /// wrapping; use <br> to start a new line.
58    width: Option<f64>,
59    /// Sets an explicit height for the text box. null (default) lets the text
60    /// set the box height. Taller text will be clipped.
61    height: Option<f64>,
62    /// Sets the opacity of the annotation (text + arrow).
63    opacity: Option<f64>,
64    /// Sets the horizontal alignment of the `text` within the box. Has an
65    /// effect only if `text` spans two or more lines (i.e. `text` contains
66    /// one or more <br> HTML tags) or if an explicit width is set to
67    /// override the text width.
68    align: Option<HAlign>,
69    /// Sets the vertical alignment of the `text` within the box. Has an effect
70    /// only if an explicit height is set to override the text height.
71    valign: Option<VAlign>,
72    /// Sets the background color of the annotation.
73    #[serde(rename = "bgcolor")]
74    background_color: Option<Box<dyn Color>>,
75    /// Sets the color of the border enclosing the annotation `text`.
76    #[serde(rename = "bordercolor")]
77    border_color: Option<Box<dyn Color>>,
78    /// Sets the padding (in px) between the `text` and the enclosing border.
79    #[serde(rename = "borderpad")]
80    border_pad: Option<f64>,
81    /// Sets the width (in px) of the border enclosing the annotation `text`.
82    #[serde(rename = "borderwidth")]
83    border_width: Option<f64>,
84    /// Determines whether or not the annotation is drawn with an arrow. If
85    /// "True", `text` is placed near the arrow's tail. If "False", `text`
86    /// lines up with the `x` and `y` provided.
87    #[serde(rename = "showarrow")]
88    show_arrow: Option<bool>,
89    /// Sets the color of the annotation arrow.
90    #[serde(rename = "arrowcolor")]
91    arrow_color: Option<Box<dyn Color>>,
92    /// Sets the end annotation arrow head style. Integer between or equal to 0
93    /// and 8.
94    #[serde(rename = "arrowhead")]
95    arrow_head: Option<u8>,
96    /// Sets the start annotation arrow head style. Integer between or equal to
97    /// 0 and 8.
98    #[serde(rename = "startarrowhead")]
99    start_arrow_head: Option<u8>,
100    /// Sets the annotation arrow head position.
101    #[serde(rename = "arrowside")]
102    arrow_side: Option<ArrowSide>,
103    /// Sets the size of the end annotation arrow head, relative to
104    /// `arrowwidth`. A value of 1 (default) gives a head about 3x as wide
105    /// as the line.
106    #[serde(rename = "arrowsize")]
107    arrow_size: Option<f64>,
108    /// Sets the size of the start annotation arrow head, relative to
109    /// `arrowwidth`. A value of 1 (default) gives a head about 3x as wide
110    /// as the line.
111    #[serde(rename = "startarrowsize")]
112    start_arrow_size: Option<f64>,
113    /// Sets the width (in px) of annotation arrow line.
114    #[serde(rename = "arrowwidth")]
115    arrow_width: Option<f64>,
116    /// Sets a distance, in pixels, to move the end arrowhead away from the
117    /// position it is pointing at, for example to point at the edge of a
118    /// marker independent of zoom. Note that this shortens the arrow from
119    /// the `ax` / `ay` vector, in contrast to `xshift` / `yshift` which
120    /// moves everything by this amount.
121    #[serde(rename = "standoff")]
122    stand_off: Option<f64>,
123    /// Sets a distance, in pixels, to move the start arrowhead away from the
124    /// position it is pointing at, for example to point at the edge of a
125    /// marker independent of zoom. Note that this shortens the arrow from
126    /// the `ax` / `ay` vector, in contrast to `xshift` / `yshift`
127    /// which moves everything by this amount.
128    #[serde(rename = "startstandoff")]
129    start_stand_off: Option<f64>,
130    /// Sets the x component of the arrow tail about the arrow head. If `axref`
131    /// is `pixel`, a positive (negative) component corresponds to an arrow
132    /// pointing from right to left (left to right). If `axref` is an axis,
133    /// this is an absolute value on that axis, like `x`, NOT a
134    /// relative value.
135    ax: Option<NumOrString>,
136    /// Sets the y component of the arrow tail about the arrow head. If `ayref`
137    /// is `pixel`, a positive (negative) component corresponds to an arrow
138    /// pointing from bottom to top (top to bottom). If `ayref` is an axis,
139    /// this is an absolute value on that axis, like `y`, NOT a
140    /// relative value.
141    ay: Option<NumOrString>,
142    /// Indicates in what terms the tail of the annotation (ax,ay) is specified.
143    /// If `pixel`, `ax` is a relative offset in pixels from `x`. If set to
144    /// an x axis id (e.g. "x" or "x2"), `ax` is specified in the same terms
145    /// as that axis. This is useful for trendline annotations which
146    /// should continue to indicate the correct trend when zoomed.
147    #[serde(rename = "axref")]
148    ax_ref: Option<String>,
149    /// Indicates in what terms the tail of the annotation (ax,ay) is specified.
150    /// If `pixel`, `ay` is a relative offset in pixels from `y`. If set to
151    /// a y axis id (e.g. "y" or "y2"), `ay` is specified in the same terms
152    /// as that axis. This is useful for trendline annotations which
153    /// should continue to indicate the correct trend when zoomed.
154    #[serde(rename = "ayref")]
155    ay_ref: Option<String>,
156    /// Sets the annotation's x coordinate axis. If set to an x axis id (e.g.
157    /// "x" or "x2"), the `x` position refers to an x coordinate If set to
158    /// "paper", the `x` position refers to the distance from the left side
159    /// of the plotting area in normalized coordinates where 0 (1)
160    /// corresponds to the left (right) side.
161    #[serde(rename = "xref")]
162    x_ref: Option<String>,
163    /// Sets the annotation's x position. If the axis `type` is "log", then you
164    /// must take the log of your desired range. If the axis `type` is
165    /// "date", it should be date strings, like date data, though Date
166    /// objects and unix milliseconds will be accepted and converted to strings.
167    /// If the axis `type` is "category", it should be numbers, using the scale
168    /// where each category is assigned a serial number from zero in the
169    /// order it appears.
170    x: Option<NumOrString>,
171    /// Sets the text box's horizontal position anchor This anchor binds the `x`
172    /// position to the "left", "center" or "right" of the annotation. For
173    /// example, if `x` is set to 1, `xref` to "paper" and `xanchor` to
174    /// "right" then the right-most portion of the annotation lines up with
175    /// the right-most edge of the plotting area. If "auto", the anchor is
176    /// equivalent to "center" for data-referenced annotations or if there
177    /// is an arrow, whereas for paper-referenced with no arrow, the anchor
178    /// picked corresponds to the closest side.
179    #[serde(rename = "xanchor")]
180    x_anchor: Option<Anchor>,
181    /// Shifts the position of the whole annotation and arrow to the right
182    /// (positive) or left (negative) by this many pixels.
183    #[serde(rename = "xshift")]
184    x_shift: Option<f64>,
185    /// Sets the annotation's y coordinate axis. If set to an y axis id (e.g.
186    /// "y" or "y2"), the `y` position refers to an y coordinate If set to
187    /// "paper", the `y` position refers to the distance from the bottom of
188    /// the plotting area in normalized coordinates where 0 (1) corresponds
189    /// to the bottom (top).
190    #[serde(rename = "yref")]
191    y_ref: Option<String>,
192    /// Sets the annotation's y position. If the axis `type` is "log", then you
193    /// must take the log of your desired range. If the axis `type` is
194    /// "date", it should be date strings, like date data, though Date
195    /// objects and unix milliseconds will be accepted and converted to strings.
196    /// If the axis `type` is "category", it should be numbers, using the
197    /// scale where each category is assigned a serial number from zero in
198    /// the order it appears.
199    y: Option<NumOrString>,
200    /// Sets the text box's vertical position anchor This anchor binds the `y`
201    /// position to the "top", "middle" or "bottom" of the annotation. For
202    /// example, if `y` is set to 1, `yref` to "paper" and `yanchor` to
203    /// "top" then the top-most portion of the annotation lines up with the
204    /// top-most edge of the plotting area. If "auto", the anchor is equivalent
205    /// to "middle" for data-referenced annotations or if there is an arrow,
206    /// whereas for paper-referenced with no arrow, the anchor picked
207    /// corresponds to the closest side.
208    #[serde(rename = "yanchor")]
209    y_anchor: Option<Anchor>,
210    /// Shifts the position of the whole annotation and arrow up (positive) or
211    /// down (negative) by this many pixels.
212    #[serde(rename = "yshift")]
213    y_shift: Option<f64>,
214    /// Makes this annotation respond to clicks on the plot. If you click a data
215    /// point that exactly matches the `x` and `y` values of this
216    /// annotation, and it is hidden (visible: false), it will appear. In
217    /// "onoff" mode, you must click the same point again to make it disappear,
218    /// so if you click multiple points, you can show multiple annotations.
219    /// In "onout" mode, a click anywhere else in the plot (on another data
220    /// point or not) will hide this annotation. If you need to show/hide
221    /// this annotation in response to different `x` or `y` values, you can set
222    /// `xclick` and/or `yclick`. This is useful for example to label the side
223    /// of a bar. To label markers though, `standoff` is preferred over
224    /// `xclick` and `yclick`.
225    #[serde(rename = "clicktoshow")]
226    click_to_show: Option<ClickToShow>,
227    /// Toggle this annotation when clicking a data point whose `x` value is
228    /// `xclick` rather than the annotation's `x` value.
229    #[serde(rename = "xclick")]
230    x_click: Option<NumOrString>,
231    /// Toggle this annotation when clicking a data point whose `y` value is
232    /// `yclick` rather than the annotation's `y` value.
233    #[serde(rename = "yclick")]
234    y_click: Option<NumOrString>,
235    /// Sets text to appear when hovering over this annotation. If omitted or
236    /// blank, no hover label will appear.
237    #[serde(rename = "hovertext")]
238    hover_text: Option<String>,
239    /// Label displayed on mouse hover.
240    #[serde(rename = "hoverlabel")]
241    hover_label: Option<Label>,
242    /// Determines whether the annotation text box captures mouse move and click
243    /// events, or allows those events to pass through to data points in the
244    /// plot that may be behind the annotation. By default `captureevents`
245    /// is "false" unless `hovertext` is provided. If you use the event
246    /// `plotly_clickannotation` without `hovertext` you must explicitly enable
247    /// `captureevents`.
248    #[serde(rename = "captureevents")]
249    capture_events: Option<bool>,
250    /// When used in a template, named items are created in the output figure in
251    /// addition to any items the figure already has in this array. You can
252    /// modify these items in the output figure by making your own item with
253    /// `templateitemname` matching this `name` alongside your modifications
254    /// (including `visible: false` or `enabled: false` to hide it). Has no
255    /// effect outside of a template.
256    name: Option<String>,
257    /// Used to refer to a named item in this array in the template. Named items
258    /// from the template will be created even without a matching item in
259    /// the input figure, but you can modify one by making an item with
260    /// `templateitemname` matching its `name`, alongside your modifications
261    /// (including `visible: false` or `enabled: false` to hide it). If there is
262    /// no template or no matching item, this item will be hidden unless you
263    /// explicitly show it with `visible: true`.
264    #[serde(rename = "templateitemname")]
265    template_item_name: Option<String>,
266}
267
268impl Annotation {
269    pub fn new() -> Self {
270        Default::default()
271    }
272}
273
274#[cfg(test)]
275mod tests {
276    use serde_json::{json, to_value};
277
278    use super::*;
279    use crate::common::{Anchor, Font, Label};
280
281    #[test]
282    fn serialize_click_to_show() {
283        assert_eq!(to_value(ClickToShow::False).unwrap(), json!(false));
284        assert_eq!(to_value(ClickToShow::OnOff).unwrap(), json!("onoff"));
285        assert_eq!(to_value(ClickToShow::OnOut).unwrap(), json!("onout"));
286    }
287
288    #[test]
289    fn serialize_arrow_side() {
290        assert_eq!(to_value(ArrowSide::End).unwrap(), json!("end"));
291        assert_eq!(to_value(ArrowSide::Start).unwrap(), json!("start"));
292        assert_eq!(to_value(ArrowSide::StartEnd).unwrap(), json!("end+start"));
293        assert_eq!(to_value(ArrowSide::None).unwrap(), json!("none"));
294    }
295
296    #[test]
297    fn serialize_annotation() {
298        let annotation = Annotation::new()
299            .align(HAlign::Center)
300            .arrow_color("#464646")
301            .arrow_head(2)
302            .arrow_size(123.4)
303            .arrow_side(ArrowSide::End)
304            .arrow_width(111.1)
305            .ax("ax")
306            .ax_ref("axref")
307            .ay("ay")
308            .ay_ref("ayref")
309            .background_color("#123456")
310            .border_color("#456789")
311            .border_pad(500.)
312            .border_width(1000.)
313            .capture_events(false)
314            .click_to_show(ClickToShow::OnOff)
315            .font(Font::new())
316            .height(6.)
317            .hover_label(Label::new())
318            .hover_text("hovertext")
319            .name("name")
320            .opacity(0.01)
321            .show_arrow(false)
322            .stand_off(999.9)
323            .start_arrow_head(0)
324            .start_stand_off(8.8)
325            .start_arrow_size(456.7)
326            .template_item_name("templateitemname")
327            .text("text")
328            .text_angle(5.)
329            .valign(VAlign::Middle)
330            .visible(true)
331            .width(4.)
332            .x_ref("xref")
333            .x("x")
334            .x_anchor(Anchor::Auto)
335            .x_click("xclick")
336            .x_shift(4.0)
337            .y_ref("yref")
338            .y("y")
339            .y_anchor(Anchor::Bottom)
340            .y_click("yclick")
341            .y_shift(6.3);
342
343        let expected = json!({
344            "visible": true,
345            "text": "text",
346            "textangle": 5.0,
347            "font": {},
348            "width": 4.0,
349            "height": 6.0,
350            "opacity": 0.01,
351            "align": "center",
352            "valign": "middle",
353            "bgcolor": "#123456",
354            "bordercolor": "#456789",
355            "borderpad": 500.0,
356            "borderwidth": 1000.0,
357            "showarrow": false,
358            "arrowcolor": "#464646",
359            "arrowhead": 2,
360            "startarrowhead": 0,
361            "arrowside": "end",
362            "arrowsize": 123.4,
363            "startarrowsize": 456.7,
364            "arrowwidth": 111.1,
365            "standoff": 999.9,
366            "startstandoff": 8.8,
367            "ax": "ax",
368            "ay": "ay",
369            "x": "x",
370            "y": "y",
371            "axref": "axref",
372            "ayref": "ayref",
373            "xref": "xref",
374            "yref": "yref",
375            "xanchor": "auto",
376            "yanchor": "bottom",
377            "xshift": 4.0,
378            "yshift": 6.3,
379            "clicktoshow": "onoff",
380            "xclick": "xclick",
381            "yclick": "yclick",
382            "hovertext": "hovertext",
383            "hoverlabel": {},
384            "captureevents": false,
385            "name": "name",
386            "templateitemname": "templateitemname",
387        });
388
389        assert_eq!(to_value(annotation).unwrap(), expected);
390    }
391}