How to Optimize ECharts Performance for Large Datasets: A Complete Guide to Large-Mode Rendering

Enable ECharts large-mode by setting large: true or adjusting largeThreshold to switch from individual graphic elements to typed-array rendering, reducing memory usage and draw calls for datasets exceeding 2,000 points.

When visualizing massive datasets in the apache/echarts repository, the default rendering path creates individual graphic elements for every data point, causing memory pressure and frame drops. To optimize ECharts performance, the library implements an internal large-mode pipeline that uses typed arrays and batched draw calls instead of DOM-heavy graphic primitives.

Understanding ECharts Large-Mode Architecture

The large-mode mechanism relies on three core components working together during the rendering lifecycle.

The Scheduler and Pipeline Context

The Scheduler class in src/core/Scheduler.ts (lines 60‑63) maintains a PipelineContext that tracks whether the current frame should use large-mode rendering. This context holds two critical boolean flags:

  • large: Indicates whether the series should use typed-array rendering.
  • progressiveRender: Controls whether data should be painted in chunks to maintain UI responsiveness.

The scheduler evaluates data length against series-specific thresholds to set these flags before the render cycle begins.

Render Planner and Series Models

The createRenderPlanner function in src/chart/helper/createRenderPlanner.ts (lines 43‑48) acts as a stage handler that reads the pipelineContext.large flag and applies it to the series model. When the flag changes between render cycles—such as when a dataset grows from 1,000 to 50,000 points—the planner returns 'reset', triggering a complete rebuild of the series layout and switching the rendering path from standard to large-mode.

Series models expose the configuration interface for this behavior:

How Large-Mode Rendering Works Internally

The transition from standard to large-mode involves five distinct stages that optimize memory layout and rendering throughput.

Threshold Detection and Pipeline Flags

During the update cycle, the scheduler compares the current data length against the series largeThreshold. For scatter series, the default threshold is 2000 points; for bar series, it is 400. The detection logic in src/core/Scheduler.ts (around line 230) evaluates:

const large = this.option.large ? true
             : dataLen >= this.get('largeThreshold');

When this condition evaluates to true, the pipelineContext.large flag is set, signaling downstream components to use the optimized rendering path.

Typed-Array Layout and Memory Optimization

Once large-mode activates, the series stops creating individual ZRender graphic elements (which consume significant memory for DOM references and styling properties). Instead, it allocates compact typed arrays:

  • largePoints: A Float32Array storing x/y coordinates.
  • largeBackgroundPoints: Optional buffer for background geometries.
  • largeDataIndices: An index mapping for interaction and tooltip lookups.

These buffers are processed by the renderer in a single draw call, bypassing the expensive per-element creation and update cycles of the standard path. This reduces memory usage from megabytes to kilobytes per thousand points and eliminates thousands of individual draw calls.

Progressive Rendering for Massive Data

For datasets exceeding 100,000 points, even typed-array rendering can cause frame drops during the initial paint. The progressive option enables chunked rendering:

// From BarSeries.ts lines 14-20
getProgressive(): number | false {
    return this.get('large') ? (this.get('progressive') || 5000) : false;
}

The series returns a progressive step size (default 5000 for large mode) and a threshold (getProgressiveThreshold()) that is automatically enlarged to at least the largeThreshold. The scheduler uses these values to paint data in batches across multiple animation frames, keeping the UI responsive while the chart initializes.

Reset on Mode Change

If a dataset dynamically grows or shrinks across the threshold boundary, the render planner detects the flag change and returns 'reset' (createRenderPlanner.ts lines 46‑48). This triggers a complete series layout rebuild, ensuring the correct rendering path is used without visual artifacts or memory leaks from mixed-mode rendering.

Practical Configuration: Optimize ECharts Performance with Code Examples

Enabling Large-Mode for Scatter Plots

The scatter series provides the most dramatic performance improvements with large-mode due to the high point count typical in scatter visualizations.

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

// Generate 50,000 random data points
const data = Array.from({length: 50000}, () => [
  Math.random() * 100,
  Math.random() * 100
]);

const option = {
  xAxis: {},
  yAxis: {},
  series: [{
    type: 'scatter',
    // Force large-mode regardless of data size
    large: true,
    // Only switch to large-mode when exceeding 10,000 points
    largeThreshold: 10000,
    // Render in batches of 5,000 points per frame
    progressive: 5000,
    data: data
  }]
};

chart.setOption(option);

Key configuration points:

  • large: true forces typed-array rendering even for smaller datasets, useful when you anticipate dynamic growth.
  • largeThreshold: 10000 overrides the default 2000-point threshold for scatter series as defined in src/chart/scatter/ScatterSeries.ts.
  • progressive: 5000 ensures the UI remains responsive during the initial render of 50,000 points.

Configuring Progressive Rendering for Bar Charts

Bar charts use a lower default threshold (400 bars) for large-mode activation and adapt progressive settings automatically.

const option = {
  xAxis: {
    type: 'category',
    data: Array.from({length: 20000}, (_, i) => `Item ${i}`)
  },
  yAxis: {},
  series: [{
    type: 'bar',
    // Auto-detect large mode (default largeThreshold = 400)
    // Set higher threshold so large-mode triggers only above 5,000 bars
    largeThreshold: 5000,
    // Chunk size for progressive painting
    progressive: 2000,
    data: Array.from({length: 20000}, () => Math.round(Math.random() * 1000))
  }]
};

echarts.init(document.getElementById('chart')).setOption(option);

Implementation details from the source: According to src/chart/bar/BarSeries.ts (lines 14‑20), the getProgressive() method returns this.get('large') ? (this.get('progressive') || 5000) : false. This means when large mode activates (either manually or via largeThreshold), the series automatically adopts progressive rendering with a default chunk size of 5000 unless overridden.

Dynamic Mode Switching After Data Updates

When datasets grow dynamically across the threshold boundary, ECharts automatically rebuilds the rendering pipeline.

// Initial small dataset within normal rendering limits
let data = Array.from({length: 1500}, () => [
  Math.random() * 100, 
  Math.random() * 100
]);

chart.setOption({
  series: [{
    type: 'scatter', 
    data, 
    largeThreshold: 2000
  }]
});

// Simulate data growth beyond threshold
setTimeout(() => {
  data = Array.from({length: 80000}, () => [
    Math.random() * 100, 
    Math.random() * 100
  ]);
  
  chart.setOption({
    series: [{
      type: 'scatter', 
      data
      // large will be auto-enabled (dataLen > largeThreshold)
    }]
  });
}, 3000);

Automatic pipeline reset: As implemented in src/chart/helper/createRenderPlanner.ts (lines 46‑48), when the pipelineContext.large flag changes between true and false, the planner returns 'reset'. This triggers a complete series layout rebuild, ensuring the series switches from standard graphic element rendering to the typed-array largePoints buffer without visual artifacts.

Performance Tuning Best Practices

To maximize rendering speed when working with massive datasets in ECharts, apply these configuration strategies derived from the source code implementation:

  • Force large-mode explicitly with large: true when you know datasets will exceed 10,000 points. This bypasses threshold detection and immediately allocates Float32Array buffers in src/core/Scheduler.ts, preventing the memory spike of initial standard rendering.

  • Raise the switching threshold using largeThreshold when you need interactive features like individual point emphasis or labels for moderately large datasets (5,000–10,000 points). The default 2000 for scatter (src/chart/scatter/ScatterSeries.ts) may be conservative for modern hardware.

  • Combine large-mode with progressive rendering by setting both large: true and progressive: 5000. As defined in src/chart/bar/BarSeries.ts, the series automatically enlarges progressive thresholds when large mode is active, painting data in chunks to maintain 60fps during initialization.

  • Disable expensive visual effects including animation, label, and emphasis.scale when using large-mode. While the typed-array path in src/core/Scheduler.ts reduces memory overhead, per-point effects still require individual processing that defeats the optimization purpose.

  • Monitor pipeline resets when dynamically updating data across threshold boundaries. The createRenderPlanner.ts logic triggers a full series reset when switching modes, which may cause a brief frame drop; batch updates to stay consistently above or below largeThreshold.

Summary

Optimizing ECharts performance for large datasets requires understanding the library's internal large-mode pipeline that switches from individual graphic elements to typed-array rendering. Key takeaways include:

  • Threshold-based activation: Series automatically switch to large-mode when data exceeds largeThreshold (default 2000 for scatter, 400 for bar), controlled by the Scheduler in src/core/Scheduler.ts.
  • Memory efficiency: Large-mode stores coordinates in Float32Array buffers (largePoints, largeDataIndices) rather than full ZRender elements, reducing memory usage by orders of magnitude.
  • Progressive rendering: The progressive option chunks rendering across frames when combined with large-mode, preventing UI freezing during initial paint of 100,000+ points.
  • Dynamic adaptation: When datasets grow across threshold boundaries, createRenderPlanner.ts automatically triggers a series reset to rebuild the typed-array layout without manual intervention.

Frequently Asked Questions

What is the default threshold for activating large-mode in ECharts scatter plots?

The default largeThreshold for scatter series is 2000 points, as defined in src/chart/scatter/ScatterSeries.ts (lines 137‑140). When your dataset exceeds this count and large is not explicitly set to false, ECharts automatically switches to typed-array rendering to maintain performance.

How does large-mode reduce memory usage compared to standard rendering?

Standard rendering creates individual ZRender graphic elements for every data point, each carrying DOM references, style properties, and event handlers. In large-mode, ECharts stores data in compact typed arrays (Float32Array for coordinates, Uint32Array for indices) as implemented in src/core/Scheduler.ts. This reduces per-point memory overhead from hundreds of bytes to approximately 8 bytes per coordinate pair.

Can I use progressive rendering without enabling large-mode?

Yes, but with limitations. The progressive option works independently, but as implemented in src/chart/bar/BarSeries.ts (lines 14‑20), the getProgressive() method returns false when large is disabled unless you explicitly set a progressive value in your options. For optimal performance with datasets exceeding 50,000 points, combining both features is recommended.

Why does my chart flicker or reset when updating data across the large-mode threshold?

This occurs because src/chart/helper/createRenderPlanner.ts (lines 46‑48) detects when the pipelineContext.large flag changes between true and false. To prevent mixed-mode rendering artifacts, the planner returns 'reset', forcing the series to rebuild its internal layout and switch from standard graphic elements to typed-array buffers (or vice versa). To minimize disruption, keep largeThreshold consistent or explicitly set large: true to avoid automatic threshold crossing.

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 →