Loading...
Hull is used to process and represent the convex or concave polygon bounding box of a set of points. It can wrap a set of nodes in a minimal geometric shape, helping users better understand and analyze datasets.
The hull plugin is mainly applicable to the following scenarios:
Below is a simple example of initializing the Hull plugin:
const graph = new Graph({plugins: [{type: 'hull',key: 'my-hull', // Specify a unique identifier for subsequent dynamic updatesmembers: ['node-1', 'node-2'], // List of node IDs to be wrappedconcavity: Infinity, // Default to convex hull},],});
createGraph({data: {nodes: [{id: 'node-0',data: { cluster: 'a' },style: { x: 555, y: 151 },},{id: 'node-1',data: { cluster: 'a' },style: { x: 532, y: 323 },},{id: 'node-2',data: { cluster: 'a' },style: { x: 473, y: 227 },},{id: 'node-3',data: { cluster: 'a' },style: { x: 349, y: 212 },},{id: 'node-4',data: { cluster: 'b' },style: { x: 234, y: 201 },},{id: 'node-5',data: { cluster: 'b' },style: { x: 338, y: 333 },},{id: 'node-6',data: { cluster: 'b' },style: { x: 365, y: 91 },},],edges: [{source: 'node-0',target: 'node-2',},{source: 'node-1',target: 'node-2',},{source: 'node-2',target: 'node-3',},{source: 'node-3',target: 'node-4',},{source: 'node-3',target: 'node-5',},{source: 'node-3',target: 'node-6',},],},node: {style: { labelText: (d) => d.id },palette: { field: 'cluster', color: ['#7e3feb', '#ffa940'] },},behaviors: ['drag-canvas', 'drag-element'],plugins: ['grid-line',{type: 'hull',key: 'hull-a',members: ['node-0', 'node-1', 'node-2', 'node-3'],labelText: 'hull-a',fill: '#7e3feb',stroke: '#7e3feb',fillOpacity: 0.1,strokeOpacity: 1,labelFill: '#fff',labelPadding: 2,labelBackgroundFill: '#7e3feb',labelBackgroundRadius: 5,},],},{ width: 600, height: 450 },(gui, graph) => {const options = {type: 'hull',members: ['node-0', 'node-1', 'node-2', 'node-3'],concavity: Infinity,corner: 'rounded',padding: 10,// stylefill: '#7e3feb',stroke: '#7e3feb',fillOpacity: 0.1,strokeOpacity: 1,// labellabel: true,labelCloseToPath: true,labelAutoRotate: true,labelOffsetX: 0,labelOffsetY: 0,labelPlacement: 'bottom',};const optionFolder = gui.addFolder('Hull Options');optionFolder.add(options, 'type').disable();optionFolder.add(options, 'concavity', 0, 200, 1);optionFolder.add(options, 'corner', ['rounded', 'smooth', 'sharp']);optionFolder.add(options, 'padding', 0, 20, 1);optionFolder.addColor(options, 'fill');optionFolder.addColor(options, 'stroke');optionFolder.add(options, 'fillOpacity', 0, 1, 0.1);optionFolder.add(options, 'strokeOpacity', 0, 1, 0.1);optionFolder.add(options, 'label');optionFolder.add(options, 'labelCloseToPath');optionFolder.add(options, 'labelAutoRotate');optionFolder.add(options, 'labelOffsetX', 0, 20, 1);optionFolder.add(options, 'labelOffsetY', 0, 20, 1);optionFolder.add(options, 'labelPlacement', ['left', 'right', 'top', 'bottom', 'center']);optionFolder.onChange(({ property, value }) => {graph.updatePlugin({key: 'hull-a',[property]: value,});graph.render();});const apiConfig = {member: 'node-1',};const apiFolder = gui.addFolder('Hull API');const instance = graph.getPluginInstance('hull-a');apiFolder.add(apiConfig,'member',new Array(7).fill(0).map((_, index) => `node-${index}`),);apiFolder.add({ addMember: () => instance.addMember(apiConfig.member) }, 'addMember').name('add member');apiFolder.add({ removeMember: () => instance.removeMember(apiConfig.member) }, 'removeMember').name('remove member');apiFolder.add({ removeMember: () => alert('Members in Hull-a: ' + instance.getMember()) }, 'removeMember').name('get member');},);
Property | Description | Type | Default Value | Required |
---|---|---|---|---|
type | Plugin type | string | hull | ✓ |
key | Unique identifier for the plugin, used for subsequent updates | string | - | |
members | Elements within the Hull, including nodes and edges | string[] | - | ✓ |
concavity | Concavity, the larger the value, the smaller the concavity; default is Infinity representing Convex Hull | number | Infinity | |
corner | Corner type, options are rounded | smooth | sharp | string | rounded | |
padding | Padding | number | 10 | |
label | Whether to display the label | boolean | true | |
labelPlacement | Label position | left | right | top | bottom | center | bottom | |
labelBackground | Whether to display the background | boolean | false | |
labelPadding | Label padding | number | number[] | 0 | |
labelCloseToPath | Whether the label is close to the hull | boolean | true | |
labelAutoRotate | Whether the label rotates with the hull, effective only when closeToPath is true | boolean | true | |
labelOffsetX | X-axis offset | number | 0 | |
labelOffsetY | Y-axis offset | number | 0 | |
labelMaxWidth | Maximum width of the text, exceeding will automatically ellipsis | number | 0 |
For complete label styles, see this link
The concavity attribute is used to control the concavity of the Hull. When set to Infinity, a convex hull is generated; otherwise, a concave hull is generated.
// Convex hull exampleconst graph = new Graph({plugins: [{type: 'hull',concavity: Infinity, // Convex hullmembers: ['node-1', 'node-2'],},],});// Concave hull exampleconst graph = new Graph({plugins: [{type: 'hull',concavity: 50, // Concave hullmembers: ['node-1', 'node-2'],},],});
The simplest way is to use the preset configuration directly:
const graph = new Graph({plugins: [{type: 'hull',members: ['node-1', 'node-2'], // List of node IDs to be wrapped},],});
The effect is as follows:
createGraph({autoFit: 'view',data: {nodes: [{id: 'node-0',data: { cluster: 'a' },style: { x: 555, y: 151 },},{id: 'node-1',data: { cluster: 'a' },style: { x: 532, y: 323 },},{id: 'node-2',data: { cluster: 'a' },style: { x: 473, y: 227 },},{id: 'node-3',data: { cluster: 'a' },style: { x: 349, y: 212 },},{id: 'node-4',data: { cluster: 'b' },style: { x: 234, y: 201 },},{id: 'node-5',data: { cluster: 'b' },style: { x: 338, y: 333 },},{id: 'node-6',data: { cluster: 'b' },style: { x: 365, y: 91 },},],edges: [{source: 'node-0',target: 'node-2',},{source: 'node-1',target: 'node-2',},{source: 'node-2',target: 'node-3',},{source: 'node-3',target: 'node-4',},{source: 'node-3',target: 'node-5',},{source: 'node-3',target: 'node-6',},],},plugins: [{type: 'hull',members: ['node-1', 'node-2'], // List of node IDs to be wrapped},],behaviors: ['zoom-canvas', 'drag-canvas'],},{ width: 300, height: 150 },);
You can customize the style of the Hull as needed, such as adjusting color, transparency, and other properties.
const graph = new Graph({plugins: [{type: 'hull',members: ['node-1', 'node-2', 'node-3'],stroke: '#ff000033', // Red semi-transparent borderfill: '#7e3feb', // Light purple fillfillOpacity: 0.2,lineWidth: 2,padding: 15, // Larger padding},],});
The effect is as follows:
createGraph({autoFit: 'view',data: {nodes: [{id: 'node-0',data: { cluster: 'a' },style: { x: 555, y: 151 },},{id: 'node-1',data: { cluster: 'a' },style: { x: 532, y: 323 },},{id: 'node-2',data: { cluster: 'a' },style: { x: 473, y: 227 },},{id: 'node-3',data: { cluster: 'a' },style: { x: 349, y: 212 },},{id: 'node-4',data: { cluster: 'b' },style: { x: 234, y: 201 },},{id: 'node-5',data: { cluster: 'b' },style: { x: 338, y: 333 },},{id: 'node-6',data: { cluster: 'b' },style: { x: 365, y: 91 },},],edges: [{source: 'node-0',target: 'node-2',},{source: 'node-1',target: 'node-2',},{source: 'node-2',target: 'node-3',},{source: 'node-3',target: 'node-4',},{source: 'node-3',target: 'node-5',},{source: 'node-3',target: 'node-6',},],},plugins: [{type: 'hull',members: ['node-1', 'node-2', 'node-3'],stroke: '#ff000033', // Red semi-transparent borderfill: '#7e3feb', // Light purple fillfillOpacity: 0.2,lineWidth: 2,padding: 15, // Larger padding},],behaviors: ['zoom-canvas', 'drag-canvas'],},{ width: 300, height: 150 },);
You can configure the position, background, offset, and other properties of the label to enhance the visual effect.
const graph = new Graph({plugins: [{type: 'hull',members: ['node-1', 'node-2'],label: true, // Display labellabelText: 'hull-a',labelPlacement: 'top', // Label positionlabelBackground: true, // Display label backgroundlabelPadding: 5, // Label padding},],});
The effect is as follows:
createGraph({autoFit: 'center',data: {nodes: [{id: 'node-0',data: { cluster: 'a' },style: { x: 555, y: 151 },},{id: 'node-1',data: { cluster: 'a' },style: { x: 532, y: 323 },},{id: 'node-2',data: { cluster: 'a' },style: { x: 473, y: 227 },},{id: 'node-3',data: { cluster: 'a' },style: { x: 349, y: 212 },},{id: 'node-4',data: { cluster: 'b' },style: { x: 234, y: 201 },},{id: 'node-5',data: { cluster: 'b' },style: { x: 338, y: 333 },},{id: 'node-6',data: { cluster: 'b' },style: { x: 365, y: 91 },},],edges: [{source: 'node-0',target: 'node-2',},{source: 'node-1',target: 'node-2',},{source: 'node-2',target: 'node-3',},{source: 'node-3',target: 'node-4',},{source: 'node-3',target: 'node-5',},{source: 'node-3',target: 'node-6',},],},plugins: [{type: 'hull',members: ['node-1', 'node-2'],label: true, // Display labellabelText: 'hull-a',labelPlacement: 'top', // Label positionlabelBackground: true, // Display label backgroundlabelPadding: 5, // Label padding},],behaviors: ['zoom-canvas', 'drag-canvas'],},{ width: 300, height: 150 },);