# How to Handle Events and Implement Custom Interactions in ECharts

> Master ECharts event handling and custom interactions. Learn to use chart.on, registerAction, and dispatchAction for dynamic data visualization and user engagement.

- Repository: [The Apache Software Foundation/echarts](https://github.com/apache/echarts)
- Tags: how-to-guide
- Published: 2026-03-04

---

**ECharts provides a unified event-handling architecture that lets you listen to built-in events via `chart.on` and create custom interactions through `echarts.registerAction` and `chart.dispatchAction`.**

To handle events and implement custom interactions in ECharts, you work with three core APIs provided by the Apache ECharts library: `chart.on` for event registration, `chart.dispatchAction` for programmatic triggering, and `echarts.registerAction` for extending the framework with custom behavior. This architecture decouples low-level DOM events from high-level logical interactions, enabling precise control over how users interact with your visualizations.

## Understanding the ECharts Event Architecture

The event system in ECharts is built around three interconnected concepts that bridge user input and chart state:

| Concept | Purpose | Key Source Location |
|---------|---------|---------------------|
| **Event Registration** (`chart.on`) | Attaches handlers to ZRender events or logical component queries. Uses `ECEventProcessor` to normalize queries against component models. | [`src/util/ECEventProcessor.ts`](https://github.com/apache/echarts/blob/main/src/util/ECEventProcessor.ts) |
| **Action Dispatching** (`chart.dispatchAction`) | Triggers built-in or custom actions programmatically. Looks up handlers in the global `actions` registry and updates models accordingly. | [`src/core/echarts.ts`](https://github.com/apache/echarts/blob/main/src/core/echarts.ts) (around line 1479) |
| **Custom Action Registration** (`echarts.registerAction`) | Declares new action types with event names and handler logic. Stores handlers in the `actions` map for use by `dispatchAction`. | [`src/core/echarts.ts`](https://github.com/apache/echarts/blob/main/src/core/echarts.ts) |

## Listening to Events with `chart.on`

The `chart.on` method is your entry point for handling user interactions. It supports both raw ZRender events (like `click`, `mouseover`) and logical ECharts events (like `legendselectchanged`).

### Basic Event Registration

To listen to any click on the chart canvas:

```javascript
chart.on('click', function (params) {
    console.log('Clicked element:', params);
    console.log('Component type:', params.componentType);
    console.log('Data index:', params.dataIndex);
});

```

The `params` object contains contextual information about the clicked element, including `seriesIndex`, `dataIndex`, `name`, and `value`.

### Filtering Events with Component Queries

For precise control, pass a query object as the second argument. The query is normalized by `ECEventProcessor.normalizeQuery` into sub-queries (`cptQuery`, `dataQuery`, `otherQuery`) and filtered against the triggering component:

```javascript
// Only handle clicks on series index 2
chart.on('click', { seriesIndex: 2 }, function (params) {
    console.log('Series 2 clicked:', params.data);
});

// Listen to legend selection changes on a specific legend component
chart.on('legendselectchanged', { name: 'Revenue' }, function (params) {
    console.log('Legend item toggled:', params.selected);
});

```

The filtering logic resides in `ECEventProcessor.filter` (lines 28-45 of [`src/util/ECEventProcessor.ts`](https://github.com/apache/echarts/blob/main/src/util/ECEventProcessor.ts)), which checks if the event target matches the query constraints before invoking your handler.

## Triggering Actions Programmatically with `chart.dispatchAction`

While `chart.on` reacts to user input, `chart.dispatchAction` allows you to trigger chart behaviors programmatically. This is essential for implementing external controls or automated interactions.

Built-in actions include `highlight`, `downplay`, `showTip`, `hideTip`, `dataZoom`, `legendToggleSelect`, and `brush`:

```javascript
// Highlight the first series when a button is clicked
document.getElementById('highlightBtn').addEventListener('click', function () {
    chart.dispatchAction({
        type: 'highlight',
        seriesIndex: 0
    });
});

// Remove highlight
document.getElementById('downplayBtn').addEventListener('click', function () {
    chart.dispatchAction({
        type: 'downplay',
        seriesIndex: 0
    });
});

```

The `dispatchAction` implementation (around line 1479 in [`src/core/echarts.ts`](https://github.com/apache/echarts/blob/main/src/core/echarts.ts)) looks up the action handler in the global registry, executes it to update the model, and then fires the corresponding public event.

## Creating Custom Interactions with `echarts.registerAction`

To implement truly custom behavior that integrates with ECharts' reactive system, use `echarts.registerAction`. This API declares a new action type that can be dispatched and listened to like built-in actions.

### Registering a Custom Action

The registration requires a configuration object and a handler function:

```javascript
echarts.registerAction(
    {
        type: 'myCustomToggle',
        event: 'myCustomToggle',  // Public event name (will be lowercased internally)
        update: 'updateView'      // Optional: lifecycle hook to trigger after action
    },
    function (payload, ecModel) {
        // Access the target series
        const series = ecModel.getSeriesByIndex(payload.seriesIndex);
        
        // Toggle custom state
        series.__myFlag = !series.__myFlag;
        
        // Return refined event data that listeners will receive
        return {
            type: 'myCustomToggle',
            seriesIndex: payload.seriesIndex,
            selected: series.__myFlag
        };
    }
);

```

The registration code (in [`src/core/echarts.ts`](https://github.com/apache/echarts/blob/main/src/core/echarts.ts)) stores the handler in the `actions` map and creates the public event type by lowercasing the provided event name.

### Dispatching and Listening to Custom Events

Once registered, your custom action works identically to built-in actions:

```javascript
// Dispatch the custom action
chart.dispatchAction({
    type: 'myCustomToggle',
    seriesIndex: 0
});

// Listen for the custom event (note the lowercased event name)
chart.on('mycustomtoggle', function (event) {
    console.log('Custom toggle state:', event.selected);
});

```

This pattern allows you to implement complex interactions—such as multi-stage selections, custom brushing logic, or external synchronization—while maintaining ECharts' reactive data flow.

## Complete Code Examples

### Example 1 – Basic Click Handling with Component Query

```javascript
// Initialize chart
var chart = echarts.init(document.getElementById('main'));

// Configure option with multiple series
chart.setOption({
    series: [
        { name: 'Sales', type: 'bar', data: [120, 200, 150] },
        { name: 'Revenue', type: 'line', data: [60, 80, 100] }
    ]
});

// Listen only to clicks on the second series (index 1)
chart.on('click', { seriesIndex: 1 }, function (params) {
    console.log('Revenue series clicked:', params.name, params.value);
});

```

### Example 2 – Highlight and Downplay via API

```html
<button id="highlightBtn">Highlight Series 0</button>
<button id="downplayBtn">Clear Highlight</button>
<div id="main" style="width: 600px; height: 400px;"></div>

<script>
    var chart = echarts.init(document.getElementById('main'));
    chart.setOption({
        series: [{ type: 'scatter', data: [[10, 20], [30, 40]] }]
    });

    document.getElementById('highlightBtn').addEventListener('click', function () {
        chart.dispatchAction({
            type: 'highlight',
            seriesIndex: 0
        });
    });

    document.getElementById('downplayBtn').addEventListener('click', function () {
        chart.dispatchAction({
            type: 'downplay',
            seriesIndex: 0
        });
    });
</script>

```

### Example 3 – Register and Use a Custom Interaction

```javascript
// Register a custom "toggleSelect" action
echarts.registerAction(
    {
        type: 'customToggle',
        event: 'customToggle',
        update: 'updateView'
    },
    function (payload, ecModel) {
        const series = ecModel.getSeriesByIndex(payload.seriesIndex);
        series.__isToggled = !series.__isToggled;
        return {
            type: 'customToggle',
            seriesIndex: payload.seriesIndex,
            isToggled: series.__isToggled
        };
    }
);

// Initialize chart
var chart = echarts.init(document.getElementById('main'));
chart.setOption({
    series: [
        { type: 'bar', data: [5, 10, 15] },
        { type: 'bar', data: [20, 25, 30] }
    ]
});

// Double-click to toggle custom state
chart.on('dblclick', { seriesIndex: 0 }, function () {
    chart.dispatchAction({ type: 'customToggle', seriesIndex: 0 });
});

// Listen for the custom event
chart.on('customtoggle', function (e) {
    console.log('Series toggled state:', e.isToggled);
});

```

### Example 4 – Data-Level Query for Precise Interaction

```javascript
var chart = echarts.init(document.getElementById('main'));
chart.setOption({
    xAxis: { type: 'category', data: ['Mon', 'Tue', 'Wed'] },
    yAxis: { type: 'value' },
    series: [{ type: 'bar', data: [120, 200, 150], name: 'Sales' }]
});

// Only respond to mouseover on the data item named 'Tue'
chart.on('mouseover', { name: 'Tue' }, function (params) {
    chart.dispatchAction({
        type: 'highlight',
        seriesIndex: params.seriesIndex,
        dataIndex: params.dataIndex
    });
    
    console.log('Highlighted Tuesday value:', params.value);
});

```

## Key Source Files and Implementation Details

Understanding the internal implementation helps debug complex interaction scenarios. The event system relies on these critical files:

| File | Role | Key Functions |
|------|------|---------------|
| [`src/util/ECEventProcessor.ts`](https://github.com/apache/echarts/blob/main/src/util/ECEventProcessor.ts) | Normalizes query objects and filters events against component models. Contains `normalizeQuery` and `filter` methods (lines 28-45). | `normalizeQuery`, `filter`, `isTarget` |
| [`src/core/echarts.ts`](https://github.com/apache/echarts/blob/main/src/core/echarts.ts) | Core chart class implementing the public API. Contains `on`, `off`, `trigger`, and `dispatchAction` (around line 1479). | `dispatchAction`, `registerAction`, `on` |
| [`src/util/types.ts`](https://github.com/apache/echarts/blob/main/src/util/types.ts) | TypeScript definitions for event payloads and query structures. | `ECElementEvent`, `ECActionEvent`, `NormalizedEventQuery` |
| [`src/component/tooltip/TooltipView.ts`](https://github.com/apache/echarts/blob/main/src/component/tooltip/TooltipView.ts) | Demonstrates component-level usage of `dispatchAction` for tooltip coordination. | `dispatchAction` calls for `showTip` |
| [`src/component/visualMap/ContinuousView.ts`](https://github.com/apache/echarts/blob/main/src/component/visualMap/ContinuousView.ts) | Shows how complex components register mouse handlers and dispatch custom actions. | Mouse event handlers, action dispatching |

The architecture decouples **ZRender** (the rendering engine) events from logical ECharts events through the `ECEventProcessor` class. When a user clicks, the raw DOM event flows through ZRender, gets wrapped with model information in [`echarts.ts`](https://github.com/apache/echarts/blob/main/echarts.ts), and is then filtered through `ECEventProcessor` before reaching your handler.

## Summary

- **Event Registration**: Use `chart.on(eventName, [query], handler)` to listen to user interactions. The optional query object filters events by component or data item using logic in [`src/util/ECEventProcessor.ts`](https://github.com/apache/echarts/blob/main/src/util/ECEventProcessor.ts).
- **Action Dispatching**: Use `chart.dispatchAction({ type: 'actionName', ... })` to programmatically trigger chart behaviors like highlighting, data zoom, or tooltip display.
- **Custom Actions**: Extend ECharts by calling `echarts.registerAction({ type, event, update }, handler)` to create reusable interaction patterns that integrate with the chart's reactive update cycle.
- **Architecture**: The system separates low-level ZRender events from high-level logical events through `ECEventProcessor`, enabling precise targeting of components and data points.

## Frequently Asked Questions

### How do I listen to clicks on a specific series only?

Use the query parameter in `chart.on` to filter by `seriesIndex`, `seriesName`, or other component properties. The query is processed by `ECEventProcessor.normalizeQuery` in [`src/util/ECEventProcessor.ts`](https://github.com/apache/echarts/blob/main/src/util/ECEventProcessor.ts) and filtered against the event target:

```javascript
chart.on('click', { seriesIndex: 2 }, function (params) {
    console.log('Only series index 2 clicks trigger this');
});

```

### What is the difference between `dispatchAction` and `registerAction`?

`registerAction` defines a new action type and its handler, storing it in the global `actions` registry in [`src/core/echarts.ts`](https://github.com/apache/echarts/blob/main/src/core/echarts.ts). `dispatchAction` invokes an action (built-in or custom) by looking it up in that registry and executing its handler. Think of `registerAction` as declaring a function, and `dispatchAction` as calling it.

### Can I create completely custom event types in ECharts?

Yes. By using `echarts.registerAction`, you can define custom action types that emit public events. The `event` property in the registration config specifies the public event name (automatically lowercased by the internal `createEventType` function). After registration, you can dispatch your custom action and listen to it via `chart.on` like any built-in event.

### How does ECharts filter events when using query objects?

When you provide a query object to `chart.on`, the `ECEventProcessor` class normalizes it into sub-queries (`cptQuery`, `dataQuery`, `otherQuery`). When an event fires, the `filter` method (lines 28-45 in [`src/util/ECEventProcessor.ts`](https://github.com/apache/echarts/blob/main/src/util/ECEventProcessor.ts)) checks if the event target's component model and data index match these queries before invoking your handler. This allows precise targeting of specific data points or components without manual conditional logic in your event handlers.