plotly/traces/
scatter3d.rs

1//! Scatter3D plot
2
3#[cfg(feature = "plotly_ndarray")]
4use ndarray::{Array, Ix1};
5use plotly_derive::FieldSetter;
6use serde::Serialize;
7
8use crate::{
9    color::Color,
10    common::{
11        Calendar, Dim, ErrorData, HoverInfo, Label, LegendGroupTitle, Line, Marker, Mode, PlotType,
12        Position, Visible,
13    },
14    private, Trace,
15};
16
17#[serde_with::skip_serializing_none]
18#[derive(Debug, FieldSetter, Clone, Serialize)]
19pub struct ProjectionCoord {
20    opacity: Option<f64>,
21    scale: Option<f64>,
22    show: Option<bool>,
23}
24
25impl ProjectionCoord {
26    pub fn new() -> Self {
27        Default::default()
28    }
29}
30
31#[serde_with::skip_serializing_none]
32#[derive(Debug, FieldSetter, Clone, Serialize)]
33pub struct Projection {
34    x: Option<ProjectionCoord>,
35    y: Option<ProjectionCoord>,
36    z: Option<ProjectionCoord>,
37}
38
39impl Projection {
40    pub fn new() -> Self {
41        Default::default()
42    }
43}
44
45#[derive(Debug, Clone, Serialize)]
46pub enum SurfaceAxis {
47    #[serde(rename = "-1")]
48    MinusOne,
49    #[serde(rename = "0")]
50    Zero,
51    #[serde(rename = "1")]
52    One,
53    #[serde(rename = "2")]
54    Two,
55}
56
57/// Construct a scatter3D trace.
58///
59/// # Examples
60///
61/// ```
62/// use plotly::Scatter3D;
63///
64/// let trace = Scatter3D::new(
65///     vec![0.0, 1.0],
66///     vec![2.0, 3.0],
67///     vec![4.0, 5.0],
68/// );
69///
70/// let expected = serde_json::json!({
71///     "type": "scatter3d",
72///     "x": [0.0, 1.0],
73///     "y": [2.0, 3.0],
74///     "z": [4.0, 5.0],
75///
76/// });
77///
78/// assert_eq!(serde_json::to_value(trace).unwrap(), expected);
79/// ```
80#[serde_with::skip_serializing_none]
81#[derive(Serialize, Clone, Debug, FieldSetter)]
82#[field_setter(box_self, kind = "trace")]
83pub struct Scatter3D<X, Y, Z>
84where
85    X: Serialize + Clone,
86    Y: Serialize + Clone,
87    Z: Serialize + Clone,
88{
89    #[field_setter(default = "PlotType::Scatter3D")]
90    r#type: PlotType,
91    /// Sets the trace name. The trace name is used as the label for the trace
92    /// in the legend, as well as when the trace is hovered hover.
93    name: Option<String>,
94    /// Determines whether or not this trace is visible. If
95    /// `Visible::LegendOnly`, the trace is not drawn, but can appear as a
96    /// legend item (provided that the legend itself is visible).
97    visible: Option<Visible>,
98    /// Determines whether or not an item corresponding to this trace is shown
99    /// in the legend.
100    #[serde(rename = "showlegend")]
101    show_legend: Option<bool>,
102    /// Sets the legend group for this trace. Traces part of the same legend
103    /// group show/hide at the same time when toggling legend items.
104    #[serde(rename = "legendgroup")]
105    legend_group: Option<String>,
106    /// Sets the legend rank for this trace. Items and groups with smaller ranks
107    /// are presented on top/left side while with `"reversed"
108    /// `legend.traceorder` they are on bottom/right side. The default
109    /// legendrank is 1000, so that you can use ranks less than 1000 to
110    /// place certain items before all unranked items, and ranks greater than
111    /// 1000 to go after all unranked items.
112    #[serde(rename = "legendrank")]
113    legend_rank: Option<usize>,
114    /// Sets the `LegendGroupTitle` object for the trace.
115    #[serde(rename = "legendgrouptitle")]
116    legend_group_title: Option<LegendGroupTitle>,
117    /// Sets the opacity of the trace.
118    opacity: Option<f64>,
119    /// Determines the drawing mode for this scatter trace. If the provided
120    /// `Mode` includes "Text" then the `text` elements appear at the
121    /// coordinates. Otherwise, the `text` elements appear on hover. If
122    /// there are less than 20 points and the trace is not stacked then the
123    /// default is `Mode::LinesMarkers`, otherwise it is `Mode::Lines`.
124    mode: Option<Mode>,
125    /// Assigns id labels to each datum. These ids for object constancy of data
126    /// points during animation. Should be an array of strings, not numbers
127    /// or any other type.
128    ids: Option<Vec<String>>,
129
130    x: Option<Vec<X>>,
131    y: Option<Vec<Y>>,
132    z: Option<Vec<Z>>,
133
134    /// Sets the surface fill color.
135    #[serde(rename = "surfacecolor")]
136    surface_color: Option<Box<dyn Color>>,
137    /// Sets text element associated with each (x, y, z) triplet. The same tet
138    /// will be applied to each data point. If the trace `HoverInfo`
139    /// contains a "text" flag and `hover_text` is not set, these elements
140    /// will be seen in the hover labels.
141    text: Option<Dim<String>>,
142    /// Sets the positions of the `text` elements with respects to the (x, y)
143    /// coordinates.
144    #[serde(rename = "textposition")]
145    text_position: Option<Dim<Position>>,
146    /// Template string used for rendering the information text that appear on
147    /// points. Note that this will override `textinfo`. Variables are
148    /// inserted using %{variable}, for example "y: %{y}". Numbers are
149    /// formatted using d3-format's syntax %{variable:d3-format}, for example "Price: %{y:$.2f}". See [format](https://github.com/d3/d3-3.x-api-reference/blob/master/Formatting.md#d3)
150    /// for details on the formatting syntax. Dates are formatted using
151    /// d3-time-format's syntax %{variable|d3-time-format}, for example
152    /// "Day: %{2019-01-01|%A}". See [format](https://github.com/d3/d3-3.x-api-reference/blob/master/Time-Formatting.md#format) for details
153    /// on the date formatting syntax. Every attributes that can be specified
154    /// per-point (the ones that are `arrayOk: true`) are available.
155    #[serde(rename = "texttemplate")]
156    text_template: Option<Dim<String>>,
157    /// Sets hover text elements associated with each (x, y, z) triplet. The
158    /// same text will be associated with all data points. To be seen, the
159    /// trace `hover_info` must contain a "Text" flag.
160    #[serde(rename = "hovertext")]
161    hover_text: Option<Dim<String>>,
162    /// Determines which trace information appears on hover. If
163    /// `HoverInfo::None` or `HoverInfo::Skip` are set, no information is
164    /// displayed upon hovering. But, if `HoverInfo::None` is set, click and
165    /// hover events are still fired.
166    #[serde(rename = "hoverinfo")]
167    hover_info: Option<HoverInfo>,
168    /// Template string used for rendering the information that appear on hover
169    /// box. Note that this will override `HoverInfo`. Variables are
170    /// inserted using %{variable}, for example "y: %{y}". Numbers are
171    /// formatted using d3-format's syntax %{variable:d3-format}, for example
172    /// "Price: %{y:$.2f}". <https://github.com/d3/d3-3.x-api-reference/blob/master/Formatting.md#d3_format> for details
173    /// on the formatting syntax. Dates are formatted using d3-time-format's
174    /// syntax %{variable|d3-time-format}, for example "Day:
175    /// %{2019-01-01|%A}". <https://github.com/d3/d3-3.x-api-reference/blob/master/Time-Formatting.md#format> for details
176    /// on the date formatting syntax. The variables available in
177    /// `hovertemplate` are the ones emitted as event data described at this link <https://plotly.com/javascript/plotlyjs-events/#event-data>.
178    /// Additionally, every attributes that can be specified per-point (the ones
179    /// that are `arrayOk: true`) are available. Anything contained in tag
180    /// `<extra>` is displayed in the secondary box, for example
181    /// "<extra>{fullData.name}</extra>". To hide the secondary box
182    /// completely, use an empty tag `<extra></extra>`.
183    #[serde(rename = "hovertemplate")]
184    hover_template: Option<Dim<String>>,
185    /// Sets the hover text formatting rulefor `x` using d3 formatting
186    /// 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
187    /// dates see: <https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format>. We add two items to d3's
188    /// date formatter: "%h" for half of the year as a decimal number as well as
189    /// "%{n}f" for fractional seconds with n digits. For example,
190    /// "2016-10-13 09:15:23.456" with tickformat "%H~%M~%S.%2f" would display
191    /// "09~15~23.46". By default the values are formatted using
192    /// `x_axis.hover_format`.
193    #[serde(rename = "xhoverformat")]
194    x_hover_format: Option<String>,
195    /// Sets the hover text formatting rulefor `y` using d3 formatting
196    /// 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
197    /// dates see: <https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format>. We add two items to d3's
198    /// date formatter: "%h" for half of the year as a decimal number as well as
199    /// "%{n}f" for fractional seconds with n digits. For example,
200    /// "2016-10-13 09:15:23.456" with tickformat "%H~%M~%S.%2f" would display
201    /// "09~15~23.46". By default the values are formatted using
202    /// `y_axis.hover_format`.
203    #[serde(rename = "yhoverformat")]
204    y_hover_format: Option<String>,
205    /// Sets the hover text formatting rulefor `z` using d3 formatting
206    /// 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
207    /// dates see: <https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format>. We add two items to d3's
208    /// date formatter: "%h" for half of the year as a decimal number as well as
209    /// "%{n}f" for fractional seconds with n digits. For example,
210    /// "2016-10-13 09:15:23.456" with tickformat "%H~%M~%S.%2f" would display
211    /// "09~15~23.46". By default the values are formatted using
212    /// `z_axis.hover_format`.
213    #[serde(rename = "zhoverformat")]
214    z_hover_format: Option<String>,
215
216    /// Assigns extra meta information associated with this trace that can be
217    /// used in various text attributes. Attributes such as trace `name`,
218    /// graph, axis and colorbar `title.text`, annotation `text`
219    /// `rangeselector`, `updatemenues` and `sliders` `label` text all support
220    /// `meta`. To access the trace `meta` values in an attribute in the same
221    /// trace, simply use `%{meta[i]}` where `i` is the index or key of the
222    /// `meta` item in question. To access trace `meta` in layout
223    /// attributes, use `%{data[n[.meta[i]}` where `i` is the index or key of
224    /// the `meta` and `n` is the trace index.
225    meta: Option<private::NumOrString>,
226    /// Assigns extra data each datum. This may be useful when listening to
227    /// hover, click and selection events. Note that, "scatter" traces also
228    /// appends customdata items in the markers DOM elements.
229    #[serde(rename = "customdata")]
230    custom_data: Option<private::NumOrStringCollection>,
231    /// Sets a reference between this trace's 3D coordinate system and a 3D
232    /// scene. If "scene" (the default value), the (x,y,z) coordinates refer
233    /// to `layout.scene`. If "scene2", the (x, y, z) coordinates refer to
234    /// `layout.scene2`, and so on.
235    scene: Option<String>,
236    /// Determines how points are displayed and joined.
237    marker: Option<Marker>,
238    /// Line display properties.
239    line: Option<Line>,
240    /// x-axis error display properties.
241    error_x: Option<ErrorData>,
242    /// y-axis error display properties.
243    error_y: Option<ErrorData>,
244    /// z-axis error display properties.
245    error_z: Option<ErrorData>,
246    /// Determines whether or not gaps (i.e. {nan} or missing values) in the
247    /// provided data arrays are connected.
248    #[serde(rename = "connectgaps")]
249    connect_gaps: Option<bool>,
250    /// Properties of label displayed on mouse hover.
251    #[serde(rename = "hoverlabel")]
252    hover_label: Option<Label>,
253    /// Configure the projection for each axis.
254    projection: Option<Projection>,
255    /// If `SurfaceAxis::MinusOne`, the scatter points are not filled with a
256    /// surface. If one of the remaining three variants, the scatter points
257    /// are filled with a Delaunay surface about the x, y, z respectively.
258    #[serde(rename = "surfaceaxis")]
259    surface_axis: Option<SurfaceAxis>,
260    /// Sets the calendar system to use with `x` date data.
261    #[serde(rename = "xcalendar")]
262    x_calendar: Option<Calendar>,
263    /// Sets the calendar system to use with `y` date data.
264    #[serde(rename = "ycalendar")]
265    y_calendar: Option<Calendar>,
266    /// Sets the calendar system to use with `z` date data.
267    #[serde(rename = "zcalendar")]
268    z_calendar: Option<Calendar>,
269}
270
271impl<X, Y, Z> Scatter3D<X, Y, Z>
272where
273    X: Serialize + Default + Clone,
274    Y: Serialize + Default + Clone,
275    Z: Serialize + Default + Clone,
276{
277    pub fn new(x: Vec<X>, y: Vec<Y>, z: Vec<Z>) -> Box<Self> {
278        Box::new(Self {
279            r#type: PlotType::Scatter3D,
280            x: Some(x),
281            y: Some(y),
282            z: Some(z),
283            ..Default::default()
284        })
285    }
286
287    #[cfg(feature = "plotly_ndarray")]
288    pub fn from_array(x: Array<X, Ix1>, y: Array<Y, Ix1>, z: Array<Z, Ix1>) -> Box<Self> {
289        Box::new(Scatter3D {
290            r#type: PlotType::Scatter3D,
291            x: Some(x.to_vec()),
292            y: Some(y.to_vec()),
293            z: Some(z.to_vec()),
294            ..Default::default()
295        })
296    }
297}
298
299impl<X, Y, Z> Trace for Scatter3D<X, Y, Z>
300where
301    X: Serialize + Clone,
302    Y: Serialize + Clone,
303    Z: Serialize + Clone,
304{
305    fn to_json(&self) -> String {
306        serde_json::to_string(&self).unwrap()
307    }
308}
309
310#[cfg(test)]
311mod tests {
312    use serde_json::{json, to_value};
313
314    use super::*;
315    use crate::common::ErrorType;
316
317    #[test]
318    fn serialize_projection() {
319        let projection = Projection::new()
320            .x(ProjectionCoord::new())
321            .y(ProjectionCoord::new())
322            .z(ProjectionCoord::new());
323        let expected = json!({"x": {}, "y": {}, "z": {}});
324
325        assert_eq!(to_value(projection).unwrap(), expected);
326    }
327
328    #[test]
329    fn serialize_projection_coord() {
330        let projection_coord = ProjectionCoord::new().opacity(0.75).scale(5.0).show(false);
331        let expected = json!({"opacity": 0.75, "scale": 5.0, "show": false});
332
333        assert_eq!(to_value(projection_coord).unwrap(), expected);
334    }
335
336    #[test]
337    fn serialize_surface_axis() {
338        assert_eq!(to_value(SurfaceAxis::MinusOne).unwrap(), json!("-1"));
339        assert_eq!(to_value(SurfaceAxis::Zero).unwrap(), json!("0"));
340        assert_eq!(to_value(SurfaceAxis::One).unwrap(), json!("1"));
341        assert_eq!(to_value(SurfaceAxis::Two).unwrap(), json!("2"));
342    }
343
344    #[test]
345    fn serialize_default_scatter3d() {
346        let trace = Scatter3D::<f64, f64, f64>::default();
347        let expected = json!({"type": "scatter3d"}).to_string();
348
349        assert_eq!(trace.to_json(), expected);
350    }
351
352    #[test]
353    fn serialize_scatter3d() {
354        let trace = Scatter3D::new(vec![0, 1], vec![2, 3], vec![4, 5])
355            .connect_gaps(true)
356            .custom_data(vec!["custom_data"])
357            .error_x(ErrorData::new(ErrorType::SquareRoot))
358            .error_y(ErrorData::new(ErrorType::Percent))
359            .error_z(ErrorData::new(ErrorType::Data))
360            .hover_label(Label::new())
361            .hover_text("hover_text")
362            .hover_text_array(vec!["hover_text"])
363            .hover_info(HoverInfo::XAndYAndZ)
364            .hover_template("hover_template")
365            .hover_template_array(vec!["hover_template"])
366            .ids(vec!["1"])
367            .legend_group("legend_group")
368            .legend_rank(1000)
369            .legend_group_title("Legend Group Title")
370            .line(Line::new())
371            .marker(Marker::new())
372            .meta("meta")
373            .mode(Mode::LinesText)
374            .name("trace_name")
375            .opacity(0.2)
376            .projection(Projection::new())
377            .scene("scene2")
378            .show_legend(true)
379            .surface_axis(SurfaceAxis::One)
380            .surface_color("#123456")
381            .text("text")
382            .text_array(vec!["text"])
383            .text_position(Position::BottomLeft)
384            .text_position_array(vec![Position::TopCenter])
385            .text_template("text_template")
386            .text_template_array(vec!["text_template"])
387            .visible(Visible::True)
388            .x_calendar(Calendar::Chinese)
389            .x_hover_format("x_hover_format")
390            .y_calendar(Calendar::Coptic)
391            .y_hover_format("y_hover_format")
392            .z_calendar(Calendar::Ummalqura)
393            .z_hover_format("z_hover_format");
394
395        let expected = json!({
396            "type": "scatter3d",
397            "connectgaps": true,
398            "customdata": ["custom_data"],
399            "error_x": {"type": "sqrt"},
400            "error_y": {"type": "percent"},
401            "error_z": {"type": "data"},
402            "ids": ["1"],
403            "hoverinfo": "x+y+z",
404            "hoverlabel": {},
405            "hovertemplate": ["hover_template"],
406            "hovertext": ["hover_text"],
407            "legendgroup": "legend_group",
408            "legendgrouptitle": {"text": "Legend Group Title"},
409            "legendrank": 1000,
410            "line": {},
411            "marker": {},
412            "meta": "meta",
413            "mode": "lines+text",
414            "name": "trace_name",
415            "opacity": 0.2,
416            "projection": {},
417            "scene": "scene2",
418            "showlegend": true,
419            "surfaceaxis": "1",
420            "surfacecolor": "#123456",
421            "text": ["text"],
422            "textposition": ["top center"],
423            "texttemplate": ["text_template"],
424            "visible": true,
425            "x": [0, 1],
426            "xhoverformat": "x_hover_format",
427            "xcalendar": "chinese",
428            "y": [2, 3],
429            "ycalendar": "coptic",
430            "yhoverformat": "y_hover_format",
431            "z": [4, 5],
432            "zcalendar": "ummalqura",
433            "zhoverformat": "z_hover_format",
434        });
435
436        assert_eq!(to_value(trace).unwrap(), expected);
437    }
438}