Spaces:
Running
Running
Update public/index.html
Browse files- public/index.html +106 -116
public/index.html
CHANGED
|
@@ -10,7 +10,6 @@
|
|
| 10 |
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
| 11 |
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=Rubik:wght@500;600;700;800&display=swap" rel="stylesheet">
|
| 12 |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
| 13 |
-
|
| 14 |
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
| 15 |
|
| 16 |
<style>
|
|
@@ -48,18 +47,10 @@
|
|
| 48 |
|
| 49 |
/* --- HERO --- */
|
| 50 |
.hero {
|
| 51 |
-
max-width: 1000px;
|
| 52 |
-
|
| 53 |
-
padding: 0 40px;
|
| 54 |
-
position: relative;
|
| 55 |
-
text-align: center;
|
| 56 |
-
}
|
| 57 |
-
.hero-content {
|
| 58 |
-
z-index: 2;
|
| 59 |
-
display: flex;
|
| 60 |
-
flex-direction: column;
|
| 61 |
-
align-items: center;
|
| 62 |
}
|
|
|
|
| 63 |
.conf-badge { background: white; color: black; display: inline-flex; align-items: center; gap: 10px; padding: 6px 16px; border-radius: 8px; margin-bottom: 30px; font-weight: 700; font-size: 14px; }
|
| 64 |
.conf-badge i { color: #cc0000; font-size: 18px; }
|
| 65 |
.hero h1 { font-family: 'Rubik', sans-serif; font-size: 64px; font-weight: 800; margin: 0; line-height: 1.1; }
|
|
@@ -71,25 +62,17 @@
|
|
| 71 |
.bg-red { background: var(--grad-red); } .bg-purple { background: var(--grad-purple); }
|
| 72 |
|
| 73 |
/* Wave Animation */
|
| 74 |
-
.wave-bg {
|
| 75 |
-
|
| 76 |
-
background: radial-gradient(circle, rgba(255,255,255,0.03) 0%, rgba(255,255,255,0) 70%);
|
| 77 |
-
z-index: 0; pointer-events: none;
|
| 78 |
-
}
|
| 79 |
-
.audio-wave {
|
| 80 |
-
height: 150px; display: flex; align-items: center; justify-content: center; gap: 6px;
|
| 81 |
-
margin-top: 60px; mask-image: linear-gradient(to right, transparent, black 10%, black 90%, transparent);
|
| 82 |
-
}
|
| 83 |
.bar { width: 6px; background: #ffffff; border-radius: 99px; animation: bounce 1.2s ease-in-out infinite; }
|
| 84 |
.bar:nth-child(odd) { animation-duration: 1.5s; } .bar:nth-child(2n) { animation-duration: 1.1s; }
|
| 85 |
@keyframes bounce { 0%, 100% { height: 20px; } 50% { height: 120px; } }
|
| 86 |
|
| 87 |
-
/* ---
|
| 88 |
.content-container { width: 80%; max-width: 1600px; margin: 0 auto; }
|
| 89 |
-
|
| 90 |
-
/* --- MAIN BOARD --- */
|
| 91 |
.main-card { background: white; border-radius: 24px; padding: 40px; color: #0f172a; min-height: 600px; position: relative; z-index: 10; margin-bottom: 60px; }
|
| 92 |
|
|
|
|
| 93 |
.tabs { display: flex; gap: 10px; border-bottom: 2px solid #e2e8f0; margin-bottom: 30px; padding-bottom: 10px; overflow-x: auto; }
|
| 94 |
.tab-btn { background: none; border: none; font-size: 15px; font-weight: 600; color: #64748b; padding: 10px 20px; cursor: pointer; transition: 0.3s; border-radius: 8px; white-space: nowrap; }
|
| 95 |
.tab-btn:hover { background: #f1f5f9; color: var(--bg-deep); }
|
|
@@ -97,12 +80,53 @@
|
|
| 97 |
.view { display: none !important; animation: fadeIn 0.4s; }
|
| 98 |
.view.active { display: block !important; }
|
| 99 |
|
|
|
|
| 100 |
.controls { background: #f8fafc; padding: 20px; border-radius: 12px; display: flex; gap: 20px; align-items: center; margin-bottom: 20px; flex-wrap: wrap; }
|
| 101 |
select { padding: 10px; border-radius: 8px; border: 1px solid #cbd5e1; font-size: 14px; min-width: 250px; cursor:pointer; }
|
| 102 |
-
|
| 103 |
.rank-info { font-size: 13px; color: #64748b; margin-top: 5px; font-style: italic; }
|
| 104 |
.score-desc { font-size: 14px; color: #475569; background: #fff7ed; border-left: 4px solid #f97316; padding: 10px 15px; margin-bottom: 20px; border-radius: 4px; }
|
| 105 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 106 |
/* Table */
|
| 107 |
.table-wrap { overflow-x: auto; border: 1px solid #e2e8f0; border-radius: 12px; margin-bottom: 30px; }
|
| 108 |
table { width: 100%; border-collapse: collapse; font-size: 14px; }
|
|
@@ -112,28 +136,10 @@
|
|
| 112 |
th:first-child, td:first-child { position: sticky; left: 0; background: white; z-index: 2; text-align: left; font-weight: 700; color: #0f172a; border-right: 2px solid #f1f5f9; min-width: 220px; }
|
| 113 |
thead th:first-child { background: #f1f5f9; z-index: 3; }
|
| 114 |
th:nth-child(2), td:nth-child(2) { text-align: left; }
|
| 115 |
-
|
| 116 |
h4 { color: var(--text-yellow); font-family: 'Rubik', sans-serif; font-size: 18px; margin: 0 0 15px 0; border-bottom: 1px solid #eee; padding-bottom: 10px; }
|
| 117 |
|
| 118 |
-
/* --- SUMMARY CARDS (Language Analysis) --- */
|
| 119 |
-
.summary-grid {
|
| 120 |
-
display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 20px; margin-bottom: 40px;
|
| 121 |
-
}
|
| 122 |
-
.stat-card {
|
| 123 |
-
background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 12px; padding: 20px;
|
| 124 |
-
box-shadow: 0 2px 8px rgba(0,0,0,0.05); text-align: center; transition: 0.3s;
|
| 125 |
-
}
|
| 126 |
-
.stat-card:hover { transform: translateY(-3px); border-color: var(--text-yellow); }
|
| 127 |
-
.stat-title { font-size: 12px; font-weight: 700; color: #64748b; text-transform: uppercase; letter-spacing: 1px; margin-bottom: 10px; }
|
| 128 |
-
.stat-model { font-size: 18px; font-weight: 700; color: #0f172a; margin-bottom: 5px; }
|
| 129 |
-
.stat-val { font-size: 14px; color: var(--text-yellow); font-weight: 600; }
|
| 130 |
-
|
| 131 |
-
/* --- CHART CONTAINERS --- */
|
| 132 |
-
.chart-section { margin-bottom: 50px; }
|
| 133 |
-
.chart-box { height: 300px; width: 100%; margin-bottom: 20px; }
|
| 134 |
-
|
| 135 |
/* Citations */
|
| 136 |
-
.citation-section { background: #0f172a; border: 1px solid #334155; border-radius: 16px; padding: 40px; box-sizing: border-box; box-shadow: 0 20px 40px rgba(0,0,0,0.3); position: relative; }
|
| 137 |
.citation-section h3 { color: var(--text-yellow); font-family: 'Rubik', sans-serif; font-size: 24px; margin-top: 0; }
|
| 138 |
.citation-box { background: #1e293b; color: #cbd5e1; padding: 20px; border-radius: 12px; font-family: monospace; white-space: pre-wrap; margin-top: 20px; overflow-x: auto; border: 1px solid #334155; font-size: 13px; line-height: 1.5; }
|
| 139 |
.copy-btn { position: absolute; top: 40px; right: 40px; background: var(--text-yellow); border: none; padding: 8px 16px; border-radius: 6px; font-weight: bold; cursor: pointer; color: #0f172a; }
|
|
@@ -195,9 +201,9 @@
|
|
| 195 |
<div id="asr" class="view active">
|
| 196 |
<div class="controls">
|
| 197 |
<span style="font-weight:700;">View Mode:</span>
|
| 198 |
-
<button id="btn-over" class="
|
| 199 |
-
<button id="btn-fam" class="
|
| 200 |
-
<button id="btn-mod" class="
|
| 201 |
|
| 202 |
<div style="flex-grow:1; text-align:right;">
|
| 203 |
<div id="grp-fam" style="display:none;"><label>Select Family: </label><select id="asr-select-fam" onchange="renderASR()"></select></div>
|
|
@@ -240,20 +246,20 @@
|
|
| 240 |
<div id="lang-summary" class="summary-grid"></div>
|
| 241 |
|
| 242 |
<div class="chart-section">
|
| 243 |
-
<h4>Automatic Speech Recognition (ASR) <span class="rank-info" style="font-weight:normal;">(WER β)</span></h4>
|
| 244 |
-
<div class="chart-
|
| 245 |
<div id="lang-asr-table" class="table-wrap"></div>
|
| 246 |
</div>
|
| 247 |
|
| 248 |
<div class="chart-section">
|
| 249 |
-
<h4>Text-to-Speech (TTS)</h4>
|
| 250 |
-
<div class="chart-
|
| 251 |
<div id="lang-tts-table" class="table-wrap"></div>
|
| 252 |
</div>
|
| 253 |
|
| 254 |
<div class="chart-section">
|
| 255 |
-
<h4>Spoken Language Identification (SLID) <span class="rank-info" style="font-weight:normal;">(
|
| 256 |
-
<div class="chart-
|
| 257 |
<div id="lang-slid-table" class="table-wrap"></div>
|
| 258 |
</div>
|
| 259 |
</div>
|
|
@@ -287,11 +293,10 @@
|
|
| 287 |
</div>
|
| 288 |
|
| 289 |
<script>
|
| 290 |
-
// --- DATA & LOGIC ---
|
| 291 |
const NAME_TO_ISO = {'Afrikaans':'afr', 'Akuapim-twi':'ak', 'Amharic':'amh', 'Asante-twi':'tw', 'Bemba':'bem', 'Hausa':'hau', 'Igbo':'ibo', 'Yoruba':'yor', 'Zulu':'zul', 'Swahili':'swa', 'Somali':'som', 'Wolof':'wol', 'Xhosa':'xho'};
|
| 292 |
let DATA = null;
|
| 293 |
let asrMode = 'overview';
|
| 294 |
-
let charts = { asr:null, tts:null, slid:null };
|
| 295 |
|
| 296 |
fetch('/api/data').then(r=>r.json()).then(d=>{
|
| 297 |
DATA = d;
|
|
@@ -325,7 +330,7 @@
|
|
| 325 |
return `${name} (${iso})`;
|
| 326 |
}
|
| 327 |
|
| 328 |
-
// ---
|
| 329 |
function renderASR() {
|
| 330 |
const div = document.getElementById('asr-table');
|
| 331 |
const title = document.getElementById('asr-title');
|
|
@@ -346,21 +351,13 @@
|
|
| 346 |
if(n>0) modelStats.push({ name: modName, wer: sW/n, cer: sC/n });
|
| 347 |
});
|
| 348 |
|
| 349 |
-
modelStats.sort((a,b) => (a.wer - b.wer) || (a.cer - b.cer));
|
| 350 |
-
|
| 351 |
html = `<table><thead><tr><th>Model</th><th>Simba ASR Score (WER/CER) <i class="fa-solid fa-arrow-down arrow-down"></i></th></tr></thead><tbody>`;
|
| 352 |
|
| 353 |
let currentRank = 1;
|
| 354 |
modelStats.forEach((m, i) => {
|
| 355 |
-
if (i > 0)
|
| 356 |
-
|
| 357 |
-
if (Math.abs(m.wer - prev.wer) > 0.001) { currentRank = i + 1; }
|
| 358 |
-
} else { currentRank = 1; }
|
| 359 |
-
m.rank = currentRank;
|
| 360 |
-
|
| 361 |
-
let icon = '';
|
| 362 |
-
if(m.rank === 1) icon = 'π₯ '; else if(m.rank === 2) icon = 'π₯ '; else if(m.rank === 3) icon = 'π₯ ';
|
| 363 |
-
|
| 364 |
html += `<tr><td style="font-weight:600; color:var(--simba-navy)">${icon}${m.name}</td><td><b>${fmt(m.wer)} / ${fmt(m.cer)}</b></td></tr>`;
|
| 365 |
});
|
| 366 |
|
|
@@ -373,7 +370,6 @@
|
|
| 373 |
|
| 374 |
const d = DATA.asr.by_family[val];
|
| 375 |
if(!d) return;
|
| 376 |
-
|
| 377 |
d.data.sort((a,b) => a.Avg_WER - b.Avg_WER);
|
| 378 |
|
| 379 |
html = `<table><thead><tr><th>Model</th><th>Simba ASR Family Score <i class="fa-solid fa-arrow-down arrow-down"></i></th>`;
|
|
@@ -382,15 +378,8 @@
|
|
| 382 |
|
| 383 |
let currentRank = 1;
|
| 384 |
d.data.forEach((r, idx) => {
|
| 385 |
-
if(idx > 0)
|
| 386 |
-
|
| 387 |
-
if (Math.abs(r.Avg_WER - prev.Avg_WER) > 0.001) currentRank = idx + 1;
|
| 388 |
-
} else { currentRank = 1; }
|
| 389 |
-
r.rank = currentRank;
|
| 390 |
-
|
| 391 |
-
let icon = '';
|
| 392 |
-
if(r.rank === 1) icon = 'π₯ '; else if(r.rank === 2) icon = 'π₯ '; else if(r.rank === 3) icon = 'π₯ ';
|
| 393 |
-
|
| 394 |
html += `<tr><td style="font-weight:600; color:var(--simba-navy)">${icon}${r.Model}</td><td style="font-weight:bold; background:#f8fafc">${fmt(r.Avg_WER)} / ${fmt(r.Avg_CER)}</td>`;
|
| 395 |
d.languages.forEach(l => {
|
| 396 |
let w=r[`WER_${l.name}`], c=r[`CER_${l.name}`];
|
|
@@ -414,7 +403,7 @@
|
|
| 414 |
div.innerHTML = html;
|
| 415 |
}
|
| 416 |
|
| 417 |
-
// ---
|
| 418 |
function renderTTS() {
|
| 419 |
const val = document.getElementById('tts-select').value;
|
| 420 |
document.getElementById('tts-title').innerText = `Model: ${val}`;
|
|
@@ -444,7 +433,7 @@
|
|
| 444 |
document.getElementById('tts-table').innerHTML = html;
|
| 445 |
}
|
| 446 |
|
| 447 |
-
// ---
|
| 448 |
function renderSLID() {
|
| 449 |
let html = `<table><thead><tr><th>Language</th><th>MMS-LID-1024 (β)</th><th>Simba-SLID (β)</th></tr></thead><tbody>`;
|
| 450 |
DATA.slid.forEach(r => {
|
|
@@ -456,11 +445,11 @@
|
|
| 456 |
document.getElementById('slid-table').innerHTML = html;
|
| 457 |
}
|
| 458 |
|
| 459 |
-
// ---
|
| 460 |
function renderLanguageView() {
|
| 461 |
const lang = document.getElementById('lang-select').value;
|
| 462 |
|
| 463 |
-
// 1. ASR
|
| 464 |
let asrRows = [];
|
| 465 |
Object.keys(DATA.asr.by_model).forEach(modName => {
|
| 466 |
const row = DATA.asr.by_model[modName].find(r => r.Language === lang);
|
|
@@ -468,14 +457,14 @@
|
|
| 468 |
});
|
| 469 |
asrRows.sort((a,b) => (a.wer - b.wer) || (a.cer - b.cer));
|
| 470 |
|
| 471 |
-
// 2. TTS
|
| 472 |
let ttsRows = [];
|
| 473 |
Object.keys(DATA.tts).forEach(mod => {
|
| 474 |
const row = DATA.tts[mod].find(r => r.language === lang);
|
| 475 |
if(row) ttsRows.push({model: mod, ...row});
|
| 476 |
});
|
| 477 |
|
| 478 |
-
// 3. SLID
|
| 479 |
let slidRows = [];
|
| 480 |
const slidRow = DATA.slid.find(r => r.Language === lang) ||
|
| 481 |
DATA.slid.find(r => r.Language.startsWith(lang + " (")) ||
|
|
@@ -483,45 +472,53 @@
|
|
| 483 |
if(slidRow) {
|
| 484 |
const models = Object.keys(slidRow).filter(k => k !== 'Language' && k !== 'ISO');
|
| 485 |
models.forEach(m => slidRows.push({model: m, val: slidRow[m]}));
|
| 486 |
-
slidRows.sort((a,b) => b.val - a.val);
|
| 487 |
}
|
| 488 |
|
| 489 |
-
// ---
|
| 490 |
let summaryHTML = "";
|
| 491 |
|
| 492 |
-
// ASR
|
| 493 |
if(asrRows.length > 0) {
|
| 494 |
summaryHTML += `<div class="stat-card"><div class="stat-title">Best ASR Model</div><div class="stat-model">${asrRows[0].model}</div><div class="stat-val">WER: ${fmt(asrRows[0].wer)}%</div></div>`;
|
| 495 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 496 |
|
| 497 |
-
// TTS
|
| 498 |
if(ttsRows.length > 0) {
|
| 499 |
-
// Sort by WER ascending
|
| 500 |
const bestTTS = [...ttsRows].sort((a,b) => a.wer - b.wer)[0];
|
| 501 |
summaryHTML += `<div class="stat-card"><div class="stat-title">Best TTS (Intelligibility)</div><div class="stat-model">${bestTTS.model}</div><div class="stat-val">WER: ${fmt(bestTTS.wer)}%</div></div>`;
|
| 502 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 503 |
|
| 504 |
-
// SLID
|
| 505 |
if(slidRows.length > 0) {
|
| 506 |
summaryHTML += `<div class="stat-card"><div class="stat-title">Best SLID Model</div><div class="stat-model">${slidRows[0].model}</div><div class="stat-val">F1: ${fmt(slidRows[0].val)}%</div></div>`;
|
| 507 |
-
|
| 508 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 509 |
document.getElementById('lang-summary').innerHTML = summaryHTML;
|
| 510 |
|
| 511 |
-
// --- RENDER CHARTS ---
|
| 512 |
-
updateChart('chart-asr', 'ASR (WER Lower is Better)', asrRows.map(r=>r.model), asrRows.map(r=>r.wer), '#3b82f6');
|
| 513 |
-
updateChart('chart-tts', 'TTS (WER Lower is Better)', ttsRows.map(r=>r.model), ttsRows.map(r=>r.wer), '#f97316');
|
| 514 |
-
updateChart('chart-slid', 'SLID (F1 Higher is Better)', slidRows.map(r=>r.model), slidRows.map(r=>r.val), '#22c55e');
|
| 515 |
-
|
| 516 |
-
// --- RENDER TABLES ---
|
| 517 |
|
| 518 |
-
//
|
|
|
|
| 519 |
let h1 = `<table><thead><tr><th>Model</th><th>WER <i class="fa-solid fa-arrow-down arrow-down"></i></th><th>CER <i class="fa-solid fa-arrow-down arrow-down"></i></th></tr></thead><tbody>`;
|
| 520 |
if(asrRows.length === 0) h1 += `<tr><td colspan="3" class="empty-msg">No ASR results available for ${lang}.</td></tr>`;
|
| 521 |
else {
|
| 522 |
let currentRank = 1;
|
| 523 |
asrRows.forEach((r, i) => {
|
| 524 |
-
if (i > 0
|
| 525 |
let icon = (currentRank===1)?'π₯ ':(currentRank===2)?'π₯ ':(currentRank===3)?'π₯ ':'';
|
| 526 |
h1 += `<tr><td style="font-weight:600; color:var(--simba-navy)">${icon}${r.model}</td><td>${fmt(r.wer)}</td><td>${fmt(r.cer)}</td></tr>`;
|
| 527 |
});
|
|
@@ -529,7 +526,7 @@
|
|
| 529 |
h1 += `</tbody></table>`;
|
| 530 |
document.getElementById('lang-asr-table').innerHTML = h1;
|
| 531 |
|
| 532 |
-
// TTS
|
| 533 |
let h2 = `<table><thead><tr><th>Model</th>
|
| 534 |
<th>WER(β)</th><th>MCD(β)</th><th>LogF0RMSE(β)</th><th>SpeechTokenDistance(β)</th>
|
| 535 |
<th>PESQ(β)</th><th>UTMOS(β)</th><th>SpeechBLEU(β)</th><th>SpeechBERTScore(β)</th>
|
|
@@ -549,13 +546,13 @@
|
|
| 549 |
h2 += `</tbody></table>`;
|
| 550 |
document.getElementById('lang-tts-table').innerHTML = h2;
|
| 551 |
|
| 552 |
-
// SLID
|
| 553 |
let h3 = `<table><thead><tr><th>Model</th><th>F1-Score <i class="fa-solid fa-arrow-up arrow-up"></i></th></tr></thead><tbody>`;
|
| 554 |
if(slidRows.length === 0) h3 += `<tr><td colspan="2" class="empty-msg">No SLID results available for ${lang}.</td></tr>`;
|
| 555 |
else {
|
| 556 |
let currentRank = 1;
|
| 557 |
slidRows.forEach((sc, i) => {
|
| 558 |
-
if (i > 0
|
| 559 |
let icon = (currentRank===1)?'π₯ ':(currentRank===2)?'π₯ ':(currentRank===3)?'π₯ ':'';
|
| 560 |
h3 += `<tr><td style="font-weight:600;">${icon}${sc.model}</td><td>${fmt(sc.val)}</td></tr>`;
|
| 561 |
});
|
|
@@ -564,14 +561,10 @@
|
|
| 564 |
document.getElementById('lang-slid-table').innerHTML = h3;
|
| 565 |
}
|
| 566 |
|
| 567 |
-
// --- CHART LOGIC ---
|
| 568 |
function updateChart(canvasId, label, labels, data, color) {
|
| 569 |
const ctx = document.getElementById(canvasId).getContext('2d');
|
| 570 |
-
|
| 571 |
-
// Destroy existing chart if it exists
|
| 572 |
if(charts[canvasId]) charts[canvasId].destroy();
|
| 573 |
-
|
| 574 |
-
if(labels.length === 0) return; // Don't draw empty chart
|
| 575 |
|
| 576 |
charts[canvasId] = new Chart(ctx, {
|
| 577 |
type: 'bar',
|
|
@@ -585,10 +578,11 @@
|
|
| 585 |
}]
|
| 586 |
},
|
| 587 |
options: {
|
|
|
|
| 588 |
responsive: true,
|
| 589 |
maintainAspectRatio: false,
|
| 590 |
plugins: { legend: { display: true } },
|
| 591 |
-
scales: {
|
| 592 |
}
|
| 593 |
});
|
| 594 |
}
|
|
@@ -604,12 +598,8 @@
|
|
| 604 |
|
| 605 |
window.setASRMode = (m) => {
|
| 606 |
asrMode = m;
|
| 607 |
-
['btn-over','btn-fam','btn-mod'].forEach(b =>
|
| 608 |
-
|
| 609 |
-
document.getElementById(b).style.background="#e2e8f0"; document.getElementById(b).style.color="#334155";
|
| 610 |
-
});
|
| 611 |
-
const activeBtn = document.getElementById(m==='overview'?'btn-over':(m==='family'?'btn-fam':'btn-mod'));
|
| 612 |
-
activeBtn.className = "action-btn bg-blue"; activeBtn.style.color="white"; activeBtn.style.background="";
|
| 613 |
renderASR();
|
| 614 |
}
|
| 615 |
|
|
|
|
| 10 |
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
| 11 |
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=Rubik:wght@500;600;700;800&display=swap" rel="stylesheet">
|
| 12 |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
|
|
|
| 13 |
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
| 14 |
|
| 15 |
<style>
|
|
|
|
| 47 |
|
| 48 |
/* --- HERO --- */
|
| 49 |
.hero {
|
| 50 |
+
max-width: 1000px; margin: 60px auto 100px; padding: 0 40px;
|
| 51 |
+
position: relative; text-align: center;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
}
|
| 53 |
+
.hero-content { z-index: 2; display: flex; flex-direction: column; align-items: center; }
|
| 54 |
.conf-badge { background: white; color: black; display: inline-flex; align-items: center; gap: 10px; padding: 6px 16px; border-radius: 8px; margin-bottom: 30px; font-weight: 700; font-size: 14px; }
|
| 55 |
.conf-badge i { color: #cc0000; font-size: 18px; }
|
| 56 |
.hero h1 { font-family: 'Rubik', sans-serif; font-size: 64px; font-weight: 800; margin: 0; line-height: 1.1; }
|
|
|
|
| 62 |
.bg-red { background: var(--grad-red); } .bg-purple { background: var(--grad-purple); }
|
| 63 |
|
| 64 |
/* Wave Animation */
|
| 65 |
+
.wave-bg { position: absolute; top: -50px; left: -50%; width: 200%; height: 100%; background: radial-gradient(circle, rgba(255,255,255,0.03) 0%, rgba(255,255,255,0) 70%); z-index: 0; pointer-events: none; }
|
| 66 |
+
.audio-wave { height: 150px; display: flex; align-items: center; justify-content: center; gap: 6px; margin-top: 60px; mask-image: linear-gradient(to right, transparent, black 10%, black 90%, transparent); }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
.bar { width: 6px; background: #ffffff; border-radius: 99px; animation: bounce 1.2s ease-in-out infinite; }
|
| 68 |
.bar:nth-child(odd) { animation-duration: 1.5s; } .bar:nth-child(2n) { animation-duration: 1.1s; }
|
| 69 |
@keyframes bounce { 0%, 100% { height: 20px; } 50% { height: 120px; } }
|
| 70 |
|
| 71 |
+
/* --- CONTENT --- */
|
| 72 |
.content-container { width: 80%; max-width: 1600px; margin: 0 auto; }
|
|
|
|
|
|
|
| 73 |
.main-card { background: white; border-radius: 24px; padding: 40px; color: #0f172a; min-height: 600px; position: relative; z-index: 10; margin-bottom: 60px; }
|
| 74 |
|
| 75 |
+
/* Tabs */
|
| 76 |
.tabs { display: flex; gap: 10px; border-bottom: 2px solid #e2e8f0; margin-bottom: 30px; padding-bottom: 10px; overflow-x: auto; }
|
| 77 |
.tab-btn { background: none; border: none; font-size: 15px; font-weight: 600; color: #64748b; padding: 10px 20px; cursor: pointer; transition: 0.3s; border-radius: 8px; white-space: nowrap; }
|
| 78 |
.tab-btn:hover { background: #f1f5f9; color: var(--bg-deep); }
|
|
|
|
| 80 |
.view { display: none !important; animation: fadeIn 0.4s; }
|
| 81 |
.view.active { display: block !important; }
|
| 82 |
|
| 83 |
+
/* Controls */
|
| 84 |
.controls { background: #f8fafc; padding: 20px; border-radius: 12px; display: flex; gap: 20px; align-items: center; margin-bottom: 20px; flex-wrap: wrap; }
|
| 85 |
select { padding: 10px; border-radius: 8px; border: 1px solid #cbd5e1; font-size: 14px; min-width: 250px; cursor:pointer; }
|
|
|
|
| 86 |
.rank-info { font-size: 13px; color: #64748b; margin-top: 5px; font-style: italic; }
|
| 87 |
.score-desc { font-size: 14px; color: #475569; background: #fff7ed; border-left: 4px solid #f97316; padding: 10px 15px; margin-bottom: 20px; border-radius: 4px; }
|
| 88 |
|
| 89 |
+
/* --- NEW ASR BUTTON STYLES --- */
|
| 90 |
+
.mode-btn {
|
| 91 |
+
padding: 8px 20px;
|
| 92 |
+
border-radius: 8px;
|
| 93 |
+
font-weight: 600;
|
| 94 |
+
font-size: 13px;
|
| 95 |
+
cursor: pointer;
|
| 96 |
+
transition: all 0.2s;
|
| 97 |
+
border: 1px solid #e2e8f0;
|
| 98 |
+
background: white;
|
| 99 |
+
color: var(--simba-navy);
|
| 100 |
+
}
|
| 101 |
+
.mode-btn:hover {
|
| 102 |
+
border-color: var(--text-yellow);
|
| 103 |
+
color: var(--text-yellow);
|
| 104 |
+
}
|
| 105 |
+
.mode-btn.active {
|
| 106 |
+
background-color: var(--simba-navy);
|
| 107 |
+
color: white;
|
| 108 |
+
border-color: var(--simba-navy);
|
| 109 |
+
box-shadow: 0 4px 12px rgba(15, 23, 42, 0.15);
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
/* --- CHARTS --- */
|
| 113 |
+
.chart-wrapper {
|
| 114 |
+
width: 100%;
|
| 115 |
+
/* Max height limits the chart size if small data */
|
| 116 |
+
max-height: 400px;
|
| 117 |
+
margin-bottom: 20px;
|
| 118 |
+
display: none; /* Hidden by default */
|
| 119 |
+
}
|
| 120 |
+
canvas { max-width: 100%; }
|
| 121 |
+
|
| 122 |
+
/* Summary Cards */
|
| 123 |
+
.summary-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 20px; margin-bottom: 40px; }
|
| 124 |
+
.stat-card { background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 12px; padding: 20px; box-shadow: 0 2px 8px rgba(0,0,0,0.05); text-align: center; transition: 0.3s; }
|
| 125 |
+
.stat-card:hover { transform: translateY(-3px); border-color: var(--text-yellow); }
|
| 126 |
+
.stat-title { font-size: 12px; font-weight: 700; color: #64748b; text-transform: uppercase; letter-spacing: 1px; margin-bottom: 10px; }
|
| 127 |
+
.stat-model { font-size: 18px; font-weight: 700; color: #0f172a; margin-bottom: 5px; }
|
| 128 |
+
.stat-val { font-size: 14px; color: var(--text-yellow); font-weight: 600; }
|
| 129 |
+
|
| 130 |
/* Table */
|
| 131 |
.table-wrap { overflow-x: auto; border: 1px solid #e2e8f0; border-radius: 12px; margin-bottom: 30px; }
|
| 132 |
table { width: 100%; border-collapse: collapse; font-size: 14px; }
|
|
|
|
| 136 |
th:first-child, td:first-child { position: sticky; left: 0; background: white; z-index: 2; text-align: left; font-weight: 700; color: #0f172a; border-right: 2px solid #f1f5f9; min-width: 220px; }
|
| 137 |
thead th:first-child { background: #f1f5f9; z-index: 3; }
|
| 138 |
th:nth-child(2), td:nth-child(2) { text-align: left; }
|
|
|
|
| 139 |
h4 { color: var(--text-yellow); font-family: 'Rubik', sans-serif; font-size: 18px; margin: 0 0 15px 0; border-bottom: 1px solid #eee; padding-bottom: 10px; }
|
| 140 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 141 |
/* Citations */
|
| 142 |
+
.citation-section { background: #0f172a; border: 1px solid #334155; border-radius: 16px; padding: 40px; box-sizing: border-box; box-shadow: 0 20px 40px rgba(0,0,0,0.3); position: relative; margin-bottom: 60px; }
|
| 143 |
.citation-section h3 { color: var(--text-yellow); font-family: 'Rubik', sans-serif; font-size: 24px; margin-top: 0; }
|
| 144 |
.citation-box { background: #1e293b; color: #cbd5e1; padding: 20px; border-radius: 12px; font-family: monospace; white-space: pre-wrap; margin-top: 20px; overflow-x: auto; border: 1px solid #334155; font-size: 13px; line-height: 1.5; }
|
| 145 |
.copy-btn { position: absolute; top: 40px; right: 40px; background: var(--text-yellow); border: none; padding: 8px 16px; border-radius: 6px; font-weight: bold; cursor: pointer; color: #0f172a; }
|
|
|
|
| 201 |
<div id="asr" class="view active">
|
| 202 |
<div class="controls">
|
| 203 |
<span style="font-weight:700;">View Mode:</span>
|
| 204 |
+
<button id="btn-over" class="mode-btn active" onclick="setASRMode('overview')">Overview</button>
|
| 205 |
+
<button id="btn-fam" class="mode-btn" onclick="setASRMode('family')">By Family</button>
|
| 206 |
+
<button id="btn-mod" class="mode-btn" onclick="setASRMode('model')">By Model</button>
|
| 207 |
|
| 208 |
<div style="flex-grow:1; text-align:right;">
|
| 209 |
<div id="grp-fam" style="display:none;"><label>Select Family: </label><select id="asr-select-fam" onchange="renderASR()"></select></div>
|
|
|
|
| 246 |
<div id="lang-summary" class="summary-grid"></div>
|
| 247 |
|
| 248 |
<div class="chart-section">
|
| 249 |
+
<h4>Automatic Speech Recognition (ASR) <span class="rank-info" style="font-weight:normal;">(Ranked by WER β, then CER β)</span></h4>
|
| 250 |
+
<div id="wrp-chart-asr" class="chart-wrapper"><canvas id="chart-asr"></canvas></div>
|
| 251 |
<div id="lang-asr-table" class="table-wrap"></div>
|
| 252 |
</div>
|
| 253 |
|
| 254 |
<div class="chart-section">
|
| 255 |
+
<h4>Text-to-Speech (TTS) Results</h4>
|
| 256 |
+
<div id="wrp-chart-tts" class="chart-wrapper"><canvas id="chart-tts"></canvas></div>
|
| 257 |
<div id="lang-tts-table" class="table-wrap"></div>
|
| 258 |
</div>
|
| 259 |
|
| 260 |
<div class="chart-section">
|
| 261 |
+
<h4>Spoken Language Identification (SLID) Results <span class="rank-info" style="font-weight:normal;">(Ranked by Score β)</span></h4>
|
| 262 |
+
<div id="wrp-chart-slid" class="chart-wrapper"><canvas id="chart-slid"></canvas></div>
|
| 263 |
<div id="lang-slid-table" class="table-wrap"></div>
|
| 264 |
</div>
|
| 265 |
</div>
|
|
|
|
| 293 |
</div>
|
| 294 |
|
| 295 |
<script>
|
|
|
|
| 296 |
const NAME_TO_ISO = {'Afrikaans':'afr', 'Akuapim-twi':'ak', 'Amharic':'amh', 'Asante-twi':'tw', 'Bemba':'bem', 'Hausa':'hau', 'Igbo':'ibo', 'Yoruba':'yor', 'Zulu':'zul', 'Swahili':'swa', 'Somali':'som', 'Wolof':'wol', 'Xhosa':'xho'};
|
| 297 |
let DATA = null;
|
| 298 |
let asrMode = 'overview';
|
| 299 |
+
let charts = { asr:null, tts:null, slid:null };
|
| 300 |
|
| 301 |
fetch('/api/data').then(r=>r.json()).then(d=>{
|
| 302 |
DATA = d;
|
|
|
|
| 330 |
return `${name} (${iso})`;
|
| 331 |
}
|
| 332 |
|
| 333 |
+
// --- ASR ---
|
| 334 |
function renderASR() {
|
| 335 |
const div = document.getElementById('asr-table');
|
| 336 |
const title = document.getElementById('asr-title');
|
|
|
|
| 351 |
if(n>0) modelStats.push({ name: modName, wer: sW/n, cer: sC/n });
|
| 352 |
});
|
| 353 |
|
| 354 |
+
modelStats.sort((a,b) => (a.wer - b.wer) || (a.cer - b.cer));
|
|
|
|
| 355 |
html = `<table><thead><tr><th>Model</th><th>Simba ASR Score (WER/CER) <i class="fa-solid fa-arrow-down arrow-down"></i></th></tr></thead><tbody>`;
|
| 356 |
|
| 357 |
let currentRank = 1;
|
| 358 |
modelStats.forEach((m, i) => {
|
| 359 |
+
if (i > 0 && Math.abs(m.wer - modelStats[i-1].wer) > 0.001) currentRank = i + 1; else currentRank = 1;
|
| 360 |
+
let icon = (currentRank===1)?'π₯ ':(currentRank===2)?'π₯ ':(currentRank===3)?'π₯ ':'';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 361 |
html += `<tr><td style="font-weight:600; color:var(--simba-navy)">${icon}${m.name}</td><td><b>${fmt(m.wer)} / ${fmt(m.cer)}</b></td></tr>`;
|
| 362 |
});
|
| 363 |
|
|
|
|
| 370 |
|
| 371 |
const d = DATA.asr.by_family[val];
|
| 372 |
if(!d) return;
|
|
|
|
| 373 |
d.data.sort((a,b) => a.Avg_WER - b.Avg_WER);
|
| 374 |
|
| 375 |
html = `<table><thead><tr><th>Model</th><th>Simba ASR Family Score <i class="fa-solid fa-arrow-down arrow-down"></i></th>`;
|
|
|
|
| 378 |
|
| 379 |
let currentRank = 1;
|
| 380 |
d.data.forEach((r, idx) => {
|
| 381 |
+
if(idx > 0 && Math.abs(r.Avg_WER - d.data[idx-1].Avg_WER) > 0.001) currentRank = idx + 1; else currentRank = 1;
|
| 382 |
+
let icon = (currentRank===1)?'π₯ ':(currentRank===2)?'π₯ ':(currentRank===3)?'π₯ ':'';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 383 |
html += `<tr><td style="font-weight:600; color:var(--simba-navy)">${icon}${r.Model}</td><td style="font-weight:bold; background:#f8fafc">${fmt(r.Avg_WER)} / ${fmt(r.Avg_CER)}</td>`;
|
| 384 |
d.languages.forEach(l => {
|
| 385 |
let w=r[`WER_${l.name}`], c=r[`CER_${l.name}`];
|
|
|
|
| 403 |
div.innerHTML = html;
|
| 404 |
}
|
| 405 |
|
| 406 |
+
// --- TTS ---
|
| 407 |
function renderTTS() {
|
| 408 |
const val = document.getElementById('tts-select').value;
|
| 409 |
document.getElementById('tts-title').innerText = `Model: ${val}`;
|
|
|
|
| 433 |
document.getElementById('tts-table').innerHTML = html;
|
| 434 |
}
|
| 435 |
|
| 436 |
+
// --- SLID ---
|
| 437 |
function renderSLID() {
|
| 438 |
let html = `<table><thead><tr><th>Language</th><th>MMS-LID-1024 (β)</th><th>Simba-SLID (β)</th></tr></thead><tbody>`;
|
| 439 |
DATA.slid.forEach(r => {
|
|
|
|
| 445 |
document.getElementById('slid-table').innerHTML = html;
|
| 446 |
}
|
| 447 |
|
| 448 |
+
// --- LANGUAGE VIEW + CHARTS ---
|
| 449 |
function renderLanguageView() {
|
| 450 |
const lang = document.getElementById('lang-select').value;
|
| 451 |
|
| 452 |
+
// 1. ASR
|
| 453 |
let asrRows = [];
|
| 454 |
Object.keys(DATA.asr.by_model).forEach(modName => {
|
| 455 |
const row = DATA.asr.by_model[modName].find(r => r.Language === lang);
|
|
|
|
| 457 |
});
|
| 458 |
asrRows.sort((a,b) => (a.wer - b.wer) || (a.cer - b.cer));
|
| 459 |
|
| 460 |
+
// 2. TTS
|
| 461 |
let ttsRows = [];
|
| 462 |
Object.keys(DATA.tts).forEach(mod => {
|
| 463 |
const row = DATA.tts[mod].find(r => r.language === lang);
|
| 464 |
if(row) ttsRows.push({model: mod, ...row});
|
| 465 |
});
|
| 466 |
|
| 467 |
+
// 3. SLID
|
| 468 |
let slidRows = [];
|
| 469 |
const slidRow = DATA.slid.find(r => r.Language === lang) ||
|
| 470 |
DATA.slid.find(r => r.Language.startsWith(lang + " (")) ||
|
|
|
|
| 472 |
if(slidRow) {
|
| 473 |
const models = Object.keys(slidRow).filter(k => k !== 'Language' && k !== 'ISO');
|
| 474 |
models.forEach(m => slidRows.push({model: m, val: slidRow[m]}));
|
| 475 |
+
slidRows.sort((a,b) => b.val - a.val);
|
| 476 |
}
|
| 477 |
|
| 478 |
+
// --- SUMMARY & CHARTS ---
|
| 479 |
let summaryHTML = "";
|
| 480 |
|
| 481 |
+
// ASR
|
| 482 |
if(asrRows.length > 0) {
|
| 483 |
summaryHTML += `<div class="stat-card"><div class="stat-title">Best ASR Model</div><div class="stat-model">${asrRows[0].model}</div><div class="stat-val">WER: ${fmt(asrRows[0].wer)}%</div></div>`;
|
| 484 |
+
document.getElementById('wrp-chart-asr').style.display = 'block';
|
| 485 |
+
updateChart('chart-asr', 'ASR WER (Lower is Better)', asrRows.map(r=>r.model), asrRows.map(r=>r.wer), '#3b82f6');
|
| 486 |
+
} else {
|
| 487 |
+
summaryHTML += `<div class="stat-card"><div class="stat-title">ASR</div><div class="stat-val">No Data</div></div>`;
|
| 488 |
+
document.getElementById('wrp-chart-asr').style.display = 'none';
|
| 489 |
+
}
|
| 490 |
|
| 491 |
+
// TTS
|
| 492 |
if(ttsRows.length > 0) {
|
|
|
|
| 493 |
const bestTTS = [...ttsRows].sort((a,b) => a.wer - b.wer)[0];
|
| 494 |
summaryHTML += `<div class="stat-card"><div class="stat-title">Best TTS (Intelligibility)</div><div class="stat-model">${bestTTS.model}</div><div class="stat-val">WER: ${fmt(bestTTS.wer)}%</div></div>`;
|
| 495 |
+
document.getElementById('wrp-chart-tts').style.display = 'block';
|
| 496 |
+
updateChart('chart-tts', 'TTS WER (Lower is Better)', ttsRows.map(r=>r.model), ttsRows.map(r=>r.wer), '#f97316');
|
| 497 |
+
} else {
|
| 498 |
+
summaryHTML += `<div class="stat-card"><div class="stat-title">TTS</div><div class="stat-val">No Data</div></div>`;
|
| 499 |
+
document.getElementById('wrp-chart-tts').style.display = 'none';
|
| 500 |
+
}
|
| 501 |
|
| 502 |
+
// SLID
|
| 503 |
if(slidRows.length > 0) {
|
| 504 |
summaryHTML += `<div class="stat-card"><div class="stat-title">Best SLID Model</div><div class="stat-model">${slidRows[0].model}</div><div class="stat-val">F1: ${fmt(slidRows[0].val)}%</div></div>`;
|
| 505 |
+
document.getElementById('wrp-chart-slid').style.display = 'block';
|
| 506 |
+
updateChart('chart-slid', 'SLID F1 Score (Higher is Better)', slidRows.map(r=>r.model), slidRows.map(r=>r.val), '#22c55e');
|
| 507 |
+
} else {
|
| 508 |
+
summaryHTML += `<div class="stat-card"><div class="stat-title">SLID</div><div class="stat-val">No Data</div></div>`;
|
| 509 |
+
document.getElementById('wrp-chart-slid').style.display = 'none';
|
| 510 |
+
}
|
| 511 |
document.getElementById('lang-summary').innerHTML = summaryHTML;
|
| 512 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 513 |
|
| 514 |
+
// --- TABLES ---
|
| 515 |
+
// ASR
|
| 516 |
let h1 = `<table><thead><tr><th>Model</th><th>WER <i class="fa-solid fa-arrow-down arrow-down"></i></th><th>CER <i class="fa-solid fa-arrow-down arrow-down"></i></th></tr></thead><tbody>`;
|
| 517 |
if(asrRows.length === 0) h1 += `<tr><td colspan="3" class="empty-msg">No ASR results available for ${lang}.</td></tr>`;
|
| 518 |
else {
|
| 519 |
let currentRank = 1;
|
| 520 |
asrRows.forEach((r, i) => {
|
| 521 |
+
if (i > 0 && Math.abs(r.wer - asrRows[i-1].wer) > 0.001) currentRank = i + 1; else currentRank = 1;
|
| 522 |
let icon = (currentRank===1)?'π₯ ':(currentRank===2)?'π₯ ':(currentRank===3)?'π₯ ':'';
|
| 523 |
h1 += `<tr><td style="font-weight:600; color:var(--simba-navy)">${icon}${r.model}</td><td>${fmt(r.wer)}</td><td>${fmt(r.cer)}</td></tr>`;
|
| 524 |
});
|
|
|
|
| 526 |
h1 += `</tbody></table>`;
|
| 527 |
document.getElementById('lang-asr-table').innerHTML = h1;
|
| 528 |
|
| 529 |
+
// TTS
|
| 530 |
let h2 = `<table><thead><tr><th>Model</th>
|
| 531 |
<th>WER(β)</th><th>MCD(β)</th><th>LogF0RMSE(β)</th><th>SpeechTokenDistance(β)</th>
|
| 532 |
<th>PESQ(β)</th><th>UTMOS(β)</th><th>SpeechBLEU(β)</th><th>SpeechBERTScore(β)</th>
|
|
|
|
| 546 |
h2 += `</tbody></table>`;
|
| 547 |
document.getElementById('lang-tts-table').innerHTML = h2;
|
| 548 |
|
| 549 |
+
// SLID
|
| 550 |
let h3 = `<table><thead><tr><th>Model</th><th>F1-Score <i class="fa-solid fa-arrow-up arrow-up"></i></th></tr></thead><tbody>`;
|
| 551 |
if(slidRows.length === 0) h3 += `<tr><td colspan="2" class="empty-msg">No SLID results available for ${lang}.</td></tr>`;
|
| 552 |
else {
|
| 553 |
let currentRank = 1;
|
| 554 |
slidRows.forEach((sc, i) => {
|
| 555 |
+
if (i > 0 && Math.abs(sc.val - slidRows[i-1].val) > 0.001) currentRank = i + 1; else currentRank = 1;
|
| 556 |
let icon = (currentRank===1)?'π₯ ':(currentRank===2)?'π₯ ':(currentRank===3)?'π₯ ':'';
|
| 557 |
h3 += `<tr><td style="font-weight:600;">${icon}${sc.model}</td><td>${fmt(sc.val)}</td></tr>`;
|
| 558 |
});
|
|
|
|
| 561 |
document.getElementById('lang-slid-table').innerHTML = h3;
|
| 562 |
}
|
| 563 |
|
|
|
|
| 564 |
function updateChart(canvasId, label, labels, data, color) {
|
| 565 |
const ctx = document.getElementById(canvasId).getContext('2d');
|
|
|
|
|
|
|
| 566 |
if(charts[canvasId]) charts[canvasId].destroy();
|
| 567 |
+
if(labels.length === 0) return;
|
|
|
|
| 568 |
|
| 569 |
charts[canvasId] = new Chart(ctx, {
|
| 570 |
type: 'bar',
|
|
|
|
| 578 |
}]
|
| 579 |
},
|
| 580 |
options: {
|
| 581 |
+
indexAxis: 'y', // Horizontal Bar Chart
|
| 582 |
responsive: true,
|
| 583 |
maintainAspectRatio: false,
|
| 584 |
plugins: { legend: { display: true } },
|
| 585 |
+
scales: { x: { beginAtZero: true } }
|
| 586 |
}
|
| 587 |
});
|
| 588 |
}
|
|
|
|
| 598 |
|
| 599 |
window.setASRMode = (m) => {
|
| 600 |
asrMode = m;
|
| 601 |
+
['btn-over','btn-fam','btn-mod'].forEach(b => document.getElementById(b).classList.remove('active'));
|
| 602 |
+
document.getElementById(m==='overview'?'btn-over':(m==='family'?'btn-fam':'btn-mod')).classList.add('active');
|
|
|
|
|
|
|
|
|
|
|
|
|
| 603 |
renderASR();
|
| 604 |
}
|
| 605 |
|