Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a
- [[#414](https://github.com/plotly/plotly.rs/issues/414)] Add `DensityMap` (MapLibre `map` subplot) trace type — density heatmaps with full color-scale and hover support
- [[#417](https://github.com/plotly/plotly.rs/issues/417)] Add `ScatterMap` (MapLibre `map` subplot) trace type — the modern counterpart to `ScatterMapbox`
- [[#418](https://github.com/plotly/plotly.rs/issues/418)] Add native point clustering to `ScatterMap` via a `Cluster` option
- Add `Splom` trace type (scatter-plot matrix) with `SplomDimension`/`SplomAxis`/`SplomDiagonal` config and `showupperhalf`/`showlowerhalf` controls
- Add `Parcats` trace type (parallel categories) with `ParcatsDimension`/`ParcatsLine` config and `ParcatsLineShape`/`ParcatsArrangement`/`ParcatsHoverOn`/`ParcatsSortPaths` enums

### Changed

Expand Down
2 changes: 2 additions & 0 deletions plotly/src/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,9 @@ pub enum PlotType {
Image,
Mesh3D,
Ohlc,
Parcats,
Sankey,
Splom,
Surface,
DensityMapbox,
DensityMap,
Expand Down
7 changes: 4 additions & 3 deletions plotly/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,14 @@ pub use plot::{Plot, Trace, Traces};
// Also provide easy access to modules which contain additional trace-specific types
pub use traces::{
box_plot, choropleth, choropleth_map, contour, density_map, heat_map, histogram, image, mesh3d,
sankey, scatter, scatter3d, scatter_map, scatter_mapbox, sunburst, surface, treemap, violin,
parcats, sankey, scatter, scatter3d, scatter_map, scatter_mapbox, splom, sunburst, surface,
treemap, violin,
};
// Bring the different trace types into the top-level scope
pub use traces::{
Bar, BoxPlot, Candlestick, Choropleth, ChoroplethMap, Contour, DensityMap, DensityMapbox,
HeatMap, Histogram, Image, Mesh3D, Ohlc, Pie, Sankey, Scatter, Scatter3D, ScatterGeo,
ScatterMap, ScatterMapbox, ScatterPolar, Sunburst, Surface, Table, Treemap, Violin,
HeatMap, Histogram, Image, Mesh3D, Ohlc, Parcats, Pie, Sankey, Scatter, Scatter3D, ScatterGeo,
ScatterMap, ScatterMapbox, ScatterPolar, Splom, Sunburst, Surface, Table, Treemap, Violin,
};

pub trait Restyle: serde::Serialize {}
Expand Down
4 changes: 4 additions & 0 deletions plotly/src/traces/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ pub mod histogram;
pub mod image;
pub mod mesh3d;
mod ohlc;
pub mod parcats;
pub mod pie;
pub mod sankey;
pub mod scatter;
Expand All @@ -21,6 +22,7 @@ pub mod scatter_geo;
pub mod scatter_map;
pub mod scatter_mapbox;
mod scatter_polar;
pub mod splom;
pub mod sunburst;
pub mod surface;
pub mod table;
Expand All @@ -39,6 +41,7 @@ pub use heat_map::HeatMap;
pub use histogram::Histogram;
pub use mesh3d::Mesh3D;
pub use ohlc::Ohlc;
pub use parcats::Parcats;
pub use pie::Pie;
pub use sankey::Sankey;
pub use scatter::Scatter;
Expand All @@ -47,6 +50,7 @@ pub use scatter_geo::ScatterGeo;
pub use scatter_map::ScatterMap;
pub use scatter_mapbox::ScatterMapbox;
pub use scatter_polar::ScatterPolar;
pub use splom::Splom;
pub use sunburst::Sunburst;
pub use surface::Surface;
pub use table::Table;
Expand Down
300 changes: 300 additions & 0 deletions plotly/src/traces/parcats.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,300 @@
//! Parallel categories (parcats) trace

use plotly_derive::FieldSetter;
use serde::Serialize;

use crate::{
color::Color,
common::{ColorScale, Dim, Domain, HoverInfo, PlotType},
layout::CategoryOrder,
Trace,
};

/// Sets the shape of the paths connecting the categories.
#[derive(Serialize, Clone, Debug)]
#[serde(rename_all = "lowercase")]
pub enum ParcatsLineShape {
Linear,
Hspline,
}

/// Sets the drag interaction mode for the categories and dimensions.
#[derive(Serialize, Clone, Debug)]
#[serde(rename_all = "lowercase")]
pub enum ParcatsArrangement {
Perpendicular,
Freeform,
Fixed,
}

/// Sets the hover interaction mode for the parcats diagram.
#[derive(Serialize, Clone, Debug)]
#[serde(rename_all = "lowercase")]
pub enum ParcatsHoverOn {
Category,
Color,
Dimension,
}

/// Sets the path sorting algorithm.
#[derive(Serialize, Clone, Debug)]
#[serde(rename_all = "lowercase")]
pub enum ParcatsSortPaths {
Forward,
Backward,
}

/// A single dimension (column) of a [`Parcats`] trace.
#[serde_with::skip_serializing_none]
#[derive(Serialize, Debug, Clone, FieldSetter)]
pub struct ParcatsDimension<V>
where
V: Serialize + Clone,
{
/// The shown name of the dimension.
label: Option<String>,
/// Sets the category values, one per data point.
values: Option<Vec<V>>,
/// Specifies the ordering logic for the categories in the dimension.
#[serde(rename = "categoryorder")]
category_order: Option<CategoryOrder>,
/// Sets the order in which categories in this dimension appear, only used if
/// `category_order` is set to "array".
#[serde(rename = "categoryarray")]
category_array: Option<Vec<V>>,
/// Sets alternative tick labels for the categories in this dimension.
ticktext: Option<Vec<String>>,
/// The display index of the dimension, from left to right, zero indexed,
/// defaults to dimension index.
#[serde(rename = "displayindex")]
display_index: Option<usize>,
/// Determines whether or not this dimension is visible.
visible: Option<bool>,
}

impl<V> ParcatsDimension<V>
where
V: Serialize + Clone,
{
pub fn new() -> Self {
Default::default()
}
}

/// Styles the paths (lines) connecting the categories of a [`Parcats`] trace.
#[serde_with::skip_serializing_none]
#[derive(Serialize, Debug, Clone, FieldSetter)]
pub struct ParcatsLine {
/// Sets the line color. It accepts either a specific color or an array of
/// numbers that are mapped to the colorscale.
color: Option<Dim<Box<dyn Color>>>,
/// Sets the colorscale used to map the `color` values to colors.
#[serde(rename = "colorscale")]
color_scale: Option<ColorScale>,
/// Sets the shape of the paths.
shape: Option<ParcatsLineShape>,
/// Sets the lower bound of the color domain.
cmin: Option<f64>,
/// Sets the upper bound of the color domain.
cmax: Option<f64>,
/// Sets the mid-point of the color domain by scaling `cmin` and/or `cmax`.
cmid: Option<f64>,
/// Determines whether or not a colorbar is displayed for this trace.
#[serde(rename = "showscale")]
show_scale: Option<bool>,
}

impl ParcatsLine {
pub fn new() -> Self {
Default::default()
}
}

/// Construct a parallel categories (parcats) trace.
///
/// A `Parcats` trace visualizes multi-dimensional categorical data as a set of
/// parallel axes, one per [`ParcatsDimension`], with ribbons flowing between
/// the categories. It is domain-based (like Sankey), so it does not share the
/// cartesian subplot grid.
///
/// # Examples
///
/// ```
/// use plotly::Parcats;
/// use plotly::parcats::{ParcatsArrangement, ParcatsDimension};
///
/// let trace = Parcats::new()
/// .dimensions(vec![
/// ParcatsDimension::new().label("A").values(vec!["x", "y", "x"]),
/// ParcatsDimension::new().label("B").values(vec!["p", "q", "p"]),
/// ])
/// .counts_array(vec![1.0, 2.0, 3.0])
/// .arrangement(ParcatsArrangement::Perpendicular);
///
/// let expected = serde_json::json!({
/// "type": "parcats",
/// "dimensions": [
/// {"label": "A", "values": ["x", "y", "x"]},
/// {"label": "B", "values": ["p", "q", "p"]}
/// ],
/// "counts": [1.0, 2.0, 3.0],
/// "arrangement": "perpendicular"
/// });
///
/// assert_eq!(serde_json::to_value(trace).unwrap(), expected);
/// ```
#[serde_with::skip_serializing_none]
#[derive(Serialize, Debug, Clone, FieldSetter)]
#[field_setter(box_self, kind = "trace")]
pub struct Parcats<V>
where
V: Serialize + Clone,
{
#[field_setter(default = "PlotType::Parcats")]
r#type: PlotType,
/// Sets the trace name. The trace name appears as the legend item and on
/// hover.
name: Option<String>,
/// The dimensions (columns) of the parallel-categories diagram.
dimensions: Option<Vec<ParcatsDimension<V>>>,
/// Styles the paths connecting the categories.
line: Option<ParcatsLine>,
/// The number of observations represented by each state. Defaults to 1 so
/// that each state represents one observation. This lets callers pass
/// aggregated per-path weights instead of one row per record.
counts: Option<Dim<f64>>,
/// Sets the drag interaction mode for categories and dimensions.
arrangement: Option<ParcatsArrangement>,
/// Sort paths so that like colors are bundled together within each category.
#[serde(rename = "bundlecolors")]
bundle_colors: Option<bool>,
/// Sets the hover interaction mode for the parcats diagram.
#[serde(rename = "hoveron")]
hover_on: Option<ParcatsHoverOn>,
/// Sets the path sorting algorithm.
#[serde(rename = "sortpaths")]
sort_paths: Option<ParcatsSortPaths>,
/// Template string used for rendering the information that appear on hover
/// box.
// NOTE: parcats also accepts `line.hovertemplate`; trace-level placement is
// the more general form and is used here.
#[serde(rename = "hovertemplate")]
hover_template: Option<String>,
/// Determines which trace information appears on hover.
#[serde(rename = "hoverinfo")]
hover_info: Option<HoverInfo>,
/// Sets the domain within which this parcats trace is drawn.
domain: Option<Domain>,
}

impl<V> Parcats<V>
where
V: Serialize + Clone,
{
/// Creates a new empty parcats trace.
pub fn new() -> Box<Self> {
Box::default()
}
}

impl<V> Trace for Parcats<V>
where
V: Serialize + Clone,
{
fn to_json(&self) -> String {
serde_json::to_string(self).unwrap()
}
}

#[cfg(test)]
mod tests {
use serde_json::{json, to_value};

use super::*;
use crate::color::NamedColor;

#[test]
fn serialize_default_parcats() {
let trace = Parcats::<i32>::default();
let expected = json!({"type": "parcats"}).to_string();

assert_eq!(trace.to_json(), expected);
}

#[test]
fn serialize_basic_parcats_trace() {
let trace = Parcats::new()
.name("parcats")
.dimensions(vec![
ParcatsDimension::new()
.label("Sex")
.values(vec!["M", "F", "F"]),
ParcatsDimension::new()
.label("Survived")
.values(vec!["yes", "no", "yes"]),
])
.counts_array(vec![1.0, 2.0, 3.0])
.arrangement(ParcatsArrangement::Perpendicular)
.bundle_colors(true)
.hover_on(ParcatsHoverOn::Category)
.sort_paths(ParcatsSortPaths::Forward);

let expected = json!({
"type": "parcats",
"name": "parcats",
"dimensions": [
{"label": "Sex", "values": ["M", "F", "F"]},
{"label": "Survived", "values": ["yes", "no", "yes"]}
],
"counts": [1.0, 2.0, 3.0],
"arrangement": "perpendicular",
"bundlecolors": true,
"hoveron": "category",
"sortpaths": "forward"
});

assert_eq!(to_value(trace).unwrap(), expected);
}

#[test]
fn serialize_parcats_line() {
let line = ParcatsLine::new()
.color(NamedColor::Blue)
.shape(ParcatsLineShape::Hspline)
.cmin(0.0)
.cmax(1.0)
.show_scale(true);
let expected = json!({
"color": "blue",
"shape": "hspline",
"cmin": 0.0,
"cmax": 1.0,
"showscale": true
});

assert_eq!(to_value(line).unwrap(), expected);
}

#[test]
fn serialize_parcats_dimension() {
let dim = ParcatsDimension::new()
.label("A")
.values(vec![0, 1, 0])
.category_order(CategoryOrder::CategoryAscending)
.category_array(vec![0, 1])
.ticktext(vec!["zero", "one"])
.display_index(2)
.visible(true);
let expected = json!({
"label": "A",
"values": [0, 1, 0],
"categoryorder": "category ascending",
"categoryarray": [0, 1],
"ticktext": ["zero", "one"],
"displayindex": 2,
"visible": true
});

assert_eq!(to_value(dim).unwrap(), expected);
}
}
Loading
Loading