Skip to content

Commit 3bc1ccb

Browse files
authored
perf: 复用行列头/数值单元格 以提升百万滚动性能至50FPS (#3222)
* perf: 优化100万数据下的滚动性能 * perf: 优化pivot-chart的滚动性能 * perf: 优化100万数据下的PivotChartDataCell滚动性能 * fix: 媒体渲染场景等异步场景不能复用单元格 * perf: 列头行头滚动性能优化 * perf: 列头行头滚动性能优化 * perf: 字段标记icon情况下优化渲染 * perf: 优化icon的位置渲染 * chore: 更新依赖版本 * perf: 使用g的新版优化性能 * test: 更新快照 * test: 导出超时 * test: 修复测试用例 * test: 修复测试用例 * perf: 使用性能更好的pop * perf: 提供开关 * perf: 百万滚动场景关闭showDefaultHeaderActionIcon * perf: updateGrid过程中复用line * perf: updateGrid过程中复用line * perf: 设置ClipPath时,不再重新new Rect,而是直接修改已有rect的style * refactor: 轻微重构相同函数 * fix: 删除错误参数 * perf: 允许用户自定义G的渲染器参数 * refactor: 轻微重构 * refactor: 轻微重构 * fix: 增加兜底分支 * perf: 提升getBackgroundColor性能 * refactor: 完善类型 * perf: 批量设置样式 * perf: 适配新版写法 * perf: 适配新版写法 * refactor: 接受CR意见 * chore: 使用beta版的G * Revert "chore: 使用beta版的G" This reverts commit 24cdae0. * chore: 使用G的新版本 * chore: 增大limit * refactor: 增加experimentalReuseCell文档 * chore: 修复CodeQL CI问题
1 parent 0855b04 commit 3bc1ccb

27 files changed

Lines changed: 638 additions & 242 deletions

File tree

.github/workflows/codeql-analysis.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ jobs:
4343
steps:
4444
- name: Checkout repository
4545
uses: actions/checkout@v4
46+
with:
47+
fetch-depth: 0
4648

4749
# Initializes the CodeQL tools for scanning.
4850
- name: Initialize CodeQL

packages/s2-core/__tests__/unit/utils/g-mini-charts-spec.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,7 @@ import {
1616
scale,
1717
transformRatioToPercent,
1818
} from '@/utils/g-mini-charts';
19-
import type { IElement } from '@antv/g-lite';
20-
import { forEach, last, map } from 'lodash';
19+
import { forEach, map } from 'lodash';
2120
import { data } from 'tests/data/mock-dataset.json';
2221
import { assembleDataCfg, assembleOptions } from 'tests/util';
2322
import { createPivotSheet, getContainer } from 'tests/util/helpers';
@@ -411,7 +410,7 @@ describe('Render Chart Shape Tests', () => {
411410
cell,
412411
);
413412

414-
const text = last(cell.getChildren()) as IElement;
413+
const text = cell.children.find((child) => child.style.text);
415414

416415
expect(text.attr('text')).toEqual('10.00%');
417416
});

packages/s2-core/package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,9 @@
7474
},
7575
"dependencies": {
7676
"@antv/event-emitter": "^0.1.3",
77-
"@antv/g": "^6.1.28",
78-
"@antv/g-canvas": "^2.0.48",
79-
"@antv/g-lite": "^2.3.2",
77+
"@antv/g": "^6.3.1",
78+
"@antv/g-canvas": "^2.2.0",
79+
"@antv/g-lite": "^2.7.0",
8080
"@antv/vendor": "^1.0.11",
8181
"decimal.js": "^10.5.0",
8282
"flru": "^1.0.2",
@@ -104,7 +104,7 @@
104104
{
105105
"path": "./dist/s2.min.js",
106106
"import": "{ createComponent }",
107-
"limit": "240 kB"
107+
"limit": "300 kB"
108108
},
109109
{
110110
"path": "./dist/s2.min.css",

packages/s2-core/src/cell/base-cell.ts

Lines changed: 80 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import type {
88
Text,
99
TextStyleProps,
1010
} from '@antv/g';
11-
import { Group } from '@antv/g';
11+
import { Group, RectStyleProps } from '@antv/g';
1212
import {
1313
each,
1414
get,
@@ -78,6 +78,7 @@ import {
7878
renderText,
7979
updateShapeAttr,
8080
} from '../utils/g-renders';
81+
import { batchSetStyle } from '../utils/g-utils';
8182
import { isLinkFieldNode } from '../utils/interaction/link-field';
8283
import { isMobile } from '../utils/is-mobile';
8384
import {
@@ -125,6 +126,8 @@ export abstract class BaseCell<T extends SimpleBBox> extends Group {
125126
// interactive control shapes, unify read and manipulate operations
126127
protected stateShapes = new Map<StateShapeLayer, DisplayObject>();
127128

129+
protected borders: Map<keyof typeof CellBorderPosition, Line> = new Map();
130+
128131
/* -------------------------------------------------------------------------- */
129132
/* abstract functions that must be implemented by subtype */
130133
/* -------------------------------------------------------------------------- */
@@ -414,47 +417,70 @@ export abstract class BaseCell<T extends SimpleBBox> extends Group {
414417
this.getStyle()?.cell!,
415418
);
416419

417-
renderLine(this, { ...position, ...style });
420+
const borderStyle = { ...position, ...style };
421+
422+
if (this.borders.has(type)) {
423+
batchSetStyle(this.borders.get(type)!, borderStyle);
424+
} else {
425+
this.borders.set(type, renderLine(this, borderStyle));
426+
}
418427
});
419428
}
420429

421430
/**
422431
* 绘制 hover 悬停,刷选的外框
423432
*/
424433
protected drawInteractiveBorderShape() {
425-
this.stateShapes.set(
434+
const style = {
435+
...this.getBBoxByType(CellClipBox.PADDING_BOX),
436+
visibility: 'hidden',
437+
pointerEvents: 'none',
438+
} as RectStyleProps;
439+
440+
const interactiveBorderShape = this.stateShapes.get(
426441
'interactiveBorderShape',
427-
renderRect(this, {
428-
...this.getBBoxByType(CellClipBox.PADDING_BOX),
429-
visibility: 'hidden',
430-
pointerEvents: 'none',
431-
}),
432442
);
443+
444+
if (interactiveBorderShape) {
445+
batchSetStyle(interactiveBorderShape, style);
446+
} else {
447+
this.stateShapes.set('interactiveBorderShape', renderRect(this, style));
448+
}
433449
}
434450

435451
/**
436452
* 交互使用的背景色
437453
*/
438454
protected drawInteractiveBgShape() {
439-
this.stateShapes.set(
440-
'interactiveBgShape',
441-
renderRect(this, {
442-
...this.getBBoxByType(),
443-
visibility: 'hidden',
444-
pointerEvents: 'none',
445-
}),
446-
);
455+
const style = {
456+
...this.getBBoxByType(),
457+
visibility: 'hidden',
458+
pointerEvents: 'none',
459+
} as RectStyleProps;
460+
461+
const reuseInteractiveBgShape = this.stateShapes.get('interactiveBgShape');
462+
463+
if (reuseInteractiveBgShape) {
464+
batchSetStyle(reuseInteractiveBgShape, style);
465+
} else {
466+
this.stateShapes.set('interactiveBgShape', renderRect(this, style));
467+
}
447468
}
448469

449470
protected drawBackgroundShape() {
450471
const { backgroundColor, backgroundColorOpacity } =
451472
this.getBackgroundColor();
452-
453-
this.backgroundShape = renderRect(this, {
473+
const style = {
454474
...this.getBBoxByType(),
455475
fill: backgroundColor,
456476
fillOpacity: backgroundColorOpacity,
457-
});
477+
};
478+
479+
if (this.backgroundShape) {
480+
batchSetStyle(this.backgroundShape, style);
481+
} else {
482+
this.backgroundShape = renderRect(this, style);
483+
}
458484
}
459485

460486
public renderTextShape(
@@ -464,15 +490,23 @@ export abstract class BaseCell<T extends SimpleBBox> extends Group {
464490
const text = getDisplayText(style.text, this.getEmptyPlaceholder());
465491
const shallowRender = options?.shallowRender || this.isShallowRender();
466492

467-
this.textShape = renderText({
468-
group: this,
469-
textShape: shallowRender ? undefined : this.textShape,
470-
style: {
493+
if (this.textShape && !shallowRender) {
494+
batchSetStyle(this.textShape, {
471495
...style,
472496
// 文本必须为字符串
473497
text: `${text}`,
474-
},
475-
});
498+
});
499+
} else {
500+
this.textShape = renderText({
501+
group: this,
502+
textShape: shallowRender ? undefined : this.textShape,
503+
style: {
504+
...style,
505+
// 文本必须为字符串
506+
text: `${text}`,
507+
},
508+
});
509+
}
476510

477511
this.addTextShape(this.textShape);
478512

@@ -560,16 +594,21 @@ export abstract class BaseCell<T extends SimpleBBox> extends Group {
560594
}
561595

562596
const { bottom: maxY } = this.textShape.getBBox();
563-
564-
this.linkFieldShape = renderLine(this, {
597+
const options = {
565598
x1: startX,
566599
y1: maxY + 1,
567600
// 不用 bbox 的 maxX,因为 g-base 文字宽度预估偏差较大
568601
x2: startX + actualTextWidth,
569602
y2: maxY + 1,
570603
stroke: linkFillColor,
571604
lineWidth: 1,
572-
});
605+
};
606+
607+
if (this.linkFieldShape) {
608+
batchSetStyle(this.linkFieldShape, options);
609+
} else {
610+
this.linkFieldShape = renderLine(this, options);
611+
}
573612
}
574613

575614
this.textShape.style.fill = linkFillColor;
@@ -727,13 +766,20 @@ export abstract class BaseCell<T extends SimpleBBox> extends Group {
727766
const position = this.getIconPosition();
728767
const { size } = this.getStyle()!.icon!;
729768

730-
this.conditionIconShape = renderIcon(this, {
769+
const iconCfg = {
731770
...position,
732771
name: attrs?.name!,
733772
width: size,
734773
height: size,
735774
fill: attrs?.fill,
736-
});
775+
};
776+
777+
if (this.conditionIconShape) {
778+
this.conditionIconShape.reRender(iconCfg);
779+
} else {
780+
this.conditionIconShape = renderIcon(this, iconCfg);
781+
}
782+
737783
this.addConditionIconShape(this.conditionIconShape);
738784
}
739785
}
@@ -890,4 +936,8 @@ export abstract class BaseCell<T extends SimpleBBox> extends Group {
890936
(m) => m.field === this.getMetaField(),
891937
)?.renderer;
892938
}
939+
940+
public getConditionIntervalShape() {
941+
return this.conditionIntervalShape;
942+
}
893943
}

packages/s2-core/src/cell/col-cell.ts

Lines changed: 50 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import {
3232
} from '../utils/cell/cell';
3333
import { adjustTextIconPositionWhileScrolling } from '../utils/cell/text-scrolling';
3434
import { renderIcon, renderLine } from '../utils/g-renders';
35+
import { batchSetStyle } from '../utils/g-utils';
3536
import {
3637
getHiddenColumnContinuousSiblingNodes,
3738
isEqualDisplaySiblingNodeId,
@@ -46,6 +47,10 @@ import { normalizeTextAlign } from '../utils/normalize';
4647
import { HeaderCell } from './header-cell';
4748

4849
export class ColCell extends HeaderCell<ColHeaderConfig> {
50+
private verticalResizeArea: CustomRect;
51+
52+
private horizontalResizeArea: CustomRect;
53+
4954
public get cellType() {
5055
return CellType.COL_CELL;
5156
}
@@ -311,20 +316,28 @@ export class ColCell extends HeaderCell<ColHeaderConfig> {
311316
cell: this,
312317
});
313318

314-
resizeArea.appendChild(
315-
new CustomRect(
316-
{
317-
name: resizeAreaName,
318-
style: {
319-
...attrs.style,
320-
x: 0,
321-
y: offsetY + height - resizeStyle.size!,
322-
width: resizeAreaWidth,
319+
const style = {
320+
...attrs.style,
321+
x: 0,
322+
y: offsetY + height - resizeStyle.size!,
323+
width: resizeAreaWidth,
324+
};
325+
326+
if (this.horizontalResizeArea) {
327+
this.horizontalResizeArea.name = resizeAreaName;
328+
this.horizontalResizeArea.appendInfo = attrs.appendInfo;
329+
batchSetStyle(this.horizontalResizeArea, style);
330+
} else {
331+
this.horizontalResizeArea = resizeArea.appendChild(
332+
new CustomRect(
333+
{
334+
name: resizeAreaName,
335+
style,
323336
},
324-
},
325-
attrs.appendInfo,
326-
),
327-
);
337+
attrs.appendInfo,
338+
),
339+
);
340+
}
328341
}
329342

330343
private getResizeAreaWidth() {
@@ -465,19 +478,26 @@ export class ColCell extends HeaderCell<ColHeaderConfig> {
465478
cell: this,
466479
});
467480

468-
resizeArea.appendChild(
469-
new CustomRect(
470-
{
471-
style: {
472-
...attrs.style,
473-
x: offsetX + width - resizeStyle.size!,
474-
y: offsetY,
475-
height,
481+
const style = {
482+
...attrs.style,
483+
x: offsetX + width - resizeStyle.size!,
484+
y: offsetY,
485+
height,
486+
};
487+
488+
if (this.verticalResizeArea) {
489+
this.verticalResizeArea.appendInfo = attrs.appendInfo;
490+
batchSetStyle(this.verticalResizeArea, style);
491+
} else {
492+
this.verticalResizeArea = resizeArea.appendChild(
493+
new CustomRect(
494+
{
495+
style,
476496
},
477-
},
478-
attrs.appendInfo,
479-
),
480-
);
497+
attrs.appendInfo,
498+
),
499+
);
500+
}
481501
}
482502

483503
// 绘制热区
@@ -642,4 +662,9 @@ export class ColCell extends HeaderCell<ColHeaderConfig> {
642662
this.spreadsheet.isValueInCols()
643663
);
644664
}
665+
666+
public setHeaderConfig(headerConfig: ColHeaderConfig) {
667+
super.setHeaderConfig(headerConfig);
668+
// this.drawResizeArea();
669+
}
645670
}

0 commit comments

Comments
 (0)