From b0c0f5c2dac2577ab4744f1cb1e712390b13cc9b Mon Sep 17 00:00:00 2001 From: Thomas Hallock Date: Tue, 16 Dec 2025 13:30:10 -0600 Subject: [PATCH] feat(blog): show adaptive vs classic comparison on same chart MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changed "All Skills" tab to display both modes simultaneously: - Solid lines = Adaptive mode (with circle markers) - Dashed lines = Classic mode (no markers) - Same color = same skill - Tooltip shows both values with diff highlighted This makes comparison much easier than toggling between modes. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../src/components/blog/ValidationCharts.tsx | 137 +++++++++++------- 1 file changed, 82 insertions(+), 55 deletions(-) diff --git a/apps/web/src/components/blog/ValidationCharts.tsx b/apps/web/src/components/blog/ValidationCharts.tsx index e6000a7c..54e4c0a4 100644 --- a/apps/web/src/components/blog/ValidationCharts.tsx +++ b/apps/web/src/components/blog/ValidationCharts.tsx @@ -600,10 +600,8 @@ export function ValidationResultsCharts() { ) } -/** Multi-skill trajectory chart showing all skills at once */ +/** Multi-skill trajectory chart showing adaptive vs classic comparison */ function MultiSkillTrajectoryChart({ data }: { data: TrajectoryData | null }) { - const [showAdaptive, setShowAdaptive] = useState(true) - if (!data) { return (
@@ -616,21 +614,48 @@ function MultiSkillTrajectoryChart({ data }: { data: TrajectoryData | null }) { const sessions = data.sessions - // Build series for all skills - const series = data.skills.map((skill) => ({ - name: skill.label, - type: 'line' as const, - data: showAdaptive ? skill.adaptive.data : skill.classic.data, - smooth: true, - symbol: 'circle', - symbolSize: 6, - lineStyle: { color: skill.color, width: 2 }, - itemStyle: { color: skill.color }, - })) + // Build series for all skills - both adaptive (solid) and classic (dashed) + const series: Array<{ + name: string + type: 'line' + data: number[] + smooth: boolean + symbol: string + symbolSize: number + lineStyle: { color: string; width: number; type?: string } + itemStyle: { color: string } + markLine?: unknown + }> = [] + + for (const skill of data.skills) { + // Adaptive line (solid, with symbols) + series.push({ + name: `${skill.label} (Adaptive)`, + type: 'line', + data: skill.adaptive.data, + smooth: true, + symbol: 'circle', + symbolSize: 6, + lineStyle: { color: skill.color, width: 2 }, + itemStyle: { color: skill.color }, + }) + + // Classic line (dashed, no symbols) + series.push({ + name: `${skill.label} (Classic)`, + type: 'line', + data: skill.classic.data, + smooth: true, + symbol: 'none', + symbolSize: 0, + lineStyle: { color: skill.color, width: 2, type: 'dashed' }, + itemStyle: { color: skill.color }, + }) + } // Add threshold marklines to first series if (series.length > 0) { - ;(series[0] as Record).markLine = { + series[0].markLine = { silent: true, lineStyle: { color: '#374151', type: 'dashed' }, data: [ @@ -644,28 +669,55 @@ function MultiSkillTrajectoryChart({ data }: { data: TrajectoryData | null }) { backgroundColor: 'transparent', tooltip: { trigger: 'axis', - formatter: (params: Array<{ seriesName: string; value: number; color: string }>) => { + formatter: ( + params: Array<{ seriesName: string; value: number; color: string; data: number }> + ) => { const session = (params[0] as unknown as { axisValue: number })?.axisValue let html = `Session ${session}
` + + // Group by skill for cleaner tooltip + const bySkill: Record = {} for (const p of params) { - html += `${p.seriesName}: ${p.value}%
` + const isAdaptive = p.seriesName.includes('(Adaptive)') + const skillLabel = p.seriesName.replace(' (Adaptive)', '').replace(' (Classic)', '') + if (!bySkill[skillLabel]) { + bySkill[skillLabel] = { color: p.color } + } + if (isAdaptive) { + bySkill[skillLabel].adaptive = p.value + } else { + bySkill[skillLabel].classic = p.value + } + } + + for (const [label, values] of Object.entries(bySkill)) { + const diff = + values.adaptive !== undefined && values.classic !== undefined + ? values.adaptive - values.classic + : 0 + const diffStr = + diff > 0 + ? `+${diff}` + : diff < 0 + ? `${diff}` + : '0' + html += `${label}: ${values.adaptive ?? '—'}% vs ${values.classic ?? '—'}% (${diffStr})
` } return html }, }, legend: { - data: data.skills.map((s) => ({ - name: s.label, - itemStyle: { color: s.color }, - })), + data: [ + { name: 'Adaptive (solid)', icon: 'line' }, + { name: 'Classic (dashed)', icon: 'line' }, + ], bottom: 0, - textStyle: { color: '#9ca3af', fontSize: 10 }, - type: 'scroll', + textStyle: { color: '#9ca3af', fontSize: 11 }, }, grid: { left: '3%', right: '4%', - bottom: '20%', + bottom: '12%', top: '10%', containLabel: true, }, @@ -694,39 +746,14 @@ function MultiSkillTrajectoryChart({ data }: { data: TrajectoryData | null }) { return (
-
-

- All Skills: {showAdaptive ? 'Adaptive' : 'Classic'} Mode -

-
- - -
-
+ Adaptive vs Classic: All Skills +

- Mastery trajectories for {data.skills.length} deficient skills over {sessions.length}{' '} - sessions. Toggle to compare adaptive vs classic modes. + Solid lines = Adaptive mode, Dashed lines = Classic mode. Same color = same skill. Adaptive + consistently reaches mastery faster.