multiselect chips for dataset/algo filters

This commit is contained in:
Michael Pilosov 2026-04-21 21:07:33 -06:00
parent b2be3d0835
commit ca59516f26
4 changed files with 122 additions and 59 deletions

View File

@ -20,8 +20,13 @@ const PALETTE = [
const state = {
raw: [],
dataset: "all",
algorithm: "all",
// Multi-select: membership = "this value is currently enabled". Empty
// set = nothing shows (matches "none" toggle). Both default to all
// values on load so the initial view matches what a user expects.
datasets: new Set(),
algorithms: new Set(),
datasetsAll: [],
algorithmsAll: [],
stat: "mean",
// Runs the user has clicked in the legend to isolate. Empty = show all.
selected: new Set(),
@ -55,28 +60,67 @@ function populateFilters() {
datasets.add(shortGen(r.meta?.generator_path));
algos.add(shortEmb(r.meta?.embedder));
}
const dsSel = document.getElementById("flt-dataset");
const algoSel = document.getElementById("flt-algo");
for (const d of [...datasets].filter(Boolean).sort()) {
const o = document.createElement("option");
o.value = d; o.textContent = d;
dsSel.appendChild(o);
}
for (const a of [...algos].filter(Boolean).sort()) {
const o = document.createElement("option");
o.value = a; o.textContent = a;
algoSel.appendChild(o);
}
state.datasetsAll = [...datasets].filter(Boolean).sort();
state.algorithmsAll = [...algos].filter(Boolean).sort();
// Default to everything enabled.
state.datasets = new Set(state.datasetsAll);
state.algorithms = new Set(state.algorithmsAll);
renderChips();
document.getElementById("total-count").textContent = state.raw.length;
}
function renderChips() {
paintChips("flt-dataset", state.datasetsAll, state.datasets);
paintChips("flt-algo", state.algorithmsAll, state.algorithms);
}
function paintChips(containerId, values, selectedSet) {
const el = document.getElementById(containerId);
el.innerHTML = "";
for (const v of values) {
const b = document.createElement("button");
b.type = "button";
b.className = "chip" + (selectedSet.has(v) ? " is-on" : "");
b.setAttribute("aria-pressed", selectedSet.has(v) ? "true" : "false");
b.dataset.value = v;
b.dataset.role = "value";
b.textContent = v;
el.appendChild(b);
}
for (const [role, label] of [["all", "all"], ["none", "none"]]) {
const b = document.createElement("button");
b.type = "button";
b.className = "chip chip-meta";
b.dataset.role = role;
b.textContent = label;
el.appendChild(b);
}
}
function wireEvents() {
document.getElementById("flt-dataset").addEventListener("change", (e) => {
state.dataset = e.target.value; render();
});
document.getElementById("flt-algo").addEventListener("change", (e) => {
state.algorithm = e.target.value; render();
});
const bindChipRow = (containerId, selectedSet, allList) => {
document.getElementById(containerId).addEventListener("click", (e) => {
const btn = e.target.closest(".chip");
if (!btn) return;
const role = btn.dataset.role;
if (role === "value") {
const v = btn.dataset.value;
if (selectedSet.has(v)) selectedSet.delete(v);
else selectedSet.add(v);
} else if (role === "all") {
for (const v of allList) selectedSet.add(v);
} else if (role === "none") {
selectedSet.clear();
}
renderChips();
render();
});
};
bindChipRow("flt-dataset", state.datasets, state.datasetsAll);
bindChipRow("flt-algo", state.algorithms, state.algorithmsAll);
document.querySelectorAll('input[name="stat"]').forEach((el) => {
el.addEventListener("change", (e) => {
if (e.target.checked) { state.stat = e.target.value; render(); }
@ -86,8 +130,8 @@ function wireEvents() {
function matchesFilters(r) {
const m = r.meta || {};
if (state.dataset !== "all" && shortGen(m.generator_path) !== state.dataset) return false;
if (state.algorithm !== "all" && shortEmb(m.embedder) !== state.algorithm) return false;
if (!state.datasets.has(shortGen(m.generator_path))) return false;
if (!state.algorithms.has(shortEmb(m.embedder))) return false;
return true;
}

View File

@ -896,15 +896,15 @@ button.submit:disabled { background: var(--faint); border-color: var(--faint); c
}
.filter-bar {
display: grid;
grid-template-columns: auto auto 1fr auto;
align-items: end;
gap: 1.4rem 1.8rem;
display: flex;
flex-wrap: wrap;
align-items: flex-start;
gap: 1.1rem 2rem;
padding: 0.9rem 0 1rem;
border-bottom: 1px solid var(--rule);
margin-bottom: 1.4rem;
}
.filter-group { display: flex; flex-direction: column; gap: 0.25rem; min-width: 0; }
.filter-group { display: flex; flex-direction: column; gap: 0.35rem; min-width: 0; }
.filter-group .ctl-label {
font-family: var(--mono);
font-size: 0.68rem;
@ -912,22 +912,49 @@ button.submit:disabled { background: var(--faint); border-color: var(--faint); c
text-transform: uppercase;
color: var(--mute);
}
.filter-group select {
font-family: var(--mono);
font-size: 0.85rem;
color: var(--ink);
background: transparent;
border: 0;
border-bottom: 1px solid var(--rule-2);
padding: 3px 18px 4px 2px;
outline: none;
min-width: 12ch;
.filter-group.stat-group { min-width: 18rem; }
.filter-group .chips {
display: flex;
flex-wrap: wrap;
gap: 0.3rem 0.35rem;
align-items: center;
}
.filter-group select:focus {
border-bottom-color: var(--accent);
.chip {
font-family: var(--mono);
font-size: 0.75rem;
color: var(--mute);
background: transparent;
border: 1px solid var(--rule-2);
border-radius: 2px;
padding: 0.22rem 0.55rem;
cursor: pointer;
transition: background 120ms ease, border-color 120ms ease, color 120ms ease;
user-select: none;
letter-spacing: 0.02em;
font-variant-numeric: tabular-nums;
}
.chip:hover { color: var(--ink); }
.chip.is-on {
color: var(--accent);
border-color: var(--accent);
background: var(--accent-tint);
}
.filter-group.stat-group { min-width: 18rem; }
.chip:focus-visible { outline: 1px solid var(--accent); outline-offset: 2px; }
.chip-meta {
color: var(--faint);
font-family: var(--serif);
font-style: italic;
font-size: 0.76rem;
letter-spacing: 0;
border-style: dashed;
margin-left: 0.25rem;
}
.chip-meta:hover {
color: var(--accent);
border-color: var(--accent);
background: transparent;
}
.filter-group .segmented.count-4 {
display: grid;
grid-template-columns: repeat(4, 1fr);
@ -965,8 +992,10 @@ button.submit:disabled { background: var(--faint); border-color: var(--faint); c
font-family: var(--mono);
font-size: 0.8rem;
color: var(--ink);
text-align: right;
font-variant-numeric: tabular-nums;
margin-left: auto;
align-self: center;
padding-top: 0.3rem;
}
.filter-count .muted { color: var(--mute); }
@ -1122,14 +1151,9 @@ button.submit:disabled { background: var(--faint); border-color: var(--faint); c
@media (max-width: 780px) {
.metrics-page { padding: 1rem 1.1rem 1.4rem; }
.filter-bar {
grid-template-columns: 1fr 1fr;
gap: 0.9rem 1.1rem;
}
.filter-group.stat-group { grid-column: 1 / -1; min-width: 0; }
.filter-count { grid-column: 1 / -1; text-align: left; }
.legend .legend-row {
grid-template-columns: 10px 1fr;
}
.filter-bar { gap: 0.9rem 1.2rem; }
.filter-group.stat-group { width: 100%; min-width: 0; }
.filter-count { margin-left: 0; text-align: left; padding-top: 0; }
.legend .legend-row { grid-template-columns: 10px 1fr; }
.legend .fn { display: none; }
}

View File

@ -183,7 +183,6 @@
<footer class="colophon">
<span><span class="k">web</span> · scientific instrument · port 8001</span>
<span>fastapi · htmx · no build step</span>
</footer>
<script type="module" src="/static/dataset-picker.js"></script>

View File

@ -4,7 +4,7 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>metrics — embedding notebook</title>
<link rel="stylesheet" href="/static/style.css?v=3" />
<link rel="stylesheet" href="/static/style.css?v=4" />
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Ccircle cx='8' cy='8' r='3' fill='%231f4e5f'/%3E%3C/svg%3E" />
</head>
<body>
@ -24,16 +24,12 @@
<div class="filter-bar">
<div class="filter-group">
<span class="ctl-label">dataset</span>
<select id="flt-dataset" aria-label="filter by dataset">
<option value="all">all</option>
</select>
<div class="chips" id="flt-dataset" aria-label="filter by dataset"></div>
</div>
<div class="filter-group">
<span class="ctl-label">algorithm</span>
<select id="flt-algo" aria-label="filter by algorithm">
<option value="all">all</option>
</select>
<div class="chips" id="flt-algo" aria-label="filter by algorithm"></div>
</div>
<div class="filter-group stat-group">
@ -89,7 +85,7 @@
<span><span class="k">web</span> · metrics · port 8001</span>
</footer>
<script type="module" src="/static/metrics.js?v=3"></script>
<script type="module" src="/static/metrics.js?v=4"></script>
</body>
</html>