ai-research-survey

Systematic scan of agentic development research. What's signal, what's noise.
git clone https://git.shiptheloop.com/ai-research-survey.git
Log | Files | Refs

commit e794391f9bd6ec7b3ed6676a0738aecb985100dc
parent 48c57f7f304597d6144168e70d4abea8de51fb65
Author: Brian Graham <brian@buildingbetterteams.de>
Date:   Tue, 24 Mar 2026 06:40:47 +0100

Replace dot chart with border-thickness encoding for tension quality

Bars are now lightly filled outlines where border thickness encodes
methodology score: 1px at 20% → 5px at 70%. A tall thin-bordered
bar = many claims from weak papers. A short thick-bordered bar =
few claims from rigorous papers. Score shown as text label.

Cleaner than dots (which were cramped) and shades (which were
imperceptible).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Diffstat:
Mexplorer/src/views/tensions.ts | 62+++++++++++++++++++++-----------------------------------------
1 file changed, 21 insertions(+), 41 deletions(-)

diff --git a/explorer/src/views/tensions.ts b/explorer/src/views/tensions.ts @@ -89,44 +89,40 @@ function renderButterfly(positive: TensionClaim[], nuanced: TensionClaim[], meta maxCount = Math.max(maxCount, (posByYear.get(y) || []).length, (nuaByYear.get(y) || []).length); } - // Layout: bars for count + dots for score on a separate scale - const w = 650, h = 300; - const pad = { l: 30, r: 50, t: 30, b: 30 }; + // Layout: bars with border thickness encoding methodology score + const w = 600, h = 280; + const pad = { l: 30, r: 15, t: 30, b: 30 }; const chartW = w - pad.l - pad.r; const chartH = h - pad.t - pad.b; const midY = pad.t + chartH / 2; - const barMaxH = chartH / 2 - 20; + const barMaxH = chartH / 2 - 15; const colW = chartW / sortedYears.length; - // Score dot scale: right side mini-axis, 0-100% mapped within bar zone - const dotMinX = (i: number) => pad.l + i * colW + colW * 0.45; - const dotMaxX = (i: number) => pad.l + i * colW + colW * 0.85; - // Score maps to x position within that range - const dotX = (i: number, score: number) => dotMinX(i) + (score / 100) * (dotMaxX(i) - dotMinX(i)); + // Map score to border thickness: 20% → 1px, 70% → 5px + function borderW(score: number): number { + return 1 + Math.max(0, Math.min(1, (score - 20) / 50)) * 4; + } let svg = ''; // Zero line svg += `<line x1="${pad.l}" y1="${midY}" x2="${w - pad.r}" y2="${midY}" stroke="var(--border)" stroke-width="1"/>`; - // Count scale ticks on left edge + // Count scale ticks for (const count of [Math.round(maxCount / 2), maxCount]) { const tickH = (count / maxCount) * barMaxH; svg += `<text x="${pad.l - 4}" y="${midY - tickH + 3}" text-anchor="end" font-size="9" fill="var(--text-dim)">${count}</text>`; svg += `<text x="${pad.l - 4}" y="${midY + tickH + 3}" text-anchor="end" font-size="9" fill="var(--text-dim)">${count}</text>`; } - // Score scale label on right - svg += `<text x="${w - 4}" y="${midY - barMaxH + 3}" text-anchor="end" font-size="9" fill="var(--text-dim)">score%</text>`; - // Side labels svg += `<text x="${w - pad.r}" y="${pad.t + 8}" text-anchor="end" font-size="10" fill="var(--text-dim)">\u2191 ${meta.positive}</text>`; svg += `<text x="${w - pad.r}" y="${h - pad.b - 2}" text-anchor="end" font-size="10" fill="var(--text-dim)">\u2193 ${meta.nuanced}</text>`; for (let i = 0; i < sortedYears.length; i++) { const y = sortedYears[i]; - const cx = pad.l + i * colW + colW * 0.2; - const barW = colW * 0.3; + const cx = pad.l + i * colW + colW / 2; + const barW = colW * 0.55; const posC = posByYear.get(y) || []; const nuaC = nuaByYear.get(y) || []; const posCount = posC.length; @@ -135,42 +131,26 @@ function renderButterfly(positive: TensionClaim[], nuanced: TensionClaim[], meta const nuaMean = nuaC.length ? nuaC.reduce((s, c) => s + c.score, 0) / nuaC.length : 0; // Year label - svg += `<text x="${pad.l + i * colW + colW / 2}" y="${h - 8}" text-anchor="middle" font-size="11" fill="var(--text)">${y}</text>`; + svg += `<text x="${cx}" y="${h - 8}" text-anchor="middle" font-size="11" fill="var(--text)">${y}</text>`; - // Positive: bar (count) + dot (score) + // Positive bar — thin fill, border thickness = score if (posCount > 0) { const barH = (posCount / maxCount) * barMaxH; - // Count bar — flat blue - svg += `<rect x="${cx - barW / 2}" y="${midY - barH - 1}" width="${barW}" height="${barH}" rx="2" fill="var(--accent)" opacity="0.6"> + const bw = borderW(posMean); + svg += `<rect x="${cx - barW / 2}" y="${midY - barH - 1}" width="${barW}" height="${barH}" rx="2" fill="var(--accent)" fill-opacity="0.15" stroke="var(--accent)" stroke-width="${bw}"> <title>${y} ${meta.positive}: ${posCount} claims, mean score ${Math.round(posMean)}%</title> </rect>`; - // Count label - svg += `<text x="${cx}" y="${midY - barH - 4}" text-anchor="middle" font-size="9" fill="var(--text-dim)">${posCount}</text>`; - // Score dot — positioned at score on a 0-100 mini axis, vertically centered in the bar zone - const dy = midY - barH / 2 - 1; - const dx = dotX(i, posMean); - svg += `<circle cx="${dx}" cy="${dy}" r="5" fill="var(--accent)" stroke="var(--surface)" stroke-width="1.5"> - <title>${Math.round(posMean)}% mean methodology</title> - </circle>`; - svg += `<text x="${dx}" y="${dy + 3.5}" text-anchor="middle" font-size="7" fill="#fff" font-weight="700" style="pointer-events:none">${Math.round(posMean)}</text>`; + svg += `<text x="${cx}" y="${midY - barH - 5}" text-anchor="middle" font-size="9" fill="var(--text)">${posCount} \u00b7 ${Math.round(posMean)}%</text>`; } - // Nuanced: bar (count) + dot (score) + // Nuanced bar — thin fill, border thickness = score if (nuaCount > 0) { const barH = (nuaCount / maxCount) * barMaxH; - // Count bar — flat gray - svg += `<rect x="${cx - barW / 2}" y="${midY + 1}" width="${barW}" height="${barH}" rx="2" fill="var(--text-dim)" opacity="0.4"> + const bw = borderW(nuaMean); + svg += `<rect x="${cx - barW / 2}" y="${midY + 1}" width="${barW}" height="${barH}" rx="2" fill="var(--text-dim)" fill-opacity="0.1" stroke="var(--text-dim)" stroke-width="${bw}"> <title>${y} ${meta.nuanced}: ${nuaCount} claims, mean score ${Math.round(nuaMean)}%</title> </rect>`; - // Count label - svg += `<text x="${cx}" y="${midY + barH + 12}" text-anchor="middle" font-size="9" fill="var(--text-dim)">${nuaCount}</text>`; - // Score dot - const dy = midY + barH / 2 + 1; - const dx = dotX(i, nuaMean); - svg += `<circle cx="${dx}" cy="${dy}" r="5" fill="var(--text-dim)" stroke="var(--surface)" stroke-width="1.5"> - <title>${Math.round(nuaMean)}% mean methodology</title> - </circle>`; - svg += `<text x="${dx}" y="${dy + 3.5}" text-anchor="middle" font-size="7" fill="var(--surface)" font-weight="700" style="pointer-events:none">${Math.round(nuaMean)}</text>`; + svg += `<text x="${cx}" y="${midY + barH + 13}" text-anchor="middle" font-size="9" fill="var(--text)">${nuaCount} \u00b7 ${Math.round(nuaMean)}%</text>`; } } @@ -208,7 +188,7 @@ function renderButterfly(positive: TensionClaim[], nuanced: TensionClaim[], meta } return `<div style="margin:1rem 0"> - <p style="font-size:0.78rem;color:var(--text-dim);margin-bottom:0.5rem"><strong>Bars</strong> = claim count. <strong>Dots</strong> = mean methodology score (further right = higher). Dashed line = quality-weighted balance.</p> + <p style="font-size:0.78rem;color:var(--text-dim);margin-bottom:0.5rem"><strong>Height</strong> = claim count. <strong>Border thickness</strong> = methodology score (thicker = more rigorous). A tall thin-bordered bar = many claims from weak papers. Dashed line = quality-weighted balance.</p> <svg viewBox="0 0 ${w} ${h}" style="width:100%;max-width:${w}px">${svg}</svg> </div>`; }

Impressum · Datenschutz