Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Guide to Apache ECharts for interactive research data dashboards
.claude/skills/brycewang-stanford-echarts-visualization-guide/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-03 | ✗→✓ | ▲ Improved | 95% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 170% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 100% | 0% |
| case-12 | ✗→✓ | ▲ Improved | 2% | 0% |
| case-13 | ✗→✓ | ▲ Improved | 83% | 0% |
Apache ECharts is a powerful, free, and open-source interactive charting and data visualization library with over 66K stars on GitHub. Originally developed by Baidu and now an Apache Software Foundation top-level project, ECharts provides a declarative configuration-based approach to building rich, interactive visualizations that run smoothly in any modern browser.
For academic researchers, ECharts offers an excellent balance between ease of use and customization depth. Its declarative option-based API means researchers can produce complex multi-series charts, geographic visualizations, and animated transitions without writing low-level rendering code. This is particularly useful when building research dashboards or interactive supplementary materials for publications.
ECharts supports over 20 chart types out of the box, including line, bar, scatter, pie, radar, candlestick, heatmap, treemap, sunburst, parallel coordinates, sankey diagrams, and geographic maps. Its built-in support for large datasets (via progressive rendering and data sampling) makes it suitable for visualizing experimental results with hundreds of thousands of data points.
ECharts uses a declarative JSON configuration object to define charts. This approach makes it straightforward to build visualizations programmatically from research data.
html<div id="chart" style="width: 800px; height: 500px;"></div> <script src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script> <script> const chart = echarts.init(document.getElementById('chart')); </script>
javascriptconst option = { title: { text: 'Gene Expression Over Time', left: 'center', textStyle: { fontSize: 16, fontWeight: 'bold' } }, tooltip: { trigger: 'axis', formatter: params => { let html = `<strong>Hour ${params[0].axisValue}</strong><br/>`; params.forEach(p => { html += `${p.marker} ${p.seriesName}: ${p.value.toFixed(3)}<br/>`; }); return html; } }, legend: { data: ['Gene A', 'Gene B', 'Gene C'], bottom: 10 }, xAxis: { type: 'category', name: 'Time (hours)', data: [0, 2, 4, 8, 12, 24, 48, 72] }, yAxis: { type: 'value', name: 'Relative Expression', nameLocation: 'middle', nameGap: 50 }, series: [ { name: 'Gene A', type: 'line', data: [1.0, 1.2, 2.4, 5.1, 8.3, 12.1, 10.5, 9.2], smooth: true, lineStyle: { width: 2 } }, { name: 'Gene B', type: 'line', data: [1.0, 0.9, 0.7, 0.5, 0.3, 0.2, 0.15, 0.1], smooth: true, lineStyle: { width: 2 } }, { name: 'Gene C', type: 'line', data: [1.0, 1.1, 1.3, 1.8, 3.2, 6.7, 8.9, 11.4], smooth: true, lineStyle: { width: 2 } } ] }; chart.setOption(option);
javascriptconst scatterOption = { title: { text: 'Treatment Response vs Dosage', left: 'center' }, xAxis: { type: 'value', name: 'Dosage (mg/kg)' }, yAxis: { type: 'value', name: 'Response Score' }, tooltip: { formatter: p => `Dosage: ${p.value[0]}<br/>Response: ${p.value[1]}` }, visualMap: { min: 0, max: 100, dimension: 2, inRange: { color: ['#3B82F6', '#EF4444'] }, text: ['High', 'Low'], calculable: true }, series: [{ type: 'scatter', symbolSize: d => Math.sqrt(d[2]) * 2, data: experimentalData.map(d => [d.dosage, d.response, d.confidence]) }] };
javascriptconst heatmapOption = { title: { text: 'Sample Correlation Matrix', left: 'center' }, tooltip: { position: 'top', formatter: p => { return `${sampleNames[p.value[0]]} vs ${sampleNames[p.value[1]]}<br/>` + `Correlation: ${p.value[2].toFixed(4)}`; } }, grid: { left: 120, top: 60, right: 80, bottom: 100 }, xAxis: { type: 'category', data: sampleNames, axisLabel: { rotate: 45 } }, yAxis: { type: 'category', data: sampleNames }, visualMap: { min: -1, max: 1, calculable: true, orient: 'vertical', right: 10, top: 'center', inRange: { color: ['#2166AC', '#F7F7F7', '#B2182B'] } }, series: [{ type: 'heatmap', data: correlationData, label: { show: true, formatter: p => p.value[2].toFixed(2), fontSize: 9 }, emphasis: { itemStyle: { shadowBlur: 10, shadowColor: 'rgba(0,0,0,0.5)' } } }] };
javascriptconst radarOption = { title: { text: 'Model Performance Comparison', left: 'center' }, legend: { data: ['Model A', 'Model B', 'Baseline'], bottom: 10 }, radar: { indicator: [ { name: 'Accuracy', max: 1.0 }, { name: 'Precision', max: 1.0 }, { name: 'Recall', max: 1.0 }, { name: 'F1 Score', max: 1.0 }, { name: 'AUC-ROC', max: 1.0 }, { name: 'Speed (norm)', max: 1.0 } ] }, series: [{ type: 'radar', data: [ { value: [0.94, 0.91, 0.89, 0.90, 0.96, 0.72], name: 'Model A' }, { value: [0.92, 0.95, 0.85, 0.90, 0.94, 0.88], name: 'Model B' }, { value: [0.85, 0.82, 0.80, 0.81, 0.87, 0.95], name: 'Baseline' } ] }] };
ECharts supports custom themes and responsive resizing, which is important when embedding visualizations in research web applications.
javascript// Register a custom academic theme echarts.registerTheme('academic', { color: ['#3B82F6', '#EF4444', '#10B981', '#F59E0B', '#8B5CF6', '#EC4899'], backgroundColor: '#FFFFFF', textStyle: { fontFamily: 'Inter, sans-serif' }, title: { textStyle: { color: '#1F2937', fontSize: 16 } }, line: { smooth: false, symbolSize: 6 } }); // Initialize chart with the academic theme const chart = echarts.init(document.getElementById('chart'), 'academic'); // Handle responsive resizing window.addEventListener('resize', () => chart.resize());
javascript// Load CSV data and convert to ECharts format async function loadExperimentData(csvUrl) { const response = await fetch(csvUrl); const text = await response.text(); const rows = text.split('\n').slice(1); const data = rows.map(row => { const [sample, condition, value, error] = row.split(','); return { sample, condition, value: parseFloat(value), error: parseFloat(error) }; }); return data; } // Export chart as PNG for publications function downloadChart(chartInstance, filename) { const url = chartInstance.getDataURL({ type: 'png', pixelRatio: 3, backgroundColor: '#fff' }); const link = document.createElement('a'); link.href = url; link.download = filename || 'chart.png'; link.click(); }
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→fail | 17,019 | 21,196 | +25% | 1 | 1 | 0% | 3,450 | 5,270 | +53% | 0 | 0 | — |
case-02 | pass→pass | 11,311 | 10,555 | -7% | 1 | 1 | 0% | 2,088 | 4,343 | +108% | 0 | 0 | — |
case-03 | fail→pass | 11,321 | 8,155 | -28% | 1 | 1 | 0% | 1,993 | 3,877 | +95% | 0 | 0 | — |
case-04 | pass→pass | 13,971 | 9,427 | -33% | 1 | 1 | 0% | 2,578 | 4,112 | +60% | 0 | 0 | — |
case-05 | pass→pass | 7,545 | 3,410 | -55% | 1 | 1 | 0% | 1,323 | 3,032 | +129% | 0 | 0 | — |
case-06 | fail→pass | 8,254 | 10,779 | +31% | 1 | 1 | 0% | 1,669 | 4,504 | +170% | 0 | 0 | — |
case-07 | pass→pass | 9,102 | 5,599 | -38% | 1 | 1 | 0% | 1,752 | 3,371 | +92% | 0 | 0 | — |
case-08 | fail→pass | 12,758 | 11,709 | -8% | 1 | 1 | 0% | 2,301 | 4,599 | +100% | 0 | 0 | — |
case-09 | pass→pass | 10,327 | 7,796 | -25% | 1 | 1 | 0% | 2,017 | 3,739 | +85% | 0 | 0 | — |
case-10 | fail→fail | 10,834 | 9,548 | -12% | 1 | 1 | 0% | 1,777 | 4,055 | +128% | 0 | 0 | — |
case-11 | fail→fail | 7,436 | 6,488 | -13% | 1 | 1 | 0% | 1,414 | 3,620 | +156% | 0 | 0 | — |
case-12 | fail→pass | 16,122 | 2,131 | -87% | 1 | 1 | 0% | 2,740 | 2,804 | +2% | 0 | 0 | — |
case-13 | fail→pass | 9,172 | 2,335 | -75% | 1 | 1 | 0% | 1,511 | 2,761 | +83% | 0 | 0 | — |
case-14 | pass→pass | 10,770 | 2,691 | -75% | 1 | 1 | 0% | 1,704 | 2,829 | +66% | 0 | 0 | — |
case-15 | fail→pass | 14,925 | 10,385 | -30% | 1 | 1 | 0% | 2,786 | 4,275 | +53% | 0 | 0 | — |
case-16 | pass→fail | 13,930 | 11,423 | -18% | 1 | 1 | 0% | 2,323 | 4,260 | +83% | 0 | 0 | — |
case-17 | pass→pass | 9,671 | 8,138 | -16% | 1 | 1 | 0% | 1,915 | 4,061 | +112% | 0 | 0 | — |
case-18 | fail→pass | 10,723 | 10,464 | -2% | 1 | 1 | 0% | 1,908 | 4,253 | +123% | 0 | 0 | — |
case-19 | pass→pass | 5,406 | 4,740 | -12% | 1 | 1 | 0% | 1,009 | 3,267 | +224% | 0 | 0 | — |
case-20 | fail→fail | 14,105 | 13,211 | -6% | 1 | 1 | 0% | 2,350 | 4,736 | +102% | 0 | 0 | — |
case-21 | pass→pass | 13,020 | 13,216 | +2% | 1 | 1 | 0% | 2,379 | 4,809 | +102% | 0 | 0 | — |
case-22 | pass→pass | 18,325 | 21,384 | +17% | 1 | 1 | 0% | 3,475 | 6,407 | +84% | 0 | 0 | — |
case-23 | pass→pass | 19,287 | 23,391 | +21% | 1 | 1 | 0% | 3,878 | 7,131 | +84% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted. The headline lift of +26 percentage points is the difference between those two pass rates over the 23 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.