Configuring Essential ECharts Features with Interactive Controls

An effective chart implementation often bundles standard configuration options such as legends, axes, toolboxes, and interactive controls. The following example demonstrates how to set up these features in ECharts, modularize the logic, and wire up UI-driven updates.

A complete ECharts opsion object lives in its own module. Notice how the legend handles overflow with a scrollable vertical panel, and how a custom toolbox button inserts custom behavior.

const chartConfig = {
  title: {
    text: 'Sample Inventory Dashboard',
    textStyle: { color: '#444', fontSize: 16 },
    left: 'center',
    show: true
  },
  grid: {
    left: '4%',
    right: '12%',
    bottom: '16%',
    containLabel: true
  },
  tooltip: {
    trigger: 'axis',
    axisPointer: { type: 'shadow' }
  },
  legend: {
    data: [
      'Shipped', 'Received', 'Sold',
      'Trial A', 'Trial B', 'Trial C', 'Trial D', 'Trial E',
      'Retest A', 'Retest B', 'Retest C', 'Retest D',
      'Backup A', 'Backup B', 'Backup C', 'Backup D'
    ],
    selected: {
      'Shipped': true, 'Received': true, 'Sold': true,
      'Trial A': false, 'Trial B': false, 'Trial C': false,
      'Trial D': false, 'Trial E': true,
      'Retest A': false, 'Retest B': true, 'Retest C': false, 'Retest D': false,
      'Backup A': false, 'Backup B': false, 'Backup C': false, 'Backup D': false
    },
    type: 'scroll',
    orient: 'vertical',
    right: 20,
    top: 55,
    backgroundColor: '#f5f5f5',
    padding: 8
  },
  toolbox: {
    feature: {
      myCustomAction: {
        show: true,
        title: 'Trigger Alert',
        icon: 'image://https://example.com/icon.png',
        onclick () {
          alert('Custom toolbox button activated!');
        }
      },
      dataView: {
        readOnly: true,
        optionToContent (opt) {
          const categories = opt.xAxis[0].data;
          const datasets = opt.series;
          let html = '| Series / Month | ${cat} |
|---|---|
| ${set.name} | ${val} |';
          return html;
        }
      },
      magicType: {
        type: ['line', 'bar'],
        title: { line: 'Line', bar: 'Bar' }
      },
      restore: {},
      saveAsImage: {}
    }
  },
  xAxis: {
    name: '(Month)',
    type: 'category',
    axisLabel: { rotate: 35 },
    data: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
  },
  yAxis: {
    name: '(Units)',
    nameLocation: 'middle',
    nameGap: 55,
    nameRotate: 90,
    type: 'value',
    axisLine: { show: true, lineStyle: { type: 'solid' } },
    splitLine: { show: true, lineStyle: { color: '#ccc', type: 'solid' } }
  },
  dataZoom: [{ type: 'slider', start: 0, end: 50, bottom: 0, show: true }],
  series: [
    { name: 'Shipped', type: 'bar', data: [820, 932, 901, 934, 1290, 1330, 1320, 901, 934, 1290, 1330, 1320], label: { show: true } },
    { name: 'Received', type: 'line', data: [820, 932, 901, 934, 1290, 1330, 1320, 901, 934, 1290, 1330, 1320], label: { show: true } },
    { name: 'Sold', type: 'bar', data: [820, 932, 901, 934, 1290, 1330, 1320, 901, 934, 1290, 1330, 1320], label: { show: true } },
    { name: 'Trial A', type: 'bar', data: [820, 932, 901, 934, 1290, 1330, 1320, 901, 934, 1290, 1330, 1320], label: { show: true } },
    { name: 'Trial B', type: 'bar', data: [820, 932, 901, 934, 1290, 1330, 1320, 901, 934, 1290, 1330, 1320], label: { show: true } },
    { name: 'Trial C', type: 'bar', data: [820, 932, 901, 934, 1290, 1330, 1320, 901, 934, 1290, 1330, 1320], label: { show: true } },
    { name: 'Trial D', type: 'bar', data: [820, 932, 901, 934, 1290, 1330, 1320, 901, 934, 1290, 1330, 1320], label: { show: true } },
    { name: 'Trial E', type: 'bar', data: [820, 932, 901, 934, 1290, 1330, 1320, 901, 934, 1290, 1330, 1320], label: { show: true } },
    { name: 'Retest A', type: 'bar', data: [820, 932, 901, 934, 1290, 1330, 1320, 901, 934, 1290, 1330, 1320], label: { show: true } },
    { name: 'Retest B', type: 'bar', data: [820, 932, 901, 934, 1290, 1330, 1320, 901, 934, 1290, 1330, 1320], label: { show: true } },
    { name: 'Retest C', type: 'bar', data: [820, 932, 901, 934, 1290, 1330, 1320, 901, 934, 1290, 1330, 1320], label: { show: true } },
    { name: 'Retest D', type: 'bar', data: [820, 932, 901, 934, 1290, 1330, 1320, 901, 934, 1290, 1330, 1320], label: { show: true } },
    { name: 'Backup A', type: 'bar', data: [820, 932, 901, 934, 1290, 1330, 1320, 901, 934, 1290, 1330, 1320], label: { show: true } },
    { name: 'Backup B', type: 'bar', data: [820, 932, 901, 934, 1290, 1330, 1320, 901, 934, 1290, 1330, 1320], label: { show: true } },
    { name: 'Backup C', type: 'bar', data: [820, 932, 901, 934, 1290, 1330, 1320, 901, 934, 1290, 1330, 1320], label: { show: true } },
    { name: 'Backup D', type: 'bar', data: [820, 932, 901, 934, 1290, 1330, 1320, 901, 934, 1290, 1330, 1320], label: { show: true } }
  ]
};

export default chartConfig;

A dedicated manager class wraps the ECharts instance and exposes methods for dynamic updates. This keeps mutation logic isolated from the main application file.

export default class ChartManager {
  constructor (configuration, instance) {
    this.config = configuration;
    this.chart = instance;
  }

  render () {
    this.chart.setOption(this.config, true);
  }

  setXAxisLabelRotation (degrees) {
    this.config.xAxis.axisLabel.rotate = Number(degrees) || 0;
    this.render();
  }

  setLabelPosition (placement) {
    const updatedSeries = this.config.series.map(item => ({
      ...item,
      label: { ...item.label, position: placement }
    }));
    this.config.series = updatedSeries;
    this.render();
  }

  toggleLabelVisibility (visible) {
    const updatedSeries = this.config.series.map(item => ({
      ...item,
      label: { ...item.label, show: !!visible }
    }));
    this.config.series = updatedSeries;
    this.render();
  }

  appendPercentSuffix (enable) {
    const updatedSeries = this.config.series.map(item => ({
      ...item,
      label: { ...item.label, formatter: enable ? '{c}%' : '{c}' }
    }));
    this.config.series = updatedSeries;
    this.render();
  }

  setLabelRotation (degrees) {
    const updatedSeries = this.config.series.map(item => ({
      ...item,
      label: { ...item.label, rotate: Number(degrees) || 0 }
    }));
    this.config.series = updatedSeries;
    this.render();
  }

  toggleDataZoom (visible) {
    if (visible) {
      this.config.dataZoom[0].show = true;
      this.config.dataZoom[0].end = 50;
    } else {
      this.config.dataZoom[0].show = false;
      this.config.dataZoom[0].end = 100;
    }
    this.render();
  }

  setTooltipPointer (mode) {
    this.config.tooltip.axisPointer.type = mode;
    this.render();
  }

  setYAxisLine (style) {
    if (style === 'none') {
      this.config.yAxis.axisLine.show = false;
    } else {
      this.config.yAxis.axisLine.show = true;
      this.config.yAxis.axisLine.lineStyle.type = style;
    }
    this.render();
  }

  setYSplitLine (style) {
    if (style === 'none') {
      this.config.yAxis.splitLine.show = false;
    } else {
      this.config.yAxis.splitLine.show = true;
      this.config.yAxis.splitLine.lineStyle.type = style;
    }
    this.render();
  }

  registerEvent (event, handler) {
    this.chart.on(event, handler);
  }

  handleResize () {
    this.chart.resize();
  }
}

The entry point initializes the chart, wires dropdown selectors to manager methods, and attaches global listeners for resize and click feedback.

import echarts from 'echarts';
import chartConfig from './chartConfig';
import ChartManager from './ChartManager';

const getById = id => document.getElementById(id);

const chartDom = getById('echartsContainer');
const manager = new ChartManager(chartConfig, echarts.init(chartDom));
manager.render();

getById('visibilitySelect').addEventListener('change', e => {
  manager.toggleLabelVisibility(e.target.value === '1');
});

getById('positionSelect').addEventListener('change', e => {
  manager.setLabelPosition(e.target.value);
});

getById('percentSelect').addEventListener('change', e => {
  manager.appendPercentSuffix(e.target.value === '1');
});

getById('rotationSelect').addEventListener('change', e => {
  manager.setXAxisLabelRotation(e.target.value);
});

getById('zoomSelect').addEventListener('change', e => {
  manager.toggleDataZoom(e.target.value === '1');
});

getById('tooltipSelect').addEventListener('change', e => {
  manager.setTooltipPointer(e.target.value);
});

getById('yAxisLineSelect').addEventListener('change', e => {
  manager.setYAxisLine(e.target.value);
});

getById('labelRotateSelect').addEventListener('change', e => {
  manager.setLabelRotation(e.target.value);
});

getById('splitLineSelect').addEventListener('change', e => {
  manager.setYSplitLine(e.target.value);
});

manager.registerEvent('click', params => {
  alert(`${params.name} - ${params.seriesName}: ${params.data}`);
});

window.addEventListener('resize', () => {
  manager.handleResize();
});

The HTML file imports the bundle produced by a module bundler and provides the control panel.


<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>ECharts Interactive Demo</title>
  <style>
    #echartsContainer {
      width: 100%;
      height: 650px;
      margin-top: 2rem;
    }
    .controls {
      margin-top: 2rem;
      padding: 20px;
      background: #f8f8f8;
      border: 1px solid #4a90e2;
    }
    select {
      padding: 8px;
      width: 200px;
      margin: 6px 12px;
    }
    .chart-table {
      width: 100%;
      border-collapse: collapse;
    }
    .chart-table th, .chart-table td {
      border: 1px solid #ddd;
      text-align: center;
      padding: 8px 4px;
    }
    .chart-table thead {
      background: #e9e9e9;
    }
  </style>
</head>
<body>
  <div id="echartsContainer"></div>
  <div class="controls">
    <select id="visibilitySelect">
      <option value="1">Show Labels</option>
      <option value="0">Hide Labels</option>
    </select>
    <select id="positionSelect">
      <option value="inside">Inside Center</option>
      <option value="insideBottom">Inside Bottom</option>
      <option value="top">Top</option>
    </select>
    <select id="percentSelect">
      <option value="0">Plain Value</option>
      <option value="1">Suffix `%`</option>
    </select>
    <select id="rotationSelect">
      <option value="0">X Label 0°</option>
      <option value="30" selected>X Label 30°</option>
      <option value="60">X Label 60°</option>
      <option value="90">X Label 90°</option>
    </select>
    <select id="zoomSelect">
      <option value="1">Show Slider</option>
      <option value="0">Hide Slider</option>
    </select>
    <select id="tooltipSelect">
      <option value="shadow">Shadow Pointer</option>
      <option value="line">Line Pointer</option>
      <option value="cross">Cross Pointer</option>
    </select>
    <select id="yAxisLineSelect">
      <option value="none">Hide Y Line</option>
      <option value="solid" selected>Y Solid Line</option>
      <option value="dashed">Y Dashed</option>
      <option value="dotted">Y Dotted</option>
    </select>
    <select id="labelRotateSelect">
      <option value="0">Label 0°</option>
      <option value="30">Label 30°</option>
      <option value="60">Label 60°</option>
      <option value="90">Label 90°</option>
    </select>
    <select id="splitLineSelect">
      <option value="none">Hide Split Line</option>
      <option value="solid" selected>Solid Split</option>
      <option value="dashed">Dashed Split</option>
      <option value="dotted">Dotted Split</option>
    </select>
  </div>
  <script src="bundle.js"></script>
</body>
</html>

The toolbox’s data view functionality overrides the default optionToContent to render a styled HTML table, providing a cleaner representation of the chart’s underlying dataset.

Tags: echarts javascript data-visualization configuration interactive

Posted on Sat, 22 Aug 2026 16:39:51 +0000 by maxholman