# Strip Plot
A strip plot (dot plot / univariate scatter) shows every individual data point along a categorical axis. Unlike a box or violin, nothing is summarised — the raw values are shown directly, making sample size and exact distribution shape immediately visible.
**Import path:** `kuva::plot::StripPlot`
---
## Basic usage
Add one group per category with `.with_group(label, values)`. Groups are rendered left-to-right in the order they are added.
```rust,no_run
use kuva::plot::StripPlot;
use kuva::backend::svg::SvgBackend;
use kuva::render::render::render_multiple;
use kuva::render::layout::Layout;
use kuva::render::plots::Plot;
let strip = StripPlot::new()
.with_group("Control", control_data)
.with_group("Low dose", low_data)
.with_group("High dose", high_data)
.with_group("Washout", washout_data)
.with_color("steelblue")
.with_point_size(2.5)
.with_jitter(0.35);
let plots = vec![Plot::Strip(strip)];
let layout = Layout::auto_from_plots(&plots)
.with_title("Jittered Strip Plot")
.with_y_label("Measurement");
let scene = render_multiple(plots, layout);
let svg = SvgBackend.render_scene(&scene);
std::fs::write("strip.svg", svg).unwrap();
```
<img src="../assets/strip/basic.svg" alt="Jittered strip plot" width="560">
300 points per group. The jitter cloud fills out the slot width, making the spread and central tendency of each distribution easy to compare.
---
## Layout modes
Three modes control how points are spread horizontally within each group slot.
### Jittered strip
`.with_jitter(j)` assigns each point a random horizontal offset. `j` is the half-width as a fraction of the slot — `0.3` spreads points ±30 % of the slot width. This is the default (`j = 0.3`).
Use a smaller `j` to tighten the column or a larger `j` to spread it out. The jitter positions are randomised with a fixed seed (changeable via `.with_seed()`), so output is reproducible.
```rust,no_run
# use kuva::plot::StripPlot;
let strip = StripPlot::new()
.with_group("A", data)
.with_jitter(0.35) // ±35 % of slot width
.with_point_size(2.5);
```
### Beeswarm
`.with_swarm()` uses a deterministic algorithm to place each point as close to the group center as possible without overlapping any already-placed point. The outline of the resulting shape traces the density of the distribution.
Swarm works best for **N < ~200 per group**. With very large N, points are pushed far from center and the spread becomes impractical.
```rust,no_run
use kuva::plot::StripPlot;
use kuva::backend::svg::SvgBackend;
use kuva::render::render::render_multiple;
use kuva::render::layout::Layout;
use kuva::render::plots::Plot;
let strip = StripPlot::new()
.with_group("Control", normal_data)
.with_group("Bimodal", bimodal_data)
.with_group("Right-skewed", skewed_data)
.with_color("steelblue")
.with_point_size(3.0)
.with_swarm();
let plots = vec![Plot::Strip(strip)];
let layout = Layout::auto_from_plots(&plots)
.with_title("Beeswarm")
.with_y_label("Value");
let svg = SvgBackend.render_scene(&render_multiple(plots, layout));
```
<img src="../assets/strip/swarm.svg" alt="Beeswarm strip plot" width="560">
150 points per group. The bimodal group shows two distinct lobes; the right-skewed group shows the long tail — structure that jitter reveals less cleanly at this sample size.
### Center stack
`.with_center()` places all points at x = group center with no horizontal spread, creating a vertical column. The density of the distribution is readable directly from where points are most tightly packed.
```rust,no_run
# use kuva::plot::StripPlot;
let strip = StripPlot::new()
.with_group("Normal", normal_data)
.with_group("Bimodal", bimodal_data)
.with_group("Skewed", skewed_data)
.with_color("steelblue")
.with_point_size(2.0)
.with_center();
```
<img src="../assets/strip/center.svg" alt="Center stack strip plot" width="560">
400 points per group. The bimodal group shows a clear gap in the column; the skewed group has a dense cluster at the low end thinning toward the tail.
---
## Composing with a box plot
A `StripPlot` can be layered on top of a `BoxPlot` by passing both to `render_multiple`. Use a semi-transparent `rgba` color for the strip so the box summary remains legible underneath.
```rust,no_run
use kuva::plot::{StripPlot, BoxPlot};
use kuva::backend::svg::SvgBackend;
use kuva::render::render::render_multiple;
use kuva::render::layout::Layout;
use kuva::render::plots::Plot;
let boxplot = BoxPlot::new()
.with_group("Control", control_data.clone())
.with_group("Bimodal", bimodal_data.clone())
.with_group("High-spread", spread_data.clone())
.with_color("steelblue");
let strip = StripPlot::new()
.with_group("Control", control_data)
.with_group("Bimodal", bimodal_data)
.with_group("High-spread", spread_data)
.with_color("rgba(0,0,0,0.3)") // semi-transparent so box shows through
.with_point_size(2.5)
.with_jitter(0.2);
let plots = vec![Plot::Box(boxplot), Plot::Strip(strip)];
let layout = Layout::auto_from_plots(&plots)
.with_title("Box + Strip")
.with_y_label("Value");
let svg = SvgBackend.render_scene(&render_multiple(plots, layout));
```
<img src="../assets/strip/composed.svg" alt="Box plot with strip overlay" width="560">
The box summarises Q1/median/Q3; the individual points reveal that the bimodal group has two sub-populations the box conceals entirely.
---
## Multiple strip plots with a palette
Passing multiple `StripPlot`s to `render_multiple` with a `Layout::with_palette()` auto-assigns distinct colors. Attach `.with_legend()` to each plot to identify them.
```rust,no_run
use kuva::plot::StripPlot;
use kuva::backend::svg::SvgBackend;
use kuva::render::render::render_multiple;
use kuva::render::layout::Layout;
use kuva::render::plots::Plot;
use kuva::Palette;
let line_a = StripPlot::new()
.with_group("WT", wt_a).with_group("HET", het_a).with_group("KO", ko_a)
.with_jitter(0.3).with_point_size(2.5)
.with_legend("Line A");
let line_b = StripPlot::new()
.with_group("WT", wt_b).with_group("HET", het_b).with_group("KO", ko_b)
.with_jitter(0.3).with_point_size(2.5)
.with_legend("Line B");
let plots = vec![Plot::Strip(line_a), Plot::Strip(line_b)];
let layout = Layout::auto_from_plots(&plots)
.with_title("Two Lines – Palette")
.with_y_label("Expression")
.with_palette(Palette::wong());
let svg = SvgBackend.render_scene(&render_multiple(plots, layout));
```
<img src="../assets/strip/palette.svg" alt="Two strip plots with palette colors" width="560">
---
## Per-group colors
Color each group independently within a single `StripPlot` using `.with_group_colors()`. Colors are matched to groups by position — the first color applies to the first group added, and so on. The uniform `.with_color()` value is used as a fallback for any group without an entry.
This is an alternative to creating one `StripPlot` per group when the data is already grouped. The legend is **not** updated automatically; use separate `StripPlot` instances with `.with_legend()` when you need labeled legend entries.
```rust,no_run
use kuva::plot::StripPlot;
use kuva::backend::svg::SvgBackend;
use kuva::render::render::render_multiple;
use kuva::render::layout::Layout;
use kuva::render::plots::Plot;
let strip = StripPlot::new()
.with_group("Control", vec![4.1, 5.0, 5.3, 5.8, 6.2, 4.7])
.with_group("Treatment", vec![5.5, 6.1, 6.4, 7.2, 7.8, 6.9])
.with_group("Placebo", vec![3.9, 4.5, 4.8, 5.1, 5.6, 4.3])
.with_group_colors(vec!["steelblue", "crimson", "seagreen"])
.with_point_size(4.0)
.with_jitter(0.3);
let plots = vec![Plot::Strip(strip)];
let layout = Layout::auto_from_plots(&plots)
.with_title("Per-Group Colors")
.with_y_label("Measurement");
let svg = SvgBackend.render_scene(&render_multiple(plots, layout));
```
<img src="../assets/strip/group_colors.svg" alt="Strip plot with per-group colors" width="560">
---
## API reference
| `StripPlot::new()` | Create a strip plot with defaults |
| `.with_group(label, values)` | Add a group; accepts any `Into<f64>` iterable |
| `.with_color(s)` | Uniform point fill color (CSS color string, default `"steelblue"`) |
| `.with_group_colors(iter)` | Per-group colors; falls back to `.with_color` for out-of-range indices |
| `.with_point_size(r)` | Point radius in pixels (default `4.0`) |
| `.with_jitter(j)` | Jittered strip layout; `j` is half-width as fraction of slot (default `0.3`) |
| `.with_swarm()` | Beeswarm layout — non-overlapping, best for N < 200 |
| `.with_center()` | All points at group center — vertical density column |
| `.with_seed(n)` | RNG seed for jitter positions (default `42`) |
| `.with_legend(s)` | Attach a legend label |