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}