plotly_fork/traces/
image.rs

1//! Image plot
2
3#[cfg(feature = "plotly_image")]
4use image::{Pixel, RgbImage, RgbaImage};
5#[cfg(feature = "plotly_ndarray")]
6use ndarray::{Array, Ix2};
7use plotly_derive::FieldSetter;
8use serde::Serialize;
9
10use crate::color::{Rgb, Rgba};
11use crate::common::{Dim, HoverInfo, Label, LegendGroupTitle, PlotType, Visible};
12use crate::private::{NumOrString, NumOrStringCollection};
13use crate::Trace;
14
15#[derive(Serialize, Clone, Copy, Debug)]
16#[serde(untagged)]
17pub enum PixelColor {
18    Color3(u8, u8, u8),
19    Color4(u8, u8, u8, f64),
20}
21
22/// A marker trait allowing several ways to describe the pixel data for an
23/// `Image` trace.
24pub trait ImageData {
25    fn to_image_data(&self) -> Vec<Vec<PixelColor>>;
26}
27
28impl ImageData for Vec<Vec<Rgb>> {
29    fn to_image_data(&self) -> Vec<Vec<PixelColor>> {
30        let mut out = Vec::with_capacity(self.len());
31        for row in self {
32            let mut new_row = Vec::with_capacity(row.len());
33            for pixel in row {
34                new_row.push(PixelColor::Color3(pixel.r, pixel.g, pixel.b))
35            }
36            out.push(new_row);
37        }
38        out
39    }
40}
41
42impl ImageData for Vec<Vec<Rgba>> {
43    fn to_image_data(&self) -> Vec<Vec<PixelColor>> {
44        let mut out = Vec::with_capacity(self.len());
45        for row in self {
46            let mut new_row = Vec::with_capacity(row.len());
47            for pixel in row {
48                new_row.push(PixelColor::Color4(pixel.r, pixel.g, pixel.b, pixel.a))
49            }
50            out.push(new_row);
51        }
52        out
53    }
54}
55
56#[cfg(feature = "plotly_image")]
57impl ImageData for RgbImage {
58    fn to_image_data(&self) -> Vec<Vec<PixelColor>> {
59        let mut out = Vec::with_capacity(self.height() as usize);
60        for row in self.rows() {
61            let mut new_row = Vec::with_capacity(row.len());
62            for pixel in row {
63                let ch = pixel.channels();
64                new_row.push(PixelColor::Color3(ch[0], ch[1], ch[2]))
65            }
66            out.push(new_row);
67        }
68        out
69    }
70}
71
72#[cfg(feature = "plotly_image")]
73impl ImageData for RgbaImage {
74    fn to_image_data(&self) -> Vec<Vec<PixelColor>> {
75        let mut out = Vec::with_capacity(self.height() as usize);
76        for row in self.rows() {
77            let mut new_row = Vec::with_capacity(row.len());
78            for pixel in row {
79                let ch = pixel.channels();
80                new_row.push(PixelColor::Color4(
81                    ch[0],
82                    ch[1],
83                    ch[2],
84                    ch[3] as f64 / 255.0,
85                ))
86            }
87            out.push(new_row);
88        }
89        out
90    }
91}
92
93#[cfg(feature = "plotly_ndarray")]
94impl ImageData for Array<(u8, u8, u8), Ix2> {
95    fn to_image_data(&self) -> Vec<Vec<PixelColor>> {
96        let height = self.shape()[0];
97        let mut out = Vec::with_capacity(height);
98        let pixels = self.map(|p| PixelColor::Color3(p.0, p.1, p.2));
99        for row in pixels.rows() {
100            out.push(row.to_vec());
101        }
102        out
103    }
104}
105
106#[cfg(feature = "plotly_ndarray")]
107impl ImageData for Array<(u8, u8, u8, f64), Ix2> {
108    fn to_image_data(&self) -> Vec<Vec<PixelColor>> {
109        let height = self.shape()[0];
110        let mut out = Vec::with_capacity(height);
111        let pixels = self.map(|p| PixelColor::Color4(p.0, p.1, p.2, p.3));
112        for row in pixels.rows() {
113            out.push(row.to_vec());
114        }
115        out
116    }
117}
118
119#[derive(Serialize, Clone, Debug)]
120#[serde(rename_all = "lowercase")]
121pub enum ColorModel {
122    RGB,
123    RGBA,
124    RGBA256,
125    HSL,
126    HSLA,
127}
128
129#[derive(Clone, Debug)]
130pub enum ZSmooth {
131    Fast,
132    False,
133}
134
135impl Serialize for ZSmooth {
136    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
137    where
138        S: serde::Serializer,
139    {
140        match self {
141            Self::Fast => serializer.serialize_str("fast"),
142            Self::False => serializer.serialize_bool(false),
143        }
144    }
145}
146
147/// Construct an image trace.
148///
149/// # Examples
150///
151/// ```
152/// use plotly::{color::Rgb, image::ColorModel, Image};
153///
154/// let b = Rgb::new(0, 0, 0);
155/// let w = Rgb::new(255, 255, 255);
156///
157/// let z = vec![
158///     vec![b, w],
159///     vec![w, b],
160/// ];
161///
162/// let trace = Image::new(z).color_model(ColorModel::RGB);
163///
164/// let expected = serde_json::json!({
165///     "type": "image",
166///     "z": [[[0, 0, 0], [255, 255, 255]], [[255, 255, 255], [0, 0, 0]]],
167///     "colormodel": "rgb"
168/// });
169///
170/// assert_eq!(serde_json::to_value(trace).unwrap(), expected);
171/// ```
172#[serde_with::skip_serializing_none]
173#[derive(Serialize, Clone, Debug, FieldSetter)]
174#[field_setter(box_self, kind = "trace")]
175pub struct Image {
176    #[field_setter(default = "PlotType::Image")]
177    r#type: PlotType,
178
179    #[field_setter(skip)]
180    z: Option<Vec<Vec<PixelColor>>>,
181
182    /// Sets the trace name. The trace name appear as the legend item and on
183    /// hover.
184    name: Option<String>,
185    /// Determines whether or not this trace is visible. If
186    /// `Visible::LegendOnly`, the trace is not drawn, but can appear as a
187    /// legend item (provided that the legend itself is visible).
188    visible: Option<Visible>,
189
190    /// Sets the legend rank for this trace. Items and groups with smaller ranks
191    /// are presented on top/left side while with `"reversed"
192    /// `legend.trace_order` they are on bottom/right side. The default
193    /// legendrank is 1000, so that you can use ranks less than 1000 to
194    /// place certain items before all unranked items, and ranks greater
195    /// than 1000 to go after all unranked items.
196    #[serde(rename = "legendrank")]
197    legend_rank: Option<usize>,
198    /// Set and style the title to appear for the legend group.
199    #[serde(rename = "legendgrouptitle")]
200    legend_group_title: Option<LegendGroupTitle>,
201
202    /// Sets the opacity of the trace.
203    opacity: Option<f64>,
204    /// Assigns id labels to each datum. These ids for object constancy of data
205    /// points during animation. Should be an array of strings, not numbers
206    /// or any other type.
207    ids: Option<Vec<String>>,
208
209    /// Set the image's x position.
210    x0: Option<NumOrString>,
211    /// Set the pixel's horizontal size.
212    dx: Option<f64>,
213
214    /// Set the image's y position.
215    y0: Option<NumOrString>,
216    /// Set the pixel's vertical size.
217    dy: Option<f64>,
218
219    /// Specifies the data URI of the image to be visualized. The URI consists
220    /// of "data:image/[<media subtype>][;base64],<data>".
221    source: Option<String>,
222
223    /// Sets text elements associated with each (x,y) pair. If a single string,
224    /// the same string appears over all the data points. If an array of
225    /// strings the items are mapped in order to the this trace's (x,y)
226    /// coordinates. If the trace `HoverInfo` contains a "text" flag and
227    /// `hover_text` is not set, these elements will be seen in the hover
228    /// labels.
229    text: Option<Dim<String>>,
230    /// Sets hover text elements associated with each (x,y) pair. If a single
231    /// string, the same string appears over all the data points. If an
232    /// array of strings, the items are mapped in order to the this trace's
233    /// (x,y) coordinates. To be seen, trace `HoverInfo` must contain a
234    /// "Text" flag.
235    #[serde(rename = "hovertext")]
236    hover_text: Option<Dim<String>>,
237    /// Determines which trace information appear on hover. If `HoverInfo::None`
238    /// or `HoverInfo::Skip` are set, no information is displayed upon
239    /// hovering. But, if `HoverInfo::None` is set, click and hover events
240    /// are still fired.
241    #[serde(rename = "hoverinfo")]
242    hover_info: Option<HoverInfo>,
243    /// Template string used for rendering the information that appear on hover
244    /// box. Note that this will override `HoverInfo`. Variables are
245    /// inserted using %{variable}, for example "y: %{y}". Numbers are
246    /// formatted using d3-format's syntax %{variable:d3-format}, for example
247    /// "Price: %{y:$.2f}".
248    /// https://github.com/d3/d3-3.x-api-reference/blob/master/Formatting.md#d3_format for details
249    /// on the formatting syntax. Dates are formatted using d3-time-format's
250    /// syntax %{variable|d3-time-format}, for example "Day:
251    /// %{2019-01-01|%A}". https://github.com/d3/d3-3.x-api-reference/blob/master/Time-Formatting.md#format for details
252    /// on the date formatting syntax. The variables available in
253    /// `hovertemplate` are the ones emitted as event data described at this link https://plotly.com/javascript/plotlyjs-events/#event-data.
254    /// Additionally, every attributes that can be specified per-point (the ones
255    /// that are `arrayOk: true`) are available. Anything contained in tag
256    /// `<extra>` is displayed in the secondary box, for example
257    /// "<extra>{fullData.name}</extra>". To hide the secondary box
258    /// completely, use an empty tag `<extra></extra>`.
259    #[serde(rename = "hovertemplate")]
260    hover_template: Option<Dim<String>>,
261
262    /// Assigns extra meta information associated with this trace that can be
263    /// used in various text attributes. Attributes such as trace `name`,
264    /// graph, axis and colorbar `title.text`, annotation `text`
265    /// `rangeselector`, `updatemenues` and `sliders` `label` text all support
266    /// `meta`. To access the trace `meta` values in an attribute in the same
267    /// trace, simply use `%{meta[i]}` where `i` is the index or key of the
268    /// `meta` item in question. To access trace `meta` in layout
269    /// attributes, use `%{data[n[.meta[i]}` where `i` is the index or key of
270    /// the `meta` and `n` is the trace index.
271    meta: Option<NumOrString>,
272    /// Assigns extra data each datum. This may be useful when listening to
273    /// hover, click and selection events. Note that, "scatter" traces also
274    /// appends customdata items in the markers DOM elements
275    #[serde(rename = "customdata")]
276    custom_data: Option<NumOrStringCollection>,
277
278    /// Sets a reference between this trace's x coordinates and a 2D cartesian x
279    /// axis. If "x" ( the default value), the x coordinates refer to
280    /// `Layout::x_axis`. If "x2", the x coordinates
281    /// refer to `Layout::x_axis2`, and so on.
282    #[serde(rename = "xaxis")]
283    x_axis: Option<String>,
284    /// Sets a reference between this trace's y coordinates and a 2D cartesian y
285    /// axis. If "y" (the default value), the y coordinates refer to
286    /// `Layout::y_axis`. If "y2", the y coordinates
287    /// refer to `Layout::y_axis2`, and so on.
288    #[serde(rename = "yaxis")]
289    y_axis: Option<String>,
290
291    /// Color model used to map the numerical color components described in `z`
292    /// into colors. If `source` is specified, this attribute will be set to
293    /// `rgba256` otherwise it defaults to `rgb`.
294    #[serde(rename = "colormodel")]
295    color_model: Option<ColorModel>,
296
297    #[field_setter(skip)]
298    #[serde(rename = "zmax")]
299    z_max: Option<Vec<Vec<PixelColor>>>,
300
301    #[field_setter(skip)]
302    #[serde(rename = "zmin")]
303    z_min: Option<Vec<Vec<PixelColor>>>,
304
305    /// Picks a smoothing algorithm used to smooth `z` data. This only applies
306    /// for image traces that use the `source` attribute.
307    #[serde(rename = "zsmooth")]
308    z_smooth: Option<ZSmooth>,
309
310    /// Properties of label displayed on mouse hover.
311    #[serde(rename = "hoverlabel")]
312    hover_label: Option<Label>,
313
314    /// Controls persistence of some user-driven changes to the trace:
315    /// `constraintrange` in `parcoords` traces, as well as some `editable:
316    /// True` modifications such as `name` and `colorbar.title`. Defaults to
317    /// `layout.uirevision`. Note that other user-driven trace attribute changes
318    /// are controlled by `layout` attributes: `trace.visible` is controlled
319    /// by `layout.legend.uirevision`, `selectedpoints` is controlled
320    /// by `layout.selectionrevision`, and `colorbar.(x|y)` (accessible with
321    /// `config: {editable: True}`) is controlled by `layout.editrevision`.
322    /// Trace changes are tracked by `uid`, which only falls back on trace
323    /// index if no `uid` is provided. So if your app can add/remove traces
324    /// before the end of the `data` array, such that the same trace has a
325    /// different index, you can still preserve user-driven changes if you give
326    /// each trace a `uid` that stays with it as it moves.
327    #[serde(rename = "uirevision")]
328    ui_revision: Option<NumOrString>,
329}
330
331impl Image {
332    /// A 2-dimensional array in which each element is an array of 3 or 4
333    /// numbers representing a color.
334    pub fn new(z: impl ImageData) -> Box<Self> {
335        Box::new(Self {
336            z: Some(z.to_image_data()),
337            ..Default::default()
338        })
339    }
340
341    /// Array defining the higher bound for each color component. Note that the
342    /// default value will depend on the colormodel. For the `rgb`
343    /// colormodel, it is [255, 255, 255]. For the `rgba` colormodel, it is
344    /// [255, 255, 255, 1]. For the `rgba256` colormodel, it is [255, 255, 255,
345    /// 255]. For the `hsl` colormodel, it is [360, 100, 100]. For the
346    /// `hsla` colormodel, it is [360, 100, 100, 1].
347    pub fn z_max(mut self, z_max: impl ImageData) -> Box<Self> {
348        self.z_max = Some(z_max.to_image_data());
349        Box::new(self)
350    }
351
352    /// Array defining the lower bound for each color component. Note that the
353    /// default value will depend on the colormodel. For the `rgb`
354    /// colormodel, it is [0, 0, 0]. For the `rgba` colormodel, it is [0, 0, 0,
355    /// 0]. For the `rgba256` colormodel, it is [0, 0, 0, 0]. For the `hsl`
356    /// colormodel, it is [0, 0, 0]. For the `hsla` colormodel, it is [0, 0,
357    /// 0, 0].
358    pub fn z_min(mut self, z_min: impl ImageData) -> Box<Self> {
359        self.z_min = Some(z_min.to_image_data());
360        Box::new(self)
361    }
362}
363
364impl Trace for Image {
365    fn to_json(&self) -> String {
366        serde_json::to_string(&self).unwrap()
367    }
368}
369
370#[cfg(test)]
371mod tests {
372    use serde_json::{json, to_value};
373
374    use super::*;
375
376    #[test]
377    fn test_serialize_pixel_color() {
378        assert_eq!(
379            to_value(PixelColor::Color3(255, 100, 150)).unwrap(),
380            json!([255, 100, 150])
381        );
382        assert_eq!(
383            to_value(PixelColor::Color4(150, 140, 190, 0.5)).unwrap(),
384            json!([150, 140, 190, 0.5])
385        );
386    }
387
388    #[test]
389    fn test_serialize_color_model() {
390        assert_eq!(to_value(ColorModel::RGB).unwrap(), json!("rgb"));
391        assert_eq!(to_value(ColorModel::RGBA).unwrap(), json!("rgba"));
392        assert_eq!(to_value(ColorModel::RGBA256).unwrap(), json!("rgba256"));
393        assert_eq!(to_value(ColorModel::HSL).unwrap(), json!("hsl"));
394        assert_eq!(to_value(ColorModel::HSLA).unwrap(), json!("hsla"));
395    }
396
397    #[test]
398    fn test_serialize_z_smooth() {
399        assert_eq!(to_value(ZSmooth::Fast).unwrap(), json!("fast"));
400        assert_eq!(to_value(ZSmooth::False).unwrap(), json!(false));
401    }
402
403    #[test]
404    fn test_serialize_image() {
405        let b = Rgba::new(0, 0, 0, 0.5);
406        let w = Rgba::new(255, 255, 255, 1.0);
407        let image = Image::new(vec![vec![b, w, b, w, b], vec![w, b, w, b, w]])
408            .name("image name")
409            .visible(Visible::True)
410            .legend_rank(1000)
411            .legend_group_title(LegendGroupTitle::new("Legend Group Title"))
412            .opacity(0.5)
413            .ids(vec!["one"])
414            .x0(0.0)
415            .dx(1.0)
416            .y0(2.0)
417            .dy(3.0)
418            .source("https://raw.githubusercontent.com/michaelbabyn/plot_data/master/bridge.jpg")
419            .text("text")
420            .text_array(vec!["text"])
421            .hover_text("hover_text")
422            .hover_text_array(vec!["hover_text"])
423            .hover_info(HoverInfo::XAndYAndZ)
424            .hover_template("hover_template")
425            .hover_template_array(vec!["hover_template"])
426            .meta("meta")
427            .custom_data(vec!["custom_data"])
428            .x_axis("x2")
429            .y_axis("y2")
430            .color_model(ColorModel::RGBA)
431            .z_max(vec![vec![w, w, w, w, w], vec![w, w, w, w, w]])
432            .z_min(vec![vec![b, b, b, b, b], vec![b, b, b, b, b]])
433            .z_smooth(ZSmooth::Fast)
434            .hover_label(Label::new())
435            .ui_revision(6);
436
437        let b = (0, 0, 0, 0.5);
438        let w = (255, 255, 255, 1.0);
439        let expected = json!({
440            "type": "image",
441            "z": [[b, w, b, w, b], [w, b, w, b, w]],
442            "name": "image name",
443            "visible": true,
444            "legendrank": 1000,
445            "legendgrouptitle": {"text": "Legend Group Title"},
446            "opacity": 0.5,
447            "ids": ["one"],
448            "x0": 0.0,
449            "dx": 1.0,
450            "y0": 2.0,
451            "dy": 3.0,
452            "source": "https://raw.githubusercontent.com/michaelbabyn/plot_data/master/bridge.jpg",
453            "text": ["text"],
454            "hovertext": ["hover_text"],
455            "hoverinfo": "x+y+z",
456            "hovertemplate": ["hover_template"],
457            "meta": "meta",
458            "customdata": ["custom_data"],
459            "xaxis": "x2",
460            "yaxis": "y2",
461            "colormodel": "rgba",
462            "zmax": [[w, w, w, w, w], [w, w, w, w, w]],
463            "zmin": [[b, b, b, b, b], [b, b, b, b, b]],
464            "zsmooth": "fast",
465            "hoverlabel": {},
466            "uirevision": 6,
467        });
468
469        assert_eq!(to_value(image).unwrap(), expected);
470    }
471}