Search code examples
javascriptecharts

How to maintain original data while using dataZoom in ECharts?


I'm working on a project using ECharts to visualize a large dataset. I want to display the data as a percentage change from the base value and allow users to zoom in and out. However, I'm facing issues with maintaining the original data when zooming out. The chart only shows the filtered data, and I can't zoom out to see the full range of data.

import * as echarts from 'echarts';

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

let base = +new Date(1988, 9, 3);
let oneDay = 24 * 3600 * 1000;
let data = [[base, Math.random() * 300]];
for (let i = 1; i < 20000; i++) {
  let now = new Date((base += oneDay));
  data.push([+now, Math.round((Math.random() - 0.5) * 20 + data[i - 1][1])]);
}

function calculatePercentage(data) {
  const baseValue = data[0][1];
  return data.map(([date, value]) => [date, (((value - baseValue) / baseValue) * 100).toFixed(2)]);
}

const originalData = calculatePercentage(data);

export function showData() {
  const option = {
    tooltip: {
      trigger: 'axis',
      position: function (pt) {
        return [pt[0], '10%'];
      }
    },
    title: {
      left: 'center',
      text: 'Large Area Chart'
    },
    toolbox: {
      feature: {
        dataZoom: {
          yAxisIndex: 'none'
        },
        restore: {},
        saveAsImage: {}
      }
    },
    xAxis: {
      type: 'time',
      boundaryGap: false
    },
    yAxis: {
      type: 'value',
      boundaryGap: [0, '100%']
    },
    dataZoom: [
      {
        type: 'inside',
        start: 0,
        end: 100,
        rangeMode: ['value', 'value']
      },
      {
        start: 0,
        end: 100,
        rangeMode: ['value', 'value']
      }
    ],
    series: [
      {
        name: 'Fake Data',
        type: 'line',
        smooth: true,
        symbol: 'none',
        areaStyle: {},
        data: originalData
      }
    ]
  };

  myChart.setOption(option);

  myChart.on('dataZoom', function (params) {
    const dataZoom = myChart.getOption().dataZoom[0];
    const startValue = dataZoom.startValue;
    const endValue = dataZoom.endValue;

    const startTimestamp = new Date(startValue).getTime();
    const endTimestamp = new Date(endValue).getTime();

    const filteredData = data.filter(([date]) => {
      const dateValue = new Date(date).getTime();
      return dateValue >= startTimestamp && dateValue <= endTimestamp;
    });

    const percentageData = calculatePercentage(filteredData);

    myChart.setOption({
      series: [{
        data: percentageData
      }]
    });
  });
}

// Initialize with 1 month data
showData();

A lot of different things.


Solution

  • You should first set the min and max of the x axis to the absolute values of your data, so the system "knows" what are the non-zoomed limits:

    xAxis: {
       type: 'time',
       boundaryGap: false,
       min: data[0][0],
       max: data[data.length-1][0],
    },
    

    Don't set min and max in the dataZoom; since you set rangeModes to value you should use minValue and maxValue, but that can be done in the zoom callback:

    myChart.setOption({
       series: [{
          data: percentageData
       }],
       dataZoom:[{
          type: 'inside',
          startValue: percentageData[0][0],
          endValue: percentageData[percentageData.length-1][0],
          rangeMode: ['value', 'value']
       },
          {
          type: 'slider',
          startValue: percentageData[0][0],
          endValue: percentageData[percentageData.length-1][0],
          rangeMode: ['value', 'value']
       }]
    });
    

    All code in a stack snippet:

    const chartDom = document.getElementById('main');
    const myChart = echarts.init(chartDom);
    
    let base = +new Date(1988, 9, 3);
    let oneDay = 24 * 3600 * 1000;
    let data = [[base, Math.random() * 300]];
    for (let i = 1; i < 20000; i++) {
       let now = new Date((base += oneDay));
       data.push([+now, Math.round((Math.random() - 0.5) * 20 + data[i - 1][1])]);
    }
    
    function calculatePercentage(data) {
       const baseValue = data[0][1];
       return data.map(([date, value]) => [date, (((value - baseValue) / baseValue) * 100).toFixed(2)]);
    }
    
    const originalData = calculatePercentage(data);
    
    function showData() {
       const option = {
          tooltip: {
             trigger: 'axis',
             position: function (pt) {
                return [pt[0], '10%'];
             }
          },
          title: {
             left: 'center',
             text: 'Large Area Chart'
          },
          toolbox: {
             feature: {
                dataZoom: {
                   yAxisIndex: 'none'
                },
                restore: {},
                saveAsImage: {}
             }
          },
          xAxis: {
             type: 'time',
             boundaryGap: false,
             min: data[0][0],
             max: data[data.length-1][0],
          },
          yAxis: {
             type: 'value',
             boundaryGap: [0, '100%']
          },
          dataZoom: [
             {
                type: 'inside',
                rangeMode: ['value', 'value']
             },
             {
                type: 'slider',
                rangeMode: ['value', 'value']
             }
          ],
          series: [
             {
                name: 'Fake Data',
                type: 'line',
                smooth: true,
                symbol: 'none',
                areaStyle: {},
                data: originalData
             }
          ]
       };
    
       myChart.setOption(option);
    
       myChart.on('dataZoom', function (params) {
          const dataZoom = myChart.getOption().dataZoom[0];
          const startValue = dataZoom.startValue;
          const endValue = dataZoom.endValue;
    
          const startTimestamp = new Date(startValue).getTime();
          const endTimestamp = new Date(endValue).getTime();
    
          const filteredData = data.filter(([date]) => {
             const dateValue = new Date(date).getTime();
             return dateValue >= startTimestamp && dateValue <= endTimestamp;
          });
    
          const percentageData = calculatePercentage(filteredData);
    
    
          myChart.setOption({
             series: [{
                data: percentageData
             }],
             dataZoom:[{
                type: 'inside',
                startValue: percentageData[0][0],
                endValue: percentageData[percentageData.length-1][0],
                rangeMode: ['value', 'value']
             },
                {
                type: 'slider',
                startValue: percentageData[0][0],
                endValue: percentageData[percentageData.length-1][0],
                rangeMode: ['value', 'value']
             }]
          });
       });
    }
    
    showData();
    <div id="main" style="height:300px"></div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/echarts/5.6.0/echarts.common.min.js"></script>