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}