Skip to content

Commit 46553b2

Browse files
jqnatividadclaude
andcommitted
feat: add Violin trace type
Add the `Violin` trace with the full plotly.js violin attribute surface: bandwidth, box (inner mini box plot), meanline, KDE span/spanmode, scalegroup/scalemode, side (split violins), points/jitter/pointpos, selected/unselected, quartilemethod, hoveron, and zorder. Grouped and split violins work via the existing `ViolinMode` layout attribute. Includes unit tests, a doctest, a statistical-charts example (basic, horizontal, split), and a book recipe. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent d11094e commit 46553b2

9 files changed

Lines changed: 630 additions & 4 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a
77

88
### Added
99

10+
- Add `Violin` trace type with box, mean line, KDE span, and split/grouped support
1011
- [[#406](https://github.com/plotly/plotly.rs/issues/406)] Expose `plotly.js` 3.1–3.6 attributes
1112

1213
### Changed

docs/book/src/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
- [Statistical Charts](./recipes/statistical_charts.md)
2222
- [Error Bars](./recipes/statistical_charts/error_bars.md)
2323
- [Box Plots](./recipes/statistical_charts/box_plots.md)
24+
- [Violin Plots](./recipes/statistical_charts/violin_plots.md)
2425
- [Histograms](./recipes/statistical_charts/histograms.md)
2526
- [Scientific Charts](./recipes/scientific_charts.md)
2627
- [Contour Plots](./recipes/scientific_charts/contour_plots.md)

docs/book/src/recipes/statistical_charts.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ Kind | Link
66
:---|:----:
77
Error Bars |[![Scatter Plots](./img/error_bars.png)](./statistical_charts/error_bars.md)
88
Box Plots | [![Line Charts](./img/box_plot.png)](./statistical_charts/box_plots.md)
9+
Violin Plots | [![Violin Plots](./img/box_plot.png)](./statistical_charts/violin_plots.md)
910
Histograms | [![Scatter Plots](./img/overlaid_histogram.png)](./statistical_charts/histograms.md)
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Violin Plots
2+
3+
The following imports have been used to produce the plots below:
4+
5+
```rust,no_run
6+
use plotly::common::{Line, Orientation};
7+
use plotly::layout::{Layout, ViolinMode};
8+
use plotly::violin::{MeanLine, ViolinBox, ViolinPoints, ViolinSide};
9+
use plotly::{color::NamedColor, Plot, Violin};
10+
```
11+
12+
The `to_inline_html` method is used to produce the html plot displayed in this page.
13+
14+
15+
## Basic Violin Plot
16+
```rust,no_run
17+
{{#include ../../../../../examples/statistical_charts/src/main.rs:basic_violin_plot}}
18+
```
19+
20+
{{#include ../../../../../examples/statistical_charts/output/inline_basic_violin_plot.html}}
21+
22+
23+
## Horizontal Violin Plot
24+
```rust,no_run
25+
{{#include ../../../../../examples/statistical_charts/src/main.rs:horizontal_violin_plot}}
26+
```
27+
28+
{{#include ../../../../../examples/statistical_charts/output/inline_horizontal_violin_plot.html}}
29+
30+
31+
## Split Violin Plot
32+
```rust,no_run
33+
{{#include ../../../../../examples/statistical_charts/src/main.rs:split_violin_plot}}
34+
```
35+
36+
{{#include ../../../../../examples/statistical_charts/output/inline_split_violin_plot.html}}

examples/statistical_charts/src/main.rs

Lines changed: 100 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ use plotly::{
66
color::{NamedColor, Rgb, Rgba},
77
common::{ErrorData, ErrorType, Line, Marker, Mode, Orientation},
88
histogram::{Bins, Cumulative, HistFunc, HistNorm},
9-
layout::{Axis, BarMode, BoxMode, Layout, Margin},
10-
Bar, BoxPlot, Histogram, Plot, Scatter,
9+
layout::{Axis, BarMode, BoxMode, Layout, Margin, ViolinMode},
10+
violin::{MeanLine, ViolinBox, ViolinPoints, ViolinSide},
11+
Bar, BoxPlot, Histogram, Plot, Scatter, Violin,
1112
};
1213
use plotly_utils::write_example_to_html;
1314
use rand_distr::{Distribution, Normal, Uniform};
@@ -477,6 +478,98 @@ fn fully_styled_box_plot(show: bool, file_name: &str) {
477478
}
478479
// ANCHOR_END: fully_styled_box_plot
479480

481+
// Violin Plots
482+
// ANCHOR: basic_violin_plot
483+
fn basic_violin_plot(show: bool, file_name: &str) {
484+
let y = vec![
485+
0.2, 0.2, 0.6, 1.0, 0.5, 0.4, 0.2, 0.7, 0.9, 0.1, 0.5, 0.3, 0.8, 0.4, 0.6,
486+
];
487+
488+
let trace = Violin::new(y)
489+
.box_plot(ViolinBox::new().visible(true))
490+
.mean_line(MeanLine::new().visible(true))
491+
.name("Total");
492+
493+
let layout = Layout::new().title("Basic Violin Plot");
494+
495+
let mut plot = Plot::new();
496+
plot.set_layout(layout);
497+
plot.add_trace(trace);
498+
499+
let path = write_example_to_html(&plot, file_name);
500+
if show {
501+
plot.show_html(path);
502+
}
503+
}
504+
// ANCHOR_END: basic_violin_plot
505+
506+
// ANCHOR: horizontal_violin_plot
507+
fn horizontal_violin_plot(show: bool, file_name: &str) {
508+
let x = vec![1.4, 2.1, 1.9, 3.2, 2.7, 2.2, 1.8, 2.5, 3.1, 2.0, 2.6, 1.7];
509+
510+
let trace = Violin::<f64, f64>::default()
511+
.x(x)
512+
.points(ViolinPoints::All)
513+
.box_plot(ViolinBox::new().visible(true))
514+
.mean_line(MeanLine::new().visible(true))
515+
.orientation(Orientation::Horizontal)
516+
.name("Score");
517+
518+
let layout = Layout::new().title("Horizontal Violin Plot");
519+
520+
let mut plot = Plot::new();
521+
plot.set_layout(layout);
522+
plot.add_trace(trace);
523+
524+
let path = write_example_to_html(&plot, file_name);
525+
if show {
526+
plot.show_html(path);
527+
}
528+
}
529+
// ANCHOR_END: horizontal_violin_plot
530+
531+
// ANCHOR: split_violin_plot
532+
fn split_violin_plot(show: bool, file_name: &str) {
533+
let x = vec![
534+
"Mon", "Mon", "Mon", "Mon", "Tue", "Tue", "Tue", "Tue", "Wed", "Wed", "Wed", "Wed",
535+
];
536+
537+
let trace1 = Violin::new_xy(
538+
x.clone(),
539+
vec![0.6, 0.9, 0.4, 0.7, 0.8, 1.1, 0.6, 0.9, 1.0, 1.3, 0.8, 1.1],
540+
)
541+
.legend_group("Yes")
542+
.scale_group("Yes")
543+
.name("Yes")
544+
.side(ViolinSide::Negative)
545+
.line(Line::new().color(NamedColor::Blue));
546+
547+
let trace2 = Violin::new_xy(
548+
x,
549+
vec![0.4, 0.7, 0.3, 0.5, 0.6, 0.9, 0.4, 0.7, 0.8, 1.1, 0.6, 0.9],
550+
)
551+
.legend_group("No")
552+
.scale_group("No")
553+
.name("No")
554+
.side(ViolinSide::Positive)
555+
.line(Line::new().color(NamedColor::Green));
556+
557+
let layout = Layout::new()
558+
.title("Split Violin Plot")
559+
.violin_mode(ViolinMode::Overlay);
560+
561+
let mut plot = Plot::new();
562+
plot.set_layout(layout);
563+
plot.add_trace(trace1);
564+
plot.add_trace(trace2);
565+
566+
let path = write_example_to_html(&plot, file_name);
567+
if show {
568+
plot.show_html(path);
569+
}
570+
}
571+
// ANCHOR_END: split_violin_plot
572+
480573
// Histograms
481574
fn sample_normal_distribution(n: usize, mean: f64, std_dev: f64) -> Vec<f64> {
482575
let mut rng = rand::rng();
@@ -729,6 +822,11 @@ fn main() {
729822
grouped_horizontal_box_plot(false, "grouped_horizontal_box_plot");
730823
fully_styled_box_plot(false, "fully_styled_box_plot");
731824

825+
// Violin Plots
826+
basic_violin_plot(false, "basic_violin_plot");
827+
horizontal_violin_plot(false, "horizontal_violin_plot");
828+
split_violin_plot(false, "split_violin_plot");
829+
732830
// Histograms
733831
basic_histogram(false, "basic_histogram");
734832
horizontal_histogram(false, "horizontal_histogram");

plotly/src/common/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,7 @@ pub enum PlotType {
232232
Pie,
233233
Treemap,
234234
Sunburst,
235+
Violin,
235236
}
236237

237238
#[derive(Serialize, Clone, Debug)]

plotly/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,13 @@ pub use plot::{Plot, Trace, Traces};
6161
// Also provide easy access to modules which contain additional trace-specific types
6262
pub use traces::{
6363
box_plot, contour, heat_map, histogram, image, mesh3d, sankey, scatter, scatter3d,
64-
scatter_mapbox, sunburst, surface, treemap,
64+
scatter_mapbox, sunburst, surface, treemap, violin,
6565
};
6666
// Bring the different trace types into the top-level scope
6767
pub use traces::{
6868
Bar, BoxPlot, Candlestick, Contour, DensityMapbox, HeatMap, Histogram, Image, Mesh3D, Ohlc,
6969
Pie, Sankey, Scatter, Scatter3D, ScatterGeo, ScatterMapbox, ScatterPolar, Sunburst, Surface,
70-
Table, Treemap,
70+
Table, Treemap, Violin,
7171
};
7272

7373
pub trait Restyle: serde::Serialize {}

plotly/src/traces/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ pub mod sunburst;
2121
pub mod surface;
2222
pub mod table;
2323
pub mod treemap;
24+
pub mod violin;
2425

2526
pub use bar::Bar;
2627
pub use box_plot::BoxPlot;
@@ -42,5 +43,6 @@ pub use sunburst::Sunburst;
4243
pub use surface::Surface;
4344
pub use table::Table;
4445
pub use treemap::Treemap;
46+
pub use violin::Violin;
4547

4648
pub use self::image::Image;

0 commit comments

Comments
 (0)