Skip to content

Commit

Permalink
refactor: support dynamic switch renderer (#6062)
Browse files Browse the repository at this point in the history
* feat(runtime): graph emit renderer change event

* refactor: refactor canvas and support switch renderer

* fix(3d): remove light on destroy plugin

* refactor(3d): clear cache when renderer change

* test: add switch renderer demo

---------

Co-authored-by: antv <[email protected]>
  • Loading branch information
Aarebecca and antv authored Jul 19, 2024
1 parent 6775bbc commit ed6e940
Show file tree
Hide file tree
Showing 18 changed files with 277 additions and 56 deletions.
6 changes: 4 additions & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@
"afterelementtranslate",
"afterelementupdate",
"afterlayout",
"afterstagelayout",
"afterrender",
"afterrendererchange",
"aftersizechange",
"afterstagelayout",
"aftertransform",
"afterviewportanimate",
"antv",
Expand All @@ -30,9 +31,10 @@
"beforeelementtranslate",
"beforeelementupdate",
"beforelayout",
"beforestagelayout",
"beforerender",
"beforerendererchange",
"beforesizechange",
"beforestagelayout",
"beforetransform",
"beforeviewportanimate",
"bubblesets",
Expand Down
1 change: 1 addition & 0 deletions packages/g6-extension-3d/__tests__/demos/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ export { massiveElements } from './massive-elements';
export * from './position';
export * from './shapes';
export * from './solar-system';
export { switchRenderer } from './switch-renderer';
60 changes: 60 additions & 0 deletions packages/g6-extension-3d/__tests__/demos/switch-renderer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { Renderer as CanvasRenderer } from '@antv/g-canvas';
import type { NodeData } from '@antv/g6';
import { ExtensionCategory, Graph, register } from '@antv/g6';
import { Light, Sphere, renderer } from '../../src';

export const switchRenderer: TestCase = async (context) => {
register(ExtensionCategory.PLUGIN, '3d-light', Light);
register(ExtensionCategory.NODE, 'sphere', Sphere);

const nodes: NodeData[] = [{ id: '1' }, { id: '2' }];

const graph = new Graph({
...context,
data: {
nodes,
},
layout: {
type: 'grid',
},
});

await graph.render();

switchRenderer.form = (panel) => {
panel.add({ renderer: '2d' }, 'renderer', ['2d', '3d']).onChange((name: string) => {
if (name === '2d') {
graph.setOptions({
renderer: () => new CanvasRenderer(),
node: {
type: 'circle',
},
plugins: [],
});
} else {
graph.setOptions({
renderer,
node: {
type: 'sphere',
style: {
materialType: 'phong',
},
},
plugins: [
{
type: '3d-light',
directional: {
direction: [0, 0, 1],
},
},
],
});
}

graph.draw();
});
return [];
};

return graph;
};
2 changes: 2 additions & 0 deletions packages/g6-extension-3d/src/plugins/light.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ export class Light extends BasePlugin<LightOptions> {
}

public destroy() {
this.ambient?.remove();
this.directional?.remove();
this.unbindEvents();
super.destroy();
}
Expand Down
8 changes: 8 additions & 0 deletions packages/g6-extension-3d/src/utils/geometry.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import type { Device } from '@antv/g-device-api';
import type { ProceduralGeometry } from '@antv/g-plugin-3d';

let DEVICE: Device;

const GEOMETRY_CACHE = new Map<string, unknown>();

/**
Expand All @@ -19,6 +21,12 @@ export function createGeometry<T extends ProceduralGeometry<any>>(
Ctor: new (...args: any[]) => T,
style: Record<string, unknown>,
) {
if (!DEVICE) DEVICE = device;
else if (DEVICE !== device) {
DEVICE = device;
GEOMETRY_CACHE.clear();
}

const cacheKey =
type +
'|' +
Expand Down
4 changes: 4 additions & 0 deletions packages/g6-extension-3d/src/utils/map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,8 @@ export class TupleMap<K1, K2, V> {
}
this.map.get(key1)!.set(key2, value);
}

clear() {
this.map.clear();
}
}
8 changes: 8 additions & 0 deletions packages/g6-extension-3d/src/utils/material.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { getCacheKey } from './cache';
import { TupleMap } from './map';
import { createTexture } from './texture';

let PLUGIN: Plugin;

const MATERIAL_CACHE = new TupleMap<symbol, string | TexImageSource | undefined, GMaterial>();

const MATERIAL_MAP = {
Expand All @@ -25,6 +27,12 @@ const MATERIAL_MAP = {
* @returns <zh/> 材质对象 <en/> material object
*/
export function createMaterial(plugin: Plugin, options: Material, texture?: string | TexImageSource): GMaterial {
if (!PLUGIN) PLUGIN = plugin;
else if (PLUGIN !== plugin) {
PLUGIN = plugin;
MATERIAL_CACHE.clear();
}

const key = getCacheKey(options);

if (MATERIAL_CACHE.has(key, texture)) {
Expand Down
33 changes: 33 additions & 0 deletions packages/g6/__tests__/demos/canvas-switch-renderer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Graph } from '@/src';
import { Renderer as CanvasRenderer } from '@antv/g-canvas';
import { Renderer as SVGRenderer } from '@antv/g-svg';
import { Renderer as WebGLRenderer } from '@antv/g-webgl';

export const canvasSwitchRenderer: TestCase = async (context) => {
const graph = new Graph({
...context,
data: {
nodes: Array.from({ length: 10 }).map((_, i) => ({ id: `node-${i}` })),
},
layout: {
type: 'grid',
},
});

await graph.render();

canvasSwitchRenderer.form = (panel) => {
panel.add({ renderer: 'canvas' }, 'renderer', ['canvas', 'svg', 'webgl']).onChange((name: string) => {
graph.setOptions({
renderer: () => {
if (name === 'svg') return new SVGRenderer();
if (name === 'webgl') return new WebGLRenderer();
return new CanvasRenderer();
},
});
});
return [];
};

return graph;
};
1 change: 1 addition & 0 deletions packages/g6/__tests__/demos/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export { behaviorLassoSelect } from './behavior-lasso-select';
export { behaviorOptimizeViewportTransform } from './behavior-optimize-viewport-transform';
export { behaviorScrollCanvas } from './behavior-scroll-canvas';
export { behaviorZoomCanvas } from './behavior-zoom-canvas';
export { canvasSwitchRenderer } from './canvas-switch-renderer';
export { caseIndentedTree } from './case-indented-tree';
export { caseOrgChart } from './case-org-chart';
export { elementCombo } from './combo';
Expand Down
27 changes: 13 additions & 14 deletions packages/g6/__tests__/unit/runtime/canvas.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { parsePoint } from '@/src/utils/point';
import { createGraphCanvas } from '@@/utils';

describe('Canvas', () => {
Expand All @@ -19,32 +18,32 @@ describe('Canvas', () => {

it('coordinate transform', () => {
// TODO g canvas client 坐标转换疑似异常
expect(parsePoint(svg.viewport2Client({ x: 0, y: 0 }))).toBeCloseTo([0, 0, 0]);
expect(parsePoint(svg.viewport2Canvas({ x: 0, y: 0 }))).toBeCloseTo([0, 0, 0]);
expect(svg.getClientByCanvas([0, 0])).toBeCloseTo([0, 0, 0]);
expect(svg.getCanvasByViewport([0, 0])).toBeCloseTo([0, 0, 0]);

expect(parsePoint(svg.client2Viewport({ x: 0, y: 0 }))).toBeCloseTo([0, 0, 0]);
expect(parsePoint(svg.canvas2Viewport({ x: 0, y: 0 }))).toBeCloseTo([0, 0, 0]);
expect(svg.getViewportByClient([0, 0])).toBeCloseTo([0, 0, 0]);
expect(svg.getViewportByCanvas([0, 0])).toBeCloseTo([0, 0, 0]);

const camera = svg.getCamera();
camera.pan(100, 100);
expect([...camera.getPosition()]).toBeCloseTo([350, 350, 500]);
expect([...camera.getFocalPoint()]).toBeCloseTo([250, 250, 0]);
expect(parsePoint(svg.viewport2Canvas({ x: 0, y: 0 }))).toBeCloseTo([100, 100, 0]);
expect(parsePoint(svg.canvas2Viewport({ x: 0, y: 0 }))).toBeCloseTo([-100, -100, 0]);
expect(svg.getCanvasByViewport([0, 0])).toBeCloseTo([100, 100, 0]);
expect(svg.getViewportByCanvas([0, 0])).toBeCloseTo([-100, -100, 0]);

// camera pan 采用相对移动
camera.pan(-200, -200);
// focal point wont change
// expect([...camera.getFocalPoint()]).toBeCloseTo([250, 250, 0]);
expect([...camera.getPosition()]).toBeCloseTo([150, 150, 500]);
expect(parsePoint(svg.viewport2Canvas({ x: 0, y: 0 }))).toBeCloseTo([-100, -100, 0]);
expect(parsePoint(svg.canvas2Viewport({ x: 0, y: 0 }))).toBeCloseTo([100, 100, 0]);
expect(svg.getCanvasByViewport([0, 0])).toBeCloseTo([-100, -100, 0]);
expect(svg.getViewportByCanvas([0, 0])).toBeCloseTo([100, 100, 0]);

// move to origin
camera.pan(100, 100);

camera.pan(-100, -100);
expect(parsePoint(svg.viewport2Canvas({ x: 0, y: 0 }))).toBeCloseTo([-100, -100, 0]);
expect(svg.getCanvasByViewport([0, 0])).toBeCloseTo([-100, -100, 0]);

camera.pan(100, 100);
});
Expand All @@ -69,8 +68,8 @@ describe('Canvas', () => {
camera.gotoLandmark(landmark1, { onfinish: resolve });
});

expect(parsePoint(svg.viewport2Canvas({ x: 0, y: 0 }))).toBeCloseTo([100, 100, 0]);
expect(parsePoint(svg.canvas2Viewport({ x: 0, y: 0 }))).toBeCloseTo([-100, -100, 0]);
expect(svg.getCanvasByViewport([0, 0])).toBeCloseTo([100, 100, 0]);
expect(svg.getViewportByCanvas([0, 0])).toBeCloseTo([-100, -100, 0]);

const landmark2 = camera.createLandmark('landmark2', {
// 视点坐标 / viewport coordinates
Expand All @@ -83,8 +82,8 @@ describe('Canvas', () => {
camera.gotoLandmark(landmark2, { onfinish: resolve });
});

expect(parsePoint(svg.viewport2Canvas({ x: 0, y: 0 }))).toBeCloseTo([-100, -100, 0]);
expect(parsePoint(svg.canvas2Viewport({ x: 0, y: 0 }))).toBeCloseTo([100, 100, 0]);
expect(svg.getCanvasByViewport([0, 0])).toBeCloseTo([-100, -100, 0]);
expect(svg.getViewportByCanvas([0, 0])).toBeCloseTo([100, 100, 0]);

expect([...camera.getFocalPoint()]).toBeCloseTo([150, 150, 0]);
expect([...camera.getPosition()]).toBeCloseTo([150, 150, 500]);
Expand Down
30 changes: 30 additions & 0 deletions packages/g6/__tests__/unit/runtime/graph/event.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { GraphEvent } from '@/src';
import { createGraph } from '@@/utils';
import { Renderer as CanvasRenderer } from '@antv/g-canvas';

describe('event', () => {
it('canvas ready', async () => {
Expand Down Expand Up @@ -153,4 +154,33 @@ describe('event', () => {

graph.destroy();
});

it('renderer change event', async () => {
const graph = createGraph({
data: {
nodes: [{ id: 'node-1' }, { id: 'node-2' }],
edges: [{ id: 'edge-1', source: 'node-1', target: 'node-2' }],
},
});

const beforeRendererChange = jest.fn();
const afterRendererChange = jest.fn();

graph.on(GraphEvent.BEFORE_RENDERER_CHANGE, beforeRendererChange);
graph.on(GraphEvent.AFTER_RENDERER_CHANGE, afterRendererChange);

await graph.render();

expect(beforeRendererChange).toHaveBeenCalledTimes(0);
expect(afterRendererChange).toHaveBeenCalledTimes(0);

const renderer = () => new CanvasRenderer();

graph.setOptions({
renderer,
});

expect(beforeRendererChange).toHaveBeenCalledTimes(1);
expect(afterRendererChange).toHaveBeenCalledTimes(1);
});
});
2 changes: 2 additions & 0 deletions packages/g6/__tests__/utils/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ import type { Node, Point } from '@/src/types';
import { resetEntityCounter } from '@antv/g';
import { Renderer as CanvasRenderer } from '@antv/g-canvas';
import { Renderer as SVGRenderer } from '@antv/g-svg';
import { Renderer as WebGLRenderer } from '@antv/g-webgl';
import { OffscreenCanvasContext } from './offscreen-canvas-context';

function getRenderer(renderer: string) {
switch (renderer) {
case 'svg':
return new SVGRenderer();
case 'webgl':
return new WebGLRenderer();
case 'canvas':
return new CanvasRenderer();
default:
Expand Down
1 change: 1 addition & 0 deletions packages/g6/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
},
"devDependencies": {
"@antv/g-svg": "^2.0.8",
"@antv/g-webgl": "^2.0.11",
"@antv/layout-gpu": "^1.1.6",
"@antv/layout-wasm": "^1.4.1",
"@types/hull.js": "^1.0.4",
Expand Down
12 changes: 12 additions & 0 deletions packages/g6/src/constants/events/graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,4 +167,16 @@ export enum GraphEvent {
* <en/> After destruction
*/
AFTER_DESTROY = 'afterdestroy',
/**
* <zh/> 渲染器变更之前
*
* <en/> Before the renderer changes
*/
BEFORE_RENDERER_CHANGE = 'beforerendererchange',
/**
* <zh/> 渲染器变更之后
*
* <en/> After the renderer changes
*/
AFTER_RENDERER_CHANGE = 'afterrendererchange',
}
2 changes: 1 addition & 1 deletion packages/g6/src/elements/nodes/html.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ export class HTML extends BaseNode<HTMLStyleProps> {
const { x, y } = this.getViewportXY(normalizedEvent);
event.viewport.x = x;
event.viewport.y = y;
const { x: canvasX, y: canvasY } = this.attributes.context!.canvas.viewport2Canvas(event.viewport);
const [canvasX, canvasY] = this.context.canvas.getCanvasByViewport([x, y]);
event.canvas.x = canvasX;
event.canvas.y = canvasY;
event.global.copyFrom(event.canvas);
Expand Down
Loading

0 comments on commit ed6e940

Please sign in to comment.