antvis / antvis/G2Plot

🤔 [QUESTION] Line 图在120HZ更新频率下EE事件积压.

Open
#3,871 0 comments 0 reactions 0 assignees View on GitHub
Question
Dominant language
TypeScript
Stars
2.7k
Forks
591
PR merge metrics
No merged PRs in 30d

Description

### 🐛 使用Line图表,以120hz频率更新图表数据,EE事件积压.

G2 的Annotation组件调用 update 方法时,调用onAfterRender方法,会调用ee.once方法,但是好像因为更新频率过快,事件没有触发,造成事件积压,内存上升.

Image

Image

以下代码可以复现:
```
// #hook.ts
import { ref, onUnmounted } from 'vue';
import { Line } from '@antv/g2plot';

console.dir(Line.__proto__.prototype.__proto__);
Line.__proto__.prototype.__proto__.on = function (evt, callback, once) {
// if (evt === 'afterrender') {
// return;
// }
if (!this._events[evt]) {
this._events[evt] = [];
}
this._events[evt].push({
callback,
once: !!once
});
if (evt === 'afterrender') {
if (this._events[evt].length % 1000 == 0) {
console.log(this._events, this);
}
}
return this;
};

// -------------------------
const MAX_DATA_POINTS = 120;
const RENDER_FPS = 30;
const RENDER_INTERVAL = 1000 / RENDER_FPS;

const useWatchChart = (options) => {
const chartContainerRef = ref(null);
let chart = null;
let isMounted = false;
let animationFrameId = null;

let dataBuffer = [];
let pendingDataQueue = [];

// 模拟数据生成器
let mockDataIndex = 0;

function mountChart() {
if (!chartContainerRef.value) return;

isMounted = true;

chart = new Line(chartContainerRef.value, {
data: dataBuffer,
xField: 'index',
yField: 'value',
seriesField: 'type',
color: ['green'],
animation: false,
smooth: false,
tooltip: false,
xAxis: {
min: 0,
max: MAX_DATA_POINTS,
tickCount: 7
},
yAxis: {
min: -0.01,
max: 1
}
});

console.log(chart);

chart.render();
startRenderLoop();
}

function unmountChart() {
isMounted = false;

if (animationFrameId) {
cancelAnimationFrame(animationFrameId);
animationFrameId = null;
}

if (chart) {
chart.destroy();
chart = null;
}

dataBuffer = [];
pendingDataQueue = [];

console.log('Chart unmounted');
}

// 模拟Socket数据接收
function updateDataBuffer() {
if (!isMounted) return;

// 生成模拟数据
const mockData = {
value1: { value: Math.random() },
value2: { value: Math.random() }
};

// 模拟多字段数据推送
['value1', 'value2'].forEach((key) => {
pendingDataQueue.push({
type: key,
value: mockData[key].value,
index: 0
});
});
// // 每处理1000个数据点记录一次
// if (mockDataIndex % 1000 === 0) {
// if (chart) {
// chart.logEvent();
// }
// }
mockDataIndex++;
}

let lastRenderTime = 0;
const renderLoop = (currentTime) => {
if (!isMounted) return;

animationFrameId = requestAnimationFrame(renderLoop);

// 限制帧率
if (currentTime - lastRenderTime < RENDER_INTERVAL) return;
if (pendingDataQueue.length === 0) return;

lastRenderTime = currentTime;

// 消费队列
for (let i = 0; i < pendingDataQueue.length; i++) {
dataBuffer.push(pendingDataQueue[i]);
}
pendingDataQueue.length = 0;

// 限制缓冲区大小
const maxTotalItems = MAX_DATA_POINTS * 2; // 2个字段
if (dataBuffer.length > maxTotalItems) {
const overflow = dataBuffer.length - maxTotalItems;
const deleteCount = Math.ceil(overflow / 2) * 2;
dataBuffer.splice(0, deleteCount);
}

// 更新索引
for (let i = 0; i < dataBuffer.length; i++) {
dataBuffer[i].index = Math.floor(i / 2);
}

// 更新图表
if (chart) {
chart.changeData(dataBuffer);
}
};

function startRenderLoop() {
if (!animationFrameId) {
animationFrameId = requestAnimationFrame(renderLoop);
}
}

// 模拟高频数据推送
function startMockDataStream() {
console.log('Starting mock data stream...');

const intervalId = setInterval(() => {
if (isMounted) {
updateDataBuffer();
}
}, 1000 / 120); // 模拟120Hz数据

return () => {
clearInterval(intervalId);
console.log('Mock data stream stopped');
};
}

onUnmounted(() => {
unmountChart();
});

return {
chartContainerRef,
mountChart,
updateDataBuffer,
unmountChart,
startMockDataStream,
isMounted
};
};

export { useWatchChart };

```

```
// 在Vue组件中使用
import { useWatchChart } from './hooks';

const { mountChart, startMockDataStream, unmountChart, chartContainerRef } =
useWatchChart({});
let stopMockStream = null;

const remove = () => {
if (stopMockStream) stopMockStream();
unmountChart();
};
const start = () => {
mountChart();
// 开始模拟数据流
stopMockStream = startMockDataStream();
};




remove
start

.io-demo-page-wrapper {
height: 100%;
.chartContainer {
width: 600px;
height: 300px;
}
}

```

我不确定是什么问题,但是事件累计造成了内存上升,对于10个图表如上频率更新,持续较长时间,内存上升明显.

Contributor guide

Open the contributing guide

Research direction

Start with the #hook.ts reproduction and trace the Line changeData path into Annotation update, onAfterRender, and ee.once. Confirm whether handlers accumulate during sustained 120Hz input, then verify that long-running updates no longer grow the event list or memory while chart rendering remains functional.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
data-visualization, frontend, performance
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.