{T}

ECharts 数据与交互

本文档介绍 ECharts 中的数据处理和交互功能,包括视觉映射、图例组件、事件处理、异步数据加载和动态更新。

数据的视觉映射

数据可视化是数据到视觉元素的映射过程。ECharts 提供了 visualMap 组件来实现通用的视觉映射。

可用的视觉元素

  • 图形类别(symbol)
  • 图形大小(symbolSize)
  • 颜色(color)
  • 透明度(opacity)
  • 颜色透明度(colorAlpha)
  • 颜色明暗度(colorLightness)
  • 颜色饱和度(colorSaturation)
  • 色调(colorHue)

连续型视觉映射

javascript
option = {
  visualMap: {
    type: "continuous",
    min: 0,
    max: 5000,
    dimension: 3,
    seriesIndex: 4,
    inRange: {
      color: ["blue", "#121122", "red"],
      symbolSize: [30, 100]
    },
    outOfRange: {
      symbolSize: [30, 100]
    }
  }
}

分段型视觉映射

javascript
option = {
  visualMap: {
    type: "piecewise",
    pieces: [
      { min: 1500 },
      { min: 900, max: 1500 },
      { min: 310, max: 1000 },
      { min: 200, max: 300 },
      { min: 10, max: 200, label: "10 到 200(自定义标签)" },
      { value: 123, label: "123(自定义特殊颜色)", color: "grey" }
    ]
  }
}

类别型视觉映射

javascript
option = {
  visualMap: {
    type: "piecewise",
    categories: ["严重", "重要", "一般", "轻微"],
    inRange: {
      color: ["#d94e5d", "#eac736", "#50a3ba", "#91cc75"]
    }
  }
}

图例组件

图例是图表中对内容区元素的注释,用不同形状、颜色、文字等来标示不同数据列。

基本图例

javascript
option = {
  legend: {
    data: ["图例一", "图例二", "图例三"]
  },
  series: [
    {
      name: "图例一",
      type: "bar",
      data: [10, 20, 30]
    }
  ]
}

图例布局

javascript
option = {
  legend: {
    orient: "vertical", // 'horizontal' 或 'vertical'
    left: "right", // 左侧位置
    top: "center" // 垂直位置
  }
}

可滚动图例

当图例较多时,可以使用可滚动翻页的图例:

javascript
option = {
  legend: {
    type: "scroll",
    orient: "vertical",
    right: 10,
    top: 20,
    bottom: 20,
    data: ["图例一", "图例二", "图例三", "..."]
  }
}

图例样式

javascript
option = {
  legend: {
    data: ["图例一", "图例二", "图例三"],
    icon: "rect", // 图例图标形状
    backgroundColor: "#ccc",
    textStyle: {
      color: "#333"
    }
  }
}

图例交互

javascript
option = {
  legend: {
    data: ["图例一", "图例二", "图例三"],
    selected: {
      图例一: true,
      图例二: true,
      图例三: false
    }
  }
}

不同类型的图例样式

javascript
option = {
  legend: {
    data: [
      {
        name: "图例一",
        icon: "rect"
      },
      {
        name: "图例二",
        icon: "circle"
      },
      {
        name: "图例三",
        icon: "pin"
      }
    ]
  }
}

事件处理

在 ECharts 的图表中,用户的操作会触发相应的事件。开发者可以监听这些事件,然后通过回调函数做相应的处理。

鼠标事件

ECharts 支持常规的鼠标事件类型:

  • 'click' - 点击
  • 'dblclick' - 双击
  • 'mousedown' - 鼠标按下
  • 'mousemove' - 鼠标移动
  • 'mouseup' - 鼠标释放
  • 'mouseover' - 鼠标悬停
  • 'mouseout' - 鼠标移出
  • 'globalout' - 全局移出
  • 'contextmenu' - 右键菜单

基本事件绑定

javascript
myChart.on("click", function (params) {
  console.log(params.name)
})

事件参数

javascript
myChart.on("click", function (params) {
  console.log("组件类型:", params.componentType)
  console.log("系列类型:", params.seriesType)
  console.log("系列索引:", params.seriesIndex)
  console.log("系列名称:", params.seriesName)
  console.log("数据名称:", params.name)
  console.log("数据索引:", params.dataIndex)
  console.log("数据值:", params.value)
  console.log("颜色:", params.color)
})

使用 query 过滤事件

javascript
// 只对特定系列响应
chart.on("click", "series", function () {})
chart.on("click", "series.line", function () {})
 
// 只对特定数据项响应
chart.on("click", { seriesName: "uuu" }, function () {})
chart.on("click", { seriesIndex: 1, name: "xx" }, function () {})
chart.on("click", { dataType: "node" }, function () {})

组件交互事件

组件交互行为会触发相应的事件:

javascript
// 图例开关事件
myChart.on("legendselectchanged", function (params) {
  var isSelected = params.selected[params.name]
  console.log((isSelected ? "选中了" : "取消选中了") + "图例" + params.name)
})
 
// 数据区域缩放事件
myChart.on("datazoom", function (params) {
  console.log("数据范围:", params.start, "-", params.end)
})

监听空白处事件

javascript
myChart.getZr().on("click", function (event) {
  if (!event.target) {
    console.log("点击了空白处")
  }
})

触发图表行为

通过 dispatchAction 可以在程序中触发图表行为。

显示提示框

javascript
myChart.dispatchAction({
  type: "showTip",
  seriesIndex: 0,
  dataIndex: 1
})

高亮数据项

javascript
myChart.dispatchAction({
  type: "highlight",
  seriesIndex: 0,
  dataIndex: 1
})
 
myChart.dispatchAction({
  type: "downplay",
  seriesIndex: 0,
  dataIndex: 1
})

数据区域缩放

javascript
myChart.dispatchAction({
  type: "dataZoom",
  start: 20,
  end: 30
})
 
// 批量操作
myChart.dispatchAction({
  type: "dataZoom",
  batch: [
    { start: 20, end: 30 },
    { dataZoomIndex: 1, start: 10, end: 20 }
  ]
})

轮流高亮示例

javascript
var currentIndex = -1
setInterval(function () {
  var dataLen = option.series[0].data.length
 
  myChart.dispatchAction({
    type: "downplay",
    seriesIndex: 0,
    dataIndex: currentIndex
  })
 
  currentIndex = (currentIndex + 1) % dataLen
 
  myChart.dispatchAction({
    type: "highlight",
    seriesIndex: 0,
    dataIndex: currentIndex
  })
 
  myChart.dispatchAction({
    type: "showTip",
    seriesIndex: 0,
    dataIndex: currentIndex
  })
}, 1000)

异步数据加载

很多时候数据需要异步加载后再填入。

异步加载示例

javascript
var myChart = echarts.init(document.getElementById("main"))
 
$.get("data.json").done(function (data) {
  myChart.setOption({
    title: {
      text: "异步数据加载示例"
    },
    tooltip: {},
    legend: {},
    xAxis: {
      data: data.categories
    },
    yAxis: {},
    series: [
      {
        name: "销量",
        type: "bar",
        data: data.values
      }
    ]
  })
})

先设置空坐标轴

javascript
var myChart = echarts.init(document.getElementById("main"))
 
myChart.setOption({
  title: {
    text: "异步数据加载示例"
  },
  tooltip: {},
  legend: {
    data: ["销量"]
  },
  xAxis: {
    data: []
  },
  yAxis: {},
  series: [
    {
      name: "销量",
      type: "bar",
      data: []
    }
  ]
})
 
$.get("data.json").done(function (data) {
  myChart.setOption({
    xAxis: {
      data: data.categories
    },
    series: [
      {
        name: "销量",
        data: data.data
      }
    ]
  })
})

Loading 动画

javascript
myChart.showLoading()
$.get("data.json").done(function (data) {
  myChart.hideLoading()
  myChart.setOption({
    // ...
  })
})

数据的动态更新

ECharts 由数据驱动,数据的改变驱动图表展现的改变。

动态更新示例

javascript
var myChart = echarts.init(document.getElementById("main"))
 
setInterval(function () {
  var data = []
  for (var i = 0; i < 5; ++i) {
    data.push(Math.round(Math.random() * 200))
  }
 
  myChart.setOption({
    series: [
      {
        type: "bar",
        data: data
      }
    ]
  })
}, 3000)

setOption 合并模式

javascript
// 普通合并(默认)
chart.setOption(option)
 
// 不合并,完全替换
chart.setOption(option, true)
 
// 替换合并,指定组件类型
chart.setOption(option, {
  replaceMerge: ["xAxis", "yAxis", "series"]
})

数据转换

ECharts 5 开始支持数据转换功能,可以对数据进行过滤、排序、聚合等操作。

使用 transform

javascript
option = {
  dataset: [
    {
      source: [
        ["product", "2015", "2016", "2017"],
        ["Matcha Latte", 43.3, 85.8, 93.7],
        ["Milk Tea", 83.1, 73.4, 55.1]
      ]
    },
    {
      transform: {
        type: "filter",
        config: { dimension: "2015", gt: 50 }
      }
    }
  ],
  series: [
    {
      type: "bar",
      datasetIndex: 1
    }
  ]
}

内置转换类型

  • filter - 过滤数据
  • sort - 排序
  • aggregate - 聚合

过滤示例

javascript
{
  transform: {
    type: 'filter',
    config: { dimension: 'age', gt: 18 }
  }
}

排序示例

javascript
{
  transform: {
    type: 'sort',
    config: { dimension: 'score', order: 'desc' }
  }
}

聚合示例

javascript
{
  transform: {
    type: 'aggregate',
    config: {
      resultDimensions: [
        { name: 'total', from: 'sales', method: 'sum' },
        { name: 'avg', from: 'sales', method: 'average' },
        { name: 'max', from: 'sales', method: 'max' }
      ],
      groupBy: 'category'  // 按类别分组
    }
  }
}

数据处理最佳实践

数据格式化

javascript
option = {
  tooltip: {
    formatter: function(params) {
      return `${params.name}: ${params.value.toLocaleString()}`
    }
  },
  xAxis: {
    axisLabel: {
      formatter: function(value) {
        // 大数字格式化
        if (value >= 10000) {
          return (value / 10000).toFixed(1) + '万'
        }
        return value
      }
    }
  },
  series: [{
    label: {
      formatter: function(params) {
        return `${params.name}\n${(params.value / 1000).toFixed(1)}K`
      }
    }
  }]
}

数据预处理

在传入 ECharts 前对数据进行预处理:

javascript
// 数据清洗
function cleanData(data) {
  return data
    .filter(item => item.value !== null && item.value !== undefined)
    .map(item => ({
      ...item,
      value: Number(item.value) || 0
    }))
}
 
// 数据聚合
function aggregateData(data, groupBy, valueKey) {
  const result = {}
  data.forEach(item => {
    const key = item[groupBy]
    if (!result[key]) {
      result[key] = 0
    }
    result[key] += item[valueKey]
  })
  return Object.entries(result).map(([name, value]) => ({ name, value }))
}
 
// 使用
const cleanedData = cleanData(rawData)
const aggregatedData = aggregateData(cleanedData, 'category', 'sales')

大数据量处理

javascript
option = {
  series: [{
    type: 'scatter',
    large: true,           // 开启大数据优化
    largeThreshold: 2000,  // 数据量阈值
    progressive: 200,      // 渐进式渲染
    progressiveThreshold: 3000,  // 渐进式渲染阈值
    data: largeDataArray
  }]
}

数据缓存策略

javascript
// 使用 Map 缓存处理后的数据
const dataCache = new Map()
 
function getCachedData(key, fetchFn) {
  if (dataCache.has(key)) {
    return Promise.resolve(dataCache.get(key))
  }
  return fetchFn().then(data => {
    dataCache.set(key, data)
    return data
  })
}
 
// 使用
getCachedData('sales-2024', () => fetch('/api/sales/2024'))
  .then(data => chart.setOption({ series: [{ data }] }))

防抖与节流

频繁数据更新时使用防抖/节流:

javascript
import { debounce, throttle } from 'lodash'
 
// 防抖:停止操作后才更新
const updateChartDebounced = debounce((data) => {
  chart.setOption({ series: [{ data }] })
}, 300)
 
// 节流:固定间隔更新
const updateChartThrottled = throttle((data) => {
  chart.setOption({ series: [{ data }] })
}, 300)
 
// 实时数据场景使用节流
ws.onmessage = (e) => {
  updateChartThrottled(JSON.parse(e.data))
}

数据验证

javascript
function validateOption(option) {
  const errors = []
 
  // 检查必需字段
  if (!option.series || option.series.length === 0) {
    errors.push('series is required')
  }
 
  // 检查数据有效性
  option.series?.forEach((s, i) => {
    if (!Array.isArray(s.data)) {
      errors.push(`series[${i}].data must be an array`)
    }
  })
 
  if (errors.length > 0) {
    console.warn('Option validation errors:', errors)
    return false
  }
  return true
}
 
// 使用
const option = generateOption()
if (validateOption(option)) {
  chart.setOption(option)
}

高级交互模式

拖拽交互

javascript
option = {
  series: [{
    type: 'graph',
    roam: true,
    draggable: true,  // 开启拖拽
    data: nodes,
    links: links
  }]
}
 
// 监听拖拽事件
chart.on('graphRoam', function(params) {
  console.log('图表被拖拽或缩放')
})

刷选交互

javascript
option = {
  toolbox: {
    feature: {
      brush: {
        type: ['rect', 'polygon', 'lineX', 'lineY']
      }
    }
  },
  brush: {
    toolbox: ['rect', 'polygon', 'lineX', 'lineY', 'keep', 'clear'],
    xAxisIndex: 0
  },
  series: [{
    type: 'scatter',
    data: scatterData
  }]
}
 
// 监听刷选事件
chart.on('brushSelected', function(params) {
  const selectedData = params.batch[0].selected[0].dataIndex
  console.log('选中的数据索引:', selectedData)
})

联动交互

多个图表之间的联动:

javascript
// 创建多个图表
const chart1 = echarts.init(dom1)
const chart2 = echarts.init(dom2)
 
// 设置联动
echarts.connect([chart1, chart2])
 
// 或使用 group
chart1.group = 'group1'
chart2.group = 'group1'
echarts.connect('group1')
 
// 一个图表的缩放、平移会同步到另一个图表

自定义交互提示

javascript
option = {
  tooltip: {
    trigger: 'axis',
    axisPointer: {
      type: 'cross',
      label: {
        backgroundColor: '#6a7985'
      }
    },
    formatter: function(params) {
      let result = `<div style="font-weight:bold">${params[0].axisValue}</div>`
      params.forEach(param => {
        result += `
          <div style="display:flex;align-items:center;margin:4px 0">
            <span style="display:inline-block;width:10px;height:10px;background:${param.color};margin-right:8px"></span>
            <span>${param.seriesName}: </span>
            <span style="font-weight:bold;margin-left:auto">${param.value.toLocaleString()}</span>
          </div>`
      })
      return result
    }
  }
}

相关链接