Spaces:
Sleeping
Sleeping
Download src/lib/explorer/CorrelationHeatmap.svelte from mfuntowicz/hfinfer: direct link, hf CLI and curl.
- Browser
- Download file 6.9 kB
-
https://huggingface.co/spaces/mfuntowicz/hfinfer/resolve/main/src/lib/explorer/CorrelationHeatmap.svelte
- Command line
-
hf download hf://spaces/mfuntowicz/hfinfer/src/lib/explorer/CorrelationHeatmap.svelte
-
curl -L -o CorrelationHeatmap.svelte https://huggingface.co/spaces/mfuntowicz/hfinfer/resolve/main/src/lib/explorer/CorrelationHeatmap.svelte
6.9 kB
| <script lang="ts"> | |
| import type { CellReason, CorrelationCell, CorrelationModel } from './model' | |
| import { metricLabel } from '../types' | |
| interface Props { | |
| model: CorrelationModel | |
| selected: { axis: string; metric: string } | |
| onselect: (axis: string, metric: string) => void | |
| } | |
| let { model, selected, onselect }: Props = $props() | |
| const REASON_LABELS: Record<CellReason, string> = { | |
| 'constant-axis': 'constant axis', | |
| 'constant-metric': 'constant metric', | |
| coverage: 'below gates', | |
| missing: 'no data', | |
| } | |
| let cellMap = $derived.by(() => { | |
| const m = new Map<string, CorrelationCell>() | |
| for (const c of model.cells) m.set(`${c.axis}\u0001${c.metric}`, c) | |
| return m | |
| }) | |
| function cellAt(axis: string, metric: string): CorrelationCell | undefined { | |
| return cellMap.get(`${axis}\u0001${metric}`) | |
| } | |
| /** Signed coefficient, e.g. '+0.82' / 'β0.31' (true minus sign). */ | |
| function signedRho(rho: number): string { | |
| return `${rho < 0 ? 'β' : '+'}${Math.abs(rho).toFixed(2)}` | |
| } | |
| /** Only ever show a coefficient the model computed AND passed its gates. */ | |
| function cellText(c: CorrelationCell | undefined): string { | |
| return c && c.passed && c.rho !== null ? signedRho(c.rho) : 'β' | |
| } | |
| function reasonText(c: CorrelationCell | undefined): string { | |
| return REASON_LABELS[c?.reason ?? 'missing'] | |
| } | |
| function cellTitle(axis: string, metric: string, c: CorrelationCell | undefined): string { | |
| const head = `${axis} Β· ${metricLabel(metric)}` | |
| if (c && c.passed && c.rho !== null) { | |
| return `${head} β Spearman rho ${signedRho(c.rho)} (${c.n} configurations)` | |
| } | |
| return `${head} β unavailable: ${reasonText(c)} (${c ? c.n : 0} configurations)` | |
| } | |
| function cellAria(axis: string, metric: string, c: CorrelationCell | undefined): string { | |
| const head = `axis ${axis}, metric ${metricLabel(metric)}` | |
| if (c && c.passed && c.rho !== null) { | |
| return `${head}: Spearman rho ${signedRho(c.rho)}, ${c.n} configurations` | |
| } | |
| return `${head}: unavailable β ${reasonText(c)}, ${c ? c.n : 0} configurations` | |
| } | |
| /** Diverging tint: blue (accent) positive, orange negative, strength = |rho|. */ | |
| function cellStyle(c: CorrelationCell | undefined): string { | |
| if (!c || c.rho === null) return '' | |
| const pct = Math.round(10 + 65 * Math.min(1, Math.abs(c.rho))) | |
| const color = c.rho >= 0 ? '--accent' : '--bad' | |
| return `background-color: color-mix(in srgb, var(${color}) ${pct}%, transparent)` | |
| } | |
| let hasGrid = $derived(model.axes.length > 0 && model.metrics.length > 0) | |
| </script> | |
| <div class="hm-card"> | |
| {#if !hasGrid} | |
| <div class="empty">No measurements match the current filters.</div> | |
| {:else} | |
| <div | |
| class="hm" | |
| style="grid-template-columns: max-content repeat({model.metrics.length}, minmax(4.75rem, 1fr))" | |
| > | |
| <div class="corner"></div> | |
| {#each model.metrics as metric (metric)} | |
| <div class="col-head">{metricLabel(metric)}</div> | |
| {/each} | |
| {#each model.axes as axis (axis)} | |
| <div class="row-head">{metricLabel(axis)}</div> | |
| {#each model.metrics as metric (metric)} | |
| {@const c = cellAt(axis, metric)} | |
| <button | |
| class="cell" | |
| class:sel={selected.axis === axis && selected.metric === metric} | |
| class:hatched={!!c && !c.passed && c.reason === 'coverage'} | |
| style={cellStyle(c)} | |
| title={cellTitle(axis, metric, c)} | |
| aria-label={cellAria(axis, metric, c)} | |
| onclick={() => onselect(axis, metric)} | |
| > | |
| <span class="rho">{cellText(c)}</span> | |
| {#if !c || !c.passed} | |
| <span class="reason">{reasonText(c)}</span> | |
| {/if} | |
| </button> | |
| {/each} | |
| {/each} | |
| </div> | |
| <div class="zero-legend"> | |
| <div class="strip"></div> | |
| <div class="strip-labels"> | |
| <span>β1</span> | |
| <span>0</span> | |
| <span>+1</span> | |
| </div> | |
| </div> | |
| <p class="caption"> | |
| Spearman Ο per cell β positive = blue tint, negative = orange tint, strength = saturation. | |
| </p> | |
| {/if} | |
| <p class="gates"> | |
| Coverage gates: at least {model.gates.minConfigs} configurations and {model.gates.minValues} | |
| distinct axis values per cell β these are coverage rules, not a guarantee of statistical | |
| significance. | |
| </p> | |
| </div> | |
| <style> | |
| .hm-card { | |
| background: var(--card); | |
| border: 1px solid var(--border); | |
| border-radius: 10px; | |
| padding: 0.9rem 1rem; | |
| min-width: 0; | |
| } | |
| .hm { | |
| display: grid; | |
| gap: 3px; | |
| align-items: stretch; | |
| } | |
| .corner { | |
| /* top-left empty slot of the grid: keeps the header row aligned */ | |
| min-width: 2.5rem; | |
| } | |
| .col-head { | |
| font-size: 0.68rem; | |
| color: var(--muted); | |
| text-align: center; | |
| padding: 0.2rem 0.15rem; | |
| overflow-wrap: anywhere; | |
| } | |
| .row-head { | |
| font-size: 0.72rem; | |
| color: var(--muted); | |
| display: flex; | |
| align-items: center; | |
| padding-right: 0.5rem; | |
| white-space: nowrap; | |
| } | |
| .cell { | |
| font: inherit; | |
| font-size: 0.78rem; | |
| font-variant-numeric: tabular-nums; | |
| color: var(--fg); | |
| background-color: transparent; | |
| border: 1px solid var(--border); | |
| border-radius: 5px; | |
| min-height: 2.6rem; | |
| padding: 0.25rem 0.2rem; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| justify-content: center; | |
| gap: 0.1rem; | |
| cursor: pointer; | |
| } | |
| .cell:hover { | |
| border-color: var(--accent); | |
| } | |
| .cell.sel { | |
| outline: 2px solid var(--accent); | |
| outline-offset: -2px; | |
| border-color: var(--accent); | |
| } | |
| .cell.hatched { | |
| background-image: repeating-linear-gradient( | |
| 45deg, | |
| transparent 0 5px, | |
| var(--border) 5px 7px | |
| ); | |
| opacity: 0.75; | |
| } | |
| .rho { | |
| font-weight: 600; | |
| } | |
| .reason { | |
| font-size: 0.58rem; | |
| color: var(--muted); | |
| text-transform: uppercase; | |
| letter-spacing: 0.03em; | |
| white-space: nowrap; | |
| } | |
| .zero-legend { | |
| margin-top: 0.7rem; | |
| max-width: 15rem; | |
| } | |
| .strip { | |
| height: 8px; | |
| border-radius: 4px; | |
| border: 1px solid var(--border); | |
| background: linear-gradient( | |
| 90deg, | |
| color-mix(in srgb, var(--bad) 75%, transparent), | |
| color-mix(in srgb, var(--bad) 0%, transparent) 50%, | |
| color-mix(in srgb, var(--accent) 75%, transparent) | |
| ); | |
| } | |
| .strip-labels { | |
| display: flex; | |
| justify-content: space-between; | |
| font-size: 0.62rem; | |
| color: var(--muted); | |
| font-variant-numeric: tabular-nums; | |
| margin-top: 0.15rem; | |
| } | |
| .caption { | |
| margin: 0.35rem 0 0; | |
| font-size: 0.68rem; | |
| color: var(--muted); | |
| } | |
| .gates { | |
| margin: 0.6rem 0 0; | |
| font-size: 0.68rem; | |
| color: var(--muted); | |
| border-top: 1px solid var(--border); | |
| padding-top: 0.5rem; | |
| } | |
| .empty { | |
| color: var(--muted); | |
| font-size: 0.85rem; | |
| padding: 2.5rem 1rem; | |
| text-align: center; | |
| } | |
| </style> | |