Interactions / facet-intervals
Interaction
One interval, applied in every panel
333 Palmer penguins split by island, with a coordinate interval selection that holds across all the facets at once.
Interaction
- Pointer
- Choose a facet preset, choose Select area, and drag inside one panel. Union accumulates independently drawn panel intervals; cross-panel projects one domain through every compatible panel.
- Keyboard
- Choose a preset, tab to Select area, press Enter for the first corner, move with Arrow keys, and press Enter again to commit.
- Touch
- Choose Select area, then drag or tap two corners inside one facet panel. Use Clear selection without resetting zoom or filters.
The newest interval replaces only its origin panel.
Choose Select area, then draw inside any facet.
Use Arrow keys to inspect data. Press Escape to dismiss.
No active datum
Source
Svelte, builder, JSON
<script lang="ts">
import type { FacetIntervalPreset } from "@ggsvelte/svelte";
import {
FacetWrap,
GeomPoint,
GGPlot,
Labs,
ThemeClean,
} from "@ggsvelte/svelte";
import { observations } from "./data.js";
const presets: readonly FacetIntervalPreset[] = [
"independent",
"union",
"cross-panel",
];
let preset = $state<FacetIntervalPreset>("independent");
let status = $state("Choose Select area, then draw inside any facet.");
const descriptions: Record<FacetIntervalPreset, string> = {
independent: "The newest interval replaces only its origin panel.",
union: "Each panel keeps its own interval; selected keys are combined.",
"cross-panel":
"One semantic domain is projected through every compatible panel.",
};
</script>
<div class="facet-interval-demo">
<fieldset class="gg-demo-chrome">
<legend>Facet interval behavior</legend>
{#each presets as option}
<label>
<input
type="radio"
name="facet-preset"
value={option}
bind:group={preset}
/>
<span>{option}</span>
</label>
{/each}
</fieldset>
<p class="explanation gg-demo-chrome">{descriptions[preset]}</p>
<p class="status gg-demo-chrome" role="status" aria-live="polite">{status}</p>
<GGPlot
data={observations}
aes={{ x: "bill", y: "mass" }}
key="id"
select={{ type: "interval", mode: "xy", persistent: true, preset }}
width="container"
height={430}
onselect={(event) => {
if (event.mode === "point") return;
status =
event.phase === "clear"
? "Facet intervals cleared."
: `${event.phase}: ${String(event.keys.length)} rows selected from ${event.panelId}.`;
}}
>
<ThemeClean />
<FacetWrap field="island" ncol={3} />
<Labs
title="Palmer penguins by island"
subtitle={`${preset} interval semantics`}
x="Bill length (mm)"
y="Body mass (g)"
/>
<GeomPoint size={3.8} />
</GGPlot>
</div>
<style>
.facet-interval-demo {
display: grid;
gap: 0.65rem;
}
fieldset {
display: flex;
flex-wrap: wrap;
gap: 0.45rem 0.8rem;
margin: 0;
border: 1px solid color-mix(in srgb, currentColor 18%, transparent);
border-radius: 0.55rem;
padding: 0.7rem 0.85rem 0.8rem;
color: var(--fg, #17202a);
font: 0.86rem/1.4 var(--gg-font-family, system-ui, sans-serif);
}
legend {
padding-inline: 0.3rem;
font-weight: 700;
}
label {
display: inline-flex;
min-height: 44px;
align-items: center;
gap: 0.38rem;
border-radius: 0.4rem;
padding: 0.3rem 0.45rem;
}
label:has(:focus-visible) {
outline: 3px solid
color-mix(in srgb, var(--accent, #2c7fb8) 45%, transparent);
outline-offset: 1px;
}
input {
width: 1rem;
height: 1rem;
}
.explanation,
.status {
margin: 0;
color: var(--muted, #59636e);
font: 0.84rem/1.4 var(--gg-font-family, system-ui, sans-serif);
}
.status {
min-height: 1.2rem;
}
</style>