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}