How to Handle Events and Implement Custom Interactions in ECharts
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 |
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 (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 |
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:
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:
// 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), 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:
// 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) 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:
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) 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:
// 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
// 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
<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
// 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
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 |
Normalizes query objects and filters events against component models. Contains normalizeQuery and filter methods (lines 28-45). |
normalizeQuery, filter, isTarget |
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 |
TypeScript definitions for event payloads and query structures. | ECElementEvent, ECActionEvent, NormalizedEventQuery |
src/component/tooltip/TooltipView.ts |
Demonstrates component-level usage of dispatchAction for tooltip coordination. |
dispatchAction calls for showTip |
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, 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 insrc/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 and filtered against the event target:
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. 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) 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →