Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose pattern fill api for histograms and bar charts #246

Merged
merged 1 commit into from
Nov 16, 2024
Merged
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
32 changes: 31 additions & 1 deletion examples/basic_charts/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use plotly::{
color::{NamedColor, Rgb, Rgba},
common::{
ColorScale, ColorScalePalette, DashType, Fill, Font, Line, LineShape, Marker, Mode,
Orientation,
Orientation, Pattern, PatternShape,
},
layout::{Axis, BarMode, CategoryOrder, Layout, Legend, TicksDirection, TraceOrder},
sankey::{Line as SankeyLine, Link, Node},
Expand Down Expand Up @@ -633,6 +633,35 @@ fn category_order_bar_chart() {
plot.show();
}

fn bar_chart_with_pattern_fills() {
let animals1 = vec!["giraffes", "orangutans", "monkeys"];
let trace1 = Bar::new(animals1, vec![20, 14, 23]).name("SF Zoo").marker(
Marker::new().line(Line::new().width(1.0)).pattern(
Pattern::new()
.shape(PatternShape::LeftDiagonalLine)
.solidity(0.1),
),
);

let animals2 = vec!["giraffes", "orangutans", "monkeys"];
let trace2 = Bar::new(animals2, vec![12, 18, 29]).name("LA Zoo").marker(
Marker::new().line(Line::new().width(1.0)).pattern(
Pattern::new()
.shape(PatternShape::RightDiagonalLine)
.solidity(0.5),
),
);

let layout = Layout::new().bar_mode(BarMode::Group);

let mut plot = Plot::new();
plot.add_trace(trace1);
plot.add_trace(trace2);
plot.set_layout(layout);

plot.show();
}

// Sankey Diagrams
fn basic_sankey_diagram() {
// https://plotly.com/javascript/sankey-diagram/#basic-sankey-diagram
Expand Down Expand Up @@ -709,6 +738,7 @@ fn main() {
// stacked_bar_chart();
// table_chart();
// category_order_bar_chart();
// bar_chart_with_pattern_fills();

// Sankey Diagrams
// basic_sankey_diagram();
Expand Down
187 changes: 185 additions & 2 deletions plotly/src/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1043,6 +1043,115 @@ pub enum AxisSide {
Right,
}

#[derive(Serialize, Debug, Clone)]
pub enum PatternShape {
#[serde(rename = "")]
None,
#[serde(rename = "-")]
HorizonalLine,
#[serde(rename = "|")]
VerticalLine,
#[serde(rename = "/")]
RightDiagonalLine,
#[serde(rename = "\\")]
LeftDiagonalLine,
#[serde(rename = "+")]
Cross,
#[serde(rename = "x")]
DiagonalCross,
#[serde(rename = ".")]
Dot,
}

#[derive(Serialize, Debug, Clone)]
#[serde(rename_all = "lowercase")]
pub enum PatternFillMode {
Replace,
Overlay,
}

#[serde_with::skip_serializing_none]
#[derive(Serialize, Clone, Debug, Default)]
pub struct Pattern {
shape: Option<Dim<PatternShape>>,
#[serde(rename = "fillmode")]
fill_mode: Option<PatternFillMode>,
#[serde(rename = "bgcolor")]
background_color: Option<Dim<Box<dyn Color>>>,
#[serde(rename = "fgcolor")]
foreground_color: Option<Dim<Box<dyn Color>>>,
#[serde(rename = "fgopacity")]
foreground_opacity: Option<f64>,
size: Option<Dim<f64>>,
solidity: Option<Dim<f64>>,
}

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

pub fn shape(mut self, shape: PatternShape) -> Self {
self.shape = Some(Dim::Scalar(shape));
self
}

pub fn shape_array(mut self, shape: Vec<PatternShape>) -> Self {
self.shape = Some(Dim::Vector(shape));
self
}

pub fn fill_mode(mut self, fill_mode: PatternFillMode) -> Self {
self.fill_mode = Some(fill_mode);
self
}

pub fn background_color<C: Color>(mut self, color: C) -> Self {
self.background_color = Some(Dim::Scalar(Box::new(color)));
self
}

pub fn background_color_array<C: Color>(mut self, colors: Vec<C>) -> Self {
self.background_color = Some(Dim::Vector(ColorArray(colors).into()));
self
}

pub fn foreground_color<C: Color>(mut self, color: C) -> Self {
self.foreground_color = Some(Dim::Scalar(Box::new(color)));
self
}

pub fn foreground_color_array<C: Color>(mut self, colors: Vec<C>) -> Self {
self.foreground_color = Some(Dim::Vector(ColorArray(colors).into()));
self
}

pub fn foreground_opacity(mut self, opacity: f64) -> Self {
self.foreground_opacity = Some(opacity);
self
}

pub fn size(mut self, size: f64) -> Self {
self.size = Some(Dim::Scalar(size));
self
}

pub fn size_array(mut self, size: Vec<f64>) -> Self {
self.size = Some(Dim::Vector(size));
self
}

pub fn solidity(mut self, solidity: f64) -> Self {
self.solidity = Some(Dim::Scalar(solidity));
self
}

pub fn solidity_array(mut self, solidity: Vec<f64>) -> Self {
self.solidity = Some(Dim::Vector(solidity));
self
}
}

#[serde_with::skip_serializing_none]
#[derive(Serialize, Clone, Debug, Default)]
pub struct Marker {
Expand Down Expand Up @@ -1076,6 +1185,7 @@ pub struct Marker {
color_bar: Option<ColorBar>,
#[serde(rename = "outliercolor")]
outlier_color: Option<Box<dyn Color>>,
pattern: Option<Pattern>,
}

impl Marker {
Expand Down Expand Up @@ -1192,6 +1302,11 @@ impl Marker {
self.outlier_color = Some(Box::new(outlier_color));
self
}

pub fn pattern(mut self, pattern: Pattern) -> Self {
self.pattern = Some(pattern);
self
}
}

#[serde_with::skip_serializing_none]
Expand Down Expand Up @@ -2132,6 +2247,63 @@ mod tests {
assert_eq!(to_value(tick_format_stop).unwrap(), expected);
}

#[test]
fn test_serialize_pattern_shape() {
assert_eq!(to_value(PatternShape::None).unwrap(), json!(""));
assert_eq!(to_value(PatternShape::HorizonalLine).unwrap(), json!("-"));
assert_eq!(to_value(PatternShape::VerticalLine).unwrap(), json!("|"));
assert_eq!(
to_value(PatternShape::RightDiagonalLine).unwrap(),
json!("/")
);
assert_eq!(
to_value(PatternShape::LeftDiagonalLine).unwrap(),
json!("\\")
);
assert_eq!(to_value(PatternShape::Cross).unwrap(), json!("+"));
assert_eq!(to_value(PatternShape::DiagonalCross).unwrap(), json!("x"));
assert_eq!(to_value(PatternShape::Dot).unwrap(), json!("."));
}

#[test]
fn test_serialize_pattern_fill_mode() {
assert_eq!(
to_value(PatternFillMode::Replace).unwrap(),
json!("replace")
);
assert_eq!(
to_value(PatternFillMode::Overlay).unwrap(),
json!("overlay")
);
}

#[test]
fn test_serialize_pattern() {
let pattern = Pattern::new()
.shape_array(vec![
PatternShape::HorizonalLine,
PatternShape::VerticalLine,
])
.fill_mode(PatternFillMode::Overlay)
.background_color_array(vec![NamedColor::Black, NamedColor::Blue])
.foreground_color_array(vec![NamedColor::Red, NamedColor::Green])
.foreground_opacity(0.9)
.size_array(vec![10.0, 20.0])
.solidity_array(vec![0.1, 0.2]);

let expected = json!({
"shape": ["-", "|"],
"fillmode": "overlay",
"bgcolor": ["black", "blue"],
"fgcolor": ["red", "green"],
"fgopacity": 0.9,
"size": [10.0, 20.0],
"solidity": [0.1, 0.2]
});

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

#[test]
fn test_serialize_marker() {
let marker = Marker::new()
Expand All @@ -2155,7 +2327,13 @@ mod tests {
.reverse_scale(true)
.show_scale(true)
.color_bar(ColorBar::new())
.outlier_color("#FFFFFF");
.outlier_color("#FFFFFF")
.pattern(
Pattern::new()
.shape(PatternShape::Cross)
.foreground_color(NamedColor::Red)
.size(10.0),
);

let expected = json!({
"symbol": "circle",
Expand All @@ -2177,7 +2355,12 @@ mod tests {
"autocolorscale": true,
"reversescale": true,
"showscale": true,
"outliercolor": "#FFFFFF"
"outliercolor": "#FFFFFF",
"pattern": {
"shape": "+",
"fgcolor": "red",
"size": 10.0
}
});

assert_eq!(to_value(marker).unwrap(), expected);
Expand Down
Loading