Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: use unified util to create plugin canvas #6524

Merged
merged 6 commits into from
Nov 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/g6/__tests__/demos/plugin-legend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const pluginLegend: TestCase = async (context) => {
}),
},
layout: { type: 'd3-force' },
behaviors: ['drag-canvas', 'drag-element'],
behaviors: ['drag-canvas', 'drag-element', 'zoom-canvas'],
node: {
type: (item: any) => {
if (item.data.cluster === 'a') return 'diamond';
Expand Down
299 changes: 1 addition & 298 deletions packages/g6/__tests__/snapshots/plugins/legend/click-again.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
299 changes: 1 addition & 298 deletions packages/g6/__tests__/snapshots/plugins/legend/click.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
299 changes: 1 addition & 298 deletions packages/g6/__tests__/snapshots/plugins/legend/mouseenter.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
299 changes: 1 addition & 298 deletions packages/g6/__tests__/snapshots/plugins/legend/mouseleave.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
299 changes: 1 addition & 298 deletions packages/g6/__tests__/snapshots/plugins/legend/normal.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
51 changes: 51 additions & 0 deletions packages/g6/__tests__/unit/plugins/utils/dom.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { createPluginContainer, insertDOM } from '@/src/plugins/utils/dom';

describe('plugin dom utils', () => {
it('createPluginContainer', () => {
const el = createPluginContainer('test');
expect(el.getAttribute('class')).toBe('g6-test');
expect(el.style.position).toBe('absolute');
expect(el.style.display).toBe('block');
expect(el.style.inset).toBe('0px');
expect(el.style.height).toBe('100%');
expect(el.style.width).toBe('100%');
expect(el.style.overflow).toBe('hidden');
expect(el.style.pointerEvents).toBe('none');
});

it('createPluginContainer cover=false', () => {
const el = createPluginContainer('test', false);
expect(el.getAttribute('class')).toBe('g6-test');
expect(el.style.position).toBe('absolute');
expect(el.style.display).toBe('block');
expect(el.style.height).not.toBe('100%');
expect(el.style.width).not.toBe('100%');
expect(el.style.overflow).not.toBe('hidden');
expect(el.style.pointerEvents).not.toBe('none');
});

it('createPluginContainer with style', () => {
const el = createPluginContainer('test', false, { color: 'red' });
expect(el.getAttribute('class')).toBe('g6-test');
expect(el.style.color).toBe('red');
});

it('insertDOM', () => {
insertDOM('g6-test', 'div', { color: 'red' }, 'test', document.body);

let el = document.getElementById('g6-test')!;
expect(el).toBeTruthy();
expect(el.style.color).toBe('red');
expect(el.innerHTML).toBe('test');

insertDOM('g6-test', 'div', { color: 'red' }, 'new html', document.body);

el = document.getElementById('g6-test')!;
expect(el.innerHTML).toBe('new html');

el = insertDOM('g6-test');
expect(el.tagName.toLowerCase()).toBe('div');
expect(el.innerHTML).toBe('');
expect(el.parentNode).toBe(document.body);
});
});
52 changes: 2 additions & 50 deletions packages/g6/__tests__/unit/utils/dom.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { createPluginContainer, insertDOM, sizeOf } from '@/src/utils/dom';
import { sizeOf } from '@/src/utils/dom';

describe('sizeOf', () => {
describe('dom', () => {
it('should return the size of the graph container', () => {
// Create a mock container element
const container = document.createElement('div');
Expand All @@ -13,52 +13,4 @@ describe('sizeOf', () => {
// Assert the result
expect(result).toEqual([500, 300]);
});

it('createPluginContainer', () => {
const el = createPluginContainer('test');
expect(el.getAttribute('class')).toBe('g6-test');
expect(el.style.position).toBe('absolute');
expect(el.style.display).toBe('block');
expect(el.style.inset).toBe('0px');
expect(el.style.height).toBe('100%');
expect(el.style.width).toBe('100%');
expect(el.style.overflow).toBe('hidden');
expect(el.style.pointerEvents).toBe('none');
});

it('createPluginContainer cover=false', () => {
const el = createPluginContainer('test', false);
expect(el.getAttribute('class')).toBe('g6-test');
expect(el.style.position).toBe('absolute');
expect(el.style.display).toBe('block');
expect(el.style.height).not.toBe('100%');
expect(el.style.width).not.toBe('100%');
expect(el.style.overflow).not.toBe('hidden');
expect(el.style.pointerEvents).not.toBe('none');
});

it('createPluginContainer with style', () => {
const el = createPluginContainer('test', false, { color: 'red' });
expect(el.getAttribute('class')).toBe('g6-test');
expect(el.style.color).toBe('red');
});

it('insertDOM', () => {
insertDOM('g6-test', 'div', { color: 'red' }, 'test', document.body);

let el = document.getElementById('g6-test')!;
expect(el).toBeTruthy();
expect(el.style.color).toBe('red');
expect(el.innerHTML).toBe('test');

insertDOM('g6-test', 'div', { color: 'red' }, 'new html', document.body);

el = document.getElementById('g6-test')!;
expect(el.innerHTML).toBe('new html');

el = insertDOM('g6-test');
expect(el.tagName.toLowerCase()).toBe('div');
expect(el.innerHTML).toBe('');
expect(el.parentNode).toBe(document.body);
});
});
2 changes: 1 addition & 1 deletion packages/g6/src/plugins/background/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { omit } from '@antv/util';
import type { RuntimeContext } from '../../runtime/types';
import { createPluginContainer } from '../../utils/dom';
import type { BasePluginOptions } from '../base-plugin';
import { BasePlugin } from '../base-plugin';
import { createPluginContainer } from '../utils/dom';

/**
* <zh/> 背景配置项
Expand Down
2 changes: 1 addition & 1 deletion packages/g6/src/plugins/contextmenu/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import type { RuntimeContext } from '../../runtime/types';
import type { Element } from '../../types';
import type { IElementEvent } from '../../types/event';
import { createPluginContainer, insertDOM } from '../../utils/dom';
import type { BasePluginOptions } from '../base-plugin';
import { BasePlugin } from '../base-plugin';
import { createPluginContainer, insertDOM } from '../utils/dom';
import type { Item } from './util';
import { CONTEXTMENU_CSS, getContentFromItems } from './util';
/**
Expand Down
8 changes: 5 additions & 3 deletions packages/g6/src/plugins/grid-line.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { GraphEvent } from '../constants';
import type { RuntimeContext } from '../runtime/types';
import type { Point } from '../types';
import { createPluginContainer, insertBefore } from '../utils/dom';
import { ViewportEvent } from '../utils/event';
import { add, mod } from '../utils/vector';
import { BasePlugin, BasePluginOptions } from './base-plugin';
import { createPluginContainer } from './utils/dom';

/**
* <zh/> 网格线配置项
Expand Down Expand Up @@ -98,15 +98,17 @@ export class GridLine extends BasePlugin<GridLineOptions> {
stroke: '#eee',
};

private $element: HTMLElement = createPluginContainer('grid-line');
private $element: HTMLElement = createPluginContainer('grid-line', true, {
zIndex: '-1',
});

private offset: Point = [0, 0];

constructor(context: RuntimeContext, options: GridLineOptions) {
super(context, Object.assign({}, GridLine.defaultOptions, options));

const $container = this.context.canvas.getContainer()!;
insertBefore($container, this.$element);
$container.prepend(this.$element);

this.updateStyle();
this.bindEvents();
Expand Down
122 changes: 67 additions & 55 deletions packages/g6/src/plugins/legend.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { Category, Layout, Selection } from '@antv/component';
import { Category, Selection } from '@antv/component';
import { CategoryStyleProps } from '@antv/component/lib/ui/legend/types';
import { Canvas } from '@antv/g';
import { get, isFunction } from '@antv/util';
import { GraphEvent } from '../constants';
import type { RuntimeContext } from '../runtime/types';
import type { ElementDatum, ElementType, ID, State } from '../types';
import type { CardinalPlacement } from '../types/placement';
import type { BasePluginOptions } from './base-plugin';
import { BasePlugin } from './base-plugin';
import { createPluginCanvas } from './utils/canvas';

interface Datum extends Record<string, any> {
id?: string;
Expand Down Expand Up @@ -40,6 +42,24 @@ export interface LegendOptions extends BasePluginOptions, Omit<CategoryStyleProp
* @defaultValue 'bottom'
*/
position?: CardinalPlacement;
/**
* <zh/> 图例挂载的容器,无则挂载到 Graph 所在容器
*
* <en/> The container where the legend is mounted, if not, it will be mounted to the container where the Graph is located
*/
container?: HTMLElement | string;
/**
* <zh/> 图例画布类名,传入外置容器时不生效
*
* <en/> The class name of the legend canvas, which does not take effect when an external container is passed in
*/
className?: string;
/**
* <zh/> 图例的容器样式,传入外置容器时不生效
*
* <en/> The style of the legend container, which does not take effect when an external container is passed in
*/
containerStyle?: Partial<CSSStyleDeclaration>;
/**
* <zh/> 节点分类标识
*
Expand Down Expand Up @@ -80,16 +100,20 @@ export class Legend extends BasePlugin<LegendOptions> {
colPadding: 10,
itemMarkerSize: 16,
itemLabelFontSize: 16,
width: 240,
height: 160,
};
private typePrefix = '__data__';
private element: Layout | null = null;
private draw = false;
private fieldMap = {
node: new Map<string, ID[]>(),
edge: new Map<string, ID[]>(),
combo: new Map<string, ID[]>(),
};
private selectedItems: string[] = [];
private category?: Category;
private container?: HTMLElement;
private canvas?: Canvas;

constructor(context: RuntimeContext, options: LegendOptions) {
super(context, Object.assign({}, Legend.defaultOptions, options));
Expand All @@ -110,8 +134,11 @@ export class Legend extends BasePlugin<LegendOptions> {
}

private clear() {
this.element?.destroy();
this.element = null;
this.canvas?.destroy();
this.container?.remove();
this.canvas = undefined;
this.container = undefined;

this.draw = false;
}

Expand Down Expand Up @@ -184,10 +211,9 @@ export class Legend extends BasePlugin<LegendOptions> {
* <en/> Refresh the status of the legend element
*/
public updateElement() {
if (!this.element) return;
const category = this.element.getChildByIndex(0) as Category;
if (!this.category) return;

category.update({
this.category.update({
itemMarkerOpacity: ({ id }) => {
if (!this.selectedItems.length || this.selectedItems.includes(id)) return 1;
return 0.5;
Expand Down Expand Up @@ -224,7 +250,7 @@ export class Legend extends BasePlugin<LegendOptions> {

private getMarkerData = (field: string | ((item: ElementDatum) => string), elementType: ElementType) => {
if (!field) return [];
const { model, element, graph } = this.context;
const { model, element } = this.context;
const { nodes, edges, combos } = model.getData();
const items: { [key: string]: Datum } = {};

Expand All @@ -239,7 +265,8 @@ export class Legend extends BasePlugin<LegendOptions> {
combo: 'rect',
};

/** 用于将 G6 element 转换为 componets 支持的类型 */
// 用于将 G6 element 转换为 components 支持的类型
// Used to convert G6 element to types supported by components
const markerMapping: { [key: string]: string } = {
circle: 'circle',
ellipse: 'circle', // 待 components 支持 ellipse
Expand Down Expand Up @@ -303,69 +330,52 @@ export class Legend extends BasePlugin<LegendOptions> {
return Object.values(items);
};

/**
* <zh/> 图例布局
*
* <en/> Legend layout
* @param position - <zh/> 图例位置| <en/> Legend position
* @returns <zh/> 图例布局样式| <en/> Legend layout style
*/
public layout = (position: CardinalPlacement) => {
const preset = {
flexDirection: 'row',
alignItems: 'flex-end',
justifyContent: 'center',
};
let { flexDirection, alignItems, justifyContent } = preset;
private upsertCanvas() {
if (this.canvas) return this.canvas;

const layout = {
top: ['row', 'flex-start', 'center'],
bottom: ['row', 'flex-end', 'center'],
left: ['column', 'flex-start', 'center'],
right: ['column', 'flex-end', 'center'],
};
const graphCanvas = this.context.canvas;
const [canvasWidth, canvasHeight] = graphCanvas.getSize();

if (position in layout) {
[flexDirection, alignItems, justifyContent] = layout[position];
}
return {
display: 'flex',
flexDirection,
justifyContent,
alignItems,
};
};
const { width = canvasWidth, height = canvasHeight, position, container, containerStyle, className } = this.options;
const [$container, canvas] = createPluginCanvas({
width,
height,
graphCanvas,
container,
containerStyle,
placement: position,
className: 'legend',
});

this.container = $container;
if (className) $container.classList.add(className);
this.canvas = canvas;

return this.canvas;
}

private createElement = () => {
if (this.draw) {
this.updateElement();
return;
}
const { canvas } = this.context;
const [canvasWidth, canvasHeight] = canvas.getSize();
const {
width = canvasWidth,
height = canvasHeight,
width,
height,
nodeField,
edgeField,
comboField,
trigger,
position,
container,
containerStyle,
className,
...rest
} = this.options;
const nodeItems = this.getMarkerData(nodeField, 'node');
const edgeItems = this.getMarkerData(edgeField, 'edge');
const comboItems = this.getMarkerData(comboField, 'combo');
const items = [...nodeItems, ...comboItems, ...edgeItems];
const layout = this.layout(position);

const layoutWrapper = new Layout({
style: {
width,
height,
...layout,
},
});

const categoryStyle = Object.assign(
{
Expand All @@ -386,9 +396,11 @@ export class Legend extends BasePlugin<LegendOptions> {
className: 'legend',
style: categoryStyle,
});
layoutWrapper.appendChild(category);
canvas.appendChild(layoutWrapper as any);
this.element = layoutWrapper;
this.category = category;

const canvas = this.upsertCanvas();
canvas.appendChild(category);

this.draw = true;
};

Expand Down
Loading
Loading