Dashboard: move per-annotator stacked bar into each table row's Progress column
Browse files
app.py
CHANGED
|
@@ -246,9 +246,7 @@ DASHBOARD_HTML = """<!DOCTYPE html>
|
|
| 246 |
tr + tr td { border-top: 1px solid var(--line); }
|
| 247 |
td.num { text-align: right; font-variant-numeric: tabular-nums; }
|
| 248 |
.name { font-weight: 600; }
|
| 249 |
-
.pct-wrap { display: flex; align-items: center; gap: 10px; min-width:
|
| 250 |
-
.bar { flex: 1; height: 8px; background: var(--panel-2); border-radius: 99px; overflow: hidden; }
|
| 251 |
-
.bar > i { display: block; height: 100%; background: var(--correct); }
|
| 252 |
.pill { display: inline-block; padding: 2px 8px; border-radius: 99px; font-size: 12px;
|
| 253 |
font-variant-numeric: tabular-nums; }
|
| 254 |
.pill.delete { background: rgba(239,83,80,.15); color: var(--delete); }
|
|
@@ -265,27 +263,22 @@ DASHBOARD_HTML = """<!DOCTYPE html>
|
|
| 265 |
display: flex; align-items: center; gap: 6px; white-space: nowrap; }
|
| 266 |
.theme-toggle:hover { border-color: var(--accent); }
|
| 267 |
/* ---- charts ---- */
|
| 268 |
-
.charts { display:
|
| 269 |
-
margin-bottom: 28px; }
|
| 270 |
-
@media (max-width: 760px) { .charts { grid-template-columns: 1fr; } }
|
| 271 |
.chart-card { background: var(--panel); border: 1px solid var(--line); border-radius: 12px;
|
| 272 |
padding: 18px; }
|
| 273 |
.chart-title { font-size: 12px; text-transform: uppercase; letter-spacing: .04em;
|
| 274 |
color: var(--muted); font-weight: 600; margin-bottom: 14px; }
|
| 275 |
-
.donut-wrap { display: flex; align-items: center; gap:
|
| 276 |
.donut { flex: 0 0 auto; }
|
| 277 |
.donut-center { font-weight: 700; }
|
| 278 |
-
.chart-legend { display: flex; flex-direction: column; gap: 6px; font-size: 12px; }
|
| 279 |
.legend-item { display: flex; align-items: center; gap: 8px; white-space: nowrap; }
|
| 280 |
.legend-item .count { margin-left: auto; color: var(--muted); font-variant-numeric: tabular-nums; }
|
| 281 |
.swatch { width: 11px; height: 11px; border-radius: 3px; flex: 0 0 auto; }
|
| 282 |
-
|
| 283 |
-
.bar-
|
| 284 |
-
.bar-track { flex: 1; display: flex; height: 16px; border-radius: 4px; overflow: hidden;
|
| 285 |
background: var(--panel-2); }
|
| 286 |
.bar-track .seg { height: 100%; }
|
| 287 |
-
.bar-total { flex: 0 0 auto; color: var(--muted); font-variant-numeric: tabular-nums;
|
| 288 |
-
min-width: 44px; text-align: right; }
|
| 289 |
</style>
|
| 290 |
<script>
|
| 291 |
// Apply saved theme before first paint to avoid a flash. Default: dark.
|
|
@@ -426,20 +419,15 @@ function renderLegend(counts) {
|
|
| 426 |
+ `${LABEL[k]}<span class="count">${counts[k] || 0}</span></div>`).join("") + `</div>`;
|
| 427 |
}
|
| 428 |
|
| 429 |
-
|
| 430 |
-
|
| 431 |
-
|
| 432 |
-
|
| 433 |
-
const
|
| 434 |
-
|
| 435 |
-
|
| 436 |
-
if (!v) return "";
|
| 437 |
-
return `<span class="seg" style="width:${(100 * v / maxTotal).toFixed(3)}%;background:var(${varName})" title="${LABEL[k]}: ${v}"></span>`;
|
| 438 |
-
}).join("");
|
| 439 |
-
const name = escapeHtml(r.annotator || "—");
|
| 440 |
-
return `<div class="bar-row"><div class="bar-name" title="${name}">${name}</div>`
|
| 441 |
-
+ `<div class="bar-track">${segs}</div><div class="bar-total">${r.total || 0}</div></div>`;
|
| 442 |
}).join("");
|
|
|
|
| 443 |
}
|
| 444 |
|
| 445 |
function renderCharts(reports, agg) {
|
|
@@ -448,8 +436,7 @@ function renderCharts(reports, agg) {
|
|
| 448 |
const counts = agg.counts || {};
|
| 449 |
el.innerHTML =
|
| 450 |
`<div class="chart-card"><div class="chart-title">Team status</div>`
|
| 451 |
-
+ `<div class="donut-wrap">${renderDonut(counts, agg.total || 0, agg.percent || 0)}${renderLegend(counts)}</div></div>`
|
| 452 |
-
+ `<div class="chart-card"><div class="chart-title">Per-annotator breakdown</div>${renderBars(reports)}</div>`;
|
| 453 |
}
|
| 454 |
|
| 455 |
async function refresh() {
|
|
@@ -500,7 +487,7 @@ async function refresh() {
|
|
| 500 |
<td><span class="pill edited">${c.edited || 0}</span></td>
|
| 501 |
<td class="num muted">${c.pending || 0}</td>
|
| 502 |
<td class="num"><strong>${done(c)}</strong></td>
|
| 503 |
-
<td><div class="pct-wrap">
|
| 504 |
<td class="muted">${fmtTime(r.updatedAt)}</td>
|
| 505 |
</tr>`;
|
| 506 |
}).join("");
|
|
@@ -515,7 +502,7 @@ async function refresh() {
|
|
| 515 |
<td><span class="pill edited">${ac.edited || 0}</span></td>
|
| 516 |
<td class="num">${ac.pending || 0}</td>
|
| 517 |
<td class="num">${agg.done != null ? agg.done : done(ac)}</td>
|
| 518 |
-
<td><div class="pct-wrap">
|
| 519 |
<td></td>
|
| 520 |
</tr>`;
|
| 521 |
|
|
|
|
| 246 |
tr + tr td { border-top: 1px solid var(--line); }
|
| 247 |
td.num { text-align: right; font-variant-numeric: tabular-nums; }
|
| 248 |
.name { font-weight: 600; }
|
| 249 |
+
.pct-wrap { display: flex; align-items: center; gap: 10px; min-width: 180px; }
|
|
|
|
|
|
|
| 250 |
.pill { display: inline-block; padding: 2px 8px; border-radius: 99px; font-size: 12px;
|
| 251 |
font-variant-numeric: tabular-nums; }
|
| 252 |
.pill.delete { background: rgba(239,83,80,.15); color: var(--delete); }
|
|
|
|
| 263 |
display: flex; align-items: center; gap: 6px; white-space: nowrap; }
|
| 264 |
.theme-toggle:hover { border-color: var(--accent); }
|
| 265 |
/* ---- charts ---- */
|
| 266 |
+
.charts { display: flex; flex-wrap: wrap; gap: 12px; margin-bottom: 28px; }
|
|
|
|
|
|
|
| 267 |
.chart-card { background: var(--panel); border: 1px solid var(--line); border-radius: 12px;
|
| 268 |
padding: 18px; }
|
| 269 |
.chart-title { font-size: 12px; text-transform: uppercase; letter-spacing: .04em;
|
| 270 |
color: var(--muted); font-weight: 600; margin-bottom: 14px; }
|
| 271 |
+
.donut-wrap { display: flex; align-items: center; gap: 18px; flex-wrap: wrap; }
|
| 272 |
.donut { flex: 0 0 auto; }
|
| 273 |
.donut-center { font-weight: 700; }
|
| 274 |
+
.chart-legend { display: flex; flex-direction: column; gap: 6px; font-size: 12px; min-width: 150px; }
|
| 275 |
.legend-item { display: flex; align-items: center; gap: 8px; white-space: nowrap; }
|
| 276 |
.legend-item .count { margin-left: auto; color: var(--muted); font-variant-numeric: tabular-nums; }
|
| 277 |
.swatch { width: 11px; height: 11px; border-radius: 3px; flex: 0 0 auto; }
|
| 278 |
+
/* in-row stacked status bar (table Progress column) */
|
| 279 |
+
.bar-track { flex: 1; display: flex; height: 14px; border-radius: 4px; overflow: hidden;
|
|
|
|
| 280 |
background: var(--panel-2); }
|
| 281 |
.bar-track .seg { height: 100%; }
|
|
|
|
|
|
|
| 282 |
</style>
|
| 283 |
<script>
|
| 284 |
// Apply saved theme before first paint to avoid a flash. Default: dark.
|
|
|
|
| 419 |
+ `${LABEL[k]}<span class="count">${counts[k] || 0}</span></div>`).join("") + `</div>`;
|
| 420 |
}
|
| 421 |
|
| 422 |
+
// In-row stacked status bar, scaled to that row's own total (full width = total),
|
| 423 |
+
// so each row shows its composition. title attrs give hover counts.
|
| 424 |
+
function rowBar(c, total) {
|
| 425 |
+
const segs = CHART_ORDER.map(([k, varName]) => {
|
| 426 |
+
const v = c[k] || 0;
|
| 427 |
+
if (!v || !total) return "";
|
| 428 |
+
return `<span class="seg" style="width:${(100 * v / total).toFixed(3)}%;background:var(${varName})" title="${LABEL[k]}: ${v}"></span>`;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 429 |
}).join("");
|
| 430 |
+
return `<div class="bar-track">${segs}</div>`;
|
| 431 |
}
|
| 432 |
|
| 433 |
function renderCharts(reports, agg) {
|
|
|
|
| 436 |
const counts = agg.counts || {};
|
| 437 |
el.innerHTML =
|
| 438 |
`<div class="chart-card"><div class="chart-title">Team status</div>`
|
| 439 |
+
+ `<div class="donut-wrap">${renderDonut(counts, agg.total || 0, agg.percent || 0)}${renderLegend(counts)}</div></div>`;
|
|
|
|
| 440 |
}
|
| 441 |
|
| 442 |
async function refresh() {
|
|
|
|
| 487 |
<td><span class="pill edited">${c.edited || 0}</span></td>
|
| 488 |
<td class="num muted">${c.pending || 0}</td>
|
| 489 |
<td class="num"><strong>${done(c)}</strong></td>
|
| 490 |
+
<td><div class="pct-wrap">${rowBar(c, r.total || 0)}<span>${p}%</span></div></td>
|
| 491 |
<td class="muted">${fmtTime(r.updatedAt)}</td>
|
| 492 |
</tr>`;
|
| 493 |
}).join("");
|
|
|
|
| 502 |
<td><span class="pill edited">${ac.edited || 0}</span></td>
|
| 503 |
<td class="num">${ac.pending || 0}</td>
|
| 504 |
<td class="num">${agg.done != null ? agg.done : done(ac)}</td>
|
| 505 |
+
<td><div class="pct-wrap">${rowBar(ac, agg.total || 0)}<span>${agg.percent}%</span></div></td>
|
| 506 |
<td></td>
|
| 507 |
</tr>`;
|
| 508 |
|