Add filter option for percentage won by
This commit is contained in:
parent
c4c5b6b506
commit
9d4d39058a
@ -53,8 +53,17 @@
|
||||
let sortDir: 'asc' | 'desc' = 'desc';
|
||||
let page = 1;
|
||||
let pageSize: number | string = 24;
|
||||
let filterWinMargin: string = 'all'; // 'all', '1', '2'
|
||||
$: pageSizeNum = Number(pageSize) || 24;
|
||||
$: sorted = [...items].sort((a, b) => (sortDir === 'asc' ? a.timeset - b.timeset : b.timeset - a.timeset));
|
||||
$: filtered = (() => {
|
||||
if (filterWinMargin === 'all') return items;
|
||||
const margin = Number(filterWinMargin);
|
||||
return items.filter(i => {
|
||||
if (i.accA == null || i.accB == null) return false;
|
||||
return i.accA > i.accB && (i.accA - i.accB) > margin;
|
||||
});
|
||||
})();
|
||||
$: sorted = [...filtered].sort((a, b) => (sortDir === 'asc' ? a.timeset - b.timeset : b.timeset - a.timeset));
|
||||
$: totalPages = Math.max(1, Math.ceil(sorted.length / pageSizeNum));
|
||||
$: page = Math.min(page, totalPages);
|
||||
$: pageItems = sorted.slice((page - 1) * pageSizeNum, (page - 1) * pageSizeNum + pageSizeNum);
|
||||
@ -246,6 +255,8 @@
|
||||
if (dir === 'asc' || dir === 'desc') sortDir = dir;
|
||||
const size = sp.get('size') ?? sp.get('ps');
|
||||
if (size) pageSize = Number(size) || pageSize;
|
||||
const filter = sp.get('filter');
|
||||
if (filter && ['all', '1', '2'].includes(filter)) filterWinMargin = filter;
|
||||
initialized = true;
|
||||
});
|
||||
|
||||
@ -272,6 +283,7 @@
|
||||
if (page > 1) sp.set('page', String(page)); else sp.delete('page');
|
||||
if (sortDir !== 'desc') sp.set('dir', sortDir); else sp.delete('dir');
|
||||
if (pageSizeNum !== 24) sp.set('size', String(pageSizeNum)); else sp.delete('size');
|
||||
if (filterWinMargin !== 'all') sp.set('filter', filterWinMargin); else sp.delete('filter');
|
||||
const qs = sp.toString();
|
||||
const url = location.pathname + (qs ? `?${qs}` : '');
|
||||
if (replace) history.replaceState(null, '', url); else history.pushState(null, '', url);
|
||||
@ -508,53 +520,62 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Win Margin Histogram -->
|
||||
<div class="mt-6 card-surface p-4">
|
||||
<div class="text-sm text-muted mb-2">Win Margin Histogram (A − B, %)</div>
|
||||
<div class="hist">
|
||||
{#each histogram as count, i}
|
||||
<div
|
||||
class="bar"
|
||||
title={`${(histRangeMin + (i + 0.5) * histBinWidth).toFixed(2)}%`}
|
||||
style={`left:${(i / histogram.length) * 100}%; width:${(1 / histogram.length) * 100}%; height:${((count / histMaxCount) * 100).toFixed(1)}%; background:${(histRangeMin + (i + 0.5) * histBinWidth) >= 0 ? 'rgba(0,255,204,0.8)' : 'rgba(255,0,170,0.8)'};`}
|
||||
></div>
|
||||
{/each}
|
||||
<div class="zero-line"></div>
|
||||
<!-- Win Margin Histogram & Cumulative Wins -->
|
||||
<div class="mt-6 grid gap-4 lg:grid-cols-2">
|
||||
<div class="card-surface p-4">
|
||||
<div class="text-sm text-muted mb-2">Win Margin Histogram (A − B, %)</div>
|
||||
<div class="hist">
|
||||
{#each histogram as count, i}
|
||||
<div
|
||||
class="bar"
|
||||
title={`${(histRangeMin + (i + 0.5) * histBinWidth).toFixed(2)}%`}
|
||||
style={`left:${(i / histogram.length) * 100}%; width:${(1 / histogram.length) * 100}%; height:${((count / histMaxCount) * 100).toFixed(1)}%; background:${(histRangeMin + (i + 0.5) * histBinWidth) >= 0 ? 'rgba(0,255,204,0.8)' : 'rgba(255,0,170,0.8)'};`}
|
||||
></div>
|
||||
{/each}
|
||||
<div class="zero-line"></div>
|
||||
</div>
|
||||
<div class="mt-2 text-xs text-muted">Left (magenta) favors {idShortB}, Right (cyan) favors {idShortA}</div>
|
||||
</div>
|
||||
<div class="card-surface p-4">
|
||||
<div class="text-sm text-muted mb-2">Cumulative Wins Over Time</div>
|
||||
<svg viewBox="0 0 600 140" class="spark">
|
||||
<!-- Axes bg -->
|
||||
<rect x="0" y="0" width="600" height="140" fill="transparent" />
|
||||
{#if cumSeries.length > 1}
|
||||
{#key cumSeries.length}
|
||||
<!-- A line -->
|
||||
<polyline fill="none" stroke="rgba(0,255,204,0.9)" stroke-width="2" points={cumSeries.map(p => `${mapX(p.t, 600)},${mapY(p.a, 120)}`).join(' ')} />
|
||||
<!-- B line -->
|
||||
<polyline fill="none" stroke="rgba(255,0,170,0.9)" stroke-width="2" points={cumSeries.map(p => `${mapX(p.t, 600)},${mapY(p.b, 120)}`).join(' ')} />
|
||||
{/key}
|
||||
{/if}
|
||||
</svg>
|
||||
</div>
|
||||
<div class="mt-2 text-xs text-muted">Left (magenta) favors {idShortB}, Right (cyan) favors {idShortA}</div>
|
||||
</div>
|
||||
|
||||
<!-- Cumulative Wins Over Time -->
|
||||
<div class="mt-6 card-surface p-4">
|
||||
<div class="text-sm text-muted mb-2">Cumulative Wins Over Time</div>
|
||||
<svg viewBox="0 0 600 140" class="spark">
|
||||
<!-- Axes bg -->
|
||||
<rect x="0" y="0" width="600" height="140" fill="transparent" />
|
||||
{#if cumSeries.length > 1}
|
||||
{#key cumSeries.length}
|
||||
<!-- A line -->
|
||||
<polyline fill="none" stroke="rgba(0,255,204,0.9)" stroke-width="2" points={cumSeries.map(p => `${mapX(p.t, 600)},${mapY(p.a, 120)}`).join(' ')} />
|
||||
<!-- B line -->
|
||||
<polyline fill="none" stroke="rgba(255,0,170,0.9)" stroke-width="2" points={cumSeries.map(p => `${mapX(p.t, 600)},${mapY(p.b, 120)}`).join(' ')} />
|
||||
{/key}
|
||||
{/if}
|
||||
</svg>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if items.length > 0}
|
||||
<div class="mt-6 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div class="flex items-center gap-3 text-sm text-muted">
|
||||
<span>{items.length} songs</span>
|
||||
<span>·</span>
|
||||
<div class="mt-6 flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div class="text-sm text-muted">
|
||||
{filtered.length} / {items.length} songs
|
||||
</div>
|
||||
<div class="flex items-center gap-4 text-sm text-muted flex-wrap justify-end">
|
||||
<label class="flex items-center gap-3">
|
||||
<span class="filter-label {filterWinMargin !== 'all' ? 'active' : ''}">Options:</span>
|
||||
<select class="neon-select {filterWinMargin !== 'all' ? 'active' : ''}" bind:value={filterWinMargin}>
|
||||
<option value="all">All Songs</option>
|
||||
<option value="1">{idShortA} wins by >1%</option>
|
||||
<option value="2">{idShortA} wins by >2%</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="flex items-center gap-2">Dir
|
||||
<select class="rounded-md border border-white/10 bg-transparent px-2 py-1 text-sm" bind:value={sortDir}>
|
||||
<select class="neon-select" bind:value={sortDir}>
|
||||
<option value="desc">Desc</option>
|
||||
<option value="asc">Asc</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="flex items-center gap-2">Page size
|
||||
<select class="rounded-md border border-white/10 bg-transparent px-2 py-1 text-sm" bind:value={pageSize}>
|
||||
<select class="neon-select" bind:value={pageSize}>
|
||||
<option value={12}>12</option>
|
||||
<option value={24}>24</option>
|
||||
<option value={36}>36</option>
|
||||
@ -744,6 +765,56 @@
|
||||
|
||||
/* Sparkline */
|
||||
.spark { width: 100%; height: 140px; }
|
||||
|
||||
/* Filter label styling */
|
||||
.filter-label {
|
||||
font-size: 1em;
|
||||
letter-spacing: 0.05em;
|
||||
font-weight: 700;
|
||||
color: rgba(255, 0, 170, 0.95);
|
||||
text-shadow: 0 0 8px rgba(255, 0, 170, 0.3);
|
||||
transition: all 0.3s ease;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
font-family: var(--font-display);
|
||||
line-height: 1;
|
||||
}
|
||||
.filter-label.active {
|
||||
color: rgba(255, 0, 170, 1);
|
||||
text-shadow: 0 0 12px rgba(255, 0, 170, 0.5);
|
||||
}
|
||||
|
||||
/* Neon select dropdown */
|
||||
.neon-select {
|
||||
border-radius: 0.375rem;
|
||||
border: 1px solid rgba(34, 211, 238, 0.3);
|
||||
background: linear-gradient(180deg, rgba(15,23,42,0.9), rgba(11,15,23,0.95));
|
||||
padding: 0.25rem 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
color: rgba(148, 163, 184, 1);
|
||||
transition: all 0.2s ease;
|
||||
box-shadow: 0 0 8px rgba(34, 211, 238, 0.15);
|
||||
cursor: pointer;
|
||||
}
|
||||
.neon-select.active {
|
||||
border-color: rgba(34, 211, 238, 0.6);
|
||||
color: rgba(255, 255, 255, 1);
|
||||
box-shadow: 0 0 18px rgba(34, 211, 238, 0.35);
|
||||
}
|
||||
.neon-select:hover {
|
||||
border-color: rgba(34, 211, 238, 0.5);
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
box-shadow: 0 0 16px rgba(34, 211, 238, 0.25);
|
||||
}
|
||||
.neon-select:focus {
|
||||
outline: none;
|
||||
border-color: rgba(34, 211, 238, 0.7);
|
||||
box-shadow: 0 0 20px rgba(34, 211, 238, 0.35), 0 0 0 2px rgba(34, 211, 238, 0.1);
|
||||
}
|
||||
.neon-select option {
|
||||
background: #0f172a;
|
||||
color: #fff;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user