plotly_fork/traces/scatter.rs
1//! Scatter trace
2
3#[cfg(feature = "plotly_ndarray")]
4use ndarray::{Array, Ix1, Ix2};
5use plotly_derive::FieldSetter;
6use serde::Serialize;
7
8#[cfg(feature = "plotly_ndarray")]
9use crate::ndarray::ArrayTraces;
10use crate::{
11 color::Color,
12 common::{
13 Calendar, Dim, ErrorData, Fill, Font, HoverInfo, HoverOn, Label, LegendGroupTitle, Line,
14 Marker, Mode, Orientation, PlotType, Position, Visible,
15 },
16 private::{NumOrString, NumOrStringCollection},
17 Trace,
18};
19
20#[derive(Serialize, Clone, Debug)]
21#[serde(rename_all = "lowercase")]
22pub enum GroupNorm {
23 #[serde(rename = "")]
24 Default,
25 Fraction,
26 Percent,
27}
28
29#[derive(Serialize, Clone, Debug)]
30#[serde(rename_all = "lowercase")]
31pub enum StackGaps {
32 #[serde(rename = "infer zero")]
33 InferZero,
34 Interpolate,
35}
36
37/// Construct a scatter trace.
38///
39/// # Examples
40///
41/// ```
42/// use plotly::Scatter;
43///
44/// let trace = Scatter::new(vec![0, 1, 2], vec![2, 1, 0]);
45///
46/// let expected = serde_json::json!({
47/// "type": "scatter",
48/// "x": [0, 1, 2],
49/// "y": [2, 1, 0]
50/// });
51///
52/// assert_eq!(serde_json::to_value(trace).unwrap(), expected);
53/// ```
54#[serde_with::skip_serializing_none]
55#[derive(Serialize, Clone, Debug, FieldSetter)]
56#[field_setter(box_self, kind = "trace")]
57pub struct Scatter<X, Y>
58where
59 X: Serialize + Clone + 'static,
60 Y: Serialize + Clone + 'static,
61{
62 #[field_setter(default = "PlotType::Scatter")]
63 r#type: PlotType,
64 /// Sets the trace name. The trace name appear as the legend item and on
65 /// hover.
66 name: Option<String>,
67 /// Determines whether or not this trace is visible. If
68 /// `Visible::LegendOnly`, the trace is not drawn, but can appear as a
69 /// legend item (provided that the legend itself is visible).
70 visible: Option<Visible>,
71 /// Determines whether or not an item corresponding to this trace is shown
72 /// in the legend.
73 #[serde(rename = "showlegend")]
74 show_legend: Option<bool>,
75 /// Sets the legend group for this trace. Traces part of the same legend
76 /// group hide/show at the same time when toggling legend items.
77 #[serde(rename = "legendgroup")]
78 legend_group: Option<String>,
79 /// Set and style the title to appear for the legend group
80 #[serde(rename = "legendgrouptitle")]
81 legend_group_title: Option<LegendGroupTitle>,
82 /// Sets the opacity of the trace.
83 opacity: Option<f64>,
84 /// Determines the drawing mode for this scatter trace. If the provided
85 /// `Mode` includes "Text" then the `text` elements appear at the
86 /// coordinates. Otherwise, the `text` elements appear on hover. If
87 /// there are less than 20 points and the trace is not stacked then the
88 /// default is `Mode::LinesMarkers`, otherwise it is `Mode::Lines`.
89 mode: Option<Mode>,
90 /// Assigns id labels to each datum. These ids for object constancy of data
91 /// points during animation. Should be an array of strings, not numbers
92 /// or any other type.
93 ids: Option<Vec<String>>,
94 x: Option<Vec<X>>,
95 /// Alternate to `x`. Builds a linear space of x coordinates. Use with `dx`
96 /// where `x0` is the starting coordinate and `dx` the step.
97 x0: Option<NumOrString>,
98 /// Sets the x coordinate step. See `x0` for more info.
99 dx: Option<f64>,
100
101 y: Option<Vec<Y>>,
102
103 /// Alternate to `y`. Builds a linear space of y coordinates. Use with `dy`
104 /// where `y0` is the starting coordinate and `dy` the step.
105 y0: Option<NumOrString>,
106 /// Sets the y coordinate step. See `y0` for more info.
107 dy: Option<f64>,
108
109 /// Sets text elements associated with each (x,y) pair. If a single string,
110 /// the same string appears over all the data points. If an array of
111 /// string, the items are mapped in order to the this trace's (x,y)
112 /// coordinates. If the trace `HoverInfo` contains a "text" flag and
113 /// `hover_text` is not set, these elements will be seen in the hover
114 /// labels.
115 text: Option<Dim<String>>,
116 /// Sets the positions of the `text` elements with respects to the (x,y)
117 /// coordinates.
118 #[serde(rename = "textposition")]
119 text_position: Option<Dim<Position>>,
120 /// Template string used for rendering the information text that appear on
121 /// points. Note that this will override `textinfo`. Variables are
122 /// inserted using %{variable}, for example "y: %{y}". Numbers are
123 /// 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)
124 /// for details on the formatting syntax. Dates are formatted using
125 /// d3-time-format's syntax %{variable|d3-time-format}, for example
126 /// "Day: %{2019-01-01|%A}". See [format](https://github.com/d3/d3-3.x-api-reference/blob/master/Time-Formatting.md#format) for details
127 /// on the date formatting syntax. Every attributes that can be specified
128 /// per-point (the ones that are `arrayOk: true`) are available.
129 #[serde(rename = "texttemplate")]
130 text_template: Option<Dim<String>>,
131 /// Sets hover text elements associated with each (x,y) pair. If a single
132 /// string, the same string appears over all the data points. If an
133 /// array of string, the items are mapped in order to the this trace's
134 /// (x,y) coordinates. To be seen, trace `HoverInfo` must contain a
135 /// "Text" flag.
136 #[serde(rename = "hovertext")]
137 hover_text: Option<Dim<String>>,
138 /// Determines which trace information appear on hover. If `HoverInfo::None`
139 /// or `HoverInfo::Skip` are set, no information is displayed upon
140 /// hovering. But, if `HoverInfo::None` is set, click and hover events
141 /// are still fired.
142 #[serde(rename = "hoverinfo")]
143 hover_info: Option<HoverInfo>,
144 /// Template string used for rendering the information that appear on hover
145 /// box. Note that this will override `HoverInfo`. Variables are
146 /// inserted using %{variable}, for example "y: %{y}". Numbers are
147 /// formatted using d3-format's syntax %{variable:d3-format}, for example
148 /// "Price: %{y:$.2f}".
149 /// https://github.com/d3/d3-3.x-api-reference/blob/master/Formatting.md#d3_format for details
150 /// on the formatting syntax. Dates are formatted using d3-time-format's
151 /// syntax %{variable|d3-time-format}, for example "Day:
152 /// %{2019-01-01|%A}". https://github.com/d3/d3-3.x-api-reference/blob/master/Time-Formatting.md#format for details
153 /// on the date formatting syntax. The variables available in
154 /// `hovertemplate` are the ones emitted as event data described at this link https://plotly.com/javascript/plotlyjs-events/#event-data.
155 /// Additionally, every attributes that can be specified per-point (the ones
156 /// that are `arrayOk: true`) are available. Anything contained in tag
157 /// `<extra>` is displayed in the secondary box, for example
158 /// "<extra>{fullData.name}</extra>". To hide the secondary box
159 /// completely, use an empty tag `<extra></extra>`.
160 #[serde(rename = "hovertemplate")]
161 hover_template: Option<Dim<String>>,
162 /// Assigns extra meta information associated with this trace that can be
163 /// used in various text attributes. Attributes such as trace `name`,
164 /// graph, axis and colorbar `title.text`, annotation `text`
165 /// `rangeselector`, `updatemenues` and `sliders` `label` text all support
166 /// `meta`. To access the trace `meta` values in an attribute in the same
167 /// trace, simply use `%{meta[i]}` where `i` is the index or key of the
168 /// `meta` item in question. To access trace `meta` in layout
169 /// attributes, use `%{data[n[.meta[i]}` where `i` is the index or key of
170 /// the `meta` and `n` is the trace index.
171 meta: Option<NumOrString>,
172 /// Assigns extra data each datum. This may be useful when listening to
173 /// hover, click and selection events. Note that, "scatter" traces also
174 /// appends customdata items in the markers DOM elements
175 #[serde(rename = "customdata")]
176 custom_data: Option<NumOrStringCollection>,
177
178 /// Sets a reference between this trace's x coordinates and a 2D cartesian x
179 /// axis. If "x" ( the default value), the x coordinates refer to
180 /// `Layout::x_axis`. If "x2", the x coordinates
181 /// refer to `Layout::x_axis2`, and so on.
182 #[serde(rename = "xaxis")]
183 x_axis: Option<String>,
184 /// Sets a reference between this trace's y coordinates and a 2D cartesian y
185 /// axis. If "y" (the default value), the y coordinates refer to
186 /// `Layout::y_axis`. If "y2", the y coordinates
187 /// refer to `Layout::y_axis2`, and so on.
188 #[serde(rename = "yaxis")]
189 y_axis: Option<String>,
190 /// Only relevant when `stackgroup` is used, and only the first
191 /// `orientation` found in the `stackgroup` will be used - including if
192 /// `visible` is "legendonly" but not if it is `false`.
193 /// Sets the stacking direction. With "v" ("h"), the y (x) values of
194 /// subsequent traces are added. Also affects the default value of
195 /// `fill`.
196 orientation: Option<Orientation>,
197 /// Only relevant when `stackgroup` is used, and only the first `groupnorm`
198 /// found in the `stackgroup` will be used - including if `visible` is
199 /// "legendonly" but not if it is `false`. Sets the normalization for
200 /// the sum of this `stackgroup`. With "fraction", the value of each
201 /// trace at each location is divided by the sum of all trace values at that
202 /// location. "percent" is the same but multiplied by 100 to show
203 /// percentages. If there are multiple subplots, or
204 /// multiple `stackgroup`s on one subplot, each will be normalized within
205 /// its own set.
206 #[serde(rename = "groupnorm")]
207 group_norm: Option<GroupNorm>,
208 /// Set several scatter traces (on the same subplot) to the same stackgroup
209 /// in order to add their y values (or their x values if `orientation`
210 /// is "h"). If blank or omitted this trace will not be stacked.
211 /// Stacking also turns `fill` on by default, using "tonexty" ("tonextx")
212 /// if `orientation` is "h" ("v") and sets the default `mode` to "lines"
213 /// irrespective of point count. You can only stack on a numeric (linear
214 /// or log) axis. Traces in a `stackgroup` will only fill to (or be
215 /// filled to) other traces in the same group. With multiple `stackgroup`s
216 /// or some traces stacked and some not, if fill-linked traces are not
217 /// already consecutive, the later ones will be pushed down in the
218 /// drawing order.
219 #[serde(rename = "stackgroup")]
220 stack_group: Option<String>,
221 /// Determines how points are displayed and joined.
222 marker: Option<Marker>,
223 /// Line display properties.
224 line: Option<Line>,
225 /// Sets the text font.
226 #[serde(rename = "textfont")]
227 text_font: Option<Font>,
228 /// x-axis error display properties
229 error_x: Option<ErrorData>,
230 /// y-axis error display properties.
231 error_y: Option<ErrorData>,
232 /// Determines whether or not markers and text nodes are clipped about the
233 /// subplot axes. To show markers and text nodes above axis lines and
234 /// tick labels, make sure to set `xaxis.layer` and `yaxis.layer` to
235 /// "below traces".
236 #[serde(rename = "cliponaxis")]
237 clip_on_axis: Option<bool>,
238 /// Determines whether or not gaps (i.e. {nan} or missing values) in the
239 /// provided data arrays are connected.
240 #[serde(rename = "connectgaps")]
241 connect_gaps: Option<bool>,
242 /// Sets the area to fill with a solid color. Defaults to "none" unless this
243 /// trace is stacked, then it gets "tonexty" ("tonextx") if
244 /// `orientation` is "v" ("h") Use with `fillcolor` if not
245 /// "none". "tozerox" and "tozeroy" fill to x=0 and y=0 respectively.
246 /// "tonextx" and "tonexty" fill between the endpoints of this trace and
247 /// the endpoints of the trace before it, connecting those endpoints
248 /// with straight lines (to make a stacked area graph); if there is
249 /// no trace before it, they behave like "tozerox" and "tozeroy". "toself"
250 /// connects the endpoints of the trace (or each segment of the trace if
251 /// it has gaps) into a closed shape. "tonext" fills the space between
252 /// two traces if one completely encloses the other (eg consecutive
253 /// contour lines), and behaves like "toself" if there is no trace before
254 /// it. "tonext" should not be used if one trace does not enclose the
255 /// other. Traces in a `stackgroup` will only fill to (or be filled to)
256 /// other traces in the same group. With multiple `stackgroup`s or some
257 /// traces stacked and some not, if fill-linked traces are not
258 /// already consecutive, the later ones will be pushed down in the drawing
259 /// order.
260 fill: Option<Fill>,
261 /// Sets the fill color. Defaults to a half-transparent variant of the line
262 /// color, marker color, or marker line color, whichever is available.
263 #[serde(rename = "fillcolor")]
264 fill_color: Option<Box<dyn Color>>,
265 /// Properties of label displayed on mouse hover.
266 #[serde(rename = "hoverlabel")]
267 hover_label: Option<Label>,
268 /// Do the hover effects highlight individual points (markers or line
269 /// points) or do they highlight filled regions? If the fill is "toself"
270 /// or "tonext" and there are no markers or text, then the default is
271 /// "fills", otherwise it is "points".
272 #[serde(rename = "hoveron")]
273 hover_on: Option<HoverOn>,
274 /// Only relevant when `stack_group` is used, and only the first
275 /// `stack_gaps` found in the `stackgroup` will be used - including if
276 /// `visible` is set to `Visible::LegendOnly` but not if it is set to
277 /// `Visible::False`. Determines how we handle locations at which other
278 /// traces in this group have data but this one does not. With "infer
279 /// zero" we insert a zero at these locations. With "interpolate" we
280 /// linearly interpolate between existing values, and extrapolate a constant
281 /// beyond the existing values.
282 #[serde(rename = "stackgaps")]
283 stack_gaps: Option<StackGaps>,
284 /// Sets the calendar system to use with `x` date data.
285 #[serde(rename = "xcalendar")]
286 x_calendar: Option<Calendar>,
287 /// Sets the calendar system to use with `y` date data.
288 #[serde(rename = "ycalendar")]
289 y_calendar: Option<Calendar>,
290}
291
292impl<X, Y> Scatter<X, Y>
293where
294 X: Serialize + Clone + 'static,
295 Y: Serialize + Clone + 'static,
296{
297 pub fn new(x: Vec<X>, y: Vec<Y>) -> Box<Self> {
298 Box::new(Self {
299 x: Some(x),
300 y: Some(y),
301 ..Default::default()
302 })
303 }
304
305 #[cfg(feature = "plotly_ndarray")]
306 pub fn from_array(x: Array<X, Ix1>, y: Array<Y, Ix1>) -> Box<Self> {
307 Box::new(Scatter {
308 x: Some(x.to_vec()),
309 y: Some(y.to_vec()),
310 ..Default::default()
311 })
312 }
313
314 /// Produces `Scatter` traces from a 2 dimensional tensor (`traces_matrix`)
315 /// indexed by `x`. This function requires the `ndarray` feature.
316 ///
317 /// # Arguments
318 /// * `x` - One dimensional array (or view) that represents the
319 /// `x` axis coordinates.
320 /// * `traces_matrix` - Two dimensional array (or view) containing the `y`
321 /// axis coordinates of
322 /// the traces.
323 /// * `array_traces` - Determines whether the traces are arranged in the
324 /// matrix over the
325 /// columns (`ArrayTraces::OverColumns`) or over the rows
326 /// (`ArrayTraces::OverRows`).
327 ///
328 /// # Examples
329 ///
330 /// ```
331 /// use plotly::common::Mode;
332 /// use plotly::{Plot, Scatter, ArrayTraces};
333 /// use ndarray::{Array, Ix1, Ix2};
334 ///
335 /// fn ndarray_to_traces() {
336 /// let n: usize = 11;
337 /// let t: Array<f64, Ix1> = Array::range(0., 10., 10. / n as f64);
338 /// let mut ys: Array<f64, Ix2> = Array::zeros((11, 11));
339 /// let mut count = 0.;
340 /// for mut row in ys.columns_mut() {
341 /// for index in 0..row.len() {
342 /// row[index] = count + (index as f64).powf(2.);
343 /// }
344 /// count += 1.;
345 /// }
346 ///
347 /// let traces = Scatter::default()
348 /// .mode(Mode::LinesMarkers)
349 /// .to_traces(t, ys, ArrayTraces::OverColumns);
350 ///
351 /// let mut plot = Plot::new();
352 /// plot.add_traces(traces);
353 ///
354 /// # if false { // Skip running this line in doctests.
355 /// plot.show();
356 /// # }
357 /// }
358 /// fn main() -> std::io::Result<()> {
359 /// ndarray_to_traces();
360 /// Ok(())
361 /// }
362 /// ```
363 #[cfg(feature = "plotly_ndarray")]
364 pub fn to_traces(
365 &self,
366 x: Array<X, Ix1>,
367 traces_matrix: Array<Y, Ix2>,
368 array_traces: ArrayTraces,
369 ) -> Vec<Box<dyn Trace>> {
370 let mut traces: Vec<Box<dyn Trace>> = Vec::new();
371 let mut trace_vectors = crate::private::trace_vectors_from(traces_matrix, array_traces);
372 trace_vectors.reverse();
373 while !trace_vectors.is_empty() {
374 let mut sc = Box::new(self.clone());
375 sc.x = Some(x.to_vec());
376 let data = trace_vectors.pop();
377 if let Some(d) = data {
378 sc.y = Some(d);
379 traces.push(sc);
380 }
381 }
382
383 traces
384 }
385
386 /// Enables WebGL.
387 pub fn web_gl_mode(mut self, on: bool) -> Box<Self> {
388 self.r#type = if on {
389 PlotType::ScatterGL
390 } else {
391 PlotType::Scatter
392 };
393 Box::new(self)
394 }
395}
396
397impl<X, Y> Trace for Scatter<X, Y>
398where
399 X: Serialize + Clone + 'static,
400 Y: Serialize + Clone + 'static,
401{
402 fn to_json(&self) -> String {
403 serde_json::to_string(self).unwrap()
404 }
405}
406
407#[cfg(test)]
408mod tests {
409 use serde_json::{json, to_value};
410
411 use super::*;
412
413 #[test]
414 fn test_serialize_group_norm() {
415 assert_eq!(to_value(GroupNorm::Default).unwrap(), json!(""));
416 assert_eq!(to_value(GroupNorm::Fraction).unwrap(), json!("fraction"));
417 assert_eq!(to_value(GroupNorm::Percent).unwrap(), json!("percent"));
418 }
419
420 #[test]
421 #[rustfmt::skip]
422 fn test_serialize_stack_gaps() {
423 assert_eq!(to_value(StackGaps::InferZero).unwrap(), json!("infer zero"));
424 assert_eq!(to_value(StackGaps::Interpolate).unwrap(), json!("interpolate"));
425 }
426
427 #[test]
428 fn test_serialize_default_scatter() {
429 let trace = Scatter::<u32, u32>::default();
430 let expected = json!({"type": "scatter"});
431
432 assert_eq!(to_value(trace).unwrap(), expected);
433 }
434
435 #[test]
436 fn test_serialize_scatter() {
437 use crate::common::ErrorType;
438
439 let trace = Scatter::new(vec![0, 1], vec![2, 3])
440 .clip_on_axis(true)
441 .connect_gaps(false)
442 .custom_data(vec!["custom_data"])
443 .error_x(ErrorData::new(ErrorType::Percent))
444 .error_y(ErrorData::new(ErrorType::Data))
445 .dx(1.0)
446 .dy(4.0)
447 .fill(Fill::ToNext)
448 .fill_color("#789456")
449 .group_norm(GroupNorm::Default)
450 .hover_info(HoverInfo::Name)
451 .hover_label(Label::new())
452 .hover_on(HoverOn::Points)
453 .hover_text("hover_text")
454 .hover_text_array(vec!["hover_text"])
455 .hover_template("hover_template")
456 .hover_template_array(vec!["hover_template"])
457 .ids(vec!["1"])
458 .legend_group("legend_group")
459 .legend_group_title(LegendGroupTitle::new("Legend Group Title"))
460 .line(Line::new())
461 .marker(Marker::new())
462 .meta("meta")
463 .mode(Mode::LinesMarkers)
464 .name("scatter_trace")
465 .opacity(0.6)
466 .orientation(Orientation::Horizontal)
467 .show_legend(false)
468 .stack_gaps(StackGaps::InferZero)
469 .stack_group("stack_group")
470 .text("text")
471 .text_array(vec!["text"])
472 .text_font(Font::new())
473 .text_position(Position::MiddleCenter)
474 .text_position_array(vec![Position::MiddleLeft])
475 .text_template("text_template")
476 .text_template_array(vec!["text_template"])
477 .visible(Visible::True)
478 .x_axis("x_axis")
479 .x_calendar(Calendar::Chinese)
480 .x0(0)
481 .y_axis("y_axis")
482 .y_calendar(Calendar::Coptic)
483 .y0(2)
484 .web_gl_mode(true);
485
486 let expected = json!({
487 "type": "scattergl",
488 "x": [0, 1],
489 "y": [2, 3],
490 "cliponaxis": true,
491 "connectgaps": false,
492 "customdata": ["custom_data"],
493 "error_x": {"type": "percent"},
494 "error_y": {"type": "data"},
495 "dx": 1.0,
496 "dy": 4.0,
497 "fill": "tonext",
498 "fillcolor": "#789456",
499 "groupnorm": "",
500 "hoverinfo": "name",
501 "hoverlabel": {},
502 "hoveron": "points",
503 "hovertext": ["hover_text"],
504 "hovertemplate": ["hover_template"],
505 "ids": ["1"],
506 "legendgroup": "legend_group",
507 "legendgrouptitle": {"text": "Legend Group Title"},
508 "line": {},
509 "marker": {},
510 "meta": "meta",
511 "mode": "lines+markers",
512 "name": "scatter_trace",
513 "opacity": 0.6,
514 "orientation": "h",
515 "showlegend": false,
516 "stackgaps": "infer zero",
517 "stackgroup": "stack_group",
518 "text": ["text"],
519 "textfont": {},
520 "textposition": ["middle left"],
521 "texttemplate": ["text_template"],
522 "visible": true,
523 "xaxis": "x_axis",
524 "xcalendar": "chinese",
525 "x0": 0,
526 "yaxis": "y_axis",
527 "ycalendar": "coptic",
528 "y0": 2
529 });
530
531 assert_eq!(to_value(trace).unwrap(), expected);
532 }
533}