@opendata-ai/openchart-engine 6.15.0 → 6.16.0
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 +57 -27
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/legend.test.ts +32 -0
- package/src/annotations/resolve-text.ts +1 -0
- package/src/charts/bar/compute.ts +28 -5
- package/src/compile.ts +18 -9
- package/src/compiler/validate.ts +1 -1
- package/src/layout/dimensions.ts +1 -1
- package/src/layout/scales.ts +10 -2
- package/src/legend/compute.ts +18 -7
- package/src/tables/__tests__/category-colors.test.ts +50 -8
- package/src/tables/category-colors.ts +9 -3
- package/src/transforms/__tests__/predicates.test.ts +17 -0
- package/src/transforms/predicates.ts +7 -4
package/dist/index.js
CHANGED
|
@@ -596,7 +596,7 @@ function evaluateFieldPredicate(datum, pred) {
|
|
|
596
596
|
return pred.valid ? isValid : !isValid;
|
|
597
597
|
}
|
|
598
598
|
if (pred.equal !== void 0) {
|
|
599
|
-
return value2
|
|
599
|
+
return value2 == pred.equal;
|
|
600
600
|
}
|
|
601
601
|
const numValue = Number(value2);
|
|
602
602
|
if (pred.lt !== void 0) {
|
|
@@ -616,7 +616,7 @@ function evaluateFieldPredicate(datum, pred) {
|
|
|
616
616
|
return numValue >= min4 && numValue <= max4;
|
|
617
617
|
}
|
|
618
618
|
if (pred.oneOf !== void 0) {
|
|
619
|
-
return pred.oneOf.
|
|
619
|
+
return pred.oneOf.some((v) => v == value2);
|
|
620
620
|
}
|
|
621
621
|
return true;
|
|
622
622
|
}
|
|
@@ -727,6 +727,13 @@ function getSequentialColor(scales, value2, fallback = DEFAULT_COLOR) {
|
|
|
727
727
|
}
|
|
728
728
|
|
|
729
729
|
// src/charts/bar/compute.ts
|
|
730
|
+
function orientGradientForHorizontalBar(grad) {
|
|
731
|
+
if (grad.gradient !== "linear") return grad;
|
|
732
|
+
const lg = grad;
|
|
733
|
+
const isDefaultVertical = (lg.x1 === void 0 || lg.x1 === 0) && (lg.y1 === void 0 || lg.y1 === 0) && (lg.x2 === void 0 || lg.x2 === 0) && (lg.y2 === void 0 || lg.y2 === 1);
|
|
734
|
+
if (!isDefaultVertical) return grad;
|
|
735
|
+
return { ...lg, x1: 0, y1: 0, x2: 1, y2: 0 };
|
|
736
|
+
}
|
|
730
737
|
var MIN_BAR_WIDTH = 1;
|
|
731
738
|
function formatBarValue(value2) {
|
|
732
739
|
if (Math.abs(value2) >= 1e3) return abbreviateNumber(value2);
|
|
@@ -837,7 +844,7 @@ function computeStackedBars(data, valueField, categoryField, colorField, xScale,
|
|
|
837
844
|
y: bandY,
|
|
838
845
|
width: barWidth,
|
|
839
846
|
height: bandwidth,
|
|
840
|
-
fill: color2,
|
|
847
|
+
fill: isGradientDef(color2) ? orientGradientForHorizontalBar(color2) : color2,
|
|
841
848
|
cornerRadius: 0,
|
|
842
849
|
data: row,
|
|
843
850
|
aria,
|
|
@@ -884,7 +891,7 @@ function computeGroupedBars(data, valueField, categoryField, colorField, xScale,
|
|
|
884
891
|
y: subY,
|
|
885
892
|
width: barWidth,
|
|
886
893
|
height: subBandHeight,
|
|
887
|
-
fill: color2,
|
|
894
|
+
fill: isGradientDef(color2) ? orientGradientForHorizontalBar(color2) : color2,
|
|
888
895
|
cornerRadius: 2,
|
|
889
896
|
data: row,
|
|
890
897
|
aria,
|
|
@@ -915,7 +922,7 @@ function computeColoredBars(data, valueField, categoryField, colorField, xScale,
|
|
|
915
922
|
y: bandY,
|
|
916
923
|
width: barWidth,
|
|
917
924
|
height: bandwidth,
|
|
918
|
-
fill: color2,
|
|
925
|
+
fill: isGradientDef(color2) ? orientGradientForHorizontalBar(color2) : color2,
|
|
919
926
|
cornerRadius: 2,
|
|
920
927
|
data: row,
|
|
921
928
|
aria,
|
|
@@ -956,7 +963,7 @@ function computeSimpleBars(data, valueField, categoryField, xScale, yScale, band
|
|
|
956
963
|
y: bandY,
|
|
957
964
|
width: barWidth,
|
|
958
965
|
height: bandwidth,
|
|
959
|
-
fill: color2,
|
|
966
|
+
fill: isGradientDef(color2) ? orientGradientForHorizontalBar(color2) : color2,
|
|
960
967
|
cornerRadius: 2,
|
|
961
968
|
data: row,
|
|
962
969
|
aria,
|
|
@@ -6748,7 +6755,7 @@ function validateChartSpec(spec, errors) {
|
|
|
6748
6755
|
message: `Spec error: encoding.${channel} must have a "field" string`,
|
|
6749
6756
|
path: `encoding.${channel}.field`,
|
|
6750
6757
|
code: "MISSING_FIELD",
|
|
6751
|
-
suggestion: `
|
|
6758
|
+
suggestion: `For constant colors, use mark.fill (e.g., mark: { type: "bar", fill: "#1b7fa3" }) instead of encoding.${channel}. Encoding channels require a data field: ${availableColumns}`
|
|
6752
6759
|
});
|
|
6753
6760
|
continue;
|
|
6754
6761
|
}
|
|
@@ -8116,7 +8123,7 @@ function computeDimensions(spec, options, legendLayout, theme, strategy, waterma
|
|
|
8116
8123
|
}
|
|
8117
8124
|
}
|
|
8118
8125
|
if (maxLabelWidth > 0) {
|
|
8119
|
-
margins.right = Math.max(margins.right, padding + maxLabelWidth +
|
|
8126
|
+
margins.right = Math.max(margins.right, padding + maxLabelWidth + 8);
|
|
8120
8127
|
}
|
|
8121
8128
|
}
|
|
8122
8129
|
}
|
|
@@ -8492,14 +8499,18 @@ function buildPointScale(channel, data, rangeStart, rangeEnd) {
|
|
|
8492
8499
|
function buildOrdinalColorScale(channel, data, palette) {
|
|
8493
8500
|
const explicitDomain = channel.scale?.domain;
|
|
8494
8501
|
const values = explicitDomain ? explicitDomain.map(String) : applyCategoricalSort(uniqueStrings(fieldValues(data, channel.field)), channel.sort);
|
|
8495
|
-
const
|
|
8502
|
+
const explicitRange = channel.scale?.range;
|
|
8503
|
+
const colors = explicitRange ?? palette;
|
|
8504
|
+
const scale = ordinal().domain(values).range(colors);
|
|
8496
8505
|
return { scale, type: "ordinal", channel };
|
|
8497
8506
|
}
|
|
8498
8507
|
function buildSequentialColorScale(channel, data, palette) {
|
|
8499
8508
|
const values = parseNumbers(fieldValues(data, channel.field));
|
|
8500
8509
|
const domainMin = min2(values) ?? 0;
|
|
8501
8510
|
const domainMax = max2(values) ?? 1;
|
|
8502
|
-
const
|
|
8511
|
+
const explicitRange = channel.scale?.range;
|
|
8512
|
+
const colors = explicitRange ?? palette;
|
|
8513
|
+
const scale = linear2().domain([domainMin, domainMax]).range([colors[0], colors[colors.length - 1]]).clamp(true);
|
|
8503
8514
|
return { scale, type: "sequential", channel };
|
|
8504
8515
|
}
|
|
8505
8516
|
function buildPositionalScale(channel, data, rangeStart, rangeEnd, chartType, axis) {
|
|
@@ -8723,14 +8734,23 @@ function extractColorEntries(spec, theme) {
|
|
|
8723
8734
|
if ("condition" in colorEnc) return [];
|
|
8724
8735
|
if (colorEnc.type === "quantitative") return [];
|
|
8725
8736
|
const uniqueValues = [...new Set(spec.data.map((d) => String(d[colorEnc.field])))];
|
|
8726
|
-
const
|
|
8737
|
+
const explicitDomain = colorEnc.scale?.domain;
|
|
8738
|
+
const explicitRange = colorEnc.scale?.range;
|
|
8739
|
+
const palette = explicitRange ?? theme.colors.categorical;
|
|
8727
8740
|
const shape = swatchShapeForType(spec.markType);
|
|
8728
|
-
return uniqueValues.map((value2, i) =>
|
|
8729
|
-
|
|
8730
|
-
|
|
8731
|
-
|
|
8732
|
-
|
|
8733
|
-
|
|
8741
|
+
return uniqueValues.map((value2, i) => {
|
|
8742
|
+
let colorIndex = i;
|
|
8743
|
+
if (explicitDomain && explicitRange) {
|
|
8744
|
+
const domainIdx = explicitDomain.indexOf(value2);
|
|
8745
|
+
if (domainIdx >= 0) colorIndex = domainIdx;
|
|
8746
|
+
}
|
|
8747
|
+
return {
|
|
8748
|
+
label: value2,
|
|
8749
|
+
color: palette[colorIndex % palette.length],
|
|
8750
|
+
shape,
|
|
8751
|
+
active: true
|
|
8752
|
+
};
|
|
8753
|
+
});
|
|
8734
8754
|
}
|
|
8735
8755
|
function entriesThatFit(entries, maxWidth, maxRows, labelStyle) {
|
|
8736
8756
|
let row = 1;
|
|
@@ -9908,8 +9928,13 @@ function computeCategoryColors(data, column, theme, darkMode) {
|
|
|
9908
9928
|
if (raw == null) continue;
|
|
9909
9929
|
const key = String(raw);
|
|
9910
9930
|
let bg;
|
|
9911
|
-
|
|
9931
|
+
let isExplicit = false;
|
|
9932
|
+
if (explicitMap[key] != null) {
|
|
9933
|
+
if (explicitMap[key] === "transparent" || explicitMap[key] === "none") {
|
|
9934
|
+
continue;
|
|
9935
|
+
}
|
|
9912
9936
|
bg = explicitMap[key];
|
|
9937
|
+
isExplicit = true;
|
|
9913
9938
|
} else if (autoAssigned.has(key)) {
|
|
9914
9939
|
bg = autoAssigned.get(key);
|
|
9915
9940
|
} else {
|
|
@@ -9917,7 +9942,7 @@ function computeCategoryColors(data, column, theme, darkMode) {
|
|
|
9917
9942
|
nextPaletteIndex++;
|
|
9918
9943
|
autoAssigned.set(key, bg);
|
|
9919
9944
|
}
|
|
9920
|
-
if (darkMode) {
|
|
9945
|
+
if (darkMode && !isExplicit) {
|
|
9921
9946
|
bg = adaptColorForDarkMode(bg, lightBg, darkBg);
|
|
9922
9947
|
}
|
|
9923
9948
|
const textColor = accessibleTextColor(bg);
|
|
@@ -11132,16 +11157,21 @@ function compileChart(spec, options) {
|
|
|
11132
11157
|
const renderSpec = renderData !== chartSpec.data ? { ...chartSpec, data: renderData } : chartSpec;
|
|
11133
11158
|
const scales = computeScales(renderSpec, chartArea, renderSpec.data);
|
|
11134
11159
|
if (scales.color) {
|
|
11160
|
+
const hasExplicitRange = !!(renderSpec.encoding.color && "field" in renderSpec.encoding.color && renderSpec.encoding.color.scale?.range?.length);
|
|
11135
11161
|
if (scales.color.type === "sequential") {
|
|
11136
|
-
|
|
11137
|
-
|
|
11138
|
-
|
|
11139
|
-
|
|
11140
|
-
|
|
11162
|
+
if (!hasExplicitRange) {
|
|
11163
|
+
const seqStops = Object.values(theme.colors.sequential)[0] ?? theme.colors.categorical;
|
|
11164
|
+
scales.color.scale.range([
|
|
11165
|
+
seqStops[0],
|
|
11166
|
+
seqStops[seqStops.length - 1]
|
|
11167
|
+
]);
|
|
11168
|
+
}
|
|
11141
11169
|
} else {
|
|
11142
|
-
|
|
11143
|
-
|
|
11144
|
-
|
|
11170
|
+
if (!hasExplicitRange) {
|
|
11171
|
+
scales.color.scale.range(
|
|
11172
|
+
theme.colors.categorical
|
|
11173
|
+
);
|
|
11174
|
+
}
|
|
11145
11175
|
}
|
|
11146
11176
|
}
|
|
11147
11177
|
scales.defaultColor = chartSpec.markDef.fill ?? theme.colors.categorical[0];
|