plotly_fork/traces/mesh3d.rs
1//! Mesh plot
2
3use plotly_derive::FieldSetter;
4use serde::Serialize;
5
6use crate::common::{
7 color::Color, Calendar, ColorBar, ColorScale, Dim, HoverInfo, Label, LegendGroupTitle,
8 PlotType, Visible,
9};
10use crate::private::{NumOrString, NumOrStringCollection};
11use crate::Trace;
12
13#[derive(Serialize, Clone, Debug)]
14#[serde(rename_all = "lowercase")]
15pub enum IntensityMode {
16 Vertex,
17 Cell,
18}
19
20#[derive(Serialize, Clone, Debug)]
21#[serde(rename_all = "lowercase")]
22pub enum DelaunayAxis {
23 X,
24 Y,
25 Z,
26}
27
28#[serde_with::skip_serializing_none]
29#[derive(Serialize, Clone, Debug, FieldSetter)]
30pub struct Contour {
31 /// Sets the color of the contour lines.
32 color: Option<Box<dyn Color>>,
33 /// Sets whether or not dynamic contours are shown on hover.
34 show: Option<bool>,
35 /// Sets the width of the contour lines.
36 width: Option<usize>,
37}
38
39impl Contour {
40 pub fn new() -> Self {
41 Default::default()
42 }
43}
44
45#[serde_with::skip_serializing_none]
46#[derive(Serialize, Clone, Debug, FieldSetter)]
47pub struct Lighting {
48 /// Ambient light increases overall color visibility but can wash out the
49 /// image.
50 ambient: Option<f64>,
51 /// Represents the extent that incident rays are reflected in a range of
52 /// angles.
53 diffuse: Option<f64>,
54 #[serde(rename = "facenormalsepsilon")]
55 /// Epsilon for face normals calculation avoids math issues arising from
56 /// degenerate geometry.
57 face_normals_epsilon: Option<f64>,
58 /// Represents the reflectance as a dependency of the viewing angle; e.g.
59 /// paper is reflective when viewing it from the edge of the paper
60 /// (almost 90 degrees), causing shine.
61 fresnel: Option<f64>,
62 /// Alters specular reflection; the rougher the surface, the wider and less
63 /// contrasty the shine.
64 roughness: Option<f64>,
65 /// Represents the level that incident rays are reflected in a single
66 /// direction, causing shine.
67 specular: Option<f64>,
68 /// Epsilon for vertex normals calculation avoids math issues arising from
69 /// degenerate geometry.
70 #[serde(rename = "vertexnormalsepsilon")]
71 vertex_normals_epsilon: Option<f64>,
72}
73
74impl Lighting {
75 pub fn new() -> Self {
76 Default::default()
77 }
78}
79
80#[serde_with::skip_serializing_none]
81#[derive(Serialize, Clone, Debug, FieldSetter)]
82pub struct LightPosition {
83 /// Numeric vector, representing the X coordinate for each vertex.
84 x: Option<Vec<f64>>,
85 /// Numeric vector, representing the Y coordinate for each vertex.
86 y: Option<Vec<f64>>,
87 /// Numeric vector, representing the Z coordinate for each vertex.
88 z: Option<Vec<f64>>,
89}
90
91impl LightPosition {
92 pub fn new() -> Self {
93 Default::default()
94 }
95}
96
97#[serde_with::skip_serializing_none]
98#[derive(Serialize, Clone, Debug, FieldSetter)]
99#[field_setter(box_self, kind = "trace")]
100pub struct Mesh3D<X, Y, Z>
101where
102 X: Serialize + Clone,
103 Y: Serialize + Clone,
104 Z: Serialize + Clone,
105{
106 #[field_setter(default = "PlotType::Mesh3D")]
107 r#type: PlotType,
108 /// Sets the trace name. The trace name appear as the legend item and on
109 /// hover.
110 name: Option<String>,
111 /// Determines whether or not this trace is visible. If
112 /// `Visible::LegendOnly`, the trace is not drawn, but can appear as a
113 /// legend item (provided that the legend itself is visible).
114 visible: Option<Visible>,
115
116 /// Determines whether or not an item corresponding to this trace is shown
117 /// in the legend.
118 #[serde(rename = "showlegend")]
119 show_legend: Option<bool>,
120 /// Sets the legend rank for this trace. Items and groups with smaller ranks
121 /// are presented on top/left side while with `"reversed"
122 /// `legend.trace_order` they are on bottom/right side. The default
123 /// legendrank is 1000, so that you can use ranks less than 1000 to
124 /// place certain items before all unranked items, and ranks greater
125 /// than 1000 to go after all unranked items.
126 #[serde(rename = "legendrank")]
127 legend_rank: Option<usize>,
128 /// Sets the legend group for this trace. Traces part of the same legend
129 /// group hide/show at the same time when toggling legend items.
130 #[serde(rename = "legendgroup")]
131 legend_group: Option<String>,
132 /// Set and style the title to appear for the legend group
133 #[serde(rename = "legendgrouptitle")]
134 legend_group_title: Option<LegendGroupTitle>,
135
136 /// Sets the opacity of the trace.
137 opacity: Option<f64>,
138
139 /// Assigns id labels to each datum. These ids for object constancy of data
140 /// points during animation. Should be an array of strings, not numbers
141 /// or any other type.
142 ids: Option<Vec<String>>,
143
144 x: Option<Vec<X>>,
145 y: Option<Vec<Y>>,
146 z: Option<Vec<Z>>,
147
148 i: Option<Vec<usize>>,
149 j: Option<Vec<usize>>,
150 k: Option<Vec<usize>>,
151
152 /// Sets the color of each face. Overrides "color" and "vertexcolor".
153 #[serde(rename = "facecolor")]
154 face_color: Option<Vec<Box<dyn Color>>>,
155 /// Sets the intensity values for vertices or cells as defined by
156 /// `IntensityMode`. It can be used for plotting fields on meshes.
157 intensity: Option<Vec<f64>>,
158 #[serde(rename = "intensitymode")]
159 /// Determines the source of `intensity` values.
160 intensity_mode: Option<IntensityMode>,
161 /// Sets the color of each vertex Overrides "color". While Red, green and
162 /// blue colors are in the range of 0 and 255; in the case of having
163 /// vertex color data in RGBA format, the alpha color should be normalized
164 /// to be between 0 and 1.
165 #[serde(rename = "vertexcolor")]
166 vertex_color: Option<Vec<Box<dyn Color>>>,
167
168 /// Sets text elements associated with each (x,y) pair. If a single string,
169 /// the same string appears over all the data points. If an array of
170 /// strings, the items are mapped in order to the this trace's (x,y)
171 /// coordinates. If the trace `HoverInfo` contains a "text" flag and
172 /// `hover_text` is not set, these elements will be seen in the hover
173 /// labels.
174 text: Option<Dim<String>>,
175 /// Sets hover text elements associated with each (x,y) pair. If a single
176 /// string, the same string appears over all the data points. If an
177 /// array of strings, the items are mapped in order to the this trace's
178 /// (x,y) coordinates. To be seen, trace `HoverInfo` must contain a
179 /// "Text" flag.
180 #[serde(rename = "hovertext")]
181 hover_text: Option<Dim<String>>,
182 /// Determines which trace information appear on hover. If `HoverInfo::None`
183 /// or `HoverInfo::Skip` are set, no information is displayed upon
184 /// hovering. But, if `HoverInfo::None` is set, click and hover events
185 /// are still fired.
186 #[serde(rename = "hoverinfo")]
187 hover_info: Option<HoverInfo>,
188 /// Template string used for rendering the information that appear on hover
189 /// box. Note that this will override `HoverInfo`. Variables are
190 /// inserted using %{variable}, for example "y: %{y}". Numbers are
191 /// formatted using d3-format's syntax %{variable:d3-format}, for example
192 /// "Price: %{y:$.2f}".
193 /// https://github.com/d3/d3-3.x-api-reference/blob/master/Formatting.md#d3_format for details
194 /// on the formatting syntax. Dates are formatted using d3-time-format's
195 /// syntax %{variable|d3-time-format}, for example "Day:
196 /// %{2019-01-01|%A}". https://github.com/d3/d3-3.x-api-reference/blob/master/Time-Formatting.md#format for details
197 /// on the date formatting syntax. The variables available in
198 /// `hovertemplate` are the ones emitted as event data described at this link https://plotly.com/javascript/plotlyjs-events/#event-data.
199 /// Additionally, every attributes that can be specified per-point (the ones
200 /// that are `arrayOk: true`) are available. Anything contained in tag
201 /// `<extra>` is displayed in the secondary box, for example
202 /// "<extra>{fullData.name}</extra>". To hide the secondary box
203 /// completely, use an empty tag `<extra></extra>`.
204 #[serde(rename = "hovertemplate")]
205 hover_template: Option<Dim<String>>,
206 /// Sets the hover text formatting rulefor `x` using d3 formatting
207 /// mini-languages which are very similar to those in Python. For numbers, see: https://github.com/d3/d3-format/tree/v1.4.5#d3-format. And for dates
208 /// see: https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format. We add two items to d3's date
209 /// formatter: "%h" for half of the year as a decimal number as well as
210 /// "%{n}f" for fractional seconds with n digits. For example,
211 /// "2016-10-13 09:15:23.456" with tickformat "%H~%M~%S.%2f" would display
212 /// "09~15~23.46"By default the values are formatted using
213 /// `xaxis.hoverformat`.
214 #[serde(rename = "xhoverformat")]
215 x_hover_format: Option<String>,
216 /// Sets the hover text formatting rulefor `y` using d3 formatting
217 /// mini-languages which are very similar to those in Python. For numbers, see: https://github.com/d3/d3-format/tree/v1.4.5#d3-format. And for dates
218 /// see: https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format. We add two items to d3's date
219 /// formatter: "%h" for half of the year as a decimal number as well as
220 /// "%{n}f" for fractional seconds with n digits. For example,
221 /// "2016-10-13 09:15:23.456" with tickformat "%H~%M~%S.%2f" would display
222 /// "09~15~23.46"By default the values are formatted using
223 /// `yaxis.hoverformat`.
224 #[serde(rename = "yhoverformat")]
225 y_hover_format: Option<String>,
226
227 /// Assigns extra meta information associated with this trace that can be
228 /// used in various text attributes. Attributes such as trace `name`,
229 /// graph, axis and colorbar `title.text`, annotation `text`
230 /// `rangeselector`, `updatemenues` and `sliders` `label` text all support
231 /// `meta`. To access the trace `meta` values in an attribute in the same
232 /// trace, simply use `%{meta[i]}` where `i` is the index or key of the
233 /// `meta` item in question. To access trace `meta` in layout
234 /// attributes, use `%{data[n[.meta[i]}` where `i` is the index or key of
235 /// the `meta` and `n` is the trace index.
236 meta: Option<NumOrString>,
237 /// Assigns extra data each datum. This may be useful when listening to
238 /// hover, click and selection events. Note that, "scatter" traces also
239 /// appends customdata items in the markers DOM elements.
240 #[serde(rename = "customdata")]
241 custom_data: Option<NumOrStringCollection>,
242
243 /// Sets a reference between this trace's 3D coordinate system and a 3D
244 /// scene. If "scene" (the default value), the (x,y,z) coordinates refer
245 /// to `layout.scene`. If "scene2", the (x, y, z) coordinates refer to
246 /// `layout.scene2`, and so on.
247 scene: Option<String>,
248 /// Sets a reference to a shared color axis. References to these shared
249 /// color axes are "coloraxis", "coloraxis2", "coloraxis3", etc.
250 /// Settings for these shared color axes are set in the layout, under
251 /// `layout.coloraxis`, `layout.coloraxis2`, etc. Note that multiple color
252 /// scales can be linked to the same color axis.
253 #[serde(rename = "coloraxis")]
254 color_axis: Option<String>,
255 /// Sets the color of the whole mesh.
256 color: Option<Box<dyn Color>>,
257
258 #[serde(rename = "colorbar")]
259 color_bar: Option<ColorBar>,
260
261 /// Determines whether the colorscale is a default palette (`autocolorscale:
262 /// True`) or the palette determined by `colorscale`. In case
263 /// `colorscale` is unspecified or `autocolorscale` is True, the default
264 /// palette will be chosen according to whether numbers in the `color`
265 /// array are all positive, all negative or mixed.
266 #[serde(rename = "autocolorscale")]
267 auto_color_scale: Option<bool>,
268 /// Sets the colorscale. The colorscale must be an array containing arrays
269 /// mapping a normalized value to an rgb, rgba, hex, hsl, hsv, or named
270 /// color string. At minimum, a mapping for the lowest (0) and highest (1)
271 /// values are required. For example, `[[0, 'rgb(0,0,255)'], [1,
272 /// 'rgb(255,0,0)']]`. To control the bounds of the colorscale in color
273 /// space, use `cmin` and `cmax`. Alternatively, `colorscale` may be a
274 /// palette name string of the following list:
275 /// Blackbody,Bluered,Blues,Cividis,Earth,Electric,Greens,Greys,Hot,Jet,
276 /// Picnic, Portland,Rainbow,RdBu,Reds,Viridis,YlGnBu,YlOrRd.
277 #[serde(rename = "colorscale")]
278 color_scale: Option<ColorScale>,
279 /// Determines whether or not a colorbar is displayed for this trace.
280 #[serde(rename = "showscale")]
281 show_scale: Option<bool>,
282 /// Reverses the color mapping if True. If True, `cmin` will correspond to
283 /// the last color in the array and `cmax` will correspond to the first
284 /// color.
285 #[serde(rename = "reversescale")]
286 reverse_scale: Option<bool>,
287
288 /// Sets the hover text formatting rulefor `z` using d3 formatting
289 /// mini-languages which are very similar to those in Python. For numbers, see: https://github.com/d3/d3-format/tree/v1.4.5#d3-format. And for dates
290 /// see: https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format. We add two items to d3's date
291 /// formatter: "%h" for half of the year as a decimal number as well as
292 /// "%{n}f" for fractional seconds with n digits. For example,
293 /// "2016-10-13 09:15:23.456" with tickformat "%H~%M~%S.%2f" would display
294 /// "09~15~23.46". By default the values are formatted using
295 /// `zaxis.hoverformat`.
296 #[serde(rename = "zhoverformat")]
297 z_hover_format: Option<String>,
298
299 /// Determines whether or not the color domain is computed with respect to
300 /// the input data (here `intensity`) or the bounds set in `cmin` and
301 /// `cmax` Defaults to `False` when `cmin` and `cmax` are set by the user.
302 #[serde(rename = "cauto")]
303 c_auto: Option<bool>,
304 /// Sets the upper bound of the color domain. Value should have the same
305 /// units as `intensity` and if set, `cmin` must be set as well.
306 #[serde(rename = "cmax")]
307 c_max: Option<f64>,
308 /// Sets the mid-point of the color domain by scaling `cmin` and/or `cmax`
309 /// to be equidistant to this point. Value should have the same units as
310 /// `intensity`. Has no effect when `cauto` is `False`.
311 #[serde(rename = "cmid")]
312 c_mid: Option<f64>,
313 /// Sets the lower bound of the color domain. Value should have the same
314 /// units as `intensity` and if set, `cmax` must be set as well.
315 #[serde(rename = "cmin")]
316 c_min: Option<f64>,
317 /// Determines how the mesh surface triangles are derived from the set of
318 /// vertices (points) represented by the `x`, `y` and `z` arrays, if the
319 /// `i`, `j`, `k` arrays are not supplied. For general use of `mesh3d` it is
320 /// preferred that `i`, `j`, `k` are supplied. If "-1", Delaunay
321 /// triangulation is used, which is mainly suitable if the mesh is a
322 /// single, more or less layer surface that is perpendicular to
323 /// `delaunayaxis`. In case the `delaunayaxis` intersects the mesh
324 /// surface at more than one point it will result triangles that
325 /// are very long in the dimension of `delaunayaxis`. If ">0", the
326 /// alpha-shape algorithm is used. In this case, the positive
327 /// `alphahull` value signals the use of the alpha-shape algorithm, _and_
328 /// its value acts as the parameter for the mesh fitting. If "0", the
329 /// convex-hull algorithm is used. It is suitable for convex
330 /// bodies or if the intention is to enclose the `x`, `y` and `z` point set
331 /// into a convex hull.
332 #[serde(rename = "alphahull")]
333 alpha_hull: Option<f64>,
334 /// Sets the Delaunay axis, which is the axis that is perpendicular to the
335 /// surface of the Delaunay triangulation. It has an effect if `i`, `j`,
336 /// `k` are not provided and `alphahull` is set to indicate
337 /// Delaunay triangulation.
338 #[serde(rename = "delaunayaxis")]
339 delaunay_axis: Option<DelaunayAxis>,
340 contour: Option<Contour>,
341
342 /// Determines whether or not normal smoothing is applied to the meshes,
343 /// creating meshes with an angular, low-poly look via flat reflections.
344 #[serde(rename = "flatshading")]
345 flat_shading: Option<bool>,
346
347 /// Properties of label displayed on mouse hover.
348 #[serde(rename = "hoverlabel")]
349 hover_label: Option<Label>,
350
351 lighting: Option<Lighting>,
352 #[serde(rename = "lightposition")]
353 light_position: Option<LightPosition>,
354
355 /// Sets the calendar system to use with `x` date data.
356 #[serde(rename = "xcalendar")]
357 x_calendar: Option<Calendar>,
358 /// Sets the calendar system to use with `y` date data.
359 #[serde(rename = "ycalendar")]
360 y_calendar: Option<Calendar>,
361 /// Sets the calendar system to use with `z` date data.
362 #[serde(rename = "zcalendar")]
363 z_calendar: Option<Calendar>,
364
365 /// Controls persistence of some user-driven changes to the trace:
366 /// `constraintrange` in `parcoords` traces, as well as some `editable:
367 /// True` modifications such as `name` and `colorbar.title`. Defaults to
368 /// `layout.uirevision`. Note that other user-driven trace attribute changes
369 /// are controlled by `layout` attributes: `trace.visible` is controlled
370 /// by `layout.legend.uirevision`, `selectedpoints` is controlled
371 /// by `layout.selectionrevision`, and `colorbar.(x|y)` (accessible with
372 /// `config: {editable: True}`) is controlled by `layout.editrevision`.
373 /// Trace changes are tracked by `uid`, which only falls back on trace
374 /// index if no `uid` is provided. So if your app can add/remove traces
375 /// before the end of the `data` array, such that the same trace has a
376 /// different index, you can still preserve user-driven changes if you give
377 /// each trace a `uid` that stays with it as it moves.
378 #[serde(rename = "uirevision")]
379 ui_revision: Option<NumOrString>,
380}
381
382impl<X, Y, Z> Mesh3D<X, Y, Z>
383where
384 X: Serialize + Default + Clone,
385 Y: Serialize + Default + Clone,
386 Z: Serialize + Default + Clone,
387{
388 pub fn new(
389 x: Vec<X>,
390 y: Vec<Y>,
391 z: Vec<Z>,
392 i: Vec<usize>,
393 j: Vec<usize>,
394 k: Vec<usize>,
395 ) -> Box<Self> {
396 Box::new(Self {
397 x: Some(x),
398 y: Some(y),
399 z: Some(z),
400 i: Some(i),
401 j: Some(j),
402 k: Some(k),
403 ..Default::default()
404 })
405 }
406}
407
408impl<X, Y, Z> Trace for Mesh3D<X, Y, Z>
409where
410 X: Serialize + Clone,
411 Y: Serialize + Clone,
412 Z: Serialize + Clone,
413{
414 fn to_json(&self) -> String {
415 serde_json::to_string(&self).unwrap()
416 }
417}
418
419#[cfg(test)]
420mod tests {
421 use serde_json::{json, to_value};
422
423 use super::*;
424 use crate::common::ColorScalePalette;
425
426 #[test]
427 fn test_serialize_intensity_mode() {
428 assert_eq!(to_value(IntensityMode::Vertex).unwrap(), json!("vertex"));
429 assert_eq!(to_value(IntensityMode::Cell).unwrap(), json!("cell"));
430 }
431
432 #[test]
433 fn test_serialize_delaunay_axis() {
434 assert_eq!(to_value(DelaunayAxis::X).unwrap(), json!("x"));
435 assert_eq!(to_value(DelaunayAxis::Y).unwrap(), json!("y"));
436 assert_eq!(to_value(DelaunayAxis::Z).unwrap(), json!("z"));
437 }
438
439 #[test]
440 fn test_serialize_contour() {
441 let contour = Contour::new().color("#123456").show(true).width(6);
442 let expected = json!({"color": "#123456", "show": true, "width": 6});
443
444 assert_eq!(to_value(contour).unwrap(), expected);
445 }
446
447 #[test]
448 fn test_serialize_lighting() {
449 let lighting = Lighting::new()
450 .ambient(0.1)
451 .diffuse(0.2)
452 .face_normals_epsilon(0.3)
453 .fresnel(0.4)
454 .roughness(0.5)
455 .specular(0.6)
456 .vertex_normals_epsilon(0.7);
457 let expected = json!({
458 "ambient": 0.1,
459 "diffuse": 0.2,
460 "facenormalsepsilon": 0.3,
461 "fresnel": 0.4,
462 "roughness": 0.5,
463 "specular": 0.6,
464 "vertexnormalsepsilon": 0.7,
465 });
466
467 assert_eq!(to_value(lighting).unwrap(), expected);
468 }
469
470 #[test]
471 fn test_serialize_light_position() {
472 let light_position = LightPosition::new()
473 .x(vec![10.0])
474 .y(vec![20.0])
475 .z(vec![30.0]);
476 let expected = json!({"x": [10.0], "y": [20.0], "z": [30.0]});
477
478 assert_eq!(to_value(light_position).unwrap(), expected);
479 }
480
481 #[test]
482 fn test_serialize_mesh3d() {
483 let mesh3d = Mesh3D::new(
484 vec![0.0, 1.0, 2.0],
485 vec![3.0, 4.0, 5.0],
486 vec![6.0, 7.0, 8.0],
487 vec![0],
488 vec![1],
489 vec![2],
490 )
491 .name("trace_name")
492 .visible(Visible::True)
493 .show_legend(true)
494 .legend_rank(1000)
495 .legend_group("legend_group")
496 .legend_group_title(LegendGroupTitle::new("Legend Group Title"))
497 .opacity(0.5)
498 .ids(vec!["one"])
499 .face_color(vec!["#ff00ff"])
500 .intensity(vec![1.0])
501 .intensity_mode(IntensityMode::Vertex)
502 .vertex_color(vec!["#ff0000", "#00ff00", "#0000ff"])
503 .text("text")
504 .text_array(vec!["text"])
505 .hover_text("hover_text")
506 .hover_text_array(vec!["hover_text"])
507 .hover_info(HoverInfo::XAndYAndZ)
508 .hover_template("hover_template")
509 .hover_template_array(vec!["hover_template"])
510 .x_hover_format("x_hover_format")
511 .y_hover_format("y_hover_format")
512 .meta("meta")
513 .custom_data(vec!["custom_data"])
514 .scene("scene2")
515 .color_axis("coloraxis2")
516 .color("#cccccc")
517 .color_bar(ColorBar::new())
518 .auto_color_scale(false)
519 .color_scale(ColorScale::Palette(ColorScalePalette::Rainbow))
520 .show_scale(true)
521 .reverse_scale(true)
522 .z_hover_format("z_hover_format")
523 .c_auto(false)
524 .c_max(1.0)
525 .c_min(0.0)
526 .c_mid(0.2)
527 .alpha_hull(7.5)
528 .delaunay_axis(DelaunayAxis::Y)
529 .contour(Contour::new())
530 .flat_shading(true)
531 .hover_label(Label::new())
532 .lighting(Lighting::new())
533 .light_position(LightPosition::new())
534 .x_calendar(Calendar::Chinese)
535 .y_calendar(Calendar::Coptic)
536 .z_calendar(Calendar::Ummalqura)
537 .ui_revision(2.5);
538
539 let expected = json!({
540 "type": "mesh3d",
541 "x": [0.0, 1.0, 2.0],
542 "y": [3.0, 4.0, 5.0],
543 "z": [6.0, 7.0, 8.0],
544 "i": [0],
545 "j": [1],
546 "k": [2],
547 "name": "trace_name",
548 "visible": true,
549 "showlegend": true,
550 "legendrank": 1000,
551 "legendgroup": "legend_group",
552 "legendgrouptitle": {"text": "Legend Group Title"},
553 "opacity": 0.5,
554 "ids": ["one"],
555 "facecolor": ["#ff00ff"],
556 "intensity": [1.0],
557 "intensitymode": "vertex",
558 "vertexcolor": ["#ff0000", "#00ff00", "#0000ff"],
559 "text": ["text"],
560 "hovertext": ["hover_text"],
561 "hoverinfo": "x+y+z",
562 "hovertemplate": ["hover_template"],
563 "xhoverformat": "x_hover_format",
564 "yhoverformat": "y_hover_format",
565 "meta": "meta",
566 "customdata": ["custom_data"],
567 "scene": "scene2",
568 "coloraxis": "coloraxis2",
569 "color": "#cccccc",
570 "colorbar": {
571 },
572 "autocolorscale": false,
573 "colorscale": "Rainbow",
574 "showscale": true,
575 "reversescale": true,
576 "zhoverformat": "z_hover_format",
577 "cauto": false,
578 "cmax": 1.0,
579 "cmin": 0.0,
580 "cmid": 0.2,
581 "alphahull": 7.5,
582 "delaunayaxis": "y",
583 "contour": {},
584 "flatshading": true,
585 "hoverlabel": {},
586 "lighting": {},
587 "lightposition": {},
588 "xcalendar": "chinese",
589 "ycalendar": "coptic",
590 "zcalendar": "ummalqura",
591 "uirevision": 2.5
592 });
593
594 assert_eq!(to_value(mesh3d).unwrap(), expected);
595 }
596}