plotly_fork/layout/
update_menu.rs

1//! Buttons and Dropdowns.
2
3use plotly_derive::FieldSetter;
4use serde::Serialize;
5use serde_json::{Map, Value};
6
7use crate::{
8    color::Color,
9    common::{Anchor, Font, Pad},
10    Relayout, Restyle,
11};
12
13/// Sets the Plotly method to be called on click. If the `skip` method is used,
14/// the API updatemenu will function as normal but will perform no API calls and
15/// will not bind automatically to state updates. This may be used to create a
16/// component interface and attach to updatemenu events manually via JavaScript.
17#[derive(Serialize, Debug, Copy, Clone)]
18#[serde(rename_all = "snake_case")]
19pub enum ButtonMethod {
20    /// The restyle method should be used when modifying the data and data
21    /// attributes of the graph
22    Restyle,
23    /// The relayout method should be used when modifying the layout attributes
24    /// of the graph.
25    Relayout,
26    Animate,
27    /// The update method should be used when modifying the data and layout
28    /// sections of the graph.
29    Update,
30    Skip,
31}
32
33#[serde_with::skip_serializing_none]
34#[derive(Serialize, Clone, Debug, FieldSetter)]
35pub struct Button {
36    /// Sets the arguments values to be passed to the Plotly method set in
37    /// `method` on click.
38    args: Option<Value>,
39    /// Sets a 2nd set of `args`, these arguments values are passed to the
40    /// Plotly method set in `method` when clicking this button while in the
41    /// active state. Use this to create toggle buttons.
42    args2: Option<Value>,
43    /// When true, the API method is executed. When false, all other behaviors
44    /// are the same and command execution is skipped. This may be useful
45    /// when hooking into, for example, the `plotly_buttonclicked` method
46    /// and executing the API command manually without losing the benefit of
47    /// the updatemenu automatically binding to the state of the plot through
48    /// the specification of `method` and `args`.
49    ///
50    /// Default: true
51    execute: Option<bool>,
52    /// Sets the text label to appear on the button.
53    label: Option<String>,
54    /// Sets the Plotly method to be called on click. If the `skip` method is
55    /// used, the API updatemenu will function as normal but will perform no
56    /// API calls and will not bind automatically to state updates. This may
57    /// be used to create a component interface and attach to updatemenu
58    /// events manually via JavaScript.
59    method: Option<ButtonMethod>,
60    /// When used in a template, named items are created in the output figure in
61    /// addition to any items the figure already has in this array. You can
62    /// modify these items in the output figure by making your own item with
63    /// `templateitemname` matching this `name` alongside your modifications
64    /// (including `visible: false` or `enabled: false` to hide it). Has no
65    /// effect outside of a template.
66    name: Option<String>,
67    /// Used to refer to a named item in this array in the template. Named items
68    /// from the template will be created even without a matching item in
69    /// the input figure, but you can modify one by making an item with
70    /// `templateitemname` matching its `name`, alongside your modifications
71    /// (including `visible: false` or `enabled: false` to hide it). If there is
72    /// no template or no matching item, this item will be hidden unless you
73    /// explicitly show it with `visible: true`
74    #[serde(rename = "templateitemname")]
75    template_item_name: Option<String>,
76    /// Determines whether or not this button is visible.
77    visible: Option<bool>,
78}
79
80impl Button {
81    pub fn new() -> Self {
82        Default::default()
83    }
84}
85
86/// Builder struct to create buttons which can do restyles and/or relayouts
87#[derive(FieldSetter)]
88pub struct ButtonBuilder {
89    label: Option<String>,
90    name: Option<String>,
91    template_item_name: Option<String>,
92    visible: Option<bool>,
93    #[field_setter(default = "Map::new()")]
94    restyles: Map<String, Value>,
95    #[field_setter(default = "Map::new()")]
96    relayouts: Map<String, Value>,
97}
98
99impl ButtonBuilder {
100    pub fn new() -> Self {
101        Default::default()
102    }
103    pub fn push_restyle(mut self, restyle: impl Restyle + Serialize) -> Self {
104        let restyle = serde_json::to_value(&restyle).unwrap();
105        for (k, v) in restyle.as_object().unwrap() {
106            self.restyles.insert(k.clone(), v.clone());
107        }
108        self
109    }
110
111    pub fn push_relayout(mut self, relayout: impl Relayout + Serialize) -> Self {
112        let relayout = serde_json::to_value(&relayout).unwrap();
113        for (k, v) in relayout.as_object().unwrap() {
114            self.relayouts.insert(k.clone(), v.clone());
115        }
116        self
117    }
118
119    fn method_and_args(
120        restyles: Map<String, Value>,
121        relayouts: Map<String, Value>,
122    ) -> (ButtonMethod, Value) {
123        match (restyles.is_empty(), relayouts.is_empty()) {
124            (true, true) => (ButtonMethod::Skip, Value::Null),
125            (false, true) => (ButtonMethod::Restyle, vec![restyles].into()),
126            (true, false) => (ButtonMethod::Relayout, vec![relayouts].into()),
127            (false, false) => (ButtonMethod::Update, vec![restyles, relayouts].into()),
128        }
129    }
130
131    pub fn build(self) -> Button {
132        let (method, args) = Self::method_and_args(self.restyles, self.relayouts);
133        Button {
134            label: self.label,
135            args: Some(args),
136            method: Some(method),
137            name: self.name,
138            template_item_name: self.template_item_name,
139            visible: self.visible,
140            ..Default::default()
141        }
142    }
143}
144
145/// Determines whether the buttons are accessible via a dropdown menu or whether
146/// the buttons are stacked horizontally or vertically
147///
148/// Default: "dropdown"
149#[derive(Serialize, Debug, Clone)]
150#[serde(rename_all = "snake_case")]
151pub enum UpdateMenuType {
152    Dropdown,
153    Buttons,
154}
155
156/// Determines the direction in which the buttons are laid out, whether in a
157/// dropdown menu or a row/column of buttons. For `left` and `up`, the buttons
158/// will still appear in left-to-right or top-to-bottom order respectively.
159///
160/// Default: "down"
161#[derive(Serialize, Debug, Clone)]
162#[serde(rename_all = "snake_case")]
163pub enum UpdateMenuDirection {
164    Left,
165    Right,
166    Up,
167    Down,
168}
169
170#[serde_with::skip_serializing_none]
171#[derive(Serialize, Debug, FieldSetter, Clone)]
172pub struct UpdateMenu {
173    /// Determines which button (by index starting from 0) is considered active.
174    active: Option<i32>,
175    /// Sets the background color of the update menu buttons.
176    #[serde(rename = "bgcolor")]
177    background_color: Option<Box<dyn Color>>,
178    /// Sets the color of the border enclosing the update menu.
179    #[serde(rename = "bordercolor")]
180    border_color: Option<Box<dyn Color>>,
181    /// Sets the width (in px) of the border enclosing the update menu.
182    #[serde(rename = "borderwidth")]
183    border_width: Option<usize>,
184    buttons: Option<Vec<Button>>,
185    /// Determines the direction in which the buttons are laid out, whether in
186    /// a dropdown menu or a row/column of buttons. For `left` and `up`,
187    /// the buttons will still appear in left-to-right or top-to-bottom order
188    /// respectively.
189    direction: Option<UpdateMenuDirection>,
190    /// Sets the font of the update menu button text.
191    font: Option<Font>,
192    /// When used in a template, named items are created in the output figure in
193    /// addition to any items the figure already has in this array. You can
194    /// modify these items in the output figure by making your own item with
195    /// `templateitemname` matching this `name` alongside your modifications
196    /// (including `visible: false` or `enabled: false` to hide it). Has no
197    /// effect outside of a template.
198    name: Option<String>,
199    /// Sets the padding around the buttons or dropdown menu.
200    pad: Option<Pad>,
201    /// Highlights active dropdown item or active button if true.
202    #[serde(rename = "showactive")]
203    show_active: Option<bool>,
204    /// Used to refer to a named item in this array in the template. Named items
205    /// from the template will be created even without a matching item in
206    /// the input figure, but you can modify one by making an item with
207    /// `templateitemname` matching its `name`, alongside your modifications
208    /// (including `visible: false` or `enabled: false` to hide it). If there is
209    /// no template or no matching item, this item will be hidden unless you
210    /// explicitly show it with `visible: true`.
211    template_item_name: Option<String>,
212    /// Determines whether the buttons are accessible via a dropdown menu or
213    /// whether the buttons are stacked horizontally or vertically
214    #[serde(rename = "type")]
215    ty: Option<UpdateMenuType>,
216    /// Determines whether or not the update menu is visible.
217    visible: Option<bool>,
218    /// Type: number between or equal to -2 and 3
219    /// Default: -0.05
220    /// Sets the x position (in normalized coordinates) of the update menu.
221    x: Option<f64>,
222    /// Sets the update menu's horizontal position anchor. This anchor binds the
223    /// `x` position to the "left", "center" or "right" of the range
224    /// selector. Default: "right"
225    #[serde(rename = "xanchor")]
226    x_anchor: Option<Anchor>,
227    /// Type: number between or equal to -2 and 3
228    /// Default: 1
229    /// Sets the y position (in normalized coordinates) of the update menu.
230    y: Option<f64>,
231    /// Sets the update menu's vertical position anchor This anchor binds the
232    /// `y` position to the "top", "middle" or "bottom" of the range
233    /// selector. Default: "top"
234    #[serde(rename = "yanchor")]
235    y_anchor: Option<Anchor>,
236}
237
238impl UpdateMenu {
239    pub fn new() -> Self {
240        Default::default()
241    }
242}
243
244#[cfg(test)]
245mod tests {
246    use serde_json::{json, to_value};
247
248    use super::*;
249    use crate::{
250        common::{Title, Visible},
251        Layout,
252    };
253
254    #[test]
255    fn test_serialize_button_method() {
256        assert_eq!(to_value(ButtonMethod::Restyle).unwrap(), json!("restyle"));
257        assert_eq!(to_value(ButtonMethod::Relayout).unwrap(), json!("relayout"));
258        assert_eq!(to_value(ButtonMethod::Animate).unwrap(), json!("animate"));
259        assert_eq!(to_value(ButtonMethod::Update).unwrap(), json!("update"));
260        assert_eq!(to_value(ButtonMethod::Skip).unwrap(), json!("skip"));
261    }
262
263    #[test]
264    fn test_serialize_button() {
265        let button = Button::new()
266            .args(json!([
267                { "visible": [true, false] },
268                { "width": 20},
269            ]))
270            .args2(json!([]))
271            .execute(true)
272            .label("Label")
273            .method(ButtonMethod::Update)
274            .name("Name")
275            .template_item_name("Template")
276            .visible(true);
277
278        let expected = json!({
279            "args": [
280                { "visible": [true, false] },
281                { "width": 20},
282            ],
283            "args2": [],
284            "execute": true,
285            "label": "Label",
286            "method": "update",
287            "name": "Name",
288            "templateitemname": "Template",
289            "visible": true,
290        });
291
292        assert_eq!(to_value(button).unwrap(), expected);
293    }
294
295    #[test]
296    fn test_button_builder() {
297        let expected = json!({
298            "args": [
299                { "visible": [true, false] },
300                { "title": {"text": "Hello"}, "width": 20},
301            ],
302            "label": "Label",
303            "method": "update",
304            "name": "Name",
305            "templateitemname": "Template",
306            "visible": true,
307        });
308
309        let button = ButtonBuilder::new()
310            .label("Label")
311            .name("Name")
312            .template_item_name("Template")
313            .visible(true)
314            .push_restyle(crate::Bar::<i32, i32>::modify_visible(vec![
315                Visible::True,
316                Visible::False,
317            ]))
318            .push_relayout(Layout::modify_title(Title::new("Hello")))
319            .push_relayout(Layout::modify_width(20))
320            .build();
321
322        assert_eq!(to_value(button).unwrap(), expected);
323    }
324}