ECharts 进阶应用
本文档介绍 ECharts 的高级应用场景,包括服务端渲染、微信小程序、富文本标签等。
服务端渲染
通常情况下,ECharts 会在浏览器中动态渲染图表。但在某些特殊场景下,需要在服务端渲染图表并输出到浏览器:
- 需要缩短前端渲染时间,保证第一时间显示图表
- 需要在 Markdown、PDF 等不支持动态运行脚本的环境中嵌入图表
渲染方案对比
| 渲染方案 | 渲染结果 | 优点 |
|---|---|---|
| 服务端 SVG 渲染 | SVG 字符串 | 体积更小;矢量图不模糊;支持初始动画 |
| 服务端 Canvas 渲染 | 图片 | 适用场景更广泛,对不支持 SVG 的场景可选择 |
服务端 SVG 渲染(推荐)
使用 5.3.0 及更新版本,推荐使用零依赖的服务端 SVG 字符串渲染方案:
javascript
const echarts = require('echarts')
const chart = echarts.init(null, null, {
renderer: 'svg',
ssr: true,
width: 400,
height: 300
})
chart.setOption({
// 配置项
})
const svgStr = chart.renderToSVGString()参数说明:
container传入null或undefined(服务端不需要容器)ssr: true开启服务端渲染模式- 必须通过
width和height显式指定图表尺寸
返回给前端
javascript
res.writeHead(200, {
'Content-Type': 'application/xml'
})
res.write(chart.renderToSVGString())
res.end()保存到本地
javascript
fs.writeFile('bar.svg', chart.renderToSVGString(), 'utf-8')关闭动画
javascript
chart.setOption({
animation: false
})服务端 Canvas 渲染
使用 node-canvas 实现:
javascript
var echarts = require('echarts')
const { createCanvas } = require('canvas')
echarts.setCanvasCreator(() => {
return createCanvas()
})
const canvas = createCanvas(800, 600)
const chart = echarts.init(canvas)
chart.setOption({
// 配置项
})
res.writeHead(200, {
'Content-Type': 'image/png'
})
res.write(canvas.toBuffer('image/png'))
res.end()图片加载适配
javascript
echarts.setPlatformAPI({
createCanvas() {
return createCanvas()
},
loadImage(src, onload, onerror) {
const img = new Image()
img.onload = onload.bind(img)
img.onerror = onerror.bind(img)
img.src = src
return img
}
})服务端渲染 Hydration
服务端渲染无法支持的功能:
- 动态改变数据
- 高亮鼠标所在的数据项
- 点击图例切换系列是否显示
- 移动鼠标显示提示框
- 其他交互相关的功能
解决方案:先用 SVG 做服务端渲染,再用 Canvas 做客户端渲染(Hydration)。
javascript
// 客户端渲染时
myChart.setOption({
tooltip: { show: true }, // 开启交互组件
animation: 0 // 关闭初始动画(由服务端 SVG 动画完成)
})微信小程序
echarts-for-weixin 项目提供了小程序组件。
使用方式
- 下载该项目
- 如有必要,将
ec-canvas目录下的echarts.js替换为最新版 - 使用
pages目录下的示例文件作为参考
注意事项
- 最新版支持微信 Canvas 2d
- 当基础库版本 >= 2.9.0 且没有设置
force-use-old-canvas="true"时,使用新的 Canvas 2d(默认) - 使用新的 Canvas 2d 可以提升渲染性能,解决非同层渲染问题,强烈建议开启
富文本标签
从 v3.7 开始,ECharts 支持富文本模式,能够:
- 定制文本块整体的样式(背景、边框、阴影等)、位置、旋转等
- 对文本块中个别片段定义样式(颜色、字体、高宽、背景、阴影等)、对齐方式等
- 在文本中使用图片做小图标或者背景
- 特定组合以上的规则,可以做出简单表格、分割线等效果
基本概念
- 文本块(Text Block):文本标签块整体
- 文本片段(Text fragment):文本标签块中的部分文本
文本样式配置项
javascript
labelOption = {
formatter: [
'{a|这段文本采用样式a}',
'{b|这段文本采用样式b}这段用默认样式{x|这段用样式x}'
].join('\n'),
color: '#333',
fontSize: 5,
fontFamily: 'Arial',
borderWidth: 3,
backgroundColor: '#984455',
padding: [3, 10, 10, 5],
lineHeight: 20,
rich: {
a: {
color: 'red',
lineHeight: 10
},
b: {
backgroundColor: {
image: 'xxx/xxx.jpg'
},
height: 40
},
x: {
fontSize: 18,
fontFamily: 'Microsoft YaHei',
borderColor: '#449933',
borderRadius: 4
}
}
}文本、文本框、文本片段的样式
javascript
option = {
series: [
{
type: 'scatter',
symbolSize: 1,
data: [
{
value: [0, 0],
label: {
show: true,
formatter: [
'Plain text',
'{textBorder|textBorderColor + textBorderWidth}',
'{textShadow|textShadowColor + textShadowBlur}',
'{bg|backgroundColor + borderRadius + padding}',
'{border|borderColor + borderWidth + borderRadius}',
'{shadow|shadowColor + shadowBlur}'
].join('\n'),
backgroundColor: '#eee',
borderColor: '#333',
borderWidth: 2,
borderRadius: 5,
padding: 10,
color: '#000',
fontSize: 14,
lineHeight: 30,
rich: {
textBorder: {
fontSize: 20,
textBorderColor: '#000',
textBorderWidth: 3,
color: '#fff'
},
textShadow: {
fontSize: 16,
textShadowBlur: 5,
textShadowColor: '#000',
color: '#fff'
},
bg: {
backgroundColor: '#339911',
color: '#fff',
borderRadius: 15,
padding: 5
},
border: {
color: '#000',
borderColor: '#449911',
borderWidth: 1,
borderRadius: 3,
padding: 5
},
shadow: {
backgroundColor: '#992233',
padding: 5,
color: '#fff',
shadowBlur: 5,
shadowColor: '#336699'
}
}
}
}
]
}
]
}标签的位置
通过 label.position 和 label.distance 配置:
javascript
option = {
series: [
{
type: 'scatter',
label: {
position: 'top', // 支持:'left', 'right', 'top', 'bottom', 'inside' 等
distance: 10
}
}
]
}标签的旋转
javascript
const labelOption = {
show: true,
rotate: 90,
formatter: '{c} {name|{a}}',
fontSize: 16,
rich: {
name: {}
}
}
option = {
xAxis: [
{
type: 'category',
data: ['2012', '2013', '2014', '2015', '2016']
}
],
yAxis: [
{
type: 'value'
}
],
series: [
{
name: 'Forest',
type: 'bar',
label: labelOption,
data: [320, 332, 301, 334, 390]
}
]
}文本片段的排版和对齐
每个文本片段可以想象成 CSS 中的 inline-block,在文档流中按行放置。
自定义系列
自定义系列(custom series)是一种系列类型,它能让开发者定制渲染逻辑,从而在坐标系中绘制出自定义的图形。
renderItem 函数
javascript
option = {
series: {
type: 'custom',
renderItem: function(params, api) {
// 返回图形元素的定义
return {
type: 'group',
children: [
{
type: 'circle',
shape: {
cx: api.coord([api.value(0), api.value(1)])[0],
cy: api.coord([api.value(0), api.value(1)])[1],
r: 20
},
style: {
fill: 'blue'
}
}
]
}
},
data: [
[10, 20],
[30, 40]
]
}
}数据区域缩放
dataZoom 组件用于实现数据区域缩放。
内置型数据区域缩放
javascript
option = {
dataZoom: [
{
type: 'inside',
start: 0,
end: 100
}
]
}滑动条型数据区域缩放
javascript
option = {
dataZoom: [
{
type: 'slider',
show: true,
start: 0,
end: 100
}
]
}同时使用两种类型
javascript
option = {
dataZoom: [
{
type: 'inside',
start: 0,
end: 100
},
{
type: 'slider',
start: 0,
end: 100
}
]
}工具栏组件
toolbox 组件提供了一些内置的工具按钮。
javascript
option = {
toolbox: {
show: true,
feature: {
dataZoom: {
yAxisIndex: 'none'
},
dataView: { readOnly: false },
magicType: { type: ['line', 'bar'] },
restore: {},
saveAsImage: {}
}
}
}时间线组件
timeline 组件用于在多个 option 之间进行切换。
javascript
option = {
baseOption: {
timeline: {
data: ['2013', '2014', '2015'],
axisType: 'category'
},
xAxis: {
type: 'category'
},
yAxis: {}
},
options: [
{
series: { type: 'bar', data: [10, 20, 30] }
},
{
series: { type: 'bar', data: [15, 25, 35] }
},
{
series: { type: 'bar', data: [20, 30, 40] }
}
]
}性能优化
按需引入
减少打包体积的最有效方式:
javascript
// 只引入需要的组件
import * as echarts from 'echarts/core'
import { BarChart, LineChart } from 'echarts/charts'
import {
TitleComponent,
TooltipComponent,
GridComponent
} from 'echarts/components'
import { CanvasRenderer } from 'echarts/renderers'
echarts.use([
TitleComponent,
TooltipComponent,
GridComponent,
BarChart,
LineChart,
CanvasRenderer
])大数据量优化
javascript
const chart = echarts.init(dom, null, {
useDirtyRect: true // 开启脏矩形渲染
})
option = {
animation: false, // 大数据量时关闭动画
series: [{
type: 'scatter',
large: true, // 开启大数据优化
largeThreshold: 2000, // 触发阈值
progressive: 200, // 渐进式渲染每帧帧数
progressiveThreshold: 3000, // 渐进式渲染阈值
data: largeDataArray
}]
}增量渲染
对于动态追加的数据:
javascript
// 初始化时设置基础配置
chart.setOption(baseOption)
// 增量追加数据
function appendData(newData) {
chart.appendData({
seriesIndex: 0,
data: newData
})
}
// 分批加载数据
async function loadLargeData() {
chart.showLoading()
for (let i = 0; i < batches; i++) {
const batchData = await fetchBatch(i)
appendData(batchData)
}
chart.hideLoading()
}内存优化
javascript
// 及时销毁不再使用的实例
function cleanup() {
chart.dispose()
chart = null
}
// 移除事件监听
chart.off('click')
// 清空数据
chart.clear()
// 使用弱引用(高级用法)
const chartRef = new WeakRef(chart)渲染模式选择
| 场景 | 推荐渲染器 | 原因 |
|---|---|---|
| 大数据量 | Canvas | 性能更好 |
| 移动端 | SVG | 内存占用低 |
| 需要截图导出 | Canvas | 导出更清晰 |
| 动态交互多 | Canvas | 响应更快 |
| 图表数量多 | SVG | 内存更友好 |
javascript
// 根据场景选择渲染器
const renderer = dataLength > 10000 ? 'canvas' : 'svg'
const chart = echarts.init(dom, null, { renderer })性能监控
javascript
// 监控渲染性能
const startTime = performance.now()
chart.setOption(option)
console.log(`渲染耗时: ${performance.now() - startTime}ms`)
// 使用 PerformanceObserver
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.name.includes('echarts')) {
console.log('ECharts 性能:', entry)
}
}
})
observer.observe({ entryTypes: ['measure'] })调试技巧
开启调试模式
javascript
// 开启控制台日志
echarts.setLogLevel(3) // 0: off, 1: error, 2: warn, 3: info, 4: debug
// 获取当前配置
const option = chart.getOption()
console.log('当前配置:', JSON.stringify(option, null, 2))常见问题排查
问题 1:图表不显示
javascript
// 排查步骤
function debugChart(dom) {
// 1. 检查容器
console.log('容器尺寸:', dom.offsetWidth, dom.offsetHeight)
if (dom.offsetWidth === 0 || dom.offsetHeight === 0) {
console.error('容器宽高为 0')
}
// 2. 检查实例
const chart = echarts.getInstanceByDom(dom)
console.log('图表实例:', chart)
// 3. 检查配置
if (chart) {
console.log('当前配置:', chart.getOption())
}
}问题 2:数据更新不生效
javascript
// 调试数据更新
function debugSetOption(chart, option) {
console.log('旧配置:', chart.getOption())
console.log('新配置:', option)
chart.setOption(option, true) // 使用 notMerge 模式
console.log('更新后配置:', chart.getOption())
}问题 3:事件不触发
javascript
// 检查事件绑定
function debugEvents(chart) {
// 列出所有事件监听器
console.log('事件监听器:', chart._events)
// 测试事件触发
chart.dispatchAction({
type: 'showTip',
seriesIndex: 0,
dataIndex: 0
})
}浏览器开发工具
javascript
// 在控制台暴露图表实例
window.myChart = chart
// 然后可以在控制台直接操作
// myChart.setOption({ ... })
// myChart.getOption()
// myChart.getDataURL()视觉调试
javascript
option = {
// 显示组件边界(调试用)
grid: {
show: true,
backgroundColor: 'rgba(0, 0, 0, 0.1)',
borderColor: 'red'
},
// 显示数据索引
series: [{
label: {
show: true,
formatter: function(params) {
return `idx:${params.dataIndex}\nval:${params.value}`
}
}
}]
}最佳实践总结
1. 初始化
- 确保容器有明确宽高
- 使用按需引入减少体积
- 选择合适的渲染器
2. 数据处理
- 大数据量使用
large模式 - 使用增量渲染避免卡顿
- 避免频繁全量更新
3. 交互优化
- 对高频事件使用节流/防抖
- 合理使用
emphasis状态 - 避免过度使用动画
4. 内存管理
- 及时销毁不再使用的实例
- 移除事件监听器
- 清理定时器和观察者
5. 错误处理
javascript
// 全局错误处理
echarts.on('error', function(err) {
console.error('ECharts 错误:', err)
})
// setOption 错误捕获
try {
chart.setOption(option)
} catch (e) {
console.error('配置错误:', e)
chart.setOption(fallbackOption)
}