@opendata-ai/openchart-vanilla 6.1.2 → 6.1.3
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 +101 -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 +122 -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,56 @@ 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 w2 = new Worker(new URL("./simulation-worker.ts", import.meta.url), {
|
|
2352
|
+
type: "module"
|
|
2353
|
+
});
|
|
2354
|
+
w2.onerror = () => {
|
|
2355
|
+
if (this.destroyed) return;
|
|
2356
|
+
console.warn("[SimulationManager] Worker failed to load, falling back to sync");
|
|
2357
|
+
w2.terminate();
|
|
2358
|
+
this.worker = null;
|
|
2359
|
+
this.initSync(this.initNodes, this.initEdges, this.initConfig);
|
|
2360
|
+
};
|
|
2361
|
+
wireWorker(w2);
|
|
2362
|
+
} catch {
|
|
2363
|
+
console.warn("[SimulationManager] Worker creation failed, using sync fallback");
|
|
2364
|
+
this.initSync(this.initNodes, this.initEdges, this.initConfig);
|
|
2365
|
+
}
|
|
2366
|
+
};
|
|
2367
|
+
wireWorker(w);
|
|
2317
2368
|
} catch {
|
|
2318
2369
|
console.warn("[SimulationManager] Worker creation failed, using sync fallback");
|
|
2319
2370
|
this.initSync(nodes, edges, config);
|
|
2320
|
-
return;
|
|
2321
2371
|
}
|
|
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
2372
|
}
|
|
2346
2373
|
// -------------------------------------------------------------------------
|
|
2347
2374
|
// Synchronous fallback
|
|
@@ -2374,36 +2401,52 @@ var SimulationManager = class _SimulationManager {
|
|
|
2374
2401
|
this.runSyncTicks(true);
|
|
2375
2402
|
}
|
|
2376
2403
|
/**
|
|
2377
|
-
* Run ticks
|
|
2378
|
-
*
|
|
2379
|
-
*
|
|
2380
|
-
*
|
|
2404
|
+
* Run simulation ticks in batches, yielding to the main thread between
|
|
2405
|
+
* batches via requestAnimationFrame. This prevents a multi-second freeze
|
|
2406
|
+
* when the sync fallback handles large graphs (1k+ nodes).
|
|
2407
|
+
*
|
|
2408
|
+
* Each batch runs SYNC_TICKS_PER_BATCH ticks, emits positions for
|
|
2409
|
+
* progressive rendering, then schedules the next batch.
|
|
2410
|
+
*
|
|
2411
|
+
* @param deferred - When true, start via microtask (initial run where
|
|
2412
|
+
* callbacks aren't wired yet). Otherwise start immediately.
|
|
2381
2413
|
*/
|
|
2382
2414
|
runSyncTicks(deferred = false) {
|
|
2383
2415
|
if (!this.syncSim || this.destroyed) return;
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
if (sim.alpha() < 1e-3) break;
|
|
2416
|
+
if (this.syncRafId !== null) {
|
|
2417
|
+
cancelAnimationFrame(this.syncRafId);
|
|
2418
|
+
this.syncRafId = null;
|
|
2388
2419
|
}
|
|
2389
|
-
const
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2420
|
+
const sim = this.syncSim;
|
|
2421
|
+
let tickCount = 0;
|
|
2422
|
+
const runBatch = () => {
|
|
2423
|
+
if (this.destroyed || !this.syncSim) return;
|
|
2424
|
+
this.syncRafId = null;
|
|
2425
|
+
for (let i = 0; i < SYNC_TICKS_PER_BATCH && tickCount < SYNC_MAX_TICKS; i++, tickCount++) {
|
|
2426
|
+
sim.tick();
|
|
2427
|
+
if (sim.alpha() < 1e-3) {
|
|
2428
|
+
tickCount = SYNC_MAX_TICKS;
|
|
2429
|
+
break;
|
|
2430
|
+
}
|
|
2431
|
+
}
|
|
2432
|
+
const positions = this.syncNodes.map((n) => ({
|
|
2433
|
+
id: n.id,
|
|
2434
|
+
x: n.x ?? 0,
|
|
2435
|
+
y: n.y ?? 0
|
|
2436
|
+
}));
|
|
2437
|
+
const alpha = sim.alpha();
|
|
2438
|
+
const settled = alpha < 1e-3 || tickCount >= SYNC_MAX_TICKS;
|
|
2398
2439
|
this.tickCb?.(positions, alpha);
|
|
2399
2440
|
if (settled) {
|
|
2400
2441
|
this.settledCb?.();
|
|
2442
|
+
} else {
|
|
2443
|
+
this.syncRafId = requestAnimationFrame(runBatch);
|
|
2401
2444
|
}
|
|
2402
2445
|
};
|
|
2403
2446
|
if (deferred) {
|
|
2404
|
-
queueMicrotask(
|
|
2447
|
+
queueMicrotask(runBatch);
|
|
2405
2448
|
} else {
|
|
2406
|
-
|
|
2449
|
+
runBatch();
|
|
2407
2450
|
}
|
|
2408
2451
|
}
|
|
2409
2452
|
};
|
|
@@ -2815,6 +2858,7 @@ function createGraph(container, spec, options) {
|
|
|
2815
2858
|
linkStrength: config.linkStrength,
|
|
2816
2859
|
centerForce: config.centerForce
|
|
2817
2860
|
});
|
|
2861
|
+
let initialSettleDone = false;
|
|
2818
2862
|
simulation.onTick((positions, _alpha) => {
|
|
2819
2863
|
if (destroyed) return;
|
|
2820
2864
|
const posMap = /* @__PURE__ */ new Map();
|
|
@@ -2837,17 +2881,17 @@ function createGraph(container, spec, options) {
|
|
|
2837
2881
|
};
|
|
2838
2882
|
});
|
|
2839
2883
|
spatialIndex.rebuild(positionedNodes);
|
|
2840
|
-
|
|
2841
|
-
scheduleRender();
|
|
2842
|
-
});
|
|
2843
|
-
simulation.onSettled(() => {
|
|
2844
|
-
if (canvas && positionedNodes.length > 0 && interactionManager && renderer) {
|
|
2884
|
+
if (!initialSettleDone && positionedNodes.length > 0 && interactionManager) {
|
|
2845
2885
|
const { width: cw, height: ch } = getCanvasDimensions();
|
|
2846
2886
|
const { transform: fitTransform } = ZoomTransform.fitBounds(positionedNodes, cw, ch);
|
|
2847
2887
|
interactionManager.setTransform(fitTransform);
|
|
2848
|
-
needsRender = true;
|
|
2849
|
-
scheduleRender();
|
|
2850
2888
|
}
|
|
2889
|
+
needsRender = true;
|
|
2890
|
+
scheduleRender();
|
|
2891
|
+
});
|
|
2892
|
+
simulation.onSettled(() => {
|
|
2893
|
+
if (initialSettleDone) return;
|
|
2894
|
+
initialSettleDone = true;
|
|
2851
2895
|
});
|
|
2852
2896
|
}
|
|
2853
2897
|
function getCanvasDimensions() {
|