@opendata-ai/openchart-engine 8.0.0-rc.3 → 8.0.0-rc.4
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 +89 -46
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/compiler/validate.ts +30 -15
- package/src/geo/compile-map.ts +87 -50
package/dist/index.js
CHANGED
|
@@ -11806,21 +11806,25 @@ function validateMapSpec(spec, errors) {
|
|
|
11806
11806
|
}
|
|
11807
11807
|
const encoding = spec.encoding;
|
|
11808
11808
|
const hasPoints = spec.points && typeof spec.points === "object" && !Array.isArray(spec.points);
|
|
11809
|
+
const specData = spec.data;
|
|
11810
|
+
const isBasemapOnly = hasPoints && (!specData || specData.length === 0);
|
|
11809
11811
|
const keyCh = encoding.key;
|
|
11810
|
-
if (!
|
|
11811
|
-
|
|
11812
|
-
|
|
11813
|
-
|
|
11814
|
-
|
|
11815
|
-
|
|
11816
|
-
|
|
11817
|
-
|
|
11818
|
-
|
|
11819
|
-
|
|
11820
|
-
|
|
11821
|
-
|
|
11822
|
-
|
|
11823
|
-
|
|
11812
|
+
if (!isBasemapOnly) {
|
|
11813
|
+
if (!keyCh || typeof keyCh !== "object") {
|
|
11814
|
+
errors.push({
|
|
11815
|
+
message: 'Spec error: map encoding requires "key" channel',
|
|
11816
|
+
path: "encoding.key",
|
|
11817
|
+
code: "MISSING_FIELD",
|
|
11818
|
+
suggestion: 'Add encoding.key, e.g. key: { field: "id", type: "nominal" }'
|
|
11819
|
+
});
|
|
11820
|
+
} else if (!keyCh.field || typeof keyCh.field !== "string") {
|
|
11821
|
+
errors.push({
|
|
11822
|
+
message: 'Spec error: encoding.key must have a "field" string',
|
|
11823
|
+
path: "encoding.key.field",
|
|
11824
|
+
code: "MISSING_FIELD",
|
|
11825
|
+
suggestion: 'Add a field name, e.g. key: { field: "id" }'
|
|
11826
|
+
});
|
|
11827
|
+
}
|
|
11824
11828
|
}
|
|
11825
11829
|
const colorCh = encoding.color;
|
|
11826
11830
|
if (!hasPoints) {
|
|
@@ -11875,6 +11879,17 @@ function validateMapSpec(spec, errors) {
|
|
|
11875
11879
|
});
|
|
11876
11880
|
}
|
|
11877
11881
|
}
|
|
11882
|
+
for (const ch of ["size", "color", "key"]) {
|
|
11883
|
+
const enc = points[ch];
|
|
11884
|
+
if (enc && typeof enc === "object" && (!enc.field || typeof enc.field !== "string")) {
|
|
11885
|
+
errors.push({
|
|
11886
|
+
message: `Spec error: points.${ch} must have a "field" string when provided`,
|
|
11887
|
+
path: `points.${ch}.field`,
|
|
11888
|
+
code: "MISSING_FIELD",
|
|
11889
|
+
suggestion: `Add a field name, e.g. ${ch}: { field: "myField" }`
|
|
11890
|
+
});
|
|
11891
|
+
}
|
|
11892
|
+
}
|
|
11878
11893
|
}
|
|
11879
11894
|
}
|
|
11880
11895
|
function validateBarListSpec(spec, errors) {
|
|
@@ -17882,18 +17897,22 @@ function compileMap(spec, options) {
|
|
|
17882
17897
|
}
|
|
17883
17898
|
}
|
|
17884
17899
|
}
|
|
17885
|
-
|
|
17886
|
-
|
|
17887
|
-
|
|
17888
|
-
|
|
17889
|
-
|
|
17890
|
-
|
|
17891
|
-
|
|
17892
|
-
|
|
17893
|
-
|
|
17894
|
-
|
|
17895
|
-
|
|
17896
|
-
|
|
17900
|
+
let joined = /* @__PURE__ */ new Map();
|
|
17901
|
+
if (mapSpec.encoding.key) {
|
|
17902
|
+
const featureObjs = geoFeatures.map((f) => ({
|
|
17903
|
+
id: f.id,
|
|
17904
|
+
properties: f.properties ?? {}
|
|
17905
|
+
}));
|
|
17906
|
+
const joinResult = joinDataToFeatures(
|
|
17907
|
+
featureObjs,
|
|
17908
|
+
mapSpec.data,
|
|
17909
|
+
mapSpec.encoding.key.field,
|
|
17910
|
+
mapSpec.geo.idField
|
|
17911
|
+
);
|
|
17912
|
+
joined = joinResult.joined;
|
|
17913
|
+
const filteredJoinWarnings = mapSpec.data.length === 0 && mapSpec.points ? joinResult.warnings.filter((w) => w.code !== "UNMATCHED_FEATURES") : joinResult.warnings;
|
|
17914
|
+
compileWarnings.push(...filteredJoinWarnings);
|
|
17915
|
+
}
|
|
17897
17916
|
const droppedFeatures = [];
|
|
17898
17917
|
const formatter = buildD3Formatter2(mapSpec.valueFormat) ?? formatNumber3;
|
|
17899
17918
|
const neutralFill = isDarkMode ? "#2a2a2a" : "#e8e8e8";
|
|
@@ -17965,6 +17984,7 @@ function compileMap(spec, options) {
|
|
|
17965
17984
|
pathGen,
|
|
17966
17985
|
joined,
|
|
17967
17986
|
colorField: colorEncoding.field,
|
|
17987
|
+
colorScale: colorEncoding.scale,
|
|
17968
17988
|
isDarkMode: !!isDarkMode,
|
|
17969
17989
|
neutralFill,
|
|
17970
17990
|
neutralStroke,
|
|
@@ -18032,7 +18052,14 @@ function compileMap(spec, options) {
|
|
|
18032
18052
|
};
|
|
18033
18053
|
}
|
|
18034
18054
|
} else {
|
|
18035
|
-
const
|
|
18055
|
+
const explicitDomain = pts.color.scale?.domain;
|
|
18056
|
+
const explicitRange = pts.color.scale?.range;
|
|
18057
|
+
if (explicitDomain && Array.isArray(explicitDomain)) {
|
|
18058
|
+
for (const cat of explicitDomain) {
|
|
18059
|
+
pointCategories.push(String(cat));
|
|
18060
|
+
}
|
|
18061
|
+
}
|
|
18062
|
+
const seen = new Set(pointCategories);
|
|
18036
18063
|
for (const row of pts.data) {
|
|
18037
18064
|
const raw = row[pts.color.field];
|
|
18038
18065
|
if (raw != null) {
|
|
@@ -18040,21 +18067,23 @@ function compileMap(spec, options) {
|
|
|
18040
18067
|
if (!seen.has(cat)) {
|
|
18041
18068
|
seen.add(cat);
|
|
18042
18069
|
pointCategories.push(cat);
|
|
18043
|
-
pointCategoryColors.set(
|
|
18044
|
-
cat,
|
|
18045
|
-
CATEGORICAL_PALETTE[(pointCategories.length - 1) % CATEGORICAL_PALETTE.length]
|
|
18046
|
-
);
|
|
18047
18070
|
}
|
|
18048
18071
|
}
|
|
18049
18072
|
}
|
|
18073
|
+
const colorSource = explicitRange && Array.isArray(explicitRange) ? explicitRange : CATEGORICAL_PALETTE;
|
|
18074
|
+
for (let i = 0; i < pointCategories.length; i++) {
|
|
18075
|
+
pointCategoryColors.set(pointCategories[i], colorSource[i % colorSource.length]);
|
|
18076
|
+
}
|
|
18050
18077
|
pointColorScale = (val) => {
|
|
18051
|
-
if (val == null) return
|
|
18052
|
-
return pointCategoryColors.get(String(val)) ??
|
|
18078
|
+
if (val == null) return colorSource[0];
|
|
18079
|
+
return pointCategoryColors.get(String(val)) ?? colorSource[0];
|
|
18053
18080
|
};
|
|
18054
18081
|
}
|
|
18055
18082
|
}
|
|
18056
18083
|
const defaultFill = CATEGORICAL_PALETTE[0];
|
|
18084
|
+
const pointOpacity = pts.opacity ?? 0.65;
|
|
18057
18085
|
let animIndex = 0;
|
|
18086
|
+
const droppedPoints = [];
|
|
18058
18087
|
for (let i = 0; i < pts.data.length; i++) {
|
|
18059
18088
|
const row = pts.data[i];
|
|
18060
18089
|
const lon = Number(row[lonField]);
|
|
@@ -18062,11 +18091,7 @@ function compileMap(spec, options) {
|
|
|
18062
18091
|
if (!Number.isFinite(lon) || !Number.isFinite(lat)) continue;
|
|
18063
18092
|
const projected = projection2([lon, lat]);
|
|
18064
18093
|
if (!projected) {
|
|
18065
|
-
|
|
18066
|
-
code: "POINT_NULL_PROJECTION",
|
|
18067
|
-
message: `Point at [${lon}, ${lat}] projects to null and was dropped.`,
|
|
18068
|
-
context: { lon, lat }
|
|
18069
|
-
});
|
|
18094
|
+
droppedPoints.push([lon, lat]);
|
|
18070
18095
|
continue;
|
|
18071
18096
|
}
|
|
18072
18097
|
const r = sizeScale ? sizeScale.scale(Number(row[sizeScale.field])) : 5;
|
|
@@ -18081,12 +18106,21 @@ function compileMap(spec, options) {
|
|
|
18081
18106
|
fill,
|
|
18082
18107
|
stroke,
|
|
18083
18108
|
strokeWidth: 1,
|
|
18109
|
+
fillOpacity: pointOpacity,
|
|
18084
18110
|
key,
|
|
18085
18111
|
data: row,
|
|
18086
18112
|
aria: { role: "img", label: key },
|
|
18087
18113
|
animationIndex: animIndex++
|
|
18088
18114
|
});
|
|
18089
18115
|
}
|
|
18116
|
+
if (droppedPoints.length > 0) {
|
|
18117
|
+
const sample = droppedPoints.slice(0, 3).map(([lo, la]) => `[${lo}, ${la}]`).join(", ");
|
|
18118
|
+
compileWarnings.push({
|
|
18119
|
+
code: "POINT_NULL_PROJECTION",
|
|
18120
|
+
message: `${droppedPoints.length} point(s) project to null and were dropped (e.g. ${sample}). This is common with albersUsa which excludes territories. Consider using projection: "mercator" or "equalEarth".`,
|
|
18121
|
+
context: { count: droppedPoints.length, samples: droppedPoints.slice(0, 5) }
|
|
18122
|
+
});
|
|
18123
|
+
}
|
|
18090
18124
|
if (pts.color && pts.color.type !== "quantitative" && pointCategories.length > 0 && showLegend) {
|
|
18091
18125
|
const labelStyle = {
|
|
18092
18126
|
fontFamily: theme.fonts.family,
|
|
@@ -18100,10 +18134,10 @@ function compileMap(spec, options) {
|
|
|
18100
18134
|
color: pointCategoryColors.get(cat),
|
|
18101
18135
|
shape: "circle"
|
|
18102
18136
|
}));
|
|
18103
|
-
const
|
|
18104
|
-
const choroplethLegendGap =
|
|
18137
|
+
const bottomChoroplethHeight = legendPosition === "bottom" ? continuousLegend?.bounds.height ?? categoricalLegend?.bounds.height ?? 0 : 0;
|
|
18138
|
+
const choroplethLegendGap = bottomChoroplethHeight > 0 ? 8 : 0;
|
|
18105
18139
|
const legendX = fullArea.x;
|
|
18106
|
-
const legendY = fullArea.y + mapAreaHeight + legendReserveGap +
|
|
18140
|
+
const legendY = fullArea.y + mapAreaHeight + legendReserveGap + bottomChoroplethHeight + choroplethLegendGap;
|
|
18107
18141
|
const legendWidth = fullArea.width;
|
|
18108
18142
|
const swatchSize = 10;
|
|
18109
18143
|
const swatchGap = 6;
|
|
@@ -18148,10 +18182,10 @@ function compileMap(spec, options) {
|
|
|
18148
18182
|
lineHeight: 1.3,
|
|
18149
18183
|
fontVariant: "tabular-nums"
|
|
18150
18184
|
};
|
|
18151
|
-
const
|
|
18152
|
-
const choroplethLegendGap =
|
|
18185
|
+
const bottomChoroplethH = legendPosition === "bottom" ? continuousLegend?.bounds.height ?? categoricalLegend?.bounds.height ?? 0 : 0;
|
|
18186
|
+
const choroplethLegendGap = bottomChoroplethH > 0 ? 8 : 0;
|
|
18153
18187
|
const legendX = fullArea.x;
|
|
18154
|
-
const legendY = fullArea.y + mapAreaHeight + legendReserveGap +
|
|
18188
|
+
const legendY = fullArea.y + mapAreaHeight + legendReserveGap + bottomChoroplethH + choroplethLegendGap;
|
|
18155
18189
|
const ptLabelRowHeight = Math.ceil(theme.fonts.sizes.small * 1.3);
|
|
18156
18190
|
const ptLegendHeight = ptContinuousContent.barHeight + CONTINUOUS_LABEL_GAP + ptLabelRowHeight;
|
|
18157
18191
|
const bounds = {
|
|
@@ -18430,6 +18464,7 @@ function buildCategoricalMarks(opts) {
|
|
|
18430
18464
|
pathGen,
|
|
18431
18465
|
joined,
|
|
18432
18466
|
colorField,
|
|
18467
|
+
colorScale: scaleConfig,
|
|
18433
18468
|
isDarkMode,
|
|
18434
18469
|
neutralFill,
|
|
18435
18470
|
neutralStroke,
|
|
@@ -18441,7 +18476,13 @@ function buildCategoricalMarks(opts) {
|
|
|
18441
18476
|
droppedFeatures
|
|
18442
18477
|
} = opts;
|
|
18443
18478
|
const categories = [];
|
|
18444
|
-
const
|
|
18479
|
+
const explicitDomain = scaleConfig?.domain;
|
|
18480
|
+
if (explicitDomain && Array.isArray(explicitDomain)) {
|
|
18481
|
+
for (const cat of explicitDomain) {
|
|
18482
|
+
categories.push(String(cat));
|
|
18483
|
+
}
|
|
18484
|
+
}
|
|
18485
|
+
const seen = new Set(categories);
|
|
18445
18486
|
for (const row of joined.values()) {
|
|
18446
18487
|
const raw = row[colorField];
|
|
18447
18488
|
if (raw != null) {
|
|
@@ -18452,9 +18493,11 @@ function buildCategoricalMarks(opts) {
|
|
|
18452
18493
|
}
|
|
18453
18494
|
}
|
|
18454
18495
|
}
|
|
18496
|
+
const explicitRange = scaleConfig?.range;
|
|
18497
|
+
const colorSource = explicitRange && Array.isArray(explicitRange) ? explicitRange : CATEGORICAL_PALETTE;
|
|
18455
18498
|
const categoryColors = /* @__PURE__ */ new Map();
|
|
18456
18499
|
for (let i = 0; i < categories.length; i++) {
|
|
18457
|
-
categoryColors.set(categories[i],
|
|
18500
|
+
categoryColors.set(categories[i], colorSource[i % colorSource.length]);
|
|
18458
18501
|
}
|
|
18459
18502
|
const marks = [];
|
|
18460
18503
|
let animIndex = 0;
|