shaanilmo commited on
Commit
5aaa0c5
·
1 Parent(s): f38b141

Dashboard: move per-annotator stacked bar into each table row's Progress column

Browse files
Files changed (1) hide show
  1. app.py +17 -30
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: 160px; }
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: grid; grid-template-columns: minmax(240px, 320px) 1fr; gap: 12px;
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: 16px; flex-wrap: wrap; }
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
- .bar-row { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; font-size: 13px; }
283
- .bar-name { flex: 0 0 130px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
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
- function renderBars(reports) {
430
- const sorted = reports.slice().sort((a, b) => (b.total || 0) - (a.total || 0));
431
- const maxTotal = Math.max(1, ...sorted.map(r => r.total || 0));
432
- return sorted.map(r => {
433
- const c = r.counts || {};
434
- const segs = CHART_ORDER.map(([k, varName]) => {
435
- const v = c[k] || 0;
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"><div class="bar"><i style="width:${p}%"></i></div><span>${p}%</span></div></td>
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"><div class="bar"><i style="width:${agg.percent}%"></i></div><span>${agg.percent}%</span></div></td>
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