Add interactive bar charts to Scores - #5
Conversation
5uck1ess
left a comment
There was a problem hiding this comment.
Thanks for this, @estebanstifli — and for the unusually careful integration work. Reusing the existing CSS custom properties, wiring into the existing #filter / #reset-sort controls, building the bars with createElement + textContent rather than string HTML, adding aria-labels, and shipping it with zero new dependencies is exactly the right shape for a contribution here. Ruff is clean and scoring/tests/test_build_scores.py passes 4/4.
I built the page against a fixture, drove it in headless Chromium, and ran a second independent review pass over the diff. There are three things I need changed before I can merge, plus one judgment call I'd like your read on.
1. Bar lengths are min–max normalized, which misrepresents this particular data
fill.style.width = (12 + relative * 88) + '%';
// relative = span ? (lowerBetter ? max - row.value : row.value - min) / span : 1min/max come from the 15 charted rows, not from the metric's domain — so the lowest bar is always 12% and the highest is always 100%, whatever the actual spread is.
That's fine when the spread is wide. On this board it isn't. Measured against the live scoring/scores.csv, via the same _model_scores path the table uses:
| chart | true value spread | rendered as | exaggeration |
|---|---|---|---|
| Default UTMOS, top 15 | 4.487 → 4.232 (+6.0%) | 100% vs 12% (8.33×) | 7.9× |
| Default WER, top 15 | 0.0232 → 0.0657 (2.83×) | 100% vs 12% (8.33×) | 2.9× |
So the #1 and #15 models on the UTMOS chart are within 6% of each other, and the chart draws one bar eight times longer than the other. On a page whose entire job is fair comparison between models, that's the one thing the visualization can't do.
The Relative range 4.232–4.487 caption is a real mitigation and I appreciate that you added it, but it's 0.78em muted text below-right of a chart whose whole visual argument is bar length — and it discloses the min–max normalization without explaining the arbitrary 12% floor. Exact labels don't correct the encoding.
Suggested fix: normalize against the metric's domain rather than the visible extremes. UTMOS is a MOS-scale prediction (~1–5), SIM is a cosine similarity (0–1), WER is a rate. Something like width = (value / domainMax) * 100 for higher-better, and (1 - value) * 100 for WER, gives honest bars.
If that flattens the chart too much to be useful — which it will, and that's the real signal here — a dot plot on a labelled axis shows small differences legibly without implying magnitude. I'd be happy with that.
2. Uncaught TypeError when a board has no scored models
var section = chart.closest('.subsection');
var table = section.querySelector('table');_scores_table() returns <p class="mode-empty muted">No scored models yet.</p> with no <table> when a board has nothing scored, but the chart is emitted unconditionally. section.querySelector('table') is then null and the initial render() throws.
Reproduced in Chromium with an empty cloning board:
PAGEERROR: Cannot read properties of null (reading 'querySelectorAll')
The throw aborts the whole IIFE. It only looks survivable today because the empty section happens to come last in document order — if the default board is the empty one (a cloning-only bench run, which is a normal thing to do), the throw lands on the first iteration and the cloning chart never gets wired at all.
Fix is one line at the top of the forEach:
if (!table) { chart.remove(); return; }3. Flagged models lose their warning in the chart
Rows whose mean WER exceeds WER_FAIL_THRESHOLD (0.5) get class="flagged" — dimmed to 55% opacity with a ⚠ appended — because at that error rate the model is producing broken speech and its other scores shouldn't be read at face value.
The chart pulls those rows in at full brightness with no marker. This isn't hypothetical on current data: miso (UTMOS 4.232, WER 0.586) is rank 15 of the live default-board UTMOS chart. It renders as a normal bar in a "top 15" list while the table three inches below dims it and flags it as broken.
Carrying tr.flagged through to .score-bar-row would cover it.
4. Judgment call: should WER be charted at all?
The page tells the reader twice that it shouldn't be read this way — _SCORES_GUIDE:
WER = ASR word-error rate vs the intended text — a failure-detector, not a fine ranking
and scores-foot:
Scored over the 5 bench prompts (thin — WER is a failure-detector, not a fine ranking)
A ranked top-15 WER bar chart is a fine-ranking presentation of exactly that metric, and the scale caption reads longer is better, which is true of the bar but parses as "higher WER is better" at a glance.
My instinct is to drop the WER button and chart only UTMOS and SIM, or render WER as a pass/fail marker instead of a ranked bar. Interested in your view — you may see a framing I'm missing.
Smaller notes
- The
@media(max-width:600px)block restyles.controls .lens-tabsand.controls input. Those are the shared top control bar, not the chart. Sincescores_styleis only injected onscores.html, the control bar would wrap differently on Scores than on Listen / Speed / Capabilities. Worth scoping to the chart selectors. - The new test assertions check exact implementation bytes (
"var limit = 15" in html,'.score-bar-fill{display:block' in html,"longer is better" in html), so any cosmetic edit breaks them. Thedata-metricandscore-chartcount assertions are the useful ones — I'd keep those and drop the rest. - CI hasn't run on this yet; it's waiting on my approval for a first-time contributor. I'll kick it off.
Genuinely good work on the plumbing — items 2 and 3 are small, and item 1 is the one that matters. Happy to talk through the axis question if you'd rather change direction than patch it.
|
Thanks for the careful review. All four points made sense, and I pushed
Validation after the update: |
🔍 REVIEW — Codex merge-readinessReview only — a verdict on this PR; no code was changed. Corrections, when applied, arrive as a separate
Reviewed head One finding survives:
VERDICT: CHANGES_REQUESTED - SIM chart mishandles valid negative scores Automated pre-merge review by |
Corrections appliedThanks, confirmed. Pushed
Validation: |
🔍 REVIEW — Codex merge-readinessReview only — a verdict on this PR; no code was changed. Corrections, when applied, arrive as a separate
Reviewed head
VERDICT: READY Automated pre-merge review by |
|
@estebanstifli thanks for your work. how are you automating your PRs? cronjobs like me? or something else? |
Republished gh-pages (b4b792a) to carry PR #5's Scores bar charts and the 12 linux-default models that were sitting in an unpushed local commit. That also pushed the corrected 'checkmark (80+)' cell for fish_s2 live, which the task had wanted held until the FR clip existed. Recording the tradeoff so the inconsistency is tracked rather than rediscovered.
Hi! First of all, congratulations on the project. I think it’s a genuinely useful resource for comparing local TTS models, and I wanted to contribute a small improvement to the way the results are displayed.
This PR adds horizontal bar charts to the Scores page. The idea is to make it easier to see at a glance which models stand out, while keeping the existing tables available for detailed inspection.
The chart:
I’ve also tried to preserve the project’s current visual style so that the chart feels like a natural addition to the page.
I hope you find it useful! I’d be happy to adjust the design or behaviour if you would prefer a different approach. Thanks for all the work you’re putting into
tts-bench!