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 = { const state = {
raw: [], raw: [],
dataset: "all", // Multi-select: membership = "this value is currently enabled". Empty
algorithm: "all", // 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", stat: "mean",
// Runs the user has clicked in the legend to isolate. Empty = show all. // Runs the user has clicked in the legend to isolate. Empty = show all.
selected: new Set(), selected: new Set(),
@ -55,28 +60,67 @@ function populateFilters() {
datasets.add(shortGen(r.meta?.generator_path)); datasets.add(shortGen(r.meta?.generator_path));
algos.add(shortEmb(r.meta?.embedder)); algos.add(shortEmb(r.meta?.embedder));
} }
const dsSel = document.getElementById("flt-dataset"); state.datasetsAll = [...datasets].filter(Boolean).sort();
const algoSel = document.getElementById("flt-algo"); state.algorithmsAll = [...algos].filter(Boolean).sort();
for (const d of [...datasets].filter(Boolean).sort()) { // Default to everything enabled.
const o = document.createElement("option"); state.datasets = new Set(state.datasetsAll);
o.value = d; o.textContent = d; state.algorithms = new Set(state.algorithmsAll);
dsSel.appendChild(o); renderChips();
}
for (const a of [...algos].filter(Boolean).sort()) {
const o = document.createElement("option");
o.value = a; o.textContent = a;
algoSel.appendChild(o);
}
document.getElementById("total-count").textContent = state.raw.length; 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() { function wireEvents() {
document.getElementById("flt-dataset").addEventListener("change", (e) => { const bindChipRow = (containerId, selectedSet, allList) => {
state.dataset = e.target.value; render(); document.getElementById(containerId).addEventListener("click", (e) => {
}); const btn = e.target.closest(".chip");
document.getElementById("flt-algo").addEventListener("change", (e) => { if (!btn) return;
state.algorithm = e.target.value; render(); 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) => { document.querySelectorAll('input[name="stat"]').forEach((el) => {
el.addEventListener("change", (e) => { el.addEventListener("change", (e) => {
if (e.target.checked) { state.stat = e.target.value; render(); } if (e.target.checked) { state.stat = e.target.value; render(); }
@ -86,8 +130,8 @@ function wireEvents() {
function matchesFilters(r) { function matchesFilters(r) {
const m = r.meta || {}; const m = r.meta || {};
if (state.dataset !== "all" && shortGen(m.generator_path) !== state.dataset) return false; if (!state.datasets.has(shortGen(m.generator_path))) return false;
if (state.algorithm !== "all" && shortEmb(m.embedder) !== state.algorithm) return false; if (!state.algorithms.has(shortEmb(m.embedder))) return false;
return true; return true;
} }

View File

@ -896,15 +896,15 @@ button.submit:disabled { background: var(--faint); border-color: var(--faint); c
} }
.filter-bar { .filter-bar {
display: grid; display: flex;
grid-template-columns: auto auto 1fr auto; flex-wrap: wrap;
align-items: end; align-items: flex-start;
gap: 1.4rem 1.8rem; gap: 1.1rem 2rem;
padding: 0.9rem 0 1rem; padding: 0.9rem 0 1rem;
border-bottom: 1px solid var(--rule); border-bottom: 1px solid var(--rule);
margin-bottom: 1.4rem; 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 { .filter-group .ctl-label {
font-family: var(--mono); font-family: var(--mono);
font-size: 0.68rem; font-size: 0.68rem;
@ -912,22 +912,49 @@ button.submit:disabled { background: var(--faint); border-color: var(--faint); c
text-transform: uppercase; text-transform: uppercase;
color: var(--mute); color: var(--mute);
} }
.filter-group select { .filter-group.stat-group { min-width: 18rem; }
font-family: var(--mono);
font-size: 0.85rem; .filter-group .chips {
color: var(--ink); display: flex;
background: transparent; flex-wrap: wrap;
border: 0; gap: 0.3rem 0.35rem;
border-bottom: 1px solid var(--rule-2); align-items: center;
padding: 3px 18px 4px 2px;
outline: none;
min-width: 12ch;
} }
.filter-group select:focus { .chip {
border-bottom-color: var(--accent); 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); 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 { .filter-group .segmented.count-4 {
display: grid; display: grid;
grid-template-columns: repeat(4, 1fr); 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-family: var(--mono);
font-size: 0.8rem; font-size: 0.8rem;
color: var(--ink); color: var(--ink);
text-align: right;
font-variant-numeric: tabular-nums; font-variant-numeric: tabular-nums;
margin-left: auto;
align-self: center;
padding-top: 0.3rem;
} }
.filter-count .muted { color: var(--mute); } .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) { @media (max-width: 780px) {
.metrics-page { padding: 1rem 1.1rem 1.4rem; } .metrics-page { padding: 1rem 1.1rem 1.4rem; }
.filter-bar { .filter-bar { gap: 0.9rem 1.2rem; }
grid-template-columns: 1fr 1fr; .filter-group.stat-group { width: 100%; min-width: 0; }
gap: 0.9rem 1.1rem; .filter-count { margin-left: 0; text-align: left; padding-top: 0; }
} .legend .legend-row { grid-template-columns: 10px 1fr; }
.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;
}
.legend .fn { display: none; } .legend .fn { display: none; }
} }

View File

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

View File

@ -4,7 +4,7 @@
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" /> <meta name="viewport" content="width=device-width,initial-scale=1" />
<title>metrics — embedding notebook</title> <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" /> <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> </head>
<body> <body>
@ -24,16 +24,12 @@
<div class="filter-bar"> <div class="filter-bar">
<div class="filter-group"> <div class="filter-group">
<span class="ctl-label">dataset</span> <span class="ctl-label">dataset</span>
<select id="flt-dataset" aria-label="filter by dataset"> <div class="chips" id="flt-dataset" aria-label="filter by dataset"></div>
<option value="all">all</option>
</select>
</div> </div>
<div class="filter-group"> <div class="filter-group">
<span class="ctl-label">algorithm</span> <span class="ctl-label">algorithm</span>
<select id="flt-algo" aria-label="filter by algorithm"> <div class="chips" id="flt-algo" aria-label="filter by algorithm"></div>
<option value="all">all</option>
</select>
</div> </div>
<div class="filter-group stat-group"> <div class="filter-group stat-group">
@ -89,7 +85,7 @@
<span><span class="k">web</span> · metrics · port 8001</span> <span><span class="k">web</span> · metrics · port 8001</span>
</footer> </footer>
<script type="module" src="/static/metrics.js?v=3"></script> <script type="module" src="/static/metrics.js?v=4"></script>
</body> </body>
</html> </html>