apache / echarts

Apache ECharts is a powerful, interactive charting and data visualization library for browser
https://echarts.apache.org
Apache License 2.0
60.67k stars 19.62k forks source link

[Bug] ScatterChart brush selection with large amount of data is invalid #17079

Open shiboqingning opened 2 years ago

shiboqingning commented 2 years ago

Version

5.3

Link to Minimal Reproduction

https://echarts.apache.org/examples/zh/editor.html?c=scatter-large

Steps to Reproduce

  1. Create a scatter chart of a large amount of data
  2. Add brush configuration items to scatter chart
  3. Brush selected data

The specific code is as follows:

function genData(len, offset) {
  let arr = new Float32Array(len * 2);
  let off = 0;
  for (let i = 0; i < len; i++) {
    let x = +Math.random() * 10;
    let y =
      +Math.sin(x) -
      x * (len % 2 ? 0.1 : -0.1) * Math.random() +
      (offset || 0) / 10;
    arr[off++] = x;
    arr[off++] = y;
  }
  return arr;
}
const data1 = genData(5e5);
const data2 = genData(5e5, 10);
option = {
  title: {
    text:
      echarts.format.addCommas(data1.length / 2 + data2.length / 2) + ' Points'
  },
  tooltip: {},
  toolbox: {
    left: 'center',
    feature: {
      dataZoom: {}
    }
  },
  legend: {
    orient: 'vertical',
    right: 10
  },
  xAxis: [{}],
  yAxis: [{}],
  dataZoom: [
    {
      type: 'inside',
      xAxisIndex: [0]
    },
    {
      type: 'inside',
      yAxisIndex: [0]
    }
  ],
  animation: false,
  brush: {
    brushLink: 'all',
    toolbox: ['rect', 'polygon' , 'clear'],
    seriesIndex: 'all',
    xAxisIndex: 0,
    inBrush: {
      opacity: 1
    },
    throttleType: 'debounce',
    throttleDelay: 1000
  },
  series: [
    {
      name: 'A',
      type: 'scatter',
      data: data1,
      dimensions: ['x', 'y'],
      symbolSize: 3,
      itemStyle: {
        opacity: 0.4
      },
      large: true,
    },
    {
      name: 'B',
      type: 'scatter',
      data: data2,
      dimensions: ['x', 'y'],
      symbolSize: 3,
      itemStyle: {
        opacity: 0.4
      },
      large: true
    }
  ]
};

The renderings are as follows: image

Current Behavior

Does not get the value of the brush selection and has no visual effects

Expected Behavior

Can get the brush selected data and have a visual effect

Environment

- OS:
- Browser:
- Framework:

Any additional comments?

No response

FPI38 commented 2 years ago

Same issue here. Is there anybody on this ? :)

shiboqingning commented 2 years ago

Yes, they are the same, because no reply, thought not to be seen

MatthiasMert commented 1 year ago

Brush selection doesnt seem to work with large data optimization which is enabled for both series (large: true). If large data optimization is disabled, the selection works as intended, though very slow compared to the zoom.

I would appreciate getting a selection tool that either works with large data optimization or is better optimized to begin with.

Here is the working code:

function genData(len, offset) {
  let arr = new Float32Array(len * 2);
  let off = 0;
  for (let i = 0; i < len; i++) {
    let x = +Math.random() * 10;
    let y =
      +Math.sin(x) -
      x * (len % 2 ? 0.1 : -0.1) * Math.random() +
      (offset || 0) / 10;
    arr[off++] = x;
    arr[off++] = y;
  }
  return arr;
}
const data1 = genData(5e5);
const data2 = genData(5e5, 10);
option = {
  title: {
    text:
      echarts.format.addCommas(data1.length / 2 + data2.length / 2) + ' Points'
  },
  tooltip: {},
  toolbox: {
    left: 'center',
    feature: {
      dataZoom: {}
    }
  },
  legend: {
    orient: 'vertical',
    right: 10
  },
  xAxis: [{}],
  yAxis: [{}],
  dataZoom: [
    {
      type: 'inside'
    },
    {
      type: 'slider'
    }
  ],
  animation: false,
  brush: {
    toolbox: ['rect', 'polygon', 'keep', 'clear'],
  },
  series: [
    {
      name: 'A',
      type: 'scatter',
      data: data1,
      dimensions: ['x', 'y'],
      symbolSize: 3,
      itemStyle: {
        opacity: 0.4
      },
    },
    {
      name: 'B',
      type: 'scatter',
      data: data2,
      dimensions: ['x', 'y'],
      symbolSize: 3,
      itemStyle: {
        opacity: 0.4
      },
    }
  ]
};
shiboqingning commented 1 year ago

But the display of large amounts of data would be very slow

MatthiasMert commented 1 year ago

Are you planning to improve the brush/selection tool in the near future?

shiboqingning commented 1 year ago

I hope to solve this problem, some business sometimes need to use, it is best to add circular box selection, triangle box selection and so on.

MatthiasMert commented 1 year ago

I think the brush is slower than zoom, because the points in the selected region have to be rerendered/highlighted on every mousemove while the selection box is drawn, whereas the zoom is only calculated once on brushEnd. I would suggest to implement a brush behaviour similar to zoom if the largeThreshold is exceeded. So it would be still useable & performant with large scale data.

Should I open a new feature request for that?

shiboqingning commented 1 year ago

I think it is OK, depending on how much work you do. My business is mainly used for the Mapping of the wafer.

stephweiss commented 1 year ago

I have the same problem here

MatthiasMert commented 1 year ago

The following small change in the source code seems to work: In BrushView.ts exchange the _onBrush method:

// original code from BrushView.ts
private _onBrush(eventParam: BrushControllerEvents['brush']): void {
    const modelId = this.model.id;

    const areas = this.model.brushTargetManager.setOutputRanges(eventParam.areas, this.ecModel);

    // Action is not dispatched on drag end, because the drag end
    // emits the same params with the last drag move event, and
    // may have some delay when using touch pad, which makes
    // animation not smooth (when using debounce).
    (!eventParam.isEnd || eventParam.removeOnClick) && this.api.dispatchAction({
        type: 'brush',
        brushId: modelId,
        areas: zrUtil.clone(areas),
        $from: modelId
    });
    eventParam.isEnd && this.api.dispatchAction({
        type: 'brushEnd',
        brushId: modelId,
        areas: zrUtil.clone(areas),
        $from: modelId
    });
}

by the following code:

// changed code
private _onBrush(eventParam: BrushControllerEvents['brush']): void {
    const throttleType = this.model.option.throttleType;
    if (throttleType === 'debounce' && !eventParam.isEnd) {
        return;
    }

    const modelId = this.model.id;

    const areas = this.model.brushTargetManager.setOutputRanges(eventParam.areas, this.ecModel);

    // Action is not dispatched on drag end, because the drag end
    // emits the same params with the last drag move event, and
    // may have some delay when using touch pad, which makes
    // animation not smooth (when using debounce).
    (throttleType === 'debounce' || !eventParam.isEnd || eventParam.removeOnClick) && this.api.dispatchAction({
        type: 'brush',
        brushId: modelId,
        areas: zrUtil.clone(areas),
        $from: modelId
    });
    eventParam.isEnd && this.api.dispatchAction({
        type: 'brushEnd',
        brushId: modelId,
        areas: zrUtil.clone(areas),
        $from: modelId
    });
}

using the throttleType property (as it is intended if i understand correctly) to enable an "only render on release" mode.

Now use the brush component with debounce mode:

brush: {
    throttleType: 'debounce'
}

And enable large data optimization mode for your series and set the largeThreshold to a value larger than the number of items in your series (dont ask me why but it works this way):

series: [
    {
        type: 'scatter'.
        ...,
        large: true,
        largeThreshold: 99999999,
    },
    ...
]