Loading...
The Fisheye plugin is designed for focus+context exploration scenarios. It can magnify the area of interest while ensuring that the context and the relationship between the context and the focus center are not lost. It is an important visualization exploration tool.
Below is a simple example of initializing the Fisheye plugin:
const graph = new Graph({plugins: [{type: 'fisheye',trigger: 'drag', // Move fisheye by draggingd: 1.5, // Set distortion factorr: 120, // Set fisheye radiusshowDPercent: true, // Show distortion degree},],});
createGraph({data: {nodes: [// 上部节点{ id: 'Myriel', style: { x: 197, y: 58 } },{ id: 'Napoleon', style: { x: 147, y: 22 } },{ id: 'Mlle.Baptistine', style: { x: 225, y: 141 } },{ id: 'Mme.Magloire', style: { x: 255, y: 120 } },{ id: 'CountessdeLo', style: { x: 151, y: -3 } },{ id: 'Geborand', style: { x: 136, y: 41 } },{ id: 'Champtercier', style: { x: 227, y: 8 } },{ id: 'Cravatte', style: { x: 172, y: -10 } },{ id: 'Count', style: { x: 172, y: 12 } },{ id: 'OldMan', style: { x: 198, y: -6 } },// 中上部节点{ id: 'Labarre', style: { x: 266, y: 203 } },{ id: 'Marguerite', style: { x: 265, y: 171 } },{ id: 'Mme.deR', style: { x: 299, y: 133 } },{ id: 'Isabeau', style: { x: 282, y: 191 } },{ id: 'Gervais', style: { x: 334, y: 148 } },{ id: 'Simplice', style: { x: 286, y: 227 } },{ id: 'Scaufflaire', style: { x: 250, y: 231 } },{ id: 'Woman1', style: { x: 375, y: 202 } },{ id: 'Judge', style: { x: 370, y: 139 } },{ id: 'Champmathieu', style: { x: 404, y: 216 } },// 中部主要节点{ id: 'Valjean', style: { x: 322, y: 221 } },{ id: 'Fantine', style: { x: 337, y: 187 } },{ id: 'Cosette', style: { x: 343, y: 248 } },{ id: 'Javert', style: { x: 368, y: 263 } },{ id: 'Thenardier', style: { x: 317, y: 300 } },{ id: 'Mme.Thenardier', style: { x: 283, y: 267 } },{ id: 'Eponine', style: { x: 268, y: 365 } },{ id: 'Gavroche', style: { x: 393, y: 380 } },{ id: 'Marius', style: { x: 336, y: 350 } },{ id: 'Enjolras', style: { x: 376, y: 371 } },// 右侧和右上节点{ id: 'Gribier', style: { x: 437, y: 160 } },{ id: 'Jondrette', style: { x: 510, y: 327 } },{ id: 'Mme.Burgon', style: { x: 466, y: 368 } },{ id: 'Brevet', style: { x: 399, y: 183 } },{ id: 'Chenildieu', style: { x: 425, y: 194 } },{ id: 'Cochepaille', style: { x: 419, y: 148 } },{ id: 'Child1', style: { x: 361, y: 387 } },{ id: 'Child2', style: { x: 415, y: 432 } },{ id: 'Brujon', style: { x: 330, y: 394 } },{ id: 'Mme.Hucheloup', style: { x: 394, y: 450 } },// 中部其他节点{ id: 'Favourite', style: { x: 284, y: 153 } },{ id: 'Dahlia', style: { x: 303, y: 170 } },{ id: 'Zephine', style: { x: 286, y: 94 } },{ id: 'Tholomyes', style: { x: 359, y: 158 } },{ id: 'Listolier', style: { x: 308, y: 80 } },{ id: 'Fameuil', style: { x: 329, y: 89 } },{ id: 'Blacheville', style: { x: 351, y: 95 } },{ id: 'Perpetue', style: { x: 234, y: 195 } },{ id: 'Woman2', style: { x: 304, y: 254 } },{ id: 'MotherInnocent', style: { x: 350, y: 214 } },// 下部节点{ id: 'Pontmercy', style: { x: 375, y: 307 } },{ id: 'Boulatruelle', style: { x: 260, y: 279 } },{ id: 'Anzelma', style: { x: 234, y: 303 } },{ id: 'Gillenormand', style: { x: 338, y: 286 } },{ id: 'Magnon', style: { x: 277, y: 317 } },{ id: 'Mlle.Gillenormand', style: { x: 257, y: 306 } },{ id: 'Mme.Pontmercy', style: { x: 307, y: 318 } },{ id: 'Mlle.Vaubois', style: { x: 197, y: 325 } },{ id: 'Lt.Gillenormand', style: { x: 294, y: 296 } },{ id: 'Toussaint', style: { x: 306, y: 277 } },{ id: 'Gueulemer', style: { x: 344, y: 323 } },{ id: 'Babet', style: { x: 367, y: 319 } },{ id: 'Claquesous', style: { x: 303, y: 347 } },{ id: 'Montparnasse', style: { x: 322, y: 330 } },// 最下部节点{ id: 'Combeferre', style: { x: 397, y: 416 } },{ id: 'Prouvaire', style: { x: 309, y: 426 } },{ id: 'Feuilly', style: { x: 314, y: 456 } },{ id: 'Courfeyrac', style: { x: 332, y: 435 } },{ id: 'Bahorel', style: { x: 343, y: 466 } },{ id: 'Bossuet', style: { x: 305, y: 382 } },{ id: 'Joly', style: { x: 371, y: 415 } },{ id: 'Grantaire', style: { x: 370, y: 466 } },{ id: 'MotherPlutarch', style: { x: 424, y: 461 } },],edges: [// 主要连接{ id: 'e1', source: 'Valjean', target: 'Javert' },{ id: 'e2', source: 'Valjean', target: 'Cosette' },{ id: 'e3', source: 'Javert', target: 'Thenardier' },{ id: 'e4', source: 'Cosette', target: 'Marius' },{ id: 'e5', source: 'Eponine', target: 'Marius' },{ id: 'e6', source: 'Enjolras', target: 'Marius' },{ id: 'e7', source: 'Gavroche', target: 'Enjolras' },{ id: 'e8', source: 'Valjean', target: 'Fantine' },{ id: 'e9', source: 'Cosette', target: 'Thenardier' },{ id: 'e10', source: 'Eponine', target: 'Thenardier' },// 上部连接{ id: 'e11', source: 'Myriel', target: 'Napoleon' },{ id: 'e12', source: 'Myriel', target: 'Mlle.Baptistine' },{ id: 'e13', source: 'Mlle.Baptistine', target: 'Mme.Magloire' },{ id: 'e14', source: 'CountessdeLo', target: 'Myriel' },{ id: 'e15', source: 'Geborand', target: 'Myriel' },// 中部连接{ id: 'e16', source: 'Favourite', target: 'Tholomyes' },{ id: 'e17', source: 'Dahlia', target: 'Favourite' },{ id: 'e18', source: 'Zephine', target: 'Favourite' },{ id: 'e19', source: 'Tholomyes', target: 'Listolier' },{ id: 'e20', source: 'Fameuil', target: 'Blacheville' },// 下部连接{ id: 'e21', source: 'Combeferre', target: 'Enjolras' },{ id: 'e22', source: 'Prouvaire', target: 'Combeferre' },{ id: 'e23', source: 'Feuilly', target: 'Courfeyrac' },{ id: 'e24', source: 'Bahorel', target: 'Bossuet' },{ id: 'e25', source: 'Joly', target: 'Grantaire' },// 额外的中部连接{ id: 'e26', source: 'Gueulemer', target: 'Thenardier' },{ id: 'e27', source: 'Babet', target: 'Gueulemer' },{ id: 'e28', source: 'Claquesous', target: 'Montparnasse' },{ id: 'e29', source: 'Brujon', target: 'Babet' },{ id: 'e30', source: 'Child1', target: 'Gavroche' },// 新增更多连接{ id: 'e31', source: 'Valjean', target: 'Simplice' },{ id: 'e32', source: 'Fantine', target: 'Simplice' },{ id: 'e33', source: 'Javert', target: 'Simplice' },{ id: 'e34', source: 'Marius', target: 'Gillenormand' },{ id: 'e35', source: 'Cosette', target: 'Gillenormand' },{ id: 'e36', source: 'Marius', target: 'Lt.Gillenormand' },{ id: 'e37', source: 'Gillenormand', target: 'Lt.Gillenormand' },{ id: 'e38', source: 'Cosette', target: 'Toussaint' },{ id: 'e39', source: 'Javert', target: 'Toussaint' },{ id: 'e40', source: 'Valjean', target: 'Toussaint' },// 随机添加更多连接...Array.from({ length: 50 }, (_, i) => ({// 从40增加到50个随机连接id: `edge-${i + 41}`,source: ['Valjean','Javert','Cosette','Marius','Enjolras','Fantine','Thenardier','Eponine','Gavroche','Gueulemer','Babet','Claquesous','Favourite','Tholomyes','Simplice',][Math.floor(Math.random() * 15)],target: ['Favourite','Dahlia','Tholomyes','Combeferre','Prouvaire','Feuilly','Courfeyrac','Bahorel','Bossuet','Montparnasse','Brujon','Child1','Simplice','Toussaint','Gillenormand',][Math.floor(Math.random() * 15)],})),],},autoFit: 'view',node: {style: {size: (datum) => datum.id.length * 2 + 10,label: false,labelText: (datum) => datum.id,labelBackground: true,icon: false,iconFontFamily: 'iconfont',iconText: '\ue6f6',iconFill: '#fff',},palette: {type: 'group',field: (datum) => datum.id,color: ['#1783FF', '#00C9C9', '#F08F56', '#D580FF'],},},edge: {style: {stroke: '#bfbfbf',},},behaviors: ['drag-canvas'],plugins: [{type: 'fisheye',key: 'fisheye',r: 120,d: 1.5,nodeStyle: {label: true,icon: true,},},],},{ width: 600, height: 300 },(gui, graph) => {const TRIGGER_OPTIONS = ['pointermove', 'drag', 'click'];const SCALE_OPTIONS = ['wheel', 'drag', '-'];const options = {type: 'fisheye',trigger: 'pointermove',r: 120,d: 1.5,maxR: 200,minR: 50,maxD: 5,minD: 0.5,scaleRBy: '-',scaleDBy: '-',showDPercent: true,preventDefault: true,};const optionFolder = gui.addFolder('Fisheye Options');optionFolder.add(options, 'type').disable(true);optionFolder.add(options, 'trigger', TRIGGER_OPTIONS);optionFolder.add(options, 'r', 50, 200, 10);optionFolder.add(options, 'd', 0.5, 5, 0.1);optionFolder.add(options, 'scaleRBy', SCALE_OPTIONS);optionFolder.add(options, 'scaleDBy', SCALE_OPTIONS);optionFolder.add(options, 'showDPercent');optionFolder.add(options, 'preventDefault');optionFolder.onChange(({ property, value }) => {graph.updatePlugin({key: 'fisheye',[property]: value === '-' ? undefined : value,});graph.render();});},);
Property | Description | Type | Default Value | Required |
---|---|---|---|---|
type | Plugin type | string | fisheye | ✓ |
key | Unique identifier for the plugin, can be used to get the plugin instance or update plugin options | string | - | |
trigger | Method to move the fisheye: - pointermove : The fisheye always follows the mouse movement - click : Move the fisheye to the click position when clicking on the canvas - drag : Move the fisheye by dragging | pointermove | drag | click | pointermove | |
r | Radius of the fisheye | number | 120 | |
maxR | Maximum adjustable radius of the fisheye | number | Half of the smaller dimension of the canvas | |
minR | Minimum adjustable radius of the fisheye | number | 0 | |
d | Distortion factor | number | 1.5 | |
maxD | Maximum adjustable distortion factor of the fisheye | number | 5 | |
minD | Minimum adjustable distortion factor of the fisheye | number | 0 | |
scaleRBy | Method to adjust the fisheye radius: - 'wheel' : Adjust by wheel - 'drag' : Adjust by dragging | wheel | drag | - | |
scaleDBy | Method to adjust the fisheye distortion factor: - 'wheel' : Adjust by wheel - 'drag' : Adjust by dragging | wheel | drag | - | |
showDPercent | Whether to show the distortion factor value in the fisheye | boolean | true | |
style | Style of the fisheye, configuration options | object | - | |
nodeStyle | Style of nodes in the fisheye | NodeStyle | ((datum: NodeData) => NodeStyle) | { label: true } | |
preventDefault | Whether to prevent default events | boolean | true |
Circular style properties for configuring the appearance of the fisheye.
Property | Description | Type | Default Value |
---|---|---|---|
fill | Fill color | string | Pattern | null | #ccc |
stroke | Stroke color | string | Pattern | null | #000 |
opacity | Overall opacity | number | string | - |
fillOpacity | Fill opacity | number | string | 0.1 |
strokeOpacity | Stroke opacity | number | string | - |
lineWidth | Line width | number | string | 2 |
lineCap | Line cap style | butt | round | square | - |
lineJoin | Line join style | miter | round | bevel | - |
shadowColor | Shadow color | string | - |
shadowBlur | Shadow blur degree | number | - |
shadowOffsetX | Shadow X offset | number | - |
shadowOffsetY | Shadow Y offset | number | - |
For complete style properties, refer to Element - Node - Built-in Node - General Style Properties - style
scaleRBy
and scaleDBy
can be used to control the adjustment method of the fisheye's radius and distortion factor respectively:
const graph = new Graph({plugins: [{type: 'fisheye',// Adjust radius by wheelscaleRBy: 'wheel',// Adjust distortion factor by draggingscaleDBy: 'drag',// Set range for radius and distortion factorminR: 50,maxR: 200,minD: 1,maxD: 3,},],});
Note: When trigger
, scaleRBy
, and scaleDBy
are all set to 'drag'
, the priority order is trigger
> scaleRBy
> scaleDBy
, and only the highest priority configuration item will bind the drag event. Similarly, if scaleRBy
and scaleDBy
are both set to 'wheel'
, only scaleRBy
will bind the wheel event.
The simplest configuration method:
const graph = new Graph({plugins: ['fisheye'],});
You can customize the appearance and behavior of the fisheye:
const graph = new Graph({plugins: [{type: 'fisheye',r: 150,d: 2,style: {fill: '#2f54eb', // Fill color of the fisheye areafillOpacity: 0.2, // Opacity of the fill areastroke: '#1d39c4', // Color of the fisheye borderstrokeOpacity: 0.8, // Opacity of the borderlineWidth: 1.5, // Line width of the bordershadowColor: '#1d39c4', // Shadow colorshadowBlur: 10, // Shadow blur radiusshadowOffsetX: 0, // Horizontal shadow offsetshadowOffsetY: 0, // Vertical shadow offsetcursor: 'pointer', // Cursor style when hovering},nodeStyle: {// Basic node stylesize: 40, // Node sizefill: '#d6e4ff', // Node fill colorstroke: '#2f54eb', // Node border colorlineWidth: 2, // Node border widthshadowColor: '#2f54eb', // Node shadow colorshadowBlur: 5, // Node shadow blur radiuscursor: 'pointer', // Cursor style when hovering// Label stylelabel: true, // Show labellabelFontSize: 14, // Label font sizelabelFontWeight: 'bold', // Label font weightlabelFill: '#1d39c4', // Label text colorlabelBackground: true, // Show label backgroundlabelBackgroundFill: '#fff', // Label background fill colorlabelBackgroundStroke: '#1d39c4', // Label background border colorlabelBackgroundOpacity: 0.8, // Label background opacitylabelBackgroundPadding: [4, 8, 4, 8], // Label background padding [top, right, bottom, left]// Icon styleicon: true, // Show iconiconFontFamily: 'iconfont', // Icon fonticonText: '\ue6f6', // Icon UnicodeiconFill: '#1d39c4', // Icon coloriconSize: 16, // Icon sizeiconFontWeight: 'normal', // Icon font weight},},],});
The effect is as follows:
createGraph({data: {nodes: [{ id: 'node-1', style: { x: 150, y: 100 } },{ id: 'node-2', style: { x: 250, y: 100 } },{ id: 'node-3', style: { x: 200, y: 180 } },{ id: 'node-4', style: { x: 120, y: 180 } },{ id: 'node-5', style: { x: 280, y: 180 } },],edges: [{ id: 'edge-1', source: 'node-1', target: 'node-2' },{ id: 'edge-2', source: 'node-1', target: 'node-3' },{ id: 'edge-3', source: 'node-2', target: 'node-3' },{ id: 'edge-4', source: 'node-3', target: 'node-4' },{ id: 'edge-5', source: 'node-3', target: 'node-5' },],},node: {style: {size: 30,fill: '#e6f7ff',stroke: '#1890ff',lineWidth: 1,label: false,icon: false,},},edge: {style: {stroke: '#91d5ff',lineWidth: 1,},},plugins: [{type: 'fisheye',key: 'fisheye',r: 100,d: 2,style: {fill: '#2f54eb', // Fill color of the fisheye areafillOpacity: 0.2, // Opacity of the fill areastroke: '#1d39c4', // Color of the fisheye borderstrokeOpacity: 0.8, // Opacity of the borderlineWidth: 1.5, // Line width of the bordershadowColor: '#1d39c4', // Shadow colorshadowBlur: 10, // Shadow blur radiusshadowOffsetX: 0, // Horizontal shadow offsetshadowOffsetY: 0, // Vertical shadow offsetcursor: 'pointer', // Cursor style when hovering},nodeStyle: {// Basic node stylesize: 40, // Node sizefill: '#d6e4ff', // Node fill colorstroke: '#2f54eb', // Node border colorlineWidth: 2, // Node border widthshadowColor: '#2f54eb', // Node shadow colorshadowBlur: 5, // Node shadow blur radiuscursor: 'pointer', // Cursor style when hovering// Label stylelabel: true, // Show labellabelFontSize: 14, // Label font sizelabelFontWeight: 'bold', // Label font weightlabelFill: '#1d39c4', // Label text colorlabelBackground: true, // Show label backgroundlabelBackgroundFill: '#fff', // Label background fill colorlabelBackgroundStroke: '#1d39c4', // Label background border colorlabelBackgroundOpacity: 0.8, // Label background opacitylabelBackgroundPadding: [4, 8, 4, 8], // Label background padding [top, right, bottom, left]// Icon styleicon: true, // Show iconiconFontFamily: 'iconfont', // Icon fonticonText: '\ue6f6', // Icon UnicodeiconFill: '#1d39c4', // Icon coloriconSize: 16, // Icon sizeiconFontWeight: 'normal', // Icon font weight},},],},{ width: 400, height: 300 },);