@opendata-ai/openchart-engine 8.0.0-rc.12 → 8.0.0-rc.14
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 +308 -35
- 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,28 @@ 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 = [
|
|
11620
|
+
"nodeColor",
|
|
11621
|
+
"nodeSize",
|
|
11622
|
+
"nodeOpacity",
|
|
11623
|
+
"nodeLabel",
|
|
11624
|
+
"nodeLabelPriority"
|
|
11625
|
+
];
|
|
11614
11626
|
for (const channel of nodeChannels) {
|
|
11615
11627
|
const ch = encoding[channel];
|
|
11616
|
-
if (ch?.field && typeof ch.field === "string" && !nodeFields.has(ch.field)) {
|
|
11628
|
+
if (ch?.field && typeof ch.field === "string" && nodes.length > 0 && !nodeFields.has(ch.field)) {
|
|
11617
11629
|
errors.push({
|
|
11618
11630
|
message: `Spec error: encoding.${channel}.field "${ch.field}" does not exist on nodes. Available fields: ${[...nodeFields].join(", ")}`,
|
|
11619
11631
|
path: `encoding.${channel}.field`,
|
|
@@ -11622,10 +11634,10 @@ function validateGraphSpec(spec, errors) {
|
|
|
11622
11634
|
});
|
|
11623
11635
|
}
|
|
11624
11636
|
}
|
|
11625
|
-
const edgeChannels = ["edgeColor", "edgeWidth"];
|
|
11637
|
+
const edgeChannels = ["edgeColor", "edgeWidth", "edgeStyle"];
|
|
11626
11638
|
for (const channel of edgeChannels) {
|
|
11627
11639
|
const ch = encoding[channel];
|
|
11628
|
-
if (ch?.field && typeof ch.field === "string" &&
|
|
11640
|
+
if (ch?.field && typeof ch.field === "string" && edges.length > 0 && !edgeFields.has(ch.field)) {
|
|
11629
11641
|
errors.push({
|
|
11630
11642
|
message: `Spec error: encoding.${channel}.field "${ch.field}" does not exist on edges. Available fields: ${[...edgeFields].join(", ")}`,
|
|
11631
11643
|
path: `encoding.${channel}.field`,
|
|
@@ -18925,6 +18937,65 @@ import {
|
|
|
18925
18937
|
resolveTheme as resolveTheme4
|
|
18926
18938
|
} from "@opendata-ai/openchart-core";
|
|
18927
18939
|
|
|
18940
|
+
// src/graphs/animation.ts
|
|
18941
|
+
var ENTER_DEFAULT = {
|
|
18942
|
+
duration: 500,
|
|
18943
|
+
ease: "smooth",
|
|
18944
|
+
stagger: true,
|
|
18945
|
+
cameraFit: true
|
|
18946
|
+
};
|
|
18947
|
+
var UPDATE_DEFAULT = { duration: 300, ease: "smooth" };
|
|
18948
|
+
var EXIT_DEFAULT = { duration: 300, ease: "smooth" };
|
|
18949
|
+
var CAMERA_DEFAULT = { duration: "auto", ease: "smooth" };
|
|
18950
|
+
var HOVER_DEFAULT = { duration: 150, ease: "smooth" };
|
|
18951
|
+
function fullDefaults() {
|
|
18952
|
+
return {
|
|
18953
|
+
enter: { ...ENTER_DEFAULT },
|
|
18954
|
+
update: { ...UPDATE_DEFAULT },
|
|
18955
|
+
exit: { ...EXIT_DEFAULT },
|
|
18956
|
+
camera: { ...CAMERA_DEFAULT },
|
|
18957
|
+
hover: { ...HOVER_DEFAULT }
|
|
18958
|
+
};
|
|
18959
|
+
}
|
|
18960
|
+
function resolveGraphAnimation(spec) {
|
|
18961
|
+
if (spec === void 0 || spec === true) return fullDefaults();
|
|
18962
|
+
if (spec === false) return void 0;
|
|
18963
|
+
return {
|
|
18964
|
+
enter: resolveEnter(spec.enter),
|
|
18965
|
+
update: resolveDurationPhase(spec.update, UPDATE_DEFAULT),
|
|
18966
|
+
exit: resolveDurationPhase(spec.exit, EXIT_DEFAULT),
|
|
18967
|
+
camera: resolveCamera(spec.camera),
|
|
18968
|
+
hover: resolveDurationPhase(spec.hover, HOVER_DEFAULT)
|
|
18969
|
+
};
|
|
18970
|
+
}
|
|
18971
|
+
function resolveEnter(phase) {
|
|
18972
|
+
if (phase === false) return null;
|
|
18973
|
+
if (phase === void 0 || phase === true) return { ...ENTER_DEFAULT };
|
|
18974
|
+
return {
|
|
18975
|
+
duration: phase.duration ?? ENTER_DEFAULT.duration,
|
|
18976
|
+
ease: phase.ease ?? ENTER_DEFAULT.ease,
|
|
18977
|
+
stagger: phase.stagger !== false,
|
|
18978
|
+
cameraFit: ENTER_DEFAULT.cameraFit
|
|
18979
|
+
};
|
|
18980
|
+
}
|
|
18981
|
+
function resolveDurationPhase(phase, defaults) {
|
|
18982
|
+
if (phase === false) return null;
|
|
18983
|
+
if (phase === void 0 || phase === true)
|
|
18984
|
+
return { duration: defaults.duration, ease: defaults.ease };
|
|
18985
|
+
return {
|
|
18986
|
+
duration: phase.duration ?? defaults.duration,
|
|
18987
|
+
ease: phase.ease ?? defaults.ease
|
|
18988
|
+
};
|
|
18989
|
+
}
|
|
18990
|
+
function resolveCamera(phase) {
|
|
18991
|
+
if (phase === false) return null;
|
|
18992
|
+
if (phase === void 0 || phase === true) return { ...CAMERA_DEFAULT };
|
|
18993
|
+
return {
|
|
18994
|
+
duration: phase.duration ?? CAMERA_DEFAULT.duration,
|
|
18995
|
+
ease: phase.ease ?? CAMERA_DEFAULT.ease
|
|
18996
|
+
};
|
|
18997
|
+
}
|
|
18998
|
+
|
|
18928
18999
|
// src/graphs/encoding.ts
|
|
18929
19000
|
var DEFAULT_NODE_RADIUS = 5;
|
|
18930
19001
|
var MIN_NODE_RADIUS = 3;
|
|
@@ -18964,16 +19035,42 @@ function computeDegrees(nodes, edges) {
|
|
|
18964
19035
|
}
|
|
18965
19036
|
return degrees2;
|
|
18966
19037
|
}
|
|
19038
|
+
function resolveCategoricalDomain(values, sort, scaleDomain) {
|
|
19039
|
+
if (Array.isArray(scaleDomain) && scaleDomain.length > 0) {
|
|
19040
|
+
return scaleDomain.map((v) => String(v));
|
|
19041
|
+
}
|
|
19042
|
+
const seen = [];
|
|
19043
|
+
const set = /* @__PURE__ */ new Set();
|
|
19044
|
+
for (const v of values) {
|
|
19045
|
+
if (!set.has(v)) {
|
|
19046
|
+
set.add(v);
|
|
19047
|
+
seen.push(v);
|
|
19048
|
+
}
|
|
19049
|
+
}
|
|
19050
|
+
if (sort === null) return seen;
|
|
19051
|
+
if (Array.isArray(sort)) {
|
|
19052
|
+
const pinned = sort.filter((v) => set.has(v));
|
|
19053
|
+
const pinnedSet = new Set(pinned);
|
|
19054
|
+
const rest = seen.filter((v) => !pinnedSet.has(v));
|
|
19055
|
+
return [...pinned, ...rest];
|
|
19056
|
+
}
|
|
19057
|
+
const sorted = [...seen].sort((a, b) => a.localeCompare(b));
|
|
19058
|
+
return sort === "descending" ? sorted.reverse() : sorted;
|
|
19059
|
+
}
|
|
18967
19060
|
function resolveNodeVisuals(nodes, encoding, edges, theme, nodeOverrides) {
|
|
18968
19061
|
const degrees2 = computeDegrees(nodes, edges);
|
|
18969
19062
|
const maxDegree = Math.max(1, ...degrees2.values());
|
|
18970
19063
|
let sizeScale;
|
|
18971
19064
|
if (encoding.nodeSize?.field) {
|
|
18972
19065
|
const field = encoding.nodeSize.field;
|
|
19066
|
+
const scaleConfig = encoding.nodeSize.scale;
|
|
18973
19067
|
const values = nodes.map((n) => Number(n[field])).filter((v) => Number.isFinite(v));
|
|
18974
19068
|
const sizeMin = min(values) ?? 0;
|
|
18975
19069
|
const sizeMax = max(values) ?? 1;
|
|
18976
|
-
|
|
19070
|
+
const domain = scaleConfig?.domain && scaleConfig.domain.length === 2 ? scaleConfig.domain : [sizeMin, sizeMax];
|
|
19071
|
+
const range3 = scaleConfig?.range && scaleConfig.range.length >= 2 ? [Number(scaleConfig.range[0]), Number(scaleConfig.range[1])] : [MIN_NODE_RADIUS, MAX_NODE_RADIUS];
|
|
19072
|
+
const ctor = scaleConfig?.type === "linear" ? linear2 : sqrt;
|
|
19073
|
+
sizeScale = ctor().domain(domain).range(range3);
|
|
18977
19074
|
}
|
|
18978
19075
|
let colorFn;
|
|
18979
19076
|
if (encoding.nodeColor?.field) {
|
|
@@ -18994,13 +19091,39 @@ function resolveNodeVisuals(nodes, encoding, edges, theme, nodeOverrides) {
|
|
|
18994
19091
|
return Number.isFinite(val) ? colorScale(val) : theme.colors.categorical[0];
|
|
18995
19092
|
};
|
|
18996
19093
|
} else {
|
|
18997
|
-
const domain =
|
|
19094
|
+
const domain = resolveCategoricalDomain(
|
|
19095
|
+
nodes.map((n) => String(n[field] ?? "")),
|
|
19096
|
+
encoding.nodeColor.sort,
|
|
19097
|
+
scaleConfig?.domain
|
|
19098
|
+
);
|
|
18998
19099
|
const range3 = scaleConfig?.range && scaleConfig.range.length > 0 ? scaleConfig.range : theme.colors.categorical;
|
|
18999
19100
|
const ordinalScale = ordinal().domain(domain).range(range3);
|
|
19000
19101
|
colorFn = (node) => ordinalScale(String(node[field] ?? ""));
|
|
19001
19102
|
}
|
|
19002
19103
|
}
|
|
19003
19104
|
const defaultColor = theme.colors.categorical[0];
|
|
19105
|
+
let opacityScale;
|
|
19106
|
+
if (encoding.nodeOpacity?.field) {
|
|
19107
|
+
const field = encoding.nodeOpacity.field;
|
|
19108
|
+
const scaleConfig = encoding.nodeOpacity.scale;
|
|
19109
|
+
const values = nodes.map((n) => Number(n[field])).filter((v) => Number.isFinite(v));
|
|
19110
|
+
const opMin = min(values) ?? 0;
|
|
19111
|
+
const opMax = max(values) ?? 1;
|
|
19112
|
+
const domain = scaleConfig?.domain && scaleConfig.domain.length === 2 ? scaleConfig.domain : [opMin, opMax];
|
|
19113
|
+
const range3 = scaleConfig?.range && scaleConfig.range.length >= 2 ? [Number(scaleConfig.range[0]), Number(scaleConfig.range[1])] : [0.25, 1];
|
|
19114
|
+
opacityScale = linear2().domain(domain).range(range3);
|
|
19115
|
+
}
|
|
19116
|
+
let labelPriorityScale;
|
|
19117
|
+
if (encoding.nodeLabelPriority?.field) {
|
|
19118
|
+
const field = encoding.nodeLabelPriority.field;
|
|
19119
|
+
const scaleConfig = encoding.nodeLabelPriority.scale;
|
|
19120
|
+
const values = nodes.map((n) => Number(n[field])).filter((v) => Number.isFinite(v));
|
|
19121
|
+
const lpMin = min(values) ?? 0;
|
|
19122
|
+
const lpMax = max(values) ?? 1;
|
|
19123
|
+
const domain = scaleConfig?.domain && scaleConfig.domain.length === 2 ? scaleConfig.domain : [lpMin, lpMax];
|
|
19124
|
+
const range3 = scaleConfig?.range && scaleConfig.range.length >= 2 ? [Number(scaleConfig.range[0]), Number(scaleConfig.range[1])] : [0, 1];
|
|
19125
|
+
labelPriorityScale = linear2().domain(domain).range(range3);
|
|
19126
|
+
}
|
|
19004
19127
|
return nodes.map((node) => {
|
|
19005
19128
|
let radius = DEFAULT_NODE_RADIUS;
|
|
19006
19129
|
if (sizeScale && encoding.nodeSize?.field) {
|
|
@@ -19009,6 +19132,13 @@ function resolveNodeVisuals(nodes, encoding, edges, theme, nodeOverrides) {
|
|
|
19009
19132
|
radius = sizeScale(val);
|
|
19010
19133
|
}
|
|
19011
19134
|
}
|
|
19135
|
+
let opacity = 1;
|
|
19136
|
+
if (opacityScale && encoding.nodeOpacity?.field) {
|
|
19137
|
+
const val = Number(node[encoding.nodeOpacity.field]);
|
|
19138
|
+
if (Number.isFinite(val)) {
|
|
19139
|
+
opacity = opacityScale(val);
|
|
19140
|
+
}
|
|
19141
|
+
}
|
|
19012
19142
|
const fill = colorFn ? colorFn(node) : defaultColor;
|
|
19013
19143
|
const stroke = darkenColor(fill);
|
|
19014
19144
|
let label;
|
|
@@ -19018,8 +19148,14 @@ function resolveNodeVisuals(nodes, encoding, edges, theme, nodeOverrides) {
|
|
|
19018
19148
|
} else {
|
|
19019
19149
|
label = node.id;
|
|
19020
19150
|
}
|
|
19021
|
-
|
|
19022
|
-
|
|
19151
|
+
let labelPriority;
|
|
19152
|
+
if (labelPriorityScale && encoding.nodeLabelPriority?.field) {
|
|
19153
|
+
const val = Number(node[encoding.nodeLabelPriority.field]);
|
|
19154
|
+
labelPriority = Number.isFinite(val) ? labelPriorityScale(val) : 0;
|
|
19155
|
+
} else {
|
|
19156
|
+
const degree = degrees2.get(node.id) ?? 0;
|
|
19157
|
+
labelPriority = maxDegree > 0 ? degree / maxDegree : 0;
|
|
19158
|
+
}
|
|
19023
19159
|
const { id: _id, ...rest } = node;
|
|
19024
19160
|
const data = { id: node.id, ...rest };
|
|
19025
19161
|
const override = nodeOverrides?.[node.id];
|
|
@@ -19037,6 +19173,7 @@ function resolveNodeVisuals(nodes, encoding, edges, theme, nodeOverrides) {
|
|
|
19037
19173
|
label,
|
|
19038
19174
|
labelPriority: finalLabelPriority,
|
|
19039
19175
|
community: void 0,
|
|
19176
|
+
opacity,
|
|
19040
19177
|
data
|
|
19041
19178
|
};
|
|
19042
19179
|
});
|
|
@@ -19069,7 +19206,11 @@ function resolveEdgeVisuals(edges, encoding, theme) {
|
|
|
19069
19206
|
return Number.isFinite(val) ? colorScale(val) : hexWithOpacity(theme.colors.axis, 0.4);
|
|
19070
19207
|
};
|
|
19071
19208
|
} else {
|
|
19072
|
-
const domain =
|
|
19209
|
+
const domain = resolveCategoricalDomain(
|
|
19210
|
+
edges.map((e) => String(e[field] ?? "")),
|
|
19211
|
+
encoding.edgeColor.sort,
|
|
19212
|
+
scaleConfig?.domain
|
|
19213
|
+
);
|
|
19073
19214
|
const range3 = scaleConfig?.range && scaleConfig.range.length > 0 ? scaleConfig.range : theme.colors.categorical;
|
|
19074
19215
|
const ordinalScale = ordinal().domain(domain).range(range3);
|
|
19075
19216
|
edgeColorFn = (edge) => ordinalScale(String(edge[field] ?? ""));
|
|
@@ -19080,10 +19221,14 @@ function resolveEdgeVisuals(edges, encoding, theme) {
|
|
|
19080
19221
|
let styleFn;
|
|
19081
19222
|
if (encoding.edgeStyle?.field) {
|
|
19082
19223
|
const field = encoding.edgeStyle.field;
|
|
19083
|
-
const
|
|
19224
|
+
const domain = resolveCategoricalDomain(
|
|
19225
|
+
edges.map((e) => String(e[field] ?? "")),
|
|
19226
|
+
encoding.edgeStyle.sort,
|
|
19227
|
+
encoding.edgeStyle.scale?.domain
|
|
19228
|
+
);
|
|
19084
19229
|
const styleMap = /* @__PURE__ */ new Map();
|
|
19085
|
-
for (let i = 0; i <
|
|
19086
|
-
styleMap.set(
|
|
19230
|
+
for (let i = 0; i < domain.length; i++) {
|
|
19231
|
+
styleMap.set(domain[i], EDGE_STYLES[i % EDGE_STYLES.length]);
|
|
19087
19232
|
}
|
|
19088
19233
|
styleFn = (edge) => styleMap.get(String(edge[field] ?? "")) ?? "solid";
|
|
19089
19234
|
}
|
|
@@ -19141,12 +19286,49 @@ function applyCommunityColors(nodes, colorMap) {
|
|
|
19141
19286
|
}
|
|
19142
19287
|
}
|
|
19143
19288
|
|
|
19289
|
+
// src/graphs/interaction.ts
|
|
19290
|
+
var DEFAULT_DIM_OPACITY = 0.15;
|
|
19291
|
+
var DEFAULT_CURSOR_RADIUS = 80;
|
|
19292
|
+
var DEFAULT_CURSOR_STRENGTH = 30;
|
|
19293
|
+
function resolveGraphInteraction(cfg) {
|
|
19294
|
+
const hover = cfg?.hover;
|
|
19295
|
+
const cursor = cfg?.cursorRepulsion;
|
|
19296
|
+
let cursorRepulsion = null;
|
|
19297
|
+
if (cursor === true) {
|
|
19298
|
+
cursorRepulsion = { radius: DEFAULT_CURSOR_RADIUS, strength: DEFAULT_CURSOR_STRENGTH };
|
|
19299
|
+
} else if (cursor && typeof cursor === "object") {
|
|
19300
|
+
cursorRepulsion = {
|
|
19301
|
+
radius: cursor.radius ?? DEFAULT_CURSOR_RADIUS,
|
|
19302
|
+
strength: cursor.strength ?? DEFAULT_CURSOR_STRENGTH
|
|
19303
|
+
};
|
|
19304
|
+
}
|
|
19305
|
+
return {
|
|
19306
|
+
hoverMode: hover?.mode ?? "neighbors",
|
|
19307
|
+
dimOpacity: hover?.dimOpacity ?? DEFAULT_DIM_OPACITY,
|
|
19308
|
+
selectFlyTo: cfg?.select?.flyTo ?? false,
|
|
19309
|
+
cursorRepulsion,
|
|
19310
|
+
springyDrag: cfg?.springyDrag ?? false
|
|
19311
|
+
};
|
|
19312
|
+
}
|
|
19313
|
+
|
|
19144
19314
|
// src/graphs/compile-graph.ts
|
|
19145
19315
|
var graphNumberFormatter = defaultNumberFormatter8({ allIntegers: false, surface: "chart" });
|
|
19146
19316
|
var SWATCH_SIZE2 = 12;
|
|
19147
19317
|
var SWATCH_GAP2 = 6;
|
|
19148
19318
|
var ENTRY_GAP2 = 16;
|
|
19149
|
-
|
|
19319
|
+
var ENERGY_PRESETS = {
|
|
19320
|
+
gentle: { chargeStrength: -150, velocityDecay: 0.5 },
|
|
19321
|
+
balanced: { chargeStrength: -300, velocityDecay: 0.4 },
|
|
19322
|
+
energetic: { chargeStrength: -600, velocityDecay: 0.3 }
|
|
19323
|
+
};
|
|
19324
|
+
var SETTLE_PRESETS = {
|
|
19325
|
+
quick: 0.05,
|
|
19326
|
+
balanced: 0.0228,
|
|
19327
|
+
thorough: 0.01
|
|
19328
|
+
};
|
|
19329
|
+
var DEFAULT_WARMUP_TICKS = 100;
|
|
19330
|
+
var DEFAULT_WARMUP_BUDGET_MS = 250;
|
|
19331
|
+
function buildGraphLegend(nodes, communityColorMap, hasCommunities, theme, nodeColorField, legendOrder) {
|
|
19150
19332
|
const labelStyle = {
|
|
19151
19333
|
fontFamily: theme.fonts.family,
|
|
19152
19334
|
fontSize: theme.fonts.sizes.small,
|
|
@@ -19156,25 +19338,35 @@ function buildGraphLegend(nodes, communityColorMap, hasCommunities, theme, nodeC
|
|
|
19156
19338
|
};
|
|
19157
19339
|
let entries;
|
|
19158
19340
|
if (hasCommunities && communityColorMap.size > 0) {
|
|
19341
|
+
const counts = /* @__PURE__ */ new Map();
|
|
19342
|
+
for (const node of nodes) {
|
|
19343
|
+
if (node.community != null) {
|
|
19344
|
+
counts.set(node.community, (counts.get(node.community) ?? 0) + 1);
|
|
19345
|
+
}
|
|
19346
|
+
}
|
|
19159
19347
|
entries = [...communityColorMap.entries()].map(([label, color2]) => ({
|
|
19160
19348
|
label,
|
|
19161
19349
|
color: color2,
|
|
19162
19350
|
shape: "circle",
|
|
19163
|
-
active: true
|
|
19351
|
+
active: true,
|
|
19352
|
+
count: counts.get(label) ?? 0
|
|
19164
19353
|
}));
|
|
19165
19354
|
} else if (nodeColorField) {
|
|
19166
19355
|
const categoryColors = /* @__PURE__ */ new Map();
|
|
19356
|
+
const categoryCounts = /* @__PURE__ */ new Map();
|
|
19167
19357
|
for (const node of nodes) {
|
|
19168
19358
|
const category = String(node.data[nodeColorField] ?? node.label ?? node.id);
|
|
19169
19359
|
if (!categoryColors.has(category)) {
|
|
19170
19360
|
categoryColors.set(category, node.fill);
|
|
19171
19361
|
}
|
|
19362
|
+
categoryCounts.set(category, (categoryCounts.get(category) ?? 0) + 1);
|
|
19172
19363
|
}
|
|
19173
|
-
entries = categoryColors.size <= 1 ? [] : [...categoryColors.
|
|
19364
|
+
entries = categoryColors.size <= 1 ? [] : (legendOrder ?? [...categoryColors.keys()]).filter((label) => categoryColors.has(label)).map((label) => ({
|
|
19174
19365
|
label,
|
|
19175
|
-
color:
|
|
19366
|
+
color: categoryColors.get(label),
|
|
19176
19367
|
shape: "circle",
|
|
19177
|
-
active: true
|
|
19368
|
+
active: true,
|
|
19369
|
+
count: categoryCounts.get(label) ?? 0
|
|
19178
19370
|
}));
|
|
19179
19371
|
} else {
|
|
19180
19372
|
entries = [];
|
|
@@ -19190,6 +19382,30 @@ function buildGraphLegend(nodes, communityColorMap, hasCommunities, theme, nodeC
|
|
|
19190
19382
|
swatchChipFill: theme.colors.annotationFill
|
|
19191
19383
|
};
|
|
19192
19384
|
}
|
|
19385
|
+
function buildEdgeLegend(edges, encoding) {
|
|
19386
|
+
const field = encoding.edgeColor?.field;
|
|
19387
|
+
if (!field || (encoding.edgeColor?.type ?? "nominal") === "quantitative") return void 0;
|
|
19388
|
+
const categoryColors = /* @__PURE__ */ new Map();
|
|
19389
|
+
const categoryCounts = /* @__PURE__ */ new Map();
|
|
19390
|
+
for (const edge of edges) {
|
|
19391
|
+
const category = String(edge.data[field] ?? "");
|
|
19392
|
+
if (!categoryColors.has(category)) categoryColors.set(category, edge.stroke);
|
|
19393
|
+
categoryCounts.set(category, (categoryCounts.get(category) ?? 0) + 1);
|
|
19394
|
+
}
|
|
19395
|
+
if (categoryColors.size <= 1) return void 0;
|
|
19396
|
+
const order = resolveCategoricalDomain(
|
|
19397
|
+
edges.map((e) => String(e.data[field] ?? "")),
|
|
19398
|
+
encoding.edgeColor?.sort,
|
|
19399
|
+
encoding.edgeColor?.scale?.domain
|
|
19400
|
+
);
|
|
19401
|
+
return order.filter((label) => categoryColors.has(label)).map((label) => ({
|
|
19402
|
+
label,
|
|
19403
|
+
color: categoryColors.get(label),
|
|
19404
|
+
shape: "line",
|
|
19405
|
+
active: true,
|
|
19406
|
+
count: categoryCounts.get(label) ?? 0
|
|
19407
|
+
}));
|
|
19408
|
+
}
|
|
19193
19409
|
function buildGraphTooltips(nodes) {
|
|
19194
19410
|
const descriptors = /* @__PURE__ */ new Map();
|
|
19195
19411
|
for (const node of nodes) {
|
|
@@ -19216,6 +19432,21 @@ function buildGraphTooltips(nodes) {
|
|
|
19216
19432
|
}
|
|
19217
19433
|
return descriptors;
|
|
19218
19434
|
}
|
|
19435
|
+
function buildEdgeTooltip(edge) {
|
|
19436
|
+
const fields = [];
|
|
19437
|
+
for (const [key, value2] of Object.entries(edge.data)) {
|
|
19438
|
+
if (key === "source" || key === "target") continue;
|
|
19439
|
+
if (value2 == null) continue;
|
|
19440
|
+
fields.push({
|
|
19441
|
+
label: key,
|
|
19442
|
+
value: typeof value2 === "number" ? graphNumberFormatter(value2) : String(value2)
|
|
19443
|
+
});
|
|
19444
|
+
}
|
|
19445
|
+
return {
|
|
19446
|
+
title: `${edge.source} \u2192 ${edge.target}`,
|
|
19447
|
+
fields
|
|
19448
|
+
};
|
|
19449
|
+
}
|
|
19219
19450
|
function compileGraph(spec, options) {
|
|
19220
19451
|
const { spec: normalized, warnings } = compile(spec);
|
|
19221
19452
|
emitSpecWarnings(warnings, options.onWarn);
|
|
@@ -19225,6 +19456,16 @@ function compileGraph(spec, options) {
|
|
|
19225
19456
|
);
|
|
19226
19457
|
}
|
|
19227
19458
|
const graphSpec = normalized;
|
|
19459
|
+
for (const [name, ch] of [
|
|
19460
|
+
["nodeColor", graphSpec.encoding.nodeColor],
|
|
19461
|
+
["edgeColor", graphSpec.encoding.edgeColor]
|
|
19462
|
+
]) {
|
|
19463
|
+
if (ch?.sort != null && ch.type === "quantitative") {
|
|
19464
|
+
options.onWarn?.(
|
|
19465
|
+
`encoding.${name}.sort is ignored for a quantitative field (sort only orders categorical domains).`
|
|
19466
|
+
);
|
|
19467
|
+
}
|
|
19468
|
+
}
|
|
19228
19469
|
const rawWatermark = spec.watermark;
|
|
19229
19470
|
const watermark = rawWatermark !== void 0 ? graphSpec.watermark : options.watermark ?? true;
|
|
19230
19471
|
const mergedThemeConfig = options.theme ? { ...graphSpec.theme, ...options.theme } : graphSpec.theme;
|
|
@@ -19250,13 +19491,30 @@ function compileGraph(spec, options) {
|
|
|
19250
19491
|
}
|
|
19251
19492
|
const compiledEdges = resolveEdgeVisuals(graphSpec.edges, graphSpec.encoding, theme);
|
|
19252
19493
|
const useCommunitiesForLegend = hasCommunities && !hasNodeColorEncoding;
|
|
19494
|
+
const nodeColorField = graphSpec.encoding.nodeColor?.field;
|
|
19495
|
+
const legendOrder = nodeColorField ? resolveCategoricalDomain(
|
|
19496
|
+
compiledNodes.map((n) => String(n.data[nodeColorField] ?? n.label ?? n.id)),
|
|
19497
|
+
graphSpec.encoding.nodeColor?.sort,
|
|
19498
|
+
graphSpec.encoding.nodeColor?.scale?.domain
|
|
19499
|
+
) : void 0;
|
|
19253
19500
|
const legend = buildGraphLegend(
|
|
19254
19501
|
compiledNodes,
|
|
19255
19502
|
communityColorMap,
|
|
19256
19503
|
useCommunitiesForLegend,
|
|
19257
19504
|
theme,
|
|
19258
|
-
|
|
19505
|
+
nodeColorField,
|
|
19506
|
+
legendOrder
|
|
19259
19507
|
);
|
|
19508
|
+
const edgeLegend = buildEdgeLegend(compiledEdges, graphSpec.encoding);
|
|
19509
|
+
const legendField = nodeColorField ?? (useCommunitiesForLegend ? clusteringField ?? null : null);
|
|
19510
|
+
let initialHighlight;
|
|
19511
|
+
if (nodeColorField && graphSpec.encoding.nodeColor?.highlight != null) {
|
|
19512
|
+
const raw = graphSpec.encoding.nodeColor.highlight;
|
|
19513
|
+
const requested = Array.isArray(raw) ? raw : [raw];
|
|
19514
|
+
const available = new Set(legendOrder ?? []);
|
|
19515
|
+
const values = requested.filter((v) => available.has(v));
|
|
19516
|
+
if (values.length > 0) initialHighlight = { field: nodeColorField, values };
|
|
19517
|
+
}
|
|
19260
19518
|
const tooltipDescriptors = buildGraphTooltips(compiledNodes);
|
|
19261
19519
|
const communityCount = communityColorMap.size;
|
|
19262
19520
|
const altParts = [
|
|
@@ -19273,17 +19531,26 @@ function compileGraph(spec, options) {
|
|
|
19273
19531
|
};
|
|
19274
19532
|
const collisionPadding = graphSpec.layout.collisionPadding ?? 2;
|
|
19275
19533
|
const maxRadius = compiledNodes.length > 0 ? Math.max(...compiledNodes.map((n) => n.radius)) : DEFAULT_COLLISION_PADDING;
|
|
19534
|
+
const energyPreset = graphSpec.layout.energy ? ENERGY_PRESETS[graphSpec.layout.energy] : ENERGY_PRESETS.balanced;
|
|
19535
|
+
const settlePreset = graphSpec.layout.settle ? SETTLE_PRESETS[graphSpec.layout.settle] : SETTLE_PRESETS.balanced;
|
|
19536
|
+
const warmupRaw = graphSpec.layout.warmup;
|
|
19537
|
+
const warmupTicks = warmupRaw === false ? 0 : typeof warmupRaw === "number" ? Math.max(0, Math.floor(warmupRaw)) : DEFAULT_WARMUP_TICKS;
|
|
19276
19538
|
const simulationConfig = {
|
|
19277
|
-
chargeStrength: graphSpec.layout.chargeStrength ??
|
|
19539
|
+
chargeStrength: graphSpec.layout.chargeStrength ?? energyPreset.chargeStrength,
|
|
19278
19540
|
linkDistance: graphSpec.layout.linkDistance ?? 30,
|
|
19279
19541
|
clustering: clusteringField ? { field: clusteringField, strength: 0.5 } : null,
|
|
19280
|
-
alphaDecay:
|
|
19281
|
-
velocityDecay:
|
|
19542
|
+
alphaDecay: settlePreset,
|
|
19543
|
+
velocityDecay: energyPreset.velocityDecay,
|
|
19282
19544
|
collisionRadius: maxRadius + collisionPadding,
|
|
19283
19545
|
collisionPadding,
|
|
19284
19546
|
linkStrength: graphSpec.layout.linkStrength,
|
|
19285
|
-
centerForce: graphSpec.layout.centerForce
|
|
19547
|
+
centerForce: graphSpec.layout.centerForce,
|
|
19548
|
+
seed: graphSpec.layout.seed,
|
|
19549
|
+
warmupTicks,
|
|
19550
|
+
warmupBudgetMs: DEFAULT_WARMUP_BUDGET_MS
|
|
19286
19551
|
};
|
|
19552
|
+
const animation = resolveGraphAnimation(graphSpec.animation);
|
|
19553
|
+
const interaction = resolveGraphInteraction(graphSpec.interaction);
|
|
19287
19554
|
const chrome = computeChrome4(
|
|
19288
19555
|
{
|
|
19289
19556
|
title: graphSpec.chrome.title,
|
|
@@ -19312,7 +19579,12 @@ function compileGraph(spec, options) {
|
|
|
19312
19579
|
height: options.height
|
|
19313
19580
|
},
|
|
19314
19581
|
simulationConfig,
|
|
19315
|
-
watermark
|
|
19582
|
+
watermark,
|
|
19583
|
+
animation,
|
|
19584
|
+
interaction,
|
|
19585
|
+
legendField,
|
|
19586
|
+
initialHighlight,
|
|
19587
|
+
edgeLegend
|
|
19316
19588
|
};
|
|
19317
19589
|
}
|
|
19318
19590
|
var DEFAULT_COLLISION_PADDING = 5;
|
|
@@ -23739,6 +24011,7 @@ export {
|
|
|
23739
24011
|
EXIT_DEFAULTS,
|
|
23740
24012
|
UPDATE_DEFAULTS,
|
|
23741
24013
|
buildAreaPath,
|
|
24014
|
+
buildEdgeTooltip,
|
|
23742
24015
|
buildLinePath,
|
|
23743
24016
|
clampStaggerDelay,
|
|
23744
24017
|
clearRenderers,
|