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}