feat(plugin): add edge filter lens plugin (#6224)
* feat(plugin): add edge filter lens * test: add unit tests * refactor(plugins): fix cr issues * test: update unit test * docs: add demo * fix: update demo and tests
@ -113,6 +113,7 @@ export { pluginBackground } from './plugin-background';
|
||||
export { pluginBubbleSets } from './plugin-bubble-sets';
|
||||
export { pluginCameraSetting } from './plugin-camera-setting';
|
||||
export { pluginContextmenu } from './plugin-contextmenu';
|
||||
export { pluginEdgeFilterLens } from './plugin-edge-filter-lens';
|
||||
export { pluginFullscreen } from './plugin-fullscreen';
|
||||
export { pluginGridLine } from './plugin-grid-line';
|
||||
export { pluginHistory } from './plugin-history';
|
||||
|
33
packages/g6/__tests__/demos/plugin-edge-filter-lens.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import data from '@@/dataset/relations.json';
|
||||
import { Graph } from '@antv/g6';
|
||||
|
||||
export const pluginEdgeFilterLens: TestCase = async (context) => {
|
||||
const graph = new Graph({
|
||||
...context,
|
||||
data,
|
||||
node: {
|
||||
style: { size: 16 },
|
||||
palette: {
|
||||
field: (datum) => Math.floor(Number(datum.style?.y) / 60),
|
||||
},
|
||||
},
|
||||
edge: {
|
||||
style: {
|
||||
label: false,
|
||||
labelText: (d) => d.data!.value?.toString(),
|
||||
stroke: '#ccc',
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
{
|
||||
key: 'edge-filter-lens',
|
||||
type: 'edge-filter-lens',
|
||||
},
|
||||
],
|
||||
autoFit: 'view',
|
||||
});
|
||||
|
||||
await graph.render();
|
||||
|
||||
return graph;
|
||||
};
|
2172
packages/g6/__tests__/snapshots/plugins/edge-filter-lens/default.svg
Normal file
After Width: | Height: | Size: 158 KiB |
After Width: | Height: | Size: 159 KiB |
After Width: | Height: | Size: 162 KiB |
After Width: | Height: | Size: 162 KiB |
After Width: | Height: | Size: 162 KiB |
After Width: | Height: | Size: 160 KiB |
After Width: | Height: | Size: 160 KiB |
After Width: | Height: | Size: 160 KiB |
After Width: | Height: | Size: 162 KiB |
After Width: | Height: | Size: 162 KiB |
77
packages/g6/__tests__/unit/plugins/edge-filter-lens.spec.ts
Normal file
@ -0,0 +1,77 @@
|
||||
import { pluginEdgeFilterLens } from '@@/demos';
|
||||
import { CommonEvent, Graph } from '@antv/g6';
|
||||
import { createDemoGraph, dispatchCanvasEvent } from '../../utils';
|
||||
|
||||
describe('edge-filter-lens', () => {
|
||||
let graph: Graph;
|
||||
|
||||
beforeAll(async () => {
|
||||
graph = await createDemoGraph(pluginEdgeFilterLens, { animation: false });
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
graph.destroy();
|
||||
});
|
||||
|
||||
it('move lens by pointermove', async () => {
|
||||
await expect(graph).toMatchSnapshot(__filename);
|
||||
|
||||
dispatchCanvasEvent(graph, CommonEvent.POINTER_MOVE, { canvas: { x: 200, y: 100 } });
|
||||
|
||||
await expect(graph).toMatchSnapshot(__filename, 'move-lens-pointermove');
|
||||
});
|
||||
|
||||
it('move lens by click', async () => {
|
||||
graph.updatePlugin({ key: 'edge-filter-lens', trigger: 'click' });
|
||||
|
||||
dispatchCanvasEvent(graph, CommonEvent.CLICK, { canvas: { x: 180, y: 100 } });
|
||||
await expect(graph).toMatchSnapshot(__filename, 'move-lens-click-1');
|
||||
|
||||
dispatchCanvasEvent(graph, CommonEvent.CLICK, { canvas: { x: 200, y: 100 } });
|
||||
await expect(graph).toMatchSnapshot(__filename, 'move-lens-click-2');
|
||||
});
|
||||
|
||||
it('move lens by drag', async () => {
|
||||
graph.updatePlugin({ key: 'edge-filter-lens', trigger: 'drag' });
|
||||
|
||||
dispatchCanvasEvent(graph, CommonEvent.CLICK, { canvas: { x: 180, y: 100 } });
|
||||
dispatchCanvasEvent(graph, CommonEvent.DRAG_START, { canvas: { x: 200, y: 100 } });
|
||||
dispatchCanvasEvent(graph, CommonEvent.DRAG, { canvas: { x: 220, y: 100 } });
|
||||
dispatchCanvasEvent(graph, CommonEvent.DRAG_END);
|
||||
|
||||
await expect(graph).toMatchSnapshot(__filename, 'move-lens-drag');
|
||||
});
|
||||
|
||||
it('scale lens by wheel', async () => {
|
||||
function emitWheelEvent(options?: { deltaX: number; deltaY: number; clientX: number; clientY: number }) {
|
||||
const dom = graph.getCanvas().getContextService().getDomElement();
|
||||
dom?.dispatchEvent(new WheelEvent(CommonEvent.WHEEL, options));
|
||||
}
|
||||
|
||||
emitWheelEvent({ deltaX: 1, deltaY: 2, clientX: 200, clientY: 100 });
|
||||
emitWheelEvent({ deltaX: 1, deltaY: 2, clientX: 200, clientY: 100 });
|
||||
|
||||
await expect(graph).toMatchSnapshot(__filename, 'scale-larger');
|
||||
|
||||
emitWheelEvent({ deltaX: -1, deltaY: -2, clientX: 200, clientY: 100 });
|
||||
emitWheelEvent({ deltaX: -1, deltaY: -2, clientX: 200, clientY: 100 });
|
||||
emitWheelEvent({ deltaX: -1, deltaY: -2, clientX: 200, clientY: 100 });
|
||||
emitWheelEvent({ deltaX: -1, deltaY: -2, clientX: 200, clientY: 100 });
|
||||
|
||||
await expect(graph).toMatchSnapshot(__filename, 'scale-smaller');
|
||||
});
|
||||
|
||||
it('show edge when only its source/target node in lens', async () => {
|
||||
graph.updatePlugin({ key: 'edge-filter-lens', trigger: 'click', nodeType: 'source' });
|
||||
dispatchCanvasEvent(graph, CommonEvent.CLICK, { canvas: { x: 200, y: 200 } });
|
||||
await expect(graph).toMatchSnapshot(__filename, 'node-type-source');
|
||||
|
||||
graph.updatePlugin({ key: 'edge-filter-lens', trigger: 'click', nodeType: 'target' });
|
||||
dispatchCanvasEvent(graph, CommonEvent.CLICK, { canvas: { x: 200, y: 200 } });
|
||||
await expect(graph).toMatchSnapshot(__filename, 'node-type-target');
|
||||
|
||||
graph.updatePlugin({ key: 'edge-filter-lens', trigger: 'click', nodeType: 'either' });
|
||||
dispatchCanvasEvent(graph, CommonEvent.CLICK, { canvas: { x: 200, y: 200 } });
|
||||
await expect(graph).toMatchSnapshot(__filename, 'node-type-either');
|
||||
});
|
||||
});
|
@ -67,6 +67,7 @@ export {
|
||||
BubbleSets,
|
||||
CameraSetting,
|
||||
Contextmenu,
|
||||
EdgeFilterLens,
|
||||
Fullscreen,
|
||||
GridLine,
|
||||
History,
|
||||
@ -168,6 +169,7 @@ export type {
|
||||
BubbleSetsOptions,
|
||||
CameraSettingOptions,
|
||||
ContextmenuOptions,
|
||||
EdgeFilterLensOptions,
|
||||
FullscreenOptions,
|
||||
GridLineOptions,
|
||||
HistoryOptions,
|
||||
|
391
packages/g6/src/plugins/edge-filter-lens/index.ts
Normal file
@ -0,0 +1,391 @@
|
||||
import type { BaseStyleProps } from '@antv/g';
|
||||
import { CommonEvent } from '../../constants';
|
||||
import { Circle } from '../../elements';
|
||||
import type { RuntimeContext } from '../../runtime/types';
|
||||
import type { EdgeData, GraphData, NodeData } from '../../spec';
|
||||
import type { EdgeStyle } from '../../spec/element/edge';
|
||||
import type { NodeStyle } from '../../spec/element/node';
|
||||
import type {
|
||||
Element,
|
||||
ElementDatum,
|
||||
ElementType,
|
||||
ID,
|
||||
IDragEvent,
|
||||
IPointerEvent,
|
||||
Point,
|
||||
PointObject,
|
||||
} from '../../types';
|
||||
import { idOf } from '../../utils/id';
|
||||
import { parsePoint } from '../../utils/point';
|
||||
import { positionOf } from '../../utils/position';
|
||||
import { distance } from '../../utils/vector';
|
||||
import type { BasePluginOptions } from '../base-plugin';
|
||||
import { BasePlugin } from '../base-plugin';
|
||||
|
||||
export interface EdgeFilterLensOptions extends BasePluginOptions {
|
||||
/**
|
||||
* <zh/> 移动透镜的方式
|
||||
* - `'pointermove'`:始终跟随鼠标移动
|
||||
* - `'click'`:鼠标点击时透镜移动
|
||||
* - `'drag'`:拖拽透镜
|
||||
*
|
||||
* <en/> The way to move the lens
|
||||
* - `'pointermove'`: always follow the mouse movement
|
||||
* - `'click'`: move the lens when the mouse clicks
|
||||
* - `'drag'`: drag the lens
|
||||
* @defaultValue 'pointermove'
|
||||
*/
|
||||
trigger?: 'pointermove' | 'click' | 'drag';
|
||||
/**
|
||||
* <zh/> 透镜的半径
|
||||
*
|
||||
* <en/> The radius of the lens
|
||||
* @defaultValue 60
|
||||
*/
|
||||
r?: number;
|
||||
/**
|
||||
* <zh/> 透镜的最大半径。只有在开启 `scaleRByWheel` 时生效
|
||||
*
|
||||
* <en/> The maximum radius of the lens. Only valid when `scaleRByWheel` is enabled
|
||||
* @defaultValue canvas 宽高最小值的一半
|
||||
*/
|
||||
maxR?: number;
|
||||
/**
|
||||
* <zh/> 透镜的最小半径。只有在开启 `scaleRByWheel` 时生效
|
||||
*
|
||||
* <en/> The minimum radius of the lens. Only valid when `scaleRByWheel` is enabled
|
||||
* @defaultValue 0
|
||||
*/
|
||||
minR?: number;
|
||||
/**
|
||||
* <zh/> 是否通过滚轮缩放透镜的半径
|
||||
*
|
||||
* <en/> Whether to scale the radius of the lens by wheel
|
||||
* @defaultValue true
|
||||
*/
|
||||
scaleRByWheel?: boolean;
|
||||
/**
|
||||
* <zh/> 边显示的条件
|
||||
* - `'both'`:只有起始节点和目标节点都在透镜中时,边才会显示
|
||||
* - `'source'`:只有起始节点在透镜中时,边才会显示
|
||||
* - `'target'`:只有目标节点在透镜中时,边才会显示
|
||||
* - `'either'`:只要起始节点或目标节点有一个在透镜中时,边就会显示
|
||||
*
|
||||
* <zh/> The condition for displaying the edge
|
||||
* - `'both'`: The edge is displayed only when both the source node and the target node are in the lens
|
||||
* - `'source'`: The edge is displayed only when the source node is in the lens
|
||||
* - `'target'`: The edge is displayed only when the target node is in the lens
|
||||
* - `'either'`: The edge is displayed when either the source node or the target node is in the lens
|
||||
* @defaultValue 'both'
|
||||
*/
|
||||
nodeType?: 'both' | 'source' | 'target' | 'either';
|
||||
/**
|
||||
* <zh/> 过滤出始终不在透镜中显示的元素
|
||||
*
|
||||
* <en/> Filter elements that are never displayed in the lens
|
||||
* @param id - <zh/> 元素的 id | <en/> The id of the element
|
||||
* @param elementType - <zh/> 元素的类型 | <en/> The type of the element
|
||||
* @returns <zh/> 是否显示 | <en/> Whether to display
|
||||
*/
|
||||
filter?: (id: ID, elementType: ElementType) => boolean;
|
||||
/**
|
||||
* <zh/> 透镜的样式
|
||||
*
|
||||
* <en/> The style of the lens
|
||||
*/
|
||||
style?: BaseStyleProps;
|
||||
/**
|
||||
* <zh/> 在透镜中节点的样式
|
||||
*
|
||||
* <en/> The style of the nodes displayed in the lens
|
||||
*/
|
||||
nodeStyle?: NodeStyle | ((datum: NodeData) => NodeStyle);
|
||||
/**
|
||||
* <zh/> 在透镜中边的样式
|
||||
*
|
||||
* <en/> The style of the edges displayed in the lens
|
||||
*/
|
||||
edgeStyle?: EdgeStyle | ((datum: EdgeData) => EdgeStyle);
|
||||
/**
|
||||
* <zh/> 是否阻止默认事件
|
||||
*
|
||||
* <en/> Whether to prevent the default event
|
||||
* @defaultValue true
|
||||
*/
|
||||
preventDefault?: boolean;
|
||||
}
|
||||
|
||||
const defaultLensStyle: BaseStyleProps = {
|
||||
fill: '#fff',
|
||||
fillOpacity: 1,
|
||||
lineWidth: 1,
|
||||
stroke: '#000',
|
||||
strokeOpacity: 0.8,
|
||||
};
|
||||
|
||||
const DELTA = 0.05;
|
||||
|
||||
export class EdgeFilterLens extends BasePlugin<EdgeFilterLensOptions> {
|
||||
static defaultOptions: Partial<EdgeFilterLensOptions> = {
|
||||
trigger: 'pointermove',
|
||||
r: 60,
|
||||
nodeType: 'both',
|
||||
filter: () => true,
|
||||
style: { lineWidth: 2 },
|
||||
nodeStyle: { label: false },
|
||||
edgeStyle: { label: true },
|
||||
scaleRByWheel: true,
|
||||
preventDefault: true,
|
||||
};
|
||||
|
||||
constructor(context: RuntimeContext, options: EdgeFilterLensOptions) {
|
||||
super(context, Object.assign({}, EdgeFilterLens.defaultOptions, options));
|
||||
this.bindEvents();
|
||||
}
|
||||
|
||||
private lens!: Circle;
|
||||
|
||||
private shapes = new Map<ID, Element>();
|
||||
|
||||
private r = this.options.r;
|
||||
|
||||
private get canvas() {
|
||||
return this.context.canvas.getLayer('transient');
|
||||
}
|
||||
|
||||
private get isLensOn() {
|
||||
return this.lens && !this.lens.destroyed;
|
||||
}
|
||||
|
||||
protected onEdgeFilter = (event: IPointerEvent) => {
|
||||
if (this.options.trigger === 'drag' && this.isLensOn) return;
|
||||
|
||||
const origin = parsePoint(event.canvas as PointObject);
|
||||
this.renderLens(origin);
|
||||
this.renderFocusElements();
|
||||
};
|
||||
|
||||
private renderLens = (origin: Point) => {
|
||||
const [x, y] = origin;
|
||||
const positionStyle = { size: this.r * 2, x, y };
|
||||
|
||||
if (!this.isLensOn) {
|
||||
const style = Object.assign({}, defaultLensStyle, this.options.style, positionStyle);
|
||||
this.lens = new Circle({ style });
|
||||
} else {
|
||||
this.lens.update(positionStyle);
|
||||
}
|
||||
|
||||
this.canvas.appendChild(this.lens);
|
||||
};
|
||||
|
||||
private getFilterData = (): Required<GraphData> => {
|
||||
const { filter } = this.options;
|
||||
const { model } = this.context;
|
||||
const data = model.getData();
|
||||
|
||||
if (!filter) return data;
|
||||
|
||||
const { nodes, edges, combos } = data;
|
||||
|
||||
return {
|
||||
nodes: nodes.filter((node) => filter(idOf(node), 'node')),
|
||||
edges: edges.filter((edge) => filter(idOf(edge), 'edge')),
|
||||
combos: combos.filter((combo) => filter(idOf(combo), 'combo')),
|
||||
};
|
||||
};
|
||||
|
||||
private getFocusElements = (origin: Point) => {
|
||||
const { nodes, edges } = this.getFilterData();
|
||||
|
||||
const focusNodes = nodes.filter((datum) => distance(positionOf(datum), origin) < this.r);
|
||||
const focusNodeIds = focusNodes.map((node) => idOf(node));
|
||||
|
||||
const focusEdges = edges.filter((datum) => {
|
||||
const { source, target } = datum;
|
||||
const isSourceFocus = focusNodeIds.includes(source);
|
||||
const isTargetFocus = focusNodeIds.includes(target);
|
||||
|
||||
switch (this.options.nodeType) {
|
||||
case 'both':
|
||||
return isSourceFocus && isTargetFocus;
|
||||
case 'either':
|
||||
return isSourceFocus !== isTargetFocus;
|
||||
case 'source':
|
||||
return isSourceFocus && !isTargetFocus;
|
||||
case 'target':
|
||||
return !isSourceFocus && isTargetFocus;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
return { nodes: focusNodes, edges: focusEdges };
|
||||
};
|
||||
|
||||
private renderFocusElements = () => {
|
||||
const { element, graph } = this.context;
|
||||
if (!this.isLensOn) return;
|
||||
|
||||
const origin = this.lens.getCenter();
|
||||
const { nodes, edges } = this.getFocusElements(origin);
|
||||
|
||||
const ids = new Set<ID>();
|
||||
|
||||
const { nodeStyle, edgeStyle } = this.options;
|
||||
|
||||
const iterate = (datum: ElementDatum) => {
|
||||
const id = idOf(datum);
|
||||
ids.add(id);
|
||||
|
||||
const shape = element!.getElement(id);
|
||||
if (!shape) return;
|
||||
|
||||
const cloneShape = this.shapes.get(id) || shape.cloneNode();
|
||||
|
||||
cloneShape.setPosition(shape.getPosition());
|
||||
cloneShape.id = shape.id;
|
||||
|
||||
if (!this.shapes.has(id)) {
|
||||
this.canvas.appendChild(cloneShape);
|
||||
this.shapes.set(id, cloneShape);
|
||||
} else {
|
||||
Object.entries(shape.attributes).forEach(([key, value]) => {
|
||||
if (cloneShape.style[key] !== value) cloneShape.style[key] = value;
|
||||
});
|
||||
}
|
||||
|
||||
const elementType = graph.getElementType(id) as Exclude<ElementType, 'combo'>;
|
||||
const style = this.getElementStyle(elementType, datum);
|
||||
|
||||
// @ts-ignore
|
||||
cloneShape.update(style);
|
||||
};
|
||||
|
||||
nodes.forEach(iterate);
|
||||
edges.forEach(iterate);
|
||||
|
||||
this.shapes.forEach((shape, id) => {
|
||||
if (!ids.has(id)) {
|
||||
shape.destroy();
|
||||
this.shapes.delete(id);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
private getElementStyle(elementType: ElementType, datum: ElementDatum) {
|
||||
const styler = elementType === 'node' ? this.options.nodeStyle : this.options.edgeStyle;
|
||||
if (typeof styler === 'function') return styler(datum as any);
|
||||
return styler;
|
||||
}
|
||||
|
||||
private scaleRByWheel = (event: WheelEvent) => {
|
||||
if (this.options.preventDefault) event.preventDefault();
|
||||
const { clientX, clientY, deltaX, deltaY } = event;
|
||||
const { graph, canvas } = this.context;
|
||||
const scaleOrigin = graph.getCanvasByClient([clientX, clientY]);
|
||||
const origin = this.lens?.getCenter();
|
||||
|
||||
if (!this.isLensOn || distance(scaleOrigin, origin) > this.r) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { maxR, minR } = this.options;
|
||||
const ratio = deltaX + deltaY > 0 ? 1 / (1 - DELTA) : 1 - DELTA;
|
||||
const canvasR = Math.min(...canvas.getSize()) / 2;
|
||||
this.r = Math.max(minR || 0, Math.min(maxR || canvasR, this.r * ratio));
|
||||
|
||||
this.renderLens(origin);
|
||||
this.renderFocusElements();
|
||||
};
|
||||
|
||||
get graphDom() {
|
||||
return this.context.graph.getCanvas().getContextService().getDomElement();
|
||||
}
|
||||
|
||||
private isLensDragging = false;
|
||||
|
||||
private onDragStart = (event: IDragEvent) => {
|
||||
const dragOrigin = parsePoint(event.canvas as PointObject);
|
||||
const origin = this.lens?.getCenter();
|
||||
|
||||
if (!this.isLensOn || distance(dragOrigin, origin) > this.r) return;
|
||||
|
||||
this.isLensDragging = true;
|
||||
};
|
||||
|
||||
private onDrag = (event: IDragEvent) => {
|
||||
if (!this.isLensDragging) return;
|
||||
|
||||
const dragOrigin = parsePoint(event.canvas as PointObject);
|
||||
this.renderLens(dragOrigin);
|
||||
this.renderFocusElements();
|
||||
};
|
||||
|
||||
private onDragEnd = () => {
|
||||
this.isLensDragging = false;
|
||||
};
|
||||
|
||||
private bindEvents() {
|
||||
const { graph } = this.context;
|
||||
const { trigger, scaleRByWheel } = this.options;
|
||||
|
||||
const canvas = graph.getCanvas().getLayer();
|
||||
|
||||
if (['click', 'drag'].includes(trigger)) {
|
||||
canvas.addEventListener(CommonEvent.CLICK, this.onEdgeFilter);
|
||||
}
|
||||
|
||||
if (trigger === 'pointermove') {
|
||||
canvas.addEventListener(CommonEvent.POINTER_MOVE, this.onEdgeFilter);
|
||||
} else if (trigger === 'drag') {
|
||||
canvas.addEventListener(CommonEvent.DRAG_START, this.onDragStart);
|
||||
canvas.addEventListener(CommonEvent.DRAG, this.onDrag);
|
||||
canvas.addEventListener(CommonEvent.DRAG_END, this.onDragEnd);
|
||||
}
|
||||
|
||||
if (scaleRByWheel) {
|
||||
this.graphDom?.addEventListener(CommonEvent.WHEEL, this.scaleRByWheel, { passive: false });
|
||||
}
|
||||
}
|
||||
|
||||
private unbindEvents() {
|
||||
const { graph } = this.context;
|
||||
const { trigger, scaleRByWheel } = this.options;
|
||||
const canvas = graph.getCanvas().getLayer();
|
||||
|
||||
if (['click', 'drag'].includes(trigger)) {
|
||||
canvas.removeEventListener(CommonEvent.CLICK, this.onEdgeFilter);
|
||||
}
|
||||
|
||||
if (trigger === 'pointermove') {
|
||||
canvas.removeEventListener(CommonEvent.POINTER_MOVE, this.onEdgeFilter);
|
||||
} else if (trigger === 'drag') {
|
||||
canvas.removeEventListener(CommonEvent.DRAG_START, this.onDragStart);
|
||||
canvas.removeEventListener(CommonEvent.DRAG, this.onDrag);
|
||||
canvas.removeEventListener(CommonEvent.DRAG_END, this.onDragEnd);
|
||||
}
|
||||
|
||||
if (scaleRByWheel) {
|
||||
this.graphDom?.removeEventListener(CommonEvent.WHEEL, this.scaleRByWheel);
|
||||
}
|
||||
}
|
||||
|
||||
public update(options: Partial<EdgeFilterLensOptions>) {
|
||||
this.unbindEvents();
|
||||
super.update(options);
|
||||
this.bindEvents();
|
||||
}
|
||||
|
||||
public destroy() {
|
||||
this.unbindEvents();
|
||||
if (this.isLensOn) {
|
||||
this.lens.destroy();
|
||||
}
|
||||
this.shapes.forEach((shape, id) => {
|
||||
shape.destroy();
|
||||
this.shapes.delete(id);
|
||||
});
|
||||
super.destroy();
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@ export { BasePlugin } from './base-plugin';
|
||||
export { BubbleSets } from './bubble-sets';
|
||||
export { CameraSetting } from './camera-setting';
|
||||
export { Contextmenu } from './contextmenu';
|
||||
export { EdgeFilterLens } from './edge-filter-lens';
|
||||
export { Fullscreen } from './fullscreen';
|
||||
export { GridLine } from './grid-line';
|
||||
export { History } from './history';
|
||||
@ -20,6 +21,7 @@ export type { BasePluginOptions } from './base-plugin';
|
||||
export type { BubbleSetsOptions } from './bubble-sets';
|
||||
export type { CameraSettingOptions } from './camera-setting';
|
||||
export type { ContextmenuOptions } from './contextmenu';
|
||||
export type { EdgeFilterLensOptions } from './edge-filter-lens';
|
||||
export type { FullscreenOptions } from './fullscreen';
|
||||
export type { GridLineOptions } from './grid-line';
|
||||
export type { HistoryOptions } from './history';
|
||||
|
@ -59,6 +59,7 @@ import {
|
||||
Background,
|
||||
BubbleSets,
|
||||
Contextmenu,
|
||||
EdgeFilterLens,
|
||||
Fullscreen,
|
||||
GridLine,
|
||||
History,
|
||||
@ -169,6 +170,7 @@ const BUILT_IN_EXTENSIONS: ExtensionRegistry = {
|
||||
},
|
||||
plugin: {
|
||||
'bubble-sets': BubbleSets,
|
||||
'edge-filter-lens': EdgeFilterLens,
|
||||
'grid-line': GridLine,
|
||||
background: Background,
|
||||
contextmenu: Contextmenu,
|
||||
|
67
packages/site/examples/plugin/edge-filter-lens/demo/basic.js
Normal file
@ -0,0 +1,67 @@
|
||||
import { Graph } from '@antv/g6';
|
||||
|
||||
fetch('https://assets.antv.antgroup.com/g6/relations.json')
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
const graph = new Graph({
|
||||
container: 'container',
|
||||
data,
|
||||
autoFit: 'view',
|
||||
node: {
|
||||
style: { size: 16 },
|
||||
palette: {
|
||||
field: (datum) => Math.floor(datum.style?.y / 60),
|
||||
},
|
||||
},
|
||||
edge: {
|
||||
style: {
|
||||
label: false,
|
||||
labelText: (d) => d.data.value?.toString(),
|
||||
stroke: '#ccc',
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
{
|
||||
key: 'edge-filter-lens',
|
||||
type: 'edge-filter-lens',
|
||||
},
|
||||
],
|
||||
});
|
||||
graph.render();
|
||||
|
||||
const config = {
|
||||
trigger: 'pointermove',
|
||||
scaleRByWheel: true,
|
||||
nodeType: 'both',
|
||||
};
|
||||
|
||||
window.addPanel((gui) => {
|
||||
gui
|
||||
.add(config, 'trigger', ['pointermove', 'click', 'drag'])
|
||||
.name('Trigger')
|
||||
.onChange((value) => {
|
||||
graph.updatePlugin({
|
||||
key: 'edge-filter-lens',
|
||||
trigger: value,
|
||||
});
|
||||
});
|
||||
gui
|
||||
.add(config, 'scaleRByWheel')
|
||||
.name('Scale R by Wheel')
|
||||
.onChange((value) => {
|
||||
graph.updatePlugin({
|
||||
key: 'edge-filter-lens',
|
||||
scaleRByWheel: value,
|
||||
});
|
||||
});
|
||||
gui
|
||||
.add(config, 'nodeType', ['source', 'target', 'both', 'either'])
|
||||
.name('Node Type')
|
||||
.onChange((value) => {
|
||||
graph.updatePlugin({
|
||||
key: 'edge-filter-lens',
|
||||
nodeType: value,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -0,0 +1,16 @@
|
||||
{
|
||||
"title": {
|
||||
"zh": "中文分类",
|
||||
"en": "Category"
|
||||
},
|
||||
"demos": [
|
||||
{
|
||||
"filename": "basic.js",
|
||||
"title": {
|
||||
"zh": "边过滤镜",
|
||||
"en": "Edge Filter Lens"
|
||||
},
|
||||
"screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*62FuSY-LFEIAAAAAAAAAAAAADmJ7AQ/original"
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
---
|
||||
title: EdgeFilterLens
|
||||
---
|
||||
|
||||
EdgeFilterLens can keep the focused edges within the lens range, while other edges will not be displayed within that range.
|
@ -0,0 +1,5 @@
|
||||
---
|
||||
title: EdgeFilterLens 边过滤镜
|
||||
---
|
||||
|
||||
边过滤镜可以将关注的边保留在过滤镜范围内,其他边将在该范围内不显示。
|