Skip to content
Open
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
10 changes: 9 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,17 @@
- New `aggregate` SETTING on Identity-stat layers (point, line, area, bar, ribbon,
range, segment, arrow, rule, text). By default it collapses each group to a
single row by replacing every numeric mapping in place with its aggregated
value. See the `DRAW` documentation for details.
value. See the `DRAW` documentation for details (#384).
- Added panel decorations (grid lines, axes, background) for polar coordinates (#156).
- Added `radar` setting to polar coordinates for making radar plots (#418).
- New `side` SETTING on the `boxplot` layer and the `jitter` position, mirroring
the existing `violin` setting (#439).

### Fixed

- Dodging of horizontal violin plots were broken due to a bad orientation
assumption in the VegaLite writer. We now correctly use the orientation to
dodge in the correct dimension (#439).

## 0.3.2 - 2026-05-05

Expand Down
14 changes: 14 additions & 0 deletions doc/syntax/layer/position/jitter.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ Apart from the settings of the layer type, setting `position => 'jitter'` will a
If `distribution` is either `'density'` or `'intensity'` then one of the axes must be continuous
* `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.
* `adjust`: Multiplier for the `bandwidth` setting (must be > 0). Defaults to 1.
* `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:
* `'both'` (default) jitters in both directions equally.
* `'left'` or `'bottom'` jitters only toward negative offsets.
* `'right'` or `'top'` jitters only toward positive offsets.

When both axes are jittered, `side` applies independently to each axis (e.g. `'right'` produces non-negative offsets on both axes).

## Examples
When plotting points on a discrete axis they are all placed in the middle
Expand Down Expand Up @@ -65,3 +71,11 @@ DRAW point
SCALE BINNED fill
SETTING breaks => 4, pretty => false
```

Pair a half-violin with one-sided jittered points by setting opposite `side` values:

```{ggsql}
VISUALISE species AS x, bill_dep AS y FROM ggsql:penguins
DRAW violin SETTING side => 'left'
DRAW point SETTING position => 'jitter', side => 'right', width => 0.4
```
12 changes: 12 additions & 0 deletions doc/syntax/layer/type/boxplot.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ The following aesthetics are recognised by the boxplot layer.
* `outliers`: Whether to display outliers as points. Defaults to `true`.
* `coef`: Length of the whiskers as a multiple of the IQR (must be >= 0). Defaults to `1.5`.
* `width`: Relative width of the boxes (0 to 1). Defaults to `0.9`.
* `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:
* `'both'` (default) displays a complete box on both sides of the centerline.
* `'left'` or `'bottom'` displays only the half-box on the left side or bottom side.
* `'right'` or `'top'` displays only the half-box on the right side or top side.

## Data transformation
Per group, data will be divided into 4 quartiles and summary statistics will be derived from their extremes.
Expand Down Expand Up @@ -91,3 +95,11 @@ VISUALISE FROM ggsql:penguins
DRAW boxplot
MAPPING species AS y, bill_len AS x
```

Pair a half-violin with a half-boxplot on the same category by setting opposite `side` values:

```{ggsql}
VISUALISE bill_len AS x, species AS y FROM ggsql:penguins
DRAW violin SETTING side => 'top'
DRAW boxplot SETTING side => 'bottom', width => 0.3
```
14 changes: 13 additions & 1 deletion doc/syntax/layer/type/violin.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -117,4 +117,16 @@ To achieve this outcome, you can set the `side` setting and adjust `width` to ta
VISUALISE Temp AS x, Month AS y FROM ggsql:airquality
DRAW violin SETTING width => 4, side => 'top'
SCALE ORDINAL y
```
```

The same facilities can be used to create violins where each side encode different subsets

```{ggsql}
VISUALISE body_mass AS y, species AS x, sex AS fill FROM ggsql:penguins
DRAW violin
SETTING side => 'left'
FILTER sex = 'male'
DRAW violin
SETTING side => 'right'
FILTER sex = 'female'
```
16 changes: 14 additions & 2 deletions src/plot/layer/geom/boxplot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

use std::collections::HashMap;

use super::types::POSITION_VALUES;
use super::types::{POSITION_VALUES, SIDE_VALUES};
use super::{DefaultAesthetics, GeomTrait, GeomType};
use crate::{
naming,
Expand Down Expand Up @@ -72,6 +72,11 @@ impl GeomTrait for Boxplot {
default: DefaultParamValue::String("dodge"),
constraint: ParamConstraint::string_option(POSITION_VALUES),
},
ParamDefinition {
name: "side",
default: DefaultParamValue::String("both"),
constraint: ParamConstraint::string_option(SIDE_VALUES),
},
];
PARAMS
}
Expand Down Expand Up @@ -539,7 +544,7 @@ mod tests {
let boxplot = Boxplot;
let params = boxplot.default_params();

assert_eq!(params.len(), 4);
assert_eq!(params.len(), 5);

// Find and verify outliers param
let outliers_param = params.iter().find(|p| p.name == "outliers").unwrap();
Expand All @@ -566,6 +571,13 @@ mod tests {
position_param.default,
DefaultParamValue::String("dodge")
));

// Find and verify side param (defaults to both)
let side_param = params.iter().find(|p| p.name == "side").unwrap();
assert!(matches!(
side_param.default,
DefaultParamValue::String("both")
));
}

#[test]
Expand Down
5 changes: 5 additions & 0 deletions src/plot/layer/geom/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ pub const POSITION_VALUES: &[&str] = &["identity", "stack", "dodge", "jitter"];
/// Closed interval side values for binned data
pub const CLOSED_VALUES: &[&str] = &["left", "right"];

/// Standard `side` parameter values for layers and positions that can render
/// either both halves of a symmetric shape (or jitter range) or just one of
/// them. Used by violin, boxplot, and jitter.
pub const SIDE_VALUES: &[&str] = &["both", "left", "top", "right", "bottom"];

/// Aesthetic aliases: user-facing names that resolve to concrete aesthetics.
///
/// An alias is considered supported if any of its target aesthetics are supported
Expand Down
4 changes: 1 addition & 3 deletions src/plot/layer/geom/violin.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Violin geom implementation

use super::types::POSITION_VALUES;
use super::types::{POSITION_VALUES, SIDE_VALUES};
use super::{DefaultAesthetics, GeomTrait, GeomType, StatResult};
use crate::{
naming,
Expand All @@ -24,8 +24,6 @@ const KERNEL_VALUES: &[&str] = &[
"cosine",
];

const SIDE_VALUES: &[&str] = &["both", "left", "top", "right", "bottom"];

/// Violin geom - violin plots (mirrored density)
#[derive(Debug, Clone, Copy)]
pub struct Violin;
Expand Down
Loading
Loading