EdgeFilterLens
Previous
EdgeBundling
Next
Fisheye
Loading...
EdgeFilterLens can keep the focused edges within the lens range, while other edges will not be displayed within that range. This is an important visualization exploration tool that helps users focus on edge relationships in specific areas.
const graph = new Graph({plugins: [{type: 'edge-filter-lens',trigger: 'pointermove', // Follow mouse movementr: 60, // Set lens radiusnodeType: 'both', // Edge display condition},],});
createGraph({data: {nodes: [// 上部疏散区域{ id: 'Myriel', style: { x: 207, y: 78, label: 'Myriel' } },{ id: 'Napoleon', style: { x: 127, y: 62, label: 'Napoleon' } },{ id: 'CountessdeLo', style: { x: 171, y: 47, label: 'CountessdeLo' } },{ id: 'Geborand', style: { x: 106, y: 81, label: 'Geborand' } },{ id: 'Champtercier', style: { x: 247, y: 58, label: 'Champtercier' } },{ id: 'Cravatte', style: { x: 152, y: 50, label: 'Cravatte' } },// 中上部区域{ id: 'Mlle.Baptistine', style: { x: 205, y: 141, label: 'Mlle.Baptistine' } },{ id: 'Mme.Magloire', style: { x: 275, y: 120, label: 'Mme.Magloire' } },{ id: 'Labarre', style: { x: 246, y: 183, label: 'Labarre' } },{ id: 'Valjean', style: { x: 342, y: 221, label: 'Valjean' } },{ id: 'Marguerite', style: { x: 285, y: 171, label: 'Marguerite' } },// 中部密集区域{ id: 'Tholomyes', style: { x: 379, y: 158, label: 'Tholomyes' } },{ id: 'Listolier', style: { x: 288, y: 80, label: 'Listolier' } },{ id: 'Fameuil', style: { x: 349, y: 89, label: 'Fameuil' } },{ id: 'Blacheville', style: { x: 381, y: 95, label: 'Blacheville' } },{ id: 'Favourite', style: { x: 264, y: 153, label: 'Favourite' } },{ id: 'Dahlia', style: { x: 323, y: 170, label: 'Dahlia' } },{ id: 'Zephine', style: { x: 306, y: 114, label: 'Zephine' } },{ id: 'Fantine', style: { x: 357, y: 187, label: 'Fantine' } },// 右侧区域{ id: 'Bamatabois', style: { x: 411, y: 156, label: 'Bamatabois' } },{ id: 'Perpetue', style: { x: 454, y: 195, label: 'Perpetue' } },{ id: 'Simplice', style: { x: 406, y: 227, label: 'Simplice' } },// 下部区域{ id: 'Cosette', style: { x: 343, y: 248, label: 'Cosette' } },{ id: 'Javert', style: { x: 388, y: 263, label: 'Javert' } },{ id: 'Fauchelevent', style: { x: 397, y: 276, label: 'Fauchelevent' } },{ id: 'Thenardier', style: { x: 317, y: 300, label: 'Thenardier' } },{ id: 'Eponine', style: { x: 268, y: 365, label: 'Eponine' } },{ id: 'Anzelma', style: { x: 234, y: 303, label: 'Anzelma' } },{ id: 'Woman2', style: { x: 304, y: 254, label: 'Woman2' } },// 最右侧独立节点{ id: 'Gribier', style: { x: 457, y: 160, label: 'Gribier' } },{ id: 'Jondrette', style: { x: 510, y: 327, label: 'Jondrette' } },],edges: [// 上部连接{ id: 'e1', source: 'Myriel', target: 'CountessdeLo' },{ id: 'e2', source: 'Napoleon', target: 'Myriel' },{ id: 'e3', source: 'Geborand', target: 'Napoleon' },{ id: 'e4', source: 'Champtercier', target: 'Myriel' },{ id: 'e5', source: 'Cravatte', target: 'CountessdeLo' },// 中上部连接{ id: 'e6', source: 'Mlle.Baptistine', target: 'Mme.Magloire' },{ id: 'e7', source: 'Labarre', target: 'Valjean' },{ id: 'e8', source: 'Valjean', target: 'Marguerite' },{ id: 'e9', source: 'Marguerite', target: 'Mme.Magloire' },// 中部密集连接{ id: 'e10', source: 'Tholomyes', target: 'Listolier' },{ id: 'e11', source: 'Listolier', target: 'Fameuil' },{ id: 'e12', source: 'Fameuil', target: 'Blacheville' },{ id: 'e13', source: 'Blacheville', target: 'Favourite' },{ id: 'e14', source: 'Favourite', target: 'Dahlia' },{ id: 'e15', source: 'Dahlia', target: 'Zephine' },{ id: 'e16', source: 'Zephine', target: 'Fantine' },{ id: 'e17', source: 'Tholomyes', target: 'Fantine' },{ id: 'e18', source: 'Valjean', target: 'Fantine' },// 右侧连接{ id: 'e19', source: 'Bamatabois', target: 'Perpetue' },{ id: 'e20', source: 'Perpetue', target: 'Simplice' },{ id: 'e21', source: 'Bamatabois', target: 'Gribier' },// 下部连接{ id: 'e22', source: 'Valjean', target: 'Cosette' },{ id: 'e23', source: 'Cosette', target: 'Javert' },{ id: 'e24', source: 'Javert', target: 'Fauchelevent' },{ id: 'e25', source: 'Fauchelevent', target: 'Thenardier' },{ id: 'e26', source: 'Thenardier', target: 'Eponine' },{ id: 'e27', source: 'Eponine', target: 'Anzelma' },{ id: 'e28', source: 'Woman2', target: 'Cosette' },// 跨区域连接{ id: 'e29', source: 'Fantine', target: 'Bamatabois' },{ id: 'e30', source: 'Javert', target: 'Bamatabois' },{ id: 'e31', source: 'Simplice', target: 'Jondrette' },{ id: 'e32', source: 'Thenardier', target: 'Jondrette' },{ id: 'e33', source: 'Favourite', target: 'Valjean' },{ id: 'e34', source: 'Tholomyes', target: 'Cosette' },],},node: {style: {label: true,size: 16,},palette: {field: (datum) => Math.floor(datum.style?.y / 60),},},edge: {style: {label: true,labelText: (d) => d.data.value?.toString(),stroke: '#ccc',endArrow: true,endArrowType: 'triangle',},},plugins: [{type: 'edge-filter-lens',key: 'edge-filter-lens',r: 80,trigger: 'pointermove',},],},{ width: 600, height: 400 },(gui, graph) => {const TRIGGER_TYPES = ['pointermove', 'click', 'drag'];const NODE_TYPES = ['both', 'source', 'target', 'either'];const options = {type: 'edge-filter-lens',r: 80, // 透镜半径trigger: 'pointermove', // 触发方式nodeType: 'both', // 边显示条件minR: 50, // 最小半径maxR: 150, // 最大半径scaleRBy: 'wheel', // 缩放方式style: {fill: '#f0f5ff',fillOpacity: 0.4,stroke: '#1d39c4',strokeOpacity: 0.8,lineWidth: 1.5,},nodeStyle: {size: 35,fill: '#d6e4ff',stroke: '#2f54eb',lineWidth: 2,labelFontSize: 14,labelFontWeight: 'bold',labelFill: '#1d39c4',},edgeStyle: {stroke: '#1d39c4',lineWidth: 2,strokeOpacity: 0.8,},};const optionFolder = gui.addFolder('Edge Filter Lens Options');optionFolder.add(options, 'type').disable(true);optionFolder.add(options, 'r', 50, 150, 5);optionFolder.add(options, 'trigger', TRIGGER_TYPES);optionFolder.add(options, 'nodeType', NODE_TYPES);optionFolder.add(options, 'minR', 20, 100, 5);optionFolder.add(options, 'maxR', 100, 200, 5);optionFolder.onChange(({ property, value }) => {if (property.includes('.')) {const [group, prop] = property.split('.');graph.updatePlugin({key: 'edge-filter-lens',[group]: {...options[group],[prop]: value,},});} else {graph.updatePlugin({key: 'edge-filter-lens',[property]: value,});}graph.render();});},);
Name | Description | Type | Default | Required |
---|---|---|---|---|
type | Plugin type | string | edge-filter-lens | ✓ |
trigger | The way to move the lens | pointermove | click | drag | pointermove | |
r | The radius of the lens | number | 60 | |
maxR | The maximum radius of the lens | number | Half of the minimum canvas width/height | |
minR | The minimum radius of the lens | number | 0 | |
scaleRBy | The way to scale the radius | wheel | - | |
nodeType | The condition for displaying edges | both | source | target | either | both | |
filter | Filter elements never shown in lens | (id: string, elementType: 'node' | 'edge' | 'combo') => boolean | () => true | |
style | The style of the lens | CircleStyleProps | { fill: '#fff', fillOpacity: 1, lineWidth: 1, stroke: '#000', strokeOpacity: 0.8, zIndex: -Infinity } | |
nodeStyle | The style of nodes in lens | NodeStyle | ((datum: NodeData) => NodeStyle) | { label: false } | |
edgeStyle | The style of edges in lens | EdgeStyle | ((datum: EdgeData) => EdgeStyle) | { label: true } | |
preventDefault | Whether to prevent default events | boolean | true |
Style properties for the circular lens.
Name | Description | Type | Default |
---|---|---|---|
fill | Fill color | string | #fff |
fillOpacity | Fill opacity | number | 1 |
stroke | Stroke color | string | #000 |
strokeOpacity | Stroke opacity | number | 0.8 |
lineWidth | Line width | number | 1 |
zIndex | Layer level | number | -Infinity |
For complete style properties, refer to Element - Node - Built-in Node - Common Style Properties - style
The trigger
property controls how the lens moves, supporting three configurations:
pointermove
: Lens always follows mouse movementclick
: Move lens to clicked positiondrag
: Move lens by draggingconst graph = new Graph({plugins: [{type: 'edge-filter-lens',trigger: 'pointermove', // Follow mouse movement// trigger: 'click', // Click to move// trigger: 'drag', // Drag to move},],});
The nodeType
property controls edge display conditions:
both
: Edge displays only when both source and target nodes are in lenssource
: Edge displays only when source node is in lenstarget
: Edge displays only when target node is in lenseither
: Edge displays when either source or target node is in lensconst graph = new Graph({plugins: [{type: 'edge-filter-lens',nodeType: 'both', // Show edge when both nodes are in lens// nodeType: 'source', // Show edge when source node is in lens// nodeType: 'target', // Show edge when target node is in lens// nodeType: 'either', // Show edge when either node is in lens},],});
Use scaleRBy
to control lens radius adjustment:
const graph = new Graph({plugins: [{type: 'edge-filter-lens',// Adjust radius by wheelscaleRBy: 'wheel',// Set radius rangeminR: 50,maxR: 200,},],});
Simplest configuration:
const graph = new Graph({plugins: ['edge-filter-lens'],});
Effect:
createGraph({data: {nodes: [// Upper scattered area{ id: 'node1', style: { x: 150, y: 60, label: 'Node 1' } },{ id: 'node2', style: { x: 100, y: 40, label: 'Node 2' } },{ id: 'node3', style: { x: 200, y: 35, label: 'Node 3' } },{ id: 'node4', style: { x: 150, y: 30, label: 'Node 4' } },// Middle area{ id: 'node5', style: { x: 220, y: 140, label: 'Node 5' } },{ id: 'node6', style: { x: 280, y: 160, label: 'Node 6' } },{ id: 'node7', style: { x: 220, y: 120, label: 'Node 7' } },{ id: 'node8', style: { x: 260, y: 100, label: 'Node 8' } },{ id: 'node9', style: { x: 240, y: 130, label: 'Node 9' } },{ id: 'node10', style: { x: 300, y: 110, label: 'Node 10' } },// Lower area{ id: 'node11', style: { x: 240, y: 200, label: 'Node 11' } },{ id: 'node12', style: { x: 280, y: 220, label: 'Node 12' } },{ id: 'node13', style: { x: 300, y: 190, label: 'Node 13' } },{ id: 'node14', style: { x: 320, y: 210, label: 'Node 14' } },],edges: [// Upper connections{ id: 'edge1', source: 'node1', target: 'node2' },{ id: 'edge2', source: 'node2', target: 'node3' },{ id: 'edge3', source: 'node3', target: 'node4' },// Middle connections{ id: 'edge4', source: 'node5', target: 'node6' },{ id: 'edge5', source: 'node6', target: 'node7' },{ id: 'edge6', source: 'node7', target: 'node8' },{ id: 'edge7', source: 'node8', target: 'node9' },{ id: 'edge8', source: 'node9', target: 'node10' },// Lower connections{ id: 'edge9', source: 'node11', target: 'node12' },{ id: 'edge10', source: 'node12', target: 'node13' },{ id: 'edge11', source: 'node13', target: 'node14' },// Cross-region connections{ id: 'edge12', source: 'node4', target: 'node8' },{ id: 'edge13', source: 'node7', target: 'node11' },{ id: 'edge14', source: 'node10', target: 'node13' },],},node: {style: {size: 20,label: true,},},edge: {style: {stroke: '#91d5ff',lineWidth: 1,},},plugins: ['edge-filter-lens'],},{ width: 400, height: 300 },);
You can customize the appearance and behavior of the lens:
const graph = new Graph({plugins: [{type: 'edge-filter-lens',r: 80,style: {fill: '#f0f5ff', // Lens area fill colorfillOpacity: 0.6, // Fill area opacitystroke: '#7e3feb', // Lens border color (purple)strokeOpacity: 0.8, // Border opacitylineWidth: 1.5, // Border line width},nodeStyle: {size: 24, // Enlarged nodefill: '#7e3feb', // Purple fillstroke: '#5719c9', // Deep purple strokelineWidth: 1, // Thin borderlabel: true, // Show labellabelFill: '#ffffff', // White textlabelFontSize: 14, // Enlarged textlabelFontWeight: 'bold', // Bold text},edgeStyle: {stroke: '#8b9baf', // Gray edgelineWidth: 2, // Thicker linelabel: true, // Show labellabelFill: '#5719c9', // Deep purple textopacity: 0.8, // Proper opacity},},],});
Effect:
createGraph({data: {nodes: [// Upper scattered area{ id: 'node1', style: { x: 150, y: 60, label: 'Node 1' } },{ id: 'node2', style: { x: 100, y: 40, label: 'Node 2' } },{ id: 'node3', style: { x: 200, y: 35, label: 'Node 3' } },{ id: 'node4', style: { x: 150, y: 30, label: 'Node 4' } },// Middle area{ id: 'node5', style: { x: 220, y: 140, label: 'Node 5' } },{ id: 'node6', style: { x: 280, y: 160, label: 'Node 6' } },{ id: 'node7', style: { x: 220, y: 120, label: 'Node 7' } },{ id: 'node8', style: { x: 260, y: 100, label: 'Node 8' } },{ id: 'node9', style: { x: 240, y: 130, label: 'Node 9' } },{ id: 'node10', style: { x: 300, y: 110, label: 'Node 10' } },// Lower area{ id: 'node11', style: { x: 240, y: 200, label: 'Node 11' } },{ id: 'node12', style: { x: 280, y: 220, label: 'Node 12' } },{ id: 'node13', style: { x: 300, y: 190, label: 'Node 13' } },{ id: 'node14', style: { x: 320, y: 210, label: 'Node 14' } },],edges: [// Upper connections{ id: 'edge1', source: 'node1', target: 'node2' },{ id: 'edge2', source: 'node2', target: 'node3' },{ id: 'edge3', source: 'node3', target: 'node4' },// Middle connections{ id: 'edge4', source: 'node5', target: 'node6' },{ id: 'edge5', source: 'node6', target: 'node7' },{ id: 'edge6', source: 'node7', target: 'node8' },{ id: 'edge7', source: 'node8', target: 'node9' },{ id: 'edge8', source: 'node9', target: 'node10' },// Lower connections{ id: 'edge9', source: 'node11', target: 'node12' },{ id: 'edge10', source: 'node12', target: 'node13' },{ id: 'edge11', source: 'node13', target: 'node14' },// Cross-region connections{ id: 'edge12', source: 'node4', target: 'node8' },{ id: 'edge13', source: 'node7', target: 'node11' },{ id: 'edge14', source: 'node10', target: 'node13' },],},node: {style: {size: 20,label: true,},},edge: {style: {stroke: '#91d5ff',lineWidth: 1,},},plugins: [{type: 'edge-filter-lens',r: 80,style: {fill: '#f0f5ff',fillOpacity: 0.6,stroke: '#7e3feb',strokeOpacity: 0.8,lineWidth: 1.5,},nodeStyle: {size: 24,fill: '#7e3feb',stroke: '#5719c9',lineWidth: 1,label: true,labelFill: '#ffffff',labelFontSize: 14,labelFontWeight: 'bold',},edgeStyle: {stroke: '#8b9baf',lineWidth: 2,label: true,labelFill: '#5719c9',opacity: 0.8,},},],},{ width: 400, height: 300 },);