@fusion.dev/helix-docs 0.128.0 → 0.129.0
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/content/graphs.mdx +346 -0
- package/content/introduction.mdx +1 -0
- package/content/metrics.mdx +210 -210
- package/dist/pages.generated.js +6 -0
- package/dist/sources.generated.js +558 -210
- package/package.json +9 -9
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Add interactive graphs to your site, styled with your theme and drawn by Sigma.js, cosmos.gl, or three.js.
|
|
3
|
+
section: Essentials
|
|
4
|
+
title: Graphs
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Graphs
|
|
8
|
+
|
|
9
|
+
Helix Graph puts a frame around a graph drawn by Sigma.js, cosmos.gl, three.js,
|
|
10
|
+
or a renderer of your own. The frame adds a searchable list of every note, zoom
|
|
11
|
+
and fullscreen buttons, and keyboard controls. You give the frame notes and
|
|
12
|
+
links, and `@fusion.dev/helix-graph` draws them with the renderer, colored by
|
|
13
|
+
your theme.
|
|
14
|
+
|
|
15
|
+
## Add a graph
|
|
16
|
+
|
|
17
|
+
Install Helix, the graph connector, and Sigma.js with its layout:
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
bun add @fusion.dev/helix @fusion.dev/helix-graph sigma graphology graphology-layout-forceatlas2
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Add the frame, with an element inside it for the renderer to draw in:
|
|
24
|
+
|
|
25
|
+
```html
|
|
26
|
+
<helix-graph label="Research notes">
|
|
27
|
+
<div></div>
|
|
28
|
+
</helix-graph>
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Give the frame its notes and links, make the renderer, and connect the two:
|
|
32
|
+
|
|
33
|
+
```js
|
|
34
|
+
import '@fusion.dev/helix'
|
|
35
|
+
import { connectGraph } from '@fusion.dev/helix-graph'
|
|
36
|
+
import { sigmaDriver } from '@fusion.dev/helix-graph/sigma'
|
|
37
|
+
import Graph from 'graphology'
|
|
38
|
+
import forceAtlas2 from 'graphology-layout-forceatlas2'
|
|
39
|
+
import FA2Layout from 'graphology-layout-forceatlas2/worker'
|
|
40
|
+
import Sigma from 'sigma'
|
|
41
|
+
|
|
42
|
+
const frame = document.querySelector('helix-graph')
|
|
43
|
+
frame.nodes = [
|
|
44
|
+
{ id: 'roadmap', label: 'Roadmap', group: 'Product' },
|
|
45
|
+
{ id: 'pricing', label: 'Pricing', group: 'Sales' },
|
|
46
|
+
]
|
|
47
|
+
frame.links = [{ source: 'pricing', target: 'roadmap' }]
|
|
48
|
+
|
|
49
|
+
const sigma = new Sigma(new Graph(), frame.querySelector('div'))
|
|
50
|
+
const connection = connectGraph(
|
|
51
|
+
frame,
|
|
52
|
+
sigmaDriver(sigma, {
|
|
53
|
+
layout: (graph) => new FA2Layout(graph, { settings: forceAtlas2.inferSettings(graph) }),
|
|
54
|
+
}),
|
|
55
|
+
)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
`nodes` and `links` are properties. Set them in JavaScript, since a graph of
|
|
59
|
+
thousands of notes written as markup would be thousands of elements.
|
|
60
|
+
|
|
61
|
+
## What the connector does
|
|
62
|
+
|
|
63
|
+
`connectGraph` links the frame to the renderer:
|
|
64
|
+
|
|
65
|
+
- It draws the notes and links, and lays out the notes that have no place.
|
|
66
|
+
- It colors each group from your theme's series colors, and the names in your
|
|
67
|
+
theme's typeface. It recolors the graph when your theme or mode changes.
|
|
68
|
+
After you replace a stylesheet, which changes no attribute, call
|
|
69
|
+
`connection.restyle()`.
|
|
70
|
+
- The note you select in the list is the note selected in the drawing, and the
|
|
71
|
+
other way round. The selected note's links stand out, and the rest of the
|
|
72
|
+
graph fades.
|
|
73
|
+
- The frame's zoom buttons and keys move the drawing.
|
|
74
|
+
- `connection.relayout()` lays the whole graph out again.
|
|
75
|
+
- `connection.disconnect()` puts back everything `connectGraph` changed.
|
|
76
|
+
|
|
77
|
+
## Choose a renderer
|
|
78
|
+
|
|
79
|
+
| Renderer | Use it for | Install | Driver |
|
|
80
|
+
|---|---|---|---|
|
|
81
|
+
| Sigma.js | Graphs of thousands of notes | `sigma graphology graphology-layout-forceatlas2` | `sigmaDriver` from `@fusion.dev/helix-graph/sigma` |
|
|
82
|
+
| cosmos.gl | Graphs of tens of thousands of notes or more, laid out on the GPU | `@cosmos.gl/graph` | `cosmosDriver` from `@fusion.dev/helix-graph/cosmos` |
|
|
83
|
+
| three.js | Graphs in 3D, and graphs on the surface of a 3D model | `three d3-force-3d` | `threeDriver` from `@fusion.dev/helix-graph/three` |
|
|
84
|
+
|
|
85
|
+
None of the renderers is bundled with Helix, so you can use the version you
|
|
86
|
+
already have.
|
|
87
|
+
|
|
88
|
+
With cosmos.gl, make the graph in the frame's box and pass the box as well:
|
|
89
|
+
|
|
90
|
+
```js
|
|
91
|
+
import { Graph } from '@cosmos.gl/graph'
|
|
92
|
+
import { cosmosDriver } from '@fusion.dev/helix-graph/cosmos'
|
|
93
|
+
|
|
94
|
+
const box = frame.querySelector('div')
|
|
95
|
+
const graph = new Graph(box, { attribution: '', enableSimulation: true, scalePointsOnZoom: false })
|
|
96
|
+
const connection = connectGraph(frame, cosmosDriver(graph, box))
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
With three.js, pass the box, and the driver makes the renderer:
|
|
100
|
+
|
|
101
|
+
```js
|
|
102
|
+
import { threeDriver } from '@fusion.dev/helix-graph/three'
|
|
103
|
+
|
|
104
|
+
const connection = connectGraph(frame, threeDriver(frame.querySelector('div')))
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Drag to turn a 3D graph, and use the wheel or a pinch to zoom. It turns slowly
|
|
108
|
+
on its own until someone handles it, unless they asked for reduced motion. Set
|
|
109
|
+
`autoRotate: false` to keep it still.
|
|
110
|
+
|
|
111
|
+
## Free the renderer
|
|
112
|
+
|
|
113
|
+
Call `connection.disconnect()` when the graph leaves the page, then free the
|
|
114
|
+
renderer you made:
|
|
115
|
+
|
|
116
|
+
```js
|
|
117
|
+
connection.disconnect()
|
|
118
|
+
sigma.kill() // Sigma.js
|
|
119
|
+
graph.destroy() // cosmos.gl
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
`threeDriver` makes its own renderer, and `disconnect()` frees it. A graph you
|
|
123
|
+
remove from the page without disconnecting keeps its renderer running.
|
|
124
|
+
|
|
125
|
+
## Keep the layout
|
|
126
|
+
|
|
127
|
+
A layout places every note that has no `x` and `y`, and moves only those. When
|
|
128
|
+
it settles, the frame sends `helix-graph-layout` with where each note ended.
|
|
129
|
+
Save the positions and pass them back as each note's `x` and `y`, and the graph
|
|
130
|
+
opens in the same places next time, with no layout to wait for:
|
|
131
|
+
|
|
132
|
+
```js
|
|
133
|
+
frame.addEventListener('helix-graph-layout', (event) => {
|
|
134
|
+
localStorage.setItem('positions', JSON.stringify(event.detail.positions))
|
|
135
|
+
})
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Update the graph as your data changes
|
|
139
|
+
|
|
140
|
+
Give the frame new arrays whenever your data changes. The connector compares
|
|
141
|
+
them with what is drawn, by each note's `id` and each link's two ends, and
|
|
142
|
+
draws only what changed. New notes grow in beside their neighbors, and the rest
|
|
143
|
+
stay where they are:
|
|
144
|
+
|
|
145
|
+
```js
|
|
146
|
+
socket.addEventListener('message', ({ data }) => {
|
|
147
|
+
const note = JSON.parse(data)
|
|
148
|
+
frame.nodes = [...frame.nodes, note]
|
|
149
|
+
frame.links = [...frame.links, { source: note.id, target: note.parent }]
|
|
150
|
+
})
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Call `signal()` to show something passing between two notes, such as a message
|
|
154
|
+
or a payment. A comet travels along the link between them, in the first note's
|
|
155
|
+
color turning to the second's, or in the color of a tone you give it:
|
|
156
|
+
|
|
157
|
+
```js
|
|
158
|
+
frame.signal('checkout', 'payments', { tone: 'success' })
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
When someone asked for reduced motion, the link lights up for a moment
|
|
162
|
+
instead. Screen readers do not announce signals, so say anything a reader needs
|
|
163
|
+
to know in your page's own words as well.
|
|
164
|
+
|
|
165
|
+
## Style the notes
|
|
166
|
+
|
|
167
|
+
Each note takes these fields, as Helix's other components do:
|
|
168
|
+
|
|
169
|
+
| Field | Values | What it does |
|
|
170
|
+
|---|---|---|
|
|
171
|
+
| `group` | Any name | Colors the note from your theme's series. Each group's name is drawn over its notes |
|
|
172
|
+
| `shape` | `circle`, `square`, `diamond`, `ring` | Says what kind of note it is at a glance |
|
|
173
|
+
| `emphasis` | `solid`, `outlined`, `subtle` | Fills the note, rings it, or tints it |
|
|
174
|
+
| `material` | `glass`, `frosted` | Draws the note as a shaded sphere or as frosted glass |
|
|
175
|
+
| `tone` | `primary`, `accent`, `info`, `success`, `warning`, `danger`, `neutral` | Colors the note by what it means, over its group's color |
|
|
176
|
+
| `size` | A number | How large the note is. A note without one is as large as it is connected |
|
|
177
|
+
|
|
178
|
+
Set a default for every note with the driver's options, such as
|
|
179
|
+
`sigmaDriver(sigma, { material: 'glass', shape: 'circle' })`. cosmos.gl draws a
|
|
180
|
+
note's `shape` and not its `emphasis` or `material`.
|
|
181
|
+
|
|
182
|
+
## Select a note
|
|
183
|
+
|
|
184
|
+
Pressing a note selects it, and pressing where no note is clears the selection.
|
|
185
|
+
The frame sends `helix-activate` with the note's `id`, and its `active`
|
|
186
|
+
property holds the selected note. Set `active` to select a note from your page,
|
|
187
|
+
and the frame brings it into view with its neighbors. Start with one selected
|
|
188
|
+
with `default-active`:
|
|
189
|
+
|
|
190
|
+
```html
|
|
191
|
+
<helix-graph default-active="roadmap" label="Research notes">
|
|
192
|
+
<div></div>
|
|
193
|
+
</helix-graph>
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
To keep the selection in your own state, cancel `helix-before-activate` and set
|
|
197
|
+
`active` yourself.
|
|
198
|
+
|
|
199
|
+
Set `depth` to show only the notes within that many links of the selected one,
|
|
200
|
+
for a graph too large to read at once:
|
|
201
|
+
|
|
202
|
+
```html
|
|
203
|
+
<helix-graph default-active="roadmap" depth="2" label="Research notes">
|
|
204
|
+
<div></div>
|
|
205
|
+
</helix-graph>
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
## Draw the notes in a shape
|
|
209
|
+
|
|
210
|
+
Pass a `stencil` and the connector places the notes in its shape from the first
|
|
211
|
+
frame, with no layout. Each group takes a region of the shape. Give it an SVG
|
|
212
|
+
path and the size of the box it is drawn in:
|
|
213
|
+
|
|
214
|
+
```js
|
|
215
|
+
connectGraph(frame, driver, {
|
|
216
|
+
stencil: { path: outline.getAttribute('d') ?? '', width: 220, height: 170 },
|
|
217
|
+
})
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
`@fusion.dev/helix-graph/shapes` makes shapes from what you have:
|
|
221
|
+
|
|
222
|
+
| Function | Makes |
|
|
223
|
+
|---|---|
|
|
224
|
+
| `shapeFromSvg(svg)` | A shape from an SVG's paths, rectangles, circles, ellipses, and polygons |
|
|
225
|
+
| `shapeFromImage(image)` | A shape from an image's opaque pixels |
|
|
226
|
+
| `shapeFromPath(path, width, height)` | A shape from one SVG path |
|
|
227
|
+
| `inflate(shape, { depth })` | A solid from a flat shape, puffed out as a cushion is |
|
|
228
|
+
| `onSphere(shape)` | A flat map wrapped around a globe |
|
|
229
|
+
| `sphere()`, `torus()`, `cuboid()` | Solids |
|
|
230
|
+
|
|
231
|
+
With three.js, place the notes on the surface of a 3D model.
|
|
232
|
+
`shapeFromModel` reads a `.glb` or `.gltf` file, and is in its own entry,
|
|
233
|
+
`@fusion.dev/helix-graph/model`, since it loads three.js:
|
|
234
|
+
|
|
235
|
+
```js
|
|
236
|
+
import { shapeFromModel } from '@fusion.dev/helix-graph/model'
|
|
237
|
+
|
|
238
|
+
const brain = await shapeFromModel('/models/brain.glb')
|
|
239
|
+
connectGraph(frame, threeDriver(box), {
|
|
240
|
+
stencil: {
|
|
241
|
+
...brain,
|
|
242
|
+
place: 'surface',
|
|
243
|
+
regions: { Vision: 'occipital', Hearing: 'temporal' },
|
|
244
|
+
},
|
|
245
|
+
})
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
- `place: 'surface'` puts the notes in a thin shell just inside the surface,
|
|
249
|
+
and draws the model under them. `place: 'fill'`, the default, puts them all
|
|
250
|
+
through it.
|
|
251
|
+
- `regions` sends each group to a named part of the shape. A model's parts are
|
|
252
|
+
its meshes, and an SVG's are its shapes with an `id`. A part the shape does
|
|
253
|
+
not have is reported in the console, with the parts it has.
|
|
254
|
+
- In TypeScript, write `place: 'surface' as const`, so the object keeps the
|
|
255
|
+
type the stencil takes.
|
|
256
|
+
|
|
257
|
+
## Share the scroll with the page
|
|
258
|
+
|
|
259
|
+
By default a graph takes every gesture over it: the wheel zooms it, and one
|
|
260
|
+
finger moves it. A graph embedded in a page takes the wheel from a reader who
|
|
261
|
+
is scrolling past it. Set `gestures` on the frame to share them:
|
|
262
|
+
|
|
263
|
+
```html
|
|
264
|
+
<helix-graph gestures="cooperative" label="Research notes">
|
|
265
|
+
<div></div>
|
|
266
|
+
</helix-graph>
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
- `cooperative` leaves the wheel and one finger to the page. The graph takes
|
|
270
|
+
Ctrl or ⌘ with the wheel, and two fingers, and says so when a gesture passes
|
|
271
|
+
to the page. Use it for a graph embedded in a page.
|
|
272
|
+
- `activate` takes nothing until the graph is pressed, and gives the gestures
|
|
273
|
+
back on Escape, when the pointer leaves, or on a press elsewhere.
|
|
274
|
+
- `always`, the default, takes every gesture.
|
|
275
|
+
|
|
276
|
+
The keyboard and the frame's buttons work the same in every mode.
|
|
277
|
+
|
|
278
|
+
## Show the graph large
|
|
279
|
+
|
|
280
|
+
Set `no-panel` to leave the search and the list out of the frame, so the
|
|
281
|
+
drawing takes all of it, as at the top of a page. A Notes button among the
|
|
282
|
+
controls opens them over the drawing:
|
|
283
|
+
|
|
284
|
+
```html
|
|
285
|
+
<helix-graph gestures="cooperative" label="Research notes" no-panel>
|
|
286
|
+
<div></div>
|
|
287
|
+
</helix-graph>
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
Set the frame's height with `--graph-block-size`, and the list's width with
|
|
291
|
+
`--graph-panel-size`.
|
|
292
|
+
|
|
293
|
+
## Show the graph once it is drawn
|
|
294
|
+
|
|
295
|
+
When the layout settles, the frame sends `helix-graph-ready` and matches
|
|
296
|
+
`:state(ready)`. Its `ready` property is `true` from then on. To show a graph
|
|
297
|
+
only once it is laid out, fade it in on the state:
|
|
298
|
+
|
|
299
|
+
```css
|
|
300
|
+
helix-graph > div {
|
|
301
|
+
opacity: 0;
|
|
302
|
+
transition: opacity 0.3s;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
helix-graph:state(ready) > div {
|
|
306
|
+
opacity: 1;
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
## Keyboard and screen readers
|
|
311
|
+
|
|
312
|
+
- The frame is one tab stop. Arrow keys move the drawing, and the plus and
|
|
313
|
+
minus keys zoom it. Set `controls="pan zoom fullscreen"` to add buttons that
|
|
314
|
+
move it.
|
|
315
|
+
- The list is a listbox, named by `notes-label`. Arrow keys, Home, End, Page
|
|
316
|
+
Up, and Page Down move through it, and Enter or Space selects a note.
|
|
317
|
+
- In the search field, Down moves into the list, and Escape empties the search.
|
|
318
|
+
With `no-panel`, Escape on an empty search closes the list.
|
|
319
|
+
- Screen readers read the selected note first, then how each other note links
|
|
320
|
+
to it, such as "Linked from Roadmap", and the count as a search narrows it.
|
|
321
|
+
- Change the words with `search-label`, `count-label`, `selected-label`,
|
|
322
|
+
`links-to-label`, `linked-from-label`, and `linked-both-label`.
|
|
323
|
+
|
|
324
|
+
## Write a driver
|
|
325
|
+
|
|
326
|
+
To use another renderer, write a driver: an object with four methods.
|
|
327
|
+
|
|
328
|
+
- `draw({ nodes, links })` draws the graph, each note at its `x` and `y`.
|
|
329
|
+
- `look(looks)` says how each note and link is drawn, from the theme and the
|
|
330
|
+
selection.
|
|
331
|
+
- `panBy(x, y, animate)` moves the view by a number of pixels.
|
|
332
|
+
- `zoomBy(direction, animate)` zooms in for `1` and out for `-1`.
|
|
333
|
+
|
|
334
|
+
Each optional method turns on a feature:
|
|
335
|
+
|
|
336
|
+
| Method | Turns on |
|
|
337
|
+
|---|---|
|
|
338
|
+
| `update` | Drawing only what changed, for live data |
|
|
339
|
+
| `settle` | Laying out notes that have no place |
|
|
340
|
+
| `onPress` | Selecting a note by pressing it in the drawing |
|
|
341
|
+
| `center`, `fit` | Bringing a selected note and its neighbors into view |
|
|
342
|
+
| `signal` | Signals between notes |
|
|
343
|
+
| `groups` | Each group's name over its notes |
|
|
344
|
+
| `gestures`, `focusTarget` | Sharing gestures with the page, and the frame's keys |
|
|
345
|
+
| `shape` | Drawing a stencil's shape |
|
|
346
|
+
| `stop` | Removing what the driver added when the graph disconnects |
|
package/content/introduction.mdx
CHANGED
|
@@ -63,6 +63,7 @@ each framework.
|
|
|
63
63
|
| `@fusion.dev/helix-theme` | You are generating or validating a theme |
|
|
64
64
|
| `@fusion.dev/helix-icons` | You are drawing an icon outside a component |
|
|
65
65
|
| `@fusion.dev/helix-map` | You are drawing a map with MapLibre, Leaflet, or a renderer of your own. See [Maps](./maps) |
|
|
66
|
+
| `@fusion.dev/helix-graph` | You are drawing a graph with Sigma.js, cosmos.gl, three.js, or a renderer of your own. See [Graphs](./graphs) |
|
|
66
67
|
| `@fusion.dev/helix-core` | You are building a new Helix component |
|
|
67
68
|
|
|
68
69
|
React uses the elements directly. Helix ships a `jsx.d.ts` that types every
|