@opendata-ai/openchart-engine 6.1.5 → 6.2.1
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.js +46 -16
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/graphs/__tests__/compile-graph.test.ts +56 -0
- package/src/graphs/compile-graph.ts +28 -13
- package/src/graphs/encoding.ts +44 -14
- package/src/layout/axes.ts +2 -1
- package/src/layout/dimensions.ts +18 -1
package/dist/index.js
CHANGED
|
@@ -6555,20 +6555,24 @@ function resolveNodeVisuals(nodes, encoding, edges, theme, nodeOverrides) {
|
|
|
6555
6555
|
if (encoding.nodeColor?.field) {
|
|
6556
6556
|
const field = encoding.nodeColor.field;
|
|
6557
6557
|
const fieldType = encoding.nodeColor.type ?? "nominal";
|
|
6558
|
+
const scaleConfig = encoding.nodeColor.scale;
|
|
6558
6559
|
if (fieldType === "quantitative") {
|
|
6559
6560
|
const values = nodes.map((n) => Number(n[field])).filter((v) => Number.isFinite(v));
|
|
6560
6561
|
const colorMin = min2(values) ?? 0;
|
|
6561
6562
|
const colorMax = max2(values) ?? 1;
|
|
6562
6563
|
const seqPalettes = Object.values(theme.colors.sequential);
|
|
6563
6564
|
const palette = seqPalettes.length > 0 ? seqPalettes[0] : ["#ccc", "#333"];
|
|
6564
|
-
const
|
|
6565
|
+
const domain = scaleConfig?.domain && scaleConfig.domain.length === 2 ? scaleConfig.domain : [colorMin, colorMax];
|
|
6566
|
+
const range2 = scaleConfig?.range && scaleConfig.range.length >= 2 ? scaleConfig.range : [palette[0], palette[palette.length - 1]];
|
|
6567
|
+
const colorScale = linear2().domain(domain).range(range2);
|
|
6565
6568
|
colorFn = (node) => {
|
|
6566
6569
|
const val = Number(node[field]);
|
|
6567
6570
|
return Number.isFinite(val) ? colorScale(val) : theme.colors.categorical[0];
|
|
6568
6571
|
};
|
|
6569
6572
|
} else {
|
|
6570
|
-
const
|
|
6571
|
-
const
|
|
6573
|
+
const domain = scaleConfig?.domain && Array.isArray(scaleConfig.domain) ? scaleConfig.domain : [...new Set(nodes.map((n) => String(n[field] ?? "")))];
|
|
6574
|
+
const range2 = scaleConfig?.range && scaleConfig.range.length > 0 ? scaleConfig.range : theme.colors.categorical;
|
|
6575
|
+
const ordinalScale = ordinal().domain(domain).range(range2);
|
|
6572
6576
|
colorFn = (node) => ordinalScale(String(node[field] ?? ""));
|
|
6573
6577
|
}
|
|
6574
6578
|
}
|
|
@@ -6626,20 +6630,24 @@ function resolveEdgeVisuals(edges, encoding, theme) {
|
|
|
6626
6630
|
if (encoding.edgeColor?.field) {
|
|
6627
6631
|
const field = encoding.edgeColor.field;
|
|
6628
6632
|
const fieldType = encoding.edgeColor.type ?? "nominal";
|
|
6633
|
+
const scaleConfig = encoding.edgeColor.scale;
|
|
6629
6634
|
if (fieldType === "quantitative") {
|
|
6630
6635
|
const values = edges.map((e) => Number(e[field])).filter((v) => Number.isFinite(v));
|
|
6631
6636
|
const colorMin = min2(values) ?? 0;
|
|
6632
6637
|
const colorMax = max2(values) ?? 1;
|
|
6633
6638
|
const seqPalettes = Object.values(theme.colors.sequential);
|
|
6634
6639
|
const palette = seqPalettes.length > 0 ? seqPalettes[0] : ["#ccc", "#333"];
|
|
6635
|
-
const
|
|
6640
|
+
const domain = scaleConfig?.domain && scaleConfig.domain.length === 2 ? scaleConfig.domain : [colorMin, colorMax];
|
|
6641
|
+
const range2 = scaleConfig?.range && scaleConfig.range.length >= 2 ? scaleConfig.range : [palette[0], palette[palette.length - 1]];
|
|
6642
|
+
const colorScale = linear2().domain(domain).range(range2);
|
|
6636
6643
|
edgeColorFn = (edge) => {
|
|
6637
6644
|
const val = Number(edge[field]);
|
|
6638
6645
|
return Number.isFinite(val) ? colorScale(val) : hexWithOpacity(theme.colors.axis, 0.4);
|
|
6639
6646
|
};
|
|
6640
6647
|
} else {
|
|
6641
|
-
const
|
|
6642
|
-
const
|
|
6648
|
+
const domain = scaleConfig?.domain && Array.isArray(scaleConfig.domain) ? scaleConfig.domain : [...new Set(edges.map((e) => String(e[field] ?? "")))];
|
|
6649
|
+
const range2 = scaleConfig?.range && scaleConfig.range.length > 0 ? scaleConfig.range : theme.colors.categorical;
|
|
6650
|
+
const ordinalScale = ordinal().domain(domain).range(range2);
|
|
6643
6651
|
edgeColorFn = (edge) => ordinalScale(String(edge[field] ?? ""));
|
|
6644
6652
|
}
|
|
6645
6653
|
}
|
|
@@ -6713,7 +6721,7 @@ function applyCommunityColors(nodes, colorMap) {
|
|
|
6713
6721
|
var SWATCH_SIZE = 12;
|
|
6714
6722
|
var SWATCH_GAP = 6;
|
|
6715
6723
|
var ENTRY_GAP = 16;
|
|
6716
|
-
function buildGraphLegend(nodes, communityColorMap, hasCommunities, theme) {
|
|
6724
|
+
function buildGraphLegend(nodes, communityColorMap, hasCommunities, theme, nodeColorField) {
|
|
6717
6725
|
const labelStyle = {
|
|
6718
6726
|
fontFamily: theme.fonts.family,
|
|
6719
6727
|
fontSize: theme.fonts.sizes.small,
|
|
@@ -6730,16 +6738,17 @@ function buildGraphLegend(nodes, communityColorMap, hasCommunities, theme) {
|
|
|
6730
6738
|
active: true
|
|
6731
6739
|
}));
|
|
6732
6740
|
} else {
|
|
6733
|
-
const
|
|
6741
|
+
const categoryColors = /* @__PURE__ */ new Map();
|
|
6734
6742
|
for (const node of nodes) {
|
|
6735
|
-
|
|
6736
|
-
|
|
6743
|
+
const category = nodeColorField ? String(node.data[nodeColorField] ?? node.label ?? node.id) : node.label ?? node.id;
|
|
6744
|
+
if (!categoryColors.has(category)) {
|
|
6745
|
+
categoryColors.set(category, node.fill);
|
|
6737
6746
|
}
|
|
6738
6747
|
}
|
|
6739
|
-
if (
|
|
6748
|
+
if (categoryColors.size <= 1) {
|
|
6740
6749
|
entries = [];
|
|
6741
6750
|
} else {
|
|
6742
|
-
entries = [...
|
|
6751
|
+
entries = [...categoryColors.entries()].map(([label, color2]) => ({
|
|
6743
6752
|
label,
|
|
6744
6753
|
color: color2,
|
|
6745
6754
|
shape: "circle",
|
|
@@ -6806,13 +6815,21 @@ function compileGraph(spec, options) {
|
|
|
6806
6815
|
const clusteringField = graphSpec.layout.clustering?.field;
|
|
6807
6816
|
const hasCommunities = !!clusteringField;
|
|
6808
6817
|
assignCommunities(compiledNodes, clusteringField);
|
|
6818
|
+
const hasNodeColorEncoding = !!graphSpec.encoding.nodeColor?.field;
|
|
6809
6819
|
let communityColorMap = /* @__PURE__ */ new Map();
|
|
6810
|
-
if (hasCommunities) {
|
|
6820
|
+
if (hasCommunities && !hasNodeColorEncoding) {
|
|
6811
6821
|
communityColorMap = buildCommunityColorMap(compiledNodes, theme);
|
|
6812
6822
|
applyCommunityColors(compiledNodes, communityColorMap);
|
|
6813
6823
|
}
|
|
6814
6824
|
const compiledEdges = resolveEdgeVisuals(graphSpec.edges, graphSpec.encoding, theme);
|
|
6815
|
-
const
|
|
6825
|
+
const useCommunitiesForLegend = hasCommunities && !hasNodeColorEncoding;
|
|
6826
|
+
const legend = buildGraphLegend(
|
|
6827
|
+
compiledNodes,
|
|
6828
|
+
communityColorMap,
|
|
6829
|
+
useCommunitiesForLegend,
|
|
6830
|
+
theme,
|
|
6831
|
+
graphSpec.encoding.nodeColor?.field
|
|
6832
|
+
);
|
|
6816
6833
|
const tooltipDescriptors = buildGraphTooltips(compiledNodes);
|
|
6817
6834
|
const communityCount = communityColorMap.size;
|
|
6818
6835
|
const altParts = [
|
|
@@ -6987,7 +7004,8 @@ function formatTickLabel(value, resolvedScale) {
|
|
|
6987
7004
|
if (TEMPORAL_SCALE_TYPES.has(resolvedScale.type)) {
|
|
6988
7005
|
const temporalFmt = buildTemporalFormatter(formatStr);
|
|
6989
7006
|
if (temporalFmt) return temporalFmt(value);
|
|
6990
|
-
|
|
7007
|
+
const useUtc = resolvedScale.type === "utc";
|
|
7008
|
+
return formatDate(value, void 0, void 0, useUtc);
|
|
6991
7009
|
}
|
|
6992
7010
|
if (NUMERIC_SCALE_TYPES.has(resolvedScale.type)) {
|
|
6993
7011
|
const num = value;
|
|
@@ -7240,7 +7258,19 @@ function computeDimensions(spec, options, legendLayout, theme, strategy) {
|
|
|
7240
7258
|
for (const ann of spec.annotations) {
|
|
7241
7259
|
if (ann.type === "text" && String(ann.x) === maxXStr) {
|
|
7242
7260
|
const textWidth = estimateTextWidth8(ann.text, ann.fontSize ?? 11, ann.fontWeight ?? 600);
|
|
7243
|
-
|
|
7261
|
+
const dx = ann.offset?.dx ?? 0;
|
|
7262
|
+
const anchor = ann.anchor ?? "auto";
|
|
7263
|
+
const baseRightExtent = anchor === "left" ? textWidth : (
|
|
7264
|
+
// text is to the right of anchor
|
|
7265
|
+
anchor === "right" ? 0 : (
|
|
7266
|
+
// text is to the left of anchor
|
|
7267
|
+
textWidth / 2
|
|
7268
|
+
)
|
|
7269
|
+
);
|
|
7270
|
+
const rightOverflow = Math.max(0, baseRightExtent + dx);
|
|
7271
|
+
if (rightOverflow > 0) {
|
|
7272
|
+
margins.right = Math.max(margins.right, padding + rightOverflow + 12);
|
|
7273
|
+
}
|
|
7244
7274
|
}
|
|
7245
7275
|
}
|
|
7246
7276
|
}
|