事件处理(Event)

G2 对外暴露了一些事件,用于获取图表的生命周期,以及交互信息。G2 导出了一个 ChartEvent 类型,用于定义事件的类型。

click event
import { Chart, ChartEvent } from '@antv/g2';

const chart = new Chart({
  container,
  canvas,
});

chart.options({
  type: 'interval',
  data: [
    { genre: 'Sports', sold: 275 },
    { genre: 'Strategy', sold: 115 },
    { genre: 'Action', sold: 120 },
    { genre: 'Shooter', sold: 350 },
    { genre: 'Other', sold: 150 },
  ],
  encode: {
    x: 'genre',
    y: 'sold',
    color: 'genre',
  },
  axis: {
    x: { animate: false },
    y: { animate: false },
  },
});

chart.on('interval:click', (e) => {
  console.log(e.data.data); // 展示点击的数据
});

chart.on('element:click', (e) => {
  console.log(e.data.data); // 展示点击的数据
});

chart.render();

生命周期事件

如果想要获取图表的生命周期信息,可以通过下面的方式:

chart.on(ChartEvent.AFTER_RENDER, (ev) => {
  console.log(ev);
});

G2 目前提供了以下生命周期事件:

事件名 说明
ChartEvent.BEFORE_RENDER 渲染前
ChartEvent.BEFORE_PAINT 绘制前
ChartEvent.AFTER_PAINT 绘制后
ChartEvent.AFTER_RENDER 渲染后
ChartEvent.BEFORE_CHANGE_DATA 更改数据前
ChartEvent.AFTER_CHANGE_DATA 更改数据后
ChartEvent.BEFORE_CLEAR 清理画布前
ChartEvent.AFTER_CLEAR 清理画布后
ChartEvent.BEFORE_DESTROY 销毁画布前
ChartEvent.AFTER_DESTROY 销毁画布后
ChartEvent.BEFORE_CHANGE_SIZE 更改画布尺寸前
ChartEvent.AFTER_CHANGE_SIZE 更改画布尺寸后
  • 渲染前:指 G2 着手开始处理数据,进行布局,绘制图形等操作。
  • 绘制前: 指完成数据处理,布局,绘制图形等操作,但是还没有进行绘制。
  • 绘制后: 指 G2 完成所有的绘制操作,但可能存在动画,动画结束后,图表才算真正的渲染完成。
  • 渲染后: 指 G2 完成所有的绘制操作,包括动画。
  • 清理画布后: 容器中的图表已经被清理,但是 G2 实例还存在,可以继续使用。
  • 销毁画布后: G2 实例已经被销毁,无法再使用。

交互事件

如果你想要获取图表的交互信息,可以通过下面的方式:

  • 监听全局 element 事件
chart.on(`element:${ChartEvent.EventType}`, (ev) => {
  console.log(ev);
});
  • 监听指定 element 事件
chart.on(`${markType}:${ChartEvent.EventType}`, (ev) => {
  console.log(ev);
});

// 如,监听柱状图中的柱子的点击事件
chart.on(`interval:${ChartEvent.CLICK}`, (ev) => {
  console.log(ev);
});
  • 监听 plot 区域事件
chart.on('plot:click', (event) => console.log(event));
  • 监听全局 component 事件
chart.on('component:click', (event) => console.log(event));
  • 监听全局 label 事件
chart.on('label:click', (event) => console.log(event));

点击事件

事件名 说明 回调参数
ChartEvent.CLICK 点击 Event
ChartEvent.DBLCLICK 双击 Event

指针事件

事件名 说明 回调参数
ChartEvent.POINTER_TAP Event
ChartEvent.POINTER_DOWN 当指针按下时 Event
ChartEvent.POINTER_UP 当指针松开时 Event
ChartEvent.POINTER_OVER 当指针进入目标元素时 Event
ChartEvent.POINTER_OUT 当指针离开目标元素时 Event
ChartEvent.POINTER_MOVE 当指针改变坐标时 Event
ChartEvent.POINTER_ENTER 当指针进入目标元素或其子元素时 Event
ChartEvent.POINTER_LEAVE 当指针离开目标元素或其子元素时 Event
ChartEvent.POINTER_UPOUTSIDE Event

拖拽事件

如果希望监听拖拽事件,需要设置 draggable 和 droppable 属性

chart.options({
  type: 'interval',
  style: {
    draggable: true,
    droppable: true,
  },
});
事件名 说明 回调参数
ChartEvent.DRAG_START 开始拖拽时 Event
ChartEvent.DRAG 拖拽过程中 Event
ChartEvent.DRAG_END 拖拽完成时 Event
ChartEvent.DRAG_ENTER 元素被拖拽进入目标元素内时 Event
ChartEvent.DRAG_LEAVE 元素被拖拽离开目标元素时 Event
ChartEvent.DRAG_OVER 元素被拖拽悬停在目标元素内时 Event
ChartEvent.DROP 元素被放置到目标元素内时 Event

通过 className 进行精细化控制

G2 为图表中的各个组件元素提供了标准化的 className,可以通过这些 className 实现更精细化的事件监听和样式控制。

:::warning{title=重要提示} 对于图例、坐标轴等组件的交互事件,优先推荐使用 G2 提供的高级交互事件,而不是直接操作 DOM 元素。这样可以获得更稳定、语义更清晰的事件处理机制。相关交互文档:图例筛选图例高亮。 :::

组件事件监听的推荐方式

对于图例、坐标轴等组件,推荐使用以下方式监听事件:

方式一:使用高级交互事件(推荐 ⭐⭐⭐⭐⭐)

import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  autoFit: true,
});

chart.options({
  type: 'interval',
  data: [
    { genre: 'Sports', sold: 275 },
    { genre: 'Strategy', sold: 115 },
    { genre: 'Action', sold: 120 },
    { genre: 'Shooter', sold: 350 },
    { genre: 'Other', sold: 150 },
  ],
  encode: {
    x: 'genre',
    y: 'sold',
    color: 'genre',
  },
});

chart.render();

// 推荐:使用高级交互事件
chart.on('legend:filter', (e) => {
  const { nativeEvent, data } = e;
  if (!nativeEvent) return; // 过滤程序触发的事件

  console.log('✅ 图例筛选事件:', data);
  console.log('   - 通道:', data.channel);
  console.log('   - 当前选中值:', data.values);
});

chart.on('legend:reset', (e) => {
  const { nativeEvent } = e;
  if (!nativeEvent) return;
  console.log('✅ 图例重置(全选)');
});

方式二:监听具体子元素事件(适用于简单的自定义交互)

如果需要自定义交互逻辑,可以监听组件的子元素事件:

import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  autoFit: true,
});

chart.options({
  type: 'interval',
  data: [
    { genre: 'Sports', sold: 275 },
    { genre: 'Strategy', sold: 115 },
    { genre: 'Action', sold: 120 },
    { genre: 'Shooter', sold: 350 },
    { genre: 'Other', sold: 150 },
  ],
  encode: {
    x: 'genre',
    y: 'sold',
    color: 'genre',
  },
});

chart.render();

// 监听图例标签和标记的点击
const handleLegendClick = (event, source) => {
  // 方式1: 通过父容器获取完整数据(推荐)
  const item = event.target.parentNode.parentNode; // marker/label -> group -> item
  if (item && item.__data__) {
    // 向上查找真正的图例组件 (className 包含 'legend-category')
    let legend = item.parentNode;
    while (legend && !legend.className.includes('legend-category')) {
      legend = legend.parentNode;
      if (!legend) return;
    }

    if (legend && legend.attributes && legend.attributes.data) {
      const { data } = legend.attributes;
      const { index } = item.__data__;
      const itemData = data[index];
      console.log(`✅ 点击了 ${source}:`, itemData); // {id, label, color}
      console.log(`   - ID: ${itemData.id}`);
      console.log(`   - 标签: ${itemData.label}`);
      console.log(`   - 颜色: ${itemData.color}`);
    }
  }

  // 方式2: 从 target.attributes 获取部分信息
  if (source === 'label') {
    console.log('   - 文本:', event.target.attributes.text);
  } else if (source === 'marker') {
    console.log('   - 颜色:', event.target.attributes.fill);
  }
};

chart.on('g2-legend-marker:click', (e) => handleLegendClick(e, 'marker'));
chart.on('g2-legend-label:click', (e) => handleLegendClick(e, 'label'));

方式三:直接操作 Canvas DOM(最灵活,适用于完全定制化的场景)

仅在需要完全自定义行为时使用:

chart.on('afterrender', () => {
  const { canvas } = chart.getContext();
  const { document } = canvas;
  const items = document.getElementsByClassName('g2-legend-item');

  items.forEach((item) => {
    // 利用事件冒泡:点击子元素会冒泡到容器
    item.addEventListener('click', () => {
      // 向上查找真正的图例组件
      let legend = item.parentNode;
      while (legend && !legend.className.includes('legend-category')) {
        legend = legend.parentNode;
        if (!legend) return;
      }

      if (legend && legend.attributes && legend.attributes.data) {
        const { data } = legend.attributes;
        const { index } = item.__data__;
        const itemData = data[index]; // {id, label, color}
        console.log('图例项数据:', itemData);
      }
    });
  });
});

过滤特定组件事件

使用 className 可以方便地过滤掉某些组件的事件,避免干扰:

import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  autoFit: true,
});

chart.options({
  type: 'interval',
  data: [
    { genre: 'Sports', sold: 275 },
    { genre: 'Strategy', sold: 115 },
    { genre: 'Action', sold: 120 },
    { genre: 'Shooter', sold: 350 },
    { genre: 'Other', sold: 150 },
  ],
  encode: {
    x: 'genre',
    y: 'sold',
    color: 'genre',
  },
});

chart.render();

// 监听绘图区点击,但排除图例点击
chart.on('plot:click', (event) => {
  const className = event.target?.className || '';

  // 检查是否点击了图例
  const isLegendClick = className.includes('legend');

  if (isLegendClick) {
    console.log('图例被点击,忽略 plot:click 事件');
    return;
  }

  // 处理绘图区的点击逻辑
  console.log('绘图区被点击', event);
});

通过 className 控制元素样式

可以通过 className 获取特定元素并动态修改其样式:

import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  autoFit: true,
});

chart.options({
  type: 'interval',
  data: [
    { genre: 'Sports', sold: 275 },
    { genre: 'Strategy', sold: 115 },
    { genre: 'Action', sold: 120 },
    { genre: 'Shooter', sold: 350 },
    { genre: 'Other', sold: 150 },
  ],
  encode: {
    x: 'genre',
    y: 'sold',
    color: 'genre',
  },
});

chart.render().then(() => {
  const { canvas } = chart.getContext();
  const { document } = canvas;
  const legendItems = document.getElementsByClassName('g2-legend-item');

  // 修改第一个图例项的样式
  if (legendItems.length > 0) {
    const firstItem = legendItems[0];
    const firstMarker = firstItem.getElementsByClassName('g2-legend-marker')[0];
    const firstLabel = firstItem.getElementsByClassName('g2-legend-label')[0];

    // 添加高亮样式
    if (firstLabel) {
      firstLabel.style.fontWeight = 'bold';
      firstLabel.style.fill = 'orange';
      firstLabel.style.shadowColor = '#d3d3d3';
      firstLabel.style.shadowBlur = 10;
      firstLabel.style.shadowOffsetX = 5;
      firstLabel.style.shadowOffsetY = 5;
    }
  }
});

根据内容查找特定元素

结合 className 和元素属性,可以精确定位到特定的图表元素:

import { Chart } from '@antv/g2';

const chart = new Chart({
  container: 'container',
  autoFit: true,
});

chart.options({
  type: 'interval',
  data: [
    { genre: 'Sports', sold: 275 },
    { genre: 'Strategy', sold: 115 },
    { genre: 'Action', sold: 120 },
    { genre: 'Shooter', sold: 350 },
    { genre: 'Other', sold: 150 },
  ],
  encode: {
    x: 'genre',
    y: 'sold',
    color: 'genre',
  },
});

chart.render().then(() => {
  const { canvas } = chart.getContext();
  const { document } = canvas;
  const legendItems = document.getElementsByClassName('g2-legend-item');
  const targetText = 'Action';
  let targetItem = null;

  // 遍历所有图例标签,找到目标文本
  for (let i = 0; i < legendItems.length; i++) {
    const labels = legendItems[i].getElementsByClassName('g2-legend-label');
    if (labels.length > 0) {
      const labelText = labels[0].getAttribute('text') || labels[0].textContent;
      if (labelText === targetText) {
        targetItem = legendItems[i];
        break;
      }
    }
  }

  if (targetItem) {
    console.log(`✅ 找到目标图例: "${targetText}"`);
    console.log('   className:', targetItem.className);
    console.log('   可以执行业务逻辑: 例如自动聚焦、高亮显示等');

    // 为目标图例项添加背景色和特殊样式

    console.log(`🎨 为图例 "${targetText}" 添加特殊样式...`);

    // 获取图例项的背景元素
    const background = targetItem.getElementsByClassName(
      'g2-legend-background',
    )[0];
    const label = targetItem.getElementsByClassName('g2-legend-label')[0];
    const marker = targetItem.getElementsByClassName('g2-legend-marker')[0];

    // 修改背景色
    if (background) {
      background.style.fill = '#FFF3E0'; // 浅橙色背景
      background.style.fillOpacity = 0.8;
    }

    // 同时修改标签样式
    if (label) {
      label.style.fill = '#FF6B00'; // 橙色文字
      label.style.fontWeight = 'bold';
    }
  }
});

G2 组件 className 完整列表

图例组件 (Legend)

className 说明
g2-legend-title 图例的标题
g2-legend-item 分类图例项的容器
g2-legend-background 分类图例项的背景
g2-legend-marker 分类图例项的图标
g2-legend-label 分类图例项的标签文字
g2-legend-value 分类图例项的值
g2-legend-focus-icon 分类图例项的聚焦图标
g2-legend-ribbon 连续图例的色带
g2-legend-track 连续图例的滑轨
g2-legend-selection 连续图例的选区
g2-legend-handle 连续图例的滑动手柄
g2-legend-handle-marker 连续图例的滑动手柄图标
g2-legend-handle-label 连续图例的标签/刻度值

坐标轴组件 (Axis)

className 说明
g2-axis-line 坐标轴主线
g2-axis-tick 坐标轴刻度线
g2-axis-tick-item 单个刻度线项
g2-axis-label 坐标轴刻度标签
g2-axis-label-item 单个标签项
g2-axis-title 坐标轴标题
g2-axis-grid 坐标轴网格线

为什么容器元素的点击事件不生效?

g2-legend-itemg2-axis 这类组件的最外层元素通常是容器元素,背景透明,本身没有可点击的渲染区域。实际的点击事件由其子元素触发,然后通过事件冒泡机制传递到容器。

图例组件示例:

  • g2-legend-marker:click - 点击标记图标
  • g2-legend-label:click - 点击标签文字
  • g2-legend-item:click - 容器本身无点击区域

坐标轴组件示例:

  • g2-axis-label:click - 点击坐标轴标签
  • g2-axis-title:click - 点击坐标轴标题
  • g2-axis-line:click - 点击坐标轴线
  • g2-axis:click - 容器本身无点击区域

如何获取点击的图例项数据?

子元素(marker/label)上没有直接存储完整数据,需要通过以下方式获取:

  1. 通过父容器获取(推荐):向上查找到 item 容器,通过 item.__data__.index + legend.attributes.data 获取完整数据
  2. 从 attributes 获取:label 的 text 属性、marker 的 fill 属性包含部分信息
  3. 使用 DOM 监听:直接在 item 容器上绑定事件,通过 item.__data__ 获取数据

示例:点击 marker 获取对应的 label

chart.on('g2-legend-marker:click', (e) =>  {
  const item = e.target.parentNode.parentNode;
  if (item && item.__data__) {
    // 向上查找真正的图例组件
    let legend = item.parentNode;
    while (legend && !legend.className.includes('legend-category')) {
      legend = legend.parentNode;
      if (!legend) return;
    }

    if (legend && legend.attributes && legend.attributes.data) {
      const { data } = legend.attributes;
      const { index } = item.__data__;
      const itemData = data[index];
      console.log('点击 marker,对应的 label 是:', itemData.label);
      console.log('完整数据:', itemData); // {id, label, color}
    }
  }
});

典型案例

详见交互-事件示例

全站搜索
文档与示例输入关键词开始搜索

输入关键词,查找文档、API 与示例

选择打开Esc关闭