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 48c57f7f304597d6144168e70d4abea8de51fb65
parent 3776fd8528b73d85622c4a27e63d7a6e653b67c9
Author: Brian Graham <brian@buildingbetterteams.de>
Date:   Tue, 24 Mar 2026 06:35:47 +0100

Replace shade gradient with bar+dot chart on tensions

Bars show claim count (flat blue up, flat gray down).
Dots show mean methodology score as position on a mini x-axis
per year column, with score number inside each dot.
Two independent visual channels — no color interpretation needed.

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

Diffstat:
Mexplorer/src/views/tensions.ts | 77++++++++++++++++++++++++++++++++++++++++++++---------------------------------
1 file changed, 44 insertions(+), 33 deletions(-)

diff --git a/explorer/src/views/tensions.ts b/explorer/src/views/tensions.ts @@ -66,23 +66,6 @@ export async function renderTensions(app: HTMLElement) { }).join(''); } -// Single-hue gradient: lighter = weaker methodology, darker = stronger -function posBarColor(score: number): string { - // Blue family: light (#a0c0f0) at 20% → dark (#1a4080) at 80% - const t = Math.max(0, Math.min(1, (score - 20) / 60)); - const r = Math.round(160 - t * 134); - const g = Math.round(192 - t * 128); - const b = Math.round(240 - t * 112); - return `rgb(${r},${g},${b})`; -} - -function nuaBarColor(score: number): string { - // Gray family: light (#b0b0b0) at 20% → dark (#404040) at 80% - const t = Math.max(0, Math.min(1, (score - 20) / 60)); - const v = Math.round(176 - t * 112); - return `rgb(${v},${v},${v})`; -} - function renderButterfly(positive: TensionClaim[], nuanced: TensionClaim[], meta: { positive: string; nuanced: string }): string { const posByYear = new Map<number, TensionClaim[]>(); const nuaByYear = new Map<number, TensionClaim[]>(); @@ -106,14 +89,21 @@ function renderButterfly(positive: TensionClaim[], nuanced: TensionClaim[], meta maxCount = Math.max(maxCount, (posByYear.get(y) || []).length, (nuaByYear.get(y) || []).length); } - const w = 600, h = 280; - const pad = { l: 15, r: 15, t: 30, b: 30 }; + // 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 }; const chartW = w - pad.l - pad.r; const chartH = h - pad.t - pad.b; const midY = pad.t + chartH / 2; - const barMaxH = chartH / 2 - 15; + const barMaxH = chartH / 2 - 20; 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)); + let svg = ''; // Zero line @@ -122,18 +112,21 @@ function renderButterfly(positive: TensionClaim[], nuanced: TensionClaim[], meta // Count scale ticks on left edge for (const count of [Math.round(maxCount / 2), maxCount]) { const tickH = (count / maxCount) * barMaxH; - svg += `<text x="${pad.l}" y="${midY - tickH - 1}" text-anchor="start" font-size="9" fill="var(--text-dim)">${count}</text>`; - svg += `<text x="${pad.l}" y="${midY + tickH + 8}" text-anchor="start" 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>`; + svg += `<text x="${pad.l - 4}" y="${midY + tickH + 3}" text-anchor="end" font-size="9" fill="var(--text-dim)">${count}</text>`; } - // Side labels at edges + // 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 / 2; - const barW = colW * 0.6; + const cx = pad.l + i * colW + colW * 0.2; + const barW = colW * 0.3; const posC = posByYear.get(y) || []; const nuaC = nuaByYear.get(y) || []; const posCount = posC.length; @@ -142,24 +135,42 @@ 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="${cx}" y="${h - 8}" text-anchor="middle" font-size="11" fill="var(--text)">${y}</text>`; + svg += `<text x="${pad.l + i * colW + colW / 2}" y="${h - 8}" text-anchor="middle" font-size="11" fill="var(--text)">${y}</text>`; - // Positive bar (UP) — blue gradient by score + // Positive: bar (count) + dot (score) if (posCount > 0) { const barH = (posCount / maxCount) * barMaxH; - svg += `<rect x="${cx - barW / 2}" y="${midY - barH - 1}" width="${barW}" height="${barH}" rx="2" fill="${posBarColor(posMean)}"> + // 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"> <title>${y} ${meta.positive}: ${posCount} claims, mean score ${Math.round(posMean)}%</title> </rect>`; - svg += `<text x="${cx}" y="${midY - barH - 4}" text-anchor="middle" font-size="9" fill="var(--text)">${posCount} \u00b7 ${Math.round(posMean)}%</text>`; + // 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>`; } - // Nuanced bar (DOWN) — gray gradient by score + // Nuanced: bar (count) + dot (score) if (nuaCount > 0) { const barH = (nuaCount / maxCount) * barMaxH; - svg += `<rect x="${cx - barW / 2}" y="${midY + 1}" width="${barW}" height="${barH}" rx="2" fill="${nuaBarColor(nuaMean)}"> + // 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"> <title>${y} ${meta.nuanced}: ${nuaCount} claims, mean score ${Math.round(nuaMean)}%</title> </rect>`; - svg += `<text x="${cx}" y="${midY + barH + 12}" text-anchor="middle" font-size="9" fill="var(--text)">${nuaCount} \u00b7 ${Math.round(nuaMean)}%</text>`; + // 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>`; } } @@ -197,7 +208,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>Height</strong> = number of claims. <strong>Darkness</strong> = mean methodology score (darker = more rigorous). Dashed line = quality-weighted balance. A tall pale bar = many claims from weak papers.</p> + <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> <svg viewBox="0 0 ${w} ${h}" style="width:100%;max-width:${w}px">${svg}</svg> </div>`; }

Impressum · Datenschutz