Skip to content

Commit 9c9ab14

Browse files
authored
add side to jitter and boxplot (#439)
1 parent ac78cd4 commit 9c9ab14

9 files changed

Lines changed: 664 additions & 52 deletions

File tree

CHANGELOG.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,17 @@
55
- New `aggregate` SETTING on Identity-stat layers (point, line, area, bar, ribbon,
66
range, segment, arrow, rule, text). By default it collapses each group to a
77
single row by replacing every numeric mapping in place with its aggregated
8-
value. See the `DRAW` documentation for details.
8+
value. See the `DRAW` documentation for details (#384).
99
- Added panel decorations (grid lines, axes, background) for polar coordinates (#156).
1010
- Added `radar` setting to polar coordinates for making radar plots (#418).
11+
- New `side` SETTING on the `boxplot` layer and the `jitter` position, mirroring
12+
the existing `violin` setting (#439).
13+
14+
### Fixed
15+
16+
- Dodging of horizontal violin plots were broken due to a bad orientation
17+
assumption in the VegaLite writer. We now correctly use the orientation to
18+
dodge in the correct dimension (#439).
1119

1220
## 0.3.2 - 2026-05-05
1321

doc/syntax/layer/position/jitter.qmd

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ Apart from the settings of the layer type, setting `position => 'jitter'` will a
2323
If `distribution` is either `'density'` or `'intensity'` then one of the axes must be continuous
2424
* `bandwidth`: Smoothing bandwidth for the `'density'` and `'intensity'` distributions (must be > 0). If absent (default), the bandwidth will be computed using Silverman's rule of thumb.
2525
* `adjust`: Multiplier for the `bandwidth` setting (must be > 0). Defaults to 1.
26+
* `side`: Constrains the jitter to one side of the original position by folding the sample into half of the width. Dodge centers and per-group widths are computed from the full `width`, so a one-sided jitter sits inside half of the same allocated band that a two-sided jitter would fill — pairing cleanly with a half-violin or half-boxplot on the other side. One of:
27+
* `'both'` (default) jitters in both directions equally.
28+
* `'left'` or `'bottom'` jitters only toward negative offsets.
29+
* `'right'` or `'top'` jitters only toward positive offsets.
30+
31+
When both axes are jittered, `side` applies independently to each axis (e.g. `'right'` produces non-negative offsets on both axes).
2632

2733
## Examples
2834
When plotting points on a discrete axis they are all placed in the middle
@@ -65,3 +71,11 @@ DRAW point
6571
SCALE BINNED fill
6672
SETTING breaks => 4, pretty => false
6773
```
74+
75+
Pair a half-violin with one-sided jittered points by setting opposite `side` values:
76+
77+
```{ggsql}
78+
VISUALISE species AS x, bill_dep AS y FROM ggsql:penguins
79+
DRAW violin SETTING side => 'left'
80+
DRAW point SETTING position => 'jitter', side => 'right', width => 0.4
81+
```

doc/syntax/layer/type/boxplot.qmd

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ The following aesthetics are recognised by the boxplot layer.
2727
* `outliers`: Whether to display outliers as points. Defaults to `true`.
2828
* `coef`: Length of the whiskers as a multiple of the IQR (must be >= 0). Defaults to `1.5`.
2929
* `width`: Relative width of the boxes (0 to 1). Defaults to `0.9`.
30+
* `side`: Determines the sides of the centerline where the box is displayed. Only the box and median tick shift to the chosen side; whiskers and outliers remain on the centerline. The full `width` is preserved for dodge calculations, so a half-box pairs cleanly with a half-violin or one-sided jitter on the same band. One of:
31+
* `'both'` (default) displays a complete box on both sides of the centerline.
32+
* `'left'` or `'bottom'` displays only the half-box on the left side or bottom side.
33+
* `'right'` or `'top'` displays only the half-box on the right side or top side.
3034

3135
## Data transformation
3236
Per group, data will be divided into 4 quartiles and summary statistics will be derived from their extremes.
@@ -91,3 +95,11 @@ VISUALISE FROM ggsql:penguins
9195
DRAW boxplot
9296
MAPPING species AS y, bill_len AS x
9397
```
98+
99+
Pair a half-violin with a half-boxplot on the same category by setting opposite `side` values:
100+
101+
```{ggsql}
102+
VISUALISE bill_len AS x, species AS y FROM ggsql:penguins
103+
DRAW violin SETTING side => 'top'
104+
DRAW boxplot SETTING side => 'bottom', width => 0.3
105+
```

doc/syntax/layer/type/violin.qmd

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,4 +117,16 @@ To achieve this outcome, you can set the `side` setting and adjust `width` to ta
117117
VISUALISE Temp AS x, Month AS y FROM ggsql:airquality
118118
DRAW violin SETTING width => 4, side => 'top'
119119
SCALE ORDINAL y
120-
```
120+
```
121+
122+
The same facilities can be used to create violins where each side encode different subsets
123+
124+
```{ggsql}
125+
VISUALISE body_mass AS y, species AS x, sex AS fill FROM ggsql:penguins
126+
DRAW violin
127+
SETTING side => 'left'
128+
FILTER sex = 'male'
129+
DRAW violin
130+
SETTING side => 'right'
131+
FILTER sex = 'female'
132+
```

src/plot/layer/geom/boxplot.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
33
use std::collections::HashMap;
44

5-
use super::types::POSITION_VALUES;
5+
use super::types::{POSITION_VALUES, SIDE_VALUES};
66
use super::{DefaultAesthetics, GeomTrait, GeomType};
77
use crate::{
88
naming,
@@ -72,6 +72,11 @@ impl GeomTrait for Boxplot {
7272
default: DefaultParamValue::String("dodge"),
7373
constraint: ParamConstraint::string_option(POSITION_VALUES),
7474
},
75+
ParamDefinition {
76+
name: "side",
77+
default: DefaultParamValue::String("both"),
78+
constraint: ParamConstraint::string_option(SIDE_VALUES),
79+
},
7580
];
7681
PARAMS
7782
}
@@ -539,7 +544,7 @@ mod tests {
539544
let boxplot = Boxplot;
540545
let params = boxplot.default_params();
541546

542-
assert_eq!(params.len(), 4);
547+
assert_eq!(params.len(), 5);
543548

544549
// Find and verify outliers param
545550
let outliers_param = params.iter().find(|p| p.name == "outliers").unwrap();
@@ -566,6 +571,13 @@ mod tests {
566571
position_param.default,
567572
DefaultParamValue::String("dodge")
568573
));
574+
575+
// Find and verify side param (defaults to both)
576+
let side_param = params.iter().find(|p| p.name == "side").unwrap();
577+
assert!(matches!(
578+
side_param.default,
579+
DefaultParamValue::String("both")
580+
));
569581
}
570582

571583
#[test]

src/plot/layer/geom/types.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ pub const POSITION_VALUES: &[&str] = &["identity", "stack", "dodge", "jitter"];
1818
/// Closed interval side values for binned data
1919
pub const CLOSED_VALUES: &[&str] = &["left", "right"];
2020

21+
/// Standard `side` parameter values for layers and positions that can render
22+
/// either both halves of a symmetric shape (or jitter range) or just one of
23+
/// them. Used by violin, boxplot, and jitter.
24+
pub const SIDE_VALUES: &[&str] = &["both", "left", "top", "right", "bottom"];
25+
2126
/// Aesthetic aliases: user-facing names that resolve to concrete aesthetics.
2227
///
2328
/// An alias is considered supported if any of its target aesthetics are supported

src/plot/layer/geom/violin.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! Violin geom implementation
22
3-
use super::types::POSITION_VALUES;
3+
use super::types::{POSITION_VALUES, SIDE_VALUES};
44
use super::{DefaultAesthetics, GeomTrait, GeomType, StatResult};
55
use crate::{
66
naming,
@@ -24,8 +24,6 @@ const KERNEL_VALUES: &[&str] = &[
2424
"cosine",
2525
];
2626

27-
const SIDE_VALUES: &[&str] = &["both", "left", "top", "right", "bottom"];
28-
2927
/// Violin geom - violin plots (mirrored density)
3028
#[derive(Debug, Clone, Copy)]
3129
pub struct Violin;

0 commit comments

Comments
 (0)