# ECharts Template Library > **⚠️ Before writing any code, read [`_rules.md`](references/_rules.md) — three non-negotiable rules on overlap, hierarchy, and color.** ECharts strengths: interactivity (tooltip/zoom/linking), big data (Canvas renders millions of points smoothly), strong Chinese community. Output as HTML files — open directly in browser or export PNG via Playwright screenshot. ## HTML Universal Shell Wrap all ECharts charts with this shell: ```html {{TITLE}}
``` Default dimensions: `width=900, height=520`, white background `#FFFFFF`. --- ## Theme Configuration ### Light Theme (Default) ```javascript const THEME = { bg: '#FFFFFF', text: '#111827', textSub: '#6B7280', textMuted: '#9CA3AF', axis: '#E5E7EB', grid: '#F3F4F6', tooltip: { bg: '#1E293B', border: '#334155', text: '#F1F5F9' }, colors: ['#3B82F6', '#06B6D4', '#8B5CF6', '#F59E0B', '#EF4444', '#10B981'], }; ``` ### Dark Theme (Finance / Tech Dashboard) ```javascript const DARK = { bg: '#0F172A', text: '#F1F5F9', textSub: '#94A3B8', textMuted: '#64748B', axis: '#334155', grid: '#1E293B', tooltip: { bg: '#1E293B', border: '#475569', text: '#F1F5F9' }, colors: ['#3B82F6', '#06B6D4', '#8B5CF6', '#F59E0B', '#22C55E', '#EC4899'], }; ``` ### Base Option Configuration ```javascript function baseOption(theme, title, subtitle) { return { backgroundColor: theme.bg, textStyle: { fontFamily: 'system-ui, SimHei, sans-serif', color: theme.text }, title: { text: title, subtext: subtitle || '', left: 24, top: 16, textStyle: { fontSize: 16, fontWeight: 'bold', color: theme.text }, subtextStyle: { fontSize: 12, color: theme.textSub }, }, grid: { left: 60, right: 40, top: 80, bottom: 50, containLabel: true }, color: theme.colors, tooltip: { trigger: 'axis', backgroundColor: theme.tooltip.bg, borderColor: theme.tooltip.border, borderWidth: 1, textStyle: { color: theme.tooltip.text, fontSize: 12 }, }, animationDuration: 600, animationEasing: 'cubicOut', }; } function cleanAxis(theme) { return { axisLine: { lineStyle: { color: theme.axis, width: 0.8 } }, axisTick: { show: false }, splitLine: { lineStyle: { color: theme.grid, width: 0.5 } }, axisLabel: { color: theme.textSub, fontSize: 10 }, }; } ``` --- ## Template 1: Insight Bar Chart ```javascript const option = { ...baseOption(THEME, 'Q3 收入环比增长 47%', '各季度收入对比(万元)'), xAxis: { type: 'category', data: ['Q1','Q2','Q3','Q4'], ...cleanAxis(THEME) }, yAxis: { type: 'value', ...cleanAxis(THEME) }, series: [{ type: 'bar', barWidth: '50%', itemStyle: { borderRadius: [4, 4, 0, 0] }, data: [ { value: 120, itemStyle: { color: '#E5E7EB' } }, { value: 145, itemStyle: { color: '#E5E7EB' } }, { value: 213, itemStyle: { color: '#3B82F6' } }, { value: 180, itemStyle: { color: '#E5E7EB' } }, ], label: { show: true, position: 'top', fontSize: 11, color: '#6B7280', formatter: (p) => p.dataIndex === 2 ? '{hl|' + p.value + '}' : p.value, rich: { hl: { fontWeight: 'bold', fontSize: 13, color: '#111827' } }, }, }], }; ``` --- ## Template 2: Multi-Line Trend ```javascript const option = { ...baseOption(THEME, '2024 年增长持续加速'), legend: { right: 40, top: 20, textStyle: { color: '#6B7280', fontSize: 10 } }, xAxis: { type: 'category', data: months, boundaryGap: false, ...cleanAxis(THEME) }, yAxis: { type: 'value', ...cleanAxis(THEME) }, series: [ { name: '2024', type: 'line', data: thisYear, lineStyle: { width: 2.5 }, symbol: 'circle', symbolSize: 6, itemStyle: { color: '#3B82F6' }, areaStyle: { color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ { offset: 0, color: 'rgba(59,130,246,0.12)' }, { offset: 1, color: 'rgba(59,130,246,0)' }, ]), }, }, { name: '2023', type: 'line', data: lastYear, lineStyle: { width: 1.5, type: 'dashed', color: '#D1D5DB' }, symbol: 'none', itemStyle: { color: '#D1D5DB' }, }, ], }; ``` --- ## Template 3: Candlestick (Finance) ```javascript // Dark theme const option = { ...baseOption(DARK, 'BTC/USDT 日K线'), xAxis: { type: 'category', data: dates, ...cleanAxis(DARK) }, yAxis: { type: 'value', scale: true, ...cleanAxis(DARK) }, dataZoom: [ { type: 'inside', start: 70, end: 100 }, { type: 'slider', start: 70, end: 100, height: 20, bottom: 10, borderColor: DARK.axis, fillerColor: 'rgba(59,130,246,0.15)', textStyle: { color: DARK.textSub } }, ], series: [{ type: 'candlestick', data: ohlcData, // [[open,close,low,high], ...] itemStyle: { color: '#22C55E', // Bullish (close > open) color0: '#EF4444', // Bearish borderColor: '#16A34A', borderColor0: '#DC2626', }, }], }; ``` --- ## Template 4: Dashboard (Multi-Chart Linking) ⚠️ **ECharts multi-chart dashboard anti-overlap rules** (highest priority): 1. Maximum 4 subplots per canvas; more must be split into multiple HTML files 2. Each subplot's `grid` area must not overlap; maintain ≥5% safety margin between adjacent grids 3. Pie chart `center` and `radius` must not intrude into other subplots' grid areas 4. Same applies to radar chart's `radar.center` and `radar.radius` 5. Place legend in top/bottom common area, not inside subplots ### Simple Dual Chart (Bar + Pie) ```javascript const option = { ...baseOption(THEME, '产品线收入分布'), grid: [{ left: 60, right: '55%', top: 80, bottom: 50 }], xAxis: [{ type: 'value', gridIndex: 0, ...cleanAxis(THEME) }], yAxis: [{ type: 'category', data: products, gridIndex: 0, ...cleanAxis(THEME) }], series: [ { type: 'bar', data: revenues, itemStyle: { borderRadius: [0, 4, 4, 0] }, barWidth: '60%', }, { type: 'pie', center: ['78%', '50%'], radius: ['35%', '55%'], data: products.map((name, i) => ({ name, value: revenues[i] })), label: { formatter: '{b}\n{d}%', fontSize: 10 }, itemStyle: { borderColor: '#fff', borderWidth: 2 }, }, ], }; ``` ### Four-Chart Dashboard (Safe Layout Template) ```javascript // ⚠️ Key: grid areas precisely defined, no overlap, maintain safety margins const option = { ...baseOption(THEME, '数据全景仪表盘'), grid: [ // Top-left: bar chart { left: '5%', right: '55%', top: '12%', bottom: '55%' }, // Top-right: line chart { left: '55%', right: '5%', top: '12%', bottom: '55%' }, // Bottom-left: scatter plot { left: '5%', right: '55%', top: '55%', bottom: '5%' }, // Bottom-right area reserved for pie chart (pie uses center + radius, not grid) ], xAxis: [ { type: 'category', gridIndex: 0, data: categories1, ...cleanAxis(THEME) }, { type: 'category', gridIndex: 1, data: categories2, ...cleanAxis(THEME), boundaryGap: false }, { type: 'value', gridIndex: 2, ...cleanAxis(THEME) }, ], yAxis: [ { type: 'value', gridIndex: 0, ...cleanAxis(THEME) }, { type: 'value', gridIndex: 1, ...cleanAxis(THEME) }, { type: 'value', gridIndex: 2, ...cleanAxis(THEME) }, ], series: [ // Top-left: bar chart { type: 'bar', xAxisIndex: 0, yAxisIndex: 0, data: barData, itemStyle: { borderRadius: [4, 4, 0, 0] }, }, // Top-right: line chart { type: 'line', xAxisIndex: 1, yAxisIndex: 1, data: lineData, smooth: true, areaStyle: { opacity: 0.08 }, }, // Bottom-left: scatter plot { type: 'scatter', xAxisIndex: 2, yAxisIndex: 2, data: scatterData, symbolSize: 8, }, // Bottom-right: pie chart (positioned via center in bottom-right quadrant) { type: 'pie', center: ['77%', '72%'], // Positioned at bottom-right area center radius: ['15%', '25%'], // Radius stays within bottom-right quadrant data: pieData, label: { formatter: '{b}\n{d}%', fontSize: 10 }, itemStyle: { borderColor: '#fff', borderWidth: 2 }, }, ], }; ``` ### Grid Safety Margin Quick Reference | Layout | Grid Config | Safety Margin | |------|----------|---------| | Left-right dual | Left `right:'55%'` Right `left:'55%'` | 10% center gap | | Top-bottom dual | Top `bottom:'55%'` Bottom `top:'55%'` | 10% center gap | | 2x2 quad | Each quadrant 45%, 10% center gap | 5% margin on all sides | | With pie/radar | Pie center+radius must not intrude grid | Pie radius ≤ 40% of available area | ### What If More Than 4 Subplots? ```javascript // ❌ Wrong: 8 charts crammed into one canvas — all labels will inevitably overlap // ✅ Correct: split into 2 HTML files // dashboard_overview.html — 4 overview charts // dashboard_detail.html — 4 detailed analysis charts // Or use tab switching (ECharts toolbox doesn't support this, need custom HTML tabs) ``` --- ## Template 5: Radar Chart ```javascript const option = { ...baseOption(THEME, '团队能力评估'), radar: { indicator: dims.map(d => ({ name: d, max: 100 })), axisName: { color: '#6B7280', fontSize: 10 }, splitArea: { areaStyle: { color: ['#FAFAFA', '#F5F5F5'] } }, splitLine: { lineStyle: { color: '#E5E7EB' } }, axisLine: { lineStyle: { color: '#E5E7EB' } }, }, series: [{ type: 'radar', data: teams.map((t, i) => ({ name: t.name, value: t.scores, lineStyle: { width: 2 }, areaStyle: { opacity: 0.08 }, itemStyle: { color: THEME.colors[i] }, })), }], legend: { bottom: 10, textStyle: { color: '#6B7280' } }, }; ``` --- ## Export to PNG (Playwright) ```python import asyncio from playwright.async_api import async_playwright async def echarts_to_png(html_path, png_path, width=900, height=520): async with async_playwright() as p: browser = await p.chromium.launch(headless=True) page = await browser.new_page(viewport={'width': width, 'height': height}) await page.goto(f'file://{html_path}', wait_until='networkidle') await page.wait_for_timeout(800) # Wait for animation to complete await page.locator('#chart').screenshot(path=png_path) await browser.close() print(f'✅ {png_path}') # asyncio.run(echarts_to_png('./output/chart.html', './output/chart.png')) ``` --- ## Template 5: Tree (Interactive Only) **⚠️ For static PNG export, use Playwright+CSS (see `mindmap-css.md`). ECharts tree connector length and node spacing cannot be finely controlled — static output is not aesthetically satisfying.** **ECharts tree is suitable for interactive scenarios** (click expand/collapse, hover tooltip, zoom/drag). For PNG/PDF static output, the CSS approach looks better. ### Basic Usage ```javascript const option = { tooltip: { trigger: 'item', triggerOn: 'mousemove' }, series: [{ type: 'tree', data: [treeData], // Tree-structured JSON data layout: 'orthogonal', // Orthogonal layout (right-angle connectors) orient: 'LR', // Direction: LR(left→right) / RL / TB(top→bottom) / BT // Node spacing control (key params to prevent crowding) initialTreeDepth: -1, // -1=expand all, positive=initial expand depth // Label style label: { position: 'left', // Leaf node label position verticalAlign: 'middle', fontSize: 13, fontFamily: 'PingFang SC, SimHei, sans-serif', }, leaves: { label: { position: 'right' } // Leaf labels on right }, // Connector style lineStyle: { color: '#94A3B8', width: 1.5, curveness: 0.5, // Curvature, 0=straight, 0.5=natural curve }, // Node style itemStyle: { borderWidth: 1.5, }, // Animation animationDuration: 550, animationDurationUpdate: 750, }] }; ``` ### Tree Data Format ```javascript const treeData = { name: '中心主题', children: [ { name: '分支A', children: [ { name: '叶子1' }, { name: '叶子2' }, { name: '叶子3', children: [{ name: '更深叶子' }] } ] }, { name: '分支B', children: [ { name: '叶子4' }, { name: '叶子5' } ] } ] }; ``` ### Node Style Customization (by Level) ```javascript // Root node highlight function styleTreeData(node, depth) { const styles = [ { // Root node itemStyle: { color: '#3B82F6', borderColor: '#2563EB', borderWidth: 2 }, label: { fontSize: 18, fontWeight: 'bold', color: '#fff', backgroundColor: '#3B82F6', borderRadius: 6, padding: [8, 16] } }, { // Level 1 branches itemStyle: { color: '#60A5FA', borderColor: '#3B82F6' }, label: { fontSize: 15, fontWeight: 600, color: '#1E40AF', backgroundColor: '#EFF6FF', borderColor: '#3B82F6', borderWidth: 1.5, borderRadius: 6, padding: [6, 14] } }, { // Level 2 itemStyle: { color: '#93C5FD', borderColor: '#60A5FA' }, label: { fontSize: 13, color: '#1E40AF', backgroundColor: '#F0F7FF', borderColor: '#93C5FD', borderWidth: 1, borderRadius: 4, padding: [4, 10] } }, { // Level 3+ leaves itemStyle: { color: '#BFDBFE', borderColor: '#93C5FD' }, label: { fontSize: 12, color: '#475569', padding: [3, 8] } } ]; const style = styles[Math.min(depth, styles.length - 1)]; Object.assign(node, style); if (node.children) { node.children.forEach(child => styleTreeData(child, depth + 1)); } } styleTreeData(treeData, 0); ``` ### Left-Right Distribution (Large Tree Mode) When branches ≥ 5, use two trees for left-right expansion: ```javascript function splitTree(data) { const children = data.children || []; // Alternate assignment by subtree size const sorted = children.map((c, i) => ({ c, w: countNodes(c), i })) .sort((a, b) => b.w - a.w); const left = [], right = []; let lw = 0, rw = 0; sorted.forEach(({ c }) => { if (lw <= rw) { left.push(c); lw += countNodes(c); } else { right.push(c); rw += countNodes(c); } }); return { left: { name: data.name, children: left }, right: { name: data.name, children: right } }; } function countNodes(node) { if (!node.children) return 1; return 1 + node.children.reduce((s, c) => s + countNodes(c), 0); } // Dual tree series const { left, right } = splitTree(treeData); const option = { series: [ { type: 'tree', data: [right], orient: 'LR', left: '50%', width: '45%', /* ... */ }, { type: 'tree', data: [left], orient: 'RL', right: '50%', width: '45%', /* ... */ }, ] }; ``` ### Recommended Canvas Size | Node Count | Width | Height | |--------|------|------| | ≤ 15 | 900px | 500px | | 16-30 | 1200px | 600px | | 31-60 | 1600px | 800px | | 60+ | 2000px | 1000px | --- ## Template 6: Relationship / Force-Directed Graph **ECharts graph suits process relationships, org charts, knowledge graphs** — nodes auto-repel to avoid overlap, connectors auto-bind, supports categorical coloring. ### Basic Usage ```javascript const option = { tooltip: {}, legend: [{ data: categories.map(c => c.name) }], series: [{ type: 'graph', layout: 'force', // Force-directed auto layout // Force model params (controls repulsion and attraction) force: { repulsion: 300, // Repulsion force (higher = more spread out, recommended 200-500) gravity: 0.1, // Gravity (prevents nodes from flying too far) edgeLength: [100, 200], // Edge length range layoutAnimation: true, }, roam: true, // Allow drag and zoom draggable: true, // Allow dragging nodes // Nodes data: nodes, // Edges links: links, // Categories (for coloring) categories: categories, // Labels label: { show: true, position: 'right', fontSize: 12, fontFamily: 'PingFang SC, SimHei, sans-serif', }, // Connector style lineStyle: { color: 'source', // Edge color follows source node curveness: 0.3, // Curvature width: 1.5, }, // Highlight effect emphasis: { focus: 'adjacency', // Highlight adjacent nodes on hover lineStyle: { width: 3 }, }, }] }; ``` ### Data Format ```javascript const categories = [ { name: '核心系统', itemStyle: { color: '#3B82F6' } }, { name: '数据层', itemStyle: { color: '#10B981' } }, { name: '应用层', itemStyle: { color: '#F59E0B' } }, ]; const nodes = [ { name: 'API Gateway', category: 0, symbolSize: 40 }, { name: 'User Service', category: 0, symbolSize: 30 }, { name: 'MySQL', category: 1, symbolSize: 35 }, { name: 'Redis', category: 1, symbolSize: 28 }, { name: 'Web App', category: 2, symbolSize: 32 }, ]; const links = [ { source: 'API Gateway', target: 'User Service' }, { source: 'User Service', target: 'MySQL' }, { source: 'User Service', target: 'Redis' }, { source: 'Web App', target: 'API Gateway' }, ]; ``` ### Flowchart Mode (Fixed Layout) When you don't want force-directed auto-layout, fix node positions: ```javascript const option = { series: [{ type: 'graph', layout: 'none', // Fixed layout, positions determined by x/y data: [ { name: '开始', x: 300, y: 50, symbolSize: 40, itemStyle: { color: '#EFF6FF', borderColor: '#3B82F6', borderWidth: 2 } }, { name: '处理', x: 300, y: 200, symbolSize: 35 }, { name: '判断', x: 300, y: 350, symbolSize: 35, symbol: 'diamond', itemStyle: { color: '#FFF7ED', borderColor: '#F59E0B', borderWidth: 2 } }, { name: '结束', x: 300, y: 500, symbolSize: 40 }, ], links: [ { source: '开始', target: '处理' }, { source: '处理', target: '判断' }, { source: '判断', target: '结束', label: { show: true, formatter: '通过' } }, ], lineStyle: { color: '#94A3B8', width: 2, curveness: 0 }, edgeSymbol: ['', 'arrow'], edgeSymbolSize: [0, 10], }] }; ``` --- ## ECharts vs Other Frameworks | Capability | ECharts | Plotly | Chart.js | |------|---------|--------|----------| | Canvas rendering (big data) | ✅ Millions | ❌ SVG-based | ✅ But limited | | Chinese docs | ✅ Official | ❌ English | ❌ English | | Candlestick | ✅ Built-in | ❌ Plugin needed | ❌ None | | Maps | ✅ Built-in China map | ✅ mapbox | ❌ None | | 3D Charts | ✅ echarts-gl | ✅ Built-in | ❌ None | | No Node.js needed | ✅ CDN import | ❌ Needs plotly.js | ✅ CDN | | Server-side rendering | ✅ node-echarts | ✅ orca | ✅ chartjs-node |