mirror of
https://gitee.com/antv/g6.git
synced 2024-11-30 02:38:20 +08:00
refactor: support dynamic switch renderer (#6062)
* 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 <antv@antfin.com>
This commit is contained in:
parent
6775bbc70c
commit
ed6e940876
6
.vscode/settings.json
vendored
6
.vscode/settings.json
vendored
@ -11,9 +11,10 @@
|
||||
"afterelementtranslate",
|
||||
"afterelementupdate",
|
||||
"afterlayout",
|
||||
"afterstagelayout",
|
||||
"afterrender",
|
||||
"afterrendererchange",
|
||||
"aftersizechange",
|
||||
"afterstagelayout",
|
||||
"aftertransform",
|
||||
"afterviewportanimate",
|
||||
"antv",
|
||||
@ -30,9 +31,10 @@
|
||||
"beforeelementtranslate",
|
||||
"beforeelementupdate",
|
||||
"beforelayout",
|
||||
"beforestagelayout",
|
||||
"beforerender",
|
||||
"beforerendererchange",
|
||||
"beforesizechange",
|
||||
"beforestagelayout",
|
||||
"beforetransform",
|
||||
"beforeviewportanimate",
|
||||
"bubblesets",
|
||||
|
@ -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
packages/g6-extension-3d/__tests__/demos/switch-renderer.ts
Normal file
60
packages/g6-extension-3d/__tests__/demos/switch-renderer.ts
Normal file
@ -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;
|
||||
};
|
@ -72,6 +72,8 @@ export class Light extends BasePlugin<LightOptions> {
|
||||
}
|
||||
|
||||
public destroy() {
|
||||
this.ambient?.remove();
|
||||
this.directional?.remove();
|
||||
this.unbindEvents();
|
||||
super.destroy();
|
||||
}
|
||||
|
@ -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>();
|
||||
|
||||
/**
|
||||
@ -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 +
|
||||
'|' +
|
||||
|
@ -15,4 +15,8 @@ export class TupleMap<K1, K2, V> {
|
||||
}
|
||||
this.map.get(key1)!.set(key2, value);
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.map.clear();
|
||||
}
|
||||
}
|
||||
|
@ -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 = {
|
||||
@ -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)) {
|
||||
|
33
packages/g6/__tests__/demos/canvas-switch-renderer.ts
Normal file
33
packages/g6/__tests__/demos/canvas-switch-renderer.ts
Normal file
@ -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;
|
||||
};
|
@ -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';
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { parsePoint } from '@/src/utils/point';
|
||||
import { createGraphCanvas } from '@@/utils';
|
||||
|
||||
describe('Canvas', () => {
|
||||
@ -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);
|
||||
});
|
||||
@ -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
|
||||
@ -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]);
|
||||
|
@ -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 () => {
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
|
@ -6,6 +6,7 @@ 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) {
|
||||
@ -13,6 +14,7 @@ function getRenderer(renderer: string) {
|
||||
case 'svg':
|
||||
return new SVGRenderer();
|
||||
case 'webgl':
|
||||
return new WebGLRenderer();
|
||||
case 'canvas':
|
||||
return new CanvasRenderer();
|
||||
default:
|
||||
|
@ -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",
|
||||
|
@ -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',
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -1,11 +1,12 @@
|
||||
import type { DisplayObject, CanvasConfig as GCanvasConfig, IChildNode } from '@antv/g';
|
||||
import type { Cursor, DisplayObject, CanvasConfig as GCanvasConfig, IChildNode } from '@antv/g';
|
||||
import { CanvasEvent, Canvas as GCanvas } from '@antv/g';
|
||||
import { Renderer as CanvasRenderer } from '@antv/g-canvas';
|
||||
import { Plugin as DragNDropPlugin } from '@antv/g-plugin-dragndrop';
|
||||
import { createDOM } from '@antv/util';
|
||||
import type { CanvasOptions } from '../spec/canvas';
|
||||
import type { CanvasLayer } from '../types';
|
||||
import type { CanvasLayer, Point } from '../types';
|
||||
import { getBBoxSize, getCombinedBBox } from '../utils/bbox';
|
||||
import { parsePoint, toPointObject } from '../utils/point';
|
||||
|
||||
export interface CanvasConfig
|
||||
extends Pick<GCanvasConfig, 'container' | 'devicePixelRatio' | 'width' | 'height' | 'cursor' | 'background'> {
|
||||
@ -40,7 +41,7 @@ export interface DataURLOptions {
|
||||
|
||||
const layersName: CanvasLayer[] = ['background', 'main', 'label', 'transient'];
|
||||
|
||||
export class Canvas extends GCanvas {
|
||||
export class Canvas {
|
||||
private extends: {
|
||||
config: CanvasConfig;
|
||||
renderer: CanvasOptions['renderer'];
|
||||
@ -48,7 +49,13 @@ export class Canvas extends GCanvas {
|
||||
layers: Record<CanvasLayer, GCanvas>;
|
||||
};
|
||||
|
||||
public getLayer(layer: CanvasLayer) {
|
||||
private config: CanvasConfig;
|
||||
|
||||
public getConfig() {
|
||||
return this.config;
|
||||
}
|
||||
|
||||
public getLayer(layer: CanvasLayer = 'main') {
|
||||
return this.extends.layers[layer];
|
||||
}
|
||||
|
||||
@ -73,17 +80,46 @@ export class Canvas extends GCanvas {
|
||||
return this.extends.renderers[layer];
|
||||
}
|
||||
|
||||
/**
|
||||
* <zh/> 获取相机
|
||||
*
|
||||
* <en/> Get camera
|
||||
* @param layer - <zh/> 图层 <en/> Layer
|
||||
* @returns <zh/> 相机 <en/> Camera
|
||||
*/
|
||||
public getCamera(layer: CanvasLayer = 'main') {
|
||||
return this.getLayer(layer).getCamera();
|
||||
}
|
||||
|
||||
public getRoot(layer: CanvasLayer = 'main') {
|
||||
return this.getLayer(layer).getRoot();
|
||||
}
|
||||
|
||||
public getContextService(layer: CanvasLayer = 'main') {
|
||||
return this.getLayer(layer).getContextService();
|
||||
}
|
||||
|
||||
public setCursor(cursor: Cursor): void {
|
||||
this.config.cursor = cursor;
|
||||
this.getLayer().setCursor(cursor);
|
||||
}
|
||||
|
||||
public get document() {
|
||||
return this.getLayer().document;
|
||||
}
|
||||
|
||||
public get context() {
|
||||
return this.getLayer().context;
|
||||
}
|
||||
|
||||
constructor(config: CanvasConfig) {
|
||||
this.config = config;
|
||||
|
||||
const { renderer, background, cursor, ...restConfig } = config;
|
||||
const renderers = createRenderers(renderer);
|
||||
|
||||
// init main canvas
|
||||
super({ ...restConfig, supportsMutipleCanvasesInOneContainer: true, cursor, renderer: renderers.main });
|
||||
|
||||
const layers = Object.fromEntries(
|
||||
layersName.map((layer) => {
|
||||
if (layer === 'main') return [layer, this];
|
||||
|
||||
const canvas = new GCanvas({
|
||||
...restConfig,
|
||||
supportsMutipleCanvasesInOneContainer: true,
|
||||
@ -106,20 +142,12 @@ export class Canvas extends GCanvas {
|
||||
}
|
||||
|
||||
public get ready() {
|
||||
return Promise.all([
|
||||
super.ready,
|
||||
...Object.entries(this.getLayers()).map(([layer, canvas]) =>
|
||||
layer === 'main' ? Promise.resolve() : canvas.ready,
|
||||
),
|
||||
]);
|
||||
return Promise.all(Object.entries(this.getLayers()).map(([, canvas]) => canvas.ready));
|
||||
}
|
||||
|
||||
public resize(width: number, height: number) {
|
||||
Object.assign(this.extends.config, { width, height });
|
||||
Object.values(this.getLayers()).forEach((canvas) => {
|
||||
if (canvas === this) super.resize(width, height);
|
||||
else canvas.resize(width, height);
|
||||
});
|
||||
Object.values(this.getLayers()).forEach((canvas) => canvas.resize(width, height));
|
||||
}
|
||||
|
||||
public getBounds() {
|
||||
@ -142,19 +170,41 @@ export class Canvas extends GCanvas {
|
||||
|
||||
public appendChild<T extends IChildNode>(child: T, index?: number): T {
|
||||
const layer = ((child as unknown as DisplayObject).style?.$layer || 'main') as CanvasLayer;
|
||||
if (layer === 'main') return super.appendChild(child, index);
|
||||
return this.getLayer(layer).appendChild(child, index);
|
||||
}
|
||||
|
||||
public setRenderer(renderer: any) {
|
||||
public setRenderer(renderer: CanvasOptions['renderer']) {
|
||||
if (renderer === this.extends.renderer) return;
|
||||
const renderers = createRenderers(renderer);
|
||||
this.extends.renderers = renderers;
|
||||
Object.entries(renderers).forEach(([layer, instance]) => this.getLayer(layer as CanvasLayer).setRenderer(instance));
|
||||
configCanvasDom(this.getLayers());
|
||||
}
|
||||
|
||||
Object.entries(renderers).forEach(([layer, instance]) => {
|
||||
if (layer === 'main') super.setRenderer(instance);
|
||||
else this.getLayer(layer as CanvasLayer).setRenderer(instance);
|
||||
});
|
||||
public getCanvasByViewport(point: Point): Point {
|
||||
return parsePoint(this.getLayer().viewport2Canvas(toPointObject(point)));
|
||||
}
|
||||
|
||||
public getViewportByCanvas(point: Point): Point {
|
||||
return parsePoint(this.getLayer().canvas2Viewport(toPointObject(point)));
|
||||
}
|
||||
|
||||
public getViewportByClient(point: Point): Point {
|
||||
return parsePoint(this.getLayer().client2Viewport(toPointObject(point)));
|
||||
}
|
||||
|
||||
public getClientByViewport(point: Point): Point {
|
||||
return parsePoint(this.getLayer().viewport2Client(toPointObject(point)));
|
||||
}
|
||||
|
||||
public getClientByCanvas(point: Point): Point {
|
||||
return this.getClientByViewport(this.getViewportByCanvas(point));
|
||||
}
|
||||
|
||||
public getCanvasByClient(point: Point): Point {
|
||||
const main = this.getLayer();
|
||||
const viewportPoint = main.client2Viewport(toPointObject(point));
|
||||
return parsePoint(main.viewport2Canvas(viewportPoint));
|
||||
}
|
||||
|
||||
public async toDataURL(options: Partial<DataURLOptions> = {}) {
|
||||
@ -190,10 +240,10 @@ export class Canvas extends GCanvas {
|
||||
// Handle label canvas
|
||||
const label = this.getLayer('label').getRoot().cloneNode(true);
|
||||
const originCanvasPosition = offscreenCanvas.viewport2Canvas({ x: 0, y: 0 });
|
||||
const currentCanvasPosition = this.viewport2Canvas({ x: 0, y: 0 });
|
||||
const currentCanvasPosition = this.getCanvasByViewport([0, 0]);
|
||||
label.translate([
|
||||
currentCanvasPosition.x - originCanvasPosition.x,
|
||||
currentCanvasPosition.y - originCanvasPosition.y,
|
||||
currentCanvasPosition[0] - originCanvasPosition.x,
|
||||
currentCanvasPosition[1] - originCanvasPosition.y,
|
||||
]);
|
||||
label.scale(1 / this.getCamera().getZoom());
|
||||
offscreenCanvas.appendChild(label);
|
||||
@ -226,12 +276,11 @@ export class Canvas extends GCanvas {
|
||||
});
|
||||
}
|
||||
|
||||
public destroy(cleanUp?: boolean, skipTriggerEvent?: boolean) {
|
||||
public destroy() {
|
||||
Object.values(this.getLayers()).forEach((canvas) => {
|
||||
const camera = canvas.getCamera();
|
||||
camera.cancelLandmarkAnimation();
|
||||
if (canvas === this) super.destroy(cleanUp, skipTriggerEvent);
|
||||
else canvas.destroy(cleanUp, skipTriggerEvent);
|
||||
canvas.destroy();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -451,6 +451,9 @@ export class ElementController {
|
||||
},
|
||||
{
|
||||
before: () => {
|
||||
// 通过 elementMap[id] 访问最新的 element,防止 type 不同导致的 element 丢失
|
||||
// Access the latest element through elementMap[id] to prevent the loss of element caused by different types
|
||||
const element = this.elementMap[id];
|
||||
if (stage !== 'collapse') updateStyle(element, style);
|
||||
|
||||
if (stage === 'visibility') {
|
||||
@ -462,6 +465,7 @@ export class ElementController {
|
||||
}
|
||||
},
|
||||
after: () => {
|
||||
const element = this.elementMap[id];
|
||||
if (stage === 'collapse') updateStyle(element, style);
|
||||
if (exactStage === 'hide') updateStyle(element, { visibility: getCachedStyle(element, 'visibility') });
|
||||
this.emit(new ElementLifeCycleEvent(GraphEvent.AFTER_ELEMENT_UPDATE, elementType, datum), context);
|
||||
|
@ -43,7 +43,6 @@ import { isCollapsed } from '../utils/collapsibility';
|
||||
import { sizeOf } from '../utils/dom';
|
||||
import { GraphLifeCycleEvent, emit } from '../utils/event';
|
||||
import { idOf } from '../utils/id';
|
||||
import { parsePoint, toPointObject } from '../utils/point';
|
||||
import { format } from '../utils/print';
|
||||
import { subtract } from '../utils/vector';
|
||||
import { Animation } from './animation';
|
||||
@ -120,6 +119,15 @@ export class Graph extends EventEmitter {
|
||||
public setOptions(options: GraphOptions): void {
|
||||
const { behaviors, combo, data, edge, height, layout, node, plugins, theme, transforms, width, renderer } = options;
|
||||
|
||||
if (renderer) {
|
||||
const canvas = this.context.canvas;
|
||||
if (canvas) {
|
||||
this.emit(GraphEvent.BEFORE_RENDERER_CHANGE, { renderer: this.options.renderer });
|
||||
canvas.setRenderer(renderer);
|
||||
this.emit(GraphEvent.AFTER_RENDERER_CHANGE, { renderer });
|
||||
}
|
||||
}
|
||||
|
||||
Object.assign(this.options, options);
|
||||
|
||||
if (behaviors) this.setBehaviors(behaviors);
|
||||
@ -133,7 +141,6 @@ export class Graph extends EventEmitter {
|
||||
if (transforms) this.setTransforms(transforms);
|
||||
if (isNumber(width) || isNumber(height))
|
||||
this.setSize(width ?? this.options.width ?? 0, height ?? this.options.height ?? 0);
|
||||
if (renderer) this.context.canvas?.setRenderer(renderer);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1785,7 +1792,7 @@ export class Graph extends EventEmitter {
|
||||
* @apiCategory viewport
|
||||
*/
|
||||
public getCanvasByViewport(point: Point): Point {
|
||||
return parsePoint(this.context.canvas!.viewport2Canvas(toPointObject(point)));
|
||||
return this.context.canvas.getCanvasByViewport(point);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1797,7 +1804,7 @@ export class Graph extends EventEmitter {
|
||||
* @apiCategory viewport
|
||||
*/
|
||||
public getViewportByCanvas(point: Point): Point {
|
||||
return parsePoint(this.context.canvas.canvas2Viewport(toPointObject(point)));
|
||||
return this.context.canvas.getViewportByCanvas(point);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1809,8 +1816,7 @@ export class Graph extends EventEmitter {
|
||||
* @apiCategory viewport
|
||||
*/
|
||||
public getClientByCanvas(point: Point): Point {
|
||||
const viewportPoint = this.context.canvas.canvas2Viewport(toPointObject(point));
|
||||
return parsePoint(this.context.canvas.viewport2Canvas(viewportPoint));
|
||||
return this.context.canvas.getClientByCanvas(point);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1822,8 +1828,7 @@ export class Graph extends EventEmitter {
|
||||
* @apiCategory viewport
|
||||
*/
|
||||
public getCanvasByClient(point: Point): Point {
|
||||
const viewportPoint = this.context.canvas.client2Viewport(toPointObject(point));
|
||||
return parsePoint(this.context.canvas.viewport2Canvas(viewportPoint));
|
||||
return this.context.canvas.getCanvasByClient(point);
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user