refactor: refactor canvas, support switch renderer (#6034)

* chore: update pnpm workspace

* refactor(runtime): refactor canvas, support setRenderer

* refactor(runtime): graph adpat canvas, remove get/set background

* refactor: adjust $layer property

* refactor: refactor fullscreen plugin

* refactor: base-element add context property

* refactor: canvas add getRenderer API
This commit is contained in:
Aaron 2024-07-15 19:58:58 +08:00 committed by GitHub
parent 5b993b007d
commit 0e911c2ad9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 221 additions and 302 deletions

View File

@ -1,4 +1,3 @@
import type { IRenderer } from '@antv/g';
import { resetEntityCounter } from '@antv/g';
import { Renderer as CanvasRenderer } from '@antv/g-canvas';
import { Renderer as SVGRenderer } from '@antv/g-svg';
@ -41,12 +40,11 @@ export function createGraphCanvas(
} as unknown as HTMLCanvasElement;
const context = new OffscreenCanvasContext(offscreenNodeCanvas);
const instance = getRenderer(renderer) as any as IRenderer;
return {
container,
width,
height,
renderer: () => instance,
renderer: () => getRenderer(renderer),
document: container.ownerDocument,
offscreenCanvas: offscreenNodeCanvas,
};

View File

@ -99,7 +99,11 @@ export async function toMatchSnapshot(
detail?: string,
options: ToMatchSVGSnapshotOptions = {},
) {
return await toMatchSVGSnapshot(Object.values(graph.getCanvas().canvas), ...getSnapshotDir(dir, detail), options);
return await toMatchSVGSnapshot(
Object.values(graph.getCanvas().getLayers()),
...getSnapshotDir(dir, detail),
options,
);
}
export async function toMatchAnimation(
@ -126,7 +130,7 @@ export async function toMatchAnimation(
animation.currentTime = frame;
await sleep(32);
const result = await toMatchSVGSnapshot(
Object.values(graph.getCanvas().canvas),
Object.values(graph.getCanvas().getCanvases().canvas),
...getSnapshotDir(dir, `${detail}-${frame}`),
options,
);

View File

@ -23,7 +23,7 @@ export abstract class BaseNode3D<S extends BaseNode3DStyleProps> extends BaseNod
public type = 'node-3d';
protected get plugin() {
const renderer = this.attributes.context!.canvas.renderers['main'];
const renderer = this.context.canvas.getRenderer('main');
const plugin = renderer.getPlugin('device-renderer');
return plugin as unknown as Plugin;
}

View File

@ -78,7 +78,7 @@ export const caseIndentedTree: TestCase = async (context) => {
}
protected get childrenData() {
return this.attributes.context!.model.getChildrenData(this.id);
return this.context!.model.getChildrenData(this.id);
}
protected getKeyStyle(attributes: Required<IndentedNodeStyleProps>): RectStyleProps {
@ -147,7 +147,7 @@ export const caseIndentedTree: TestCase = async (context) => {
this.forwardEvent(btn, CommonEvent.CLICK, (event: IPointerEvent) => {
event.stopPropagation();
attributes.context!.graph.emit(TreeEvent.COLLAPSE_EXPAND, {
this.context.graph.emit(TreeEvent.COLLAPSE_EXPAND, {
id: this.id,
collapsed: false,
});
@ -184,7 +184,7 @@ export const caseIndentedTree: TestCase = async (context) => {
this.forwardEvent(btn, CommonEvent.CLICK, (event: IPointerEvent) => {
event.stopPropagation();
attributes.context!.graph.emit(TreeEvent.COLLAPSE_EXPAND, {
this.context.graph.emit(TreeEvent.COLLAPSE_EXPAND, {
id: this.id,
collapsed: !attributes.collapsed,
});
@ -221,7 +221,7 @@ export const caseIndentedTree: TestCase = async (context) => {
this.forwardEvent(btn, CommonEvent.CLICK, (event: IPointerEvent) => {
event.stopPropagation();
attributes.context!.graph.emit(TreeEvent.ADD_CHILD, { id: this.id });
this.context.graph.emit(TreeEvent.ADD_CHILD, { id: this.id });
});
}

View File

@ -64,7 +64,7 @@ async function render() {
// render
const { Renderer, Demo, Animation, Theme } = options;
const canvas = createGraphCanvas($container, 500, 500, Renderer);
await canvas.init();
await canvas.ready;
const testCase = demos[Demo as keyof typeof demos];
if (!testCase) return;

View File

@ -4,11 +4,7 @@ import { createGraphCanvas } from '@@/utils';
describe('Canvas', () => {
const svg = createGraphCanvas(null, 500, 500, 'svg');
beforeAll(async () => {
await svg.init();
});
it('getRendererType', () => {
expect(svg.getRendererType()).toBe('svg');
await svg.ready;
});
it('context', () => {

View File

@ -43,10 +43,6 @@ describe('Graph', () => {
});
});
it('setBackground/getBackground', () => {
expect(graph.getBackground()).toEqual('#ffffff');
});
it('getSize', () => {
expect(graph.getSize()).toEqual([500, 500]);
});

View File

@ -3,7 +3,6 @@ import { Graph } from '@/src';
import { Circle } from '@/src/elements';
import { Canvas } from '@/src/runtime/canvas';
import type { Node, Point } from '@/src/types';
import type { IRenderer } from '@antv/g';
import { resetEntityCounter } from '@antv/g';
import { Renderer as CanvasRenderer } from '@antv/g-canvas';
import { Renderer as SVGRenderer } from '@antv/g-svg';
@ -60,12 +59,11 @@ export function createGraphCanvas(
} as unknown as HTMLCanvasElement;
const context = new OffscreenCanvasContext(offscreenNodeCanvas);
const instance = getRenderer(renderer) as any as IRenderer;
return new Canvas({
container,
width,
height,
renderer: () => instance,
renderer: () => getRenderer(renderer),
...extraOptions,
});
}

View File

@ -1,4 +1,5 @@
import type { Graph, IAnimateEvent } from '@/src';
import type { CanvasLayer } from '@/src/types';
import type { Canvas, IAnimation } from '@antv/g';
import chalk from 'chalk';
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from 'fs';
@ -21,7 +22,7 @@ const formatSVG = (svg: string, keepSVGElementId: boolean) => {
// @see https://jestjs.io/docs/26.x/expect#expectextendmatchers
export async function toMatchSVGSnapshot(
gCanvas: Canvas | Canvas[],
gCanvas: Record<CanvasLayer, Canvas>,
dir: string,
name: string,
options: ToMatchSVGSnapshotOptions = {},
@ -32,15 +33,15 @@ export async function toMatchSVGSnapshot(
const namePath = join(dir, name);
const actualPath = join(dir, `${name}-actual.${fileFormat}`);
const expectedPath = join(dir, `${name}.${fileFormat}`);
const gCanvases = Array.isArray(gCanvas) ? gCanvas : [gCanvas];
let actual: string = '';
// Clone <svg>
const svg = (gCanvases[0].getContextService().getDomElement() as unknown as SVGElement).cloneNode(true) as SVGElement;
const svg = (gCanvas.main.getContextService().getDomElement() as unknown as SVGElement).cloneNode(true) as SVGElement;
const gRoot = svg.querySelector('#g-root');
gCanvases.slice(1).forEach((gCanvas) => {
Object.entries(gCanvas).forEach(([key, gCanvas]) => {
if (key === 'main') return;
const dom = (gCanvas.getContextService().getDomElement() as unknown as SVGElement).cloneNode(true) as SVGElement;
// @ts-expect-error dom is SVGElement
gRoot?.append(...(dom.querySelector('#g-root')?.childNodes || []));
@ -99,7 +100,7 @@ export async function toMatchSnapshot(
detail?: string,
options: ToMatchSVGSnapshotOptions = {},
) {
return await toMatchSVGSnapshot(Object.values(graph.getCanvas().canvas), ...getSnapshotDir(dir, detail), options);
return await toMatchSVGSnapshot(graph.getCanvas().getLayers(), ...getSnapshotDir(dir, detail), options);
}
export async function toMatchAnimation(
@ -127,7 +128,7 @@ export async function toMatchAnimation(
await sleep(32);
const result = await toMatchSVGSnapshot(
Object.values(graph.getCanvas().canvas),
graph.getCanvas().getLayers(),
...getSnapshotDir(dir, `${detail}-${frame}`),
options,
);

View File

@ -376,11 +376,10 @@ export class DragElement extends BaseBehavior<DragElementOptions> {
} else {
this.shadow = new Rect({
style: {
$layer: 'transient',
...shadowStyle,
...positionStyle,
pointerEvents: 'none',
// @ts-expect-error $layer is not in the type definition
$layer: 'transient',
},
});
this.context.canvas.appendChild(this.shadow);

View File

@ -1,9 +1,13 @@
import { IAnimation } from '@antv/g';
import { Keyframe } from '../types';
import type { BaseShapeStyleProps } from './shapes';
import type { IAnimation } from '@antv/g';
import type { RuntimeContext } from '../runtime/types';
import type { BaseElementStyleProps, Keyframe } from '../types';
import { BaseShape } from './shapes';
export abstract class BaseElement<T extends BaseShapeStyleProps> extends BaseShape<T> {
export abstract class BaseElement<T extends BaseElementStyleProps> extends BaseShape<T> {
protected get context(): RuntimeContext {
return this.attributes.context!;
}
protected get parsedAttributes() {
return this.attributes as Required<T>;
}

View File

@ -194,7 +194,7 @@ export abstract class BaseCombo<S extends BaseComboStyleProps = BaseComboStylePr
}
protected getComboZIndex(attributes: Required<S>): number {
const ancestors = attributes.context!.model.getAncestorsData(this.id, COMBO_KEY) || [];
const ancestors = this.context.model.getAncestorsData(this.id, COMBO_KEY) || [];
return ancestors.length;
}
@ -231,7 +231,7 @@ export abstract class BaseCombo<S extends BaseComboStyleProps = BaseComboStylePr
Object.assign(this.style, comboStyle);
// Sync combo position to model
const { x, y } = comboStyle;
attributes.context!.model.syncComboDatum({ id: this.id, style: { x, y } });
this.context.model.syncComboDatum({ id: this.id, style: { x, y } });
}
public render(attributes: Required<S>, container: Group = this) {

View File

@ -50,7 +50,7 @@ export class HTML extends BaseNode<HTMLStyleProps> {
private rootPointerEvent = new FederatedPointerEvent(null);
private get eventService() {
return this.attributes.context!.canvas.context.eventService;
return this.context.canvas.context.eventService;
}
private get events() {
@ -105,7 +105,7 @@ export class HTML extends BaseNode<HTMLStyleProps> {
}
private forwardEvents = (nativeEvent: PointerEvent) => {
const canvas = this.attributes.context!.canvas.main;
const canvas = this.context.canvas;
const iCanvas = canvas.context.renderingContext.root!.ownerDocument!.defaultView!;
const normalizedEvents = this.normalizeToPointerEvent(nativeEvent, iCanvas);
@ -232,11 +232,7 @@ export class HTML extends BaseNode<HTMLStyleProps> {
let x: number;
let y: number;
const { offsetX, offsetY, clientX, clientY } = nativeEvent;
if (
this.attributes.context?.canvas.main.context.config.supportsCSSTransform &&
!isNil(offsetX) &&
!isNil(offsetY)
) {
if (this.context.canvas.context.config.supportsCSSTransform && !isNil(offsetX) && !isNil(offsetY)) {
x = offsetX;
y = offsetY;
} else {

12
packages/g6/src/global.d.ts vendored Normal file
View File

@ -0,0 +1,12 @@
import '@antv/g';
declare module '@antv/g' {
interface BaseStyleProps {
/**
* <zh/> 'main'
*
* <en/> The layer where the shape is located, default is 'main'.
*/
$layer?: string;
}
}

View File

@ -58,7 +58,10 @@ export class Fullscreen extends BasePlugin<FullscreenOptions> {
private shortcut: Shortcut;
private style: HTMLStyleElement;
private $el = this.context.canvas.getContainer()!;
private graphSize: [number, number] = [0, 0];
constructor(context: RuntimeContext, options: FullscreenOptions) {
@ -68,7 +71,13 @@ export class Fullscreen extends BasePlugin<FullscreenOptions> {
this.bindEvents();
this.$el.style.backgroundColor = this.context.graph.getBackground()!;
this.style = document.createElement('style');
document.head.appendChild(this.style);
this.style.innerHTML = `
:not(:root):fullscreen::backdrop {
background: transparent;
}
`;
}
private bindEvents() {
@ -150,6 +159,12 @@ export class Fullscreen extends BasePlugin<FullscreenOptions> {
super.update(options);
this.bindEvents();
}
public destroy(): void {
this.exit();
this.style.remove();
super.destroy();
}
}
/**

View File

@ -1,17 +1,15 @@
import type { Cursor, DisplayObject, CanvasConfig as GCanvasConfig, IAnimation, IRenderer } from '@antv/g';
import type { 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, isFunction, isString } from '@antv/util';
import { createDOM } from '@antv/util';
import type { CanvasOptions } from '../spec/canvas';
import type { PointObject } from '../types';
import type { CanvasLayer } from '../types/canvas';
import type { CanvasLayer } from '../types';
import { getBBoxSize, getCombinedBBox } from '../utils/bbox';
export interface CanvasConfig
extends Pick<GCanvasConfig, 'container' | 'devicePixelRatio' | 'width' | 'height' | 'cursor'> {
extends Pick<GCanvasConfig, 'container' | 'devicePixelRatio' | 'width' | 'height' | 'cursor' | 'background'> {
renderer?: CanvasOptions['renderer'];
background?: string;
}
export interface DataURLOptions {
@ -40,153 +38,93 @@ export interface DataURLOptions {
encoderOptions: number;
}
/**
* @deprecated this canvas will be replace by layered canvas
*/
export class Canvas {
protected config: CanvasConfig;
const layersName: CanvasLayer[] = ['background', 'main', 'label', 'transient'];
public background!: GCanvas;
public main!: GCanvas;
public label!: GCanvas;
public transient!: GCanvas;
export class Canvas extends GCanvas {
private extends: {
config: CanvasConfig;
renderer: CanvasOptions['renderer'];
renderers: Record<CanvasLayer, CanvasRenderer>;
layers: Record<CanvasLayer, GCanvas>;
};
public get canvas() {
return {
main: this.main,
label: this.label,
transient: this.transient,
background: this.background,
public getLayer(layer: CanvasLayer) {
return this.extends.layers[layer];
}
/**
* <zh/>
*
* <en/> Get all layers
* @returns <zh/> <en/> Layer
*/
public getLayers() {
return this.extends.layers;
}
/**
* <zh/>
*
* <en/> Get renderer
* @param layer - <zh/> <en/> Layer
* @returns <zh/> <en/> Renderer
*/
public getRenderer(layer: CanvasLayer) {
return this.extends.renderers[layer];
}
constructor(config: CanvasConfig) {
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,
renderer: renderers[layer],
background: layer === 'background' ? background : undefined,
});
return [layer, canvas];
}),
) as Record<CanvasLayer, GCanvas>;
configCanvasDom(layers);
this.extends = {
config,
renderer,
renderers,
layers,
};
}
public get document() {
return this.main.document;
}
public renderers!: Record<CanvasLayer, IRenderer>;
private initialized = false;
constructor(config: CanvasConfig) {
this.config = config;
}
public async init() {
if (this.initialized) return;
const { renderer: getRenderer, background, ...restConfig } = this.config;
const names: CanvasLayer[] = ['main', 'label', 'transient', 'background'];
const { renderers, canvas } = names.reduce(
(acc, name) => {
const renderer = isFunction(getRenderer) ? getRenderer?.(name) : new CanvasRenderer();
if (name === 'main') {
renderer.registerPlugin(
new DragNDropPlugin({
isDocumentDraggable: true,
isDocumentDroppable: true,
dragstartDistanceThreshold: 10,
dragstartTimeThreshold: 100,
}),
);
} else {
renderer.unregisterPlugin(renderer.getPlugin('dom-interaction'));
}
const canvas = new GCanvas({
renderer,
supportsMutipleCanvasesInOneContainer: true,
...restConfig,
});
acc.renderers[name] = renderer;
acc.canvas[name] = canvas;
this[name] = canvas;
return acc;
},
{ renderers: {}, canvas: {} } as {
renderers: Record<CanvasLayer, IRenderer>;
canvas: Record<CanvasLayer, GCanvas>;
},
);
this.renderers = renderers;
Object.entries(canvas).forEach(([name, canvas]) => {
const domElement = canvas.getContextService().getDomElement() as unknown as HTMLElement;
domElement.style.position = 'absolute';
domElement.style.outline = 'none';
domElement.tabIndex = 1;
if (name !== 'main') domElement.style.pointerEvents = 'none';
});
this.setBackground();
await Promise.all(Object.values(this.canvas).map((canvas) => canvas.ready));
this.initialized = true;
}
public getRendererType(layer: CanvasLayer = 'main') {
const plugins = this.renderers[layer].getPlugins();
for (const plugin of plugins) {
if (plugin.name === 'canvas-renderer') return 'canvas';
if (plugin.name === 'svg-renderer') return 'svg';
if (plugin.name === 'device-renderer') return 'gpu';
}
return 'unknown';
}
public get context() {
return this.main.context;
}
public getDevice() {
// @ts-expect-error deviceRendererPlugin is private
return this.main.context?.deviceRendererPlugin?.getDevice();
}
public getConfig() {
return this.config;
}
public setBackground(background = this.config.background) {
this.config.background = background;
}
public setCursor(cursor: Cursor) {
this.config.cursor = cursor;
Object.values(this.canvas).forEach((canvas) => {
canvas.setCursor(cursor);
});
}
public getSize(): [number, number] {
return [this.config.width || 0, this.config.height || 0];
public get ready() {
return Promise.all([
super.ready,
...Object.entries(this.getLayers()).map(([layer, canvas]) =>
layer === 'main' ? Promise.resolve() : canvas.ready,
),
]);
}
public resize(width: number, height: number) {
this.config.width = width;
this.config.height = height;
Object.values(this.canvas).forEach((canvas) => {
canvas.resize(width, height);
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);
});
}
public getCamera() {
return this.main.getCamera();
}
public getBounds() {
return getCombinedBBox(
Object.values(this.canvas)
Object.values(this.getLayers())
.map((canvas) => canvas.document.documentElement)
.filter((el) => el.childNodes.length > 0)
.map((el) => el.getBounds()),
@ -194,48 +132,38 @@ export class Canvas {
}
public getContainer() {
const container = this.config.container!;
return isString(container) ? document.getElementById(container!) : container;
const container = this.extends.config.container!;
return typeof container === 'string' ? document.getElementById(container!) : container;
}
public appendChild<T extends DisplayObject>(child: T): T {
const layer = (child.style?.$layer || 'main') as CanvasLayer;
return this[layer].appendChild(child);
public getSize(): [number, number] {
return [this.extends.config.width || 0, this.extends.config.height || 0];
}
public getContextService() {
return this.main.getContextService();
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 viewport2Client(viewport: PointObject) {
return this.main.viewport2Client(viewport);
}
public setRenderer(renderer: any) {
if (renderer === this.extends.renderer) return;
const renderers = createRenderers(renderer);
this.extends.renderers = renderers;
public viewport2Canvas(viewport: PointObject) {
return this.main.viewport2Canvas(viewport);
}
public client2Viewport(client: PointObject) {
return this.main.client2Viewport(client);
}
public canvas2Viewport(canvas: PointObject) {
return this.main.canvas2Viewport(canvas);
Object.entries(renderers).forEach(([layer, instance]) => {
if (layer === 'main') super.setRenderer(instance);
else this.getLayer(layer as CanvasLayer).setRenderer(instance);
});
}
public async toDataURL(options: Partial<DataURLOptions> = {}) {
const devicePixelRatio = window.devicePixelRatio || 1;
const { mode = 'viewport', ...restOptions } = options;
let startX = 0;
let startY = 0;
let width = 0;
let height = 0;
let [startX, startY, width, height] = [0, 0, 0, 0];
if (mode === 'viewport') {
[width, height] = [this.config.width || 0, this.config.height || 0];
[width, height] = this.getSize();
} else if (mode === 'overall') {
const bounds = this.getBounds();
const size = getBBoxSize(bounds);
@ -251,28 +179,28 @@ export class Canvas {
renderer: new CanvasRenderer(),
devicePixelRatio,
container,
background: this.config.background,
background: this.extends.config.background,
});
await offscreenCanvas.ready;
offscreenCanvas.appendChild(this.background.getRoot().cloneNode(true));
offscreenCanvas.appendChild(this.main.getRoot().cloneNode(true));
offscreenCanvas.appendChild(this.getLayer('background').getRoot().cloneNode(true));
offscreenCanvas.appendChild(this.getRoot().cloneNode(true));
// Handle label canvas
const label = this.label.getRoot().cloneNode(true);
const label = this.getLayer('label').getRoot().cloneNode(true);
const originCanvasPosition = offscreenCanvas.viewport2Canvas({ x: 0, y: 0 });
const currentCanvasPosition = this.main.viewport2Canvas({ x: 0, y: 0 });
const currentCanvasPosition = this.viewport2Canvas({ x: 0, y: 0 });
label.translate([
currentCanvasPosition.x - originCanvasPosition.x,
currentCanvasPosition.y - originCanvasPosition.y,
]);
label.scale(1 / this.main.getCamera().getZoom());
label.scale(1 / this.getCamera().getZoom());
offscreenCanvas.appendChild(label);
offscreenCanvas.appendChild(this.transient.getRoot().cloneNode(true));
offscreenCanvas.appendChild(this.getLayer('transient').getRoot().cloneNode(true));
const camera = this.main.getCamera();
const camera = this.getCamera();
const offscreenCamera = offscreenCanvas.getCamera();
if (mode === 'viewport') {
@ -289,7 +217,7 @@ export class Canvas {
const contextService = offscreenCanvas.getContextService();
return new Promise<string>((resolve) => {
offscreenCanvas.on(CanvasEvent.RERENDER, async () => {
offscreenCanvas.addEventListener(CanvasEvent.RERENDER, async () => {
// 等待图片渲染完成 / Wait for the image to render
await new Promise((r) => setTimeout(r, 300));
const url = await contextService.toDataURL(restOptions);
@ -298,57 +226,60 @@ export class Canvas {
});
}
public destroy() {
this.config = {};
// @ts-expect-error force delete
this.renderers = {};
Object.entries(this.canvas).forEach(([name, canvas]) => {
public destroy(cleanUp?: boolean, skipTriggerEvent?: boolean) {
Object.values(this.getLayers()).forEach((canvas) => {
const camera = canvas.getCamera();
// @ts-expect-error landmark is private
if (camera.landmarks?.length) {
camera.cancelLandmarkAnimation();
}
destroyCanvas(canvas);
// @ts-expect-error force delete
this[name] = undefined;
camera.cancelLandmarkAnimation();
if (canvas === this) super.destroy(cleanUp, skipTriggerEvent);
else canvas.destroy(cleanUp, skipTriggerEvent);
});
}
}
/**
* <zh/> G Canvas destroy
* <zh/>
*
* <en/> G Canvas destroy does not handle animation objects, causing memory leaks
* @param canvas GCanvas
* @remarks
* <zh/> G
* 1020% 2800MB 2200MB
*
* <en/> These operations should be completed in G, this is just a temporary solution
* This operation can reduce memory usage by 10-20% in the test environment (from 2800MB to 2200MB)
* <en/> Create renderers
* @param renderer - <zh/> <en/> Renderer creator
* @returns <zh/> <en/> Renderer
*/
function destroyCanvas(canvas: GCanvas) {
canvas.destroy();
function createRenderers(renderer: CanvasConfig['renderer']) {
return Object.fromEntries(
layersName.map((layer) => {
const instance = renderer?.(layer) || new CanvasRenderer();
// 移除相机事件 / Remove camera events
const camera = canvas.getCamera();
camera.eventEmitter.removeAllListeners();
if (layer === 'main') {
instance.registerPlugin(
new DragNDropPlugin({
isDocumentDraggable: true,
isDocumentDroppable: true,
dragstartDistanceThreshold: 10,
dragstartTimeThreshold: 100,
}),
);
} else {
instance.unregisterPlugin(instance.getPlugin('dom-interaction'));
}
canvas.document.timeline.destroy();
// @ts-expect-error private property
const { animationsWithPromises } = canvas.document.timeline;
// 释放 target 对象图形 / Release target object graphics
animationsWithPromises.forEach((animation: IAnimation) => {
if (animation.effect.target) animation.effect.target = null;
// @ts-expect-error private property
if (animation.effect.computedTiming) animation.effect.computedTiming = null;
});
// @ts-expect-error private property
canvas.document.timeline.animationsWithPromises = [];
// @ts-expect-error private property
canvas.document.timeline.rafCallbacks = [];
// @ts-expect-error private property
canvas.document.timeline = null;
return [layer, instance];
}),
) as Record<CanvasLayer, CanvasRenderer>;
}
/**
* <zh/> DOM
*
* <en/> Configure canvas DOM
* @param layers - <zh/> <en/> Canvas
*/
function configCanvasDom(layers: Record<CanvasLayer, GCanvas>) {
Object.entries(layers).forEach(([layer, canvas]) => {
const domElement = canvas.getContextService().getDomElement() as unknown as HTMLElement;
domElement.style.position = 'absolute';
domElement.style.outline = 'none';
domElement.tabIndex = 1;
if (layer !== 'main') domElement.style.pointerEvents = 'none';
});
}

View File

@ -3,7 +3,6 @@ import type { AABB, BaseStyleProps } from '@antv/g';
import { debounce, isEqual, isFunction, isNumber, isObject, isString, omit } from '@antv/util';
import { COMBO_KEY, GraphEvent } from '../constants';
import type { Plugin } from '../plugins/types';
import { getExtension } from '../registry';
import type {
BehaviorOptions,
ComboData,
@ -119,12 +118,10 @@ export class Graph extends EventEmitter {
* @apiCategory option
*/
public setOptions(options: GraphOptions): void {
const { background, behaviors, combo, data, edge, height, layout, node, plugins, theme, transforms, width } =
options;
const { behaviors, combo, data, edge, height, layout, node, plugins, theme, transforms, width, renderer } = options;
Object.assign(this.options, options);
if (background) this.setBackground(background);
if (behaviors) this.setBehaviors(behaviors);
if (combo) this.setCombo(combo);
if (data) this.setData(data);
@ -136,29 +133,7 @@ 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);
}
/**
* <zh/>
*
* <en/> Set canvas background color
* @param background - <zh/> | <en/> background color
* @apiCategory canvas
*/
public setBackground(background: GraphOptions['background']): void {
this.options.background = background;
this.context.canvas?.setBackground(background);
}
/**
* <zh/>
*
* <en/> Get canvas background color
* @returns <zh/> | <en/> background color
* @apiCategory canvas
*/
public getBackground(): GraphOptions['background'] {
return this.options.background;
if (renderer) this.context.canvas.setRenderer(renderer);
}
/**
@ -280,11 +255,6 @@ export class Graph extends EventEmitter {
*/
public setTheme(theme: ThemeOptions | ((prev: ThemeOptions) => ThemeOptions)): void {
this.options.theme = isFunction(theme) ? theme(this.getTheme()) : theme;
const { background } = getExtension('theme', this.options.theme) || {};
if (background && !this.options.background) {
this.setBackground(background);
}
}
/**
@ -1034,14 +1004,13 @@ export class Graph extends EventEmitter {
}
private async initCanvas() {
if (this.context.canvas) return await this.context.canvas.init();
if (this.context.canvas) return await this.context.canvas.ready;
const { container = 'container', width, height, renderer, background } = this.options;
if (container instanceof Canvas) {
this.context.canvas = container;
container.setBackground(background);
await container.init();
await container.ready;
} else {
const $container = isString(container) ? document.getElementById(container!) : container;
const containerSize = sizeOf($container!);
@ -1057,7 +1026,7 @@ export class Graph extends EventEmitter {
});
this.context.canvas = canvas;
await canvas.init();
await canvas.ready;
this.emit(GraphEvent.AFTER_CANVAS_INIT, { canvas });
}
}

View File

@ -1,2 +1,2 @@
packages:
- 'packages/**'
- 'packages/*'