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}