How to Combine Multiple Chart Series Types in a Single ECharts Instance

You combine multiple chart series types in ECharts by declaring different type values (such as "line" and "bar") in the series array of a single setOption() call, which leverages the global ExtensionRegister to instantiate the appropriate SeriesModel and SeriesView for each entry.

Apache ECharts treats every visual element—whether a line, bar, scatter, or pie—as an independent series that can coexist within one chart instance. Because each series type registers its SeriesModel (data logic) and SeriesView (rendering logic) with the core ExtensionRegister during initialization, you can seamlessly mix types by supplying them in the same configuration object.

How ECharts Registers Series Types

Before rendering, ECharts executes an internal registration pipeline that makes multi-type charts possible. This pipeline uses install functions found in chart-specific modules to register components with the global registers object.

The ExtensionRegister Pattern

Each built-in chart type resides in its own directory under src/chart/. Within these directories, an install.ts file handles registration:

  • Line seriessrc/chart/line/install.ts calls registers.registerSeriesModel(LineSeries) and registers.registerChartView(LineView)
  • Bar seriessrc/chart/bar/install.ts calls registers.registerSeriesModel(BarSeries) and registers.registerChartView(BarView)

These install functions are aggregated and re-exported from src/export/charts.ts, which ensures that when you import the ECharts bundle, every series type becomes available globally.

Initialization and Model Instantiation

When you call echarts.init() (defined in src/echarts.ts), the instance receives all registered renderers and models. Upon calling setOption({ series: [...] }), the library iterates the series array and performs three critical steps:

  1. Reads the type field (e.g., "line" or "bar") from each series object.
  2. Looks up the corresponding SeriesModel class that was previously registered via registers.registerSeriesModel.
  3. Creates a model instance, attaches the data, and links it to the appropriate SeriesView for rendering.

Because this lookup is global and type-keyed, you can declare any combination of series in one option object without manually loading separate modules.

Combining Line and Bar Series with Dual Axes

The most common use case overlays a line series on a bar series while using separate Y-axes for different scales. In this configuration, both series share the same Cartesian coordinate system but bind to different axes via yAxisIndex.

import * as echarts from 'echarts';

const chart = echarts.init(document.getElementById('main'));

chart.setOption({
  tooltip: { trigger: 'axis' },
  legend: { data: ['Temperature', 'Precipitation'] },
  xAxis: { type: 'category', data: ['Jan', 'Feb', 'Mar', 'Apr'] },
  yAxis: [
    { type: 'value', name: 'Temperature (°C)' },
    { type: 'value', name: 'Precipitation (mm)', position: 'right' }
  ],
  series: [
    {
      name: 'Temperature',
      type: 'line',
      data: [5, 7, 9, 13]
    },
    {
      name: 'Precipitation',
      type: 'bar',
      yAxisIndex: 1,  // Binds to the second yAxis
      data: [30, 45, 28, 60]
    }
  ]
});

Behind the scenes, ECharts invokes registers.registerSeriesModel(LineSeries) and registers.registerSeriesModel(BarSeries) (as defined in their respective install.ts files) to handle the layout and rendering independently while synchronizing the shared X-axis categories.

Mixing Cartesian and Polar Coordinate Systems

You can combine series that use different coordinate systems—such as line and scatter (Cartesian) alongside pie (polar)—in a single instance. The pie series internally manages its own polar coordinate system and does not interfere with the Cartesian grid.

chart.setOption({
  legend: { data: ['Growth', 'Revenue', 'Market Share'] },
  tooltip: { trigger: 'item' },
  
  // Cartesian grid for line and scatter
  grid: { left: '10%', right: '55%', top: '10%', bottom: '10%' },
  xAxis: { type: 'category', data: ['Q1', 'Q2', 'Q3', 'Q4'] },
  yAxis: { type: 'value' },
  
  series: [
    {
      name: 'Growth',
      type: 'line',
      data: [12, 18, 24, 30]
    },
    {
      name: 'Revenue',
      type: 'scatter',
      symbolSize: 12,
      data: [20, 25, 28, 35]
    },
    {
      name: 'Market Share',
      type: 'pie',
      radius: '30%',
      center: ['80%', '50%'],  // Positions pie on the right
      data: [
        { value: 40, name: 'Product A' },
        { value: 35, name: 'Product B' },
        { value: 25, name: 'Product C' }
      ]
    }
  ]
});

The line and scatter series utilize layoutPoints routines registered in src/chart/line/install.ts and src/chart/scatter/install.ts, while the pie series uses its own layout logic from src/chart/pie/install.ts. The core dispatcher manages these disparate pipelines concurrently.

Programmatic Registration for Custom Series

If you need to load a custom series type alongside built-ins, you can manually execute the install functions against the global registers before initializing the chart.

import * as echarts from 'echarts';
import { install as LineChart } from 'echarts/src/chart/line/install';
import { install as CustomChart } from 'echarts/src/chart/custom/install';

const registers = echarts.getRegister();  // Internal API access
LineChart(registers);
CustomChart(registers);  // Register custom series

const chart = echarts.init(document.getElementById('custom'));
chart.setOption({
  series: [
    { type: 'line', data: [1, 2, 3] },
    { type: 'custom', renderItem: (params, api) => ({ /* ... */ }) }
  ]
});

This mirrors the internal flow found in src/export/charts.ts, where built-in charts are registered en masse.

Summary

  • SeriesModel and SeriesView pairs are registered globally via install functions in files like src/chart/line/install.ts and src/chart/bar/install.ts.
  • The type field in each series object triggers a lookup against the ExtensionRegister, allowing any combination of series in one setOption call.
  • Cartesian series (line, bar, scatter) share grid and axis components, while polar series (pie) operate independently within the same canvas.
  • You can manually invoke install functions from src/export/charts.ts or individual chart directories to register custom types programmatically.

Frequently Asked Questions

Can I combine more than two series types in one chart?

Yes. The ExtensionRegister architecture supports an unlimited number of series types within a single instance. Simply add additional objects to the series array with distinct type values (e.g., "line", "bar", "scatter", "pie"), and ECharts will instantiate the appropriate model and view for each entry according to the registrations found in src/export/charts.ts.

Do mixed series types share the same coordinate system?

It depends on the series type. Line, bar, and scatter series default to the Cartesian coordinate system and share grid and axis components. Pie, radar, and gauge series use independent coordinate systems (polar or custom layouts) that do not conflict with Cartesian grids, allowing you to overlay them as separate visual layers.

How do I bind different series to different Y-axes?

Use the yAxisIndex property in the series configuration. After defining multiple axes in the yAxis array (index 0, 1, etc.), set yAxisIndex: 1 on the series that should use the second axis. The SeriesModel reads this index during initialization (src/echarts.ts) to calculate positioning and scaling independently from other series.

Is it possible to use custom series with built-in types?

Yes. Import the install function from your custom series module (following the pattern in src/chart/custom/install.ts) and execute it against the global registers before calling echarts.init. This registers your custom SeriesView and SeriesModel alongside the built-ins exported from src/export/charts.ts, allowing you to declare them in the same series array using your custom type identifier.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →