|
| 1 | +//! 2D histogram trace |
| 2 | +
|
| 3 | +use plotly_derive::FieldSetter; |
| 4 | +use serde::Serialize; |
| 5 | + |
| 6 | +use crate::{ |
| 7 | + common::{ |
| 8 | + Calendar, ColorBar, ColorScale, Dim, Font, HoverInfo, Label, LegendGroupTitle, PlotType, |
| 9 | + Visible, XAxisId, YAxisId, |
| 10 | + }, |
| 11 | + private::{NumOrString, NumOrStringCollection}, |
| 12 | + traces::heat_map::Smoothing, |
| 13 | + traces::histogram::{Bins, HistFunc, HistNorm}, |
| 14 | + Trace, |
| 15 | +}; |
| 16 | + |
| 17 | +/// Construct a 2D histogram trace. |
| 18 | +/// |
| 19 | +/// A `Histogram2d` bins raw `x`/`y` samples into a 2D grid and renders the |
| 20 | +/// counts as a heatmap. Provide raw `x`/`y` data (and let Plotly.js bin it via |
| 21 | +/// `hist_func`/`n_bins_x`/`n_bins_y`/`x_bins`/`y_bins`), or supply a |
| 22 | +/// pre-computed `z` matrix. |
| 23 | +/// |
| 24 | +/// # Examples |
| 25 | +/// |
| 26 | +/// ``` |
| 27 | +/// use plotly::Histogram2d; |
| 28 | +/// |
| 29 | +/// let trace = Histogram2d::new(vec![1.0, 2.0, 2.0], vec![1.0, 1.0, 3.0]); |
| 30 | +/// |
| 31 | +/// let expected = serde_json::json!({ |
| 32 | +/// "type": "histogram2d", |
| 33 | +/// "x": [1.0, 2.0, 2.0], |
| 34 | +/// "y": [1.0, 1.0, 3.0], |
| 35 | +/// }); |
| 36 | +/// |
| 37 | +/// assert_eq!(serde_json::to_value(trace).unwrap(), expected); |
| 38 | +/// ``` |
| 39 | +#[serde_with::skip_serializing_none] |
| 40 | +#[derive(Serialize, Debug, Clone, FieldSetter)] |
| 41 | +#[field_setter(box_self, kind = "trace")] |
| 42 | +pub struct Histogram2d<X, Y, Z> |
| 43 | +where |
| 44 | + X: Serialize + Clone, |
| 45 | + Y: Serialize + Clone, |
| 46 | + Z: Serialize + Clone, |
| 47 | +{ |
| 48 | + #[field_setter(default = "PlotType::Histogram2d")] |
| 49 | + r#type: PlotType, |
| 50 | + name: Option<String>, |
| 51 | + visible: Option<Visible>, |
| 52 | + #[serde(rename = "showlegend")] |
| 53 | + show_legend: Option<bool>, |
| 54 | + #[serde(rename = "legendgroup")] |
| 55 | + legend_group: Option<String>, |
| 56 | + #[serde(rename = "legendgrouptitle")] |
| 57 | + legend_group_title: Option<LegendGroupTitle>, |
| 58 | + opacity: Option<f64>, |
| 59 | + x: Option<Vec<X>>, |
| 60 | + y: Option<Vec<Y>>, |
| 61 | + z: Option<Vec<Z>>, |
| 62 | + #[serde(rename = "xaxis")] |
| 63 | + x_axis: Option<XAxisId>, |
| 64 | + #[serde(rename = "yaxis")] |
| 65 | + y_axis: Option<YAxisId>, |
| 66 | + /// Specifies the binning function used for this histogram trace. |
| 67 | + #[serde(rename = "histfunc")] |
| 68 | + hist_func: Option<HistFunc>, |
| 69 | + /// Specifies the type of normalization used for this histogram trace. |
| 70 | + #[serde(rename = "histnorm")] |
| 71 | + hist_norm: Option<HistNorm>, |
| 72 | + /// Determines whether or not the x-axis bin attributes are picked by an |
| 73 | + /// algorithm. |
| 74 | + #[serde(rename = "autobinx")] |
| 75 | + auto_bin_x: Option<bool>, |
| 76 | + /// Determines whether or not the y-axis bin attributes are picked by an |
| 77 | + /// algorithm. |
| 78 | + #[serde(rename = "autobiny")] |
| 79 | + auto_bin_y: Option<bool>, |
| 80 | + /// Specifies the maximum number of desired bins along the x axis. |
| 81 | + #[serde(rename = "nbinsx")] |
| 82 | + n_bins_x: Option<usize>, |
| 83 | + /// Specifies the maximum number of desired bins along the y axis. |
| 84 | + #[serde(rename = "nbinsy")] |
| 85 | + n_bins_y: Option<usize>, |
| 86 | + /// Sets the binning across the x axis. |
| 87 | + #[serde(rename = "xbins")] |
| 88 | + x_bins: Option<Bins>, |
| 89 | + /// Sets the binning across the y axis. |
| 90 | + #[serde(rename = "ybins")] |
| 91 | + y_bins: Option<Bins>, |
| 92 | + #[serde(rename = "autocolorscale")] |
| 93 | + auto_color_scale: Option<bool>, |
| 94 | + #[serde(rename = "colorbar")] |
| 95 | + color_bar: Option<ColorBar>, |
| 96 | + #[serde(rename = "colorscale")] |
| 97 | + color_scale: Option<ColorScale>, |
| 98 | + #[serde(rename = "reversescale")] |
| 99 | + reverse_scale: Option<bool>, |
| 100 | + #[serde(rename = "showscale")] |
| 101 | + show_scale: Option<bool>, |
| 102 | + zauto: Option<bool>, |
| 103 | + zmin: Option<f64>, |
| 104 | + zmax: Option<f64>, |
| 105 | + zmid: Option<f64>, |
| 106 | + #[serde(rename = "zhoverformat")] |
| 107 | + zhover_format: Option<String>, |
| 108 | + zsmooth: Option<Smoothing>, |
| 109 | + #[serde(rename = "xgap")] |
| 110 | + x_gap: Option<NumOrString>, |
| 111 | + #[serde(rename = "ygap")] |
| 112 | + y_gap: Option<NumOrString>, |
| 113 | + #[serde(rename = "xcalendar")] |
| 114 | + x_calendar: Option<Calendar>, |
| 115 | + #[serde(rename = "ycalendar")] |
| 116 | + y_calendar: Option<Calendar>, |
| 117 | + #[serde(rename = "xhoverformat")] |
| 118 | + x_hover_format: Option<String>, |
| 119 | + #[serde(rename = "yhoverformat")] |
| 120 | + y_hover_format: Option<String>, |
| 121 | + #[serde(rename = "textfont")] |
| 122 | + text_font: Option<Font>, |
| 123 | + #[serde(rename = "texttemplate")] |
| 124 | + text_template: Option<Dim<String>>, |
| 125 | + #[serde(rename = "texttemplatefallback")] |
| 126 | + text_template_fallback: Option<Dim<String>>, |
| 127 | + #[serde(rename = "hoverinfo")] |
| 128 | + hover_info: Option<HoverInfo>, |
| 129 | + #[serde(rename = "hoverlabel")] |
| 130 | + hover_label: Option<Label>, |
| 131 | + #[serde(rename = "hovertemplate")] |
| 132 | + hover_template: Option<Dim<String>>, |
| 133 | + #[serde(rename = "hovertemplatefallback")] |
| 134 | + hover_template_fallback: Option<Dim<String>>, |
| 135 | + /// Assigns extra meta information associated with this trace that can be |
| 136 | + /// used in various text attributes. |
| 137 | + meta: Option<NumOrString>, |
| 138 | + /// Assigns extra data to each datum that can be used in hover, click and |
| 139 | + /// selection events. |
| 140 | + #[serde(rename = "customdata")] |
| 141 | + custom_data: Option<NumOrStringCollection>, |
| 142 | + uid: Option<String>, |
| 143 | + /// Sets the legend rank for this trace. Items and groups with smaller ranks |
| 144 | + /// are presented on top/left side while with `"reversed"` |
| 145 | + /// `legend.trace_order` they are on bottom/right side. The default |
| 146 | + /// legendrank is 1000. |
| 147 | + #[serde(rename = "legendrank")] |
| 148 | + legend_rank: Option<usize>, |
| 149 | + /// Sets the width (in px or fraction) of the legend for this trace. |
| 150 | + #[serde(rename = "legendwidth")] |
| 151 | + legend_width: Option<f64>, |
| 152 | + /// Controls persistence of user-driven changes to the trace. Defaults to |
| 153 | + /// `layout.uirevision`. |
| 154 | + #[serde(rename = "uirevision")] |
| 155 | + ui_revision: Option<NumOrString>, |
| 156 | +} |
| 157 | + |
| 158 | +impl<X, Y> Histogram2d<X, Y, f64> |
| 159 | +where |
| 160 | + X: Serialize + Clone, |
| 161 | + Y: Serialize + Clone, |
| 162 | +{ |
| 163 | + /// Build a new 2D histogram from raw `x` and `y` samples. Plotly.js bins |
| 164 | + /// the samples into a 2D grid. |
| 165 | + pub fn new(x: Vec<X>, y: Vec<Y>) -> Box<Self> { |
| 166 | + Box::new(Self { |
| 167 | + x: Some(x), |
| 168 | + y: Some(y), |
| 169 | + ..Default::default() |
| 170 | + }) |
| 171 | + } |
| 172 | +} |
| 173 | + |
| 174 | +impl<X, Y, Z> Histogram2d<X, Y, Z> |
| 175 | +where |
| 176 | + X: Serialize + Clone, |
| 177 | + Y: Serialize + Clone, |
| 178 | + Z: Serialize + Clone, |
| 179 | +{ |
| 180 | + /// Build a new 2D histogram from a pre-computed `z` matrix, with `x`/`y` |
| 181 | + /// coordinates. |
| 182 | + pub fn new_xyz(x: Vec<X>, y: Vec<Y>, z: Vec<Z>) -> Box<Self> { |
| 183 | + Box::new(Self { |
| 184 | + x: Some(x), |
| 185 | + y: Some(y), |
| 186 | + z: Some(z), |
| 187 | + ..Default::default() |
| 188 | + }) |
| 189 | + } |
| 190 | +} |
| 191 | + |
| 192 | +impl<X, Y, Z> Trace for Histogram2d<X, Y, Z> |
| 193 | +where |
| 194 | + X: Serialize + Clone, |
| 195 | + Y: Serialize + Clone, |
| 196 | + Z: Serialize + Clone, |
| 197 | +{ |
| 198 | + fn to_json(&self) -> String { |
| 199 | + serde_json::to_string(self).unwrap() |
| 200 | + } |
| 201 | +} |
| 202 | + |
| 203 | +#[cfg(test)] |
| 204 | +mod tests { |
| 205 | + use serde_json::{json, to_value}; |
| 206 | + |
| 207 | + use super::*; |
| 208 | + use crate::common::ColorScalePalette; |
| 209 | + |
| 210 | + #[test] |
| 211 | + fn serialize_default_histogram2d() { |
| 212 | + let trace = Histogram2d::<f64, f64, f64>::default(); |
| 213 | + let expected = json!({"type": "histogram2d"}).to_string(); |
| 214 | + |
| 215 | + assert_eq!(trace.to_json(), expected); |
| 216 | + } |
| 217 | + |
| 218 | + #[test] |
| 219 | + fn serialize_histogram2d_raw() { |
| 220 | + let trace = Histogram2d::new(vec![1.0, 2.0, 2.0], vec![1.0, 1.0, 3.0]) |
| 221 | + .hist_func(HistFunc::Count) |
| 222 | + .hist_norm(HistNorm::Probability) |
| 223 | + .auto_bin_x(false) |
| 224 | + .auto_bin_y(false) |
| 225 | + .n_bins_x(10) |
| 226 | + .n_bins_y(10) |
| 227 | + .x_bins(Bins::new(0.0, 4.0, 1.0)) |
| 228 | + .y_bins(Bins::new(0.0, 4.0, 1.0)) |
| 229 | + .color_scale(ColorScale::Palette(ColorScalePalette::Viridis)) |
| 230 | + .show_scale(true) |
| 231 | + .name("hist2d"); |
| 232 | + |
| 233 | + let expected = json!({ |
| 234 | + "type": "histogram2d", |
| 235 | + "x": [1.0, 2.0, 2.0], |
| 236 | + "y": [1.0, 1.0, 3.0], |
| 237 | + "histfunc": "count", |
| 238 | + "histnorm": "probability", |
| 239 | + "autobinx": false, |
| 240 | + "autobiny": false, |
| 241 | + "nbinsx": 10, |
| 242 | + "nbinsy": 10, |
| 243 | + "xbins": {"start": 0.0, "end": 4.0, "size": 1.0}, |
| 244 | + "ybins": {"start": 0.0, "end": 4.0, "size": 1.0}, |
| 245 | + "colorscale": "Viridis", |
| 246 | + "showscale": true, |
| 247 | + "name": "hist2d", |
| 248 | + }); |
| 249 | + |
| 250 | + assert_eq!(to_value(trace).unwrap(), expected); |
| 251 | + } |
| 252 | + |
| 253 | + #[test] |
| 254 | + fn serialize_histogram2d_z_matrix() { |
| 255 | + let trace = Histogram2d::new_xyz( |
| 256 | + vec![0.0, 1.0], |
| 257 | + vec![2.0, 3.0], |
| 258 | + vec![vec![4.0, 5.0], vec![6.0, 7.0]], |
| 259 | + ) |
| 260 | + .zauto(true) |
| 261 | + .zmin(0.0) |
| 262 | + .zmax(10.0) |
| 263 | + .zmid(5.0) |
| 264 | + .zsmooth(Smoothing::Best) |
| 265 | + .x_gap(1.0) |
| 266 | + .y_gap("10"); |
| 267 | + |
| 268 | + let expected = json!({ |
| 269 | + "type": "histogram2d", |
| 270 | + "x": [0.0, 1.0], |
| 271 | + "y": [2.0, 3.0], |
| 272 | + "z": [[4.0, 5.0], [6.0, 7.0]], |
| 273 | + "zauto": true, |
| 274 | + "zmin": 0.0, |
| 275 | + "zmax": 10.0, |
| 276 | + "zmid": 5.0, |
| 277 | + "zsmooth": "best", |
| 278 | + "xgap": 1.0, |
| 279 | + "ygap": "10", |
| 280 | + }); |
| 281 | + |
| 282 | + assert_eq!(to_value(trace).unwrap(), expected); |
| 283 | + } |
| 284 | + |
| 285 | + #[test] |
| 286 | + fn serialize_histogram2d_legend_and_uirevision() { |
| 287 | + let trace = Histogram2d::new(vec![1.0], vec![2.0]) |
| 288 | + .legend_rank(7) |
| 289 | + .legend_width(2.0) |
| 290 | + .ui_revision("rev"); |
| 291 | + let v = to_value(trace).unwrap(); |
| 292 | + assert_eq!(v["legendrank"], json!(7)); |
| 293 | + assert_eq!(v["legendwidth"], json!(2.0)); |
| 294 | + assert_eq!(v["uirevision"], json!("rev")); |
| 295 | + } |
| 296 | +} |
0 commit comments