mirror of
https://gitee.com/antv/g6.git
synced 2024-11-30 18:58:34 +08:00
Merge pull request #4989 from ColinChen2/feature/edge-filter-lens
Feature/edge filter lens
This commit is contained in:
commit
20bb27cffa
@ -46,7 +46,7 @@
|
|||||||
"fix": "eslint ./src ./tests --fix && prettier ./src ./tests --write ",
|
"fix": "eslint ./src ./tests --fix && prettier ./src ./tests --write ",
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
"test:integration": "node --expose-gc --max-old-space-size=4096 --unhandled-rejections=strict node_modules/jest/bin/jest tests/integration/ --config jest.node.config.js --coverage -i --logHeapUsage --detectOpenHandles",
|
"test:integration": "node --expose-gc --max-old-space-size=4096 --unhandled-rejections=strict node_modules/jest/bin/jest tests/integration/ --config jest.node.config.js --coverage -i --logHeapUsage --detectOpenHandles",
|
||||||
"test:integration_one": "node --expose-gc --max-old-space-size=4096 --unhandled-rejections=strict node_modules/jest/bin/jest tests/integration/items-edge-loop.spec.ts --config jest.node.config.js --coverage -i --logHeapUsage --detectOpenHandles",
|
"test:integration_one": "node --expose-gc --max-old-space-size=4096 --unhandled-rejections=strict node_modules/jest/bin/jest tests/integration/plugins-edgeFilterLens.spec.ts --config jest.node.config.js --coverage -i --logHeapUsage --detectOpenHandles",
|
||||||
"size": "limit-size",
|
"size": "limit-size",
|
||||||
"test-live": "DEBUG_MODE=1 jest --watch ./tests/unit/item-animate-spec.ts",
|
"test-live": "DEBUG_MODE=1 jest --watch ./tests/unit/item-animate-spec.ts",
|
||||||
"test-behavior": "DEBUG_MODE=1 jest --watch ./tests/unit/item-3d-spec.ts"
|
"test-behavior": "DEBUG_MODE=1 jest --watch ./tests/unit/item-3d-spec.ts"
|
||||||
|
@ -77,6 +77,7 @@ const {
|
|||||||
Toolbar,
|
Toolbar,
|
||||||
Timebar,
|
Timebar,
|
||||||
Snapline,
|
Snapline,
|
||||||
|
EdgeFilterLens,
|
||||||
} = Plugins;
|
} = Plugins;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -292,6 +293,7 @@ const Extensions = {
|
|||||||
Timebar,
|
Timebar,
|
||||||
Hull,
|
Hull,
|
||||||
Snapline,
|
Snapline,
|
||||||
|
EdgeFilterLens,
|
||||||
WaterMarker
|
WaterMarker
|
||||||
};
|
};
|
||||||
|
|
||||||
|
380
packages/g6/src/stdlib/plugin/edgeFilterLens/index.ts
Normal file
380
packages/g6/src/stdlib/plugin/edgeFilterLens/index.ts
Normal file
@ -0,0 +1,380 @@
|
|||||||
|
import { DisplayObject } from '@antv/g';
|
||||||
|
import { clone } from '@antv/util';
|
||||||
|
import { Plugin as Base, IPluginBaseConfig } from '../../../types/plugin';
|
||||||
|
import { IGraph } from '../../../types';
|
||||||
|
import { IG6GraphEvent } from '../../../types/event';
|
||||||
|
import { ShapeStyle } from '../../../types/item';
|
||||||
|
import { distance } from '../../../util/point';
|
||||||
|
|
||||||
|
const DELTA = 0.01;
|
||||||
|
interface EdgeFilterLensConfig extends IPluginBaseConfig {
|
||||||
|
trigger?: 'mousemove' | 'click' | 'drag';
|
||||||
|
r?: number;
|
||||||
|
delegateStyle?: ShapeStyle;
|
||||||
|
showLabel?: 'node' | 'edge' | 'both' | undefined;
|
||||||
|
scaleRBy?: 'wheel' | undefined;
|
||||||
|
maxR?: number;
|
||||||
|
minR?: number;
|
||||||
|
showType?: 'one' | 'both' | 'only-source' | 'only-target'; // 更名,原名与plugin的type冲突
|
||||||
|
shouldShow?: (d?: unknown) => boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lensDelegateStyle = {
|
||||||
|
stroke: '#000',
|
||||||
|
strokeOpacity: 0.8,
|
||||||
|
lineWidth: 2,
|
||||||
|
fillOpacity: 0.1,
|
||||||
|
fill: '#fff',
|
||||||
|
};
|
||||||
|
|
||||||
|
export class EdgeFilterLens extends Base {
|
||||||
|
private showNodeLabel: boolean;
|
||||||
|
private showEdgeLabel: boolean;
|
||||||
|
private delegate: DisplayObject;
|
||||||
|
private cachedTransientNodes: Set<string | number>;
|
||||||
|
private cachedTransientEdges: Set<string | number>;
|
||||||
|
private dragging: boolean;
|
||||||
|
private delegateCenterDiff: { x: number; y: number };
|
||||||
|
|
||||||
|
constructor(config?: EdgeFilterLensConfig) {
|
||||||
|
super(config);
|
||||||
|
this.cachedTransientNodes = new Set();
|
||||||
|
this.cachedTransientEdges = new Set();
|
||||||
|
}
|
||||||
|
|
||||||
|
public getDefaultCfgs(): EdgeFilterLensConfig {
|
||||||
|
return {
|
||||||
|
showType: 'both',
|
||||||
|
trigger: 'mousemove',
|
||||||
|
r: 60,
|
||||||
|
delegateStyle: clone(lensDelegateStyle),
|
||||||
|
showLabel: 'edge',
|
||||||
|
scaleRBy: 'wheel',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public getEvents() {
|
||||||
|
let events = {
|
||||||
|
pointerdown: this.onPointerDown,
|
||||||
|
pointerup: this.onPointerUp,
|
||||||
|
wheel: this.onWheel,
|
||||||
|
} as {
|
||||||
|
[key: string]: any;
|
||||||
|
};
|
||||||
|
switch (this.options.trigger) {
|
||||||
|
case 'click':
|
||||||
|
events = {
|
||||||
|
...events,
|
||||||
|
click: this.filter,
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case 'drag':
|
||||||
|
events = {
|
||||||
|
...events,
|
||||||
|
pointermove: this.onPointerMove,
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
events = {
|
||||||
|
...events,
|
||||||
|
pointermove: this.filter,
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return events;
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(graph: IGraph) {
|
||||||
|
super.init(graph);
|
||||||
|
const showLabel = this.options.showLabel;
|
||||||
|
const showNodeLabel = showLabel === 'node' || showLabel === 'both';
|
||||||
|
const showEdgeLabel = showLabel === 'edge' || showLabel === 'both';
|
||||||
|
this.showNodeLabel = showNodeLabel;
|
||||||
|
this.showEdgeLabel = showEdgeLabel;
|
||||||
|
const shouldShow = this.options.shouldShow;
|
||||||
|
if (!shouldShow) this.options.shouldShow = () => true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onPointerUp(e: IG6GraphEvent) {
|
||||||
|
this.dragging = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onPointerMove(e: IG6GraphEvent) {
|
||||||
|
if (!this.dragging) return;
|
||||||
|
this.moveDelegate(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onPointerDown(e: IG6GraphEvent) {
|
||||||
|
const { delegate: lensDelegate } = this;
|
||||||
|
let cacheCenter;
|
||||||
|
if (!lensDelegate || lensDelegate.destroyed) {
|
||||||
|
cacheCenter = { x: e.canvas.x, y: e.canvas.y };
|
||||||
|
this.filter(e);
|
||||||
|
} else {
|
||||||
|
cacheCenter = {
|
||||||
|
x: lensDelegate.style.cx,
|
||||||
|
y: lensDelegate.style.cy,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
this.delegateCenterDiff = {
|
||||||
|
x: e.canvas.x - cacheCenter.x,
|
||||||
|
y: e.canvas.y - cacheCenter.y,
|
||||||
|
};
|
||||||
|
this.dragging = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine whether it is dragged in the delegate
|
||||||
|
protected isInLensDelegate(lensDelegate, pointer): boolean {
|
||||||
|
const { cx: lensX, cy: lensY, r: lensR } = lensDelegate.style;
|
||||||
|
if (
|
||||||
|
pointer.x >= lensX - lensR &&
|
||||||
|
pointer.x <= lensX + lensR &&
|
||||||
|
pointer.y >= lensY - lensR &&
|
||||||
|
pointer.y <= lensY + lensR
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected moveDelegate(e) {
|
||||||
|
if (
|
||||||
|
this.isInLensDelegate(this.delegate, { x: e.canvas.x, y: e.canvas.y })
|
||||||
|
) {
|
||||||
|
const center = {
|
||||||
|
x: e.canvas.x - this.delegateCenterDiff.x,
|
||||||
|
y: e.canvas.y - this.delegateCenterDiff.y,
|
||||||
|
};
|
||||||
|
this.filter(e, center);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onWheel(e: IG6GraphEvent) {
|
||||||
|
const { delegate: lensDelegate, options } = this;
|
||||||
|
const { scaleRBy } = options;
|
||||||
|
if (!lensDelegate || lensDelegate.destroyed) return;
|
||||||
|
if (scaleRBy !== 'wheel') return;
|
||||||
|
if (this.isInLensDelegate(lensDelegate, { x: e.canvas.x, y: e.canvas.y })) {
|
||||||
|
if (scaleRBy === 'wheel') {
|
||||||
|
this.scaleRByWheel(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scale the range by wheel
|
||||||
|
* @param e mouse wheel event
|
||||||
|
*/
|
||||||
|
protected scaleRByWheel(e: IG6GraphEvent) {
|
||||||
|
if (!e || !e.originalEvent) return;
|
||||||
|
if (e.preventDefault) e.preventDefault();
|
||||||
|
const { graph, options } = this;
|
||||||
|
const graphCanvasEl = graph.canvas.context.config.canvas;
|
||||||
|
const graphHeight = graphCanvasEl?.height || 500;
|
||||||
|
const maxR = options.maxR
|
||||||
|
? Math.min(options.maxR, graphHeight)
|
||||||
|
: graphHeight;
|
||||||
|
const minR = options.minR
|
||||||
|
? Math.max(options.minR, graphHeight * DELTA)
|
||||||
|
: graphHeight * DELTA;
|
||||||
|
|
||||||
|
const scale = 1 + (e.originalEvent as any).deltaY * -1 * DELTA;
|
||||||
|
let r = options.r * scale;
|
||||||
|
r = Math.min(r, maxR);
|
||||||
|
r = Math.max(r, minR);
|
||||||
|
options.r = r;
|
||||||
|
this.delegate.style.r = r;
|
||||||
|
this.filter(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response function for mousemove, click, or drag to filter out the edges
|
||||||
|
* @param e mouse event
|
||||||
|
*/
|
||||||
|
protected filter(e: IG6GraphEvent, mousePos?) {
|
||||||
|
const {
|
||||||
|
graph,
|
||||||
|
options,
|
||||||
|
showNodeLabel,
|
||||||
|
showEdgeLabel,
|
||||||
|
cachedTransientNodes,
|
||||||
|
cachedTransientEdges,
|
||||||
|
} = this;
|
||||||
|
const r = options.r;
|
||||||
|
const showType = options.showType;
|
||||||
|
const shouldShow = options.shouldShow;
|
||||||
|
const fCenter = mousePos || { x: e.canvas.x, y: e.canvas.y };
|
||||||
|
this.updateDelegate(fCenter, r);
|
||||||
|
|
||||||
|
const nodes = graph.getAllNodesData();
|
||||||
|
const hitNodesMap = {};
|
||||||
|
nodes.forEach((node) => {
|
||||||
|
const { data, id } = node;
|
||||||
|
if (distance({ x: data.x, y: data.y }, fCenter) < r) {
|
||||||
|
hitNodesMap[id] = node;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const edges = graph.getAllEdgesData();
|
||||||
|
const hitEdges = [];
|
||||||
|
edges.forEach((edge) => {
|
||||||
|
const sourceId = edge.source;
|
||||||
|
const targetId = edge.target;
|
||||||
|
if (shouldShow(edge)) {
|
||||||
|
if (showType === 'only-source' || showType === 'one') {
|
||||||
|
if (hitNodesMap[sourceId] && !hitNodesMap[targetId])
|
||||||
|
hitEdges.push(edge);
|
||||||
|
} else if (showType === 'only-target' || showType === 'one') {
|
||||||
|
if (hitNodesMap[targetId] && !hitNodesMap[sourceId])
|
||||||
|
hitEdges.push(edge);
|
||||||
|
} else if (
|
||||||
|
showType === 'both' &&
|
||||||
|
hitNodesMap[sourceId] &&
|
||||||
|
hitNodesMap[targetId]
|
||||||
|
) {
|
||||||
|
hitEdges.push(edge);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const currentTransientNodes = new Set<string | number>();
|
||||||
|
const currentTransientEdges = new Set<string | number>();
|
||||||
|
|
||||||
|
if (showNodeLabel) {
|
||||||
|
Object.keys(hitNodesMap).forEach((key) => {
|
||||||
|
const node = hitNodesMap[key];
|
||||||
|
currentTransientNodes.add(node.id);
|
||||||
|
|
||||||
|
if (cachedTransientNodes.has(node.id)) {
|
||||||
|
cachedTransientNodes.delete(node.id);
|
||||||
|
} else {
|
||||||
|
graph.drawTransient('node', node.id, { shapeIds: ['labelShape'] });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
cachedTransientNodes.forEach((id) => {
|
||||||
|
graph.drawTransient('node', id, { action: 'remove' });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showEdgeLabel) {
|
||||||
|
hitEdges.forEach((edge) => {
|
||||||
|
currentTransientEdges.add(edge.id);
|
||||||
|
|
||||||
|
if (cachedTransientEdges.has(edge.id)) {
|
||||||
|
cachedTransientEdges.delete(edge.id);
|
||||||
|
} else {
|
||||||
|
graph.drawTransient('edge', edge.id, {
|
||||||
|
shapeIds: ['labelShape'],
|
||||||
|
drawSource: false,
|
||||||
|
drawTarget: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
cachedTransientEdges.forEach((id) => {
|
||||||
|
graph.drawTransient('edge', id, { action: 'remove' });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.cachedTransientNodes = currentTransientNodes;
|
||||||
|
this.cachedTransientEdges = currentTransientEdges;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adjust part of the parameters, including trigger, showType, r, maxR, minR, shouldShow, showLabel, and scaleRBy
|
||||||
|
* @param {EdgeFilterLensConfig} cfg
|
||||||
|
*/
|
||||||
|
public updateParams(cfg: EdgeFilterLensConfig) {
|
||||||
|
const self = this;
|
||||||
|
const { r, trigger, minR, maxR, scaleRBy, showLabel, shouldShow } = cfg;
|
||||||
|
if (!isNaN(cfg.r)) {
|
||||||
|
self.options.r = r;
|
||||||
|
}
|
||||||
|
if (!isNaN(maxR)) {
|
||||||
|
self.options.maxR = maxR;
|
||||||
|
}
|
||||||
|
if (!isNaN(minR)) {
|
||||||
|
self.options.minR = minR;
|
||||||
|
}
|
||||||
|
if (trigger === 'mousemove' || trigger === 'click' || trigger === 'drag') {
|
||||||
|
self.options.trigger = trigger;
|
||||||
|
}
|
||||||
|
if (scaleRBy === 'wheel' || scaleRBy === 'unset') {
|
||||||
|
self.options.scaleRBy = scaleRBy;
|
||||||
|
self.delegate.remove();
|
||||||
|
self.delegate.destroy();
|
||||||
|
}
|
||||||
|
if (showLabel === 'node' || showLabel === 'both') {
|
||||||
|
self.showNodeLabel = true;
|
||||||
|
}
|
||||||
|
if (showLabel === 'edge' || showLabel === 'both') {
|
||||||
|
self.showEdgeLabel = true;
|
||||||
|
}
|
||||||
|
if (shouldShow) {
|
||||||
|
self.options.shouldShow = shouldShow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the delegate shape of the lens
|
||||||
|
* @param {Point} mCenter the center of the shape
|
||||||
|
* @param {number} r the radius of the shape
|
||||||
|
*/
|
||||||
|
private updateDelegate(mCenter, r) {
|
||||||
|
const { graph, options, delegate } = this;
|
||||||
|
let lensDelegate = delegate;
|
||||||
|
if (!lensDelegate || lensDelegate.destroyed) {
|
||||||
|
// 拖动多个
|
||||||
|
const attrs = options.delegateStyle || lensDelegateStyle;
|
||||||
|
|
||||||
|
// model上的x, y是相对于图形中心的,delegateShape是g实例,x,y是绝对坐标
|
||||||
|
lensDelegate = graph.drawTransient('circle', 'lens-shape', {
|
||||||
|
style: {
|
||||||
|
r,
|
||||||
|
cx: mCenter.x,
|
||||||
|
cy: mCenter.y,
|
||||||
|
...attrs,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
lensDelegate.style.cx = mCenter.x;
|
||||||
|
lensDelegate.style.cy = mCenter.y;
|
||||||
|
lensDelegate.style.r = mCenter.r;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.delegate = lensDelegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the filtering
|
||||||
|
*/
|
||||||
|
public clear() {
|
||||||
|
const {
|
||||||
|
graph,
|
||||||
|
delegate: lensDelegate,
|
||||||
|
cachedTransientNodes,
|
||||||
|
cachedTransientEdges,
|
||||||
|
} = this;
|
||||||
|
|
||||||
|
if (lensDelegate && !lensDelegate.destroyed) {
|
||||||
|
graph.drawTransient('circle', 'lens-shape', { action: 'remove' });
|
||||||
|
}
|
||||||
|
cachedTransientNodes.forEach((id) => {
|
||||||
|
graph.drawTransient('node', id, { action: 'remove' });
|
||||||
|
});
|
||||||
|
cachedTransientEdges.forEach((id) => {
|
||||||
|
graph.drawTransient('edge', id, { action: 'remove' });
|
||||||
|
});
|
||||||
|
|
||||||
|
cachedTransientNodes.clear();
|
||||||
|
cachedTransientEdges.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroy the component
|
||||||
|
*/
|
||||||
|
public destroy() {
|
||||||
|
this.clear();
|
||||||
|
}
|
||||||
|
}
|
@ -8,3 +8,4 @@ export * from './toolbar';
|
|||||||
export * from './tooltip';
|
export * from './tooltip';
|
||||||
export * from './timebar';
|
export * from './timebar';
|
||||||
export * from './snapline';
|
export * from './snapline';
|
||||||
|
export * from './edgeFilterLens';
|
||||||
|
@ -8,3 +8,4 @@ export type { ToolbarConfig } from './toolbar';
|
|||||||
export type { TooltipConfig } from './tooltip';
|
export type { TooltipConfig } from './tooltip';
|
||||||
export type { TimebarConfig } from './timebar';
|
export type { TimebarConfig } from './timebar';
|
||||||
export type { SnapLineConfig } from './snapline';
|
export type { SnapLineConfig } from './snapline';
|
||||||
|
export type { EdgeFilterLens } from './edgeFilterLens';
|
||||||
|
@ -64,6 +64,7 @@ import legend from './plugins/legend';
|
|||||||
import snapline from './plugins/snapline';
|
import snapline from './plugins/snapline';
|
||||||
import mapper from './visual/mapper';
|
import mapper from './visual/mapper';
|
||||||
import minimap from './plugins/minimap';
|
import minimap from './plugins/minimap';
|
||||||
|
import edgeFilterLens from './plugins/edgeFilterLens';
|
||||||
import watermarker from './plugins/watermarker';
|
import watermarker from './plugins/watermarker';
|
||||||
import cube from './item/node/cube';
|
import cube from './item/node/cube';
|
||||||
import graphCore from './data/graphCore';
|
import graphCore from './data/graphCore';
|
||||||
@ -138,7 +139,8 @@ export {
|
|||||||
hull,
|
hull,
|
||||||
legend,
|
legend,
|
||||||
snapline,
|
snapline,
|
||||||
watermarker
|
edgeFilterLens,
|
||||||
|
watermarker,
|
||||||
cube,
|
cube,
|
||||||
graphCore,
|
graphCore,
|
||||||
dagreUpdate,
|
dagreUpdate,
|
||||||
|
195
packages/g6/tests/demo/plugins/edgeFilterLens.ts
Normal file
195
packages/g6/tests/demo/plugins/edgeFilterLens.ts
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
import { Graph, Extensions, extend } from '../../../src/index';
|
||||||
|
import { TestCaseContext } from '../interface';
|
||||||
|
import data from '../../datasets/force-data.json';
|
||||||
|
import { clone } from '@antv/util';
|
||||||
|
|
||||||
|
export default (context: TestCaseContext, options = {}) => {
|
||||||
|
const trigger = 'mousemove';
|
||||||
|
let filterLens = {
|
||||||
|
type: 'filterLens',
|
||||||
|
key: 'filterLens1',
|
||||||
|
trigger,
|
||||||
|
showLabel: 'edge',
|
||||||
|
r: 140,
|
||||||
|
...options,
|
||||||
|
};
|
||||||
|
|
||||||
|
// ================= The DOMs for configurations =============== //
|
||||||
|
const graphDiv = document.getElementById('container');
|
||||||
|
|
||||||
|
const buttonContainer = document.createElement('div');
|
||||||
|
buttonContainer.style.display = 'inline-block';
|
||||||
|
buttonContainer.style.height = '35px';
|
||||||
|
buttonContainer.style.width = '100%';
|
||||||
|
buttonContainer.style.textAlign = 'center';
|
||||||
|
|
||||||
|
// tip
|
||||||
|
const tip = document.createElement('span');
|
||||||
|
tip.innerHTML =
|
||||||
|
'点击画布任意位置开始探索。过滤镜中显示两端节点均在过滤镜中的边。';
|
||||||
|
buttonContainer.appendChild(tip);
|
||||||
|
|
||||||
|
buttonContainer.appendChild(document.createElement('br'));
|
||||||
|
|
||||||
|
// tip english
|
||||||
|
const tipEn = document.createElement('span');
|
||||||
|
tipEn.innerHTML =
|
||||||
|
'Click the canvas to begin. Show the edge whose both end nodes are inside the lens.';
|
||||||
|
buttonContainer.appendChild(tipEn);
|
||||||
|
|
||||||
|
buttonContainer.appendChild(document.createElement('br'));
|
||||||
|
|
||||||
|
// enable/disable the fisheye lens button
|
||||||
|
const swithButton = document.createElement('input');
|
||||||
|
swithButton.type = 'button';
|
||||||
|
swithButton.value = 'Disable';
|
||||||
|
swithButton.style.height = '25px';
|
||||||
|
swithButton.style.width = '60px';
|
||||||
|
swithButton.style.marginLeft = '16px';
|
||||||
|
buttonContainer.appendChild(swithButton);
|
||||||
|
|
||||||
|
// list for changing trigger
|
||||||
|
const triggerTag = document.createElement('span');
|
||||||
|
triggerTag.innerHTML = 'Trigger:';
|
||||||
|
triggerTag.style.marginLeft = '16px';
|
||||||
|
buttonContainer.appendChild(triggerTag);
|
||||||
|
const configTrigger = document.createElement('select');
|
||||||
|
configTrigger.value = 'mousemove';
|
||||||
|
configTrigger.style.height = '25px';
|
||||||
|
configTrigger.style.width = '100px';
|
||||||
|
configTrigger.style.marginLeft = '8px';
|
||||||
|
const mousemoveTrigger = document.createElement('option');
|
||||||
|
mousemoveTrigger.value = 'mousemove';
|
||||||
|
mousemoveTrigger.innerHTML = 'mousemove';
|
||||||
|
configTrigger.appendChild(mousemoveTrigger);
|
||||||
|
const dragTrigger = document.createElement('option');
|
||||||
|
dragTrigger.value = 'drag';
|
||||||
|
dragTrigger.innerHTML = 'drag';
|
||||||
|
configTrigger.appendChild(dragTrigger);
|
||||||
|
const clickTrigger = document.createElement('option');
|
||||||
|
clickTrigger.value = 'click';
|
||||||
|
clickTrigger.innerHTML = 'click';
|
||||||
|
configTrigger.appendChild(clickTrigger);
|
||||||
|
buttonContainer.appendChild(configTrigger);
|
||||||
|
|
||||||
|
// list for changing scaleRBy
|
||||||
|
const scaleR = document.createElement('span');
|
||||||
|
scaleR.innerHTML = 'Scale r by:';
|
||||||
|
scaleR.style.marginLeft = '16px';
|
||||||
|
buttonContainer.appendChild(scaleR);
|
||||||
|
const configScaleRBy = document.createElement('select');
|
||||||
|
configScaleRBy.value = 'wheel';
|
||||||
|
configScaleRBy.style.height = '25px';
|
||||||
|
configScaleRBy.style.width = '100px';
|
||||||
|
configScaleRBy.style.marginLeft = '8px';
|
||||||
|
const scaleRByWheel = document.createElement('option');
|
||||||
|
scaleRByWheel.value = 'wheel';
|
||||||
|
scaleRByWheel.innerHTML = 'wheel';
|
||||||
|
configScaleRBy.appendChild(scaleRByWheel);
|
||||||
|
const scaleRByUnset = document.createElement('option');
|
||||||
|
scaleRByUnset.value = 'unset';
|
||||||
|
scaleRByUnset.innerHTML = 'unset';
|
||||||
|
configScaleRBy.appendChild(scaleRByUnset);
|
||||||
|
buttonContainer.appendChild(configScaleRBy);
|
||||||
|
|
||||||
|
graphDiv.parentNode.appendChild(buttonContainer);
|
||||||
|
|
||||||
|
// ========================================================= //
|
||||||
|
|
||||||
|
const ExtGraph = extend(Graph, {
|
||||||
|
plugins: {
|
||||||
|
filterLens: Extensions.EdgeFilterLens,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const graph = new ExtGraph({
|
||||||
|
...context,
|
||||||
|
layout: {
|
||||||
|
type: 'grid',
|
||||||
|
begin: [0, 0],
|
||||||
|
},
|
||||||
|
plugins: [filterLens],
|
||||||
|
node: (innerModel) => {
|
||||||
|
return {
|
||||||
|
...innerModel,
|
||||||
|
data: {
|
||||||
|
...innerModel.data,
|
||||||
|
lodStrategy: {
|
||||||
|
levels: [
|
||||||
|
{ zoomRange: [0, 0.9] }, // -1
|
||||||
|
{ zoomRange: [0.9, 1], primary: true }, // 0
|
||||||
|
{ zoomRange: [1, 1.2] }, // 1
|
||||||
|
{ zoomRange: [1.2, 1.5] }, // 2
|
||||||
|
{ zoomRange: [1.5, Infinity] }, // 3
|
||||||
|
],
|
||||||
|
animateCfg: {
|
||||||
|
duration: 500,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
labelShape: {
|
||||||
|
text: innerModel.data.label,
|
||||||
|
lod: 1, // 图的缩放大于 levels 第一层定义的 zoomRange[0] 时展示,小于时隐藏
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
edge: (innerModel) => {
|
||||||
|
return {
|
||||||
|
...innerModel,
|
||||||
|
data: {
|
||||||
|
...innerModel.data,
|
||||||
|
lodStrategy: {
|
||||||
|
levels: [
|
||||||
|
{ zoomRange: [0, 0.9] }, // -1
|
||||||
|
{ zoomRange: [0.9, 1], primary: true }, // 0
|
||||||
|
{ zoomRange: [1, 1.2] }, // 1
|
||||||
|
{ zoomRange: [1.2, 1.5] }, // 2
|
||||||
|
{ zoomRange: [1.5, Infinity] }, // 3
|
||||||
|
],
|
||||||
|
animateCfg: {
|
||||||
|
duration: 500,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
labelShape: {
|
||||||
|
text: innerModel.data.label,
|
||||||
|
maxWidth: '100%',
|
||||||
|
lod: 1, // 图的缩放大于 levels 第一层定义的 zoomRange[0] 时展示,小于时隐藏
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
modes: {
|
||||||
|
// default: ['drag-canvas',],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
swithButton.addEventListener('click', (e) => {
|
||||||
|
if (swithButton.value === 'Disable') {
|
||||||
|
swithButton.value = 'Enable';
|
||||||
|
graph.removePlugins(['filterLens1']);
|
||||||
|
} else {
|
||||||
|
swithButton.value = 'Disable';
|
||||||
|
graph.addPlugins([filterLens]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
configScaleRBy.addEventListener('change', (e) => {
|
||||||
|
filterLens = {
|
||||||
|
...filterLens,
|
||||||
|
scaleRBy: e.target.value,
|
||||||
|
};
|
||||||
|
graph.updatePlugin(filterLens);
|
||||||
|
});
|
||||||
|
configTrigger.addEventListener('change', (e) => {
|
||||||
|
filterLens = {
|
||||||
|
...filterLens,
|
||||||
|
trigger: e.target.value,
|
||||||
|
};
|
||||||
|
graph.updatePlugin(filterLens);
|
||||||
|
});
|
||||||
|
|
||||||
|
const cloneData = clone(data);
|
||||||
|
cloneData.edges.forEach((edge) => (edge.data = { label: edge.id }));
|
||||||
|
graph.read(cloneData);
|
||||||
|
graph.zoom(0.6);
|
||||||
|
|
||||||
|
return graph;
|
||||||
|
};
|
42
packages/g6/tests/integration/plugins-edgeFilterLens.spec.ts
Normal file
42
packages/g6/tests/integration/plugins-edgeFilterLens.spec.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import { resetEntityCounter } from '@antv/g';
|
||||||
|
import EdgeFilterLens from '../demo/plugins/edgeFilterLens';
|
||||||
|
import { createContext, sleep } from './utils';
|
||||||
|
import { triggerEvent } from './utils/event';
|
||||||
|
import './utils/useSnapshotMatchers';
|
||||||
|
|
||||||
|
describe('Default EdgeFilterLens', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
resetEntityCounter();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be rendered correctly with fitler lens with mousemove', async () => {
|
||||||
|
const dir = `${__dirname}/snapshots/canvas/plugins/edgeFilterLens`;
|
||||||
|
const { backgroundCanvas, canvas, transientCanvas, container } =
|
||||||
|
createContext('canvas', 500, 500);
|
||||||
|
const graph = EdgeFilterLens({
|
||||||
|
backgroundCanvas,
|
||||||
|
canvas,
|
||||||
|
transientCanvas,
|
||||||
|
width: 500,
|
||||||
|
height: 500,
|
||||||
|
container,
|
||||||
|
});
|
||||||
|
console.log('graph', graph);
|
||||||
|
|
||||||
|
const process = new Promise((reslove) => {
|
||||||
|
graph.on('afterlayout', () => {
|
||||||
|
console.log('afterlayout');
|
||||||
|
reslove();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await process;
|
||||||
|
await sleep(300);
|
||||||
|
triggerEvent(graph, 'mousedown', 200, 200);
|
||||||
|
await expect(transientCanvas).toMatchCanvasSnapshot(
|
||||||
|
dir,
|
||||||
|
'plugins-edge-filter-lens-transients',
|
||||||
|
);
|
||||||
|
graph.destroy();
|
||||||
|
});
|
||||||
|
});
|
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
Loading…
Reference in New Issue
Block a user