@logixode/force-graph-lib 0.1.1 โ 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +250 -66
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,117 +2,301 @@
|
|
|
2
2
|
|
|
3
3
|
A TypeScript library for creating interactive force-directed graphs with advanced features and optimizations for handling large datasets.
|
|
4
4
|
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Features](#features)
|
|
8
|
+
- [Installation](#installation)
|
|
9
|
+
- [Usage in TypeScript](#usage-in-typescript)
|
|
10
|
+
- [Usage in Vue.js](#usage-in-vuejs)
|
|
11
|
+
- [Detail for Types](#detail-for-types)
|
|
12
|
+
- [API Reference](#api-reference)
|
|
13
|
+
- [License](#license)
|
|
14
|
+
|
|
5
15
|
## Features
|
|
6
16
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
17
|
+
- ๐จ Highly customizable styling for nodes, links, and groups.
|
|
18
|
+
- ๐ฏ Dynamic label visibility control based on zoom level.
|
|
19
|
+
- ๐ Incremental data loading and dynamic data updates.
|
|
20
|
+
- โ๏ธ Fine-grained control over the force simulation (e.g., collision, clustering).
|
|
21
|
+
- ๐ Optimized for large datasets with features like dynamic cooldown time.
|
|
22
|
+
- ๐ผ๏ธ Group visualization with borders and labels.
|
|
23
|
+
- ๐ Zoom and pan, including programmatic focus on specific nodes or coordinates.
|
|
24
|
+
- โ Persist node positions after dragging.
|
|
25
|
+
- ๐ง Responsive design that adapts to container size changes.
|
|
26
|
+
- ๐ง Methods to query and manipulate graph data and state.
|
|
27
|
+
- ๐งน Full cleanup and resource destruction.
|
|
14
28
|
|
|
15
29
|
## Installation
|
|
16
30
|
|
|
17
31
|
```bash
|
|
18
|
-
npm install force-graph-lib
|
|
32
|
+
npm install @logixode/force-graph-lib
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Usage in TypeScript
|
|
36
|
+
|
|
37
|
+
Here's an example of how to use the library in a TypeScript project.
|
|
38
|
+
|
|
39
|
+
**HTML File**
|
|
40
|
+
|
|
41
|
+
```html
|
|
42
|
+
<div id="graph-container" style="width: 800px; height: 600px; border: 1px solid #ccc;"></div>
|
|
19
43
|
```
|
|
20
44
|
|
|
21
|
-
|
|
45
|
+
**TypeScript File (`main.ts`)**
|
|
22
46
|
|
|
23
47
|
```typescript
|
|
24
|
-
import { ForceGraph } from 'force-graph-lib'
|
|
48
|
+
import { ForceGraph } from '@logixode/force-graph-lib'
|
|
49
|
+
import type { GraphData, GraphOptions, NodeData } from '@logixode/force-graph-lib'
|
|
50
|
+
|
|
51
|
+
// 1. Get the container element
|
|
52
|
+
const container = document.getElementById('graph-container') as HTMLElement
|
|
53
|
+
|
|
54
|
+
// 2. Define initial data
|
|
55
|
+
const initialData: GraphData = {
|
|
56
|
+
nodes: [
|
|
57
|
+
{ id: '1', label: 'Topic Node', type: 'topic' },
|
|
58
|
+
{ id: '2', label: 'Post A', type: 'post' },
|
|
59
|
+
{ id: '3', label: 'Post B', type: 'post' },
|
|
60
|
+
],
|
|
61
|
+
links: [
|
|
62
|
+
{ source: '1', target: '2' },
|
|
63
|
+
{ source: '1', target: '3' },
|
|
64
|
+
],
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// 3. Define graph options
|
|
68
|
+
const options: GraphOptions = {
|
|
69
|
+
width: container.clientWidth,
|
|
70
|
+
height: container.clientHeight,
|
|
71
|
+
keepDragPosition: true,
|
|
72
|
+
labelThreshold: 1.2,
|
|
73
|
+
|
|
74
|
+
// Node styling
|
|
75
|
+
nodeSize: (node: NodeData) => (node.type === 'topic' ? 5 : 2),
|
|
76
|
+
nodeLabel: (node: NodeData) => node.label as string,
|
|
77
|
+
nodeColor: (node: NodeData) => (node.type === 'topic' ? '#FA8F21' : '#1877F2'),
|
|
78
|
+
nodeBorderWidth: 0.5,
|
|
79
|
+
nodeBorderColor: 'white',
|
|
80
|
+
|
|
81
|
+
// Link styling
|
|
82
|
+
linkWidth: 0.4,
|
|
83
|
+
linkCurvature: 0.1,
|
|
84
|
+
|
|
85
|
+
// Simulation forces
|
|
86
|
+
collide: (node: NodeData) => (node.type === 'topic' ? 15 : 5),
|
|
87
|
+
cluster: (node: NodeData) => node.type, // Group nodes by their 'type'
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// 4. Initialize the graph
|
|
91
|
+
const graph = new ForceGraph(container, initialData, options)
|
|
25
92
|
|
|
26
|
-
//
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
container,
|
|
30
|
-
{
|
|
93
|
+
// 5. Interact with the graph
|
|
94
|
+
setTimeout(() => {
|
|
95
|
+
const newData = {
|
|
31
96
|
nodes: [
|
|
32
|
-
{ id: '
|
|
33
|
-
{ id: '
|
|
97
|
+
{ id: '4', label: 'Post C', type: 'post' },
|
|
98
|
+
{ id: '5', label: 'Repost', type: 'repost' },
|
|
34
99
|
],
|
|
35
|
-
links: [
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
graph.
|
|
47
|
-
|
|
48
|
-
|
|
100
|
+
links: [
|
|
101
|
+
{ source: '2', target: '4' },
|
|
102
|
+
{ source: '1', target: '5' },
|
|
103
|
+
],
|
|
104
|
+
}
|
|
105
|
+
graph.addData(newData)
|
|
106
|
+
graph.focusPosition({ id: '4' })
|
|
107
|
+
}, 2000)
|
|
108
|
+
|
|
109
|
+
// Handle window resizing
|
|
110
|
+
window.addEventListener('resize', () => {
|
|
111
|
+
graph.setOptions({
|
|
112
|
+
width: container.clientWidth,
|
|
113
|
+
height: container.clientHeight,
|
|
114
|
+
})
|
|
115
|
+
graph.reinitialize()
|
|
49
116
|
})
|
|
117
|
+
```
|
|
50
118
|
|
|
51
|
-
|
|
52
|
-
graph.setLayout('circlepack')
|
|
119
|
+
## Usage in Vue.js
|
|
53
120
|
|
|
54
|
-
|
|
55
|
-
graph.setLabelThreshold(1.5)
|
|
121
|
+
Here is a basic example of integrating the library within a Vue 3 component using the Composition API.
|
|
56
122
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
123
|
+
```vue
|
|
124
|
+
<template>
|
|
125
|
+
<div ref="graphContainer" style="width: 100%; height: 600px;"></div>
|
|
126
|
+
</template>
|
|
60
127
|
|
|
61
|
-
|
|
62
|
-
|
|
128
|
+
<script setup lang="ts">
|
|
129
|
+
import { ref, graphContainer, onMounted, onUnmounted, watch } from 'vue'
|
|
130
|
+
import { ForceGraph } from '@logixode/force-graph-lib'
|
|
131
|
+
import type { GraphData, GraphOptions, NodeData } from '@logixode/force-graph-lib'
|
|
132
|
+
import { useElementSize } from '@vueuse/core'
|
|
63
133
|
|
|
64
|
-
|
|
65
|
-
graph
|
|
66
|
-
|
|
134
|
+
const graphContainer = useTemplateRef('graphContainer')
|
|
135
|
+
const graph = ref<ForceGraph | null>(null)
|
|
136
|
+
const { width, height } = useElementSize(graphContainer)
|
|
67
137
|
|
|
68
|
-
|
|
138
|
+
const initialData: GraphData = {
|
|
139
|
+
nodes: [
|
|
140
|
+
{ id: 'a', label: 'A', sentiment: 'positive' },
|
|
141
|
+
{ id: 'b', label: 'B', sentiment: 'negative' },
|
|
142
|
+
{ id: 'c', label: 'C', sentiment: 'positive' },
|
|
143
|
+
],
|
|
144
|
+
links: [
|
|
145
|
+
{ source: 'a', target: 'b' },
|
|
146
|
+
{ source: 'c', target: 'a' },
|
|
147
|
+
],
|
|
148
|
+
}
|
|
69
149
|
|
|
70
|
-
|
|
150
|
+
const graphOptions: GraphOptions = {
|
|
151
|
+
keepDragPosition: true,
|
|
152
|
+
nodeSize: 3,
|
|
153
|
+
nodeLabel: (node: NodeData) => node.label as string,
|
|
154
|
+
nodeColor: (node: NodeData) => (node.sentiment === 'positive' ? 'green' : 'red'),
|
|
155
|
+
cluster: (node: NodeData) => node.sentiment,
|
|
156
|
+
}
|
|
71
157
|
|
|
72
|
-
|
|
73
|
-
|
|
158
|
+
onMounted(() => {
|
|
159
|
+
if (graphContainer.value) {
|
|
160
|
+
graph.value = new ForceGraph(graphContainer.value, initialData, {
|
|
161
|
+
...graphOptions,
|
|
162
|
+
width: width.value,
|
|
163
|
+
height: height.value,
|
|
164
|
+
})
|
|
165
|
+
}
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
onUnmounted(() => {
|
|
169
|
+
graph.value?.destroy()
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
// Make graph responsive
|
|
173
|
+
watch([width, height], (newSize, oldSize) => {
|
|
174
|
+
if (
|
|
175
|
+
graph.value &&
|
|
176
|
+
(Math.abs(newSize[0] - oldSize[0]) > 10 || Math.abs(newSize[1] - oldSize[1]) > 10)
|
|
177
|
+
) {
|
|
178
|
+
graph.value.setOptions({ width: width.value, height: height.value })
|
|
179
|
+
graph.value.reinitialize()
|
|
180
|
+
}
|
|
181
|
+
})
|
|
182
|
+
</script>
|
|
74
183
|
```
|
|
75
184
|
|
|
76
|
-
|
|
185
|
+
## Detail for Types
|
|
186
|
+
|
|
187
|
+
### `GraphData` Interface
|
|
77
188
|
|
|
78
189
|
```typescript
|
|
79
190
|
interface GraphData {
|
|
80
191
|
nodes: Array<{
|
|
81
|
-
id: string
|
|
192
|
+
id: string | number
|
|
82
193
|
[key: string]: any
|
|
83
194
|
}>
|
|
84
195
|
links: Array<{
|
|
85
|
-
source: string
|
|
86
|
-
target: string
|
|
196
|
+
source: string | number
|
|
197
|
+
target: string | number
|
|
87
198
|
[key: string]: any
|
|
88
199
|
}>
|
|
89
200
|
}
|
|
90
201
|
```
|
|
91
202
|
|
|
92
|
-
### GraphOptions Interface
|
|
203
|
+
### `GraphOptions` Interface
|
|
93
204
|
|
|
94
205
|
```typescript
|
|
95
206
|
interface GraphOptions {
|
|
207
|
+
width?: number
|
|
208
|
+
height?: number
|
|
96
209
|
labelThreshold?: number
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
210
|
+
keepDragPosition?: boolean
|
|
211
|
+
|
|
212
|
+
// Node Styling
|
|
213
|
+
nodeSize?: number | ((node: NodeData) => number)
|
|
214
|
+
nodeLabel?: string | ((node: NodeData) => string)
|
|
215
|
+
nodeLabelColor?: string | ((node: NodeData) => string)
|
|
216
|
+
nodeColor?: string | ((node: NodeData) => string)
|
|
217
|
+
nodeBorderColor?: string | ((node: NodeData) => string)
|
|
218
|
+
nodeBorderWidth?: number | ((node: NodeData) => number)
|
|
219
|
+
|
|
220
|
+
// Link Styling
|
|
221
|
+
linkWidth?: number | ((link: LinkData) => number)
|
|
222
|
+
linkCurvature?: number | 'curvature' | ((link: LinkData) => number)
|
|
223
|
+
linkDirectionalParticles?: number | ((link: LinkData) => number)
|
|
224
|
+
linkDirectionalParticleSpeed?: number | ((link: LinkData) => number)
|
|
225
|
+
linkDirectionalParticleWidth?: number | ((link: LinkData) => number)
|
|
226
|
+
linkDirectionalParticleColor?: string | ((link: LinkData) => string)
|
|
227
|
+
|
|
228
|
+
// Simulation
|
|
229
|
+
cluster?: (node: NodeData) => any
|
|
230
|
+
collide?: (node: NodeData) => number
|
|
231
|
+
|
|
232
|
+
// Grouping
|
|
233
|
+
showGroups?: boolean
|
|
234
|
+
groupBy?: string | ((node: NodeData) => string | undefined)
|
|
235
|
+
groupBorderColor?: string | ((groupId: string) => string)
|
|
236
|
+
groupBorderWidth?: number
|
|
237
|
+
groupBorderOpacity?: number
|
|
238
|
+
groupLabelColor?: string | ((groupId: string) => string)
|
|
239
|
+
groupLabelSize?: number
|
|
240
|
+
groupLabelThreshold?: number
|
|
241
|
+
groupPadding?: number
|
|
242
|
+
|
|
243
|
+
// Events
|
|
244
|
+
nodeClickHandler?: (node: NodeData) => void
|
|
103
245
|
}
|
|
104
246
|
```
|
|
105
247
|
|
|
248
|
+
## API Reference
|
|
249
|
+
|
|
250
|
+
### Constructor
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
new ForceGraph(container: HTMLElement, data: GraphData, options?: GraphOptions)
|
|
254
|
+
```
|
|
255
|
+
|
|
106
256
|
### Methods
|
|
107
257
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
- `
|
|
111
|
-
- `
|
|
112
|
-
- `
|
|
113
|
-
- `
|
|
114
|
-
- `
|
|
115
|
-
- `
|
|
258
|
+
#### Data Management
|
|
259
|
+
|
|
260
|
+
- `addData(newData: GraphData): Promise<void>`: Asynchronously adds new nodes and links, avoiding duplicates.
|
|
261
|
+
- `updateData(data: GraphData): void`: Merges new data with existing data. (Prefer `addData` for incremental updates).
|
|
262
|
+
- `graphData(data: GraphData): ForceGraph`: Replaces the entire graph data and re-renders.
|
|
263
|
+
- `updateNode(id: string | number, updates: Partial<NodeData>): boolean`: Updates properties of a single node.
|
|
264
|
+
- `removeNode(id: string | number): boolean`: Removes a node and its associated links.
|
|
265
|
+
- `reset()`: Clears all graph data and re-initializes the component.
|
|
266
|
+
|
|
267
|
+
#### Getters
|
|
268
|
+
|
|
269
|
+
- `getData(): GraphData`: Returns the current graph data object.
|
|
270
|
+
- `getNodesData(): NodeData[]`: Returns an array of all nodes.
|
|
271
|
+
- `getLinksData(): LinkData[]`: Returns an array of all links.
|
|
272
|
+
- `getNodeById(id: string | number): NodeData | undefined`: Finds a node by its ID.
|
|
273
|
+
- `hasNode(id: string | number): boolean`: Checks if a node exists.
|
|
274
|
+
- `getDataSize(): { nodes: number; links: number }`: Returns the number of nodes and links.
|
|
275
|
+
- `getOptions(): GraphOptions`: Returns the current options object.
|
|
276
|
+
|
|
277
|
+
#### Rendering & Options
|
|
278
|
+
|
|
279
|
+
- `setOptions(options: Partial<GraphOptions>)`: Updates one or more options without re-initializing the entire graph.
|
|
280
|
+
- `setLabelThreshold(threshold: number)`: Sets the zoom level at which node labels become visible.
|
|
281
|
+
- `refreshGraph()`: Refreshes the graph with the current data. Useful after manual data manipulation.
|
|
282
|
+
- `reinitialize()`: Performs a complete re-initialization. Use when major changes like dimensions are needed.
|
|
283
|
+
- `applyOptions()`: Re-applies all current options to the graph.
|
|
284
|
+
|
|
285
|
+
#### Camera & View
|
|
286
|
+
|
|
287
|
+
- `focusPosition(position: { id?: string; x?: number; y?: number }): void`: Centers the view on a specific node or coordinate.
|
|
288
|
+
|
|
289
|
+
#### Grouping
|
|
290
|
+
|
|
291
|
+
- `showGroups(show: boolean): ForceGraph`: Enables or disables the group visualization.
|
|
292
|
+
- `setGroupBy(groupBy: string | ((node: NodeData) => string | undefined)): ForceGraph`: Sets the property or function used to group nodes.
|
|
293
|
+
- `setGroupOptions(options: object): ForceGraph`: Sets styling options for groups (e.g., `borderColor`, `padding`).
|
|
294
|
+
- `getGroups(): string[]`: Returns an array of all unique group IDs.
|
|
295
|
+
- `getNodesInGroup(groupId: string): NodeData[]`: Returns all nodes belonging to a specific group.
|
|
296
|
+
|
|
297
|
+
#### Lifecycle
|
|
298
|
+
|
|
299
|
+
- `destroy()`: Cleans up all resources, including the simulation and event listeners. Essential for single-page applications.
|
|
116
300
|
|
|
117
301
|
## License
|
|
118
302
|
|