Skip to content

Commit

Permalink
Refresh prompt (#490)
Browse files Browse the repository at this point in the history
* feat(bbox): cache bbox

* feat(rename): change canvasBox to canvasBBox

* feat(render): prompt refresh

* feat(group): add group in trigger onCanvasChanged

* feat(clip): clip changed

* feat(matrix-util): update matrix-util and update version

* feat(matrix): replace matrix-util

* feat(refresh): prompt hit and refresh

* chore(version): update version

* docs(comment): add comment

* chore(json): revert package.json version

Co-authored-by: 诸岳 <fuping.dfp@antfin.com>
  • Loading branch information
dxq613 and dengfuping authored May 12, 2020
1 parent eb6a250 commit 2a70a95
Show file tree
Hide file tree
Showing 17 changed files with 442 additions and 82 deletions.
2 changes: 1 addition & 1 deletion packages/g-base/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"coverage-generator": "torch --coverage --compile --source-pattern src/*.js,src/**/*.js --opts tests/mocha.opts",
"coverage-viewer": "torch-coverage",
"test": "torch --renderer --compile --opts tests/mocha.opts",
"test-live": "torch --compile --interactive --opts tests/mocha.opts",
"test-live": "torch --compile --interactive tests/unit",
"tsc": "tsc --noEmit",
"typecheck": "tsc --noEmit"
},
Expand Down
12 changes: 1 addition & 11 deletions packages/g-base/src/abstract/container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,6 @@ import { isFunction, isObject, each, removeFromArray, upperFirst, isAllowCapture
const SHAPE_MAP = {};
const INDEX = '_INDEX';

function afterAdd(element: IElement) {
if (element.isGroup()) {
if ((element as IGroup).isEntityGroup() || element.get('children').length) {
element.onCanvasChange('add');
}
} else {
element.onCanvasChange('add');
}
}

/**
* 设置 canvas
* @param {IElement} element 元素
Expand Down Expand Up @@ -305,7 +295,7 @@ abstract class Container extends Element implements IContainer {
setTimeline(element, timeline);
}
children.push(element);
afterAdd(element);
element.onCanvasChange('add');
this._applyElementMatrix(element);
}

Expand Down
14 changes: 11 additions & 3 deletions packages/g-base/src/abstract/element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,14 @@ abstract class Element extends Base implements IElement {
* @protected
*/
afterAttrsChange(targetAttrs) {
this.onCanvasChange('attr');
if (this.cfg.isClipShape) {
const applyTo = this.cfg.applyTo;
if (applyTo) {
applyTo.onCanvasChange('clip');
}
} else {
this.onCanvasChange('attr');
}
}

show() {
Expand Down Expand Up @@ -294,10 +301,10 @@ abstract class Element extends Base implements IElement {

// 获取总的 matrix
getTotalMatrix() {
let totalMatrix = this.get('totalMatrix');
let totalMatrix = this.cfg.totalMatrix;
if (!totalMatrix) {
const currentMatrix = this.attr('matrix');
const parentMatrix = this.get('parentMatrix');
const parentMatrix = this.cfg.parentMatrix;
if (parentMatrix && currentMatrix) {
totalMatrix = multiplyMatrix(parentMatrix, currentMatrix);
} else {
Expand Down Expand Up @@ -371,6 +378,7 @@ abstract class Element extends Base implements IElement {
clipShape = new Cons({
type: clipCfg.type,
isClipShape: true, // 增加一个标记
applyTo: this,
attrs: clipCfg.attrs,
canvas, // 设置 canvas
});
Expand Down
16 changes: 8 additions & 8 deletions packages/g-base/src/abstract/shape.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ abstract class AbstractShape extends Element implements IShape {
}
// 计算包围盒时,需要缓存,这是一个高频的操作
getBBox(): BBox {
let bbox = this.get('bbox');
let bbox = this.cfg.bbox;
if (!bbox) {
bbox = this.calculateBBox();
this.set('bbox', bbox);
Expand All @@ -33,12 +33,12 @@ abstract class AbstractShape extends Element implements IShape {
}
// 计算相对于画布的包围盒
getCanvasBBox(): BBox {
let canvasBox = this.get('canvasBox');
if (!canvasBox) {
canvasBox = this.calculateCanvasBBox();
this.set('canvasBox', canvasBox);
let canvasBBox = this.cfg.canvasBBox;
if (!canvasBBox) {
canvasBBox = this.calculateCanvasBBox();
this.set('canvasBBox', canvasBBox);
}
return canvasBox;
return canvasBBox;
}

/**
Expand All @@ -50,7 +50,7 @@ abstract class AbstractShape extends Element implements IShape {
applyMatrix(matrix: number[]) {
super.applyMatrix(matrix);
// 清理掉缓存的包围盒
this.set('canvasBox', null);
this.set('canvasBBox', null);
}

/**
Expand Down Expand Up @@ -102,7 +102,7 @@ abstract class AbstractShape extends Element implements IShape {
*/
clearCacheBBox() {
this.set('bbox', null);
this.set('canvasBox', null);
this.set('canvasBBox', null);
}

// 实现接口
Expand Down
2 changes: 1 addition & 1 deletion packages/g-base/tests/unit/bbox-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ describe('test bbox', () => {
expect(canvasBBox.y).eql(bbox.y - 20);

rect.set('totalMatrix', [1, 0, 0, 1, 0, 0, 10, 20, 1]); // 位移 10, 20
rect.set('canvasBox', null);
rect.set('canvasBBox', null);
canvasBBox = rect.getCanvasBBox();

expect(canvasBBox.x).eql(bbox.x + 10);
Expand Down
4 changes: 2 additions & 2 deletions packages/g-base/tests/unit/group-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,8 @@ describe('test group', () => {
expect(bbox.minY).eqls(-20);
expect(bbox.maxX).eqls(30);
expect(bbox.maxY).eqls(30);
const canvasBox = group.getCanvasBBox();
expect(canvasBox).eqls(bbox);
const canvasBBox = group.getCanvasBBox();
expect(canvasBBox).eqls(bbox);
});

it('remove shape', () => {
Expand Down
6 changes: 3 additions & 3 deletions packages/g-base/tests/unit/shape-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,15 +67,15 @@ describe('test element', () => {
expect(bbox.maxX).equal(100 * 2);
expect(bbox.maxY).equal(100 * 2);
expect(shape.getCanvasBBox()).eql(bbox); // 测试缓存
expect(shape.get('canvasBox')).not.eqls(undefined);
expect(shape.get('canvasBox')).not.eqls(null);
expect(shape.get('canvasBBox')).not.eqls(undefined);
expect(shape.get('canvasBBox')).not.eqls(null);
});

it('attr change', () => {
shape.attr('x', 10);
shape.attr('y', 10);
expect(shape.get('bbox')).eqls(null);
expect(shape.get('canvasBox')).eqls(null);
expect(shape.get('canvasBBox')).eqls(null);
expect(shape.getBBox()).eqls({
minX: 10,
minY: 10,
Expand Down
3 changes: 2 additions & 1 deletion packages/g-canvas/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"coverage-generator": "torch --coverage --compile --source-pattern src/*.js,src/**/*.js --opts tests/mocha.opts",
"coverage-viewer": "torch-coverage",
"test": "torch --renderer --compile --opts tests/mocha.opts",
"test-live": "torch --compile --interactive --opts tests/mocha.opts",
"test-live": "torch --compile --interactive tests/unit/",
"tsc": "tsc --noEmit",
"typecheck": "tsc --noEmit",
"dist": "webpack --config webpack.config.js --mode production"
Expand Down Expand Up @@ -59,6 +59,7 @@
"@antv/g-math": "^0.1.3",
"@antv/path-util": "~2.0.5",
"@antv/util": "~2.0.0",
"@antv/matrix-util": "^3.0.2",
"gl-matrix": "^3.0.0"
},
"__npminstall_done": false
Expand Down
24 changes: 19 additions & 5 deletions packages/g-canvas/src/canvas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { IElement } from './interfaces';
import { getShape } from './util/hit';
import * as Shape from './shape';
import Group from './group';
import { applyAttrsToContext, drawChildren, getMergedRegion, mergeView } from './util/draw';
import { applyAttrsToContext, drawChildren, getMergedRegion, mergeView, checkRefresh, clearChanged } from './util/draw';
import { getPixelRatio, requestAnimationFrame, clearAnimationFrame } from './util/util';

class Canvas extends AbstractCanvas {
Expand All @@ -19,6 +19,7 @@ class Canvas extends AbstractCanvas {
cfg['refreshElements'] = [];
// 是否在视图内自动裁剪
cfg['clipView'] = true;
// 是否使用快速拾取的方案,默认为 false,上层可以打开
cfg['quickHit'] = false;
return cfg;
}
Expand Down Expand Up @@ -60,8 +61,8 @@ class Canvas extends AbstractCanvas {
return {
minX: 0,
minY: 0,
maxX: this.get('width'),
maxY: this.get('height'),
maxX: this.cfg.width,
maxY: this.cfg.height,
};
}

Expand Down Expand Up @@ -95,10 +96,13 @@ class Canvas extends AbstractCanvas {
}

getShape(x: number, y: number) {
let shape;
if (this.get('quickHit')) {
return getShape(this, x, y);
shape = getShape(this, x, y);
} else {
shape = super.getShape(x, y, null);
}
return super.getShape(x, y, null);
return shape;
}
// 对绘制区域边缘取整,避免浮点数问题
_getRefreshRegion() {
Expand Down Expand Up @@ -171,6 +175,7 @@ class Canvas extends AbstractCanvas {
const context = this.get('context');
const children = this.getChildren() as IElement[];
const region = this._getRefreshRegion();
const refreshElements = this.get('refreshElements');
// 需要注意可能没有 region 的场景
// 一般发生在设置了 localRefresh ,在没有图形发生变化的情况下,用户调用了 draw
if (region) {
Expand All @@ -182,9 +187,18 @@ class Canvas extends AbstractCanvas {
context.rect(region.minX, region.minY, region.maxX - region.minX, region.maxY - region.minY);
context.clip();
applyAttrsToContext(context, this);
// 确认更新的元素,这个优化可以提升 10 倍左右的性能,10W 个带有 group 的节点,局部渲染会从 90ms 下降到 5-6 ms
checkRefresh(this, children, region);
// 绘制子元素
drawChildren(context, children, region);
context.restore();
} else if (refreshElements.length) {
// 防止发生改变的 elements 没有 region 的场景,这会发生在多个情况下
// 1. 空的 group
// 2. 所有 elements 没有在绘图区域
// 3. group 下面的 elements 隐藏掉
// 如果不进行清理 hasChanged 的状态会不正确
clearChanged(refreshElements);
}
this.set('refreshElements', []);
}
Expand Down
62 changes: 57 additions & 5 deletions packages/g-canvas/src/group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import { IElement } from './interfaces';
import { Region } from './types';
import ShapeBase from './shape/base';
import * as Shape from './shape';
import { each, mergeRegion } from './util/util';
import { applyAttrsToContext, drawChildren, refreshElement } from './util/draw';
import { each } from '@antv/util';
import { intersectRect } from './util/util';

class Group extends AbstractGroup {
/**
Expand Down Expand Up @@ -39,19 +40,70 @@ class Group extends AbstractGroup {
}
}

// 这个方法以前直接使用的 getCanvasBBox,由于 group 上没有缓存,所以每次重新计算,导致性能开销比较大
// 大概能够节省全局渲染 15-20% 的性能,如果不在这里加缓存优化后 10W 个节点无法达到 5-6 ms,大概能够 30-40ms
private cacheCanvasBBox() {
const children = this.cfg.children;
const xArr = [];
const yArr = [];
each(children, (child) => {
const bbox = child.cfg.cacheCanvasBBox;
// isInview 的判定是一旦图形或者分组渲染就要计算是否在视图内,
// 这个判定 10W 个图形下差不多能够节省 5-6 ms 的开销
if (bbox && child.cfg.isInView) {
xArr.push(bbox.minX, bbox.maxX);
yArr.push(bbox.minY, bbox.maxY);
}
});
let bbox = null;
if (xArr.length) {
const minX = Math.min.apply(null, xArr);
const maxX = Math.max.apply(null, xArr);
const minY = Math.min.apply(null, yArr);
const maxY = Math.max.apply(null, yArr);
bbox = {
minX,
minY,
x: minX,
y: minY,
maxX,
maxY,
width: maxX - minX,
height: maxY - minY,
};
const canvas = this.cfg.canvas;
if (canvas) {
const viewRange = canvas.getViewRange();
// 如果这个地方判定 isInView == false 设置 bbox 为 false 的话,拾取的性能会更高
// 但是目前 10W 图形的拾取在 2-5ms 内,这个优化意义不大,可以后期观察再看
this.set('isInView', intersectRect(bbox, viewRange));
}
} else {
this.set('isInView', false);
}

this.set('cacheCanvasBBox', bbox);
}

draw(context: CanvasRenderingContext2D, region?: Region) {
const children = this.getChildren() as IElement[];
if (children.length) {
const children = this.cfg.children as IElement[];
const allowDraw = region ? this.cfg.refresh : true; // 局部刷新需要判定
// 这个地方需要判定,在 G6 的场景每个 group 都有 transform 的场景下性能会开销非常大
// 通过 refresh 的判定,可以不刷新没有发生过变化的分组,不在视窗内的分组等等
// 如果想进一步提升局部渲染性能,可以进一步优化 refresh 的判定,依然有潜力
if (children.length && allowDraw) {
context.save();
// group 上的矩阵和属性也会应用到上下文上
// 先将 attrs 应用到上下文中,再设置 clip。因为 clip 应该被当前元素的 matrix 所影响
applyAttrsToContext(context, this);
this._applyClip(context, this.getClip() as ShapeBase);
drawChildren(context, children, region);
context.restore();
this.cacheCanvasBBox();
}
// 这里的成本比较大
this.set('cacheCanvasBBox', this.getCanvasBBox());
// 这里的成本比较大,如果不绘制则不再
// this.set('cacheCanvasBBox', this.getCanvasBBox());
this.cfg.refresh = null;
// 绘制后,消除更新标记
this.set('hasChanged', false);
}
Expand Down
Loading

0 comments on commit 2a70a95

Please sign in to comment.