@opendata-ai/openchart-engine 8.0.0-rc.12 → 8.0.0-rc.13
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 +132 -2
- package/dist/index.js +283 -33
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -10557,15 +10557,10 @@ function normalizeSankeySpec(spec, _warnings) {
|
|
|
10557
10557
|
};
|
|
10558
10558
|
}
|
|
10559
10559
|
function normalizeGraphSpec(spec) {
|
|
10560
|
-
const
|
|
10560
|
+
const layout = {
|
|
10561
10561
|
type: "force",
|
|
10562
|
-
chargeStrength: -300,
|
|
10563
|
-
linkDistance: 30
|
|
10564
|
-
};
|
|
10565
|
-
const layout = spec.layout ? {
|
|
10566
|
-
...defaultLayout,
|
|
10567
10562
|
...spec.layout
|
|
10568
|
-
}
|
|
10563
|
+
};
|
|
10569
10564
|
return {
|
|
10570
10565
|
type: "graph",
|
|
10571
10566
|
nodes: spec.nodes,
|
|
@@ -10577,7 +10572,10 @@ function normalizeGraphSpec(spec) {
|
|
|
10577
10572
|
annotations: normalizeAnnotations(spec.annotations),
|
|
10578
10573
|
theme: spec.theme ?? {},
|
|
10579
10574
|
darkMode: spec.darkMode ?? "off",
|
|
10580
|
-
watermark: spec.watermark ?? true
|
|
10575
|
+
watermark: spec.watermark ?? true,
|
|
10576
|
+
animation: spec.animation,
|
|
10577
|
+
interaction: spec.interaction,
|
|
10578
|
+
legend: spec.legend
|
|
10581
10579
|
};
|
|
10582
10580
|
}
|
|
10583
10581
|
function normalizeTileMapSpec(spec, warnings) {
|
|
@@ -11606,14 +11604,22 @@ function validateGraphSpec(spec, errors) {
|
|
|
11606
11604
|
}
|
|
11607
11605
|
if (spec.encoding && typeof spec.encoding === "object") {
|
|
11608
11606
|
const encoding = spec.encoding;
|
|
11609
|
-
const
|
|
11610
|
-
const
|
|
11611
|
-
|
|
11612
|
-
|
|
11613
|
-
|
|
11607
|
+
const nodeFields = /* @__PURE__ */ new Set();
|
|
11608
|
+
for (const node of nodes) {
|
|
11609
|
+
if (node && typeof node === "object") {
|
|
11610
|
+
for (const key of Object.keys(node)) nodeFields.add(key);
|
|
11611
|
+
}
|
|
11612
|
+
}
|
|
11613
|
+
const edgeFields = /* @__PURE__ */ new Set();
|
|
11614
|
+
for (const edge of edges) {
|
|
11615
|
+
if (edge && typeof edge === "object") {
|
|
11616
|
+
for (const key of Object.keys(edge)) edgeFields.add(key);
|
|
11617
|
+
}
|
|
11618
|
+
}
|
|
11619
|
+
const nodeChannels = ["nodeColor", "nodeSize", "nodeOpacity", "nodeLabel"];
|
|
11614
11620
|
for (const channel of nodeChannels) {
|
|
11615
11621
|
const ch = encoding[channel];
|
|
11616
|
-
if (ch?.field && typeof ch.field === "string" && !nodeFields.has(ch.field)) {
|
|
11622
|
+
if (ch?.field && typeof ch.field === "string" && nodes.length > 0 && !nodeFields.has(ch.field)) {
|
|
11617
11623
|
errors.push({
|
|
11618
11624
|
message: `Spec error: encoding.${channel}.field "${ch.field}" does not exist on nodes. Available fields: ${[...nodeFields].join(", ")}`,
|
|
11619
11625
|
path: `encoding.${channel}.field`,
|
|
@@ -11622,10 +11628,10 @@ function validateGraphSpec(spec, errors) {
|
|
|
11622
11628
|
});
|
|
11623
11629
|
}
|
|
11624
11630
|
}
|
|
11625
|
-
const edgeChannels = ["edgeColor", "edgeWidth"];
|
|
11631
|
+
const edgeChannels = ["edgeColor", "edgeWidth", "edgeStyle"];
|
|
11626
11632
|
for (const channel of edgeChannels) {
|
|
11627
11633
|
const ch = encoding[channel];
|
|
11628
|
-
if (ch?.field && typeof ch.field === "string" &&
|
|
11634
|
+
if (ch?.field && typeof ch.field === "string" && edges.length > 0 && !edgeFields.has(ch.field)) {
|
|
11629
11635
|
errors.push({
|
|
11630
11636
|
message: `Spec error: encoding.${channel}.field "${ch.field}" does not exist on edges. Available fields: ${[...edgeFields].join(", ")}`,
|
|
11631
11637
|
path: `encoding.${channel}.field`,
|
|
@@ -18925,6 +18931,65 @@ import {
|
|
|
18925
18931
|
resolveTheme as resolveTheme4
|
|
18926
18932
|
} from "@opendata-ai/openchart-core";
|
|
18927
18933
|
|
|
18934
|
+
// src/graphs/animation.ts
|
|
18935
|
+
var ENTER_DEFAULT = {
|
|
18936
|
+
duration: 600,
|
|
18937
|
+
ease: "smooth",
|
|
18938
|
+
stagger: true,
|
|
18939
|
+
cameraFit: true
|
|
18940
|
+
};
|
|
18941
|
+
var UPDATE_DEFAULT = { duration: 300, ease: "smooth" };
|
|
18942
|
+
var EXIT_DEFAULT = { duration: 300, ease: "smooth" };
|
|
18943
|
+
var CAMERA_DEFAULT = { duration: "auto", ease: "smooth" };
|
|
18944
|
+
var HOVER_DEFAULT = { duration: 150, ease: "smooth" };
|
|
18945
|
+
function fullDefaults() {
|
|
18946
|
+
return {
|
|
18947
|
+
enter: { ...ENTER_DEFAULT },
|
|
18948
|
+
update: { ...UPDATE_DEFAULT },
|
|
18949
|
+
exit: { ...EXIT_DEFAULT },
|
|
18950
|
+
camera: { ...CAMERA_DEFAULT },
|
|
18951
|
+
hover: { ...HOVER_DEFAULT }
|
|
18952
|
+
};
|
|
18953
|
+
}
|
|
18954
|
+
function resolveGraphAnimation(spec) {
|
|
18955
|
+
if (spec === void 0 || spec === true) return fullDefaults();
|
|
18956
|
+
if (spec === false) return void 0;
|
|
18957
|
+
return {
|
|
18958
|
+
enter: resolveEnter(spec.enter),
|
|
18959
|
+
update: resolveDurationPhase(spec.update, UPDATE_DEFAULT),
|
|
18960
|
+
exit: resolveDurationPhase(spec.exit, EXIT_DEFAULT),
|
|
18961
|
+
camera: resolveCamera(spec.camera),
|
|
18962
|
+
hover: resolveDurationPhase(spec.hover, HOVER_DEFAULT)
|
|
18963
|
+
};
|
|
18964
|
+
}
|
|
18965
|
+
function resolveEnter(phase) {
|
|
18966
|
+
if (phase === false) return null;
|
|
18967
|
+
if (phase === void 0 || phase === true) return { ...ENTER_DEFAULT };
|
|
18968
|
+
return {
|
|
18969
|
+
duration: phase.duration ?? ENTER_DEFAULT.duration,
|
|
18970
|
+
ease: phase.ease ?? ENTER_DEFAULT.ease,
|
|
18971
|
+
stagger: phase.stagger !== false,
|
|
18972
|
+
cameraFit: ENTER_DEFAULT.cameraFit
|
|
18973
|
+
};
|
|
18974
|
+
}
|
|
18975
|
+
function resolveDurationPhase(phase, defaults) {
|
|
18976
|
+
if (phase === false) return null;
|
|
18977
|
+
if (phase === void 0 || phase === true)
|
|
18978
|
+
return { duration: defaults.duration, ease: defaults.ease };
|
|
18979
|
+
return {
|
|
18980
|
+
duration: phase.duration ?? defaults.duration,
|
|
18981
|
+
ease: phase.ease ?? defaults.ease
|
|
18982
|
+
};
|
|
18983
|
+
}
|
|
18984
|
+
function resolveCamera(phase) {
|
|
18985
|
+
if (phase === false) return null;
|
|
18986
|
+
if (phase === void 0 || phase === true) return { ...CAMERA_DEFAULT };
|
|
18987
|
+
return {
|
|
18988
|
+
duration: phase.duration ?? CAMERA_DEFAULT.duration,
|
|
18989
|
+
ease: phase.ease ?? CAMERA_DEFAULT.ease
|
|
18990
|
+
};
|
|
18991
|
+
}
|
|
18992
|
+
|
|
18928
18993
|
// src/graphs/encoding.ts
|
|
18929
18994
|
var DEFAULT_NODE_RADIUS = 5;
|
|
18930
18995
|
var MIN_NODE_RADIUS = 3;
|
|
@@ -18964,16 +19029,42 @@ function computeDegrees(nodes, edges) {
|
|
|
18964
19029
|
}
|
|
18965
19030
|
return degrees2;
|
|
18966
19031
|
}
|
|
19032
|
+
function resolveCategoricalDomain(values, sort, scaleDomain) {
|
|
19033
|
+
if (Array.isArray(scaleDomain) && scaleDomain.length > 0) {
|
|
19034
|
+
return scaleDomain.map((v) => String(v));
|
|
19035
|
+
}
|
|
19036
|
+
const seen = [];
|
|
19037
|
+
const set = /* @__PURE__ */ new Set();
|
|
19038
|
+
for (const v of values) {
|
|
19039
|
+
if (!set.has(v)) {
|
|
19040
|
+
set.add(v);
|
|
19041
|
+
seen.push(v);
|
|
19042
|
+
}
|
|
19043
|
+
}
|
|
19044
|
+
if (sort === null) return seen;
|
|
19045
|
+
if (Array.isArray(sort)) {
|
|
19046
|
+
const pinned = sort.filter((v) => set.has(v));
|
|
19047
|
+
const pinnedSet = new Set(pinned);
|
|
19048
|
+
const rest = seen.filter((v) => !pinnedSet.has(v));
|
|
19049
|
+
return [...pinned, ...rest];
|
|
19050
|
+
}
|
|
19051
|
+
const sorted = [...seen].sort((a, b) => a.localeCompare(b));
|
|
19052
|
+
return sort === "descending" ? sorted.reverse() : sorted;
|
|
19053
|
+
}
|
|
18967
19054
|
function resolveNodeVisuals(nodes, encoding, edges, theme, nodeOverrides) {
|
|
18968
19055
|
const degrees2 = computeDegrees(nodes, edges);
|
|
18969
19056
|
const maxDegree = Math.max(1, ...degrees2.values());
|
|
18970
19057
|
let sizeScale;
|
|
18971
19058
|
if (encoding.nodeSize?.field) {
|
|
18972
19059
|
const field = encoding.nodeSize.field;
|
|
19060
|
+
const scaleConfig = encoding.nodeSize.scale;
|
|
18973
19061
|
const values = nodes.map((n) => Number(n[field])).filter((v) => Number.isFinite(v));
|
|
18974
19062
|
const sizeMin = min(values) ?? 0;
|
|
18975
19063
|
const sizeMax = max(values) ?? 1;
|
|
18976
|
-
|
|
19064
|
+
const domain = scaleConfig?.domain && scaleConfig.domain.length === 2 ? scaleConfig.domain : [sizeMin, sizeMax];
|
|
19065
|
+
const range3 = scaleConfig?.range && scaleConfig.range.length >= 2 ? [Number(scaleConfig.range[0]), Number(scaleConfig.range[1])] : [MIN_NODE_RADIUS, MAX_NODE_RADIUS];
|
|
19066
|
+
const ctor = scaleConfig?.type === "linear" ? linear2 : sqrt;
|
|
19067
|
+
sizeScale = ctor().domain(domain).range(range3);
|
|
18977
19068
|
}
|
|
18978
19069
|
let colorFn;
|
|
18979
19070
|
if (encoding.nodeColor?.field) {
|
|
@@ -18994,13 +19085,28 @@ function resolveNodeVisuals(nodes, encoding, edges, theme, nodeOverrides) {
|
|
|
18994
19085
|
return Number.isFinite(val) ? colorScale(val) : theme.colors.categorical[0];
|
|
18995
19086
|
};
|
|
18996
19087
|
} else {
|
|
18997
|
-
const domain =
|
|
19088
|
+
const domain = resolveCategoricalDomain(
|
|
19089
|
+
nodes.map((n) => String(n[field] ?? "")),
|
|
19090
|
+
encoding.nodeColor.sort,
|
|
19091
|
+
scaleConfig?.domain
|
|
19092
|
+
);
|
|
18998
19093
|
const range3 = scaleConfig?.range && scaleConfig.range.length > 0 ? scaleConfig.range : theme.colors.categorical;
|
|
18999
19094
|
const ordinalScale = ordinal().domain(domain).range(range3);
|
|
19000
19095
|
colorFn = (node) => ordinalScale(String(node[field] ?? ""));
|
|
19001
19096
|
}
|
|
19002
19097
|
}
|
|
19003
19098
|
const defaultColor = theme.colors.categorical[0];
|
|
19099
|
+
let opacityScale;
|
|
19100
|
+
if (encoding.nodeOpacity?.field) {
|
|
19101
|
+
const field = encoding.nodeOpacity.field;
|
|
19102
|
+
const scaleConfig = encoding.nodeOpacity.scale;
|
|
19103
|
+
const values = nodes.map((n) => Number(n[field])).filter((v) => Number.isFinite(v));
|
|
19104
|
+
const opMin = min(values) ?? 0;
|
|
19105
|
+
const opMax = max(values) ?? 1;
|
|
19106
|
+
const domain = scaleConfig?.domain && scaleConfig.domain.length === 2 ? scaleConfig.domain : [opMin, opMax];
|
|
19107
|
+
const range3 = scaleConfig?.range && scaleConfig.range.length >= 2 ? [Number(scaleConfig.range[0]), Number(scaleConfig.range[1])] : [0.25, 1];
|
|
19108
|
+
opacityScale = linear2().domain(domain).range(range3);
|
|
19109
|
+
}
|
|
19004
19110
|
return nodes.map((node) => {
|
|
19005
19111
|
let radius = DEFAULT_NODE_RADIUS;
|
|
19006
19112
|
if (sizeScale && encoding.nodeSize?.field) {
|
|
@@ -19009,6 +19115,13 @@ function resolveNodeVisuals(nodes, encoding, edges, theme, nodeOverrides) {
|
|
|
19009
19115
|
radius = sizeScale(val);
|
|
19010
19116
|
}
|
|
19011
19117
|
}
|
|
19118
|
+
let opacity = 1;
|
|
19119
|
+
if (opacityScale && encoding.nodeOpacity?.field) {
|
|
19120
|
+
const val = Number(node[encoding.nodeOpacity.field]);
|
|
19121
|
+
if (Number.isFinite(val)) {
|
|
19122
|
+
opacity = opacityScale(val);
|
|
19123
|
+
}
|
|
19124
|
+
}
|
|
19012
19125
|
const fill = colorFn ? colorFn(node) : defaultColor;
|
|
19013
19126
|
const stroke = darkenColor(fill);
|
|
19014
19127
|
let label;
|
|
@@ -19037,6 +19150,7 @@ function resolveNodeVisuals(nodes, encoding, edges, theme, nodeOverrides) {
|
|
|
19037
19150
|
label,
|
|
19038
19151
|
labelPriority: finalLabelPriority,
|
|
19039
19152
|
community: void 0,
|
|
19153
|
+
opacity,
|
|
19040
19154
|
data
|
|
19041
19155
|
};
|
|
19042
19156
|
});
|
|
@@ -19069,7 +19183,11 @@ function resolveEdgeVisuals(edges, encoding, theme) {
|
|
|
19069
19183
|
return Number.isFinite(val) ? colorScale(val) : hexWithOpacity(theme.colors.axis, 0.4);
|
|
19070
19184
|
};
|
|
19071
19185
|
} else {
|
|
19072
|
-
const domain =
|
|
19186
|
+
const domain = resolveCategoricalDomain(
|
|
19187
|
+
edges.map((e) => String(e[field] ?? "")),
|
|
19188
|
+
encoding.edgeColor.sort,
|
|
19189
|
+
scaleConfig?.domain
|
|
19190
|
+
);
|
|
19073
19191
|
const range3 = scaleConfig?.range && scaleConfig.range.length > 0 ? scaleConfig.range : theme.colors.categorical;
|
|
19074
19192
|
const ordinalScale = ordinal().domain(domain).range(range3);
|
|
19075
19193
|
edgeColorFn = (edge) => ordinalScale(String(edge[field] ?? ""));
|
|
@@ -19080,10 +19198,14 @@ function resolveEdgeVisuals(edges, encoding, theme) {
|
|
|
19080
19198
|
let styleFn;
|
|
19081
19199
|
if (encoding.edgeStyle?.field) {
|
|
19082
19200
|
const field = encoding.edgeStyle.field;
|
|
19083
|
-
const
|
|
19201
|
+
const domain = resolveCategoricalDomain(
|
|
19202
|
+
edges.map((e) => String(e[field] ?? "")),
|
|
19203
|
+
encoding.edgeStyle.sort,
|
|
19204
|
+
encoding.edgeStyle.scale?.domain
|
|
19205
|
+
);
|
|
19084
19206
|
const styleMap = /* @__PURE__ */ new Map();
|
|
19085
|
-
for (let i = 0; i <
|
|
19086
|
-
styleMap.set(
|
|
19207
|
+
for (let i = 0; i < domain.length; i++) {
|
|
19208
|
+
styleMap.set(domain[i], EDGE_STYLES[i % EDGE_STYLES.length]);
|
|
19087
19209
|
}
|
|
19088
19210
|
styleFn = (edge) => styleMap.get(String(edge[field] ?? "")) ?? "solid";
|
|
19089
19211
|
}
|
|
@@ -19141,12 +19263,49 @@ function applyCommunityColors(nodes, colorMap) {
|
|
|
19141
19263
|
}
|
|
19142
19264
|
}
|
|
19143
19265
|
|
|
19266
|
+
// src/graphs/interaction.ts
|
|
19267
|
+
var DEFAULT_DIM_OPACITY = 0.15;
|
|
19268
|
+
var DEFAULT_CURSOR_RADIUS = 80;
|
|
19269
|
+
var DEFAULT_CURSOR_STRENGTH = 30;
|
|
19270
|
+
function resolveGraphInteraction(cfg) {
|
|
19271
|
+
const hover = cfg?.hover;
|
|
19272
|
+
const cursor = cfg?.cursorRepulsion;
|
|
19273
|
+
let cursorRepulsion = null;
|
|
19274
|
+
if (cursor === true) {
|
|
19275
|
+
cursorRepulsion = { radius: DEFAULT_CURSOR_RADIUS, strength: DEFAULT_CURSOR_STRENGTH };
|
|
19276
|
+
} else if (cursor && typeof cursor === "object") {
|
|
19277
|
+
cursorRepulsion = {
|
|
19278
|
+
radius: cursor.radius ?? DEFAULT_CURSOR_RADIUS,
|
|
19279
|
+
strength: cursor.strength ?? DEFAULT_CURSOR_STRENGTH
|
|
19280
|
+
};
|
|
19281
|
+
}
|
|
19282
|
+
return {
|
|
19283
|
+
hoverMode: hover?.mode ?? "neighbors",
|
|
19284
|
+
dimOpacity: hover?.dimOpacity ?? DEFAULT_DIM_OPACITY,
|
|
19285
|
+
selectFlyTo: cfg?.select?.flyTo ?? false,
|
|
19286
|
+
cursorRepulsion,
|
|
19287
|
+
springyDrag: cfg?.springyDrag ?? false
|
|
19288
|
+
};
|
|
19289
|
+
}
|
|
19290
|
+
|
|
19144
19291
|
// src/graphs/compile-graph.ts
|
|
19145
19292
|
var graphNumberFormatter = defaultNumberFormatter8({ allIntegers: false, surface: "chart" });
|
|
19146
19293
|
var SWATCH_SIZE2 = 12;
|
|
19147
19294
|
var SWATCH_GAP2 = 6;
|
|
19148
19295
|
var ENTRY_GAP2 = 16;
|
|
19149
|
-
|
|
19296
|
+
var ENERGY_PRESETS = {
|
|
19297
|
+
gentle: { chargeStrength: -150, velocityDecay: 0.5 },
|
|
19298
|
+
balanced: { chargeStrength: -300, velocityDecay: 0.4 },
|
|
19299
|
+
energetic: { chargeStrength: -600, velocityDecay: 0.3 }
|
|
19300
|
+
};
|
|
19301
|
+
var SETTLE_PRESETS = {
|
|
19302
|
+
quick: 0.05,
|
|
19303
|
+
balanced: 0.0228,
|
|
19304
|
+
thorough: 0.01
|
|
19305
|
+
};
|
|
19306
|
+
var DEFAULT_WARMUP_TICKS = 100;
|
|
19307
|
+
var DEFAULT_WARMUP_BUDGET_MS = 250;
|
|
19308
|
+
function buildGraphLegend(nodes, communityColorMap, hasCommunities, theme, nodeColorField, legendOrder) {
|
|
19150
19309
|
const labelStyle = {
|
|
19151
19310
|
fontFamily: theme.fonts.family,
|
|
19152
19311
|
fontSize: theme.fonts.sizes.small,
|
|
@@ -19156,25 +19315,35 @@ function buildGraphLegend(nodes, communityColorMap, hasCommunities, theme, nodeC
|
|
|
19156
19315
|
};
|
|
19157
19316
|
let entries;
|
|
19158
19317
|
if (hasCommunities && communityColorMap.size > 0) {
|
|
19318
|
+
const counts = /* @__PURE__ */ new Map();
|
|
19319
|
+
for (const node of nodes) {
|
|
19320
|
+
if (node.community != null) {
|
|
19321
|
+
counts.set(node.community, (counts.get(node.community) ?? 0) + 1);
|
|
19322
|
+
}
|
|
19323
|
+
}
|
|
19159
19324
|
entries = [...communityColorMap.entries()].map(([label, color2]) => ({
|
|
19160
19325
|
label,
|
|
19161
19326
|
color: color2,
|
|
19162
19327
|
shape: "circle",
|
|
19163
|
-
active: true
|
|
19328
|
+
active: true,
|
|
19329
|
+
count: counts.get(label) ?? 0
|
|
19164
19330
|
}));
|
|
19165
19331
|
} else if (nodeColorField) {
|
|
19166
19332
|
const categoryColors = /* @__PURE__ */ new Map();
|
|
19333
|
+
const categoryCounts = /* @__PURE__ */ new Map();
|
|
19167
19334
|
for (const node of nodes) {
|
|
19168
19335
|
const category = String(node.data[nodeColorField] ?? node.label ?? node.id);
|
|
19169
19336
|
if (!categoryColors.has(category)) {
|
|
19170
19337
|
categoryColors.set(category, node.fill);
|
|
19171
19338
|
}
|
|
19339
|
+
categoryCounts.set(category, (categoryCounts.get(category) ?? 0) + 1);
|
|
19172
19340
|
}
|
|
19173
|
-
entries = categoryColors.size <= 1 ? [] : [...categoryColors.
|
|
19341
|
+
entries = categoryColors.size <= 1 ? [] : (legendOrder ?? [...categoryColors.keys()]).filter((label) => categoryColors.has(label)).map((label) => ({
|
|
19174
19342
|
label,
|
|
19175
|
-
color:
|
|
19343
|
+
color: categoryColors.get(label),
|
|
19176
19344
|
shape: "circle",
|
|
19177
|
-
active: true
|
|
19345
|
+
active: true,
|
|
19346
|
+
count: categoryCounts.get(label) ?? 0
|
|
19178
19347
|
}));
|
|
19179
19348
|
} else {
|
|
19180
19349
|
entries = [];
|
|
@@ -19190,6 +19359,30 @@ function buildGraphLegend(nodes, communityColorMap, hasCommunities, theme, nodeC
|
|
|
19190
19359
|
swatchChipFill: theme.colors.annotationFill
|
|
19191
19360
|
};
|
|
19192
19361
|
}
|
|
19362
|
+
function buildEdgeLegend(edges, encoding) {
|
|
19363
|
+
const field = encoding.edgeColor?.field;
|
|
19364
|
+
if (!field || (encoding.edgeColor?.type ?? "nominal") === "quantitative") return void 0;
|
|
19365
|
+
const categoryColors = /* @__PURE__ */ new Map();
|
|
19366
|
+
const categoryCounts = /* @__PURE__ */ new Map();
|
|
19367
|
+
for (const edge of edges) {
|
|
19368
|
+
const category = String(edge.data[field] ?? "");
|
|
19369
|
+
if (!categoryColors.has(category)) categoryColors.set(category, edge.stroke);
|
|
19370
|
+
categoryCounts.set(category, (categoryCounts.get(category) ?? 0) + 1);
|
|
19371
|
+
}
|
|
19372
|
+
if (categoryColors.size <= 1) return void 0;
|
|
19373
|
+
const order = resolveCategoricalDomain(
|
|
19374
|
+
edges.map((e) => String(e.data[field] ?? "")),
|
|
19375
|
+
encoding.edgeColor?.sort,
|
|
19376
|
+
encoding.edgeColor?.scale?.domain
|
|
19377
|
+
);
|
|
19378
|
+
return order.filter((label) => categoryColors.has(label)).map((label) => ({
|
|
19379
|
+
label,
|
|
19380
|
+
color: categoryColors.get(label),
|
|
19381
|
+
shape: "line",
|
|
19382
|
+
active: true,
|
|
19383
|
+
count: categoryCounts.get(label) ?? 0
|
|
19384
|
+
}));
|
|
19385
|
+
}
|
|
19193
19386
|
function buildGraphTooltips(nodes) {
|
|
19194
19387
|
const descriptors = /* @__PURE__ */ new Map();
|
|
19195
19388
|
for (const node of nodes) {
|
|
@@ -19216,6 +19409,21 @@ function buildGraphTooltips(nodes) {
|
|
|
19216
19409
|
}
|
|
19217
19410
|
return descriptors;
|
|
19218
19411
|
}
|
|
19412
|
+
function buildEdgeTooltip(edge) {
|
|
19413
|
+
const fields = [];
|
|
19414
|
+
for (const [key, value2] of Object.entries(edge.data)) {
|
|
19415
|
+
if (key === "source" || key === "target") continue;
|
|
19416
|
+
if (value2 == null) continue;
|
|
19417
|
+
fields.push({
|
|
19418
|
+
label: key,
|
|
19419
|
+
value: typeof value2 === "number" ? graphNumberFormatter(value2) : String(value2)
|
|
19420
|
+
});
|
|
19421
|
+
}
|
|
19422
|
+
return {
|
|
19423
|
+
title: `${edge.source} \u2192 ${edge.target}`,
|
|
19424
|
+
fields
|
|
19425
|
+
};
|
|
19426
|
+
}
|
|
19219
19427
|
function compileGraph(spec, options) {
|
|
19220
19428
|
const { spec: normalized, warnings } = compile(spec);
|
|
19221
19429
|
emitSpecWarnings(warnings, options.onWarn);
|
|
@@ -19225,6 +19433,16 @@ function compileGraph(spec, options) {
|
|
|
19225
19433
|
);
|
|
19226
19434
|
}
|
|
19227
19435
|
const graphSpec = normalized;
|
|
19436
|
+
for (const [name, ch] of [
|
|
19437
|
+
["nodeColor", graphSpec.encoding.nodeColor],
|
|
19438
|
+
["edgeColor", graphSpec.encoding.edgeColor]
|
|
19439
|
+
]) {
|
|
19440
|
+
if (ch?.sort != null && ch.type === "quantitative") {
|
|
19441
|
+
options.onWarn?.(
|
|
19442
|
+
`encoding.${name}.sort is ignored for a quantitative field (sort only orders categorical domains).`
|
|
19443
|
+
);
|
|
19444
|
+
}
|
|
19445
|
+
}
|
|
19228
19446
|
const rawWatermark = spec.watermark;
|
|
19229
19447
|
const watermark = rawWatermark !== void 0 ? graphSpec.watermark : options.watermark ?? true;
|
|
19230
19448
|
const mergedThemeConfig = options.theme ? { ...graphSpec.theme, ...options.theme } : graphSpec.theme;
|
|
@@ -19250,13 +19468,30 @@ function compileGraph(spec, options) {
|
|
|
19250
19468
|
}
|
|
19251
19469
|
const compiledEdges = resolveEdgeVisuals(graphSpec.edges, graphSpec.encoding, theme);
|
|
19252
19470
|
const useCommunitiesForLegend = hasCommunities && !hasNodeColorEncoding;
|
|
19471
|
+
const nodeColorField = graphSpec.encoding.nodeColor?.field;
|
|
19472
|
+
const legendOrder = nodeColorField ? resolveCategoricalDomain(
|
|
19473
|
+
compiledNodes.map((n) => String(n.data[nodeColorField] ?? n.label ?? n.id)),
|
|
19474
|
+
graphSpec.encoding.nodeColor?.sort,
|
|
19475
|
+
graphSpec.encoding.nodeColor?.scale?.domain
|
|
19476
|
+
) : void 0;
|
|
19253
19477
|
const legend = buildGraphLegend(
|
|
19254
19478
|
compiledNodes,
|
|
19255
19479
|
communityColorMap,
|
|
19256
19480
|
useCommunitiesForLegend,
|
|
19257
19481
|
theme,
|
|
19258
|
-
|
|
19482
|
+
nodeColorField,
|
|
19483
|
+
legendOrder
|
|
19259
19484
|
);
|
|
19485
|
+
const edgeLegend = buildEdgeLegend(compiledEdges, graphSpec.encoding);
|
|
19486
|
+
const legendField = nodeColorField ?? (useCommunitiesForLegend ? clusteringField ?? null : null);
|
|
19487
|
+
let initialHighlight;
|
|
19488
|
+
if (nodeColorField && graphSpec.encoding.nodeColor?.highlight != null) {
|
|
19489
|
+
const raw = graphSpec.encoding.nodeColor.highlight;
|
|
19490
|
+
const requested = Array.isArray(raw) ? raw : [raw];
|
|
19491
|
+
const available = new Set(legendOrder ?? []);
|
|
19492
|
+
const values = requested.filter((v) => available.has(v));
|
|
19493
|
+
if (values.length > 0) initialHighlight = { field: nodeColorField, values };
|
|
19494
|
+
}
|
|
19260
19495
|
const tooltipDescriptors = buildGraphTooltips(compiledNodes);
|
|
19261
19496
|
const communityCount = communityColorMap.size;
|
|
19262
19497
|
const altParts = [
|
|
@@ -19273,17 +19508,26 @@ function compileGraph(spec, options) {
|
|
|
19273
19508
|
};
|
|
19274
19509
|
const collisionPadding = graphSpec.layout.collisionPadding ?? 2;
|
|
19275
19510
|
const maxRadius = compiledNodes.length > 0 ? Math.max(...compiledNodes.map((n) => n.radius)) : DEFAULT_COLLISION_PADDING;
|
|
19511
|
+
const energyPreset = graphSpec.layout.energy ? ENERGY_PRESETS[graphSpec.layout.energy] : ENERGY_PRESETS.balanced;
|
|
19512
|
+
const settlePreset = graphSpec.layout.settle ? SETTLE_PRESETS[graphSpec.layout.settle] : SETTLE_PRESETS.balanced;
|
|
19513
|
+
const warmupRaw = graphSpec.layout.warmup;
|
|
19514
|
+
const warmupTicks = warmupRaw === false ? 0 : typeof warmupRaw === "number" ? Math.max(0, Math.floor(warmupRaw)) : DEFAULT_WARMUP_TICKS;
|
|
19276
19515
|
const simulationConfig = {
|
|
19277
|
-
chargeStrength: graphSpec.layout.chargeStrength ??
|
|
19516
|
+
chargeStrength: graphSpec.layout.chargeStrength ?? energyPreset.chargeStrength,
|
|
19278
19517
|
linkDistance: graphSpec.layout.linkDistance ?? 30,
|
|
19279
19518
|
clustering: clusteringField ? { field: clusteringField, strength: 0.5 } : null,
|
|
19280
|
-
alphaDecay:
|
|
19281
|
-
velocityDecay:
|
|
19519
|
+
alphaDecay: settlePreset,
|
|
19520
|
+
velocityDecay: energyPreset.velocityDecay,
|
|
19282
19521
|
collisionRadius: maxRadius + collisionPadding,
|
|
19283
19522
|
collisionPadding,
|
|
19284
19523
|
linkStrength: graphSpec.layout.linkStrength,
|
|
19285
|
-
centerForce: graphSpec.layout.centerForce
|
|
19524
|
+
centerForce: graphSpec.layout.centerForce,
|
|
19525
|
+
seed: graphSpec.layout.seed,
|
|
19526
|
+
warmupTicks,
|
|
19527
|
+
warmupBudgetMs: DEFAULT_WARMUP_BUDGET_MS
|
|
19286
19528
|
};
|
|
19529
|
+
const animation = resolveGraphAnimation(graphSpec.animation);
|
|
19530
|
+
const interaction = resolveGraphInteraction(graphSpec.interaction);
|
|
19287
19531
|
const chrome = computeChrome4(
|
|
19288
19532
|
{
|
|
19289
19533
|
title: graphSpec.chrome.title,
|
|
@@ -19312,7 +19556,12 @@ function compileGraph(spec, options) {
|
|
|
19312
19556
|
height: options.height
|
|
19313
19557
|
},
|
|
19314
19558
|
simulationConfig,
|
|
19315
|
-
watermark
|
|
19559
|
+
watermark,
|
|
19560
|
+
animation,
|
|
19561
|
+
interaction,
|
|
19562
|
+
legendField,
|
|
19563
|
+
initialHighlight,
|
|
19564
|
+
edgeLegend
|
|
19316
19565
|
};
|
|
19317
19566
|
}
|
|
19318
19567
|
var DEFAULT_COLLISION_PADDING = 5;
|
|
@@ -23739,6 +23988,7 @@ export {
|
|
|
23739
23988
|
EXIT_DEFAULTS,
|
|
23740
23989
|
UPDATE_DEFAULTS,
|
|
23741
23990
|
buildAreaPath,
|
|
23991
|
+
buildEdgeTooltip,
|
|
23742
23992
|
buildLinePath,
|
|
23743
23993
|
clampStaggerDelay,
|
|
23744
23994
|
clearRenderers,
|