@fnndsc/orrery 0.1.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/README.md +12 -0
- package/dist/controls/cameraRig.d.ts +223 -0
- package/dist/controls/cameraRig.js +353 -0
- package/dist/controls/cameraRig.js.map +1 -0
- package/dist/controls/gestures.d.ts +133 -0
- package/dist/controls/gestures.js +282 -0
- package/dist/controls/gestures.js.map +1 -0
- package/dist/controls/index.d.ts +9 -0
- package/dist/controls/index.js +10 -0
- package/dist/controls/index.js.map +1 -0
- package/dist/controls/picker.d.ts +117 -0
- package/dist/controls/picker.js +167 -0
- package/dist/controls/picker.js.map +1 -0
- package/dist/controls/replay.d.ts +94 -0
- package/dist/controls/replay.js +147 -0
- package/dist/controls/replay.js.map +1 -0
- package/dist/draw/censusField.d.ts +113 -0
- package/dist/draw/censusField.js +241 -0
- package/dist/draw/censusField.js.map +1 -0
- package/dist/draw/constants.d.ts +13 -0
- package/dist/draw/constants.js +14 -0
- package/dist/draw/constants.js.map +1 -0
- package/dist/draw/geometry.d.ts +33 -0
- package/dist/draw/geometry.js +55 -0
- package/dist/draw/geometry.js.map +1 -0
- package/dist/draw/handoff.d.ts +11 -0
- package/dist/draw/handoff.js +12 -0
- package/dist/draw/handoff.js.map +1 -0
- package/dist/draw/handoffField.d.ts +74 -0
- package/dist/draw/handoffField.js +151 -0
- package/dist/draw/handoffField.js.map +1 -0
- package/dist/draw/index.d.ts +20 -0
- package/dist/draw/index.js +21 -0
- package/dist/draw/index.js.map +1 -0
- package/dist/draw/labelField.d.ts +63 -0
- package/dist/draw/labelField.js +149 -0
- package/dist/draw/labelField.js.map +1 -0
- package/dist/draw/lamps.d.ts +16 -0
- package/dist/draw/lamps.js +17 -0
- package/dist/draw/lamps.js.map +1 -0
- package/dist/draw/nebula.d.ts +8 -0
- package/dist/draw/nebula.js +28 -0
- package/dist/draw/nebula.js.map +1 -0
- package/dist/draw/palette.d.ts +41 -0
- package/dist/draw/palette.js +32 -0
- package/dist/draw/palette.js.map +1 -0
- package/dist/draw/sphereField.d.ts +98 -0
- package/dist/draw/sphereField.js +168 -0
- package/dist/draw/sphereField.js.map +1 -0
- package/dist/draw/starField.d.ts +127 -0
- package/dist/draw/starField.js +267 -0
- package/dist/draw/starField.js.map +1 -0
- package/dist/draw/stars.d.ts +65 -0
- package/dist/draw/stars.js +132 -0
- package/dist/draw/stars.js.map +1 -0
- package/dist/draw/timing.d.ts +11 -0
- package/dist/draw/timing.js +12 -0
- package/dist/draw/timing.js.map +1 -0
- package/dist/draw/tubeField.d.ts +131 -0
- package/dist/draw/tubeField.js +352 -0
- package/dist/draw/tubeField.js.map +1 -0
- package/dist/draw/tubes.d.ts +41 -0
- package/dist/draw/tubes.js +116 -0
- package/dist/draw/tubes.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/layout/accretion.d.ts +31 -0
- package/dist/layout/accretion.js +384 -0
- package/dist/layout/accretion.js.map +1 -0
- package/dist/layout/constellations.d.ts +25 -0
- package/dist/layout/constellations.js +197 -0
- package/dist/layout/constellations.js.map +1 -0
- package/dist/layout/galaxy.d.ts +12 -0
- package/dist/layout/galaxy.js +99 -0
- package/dist/layout/galaxy.js.map +1 -0
- package/dist/layout/hierarchy.d.ts +45 -0
- package/dist/layout/hierarchy.js +464 -0
- package/dist/layout/hierarchy.js.map +1 -0
- package/dist/layout/index.d.ts +20 -0
- package/dist/layout/index.js +21 -0
- package/dist/layout/index.js.map +1 -0
- package/dist/layout/layoutInput.d.ts +29 -0
- package/dist/layout/layoutInput.js +45 -0
- package/dist/layout/layoutInput.js.map +1 -0
- package/dist/layout/molecule.d.ts +64 -0
- package/dist/layout/molecule.js +155 -0
- package/dist/layout/molecule.js.map +1 -0
- package/dist/layout/pull.d.ts +94 -0
- package/dist/layout/pull.js +137 -0
- package/dist/layout/pull.js.map +1 -0
- package/dist/layout/ranked.d.ts +75 -0
- package/dist/layout/ranked.js +148 -0
- package/dist/layout/ranked.js.map +1 -0
- package/dist/layout/registry.d.ts +23 -0
- package/dist/layout/registry.js +165 -0
- package/dist/layout/registry.js.map +1 -0
- package/dist/layout/seeded.d.ts +32 -0
- package/dist/layout/seeded.js +48 -0
- package/dist/layout/seeded.js.map +1 -0
- package/dist/layout/types.d.ts +81 -0
- package/dist/layout/types.js +14 -0
- package/dist/layout/types.js.map +1 -0
- package/dist/scene/index.d.ts +6 -0
- package/dist/scene/index.js +7 -0
- package/dist/scene/index.js.map +1 -0
- package/dist/scene/orrery.d.ts +592 -0
- package/dist/scene/orrery.js +1555 -0
- package/dist/scene/orrery.js.map +1 -0
- package/dist/types/encoding.d.ts +43 -0
- package/dist/types/encoding.js +7 -0
- package/dist/types/encoding.js.map +1 -0
- package/dist/types/space.d.ts +12 -0
- package/dist/types/space.js +10 -0
- package/dist/types/space.js.map +1 -0
- package/package.json +57 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { type PhysicsTerms, type Vec3 } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* One node of a molecule.
|
|
4
|
+
*
|
|
5
|
+
* @property id - Its id.
|
|
6
|
+
* @property parents - Its parents' ids, joins included.
|
|
7
|
+
* @property metric - What sizes it; its degree when absent.
|
|
8
|
+
*/
|
|
9
|
+
export interface MoleculeNode {
|
|
10
|
+
id: string;
|
|
11
|
+
parents: ReadonlyArray<string>;
|
|
12
|
+
metric?: number;
|
|
13
|
+
}
|
|
14
|
+
/** A node where the settle left it, with the room it took. */
|
|
15
|
+
export interface MoleculePlace {
|
|
16
|
+
id: string;
|
|
17
|
+
position: Vec3;
|
|
18
|
+
radius: number;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* A molecule settle that can be run in slices.
|
|
22
|
+
*
|
|
23
|
+
* @property total - The ticks this settle runs; zero when nothing moves.
|
|
24
|
+
* @property moving - How many nodes the simulation moves: pinned ones cost nothing.
|
|
25
|
+
* @property step - Runs up to `ticks` more ticks.
|
|
26
|
+
* @property place - Where every node stands now, in the input's order.
|
|
27
|
+
*/
|
|
28
|
+
export interface MoleculeSettle {
|
|
29
|
+
total: number;
|
|
30
|
+
moving: number;
|
|
31
|
+
step: (ticks: number) => void;
|
|
32
|
+
place: () => MoleculePlace[];
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Each node's radius, as the settle sizes it: its metric against the
|
|
36
|
+
* molecule's peak.
|
|
37
|
+
*
|
|
38
|
+
* @param nodes - The molecule's nodes.
|
|
39
|
+
* @returns Radius by id.
|
|
40
|
+
*/
|
|
41
|
+
export declare function moleculeRadii_of(nodes: ReadonlyArray<MoleculeNode>): Map<string, number>;
|
|
42
|
+
/**
|
|
43
|
+
* Builds a molecule settle without running it.
|
|
44
|
+
*
|
|
45
|
+
* @param nodes - The molecule's nodes.
|
|
46
|
+
* @param dimensions - 2 or 3; in 2D the simulation itself is flat, since a
|
|
47
|
+
* 3D settle flattened afterwards piles nodes that resolved in depth.
|
|
48
|
+
* @param seed - Where nodes stood last; a seeded node starts there.
|
|
49
|
+
* @param physics - The terms of this settle.
|
|
50
|
+
* @param frozen - Nodes that stand where their seed put them.
|
|
51
|
+
* @returns The settle, ready to step.
|
|
52
|
+
*/
|
|
53
|
+
export declare function molecule_prepare(nodes: ReadonlyArray<MoleculeNode>, dimensions?: 2 | 3, seed?: ReadonlyMap<string, Vec3>, physics?: PhysicsTerms, frozen?: ReadonlySet<string>): MoleculeSettle;
|
|
54
|
+
/**
|
|
55
|
+
* Lays out a molecule in one go — {@link molecule_prepare} run to its end.
|
|
56
|
+
*
|
|
57
|
+
* @param nodes - The molecule's nodes.
|
|
58
|
+
* @param dimensions - 2 or 3.
|
|
59
|
+
* @param seed - Where nodes stood last.
|
|
60
|
+
* @param physics - The terms of this settle.
|
|
61
|
+
* @param frozen - Nodes that stand where their seed put them.
|
|
62
|
+
* @returns Every node placed, in the input's order.
|
|
63
|
+
*/
|
|
64
|
+
export declare function molecule_layout(nodes: ReadonlyArray<MoleculeNode>, dimensions?: 2 | 3, seed?: ReadonlyMap<string, Vec3>, physics?: PhysicsTerms, frozen?: ReadonlySet<string>): MoleculePlace[];
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file The molecule: a graph that finds its own shape.
|
|
3
|
+
*
|
|
4
|
+
* A d3-force-3d settle with each node's room scaled by its metric (its
|
|
5
|
+
* degree when no metric arrived): edges hold their ends surface to surface,
|
|
6
|
+
* heavier nodes repel harder, nothing overlaps. The settle can run in
|
|
7
|
+
* slices, so a caller with a frame budget steps it as it has time, and it
|
|
8
|
+
* can hold nodes still at a seed while the rest settles around them — a
|
|
9
|
+
* descent settles one feed while three thousand spheres stand.
|
|
10
|
+
*
|
|
11
|
+
* @module
|
|
12
|
+
*/
|
|
13
|
+
import { forceSimulation, forceLink, forceManyBody, forceCenter, forceCollide, forceX, forceY, forceZ } from 'd3-force-3d';
|
|
14
|
+
import { NODE_RADIUS, PHYSICS_DEFAULT } from './types.js';
|
|
15
|
+
/**
|
|
16
|
+
* Each node's metric: its own, or its degree when none arrived.
|
|
17
|
+
*
|
|
18
|
+
* @param nodes - The molecule's nodes.
|
|
19
|
+
* @returns The metrics, in the nodes' order.
|
|
20
|
+
*/
|
|
21
|
+
function metrics_of(nodes) {
|
|
22
|
+
const degree = new Map();
|
|
23
|
+
for (const node of nodes) {
|
|
24
|
+
for (const parentId of node.parents) {
|
|
25
|
+
degree.set(parentId, (degree.get(parentId) ?? 0) + 1);
|
|
26
|
+
degree.set(node.id, (degree.get(node.id) ?? 0) + 1);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return nodes.map((n) => n.metric ?? degree.get(n.id) ?? 1);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Each node's radius, as the settle sizes it: its metric against the
|
|
33
|
+
* molecule's peak.
|
|
34
|
+
*
|
|
35
|
+
* @param nodes - The molecule's nodes.
|
|
36
|
+
* @returns Radius by id.
|
|
37
|
+
*/
|
|
38
|
+
export function moleculeRadii_of(nodes) {
|
|
39
|
+
const metrics = metrics_of(nodes);
|
|
40
|
+
const peak = Math.max(...metrics, 1);
|
|
41
|
+
return new Map(nodes.map((n, i) => [n.id, NODE_RADIUS * (0.5 + ((metrics[i] ?? 1) / peak) * 1.2)]));
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Builds a molecule settle without running it.
|
|
45
|
+
*
|
|
46
|
+
* @param nodes - The molecule's nodes.
|
|
47
|
+
* @param dimensions - 2 or 3; in 2D the simulation itself is flat, since a
|
|
48
|
+
* 3D settle flattened afterwards piles nodes that resolved in depth.
|
|
49
|
+
* @param seed - Where nodes stood last; a seeded node starts there.
|
|
50
|
+
* @param physics - The terms of this settle.
|
|
51
|
+
* @param frozen - Nodes that stand where their seed put them.
|
|
52
|
+
* @returns The settle, ready to step.
|
|
53
|
+
*/
|
|
54
|
+
export function molecule_prepare(nodes, dimensions = 3, seed = new Map(), physics = PHYSICS_DEFAULT, frozen = new Set()) {
|
|
55
|
+
const links = [];
|
|
56
|
+
for (const node of nodes) {
|
|
57
|
+
for (const parentId of node.parents)
|
|
58
|
+
links.push({ source: parentId, target: node.id });
|
|
59
|
+
}
|
|
60
|
+
const metrics = metrics_of(nodes);
|
|
61
|
+
const metricPeak = Math.max(...metrics, 1);
|
|
62
|
+
// Radius is physics, not paint: the metric sets each node's room in the
|
|
63
|
+
// settle, so what is measured reshapes the molecule, not just its size.
|
|
64
|
+
const radii = metrics.map((metric) => NODE_RADIUS * (0.5 + (metric / metricPeak) * 1.2));
|
|
65
|
+
const radiusOf = new Map(nodes.map((n, i) => [n.id, radii[i] ?? NODE_RADIUS]));
|
|
66
|
+
// Warm start: a re-projection morphs from where the graph stands instead
|
|
67
|
+
// of re-rolling a new equilibrium. A frozen node stands where its seed
|
|
68
|
+
// put it and pushes on nothing.
|
|
69
|
+
const simNodes = nodes.map((n) => {
|
|
70
|
+
const from = seed.get(n.id);
|
|
71
|
+
if (from === undefined)
|
|
72
|
+
return { id: n.id };
|
|
73
|
+
if (frozen.has(n.id))
|
|
74
|
+
return { id: n.id, x: from[0], y: from[1], z: from[2], fx: from[0], fy: from[1], fz: from[2] };
|
|
75
|
+
return { id: n.id, x: from[0], y: from[1], z: from[2] };
|
|
76
|
+
});
|
|
77
|
+
// Only what moves is simulated: the free nodes and whatever they link to.
|
|
78
|
+
// A whole-field settle with most of it frozen held the page for seconds.
|
|
79
|
+
const pinned = (d) => frozen.has(d.id) && d.fx !== undefined;
|
|
80
|
+
let simulated = simNodes;
|
|
81
|
+
let simLinks = links;
|
|
82
|
+
if (frozen.size > 0) {
|
|
83
|
+
const free = new Set(simNodes.filter((d) => !pinned(d)).map((d) => d.id));
|
|
84
|
+
simLinks = links.filter((link) => free.has(link.source) || free.has(link.target));
|
|
85
|
+
const kept = new Set(free);
|
|
86
|
+
for (const link of simLinks) {
|
|
87
|
+
kept.add(link.source);
|
|
88
|
+
kept.add(link.target);
|
|
89
|
+
}
|
|
90
|
+
simulated = simNodes.filter((d) => kept.has(d.id));
|
|
91
|
+
const present = new Set(simulated.map((d) => d.id));
|
|
92
|
+
simLinks = simLinks.filter((link) => present.has(link.source) && present.has(link.target));
|
|
93
|
+
}
|
|
94
|
+
const charge = forceManyBody().strength(physics.charge
|
|
95
|
+
? (d) => (frozen.has(d.id) ? 0 : -6 * ((radiusOf.get(d.id) ?? NODE_RADIUS) / NODE_RADIUS) ** 2)
|
|
96
|
+
: (d) => (frozen.has(d.id) ? 0 : -6));
|
|
97
|
+
if (physics.reach !== undefined)
|
|
98
|
+
charge.distanceMax(physics.reach);
|
|
99
|
+
const simulation = forceSimulation(simulated, dimensions)
|
|
100
|
+
.force('link', forceLink(simLinks)
|
|
101
|
+
.id((d) => d.id)
|
|
102
|
+
// Edges reach surface to surface: a hub's children orbit its skin.
|
|
103
|
+
.distance(physics.link
|
|
104
|
+
? (link) => (radiusOf.get(link.source.id) ?? NODE_RADIUS) + (radiusOf.get(link.target.id) ?? NODE_RADIUS) + 1.4
|
|
105
|
+
: 2.2))
|
|
106
|
+
// Repulsion scales with cross-section: a heavy node carves its room.
|
|
107
|
+
.force('charge', charge)
|
|
108
|
+
.stop();
|
|
109
|
+
// Centering moves every free node by the mean of ALL nodes each tick; with
|
|
110
|
+
// most of the field frozen that mean never settles and the free few
|
|
111
|
+
// stream away. A settle with frozen nodes keeps its centre where it is.
|
|
112
|
+
if (frozen.size === 0) {
|
|
113
|
+
simulation.force('center', dimensions === 2 ? forceCenter(0, 0) : forceCenter(0, 0, 0));
|
|
114
|
+
}
|
|
115
|
+
if (physics.collide) {
|
|
116
|
+
simulation.force('collide', forceCollide().radius((d) => (radiusOf.get(d.id) ?? NODE_RADIUS) * 1.2));
|
|
117
|
+
}
|
|
118
|
+
if (physics.gravity) {
|
|
119
|
+
// Mass-weighted centering: the heaviest node settles at the heart.
|
|
120
|
+
const pull = (d) => (((radiusOf.get(d.id) ?? NODE_RADIUS) / NODE_RADIUS) ** 2) * 0.08;
|
|
121
|
+
simulation.force('gx', forceX(0).strength(pull)).force('gy', forceY(0).strength(pull));
|
|
122
|
+
if (dimensions === 3)
|
|
123
|
+
simulation.force('gz', forceZ(0).strength(pull));
|
|
124
|
+
}
|
|
125
|
+
const total = simulated.length === 0 ? 0 : (seed.size > 0 ? 90 : 150);
|
|
126
|
+
let done = 0;
|
|
127
|
+
return {
|
|
128
|
+
total,
|
|
129
|
+
moving: simulated.length,
|
|
130
|
+
step: (ticks) => {
|
|
131
|
+
for (let tick = 0; tick < ticks && done < total; tick++, done++)
|
|
132
|
+
simulation.tick();
|
|
133
|
+
},
|
|
134
|
+
place: () => nodes.map((node, index) => {
|
|
135
|
+
const sim = simNodes[index];
|
|
136
|
+
return { id: node.id, position: [sim?.x ?? 0, sim?.y ?? 0, sim?.z ?? 0], radius: radii[index] ?? NODE_RADIUS };
|
|
137
|
+
}),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Lays out a molecule in one go — {@link molecule_prepare} run to its end.
|
|
142
|
+
*
|
|
143
|
+
* @param nodes - The molecule's nodes.
|
|
144
|
+
* @param dimensions - 2 or 3.
|
|
145
|
+
* @param seed - Where nodes stood last.
|
|
146
|
+
* @param physics - The terms of this settle.
|
|
147
|
+
* @param frozen - Nodes that stand where their seed put them.
|
|
148
|
+
* @returns Every node placed, in the input's order.
|
|
149
|
+
*/
|
|
150
|
+
export function molecule_layout(nodes, dimensions = 3, seed = new Map(), physics = PHYSICS_DEFAULT, frozen = new Set()) {
|
|
151
|
+
const settle = molecule_prepare(nodes, dimensions, seed, physics, frozen);
|
|
152
|
+
settle.step(settle.total);
|
|
153
|
+
return settle.place();
|
|
154
|
+
}
|
|
155
|
+
//# sourceMappingURL=molecule.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"molecule.js","sourceRoot":"","sources":["../../src/layout/molecule.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { Vec3 } from '../types/space.js';
|
|
2
|
+
/**
|
|
3
|
+
* A drawn node as the pull needs it.
|
|
4
|
+
*
|
|
5
|
+
* @property id - The node.
|
|
6
|
+
* @property position - Where it stands, in the space the pull runs in.
|
|
7
|
+
* @property radius - Its room: link lengths, charge and overlap follow it.
|
|
8
|
+
* @property dim - Scenery: the molecule does not reach through it.
|
|
9
|
+
* @property halo - A cluster's wrap: no edge to or from it is followed.
|
|
10
|
+
*/
|
|
11
|
+
export interface PullNode {
|
|
12
|
+
id: string;
|
|
13
|
+
position: Vec3;
|
|
14
|
+
radius: number;
|
|
15
|
+
dim: boolean;
|
|
16
|
+
halo: boolean;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* What a pull begins from.
|
|
20
|
+
*
|
|
21
|
+
* @property grabbed - The node taken hold of.
|
|
22
|
+
* @property nodes - Every drawn node that could move.
|
|
23
|
+
* @property edges - The graph's edges, parent to child; those between nodes
|
|
24
|
+
* not drawn are passed over.
|
|
25
|
+
* @property dimensions - 2 for a flat view, 3 otherwise.
|
|
26
|
+
* @property baseRadius - The radius at which a node's charge is the base charge.
|
|
27
|
+
*/
|
|
28
|
+
export interface PullInput {
|
|
29
|
+
grabbed: string;
|
|
30
|
+
nodes: ReadonlyArray<PullNode>;
|
|
31
|
+
edges: ReadonlyArray<{
|
|
32
|
+
from: string;
|
|
33
|
+
to: string;
|
|
34
|
+
}>;
|
|
35
|
+
dimensions: 2 | 3;
|
|
36
|
+
baseRadius: number;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The nodes a pull on one node moves: its molecule, reached along edges
|
|
40
|
+
* between drawn nodes, never through a halo or a dimmed node.
|
|
41
|
+
*
|
|
42
|
+
* @param input - What the pull begins from.
|
|
43
|
+
* @returns The molecule's ids (the grabbed node always among them) and the
|
|
44
|
+
* edges inside it.
|
|
45
|
+
*/
|
|
46
|
+
export declare function pullMolecule_of(input: Pick<PullInput, 'grabbed' | 'nodes' | 'edges'>): {
|
|
47
|
+
ids: Set<string>;
|
|
48
|
+
links: Array<{
|
|
49
|
+
source: string;
|
|
50
|
+
target: string;
|
|
51
|
+
}>;
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* A pull under way: begun hot, following a pinned node while held, cooling
|
|
55
|
+
* to rest once released.
|
|
56
|
+
*/
|
|
57
|
+
export declare class PullSimulation {
|
|
58
|
+
private readonly nodes;
|
|
59
|
+
private readonly grabbed;
|
|
60
|
+
private readonly sim;
|
|
61
|
+
/**
|
|
62
|
+
* Builds the reaction from where the nodes stand, the grabbed one pinned
|
|
63
|
+
* there. Begin it on the first real movement, not on the press: a heated
|
|
64
|
+
* simulation on a mere press shifts nodes out from under a click.
|
|
65
|
+
*
|
|
66
|
+
* @param input - What the pull begins from.
|
|
67
|
+
*/
|
|
68
|
+
constructor(input: PullInput);
|
|
69
|
+
/**
|
|
70
|
+
* Holds the grabbed node at a point: the pointer's, in the pull's space.
|
|
71
|
+
*
|
|
72
|
+
* @param point - Where it is held.
|
|
73
|
+
*/
|
|
74
|
+
pin(point: Vec3): void;
|
|
75
|
+
/** Lets go: the grabbed node is free and the molecule cools to rest. */
|
|
76
|
+
release(): void;
|
|
77
|
+
/**
|
|
78
|
+
* One frame of the reaction: runs while held, or while still warm after
|
|
79
|
+
* the release.
|
|
80
|
+
*
|
|
81
|
+
* @param held - Whether the node is still held.
|
|
82
|
+
* @returns False once the pull has come to rest (and did not move).
|
|
83
|
+
*/
|
|
84
|
+
step(held: boolean): boolean;
|
|
85
|
+
/**
|
|
86
|
+
* Where the molecule's nodes stand now.
|
|
87
|
+
*
|
|
88
|
+
* @returns Each moving node and its position.
|
|
89
|
+
*/
|
|
90
|
+
positions(): Array<{
|
|
91
|
+
id: string;
|
|
92
|
+
position: Vec3;
|
|
93
|
+
}>;
|
|
94
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file The pull: one node grabbed and dragged, its molecule reacting.
|
|
3
|
+
*
|
|
4
|
+
* A pull moves the grabbed node's own molecule — the nodes joined to it by
|
|
5
|
+
* edges, never across a cluster's halo, never through a dimmed node — and
|
|
6
|
+
* leaves the rest of the field where it stands: pulling one feed in a
|
|
7
|
+
* universe of seven hundred re-settled all of them under physics that were
|
|
8
|
+
* not the universe's own.
|
|
9
|
+
*
|
|
10
|
+
* The molecule reacts with the forces the layout settled under — the same
|
|
11
|
+
* link lengths, charge by size within the hug's reach, no overlap — so a tug
|
|
12
|
+
* moves it rather than blowing it apart; generic forces disagreed with the
|
|
13
|
+
* rest the layout had found, and every node fled. Links and charge only, no
|
|
14
|
+
* centring force, or the pull would fight a recentring spring.
|
|
15
|
+
*
|
|
16
|
+
* Pure: positions in, positions out; no renderer.
|
|
17
|
+
*
|
|
18
|
+
* @module
|
|
19
|
+
*/
|
|
20
|
+
import { forceSimulation, forceLink, forceManyBody, forceCollide } from 'd3-force-3d';
|
|
21
|
+
/** Below this heat a released pull has come to rest. */
|
|
22
|
+
const PULL_REST_ALPHA = 0.02;
|
|
23
|
+
/**
|
|
24
|
+
* The nodes a pull on one node moves: its molecule, reached along edges
|
|
25
|
+
* between drawn nodes, never through a halo or a dimmed node.
|
|
26
|
+
*
|
|
27
|
+
* @param input - What the pull begins from.
|
|
28
|
+
* @returns The molecule's ids (the grabbed node always among them) and the
|
|
29
|
+
* edges inside it.
|
|
30
|
+
*/
|
|
31
|
+
export function pullMolecule_of(input) {
|
|
32
|
+
const drawn = new Map(input.nodes.map((node) => [node.id, node]));
|
|
33
|
+
const all = [];
|
|
34
|
+
const around = new Map();
|
|
35
|
+
for (const edge of input.edges) {
|
|
36
|
+
const from = drawn.get(edge.from);
|
|
37
|
+
const to = drawn.get(edge.to);
|
|
38
|
+
if (from === undefined || to === undefined || from.halo || to.halo)
|
|
39
|
+
continue;
|
|
40
|
+
all.push({ source: edge.from, target: edge.to });
|
|
41
|
+
around.set(edge.from, [...(around.get(edge.from) ?? []), edge.to]);
|
|
42
|
+
around.set(edge.to, [...(around.get(edge.to) ?? []), edge.from]);
|
|
43
|
+
}
|
|
44
|
+
const ids = new Set([input.grabbed]);
|
|
45
|
+
const queue = [input.grabbed];
|
|
46
|
+
while (queue.length > 0) {
|
|
47
|
+
const at = queue.pop();
|
|
48
|
+
for (const next of around.get(at) ?? []) {
|
|
49
|
+
if (ids.has(next) || drawn.get(next)?.dim === true)
|
|
50
|
+
continue;
|
|
51
|
+
ids.add(next);
|
|
52
|
+
queue.push(next);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const links = all.filter((link) => ids.has(link.source) && ids.has(link.target));
|
|
56
|
+
return { ids, links };
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* A pull under way: begun hot, following a pinned node while held, cooling
|
|
60
|
+
* to rest once released.
|
|
61
|
+
*/
|
|
62
|
+
export class PullSimulation {
|
|
63
|
+
nodes;
|
|
64
|
+
grabbed;
|
|
65
|
+
sim;
|
|
66
|
+
/**
|
|
67
|
+
* Builds the reaction from where the nodes stand, the grabbed one pinned
|
|
68
|
+
* there. Begin it on the first real movement, not on the press: a heated
|
|
69
|
+
* simulation on a mere press shifts nodes out from under a click.
|
|
70
|
+
*
|
|
71
|
+
* @param input - What the pull begins from.
|
|
72
|
+
*/
|
|
73
|
+
constructor(input) {
|
|
74
|
+
const { ids, links } = pullMolecule_of(input);
|
|
75
|
+
const radius = new Map(input.nodes.map((node) => [node.id, node.radius]));
|
|
76
|
+
const radiusOf = (id) => radius.get(id) ?? input.baseRadius;
|
|
77
|
+
const position = new Map(input.nodes.map((node) => [node.id, node.position]));
|
|
78
|
+
this.nodes = [...ids].filter((id) => position.has(id)).map((id) => {
|
|
79
|
+
const [x, y, z] = position.get(id);
|
|
80
|
+
return { id, x, y, z };
|
|
81
|
+
});
|
|
82
|
+
this.sim = forceSimulation(this.nodes, input.dimensions)
|
|
83
|
+
.force('link', forceLink(links).id((d) => d.id).distance((link) => radiusOf(link.source.id) + radiusOf(link.target.id) + 1.4))
|
|
84
|
+
.force('charge', forceManyBody().strength((d) => -6 * (radiusOf(d.id) / input.baseRadius) ** 2).distanceMax(12))
|
|
85
|
+
.force('collide', forceCollide().radius((d) => radiusOf(d.id) * 1.2))
|
|
86
|
+
.alpha(0.25)
|
|
87
|
+
.alphaTarget(0.08)
|
|
88
|
+
.stop();
|
|
89
|
+
this.grabbed = this.nodes.find((node) => node.id === input.grabbed);
|
|
90
|
+
if (this.grabbed !== undefined) {
|
|
91
|
+
this.grabbed.fx = this.grabbed.x;
|
|
92
|
+
this.grabbed.fy = this.grabbed.y;
|
|
93
|
+
this.grabbed.fz = this.grabbed.z;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Holds the grabbed node at a point: the pointer's, in the pull's space.
|
|
98
|
+
*
|
|
99
|
+
* @param point - Where it is held.
|
|
100
|
+
*/
|
|
101
|
+
pin(point) {
|
|
102
|
+
if (this.grabbed === undefined)
|
|
103
|
+
return;
|
|
104
|
+
[this.grabbed.fx, this.grabbed.fy, this.grabbed.fz] = point;
|
|
105
|
+
}
|
|
106
|
+
/** Lets go: the grabbed node is free and the molecule cools to rest. */
|
|
107
|
+
release() {
|
|
108
|
+
if (this.grabbed !== undefined) {
|
|
109
|
+
this.grabbed.fx = null;
|
|
110
|
+
this.grabbed.fy = null;
|
|
111
|
+
this.grabbed.fz = null;
|
|
112
|
+
}
|
|
113
|
+
this.sim.alphaTarget(0);
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* One frame of the reaction: runs while held, or while still warm after
|
|
117
|
+
* the release.
|
|
118
|
+
*
|
|
119
|
+
* @param held - Whether the node is still held.
|
|
120
|
+
* @returns False once the pull has come to rest (and did not move).
|
|
121
|
+
*/
|
|
122
|
+
step(held) {
|
|
123
|
+
if (!held && this.sim.alpha() <= PULL_REST_ALPHA)
|
|
124
|
+
return false;
|
|
125
|
+
this.sim.tick();
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Where the molecule's nodes stand now.
|
|
130
|
+
*
|
|
131
|
+
* @returns Each moving node and its position.
|
|
132
|
+
*/
|
|
133
|
+
positions() {
|
|
134
|
+
return this.nodes.map((node) => ({ id: node.id, position: [node.x, node.y, node.z] }));
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=pull.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pull.js","sourceRoot":"","sources":["../../src/layout/pull.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file The ranked layout, declared once.
|
|
3
|
+
*
|
|
4
|
+
* A DAG drawn as the tree it is: every node hangs beneath its anchor parent
|
|
5
|
+
* (a join's extra parents are edges, not placement), a subtree occupies a
|
|
6
|
+
* contiguous span of leaf slots and its root sits over the middle of that
|
|
7
|
+
* span, tiers by depth from the roots. The same graph always lands the same
|
|
8
|
+
* way — sibling order is settled by id, so nothing depends on the order
|
|
9
|
+
* nodes happened to arrive in.
|
|
10
|
+
*
|
|
11
|
+
* This module exists because the layout was written twice. The stage drew a
|
|
12
|
+
* pipeline with the algorithm below; the preview card drew the same
|
|
13
|
+
* pipeline by spreading each tier evenly, and the two agreed on nothing —
|
|
14
|
+
* so a card and the detail it previewed were visibly different graphs. They
|
|
15
|
+
* were not made to differ; they were written separately and could not help
|
|
16
|
+
* it.
|
|
17
|
+
*
|
|
18
|
+
* The output is in LAYOUT UNITS — slot columns and tier rows — not in
|
|
19
|
+
* scene units or pixels. Each renderer scales them for the medium it
|
|
20
|
+
* paints: three.js on the stage, SVG in a card that cannot hold a WebGL
|
|
21
|
+
* context, since a grid of ninety previews would want ninety of them and a
|
|
22
|
+
* browser grants about a dozen.
|
|
23
|
+
*
|
|
24
|
+
* Deliberately dependency-free, so it can be unit tested where the scene
|
|
25
|
+
* itself cannot: three.js will not load under jest.
|
|
26
|
+
*
|
|
27
|
+
* @module
|
|
28
|
+
*/
|
|
29
|
+
import { type Vec3 } from './types.js';
|
|
30
|
+
/** What the layout needs of a node: who it is, and what it hangs from. */
|
|
31
|
+
export interface RankedInput {
|
|
32
|
+
id: string;
|
|
33
|
+
parentIds: string[];
|
|
34
|
+
}
|
|
35
|
+
/** Where one node landed, in layout units. */
|
|
36
|
+
export interface RankedPlacement {
|
|
37
|
+
id: string;
|
|
38
|
+
/** Slot column. Fractional for a node centred over a subtree span. */
|
|
39
|
+
x: number;
|
|
40
|
+
/** Tier row, zero at the roots and increasing with depth. */
|
|
41
|
+
tier: number;
|
|
42
|
+
}
|
|
43
|
+
/** A whole graph placed, with the extents a renderer needs to fit it. */
|
|
44
|
+
export interface RankedLayout {
|
|
45
|
+
placements: RankedPlacement[];
|
|
46
|
+
/** Distance between the leftmost and rightmost slot; zero for one column. */
|
|
47
|
+
width: number;
|
|
48
|
+
/** How many tiers deep the graph runs; at least one. */
|
|
49
|
+
tierCount: number;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Places a graph as ranked tiers.
|
|
53
|
+
*
|
|
54
|
+
* @param nodes - The graph. Parent ids naming absent nodes are ignored, so
|
|
55
|
+
* a partial graph still places.
|
|
56
|
+
* @returns Every node's slot and tier, with the extents to fit them by.
|
|
57
|
+
*/
|
|
58
|
+
export declare function rankedLayout_compute(nodes: ReadonlyArray<RankedInput>): RankedLayout;
|
|
59
|
+
/** What the ranked placement reads of a node to draw it: its tree and its metric. */
|
|
60
|
+
export interface RankedSceneNode extends RankedInput {
|
|
61
|
+
metric?: number;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* The ranked layout in scene units: tiers down the screen, the graph
|
|
65
|
+
* centred, every node's radius scaled by its metric (uniform when none
|
|
66
|
+
* arrived — a mode pill that changes nothing on screen reads as broken).
|
|
67
|
+
*
|
|
68
|
+
* @param nodes - The graph.
|
|
69
|
+
* @returns Every node's place and radius, in the input's order.
|
|
70
|
+
*/
|
|
71
|
+
export declare function ranked_layout(nodes: ReadonlyArray<RankedSceneNode>): Array<{
|
|
72
|
+
id: string;
|
|
73
|
+
position: Vec3;
|
|
74
|
+
radius: number;
|
|
75
|
+
}>;
|