Loading...
The BubbleSets plugin represents sets and their relationships by creating bubble shapes, helping users intuitively understand logical relationships such as intersections and unions between sets. It is a tool to enhance data visualization effects, especially suitable for displaying complex data set relationships.
The BubbleSets plugin is mainly suitable for the following scenarios:
Below is a simple example of initializing the BubbleSets plugin:
const graph = new Graph({plugins: [{type: 'bubble-sets',members: ['node1', 'node2'], // List of node IDs to be enclosedlabel: true, // Whether to display labels},],});
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: [{id: 'edge-0',source: 'node-0',target: 'node-2',},{id: 'edge-1',source: 'node-1',target: 'node-2',},{id: 'edge-2',source: 'node-2',target: 'node-3',},{id: 'edge-3',source: 'node-3',target: 'node-4',},{id: 'edge-4',source: 'node-3',target: 'node-5',},{id: 'edge-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: 'bubble-sets',key: 'bubble-sets',members: ['node-0', 'node-1', 'node-2', 'node-3'],labelText: 'bubblesets-a',fill: '#7e3feb',fillOpacity: 0.1,stroke: '#7e3feb',strokeOpacity: 1,labelFill: '#fff',labelPadding: 2,labelBackgroundFill: '#7e3feb',labelBackgroundRadius: 5,},],},{ width: 600, height: 450 },(gui, graph) => {const options = {type: 'bubble-sets',members: ['node-0', 'node-1', 'node-2', 'node-3'],avoidMembers: [],// stylefill: '#7e3feb',fillOpacity: 0.1,stroke: '#7e3feb',strokeOpacity: 1,// labellabel: true,labelCloseToPath: true,labelAutoRotate: true,labelOffsetX: 0,labelOffsetY: 0,labelPlacement: 'bottom',// bubblesetsmaxRoutingIterations: 100,maxMarchingIterations: 20,pixelGroup: 4,edgeR0: 10,edgeR1: 20,nodeR0: 15,nodeR1: 50,morphBuffer: 10,threshold: 1,memberInfluenceFactor: 1,edgeInfluenceFactor: 1,nonMemberInfluenceFactor: -0.8,virtualEdges: true,};const optionFolder = gui.addFolder('Bubblesets Options');optionFolder.add(options, 'type').disable();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.add(options, 'maxRoutingIterations', 0, 200, 1);optionFolder.add(options, 'maxMarchingIterations', 0, 40, 1);optionFolder.add(options, 'pixelGroup', 0, 20, 1);optionFolder.add(options, 'edgeR0', 0, 50, 1);optionFolder.add(options, 'edgeR1', 0, 50, 1);optionFolder.add(options, 'nodeR0', 0, 50, 1);optionFolder.add(options, 'nodeR1', 0, 50, 1);optionFolder.add(options, 'morphBuffer', 0, 20, 1);optionFolder.add(options, 'threshold', -1, 1, 0.1);optionFolder.add(options, 'memberInfluenceFactor', -1, 1, 0.1);optionFolder.add(options, 'edgeInfluenceFactor', -1, 1, 0.1);optionFolder.add(options, 'nonMemberInfluenceFactor', -1, 1, 0.1);optionFolder.add(options, 'virtualEdges');optionFolder.onChange(({ property, value }) => {graph.updatePlugin({key: 'bubble-sets',[property]: value,});graph.render();});const apiConfig = {member: 'node-1',avoidMember: 'node-1',};const apiFolder = gui.addFolder('Bubblesets API');const instance = graph.getPluginInstance('bubble-sets');const nodeIds = graph.getData().nodes.map((node) => node.id);const edgeIds = graph.getData().edges.map((edge) => edge.id);apiFolder.add(apiConfig, 'member', [...nodeIds, ...edgeIds]);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 Bubblesets: ' + instance.getMember()) }, 'removeMember').name('get member');apiFolder.add(apiConfig, 'avoidMember', nodeIds);apiFolder.add({ addAvoidMember: () => instance.addAvoidMember(apiConfig.avoidMember) }, 'addAvoidMember').name('add avoid member');apiFolder.add({ removeAvoidMember: () => instance.removeAvoidMember(apiConfig.avoidMember) }, 'removeAvoidMember').name('remove avoid member');apiFolder.add({ removeMember: () => alert('Avoid members in Bubblesets: ' + instance.getAvoidMember()) }, 'removeMember').name('get avoid member');},);
Property | Description | Type | Default Value | Required |
---|---|---|---|---|
type | Plugin type | string | bubble-sets | ✓ |
key | Unique identifier for the plugin for subsequent updates | string | - | |
members | Member elements, including nodes and edges, example | string[] | - | ✓ |
avoidMembers | Elements to avoid, not included when drawing contours (currently supports setting nodes) | string[] | - | |
label | Whether to display labels | boolean | true | |
labelPlacement | Label position | left | right | top | bottom | center | bottom | bottom | |
labelBackground | Whether to display background | boolean | false | |
labelPadding | Label padding | number | number[] | 0 | |
labelCloseToPath | Whether the label is close to the contour, example | boolean | true | |
labelAutoRotate | Whether the label rotates with the contour, example | boolean | true | |
labelOffsetX | Label x-axis offset | number | 0 | |
labelOffsetY | Label y-axis offset | number | 0 | |
labelMaxWidth | Maximum width of the text, automatically ellipsized if exceeded | number | - | |
maxRoutingIterations | Maximum number of iterations for calculating paths between members | number | 100 | |
maxMarchingIterations | Maximum number of iterations for calculating contours | number | 20 | |
pixelGroup | Number of pixels per potential area group, used to improve speed | number | 4 | |
edgeR0 | Edge radius parameter R0 | number | - | |
edgeR1 | Edge radius parameter R1 | number | - | |
nodeR0 | Node radius parameter R0 | number | - | |
nodeR1 | Node radius parameter R1 | number | - | |
morphBuffer | Morph buffer size | number | ||
threshold | Threshold | number | - | |
memberInfluenceFactor | Member influence factor | number | - | |
edgeInfluenceFactor | Edge influence factor | number | - | |
nonMemberInfluenceFactor | Non-member influence factor | number | - | |
virtualEdges | Whether to use virtual edges | boolean | - |
Member elements, including nodes and edges.
const graph = new Graph({plugins: [{type: 'bubble-sets',members: ['node-0', 'node-1', 'node-2'],},],});
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: [{id: 'edge-0',source: 'node-0',target: 'node-2',},{id: 'edge-1',source: 'node-1',target: 'node-2',},{id: 'edge-2',source: 'node-2',target: 'node-3',},{id: 'edge-3',source: 'node-3',target: 'node-4',},{id: 'edge-4',source: 'node-3',target: 'node-5',},{id: 'edge-5',source: 'node-3',target: 'node-6',},],},behaviors: ['drag-canvas', 'zoom-canvas'],plugins: [{type: 'bubble-sets',key: 'bubble-sets-a',members: ['node-0', 'node-1', 'node-2'],},],},{ width: 300, height: 150 },);
Example: Do not let the label stick to the contour
const graph = new Graph({plugins: [{type: 'bubble-sets',members: ['node-0', 'node-1', 'node-2', 'node-3'],label: true, // Display labellabelText: 'cluster-a',labelCloseToPath: false,},],});
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: [{id: 'edge-0',source: 'node-0',target: 'node-2',},{id: 'edge-1',source: 'node-1',target: 'node-2',},{id: 'edge-2',source: 'node-2',target: 'node-3',},{id: 'edge-3',source: 'node-3',target: 'node-4',},{id: 'edge-4',source: 'node-3',target: 'node-5',},{id: 'edge-5',source: 'node-3',target: 'node-6',},],},plugins: [{key: 'bubble-sets-a',type: 'bubble-sets',members: ['node-0', 'node-1', 'node-2', 'node-3'],label: true, // Display labellabelText: 'cluster-a',labelCloseToPath: false,},],behaviors: ['drag-canvas', 'zoom-canvas'],},{ width: 300, height: 150 },);
Example: Do not let the label rotate with the contour
const graph = new Graph({plugins: [{type: 'bubble-sets',members: ['node-0', 'node-1', 'node-2', 'node-3'],label: true, // Display labellabelText: 'cluster-a',labelAutoRotate: false,},],});
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: [{id: 'edge-0',source: 'node-0',target: 'node-2',},{id: 'edge-1',source: 'node-1',target: 'node-2',},{id: 'edge-2',source: 'node-2',target: 'node-3',},{id: 'edge-3',source: 'node-3',target: 'node-4',},{id: 'edge-4',source: 'node-3',target: 'node-5',},{id: 'edge-5',source: 'node-3',target: 'node-6',},],},plugins: [{key: 'bubble-sets-a',type: 'bubble-sets',members: ['node-0', 'node-1', 'node-2', 'node-3'],label: true, // Display labellabelText: 'cluster-a',labelAutoRotate: false,},],behaviors: ['drag-canvas', 'zoom-canvas'],},{ width: 300, height: 150 },);
The simplest way is to use the preset configuration directly:
const graph = new Graph({plugins: [{type: 'bubble-sets',members: ['node-0', 'node-1', 'node-2', 'node-3'],},],});
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: [{id: 'edge-0',source: 'node-0',target: 'node-2',},{id: 'edge-1',source: 'node-1',target: 'node-2',},{id: 'edge-2',source: 'node-2',target: 'node-3',},{id: 'edge-3',source: 'node-3',target: 'node-4',},{id: 'edge-4',source: 'node-3',target: 'node-5',},{id: 'edge-5',source: 'node-3',target: 'node-6',},],},behaviors: ['drag-canvas', 'zoom-canvas'],plugins: [{type: 'bubble-sets',key: 'bubble-sets-a',members: ['node-0', 'node-1', 'node-2', 'node-3'],},],},{ width: 300, height: 150 },);
const graph = new Graph({plugins: [{type: 'bubble-sets',members: ['node-0', 'node-1', 'node-2', 'node-3'],fill: '#7e3feb', // Bubble fill colorfillOpacity: 0.1, // Fill opacitystroke: '#7e3feb', // Border colorstrokeOpacity: 1, // Border opacity},],});
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: [{id: 'edge-0',source: 'node-0',target: 'node-2',},{id: 'edge-1',source: 'node-1',target: 'node-2',},{id: 'edge-2',source: 'node-2',target: 'node-3',},{id: 'edge-3',source: 'node-3',target: 'node-4',},{id: 'edge-4',source: 'node-3',target: 'node-5',},{id: 'edge-5',source: 'node-3',target: 'node-6',},],},plugins: [{type: 'bubble-sets',members: ['node-0', 'node-1', 'node-2', 'node-3'],fill: '#7e3feb', // Bubble fill colorfillOpacity: 0.1, // Fill opacitystroke: '#7e3feb', // Border colorstrokeOpacity: 1, // Border opacity},],behaviors: ['drag-canvas', 'zoom-canvas'],},{ width: 300, height: 150 },);
You can configure the position, background, offset, and other properties of the label to enhance the visualization effect.
const graph = new Graph({plugins: [{type: 'bubble-sets',members: ['node-0', 'node-1', 'node-2', 'node-3'],label: true, // Display labellabelPlacement: '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: [{id: 'edge-0',source: 'node-0',target: 'node-2',},{id: 'edge-1',source: 'node-1',target: 'node-2',},{id: 'edge-2',source: 'node-2',target: 'node-3',},{id: 'edge-3',source: 'node-3',target: 'node-4',},{id: 'edge-4',source: 'node-3',target: 'node-5',},{id: 'edge-5',source: 'node-3',target: 'node-6',},],},plugins: [{key: 'bubble-sets-a',type: 'bubble-sets',members: ['node-0', 'node-1', 'node-2', 'node-3'],label: true, // Display labellabelText: 'cluster-a',labelPlacement: 'top', // Label positionlabelBackground: true, // Display label backgroundlabelPadding: 5, // Label padding},],behaviors: ['drag-canvas', 'zoom-canvas'],},{ width: 300, height: 150 },);