layerchart-upset

UpSet gallery

Show setup script
<script lang="ts">
	import { UpSet, computeUpset, summarize } from '../../../lib/index.js';
	import { genres, makeData } from '../../demo-data.js';

	const data = makeData();
	const result = computeUpset(data, genres, { minSize: 25 });
	const ratings = summarize(result, 'rating');
	const lo = 0;
	const hi = 10;
</script>

Compact, no labels, no stripes

Show code
<UpSet
	{data}
	sets={genres}
	minSize={8}
	cellSize={18}
	barHeight={120}
	setBarWidth={90}
	labelWidth={80}
	fontSize={10}
	showLabels={false}
	stripes={false}
/>

Big dots and thick connectors

Show code
<UpSet
	{data}
	sets={genres}
	minSize={25}
	cellSize={34}
	dotRadius={0.38}
	connectorWidth={6}
	barPadding={0.15}
	barRadius={6}
/>

Axis titles

Set size
Show code
<UpSet
	{data}
	sets={genres}
	minSize={15}
	intersectionLabel="Intersection size"
	setLabel="Set size"
/>

Annotation row

An annotation snippet renders once per column, between the bars and the matrix, aligned with the column. Here it shows each column's share of all items.

13%
9%
8%
6%
5%
5%
Show code
<UpSet {data} sets={genres} minSize={25} annotationHeight={22}>
	{#snippet annotation({ intersection })}
		<small>{Math.round((100 * intersection.size) / data.length)}%</small>
	{/snippet}
</UpSet>

Box plot per column (annotation)

summarize(result, 'rating') gives quartiles, whiskers and outliers per column; the annotation snippet draws one box plot each on a shared y-axis.

Show code
<UpSet {data} sets={genres} minSize={25} annotationHeight={90}>
	{#snippet annotation({ intersection, width, height })}
		{@const s = ratings.get(intersection.id)}
		{@const y = (v: number) => height - 4 - ((v - lo) / (hi - lo)) * (height - 8)}
		{#if s && s.n > 0}
			<svg {width} {height} style="display:block" data-boxplot>
				<line x1={width / 2} x2={width / 2} y1={y(s.whiskerLow)} y2={y(s.whiskerHigh)} stroke="currentColor" />
				<rect
					x={width * 0.2}
					y={y(s.q3)}
					width={width * 0.6}
					height={Math.max(1, y(s.q1) - y(s.q3))}
					fill="var(--upset-bar, steelblue)"
					fill-opacity="0.45"
					stroke="currentColor"
				/>
				<line x1={width * 0.2} x2={width * 0.8} y1={y(s.median)} y2={y(s.median)} stroke="currentColor" stroke-width="2" />
				{#each s.outliers as o}
					<circle cx={width / 2} cy={y(o)} r="1.5" fill="none" stroke="currentColor" />
				{/each}
			</svg>
		{/if}
	{/snippet}
</UpSet>

maxWidth: scroll inside the root

40 intersections capped at 600px; the plot scrolls horizontally.

Show code
<UpSet {data} sets={genres} maxWidth={600} limit={40} data-upset="maxwidth" />

fit: shrink cells to the container

20 columns in a 500px container; cells shrink toward minCellSize.

Show code
<div style="width: 500px">
	<UpSet {data} sets={genres} fit limit={20} data-upset="fit" />
</div>

fit="fill": span the container

3 columns in a 700px container. fit="fill" pads with empty slots so the plot spans the container without growing the rows; showLabels="auto" drops labels that would collide.

Show code
<div style="width: 700px" data-testid="fill-box">
	<UpSet {data} sets={genres.slice(0, 4)} fit="fill" showLabels="auto" limit={3} />
</div>

Percent axis

normalize="total" labels the axes and bar labels as a share of all items. Without a known total (precomputed, no total) counts stay.

Show code
<UpSet {data} sets={genres} minSize={20} normalize="total" />

Percent axis (unknown total)

Precomputed counts without total keep showing counts.

Show code
<UpSet
	sets={['A', 'B']}
	intersections={[
		{ sets: ['A'], size: 30 },
		{ sets: ['B'], size: 20 }
	]}
	normalize="total"
/>