@opendata-ai/openchart-vanilla 6.1.2 → 6.1.4
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/dist/index.d.ts +5 -15
- package/dist/index.js +100 -57
- package/dist/index.js.map +1 -1
- package/dist/simulation-worker.js +3 -0
- package/package.json +3 -3
- package/src/graph/simulation-worker-url.ts +5 -23
- package/src/graph/simulation-worker.ts +6 -4
- package/src/graph/simulation.ts +124 -60
- package/src/graph-mount.ts +13 -8
package/dist/index.d.ts
CHANGED
|
@@ -79,21 +79,11 @@ declare function exportCSV(data: Record<string, unknown>[]): string;
|
|
|
79
79
|
/**
|
|
80
80
|
* Creates a Web Worker running the force simulation.
|
|
81
81
|
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
* it as a native ES module worker with on-the-fly TypeScript transform.
|
|
88
|
-
* - Production (tsup + bun build): dist/simulation-worker.js is a self-contained
|
|
89
|
-
* IIFE produced by `bun build`. The consuming app's bundler copies it as an
|
|
90
|
-
* asset and rewrites the URL.
|
|
91
|
-
*
|
|
92
|
-
* Usage:
|
|
93
|
-
* import { createSimulationWorker } from '@opendata-ai/openchart-vanilla';
|
|
94
|
-
* const worker = createSimulationWorker();
|
|
95
|
-
* worker.postMessage({ type: 'init', nodes, links, width: 800, height: 600 });
|
|
96
|
-
* worker.onmessage = (e) => console.log(e.data);
|
|
82
|
+
* References the built .js file via `new URL` + `import.meta.url`.
|
|
83
|
+
* The consuming app's bundler resolves the worker path at build time.
|
|
84
|
+
*
|
|
85
|
+
* Note: SimulationManager handles .js/.ts fallback internally. This
|
|
86
|
+
* helper is exported for consumers who want to manage the worker directly.
|
|
97
87
|
*/
|
|
98
88
|
declare function createSimulationWorker(): Worker;
|
|
99
89
|
|
package/dist/index.js
CHANGED
|
@@ -230,9 +230,8 @@ function csvEscape(value) {
|
|
|
230
230
|
}
|
|
231
231
|
|
|
232
232
|
// src/graph/simulation-worker-url.ts
|
|
233
|
-
var workerUrl = new URL("./simulation-worker.ts", import.meta.url);
|
|
234
233
|
function createSimulationWorker() {
|
|
235
|
-
return new Worker(
|
|
234
|
+
return new Worker(new URL("./simulation-worker.js", import.meta.url), { type: "module" });
|
|
236
235
|
}
|
|
237
236
|
|
|
238
237
|
// src/graph-mount.ts
|
|
@@ -2172,8 +2171,8 @@ function y_default2(y3) {
|
|
|
2172
2171
|
}
|
|
2173
2172
|
|
|
2174
2173
|
// src/graph/simulation.ts
|
|
2175
|
-
var SYNC_THRESHOLD = 200;
|
|
2176
2174
|
var SYNC_MAX_TICKS = 300;
|
|
2175
|
+
var SYNC_TICKS_PER_BATCH = 15;
|
|
2177
2176
|
function forceCluster(nodes, strength) {
|
|
2178
2177
|
return (alpha) => {
|
|
2179
2178
|
const cx = /* @__PURE__ */ new Map();
|
|
@@ -2208,6 +2207,7 @@ var SimulationManager = class _SimulationManager {
|
|
|
2208
2207
|
tickCb = null;
|
|
2209
2208
|
settledCb = null;
|
|
2210
2209
|
destroyed = false;
|
|
2210
|
+
syncRafId = null;
|
|
2211
2211
|
// Stored for worker->sync fallback
|
|
2212
2212
|
initNodes = [];
|
|
2213
2213
|
initEdges = [];
|
|
@@ -2220,7 +2220,7 @@ var SimulationManager = class _SimulationManager {
|
|
|
2220
2220
|
*/
|
|
2221
2221
|
static create(nodes, edges, config) {
|
|
2222
2222
|
const mgr = new _SimulationManager();
|
|
2223
|
-
const useWorker = typeof Worker !== "undefined"
|
|
2223
|
+
const useWorker = typeof Worker !== "undefined";
|
|
2224
2224
|
if (useWorker) {
|
|
2225
2225
|
mgr.initWorker(nodes, edges, config);
|
|
2226
2226
|
} else {
|
|
@@ -2259,7 +2259,7 @@ var SimulationManager = class _SimulationManager {
|
|
|
2259
2259
|
}
|
|
2260
2260
|
}
|
|
2261
2261
|
}
|
|
2262
|
-
/** Unpin a node
|
|
2262
|
+
/** Unpin a node and reheat so forces settle it into equilibrium. */
|
|
2263
2263
|
unpinNode(id) {
|
|
2264
2264
|
if (this.destroyed) return;
|
|
2265
2265
|
if (this.worker) {
|
|
@@ -2270,6 +2270,10 @@ var SimulationManager = class _SimulationManager {
|
|
|
2270
2270
|
node.fx = null;
|
|
2271
2271
|
node.fy = null;
|
|
2272
2272
|
}
|
|
2273
|
+
if (this.syncSim && this.syncSim.alpha() < 0.1) {
|
|
2274
|
+
this.syncSim.alpha(0.1).restart();
|
|
2275
|
+
this.runSyncTicks();
|
|
2276
|
+
}
|
|
2273
2277
|
}
|
|
2274
2278
|
}
|
|
2275
2279
|
/** Drag a node (pins it and reheats slightly). */
|
|
@@ -2292,6 +2296,10 @@ var SimulationManager = class _SimulationManager {
|
|
|
2292
2296
|
/** Tear down the simulation and release resources. */
|
|
2293
2297
|
destroy() {
|
|
2294
2298
|
this.destroyed = true;
|
|
2299
|
+
if (this.syncRafId !== null) {
|
|
2300
|
+
cancelAnimationFrame(this.syncRafId);
|
|
2301
|
+
this.syncRafId = null;
|
|
2302
|
+
}
|
|
2295
2303
|
if (this.worker) {
|
|
2296
2304
|
this.worker.postMessage({ type: "stop" });
|
|
2297
2305
|
this.worker.terminate();
|
|
@@ -2311,37 +2319,55 @@ var SimulationManager = class _SimulationManager {
|
|
|
2311
2319
|
this.initNodes = nodes;
|
|
2312
2320
|
this.initEdges = edges;
|
|
2313
2321
|
this.initConfig = config;
|
|
2322
|
+
const initMsg = { type: "init", nodes, edges, config };
|
|
2323
|
+
const wireWorker = (worker) => {
|
|
2324
|
+
this.worker = worker;
|
|
2325
|
+
worker.onmessage = (event) => {
|
|
2326
|
+
if (this.destroyed) return;
|
|
2327
|
+
const msg = event.data;
|
|
2328
|
+
switch (msg.type) {
|
|
2329
|
+
case "positions":
|
|
2330
|
+
this.tickCb?.(msg.nodes, msg.alpha);
|
|
2331
|
+
break;
|
|
2332
|
+
case "settled":
|
|
2333
|
+
this.settledCb?.();
|
|
2334
|
+
break;
|
|
2335
|
+
case "error":
|
|
2336
|
+
console.error("[SimulationManager] Worker error:", msg.message);
|
|
2337
|
+
break;
|
|
2338
|
+
}
|
|
2339
|
+
};
|
|
2340
|
+
worker.postMessage(initMsg);
|
|
2341
|
+
};
|
|
2314
2342
|
try {
|
|
2315
|
-
const
|
|
2316
|
-
|
|
2343
|
+
const w = new Worker(new URL("./simulation-worker.js", import.meta.url), {
|
|
2344
|
+
type: "module"
|
|
2345
|
+
});
|
|
2346
|
+
w.onerror = () => {
|
|
2347
|
+
if (this.destroyed) return;
|
|
2348
|
+
w.terminate();
|
|
2349
|
+
this.worker = null;
|
|
2350
|
+
try {
|
|
2351
|
+
const tsUrl = new URL(import.meta.url.replace(/\/[^/]+$/, "/simulation-worker.ts"));
|
|
2352
|
+
const w2 = new Worker(tsUrl, { type: "module" });
|
|
2353
|
+
w2.onerror = () => {
|
|
2354
|
+
if (this.destroyed) return;
|
|
2355
|
+
console.warn("[SimulationManager] Worker failed to load, falling back to sync");
|
|
2356
|
+
w2.terminate();
|
|
2357
|
+
this.worker = null;
|
|
2358
|
+
this.initSync(this.initNodes, this.initEdges, this.initConfig);
|
|
2359
|
+
};
|
|
2360
|
+
wireWorker(w2);
|
|
2361
|
+
} catch {
|
|
2362
|
+
console.warn("[SimulationManager] Worker creation failed, using sync fallback");
|
|
2363
|
+
this.initSync(this.initNodes, this.initEdges, this.initConfig);
|
|
2364
|
+
}
|
|
2365
|
+
};
|
|
2366
|
+
wireWorker(w);
|
|
2317
2367
|
} catch {
|
|
2318
2368
|
console.warn("[SimulationManager] Worker creation failed, using sync fallback");
|
|
2319
2369
|
this.initSync(nodes, edges, config);
|
|
2320
|
-
return;
|
|
2321
2370
|
}
|
|
2322
|
-
this.worker.onmessage = (event) => {
|
|
2323
|
-
if (this.destroyed) return;
|
|
2324
|
-
const msg = event.data;
|
|
2325
|
-
switch (msg.type) {
|
|
2326
|
-
case "positions":
|
|
2327
|
-
this.tickCb?.(msg.nodes, msg.alpha);
|
|
2328
|
-
break;
|
|
2329
|
-
case "settled":
|
|
2330
|
-
this.settledCb?.();
|
|
2331
|
-
break;
|
|
2332
|
-
case "error":
|
|
2333
|
-
console.error("[SimulationManager] Worker error:", msg.message);
|
|
2334
|
-
break;
|
|
2335
|
-
}
|
|
2336
|
-
};
|
|
2337
|
-
this.worker.onerror = () => {
|
|
2338
|
-
if (this.destroyed) return;
|
|
2339
|
-
console.warn("[SimulationManager] Worker failed to load, falling back to sync");
|
|
2340
|
-
this.worker?.terminate();
|
|
2341
|
-
this.worker = null;
|
|
2342
|
-
this.initSync(this.initNodes, this.initEdges, this.initConfig);
|
|
2343
|
-
};
|
|
2344
|
-
this.worker.postMessage({ type: "init", nodes, edges, config });
|
|
2345
2371
|
}
|
|
2346
2372
|
// -------------------------------------------------------------------------
|
|
2347
2373
|
// Synchronous fallback
|
|
@@ -2374,36 +2400,52 @@ var SimulationManager = class _SimulationManager {
|
|
|
2374
2400
|
this.runSyncTicks(true);
|
|
2375
2401
|
}
|
|
2376
2402
|
/**
|
|
2377
|
-
* Run ticks
|
|
2378
|
-
*
|
|
2379
|
-
*
|
|
2380
|
-
*
|
|
2403
|
+
* Run simulation ticks in batches, yielding to the main thread between
|
|
2404
|
+
* batches via requestAnimationFrame. This prevents a multi-second freeze
|
|
2405
|
+
* when the sync fallback handles large graphs (1k+ nodes).
|
|
2406
|
+
*
|
|
2407
|
+
* Each batch runs SYNC_TICKS_PER_BATCH ticks, emits positions for
|
|
2408
|
+
* progressive rendering, then schedules the next batch.
|
|
2409
|
+
*
|
|
2410
|
+
* @param deferred - When true, start via microtask (initial run where
|
|
2411
|
+
* callbacks aren't wired yet). Otherwise start immediately.
|
|
2381
2412
|
*/
|
|
2382
2413
|
runSyncTicks(deferred = false) {
|
|
2383
2414
|
if (!this.syncSim || this.destroyed) return;
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
if (sim.alpha() < 1e-3) break;
|
|
2415
|
+
if (this.syncRafId !== null) {
|
|
2416
|
+
cancelAnimationFrame(this.syncRafId);
|
|
2417
|
+
this.syncRafId = null;
|
|
2388
2418
|
}
|
|
2389
|
-
const
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2419
|
+
const sim = this.syncSim;
|
|
2420
|
+
let tickCount = 0;
|
|
2421
|
+
const runBatch = () => {
|
|
2422
|
+
if (this.destroyed || !this.syncSim) return;
|
|
2423
|
+
this.syncRafId = null;
|
|
2424
|
+
for (let i = 0; i < SYNC_TICKS_PER_BATCH && tickCount < SYNC_MAX_TICKS; i++, tickCount++) {
|
|
2425
|
+
sim.tick();
|
|
2426
|
+
if (sim.alpha() < 1e-3) {
|
|
2427
|
+
tickCount = SYNC_MAX_TICKS;
|
|
2428
|
+
break;
|
|
2429
|
+
}
|
|
2430
|
+
}
|
|
2431
|
+
const positions = this.syncNodes.map((n) => ({
|
|
2432
|
+
id: n.id,
|
|
2433
|
+
x: n.x ?? 0,
|
|
2434
|
+
y: n.y ?? 0
|
|
2435
|
+
}));
|
|
2436
|
+
const alpha = sim.alpha();
|
|
2437
|
+
const settled = alpha < 1e-3 || tickCount >= SYNC_MAX_TICKS;
|
|
2398
2438
|
this.tickCb?.(positions, alpha);
|
|
2399
2439
|
if (settled) {
|
|
2400
2440
|
this.settledCb?.();
|
|
2441
|
+
} else {
|
|
2442
|
+
this.syncRafId = requestAnimationFrame(runBatch);
|
|
2401
2443
|
}
|
|
2402
2444
|
};
|
|
2403
2445
|
if (deferred) {
|
|
2404
|
-
queueMicrotask(
|
|
2446
|
+
queueMicrotask(runBatch);
|
|
2405
2447
|
} else {
|
|
2406
|
-
|
|
2448
|
+
runBatch();
|
|
2407
2449
|
}
|
|
2408
2450
|
}
|
|
2409
2451
|
};
|
|
@@ -2815,6 +2857,7 @@ function createGraph(container, spec, options) {
|
|
|
2815
2857
|
linkStrength: config.linkStrength,
|
|
2816
2858
|
centerForce: config.centerForce
|
|
2817
2859
|
});
|
|
2860
|
+
let initialSettleDone = false;
|
|
2818
2861
|
simulation.onTick((positions, _alpha) => {
|
|
2819
2862
|
if (destroyed) return;
|
|
2820
2863
|
const posMap = /* @__PURE__ */ new Map();
|
|
@@ -2837,17 +2880,17 @@ function createGraph(container, spec, options) {
|
|
|
2837
2880
|
};
|
|
2838
2881
|
});
|
|
2839
2882
|
spatialIndex.rebuild(positionedNodes);
|
|
2840
|
-
|
|
2841
|
-
scheduleRender();
|
|
2842
|
-
});
|
|
2843
|
-
simulation.onSettled(() => {
|
|
2844
|
-
if (canvas && positionedNodes.length > 0 && interactionManager && renderer) {
|
|
2883
|
+
if (!initialSettleDone && positionedNodes.length > 0 && interactionManager) {
|
|
2845
2884
|
const { width: cw, height: ch } = getCanvasDimensions();
|
|
2846
2885
|
const { transform: fitTransform } = ZoomTransform.fitBounds(positionedNodes, cw, ch);
|
|
2847
2886
|
interactionManager.setTransform(fitTransform);
|
|
2848
|
-
needsRender = true;
|
|
2849
|
-
scheduleRender();
|
|
2850
2887
|
}
|
|
2888
|
+
needsRender = true;
|
|
2889
|
+
scheduleRender();
|
|
2890
|
+
});
|
|
2891
|
+
simulation.onSettled(() => {
|
|
2892
|
+
if (initialSettleDone) return;
|
|
2893
|
+
initialSettleDone = true;
|
|
2851
2894
|
});
|
|
2852
2895
|
}
|
|
2853
2896
|
function getCanvasDimensions() {
|