plotly_fork/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 datas 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}".
173    /// https://github.com/d3/d3-3.x-api-reference/blob/master/Formatting.md#d3_format for details
174    /// on the formatting syntax. Dates are formatted using d3-time-format's
175    /// syntax %{variable|d3-time-format}, for example "Day:
176    /// %{2019-01-01|%A}". https://github.com/d3/d3-3.x-api-reference/blob/master/Time-Formatting.md#format for details
177    /// on the date formatting syntax. The variables available in
178    /// `hovertemplate` are the ones emitted as event data described at this link https://plotly.com/javascript/plotlyjs-events/#event-data.
179    /// Additionally, every attributes that can be specified per-point (the ones
180    /// that are `arrayOk: true`) are available. Anything contained in tag
181    /// `<extra>` is displayed in the secondary box, for example
182    /// "<extra>{fullData.name}</extra>". To hide the secondary box
183    /// completely, use an empty tag `<extra></extra>`.
184    #[serde(rename = "hovertemplate")]
185    hover_template: Option<Dim<String>>,
186    /// Sets the hover text formatting rulefor `x` using d3 formatting
187    /// 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
188    /// dates see: https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format. We add two items to d3's
189    /// date formatter: "%h" for half of the year as a decimal number as well as
190    /// "%{n}f" for fractional seconds with n digits. For example,
191    /// "2016-10-13 09:15:23.456" with tickformat "%H~%M~%S.%2f" would display
192    /// "09~15~23.46". By default the values are formatted using
193    /// `x_axis.hover_format`.
194    #[serde(rename = "xhoverformat")]
195    x_hover_format: Option<String>,
196    /// Sets the hover text formatting rulefor `y` using d3 formatting
197    /// 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
198    /// dates see: https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format. We add two items to d3's
199    /// date formatter: "%h" for half of the year as a decimal number as well as
200    /// "%{n}f" for fractional seconds with n digits. For example,
201    /// "2016-10-13 09:15:23.456" with tickformat "%H~%M~%S.%2f" would display
202    /// "09~15~23.46". By default the values are formatted using
203    /// `y_axis.hover_format`.
204    #[serde(rename = "yhoverformat")]
205    y_hover_format: Option<String>,
206    /// Sets the hover text formatting rulefor `z` 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
208    /// dates see: https://github.com/d3/d3-time-format/tree/v2.2.3#locale_format. We add two items to d3's
209    /// date 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    /// `z_axis.hover_format`.
214    #[serde(rename = "zhoverformat")]
215    z_hover_format: Option<String>,
216
217    /// Assigns extra meta information associated with this trace that can be
218    /// used in various text attributes. Attributes such as trace `name`,
219    /// graph, axis and colorbar `title.text`, annotation `text`
220    /// `rangeselector`, `updatemenues` and `sliders` `label` text all support
221    /// `meta`. To access the trace `meta` values in an attribute in the same
222    /// trace, simply use `%{meta[i]}` where `i` is the index or key of the
223    /// `meta` item in question. To access trace `meta` in layout
224    /// attributes, use `%{data[n[.meta[i]}` where `i` is the index or key of
225    /// the `meta` and `n` is the trace index.
226    meta: Option<private::NumOrString>,
227    /// Assigns extra data each datum. This may be useful when listening to
228    /// hover, click and selection events. Note that, "scatter" traces also
229    /// appends customdata items in the markers DOM elements.
230    #[serde(rename = "customdata")]
231    custom_data: Option<private::NumOrStringCollection>,
232    /// Sets a reference between this trace's 3D coordinate system and a 3D
233    /// scene. If "scene" (the default value), the (x,y,z) coordinates refer
234    /// to `layout.scene`. If "scene2", the (x, y, z) coordinates refer to
235    /// `layout.scene2`, and so on.
236    scene: Option<String>,
237    /// Determines how points are displayed and joined.
238    marker: Option<Marker>,
239    /// Line display properties.
240    line: Option<Line>,
241    /// x-axis error display properties.
242    error_x: Option<ErrorData>,
243    /// y-axis error display properties.
244    error_y: Option<ErrorData>,
245    /// z-axis error display properties.
246    error_z: Option<ErrorData>,
247    /// Determines whether or not gaps (i.e. {nan} or missing values) in the
248    /// provided data arrays are connected.
249    #[serde(rename = "connectgaps")]
250    connect_gaps: Option<bool>,
251    /// Properties of label displayed on mouse hover.
252    #[serde(rename = "hoverlabel")]
253    hover_label: Option<Label>,
254    /// Configure the projection for each axis.
255    projection: Option<Projection>,
256    /// If `SurfaceAxis::MinusOne`, the scatter points are not filled with a
257    /// surface. If one of the remaining three variants, the scatter points
258    /// are filled with a Delaunay surface about the x, y, z respectively.
259    #[serde(rename = "surfaceaxis")]
260    surface_axis: Option<SurfaceAxis>,
261    /// Sets the calendar system to use with `x` date data.
262    #[serde(rename = "xcalendar")]
263    x_calendar: Option<Calendar>,
264    /// Sets the calendar system to use with `y` date data.
265    #[serde(rename = "ycalendar")]
266    y_calendar: Option<Calendar>,
267    /// Sets the calendar system to use with `z` date data.
268    #[serde(rename = "zcalendar")]
269    z_calendar: Option<Calendar>,
270}
271
272impl<X, Y, Z> Scatter3D<X, Y, Z>
273where
274    X: Serialize + Default + Clone,
275    Y: Serialize + Default + Clone,
276    Z: Serialize + Default + Clone,
277{
278    pub fn new(x: Vec<X>, y: Vec<Y>, z: Vec<Z>) -> Box<Self> {
279        Box::new(Self {
280            r#type: PlotType::Scatter3D,
281            x: Some(x),
282            y: Some(y),
283            z: Some(z),
284            ..Default::default()
285        })
286    }
287
288    #[cfg(feature = "plotly_ndarray")]
289    pub fn from_array(x: Array<X, Ix1>, y: Array<Y, Ix1>, z: Array<Z, Ix1>) -> Box<Self> {
290        Box::new(Scatter3D {
291            r#type: PlotType::Scatter3D,
292            x: Some(x.to_vec()),
293            y: Some(y.to_vec()),
294            z: Some(z.to_vec()),
295            ..Default::default()
296        })
297    }
298}
299
300impl<X, Y, Z> Trace for Scatter3D<X, Y, Z>
301where
302    X: Serialize + Clone,
303    Y: Serialize + Clone,
304    Z: Serialize + Clone,
305{
306    fn to_json(&self) -> String {
307        serde_json::to_string(&self).unwrap()
308    }
309}
310
311#[cfg(test)]
312mod tests {
313    use serde_json::{json, to_value};
314
315    use super::*;
316    use crate::common::ErrorType;
317
318    #[test]
319    fn test_serialize_projection() {
320        let projection = Projection::new()
321            .x(ProjectionCoord::new())
322            .y(ProjectionCoord::new())
323            .z(ProjectionCoord::new());
324        let expected = json!({"x": {}, "y": {}, "z": {}});
325
326        assert_eq!(to_value(projection).unwrap(), expected);
327    }
328
329    #[test]
330    fn test_serialize_projection_coord() {
331        let projection_coord = ProjectionCoord::new().opacity(0.75).scale(5.0).show(false);
332        let expected = json!({"opacity": 0.75, "scale": 5.0, "show": false});
333
334        assert_eq!(to_value(projection_coord).unwrap(), expected);
335    }
336
337    #[test]
338    fn test_serialize_surface_axis() {
339        assert_eq!(to_value(SurfaceAxis::MinusOne).unwrap(), json!("-1"));
340        assert_eq!(to_value(SurfaceAxis::Zero).unwrap(), json!("0"));
341        assert_eq!(to_value(SurfaceAxis::One).unwrap(), json!("1"));
342        assert_eq!(to_value(SurfaceAxis::Two).unwrap(), json!("2"));
343    }
344
345    #[test]
346    fn test_serialize_default_scatter3d() {
347        let trace = Scatter3D::<f64, f64, f64>::default();
348        let expected = json!({"type": "scatter3d"}).to_string();
349
350        assert_eq!(trace.to_json(), expected);
351    }
352
353    #[test]
354    fn test_serialize_scatter3d() {
355        let trace = Scatter3D::new(vec![0, 1], vec![2, 3], vec![4, 5])
356            .connect_gaps(true)
357            .custom_data(vec!["custom_data"])
358            .error_x(ErrorData::new(ErrorType::SquareRoot))
359            .error_y(ErrorData::new(ErrorType::Percent))
360            .error_z(ErrorData::new(ErrorType::Data))
361            .hover_label(Label::new())
362            .hover_text("hover_text")
363            .hover_text_array(vec!["hover_text"])
364            .hover_info(HoverInfo::XAndYAndZ)
365            .hover_template("hover_template")
366            .hover_template_array(vec!["hover_template"])
367            .ids(vec!["1"])
368            .legend_group("legend_group")
369            .legend_rank(1000)
370            .legend_group_title(LegendGroupTitle::new("Legend Group Title"))
371            .line(Line::new())
372            .marker(Marker::new())
373            .meta("meta")
374            .mode(Mode::LinesText)
375            .name("trace_name")
376            .opacity(0.2)
377            .projection(Projection::new())
378            .scene("scene2")
379            .show_legend(true)
380            .surface_axis(SurfaceAxis::One)
381            .surface_color("#123456")
382            .text("text")
383            .text_array(vec!["text"])
384            .text_position(Position::BottomLeft)
385            .text_position_array(vec![Position::TopCenter])
386            .text_template("text_template")
387            .text_template_array(vec!["text_template"])
388            .visible(Visible::True)
389            .x_calendar(Calendar::Chinese)
390            .x_hover_format("x_hover_format")
391            .y_calendar(Calendar::Coptic)
392            .y_hover_format("y_hover_format")
393            .z_calendar(Calendar::Ummalqura)
394            .z_hover_format("z_hover_format");
395
396        let expected = json!({
397            "type": "scatter3d",
398            "connectgaps": true,
399            "customdata": ["custom_data"],
400            "error_x": {"type": "sqrt"},
401            "error_y": {"type": "percent"},
402            "error_z": {"type": "data"},
403            "ids": ["1"],
404            "hoverinfo": "x+y+z",
405            "hoverlabel": {},
406            "hovertemplate": ["hover_template"],
407            "hovertext": ["hover_text"],
408            "legendgroup": "legend_group",
409            "legendgrouptitle": {"text": "Legend Group Title"},
410            "legendrank": 1000,
411            "line": {},
412            "marker": {},
413            "meta": "meta",
414            "mode": "lines+text",
415            "name": "trace_name",
416            "opacity": 0.2,
417            "projection": {},
418            "scene": "scene2",
419            "showlegend": true,
420            "surfaceaxis": "1",
421            "surfacecolor": "#123456",
422            "text": ["text"],
423            "textposition": ["top center"],
424            "texttemplate": ["text_template"],
425            "visible": true,
426            "x": [0, 1],
427            "xhoverformat": "x_hover_format",
428            "xcalendar": "chinese",
429            "y": [2, 3],
430            "ycalendar": "coptic",
431            "yhoverformat": "y_hover_format",
432            "z": [4, 5],
433            "zcalendar": "ummalqura",
434            "zhoverformat": "z_hover_format",
435        });
436
437        assert_eq!(to_value(trace).unwrap(), expected);
438    }
439}