@opendata-ai/openchart-vanilla 6.1.1 → 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 +107 -65
- package/dist/index.js.map +1 -1
- package/dist/simulation-worker.js +3 -0
- package/package.json +3 -3
- package/src/graph/canvas-renderer.ts +2 -4
- 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/src/svg-renderer.ts +1 -3
- package/src/table-renderer.ts +1 -1
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,15 +230,15 @@ 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
|
|
239
238
|
import { compileGraph } from "@opendata-ai/openchart-engine";
|
|
240
239
|
|
|
241
240
|
// src/graph/canvas-renderer.ts
|
|
241
|
+
import { BRAND_FONT_SIZE, BRAND_MIN_WIDTH } from "@opendata-ai/openchart-core";
|
|
242
242
|
var LABEL_FONT_MIN = 8;
|
|
243
243
|
var LABEL_FONT_MAX = 12;
|
|
244
244
|
var EDGE_ALPHA_DEFAULT = 0.35;
|
|
@@ -251,7 +251,6 @@ var GLOW_ALPHA = 0.15;
|
|
|
251
251
|
var CULL_MARGIN = 50;
|
|
252
252
|
var TWO_PI = Math.PI * 2;
|
|
253
253
|
var MIN_SCREEN_RADIUS = 2.5;
|
|
254
|
-
var BRAND_MIN_WIDTH = 120;
|
|
255
254
|
function labelThreshold(zoom) {
|
|
256
255
|
const t = Math.max(0, Math.min(1, (zoom - 0.2) / 1.8));
|
|
257
256
|
return 1 - t;
|
|
@@ -386,7 +385,7 @@ var GraphCanvasRenderer = class {
|
|
|
386
385
|
const padding = theme.spacing.padding;
|
|
387
386
|
const x3 = w - padding;
|
|
388
387
|
const y3 = h - padding;
|
|
389
|
-
ctx.font = `600
|
|
388
|
+
ctx.font = `600 ${BRAND_FONT_SIZE}px ${theme.fonts.family}`;
|
|
390
389
|
ctx.fillStyle = theme.colors.axis;
|
|
391
390
|
ctx.globalAlpha = 0.55;
|
|
392
391
|
ctx.textAlign = "right";
|
|
@@ -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() {
|
|
@@ -3214,7 +3258,7 @@ import { isLayerSpec } from "@opendata-ai/openchart-core";
|
|
|
3214
3258
|
import { compileChart, compileLayer } from "@opendata-ai/openchart-engine";
|
|
3215
3259
|
|
|
3216
3260
|
// src/svg-renderer.ts
|
|
3217
|
-
import { estimateTextWidth } from "@opendata-ai/openchart-core";
|
|
3261
|
+
import { BRAND_FONT_SIZE as BRAND_FONT_SIZE2, BRAND_MIN_WIDTH as BRAND_MIN_WIDTH2, estimateTextWidth } from "@opendata-ai/openchart-core";
|
|
3218
3262
|
var SVG_NS = "http://www.w3.org/2000/svg";
|
|
3219
3263
|
function computeXAxisExtent(layout) {
|
|
3220
3264
|
const xAxis = layout.axes.x;
|
|
@@ -4025,8 +4069,6 @@ function renderLegend(parent, legend) {
|
|
|
4025
4069
|
}
|
|
4026
4070
|
parent.appendChild(g);
|
|
4027
4071
|
}
|
|
4028
|
-
var BRAND_FONT_SIZE = 20;
|
|
4029
|
-
var BRAND_MIN_WIDTH2 = 120;
|
|
4030
4072
|
var BRAND_URL = "https://tryopendata.ai";
|
|
4031
4073
|
var XLINK_NS = "http://www.w3.org/1999/xlink";
|
|
4032
4074
|
function renderBrand(parent, layout) {
|
|
@@ -4052,7 +4094,7 @@ function renderBrand(parent, layout) {
|
|
|
4052
4094
|
y: chromeY,
|
|
4053
4095
|
"dominant-baseline": "hanging",
|
|
4054
4096
|
"font-family": layout.theme.fonts.family,
|
|
4055
|
-
"font-size":
|
|
4097
|
+
"font-size": BRAND_FONT_SIZE2,
|
|
4056
4098
|
"text-anchor": "end",
|
|
4057
4099
|
"fill-opacity": 0.55
|
|
4058
4100
|
});
|
|
@@ -5851,8 +5893,8 @@ import { getBreakpoint } from "@opendata-ai/openchart-core";
|
|
|
5851
5893
|
import { compileTable } from "@opendata-ai/openchart-engine";
|
|
5852
5894
|
|
|
5853
5895
|
// src/table-renderer.ts
|
|
5896
|
+
import { BRAND_FONT_SIZE as BRAND_FONT_SIZE3 } from "@opendata-ai/openchart-core";
|
|
5854
5897
|
var BRAND_URL2 = "https://tryopendata.ai";
|
|
5855
|
-
var BRAND_FONT_SIZE2 = 20;
|
|
5856
5898
|
function renderChromeBlock(layout, position) {
|
|
5857
5899
|
const chrome = layout.chrome;
|
|
5858
5900
|
if (position === "header") {
|
|
@@ -6099,7 +6141,7 @@ function renderTable(layout, container) {
|
|
|
6099
6141
|
brandLink.href = BRAND_URL2;
|
|
6100
6142
|
brandLink.target = "_blank";
|
|
6101
6143
|
brandLink.rel = "noopener";
|
|
6102
|
-
brandLink.style.cssText = `font-size: ${
|
|
6144
|
+
brandLink.style.cssText = `font-size: ${BRAND_FONT_SIZE3}px; font-weight: 600; color: ${brandColor}; opacity: 0.55; text-decoration: none; font-family: ${theme ? theme.fonts.family : "sans-serif"};`;
|
|
6103
6145
|
brandLink.textContent = "OpenData";
|
|
6104
6146
|
brand.appendChild(brandLink);
|
|
6105
6147
|
wrapper.appendChild(brand);
|