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}