{T}

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 传入 nullundefined(服务端不需要容器)
  • ssr: true 开启服务端渲染模式
  • 必须通过 widthheight 显式指定图表尺寸

返回给前端

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 项目提供了小程序组件。

使用方式

  1. 下载该项目
  2. 如有必要,将 ec-canvas 目录下的 echarts.js 替换为最新版
  3. 使用 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.positionlabel.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)
}

相关链接