diff --git a/state-hub/dashboard/src/index.md b/state-hub/dashboard/src/index.md index 83668e7..b33c622 100644 --- a/state-hub/dashboard/src/index.md +++ b/state-hub/dashboard/src/index.md @@ -139,28 +139,43 @@ const taskRows = openWs.flatMap(w => [ {label: w.title, domain: w.domain, status: "todo", count: w.todo}, ]).filter(d => d.count > 0); +// y-axis shows domain (only for the first workstream in each domain group) +const yLabels = {}; +const _seenDomains = new Set(); +for (const w of openWs) { + yLabels[w.title] = _seenDomains.has(w.domain) ? "" : w.domain; + _seenDomains.add(w.domain); +} + if (openWs.length === 0) { display(html`
No open workstreams.
`); } else { display(Plot.plot({ - y: {label: null, tickSize: 0, domain: openWs.map(w => w.title)}, + y: {label: null, tickSize: 0, domain: openWs.map(w => w.title), tickFormat: t => yLabels[t] ?? ""}, x: {label: "Tasks", grid: true}, color: {domain: statusOrder, range: statusColors, legend: true}, marks: [ Plot.barX(taskRows, {y: "label", x: "count", fill: "status", tip: true}), + // Workstream title inside the bar + Plot.text(openWs.filter(w => w.total > 0), { + y: "title", x: 0, dx: 6, + text: d => d.title.length > 36 ? d.title.slice(0, 34) + "…" : d.title, + textAnchor: "start", fontSize: 10, fill: "#333", + }), + Plot.text(openWs.filter(w => w.total === 0), { + y: "title", x: 0, dx: 6, + text: d => `${d.title.length > 24 ? d.title.slice(0, 22) + "…" : d.title} — no tasks yet`, + textAnchor: "start", fontSize: 10, fill: "#aaa", + }), + // "done / total" label after the bar Plot.text(openWs.filter(w => w.total > 0), { y: "title", x: "total", text: d => ` ${d.done}/${d.total}`, dx: 4, textAnchor: "start", fontSize: 11, fill: "gray", }), - Plot.text(openWs.filter(w => w.total === 0), { - y: "title", x: 0, - text: () => " no tasks yet", - textAnchor: "start", fontSize: 11, fill: "#aaa", - }), Plot.ruleX([0]), ], - marginLeft: 200, + marginLeft: 160, marginRight: 70, height: Math.max(80, openWs.length * 44 + 50), width: 700,