@opendata-ai/openchart-engine 8.0.0-rc.4 → 8.0.0-rc.5
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 +56 -7
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/compiler/validate.ts +32 -5
- package/src/geo/__tests__/compile-map.test.ts +123 -0
- package/src/geo/compile-map.ts +45 -1
package/dist/index.js
CHANGED
|
@@ -11758,12 +11758,31 @@ function validateMapSpec(spec, errors) {
|
|
|
11758
11758
|
}
|
|
11759
11759
|
} else if (typeof focus === "object") {
|
|
11760
11760
|
const focusObj = focus;
|
|
11761
|
-
if (
|
|
11761
|
+
if ("points" in focusObj) {
|
|
11762
|
+
const pts = focusObj.points;
|
|
11763
|
+
const isSubset = typeof pts === "object" && pts !== null && typeof pts.field === "string" && "value" in pts;
|
|
11764
|
+
if (pts !== true && !isSubset) {
|
|
11765
|
+
errors.push({
|
|
11766
|
+
message: "Spec error: geo.focus.points must be true or { field: string, value: string | number }",
|
|
11767
|
+
path: "geo.focus.points",
|
|
11768
|
+
code: "INVALID_TYPE",
|
|
11769
|
+
suggestion: 'Use { points: true } to fit all points, or { points: { field: "rating", value: "F" } } to fit a subset.'
|
|
11770
|
+
});
|
|
11771
|
+
}
|
|
11772
|
+
if (focusObj.padding !== void 0 && typeof focusObj.padding !== "number") {
|
|
11773
|
+
errors.push({
|
|
11774
|
+
message: "Spec error: geo.focus.padding must be a number",
|
|
11775
|
+
path: "geo.focus.padding",
|
|
11776
|
+
code: "INVALID_TYPE",
|
|
11777
|
+
suggestion: "Provide padding as a number in map-local units, e.g. padding: 16"
|
|
11778
|
+
});
|
|
11779
|
+
}
|
|
11780
|
+
} else if (!("features" in focusObj) || focusObj.features == null) {
|
|
11762
11781
|
errors.push({
|
|
11763
|
-
message: 'Spec error: geo.focus object must have a "features" property',
|
|
11782
|
+
message: 'Spec error: geo.focus object must have a "features" or "points" property',
|
|
11764
11783
|
path: "geo.focus.features",
|
|
11765
11784
|
code: "MISSING_FIELD",
|
|
11766
|
-
suggestion: 'Provide feature id(s) to focus on, e.g.
|
|
11785
|
+
suggestion: 'Provide feature id(s) to focus on, e.g. { features: "36", padding: 32 }, or { points: true } to fit the point cluster.'
|
|
11767
11786
|
});
|
|
11768
11787
|
} else {
|
|
11769
11788
|
const feats = focusObj.features;
|
|
@@ -11797,10 +11816,10 @@ function validateMapSpec(spec, errors) {
|
|
|
11797
11816
|
}
|
|
11798
11817
|
} else {
|
|
11799
11818
|
errors.push({
|
|
11800
|
-
message: "Spec error: geo.focus must be a string, number, array of ids, or object with { features, padding? }",
|
|
11819
|
+
message: "Spec error: geo.focus must be a string, number, array of ids, or object with { features, padding? } or { points: true, padding? }",
|
|
11801
11820
|
path: "geo.focus",
|
|
11802
11821
|
code: "INVALID_TYPE",
|
|
11803
|
-
suggestion: 'Use a feature id, array of ids,
|
|
11822
|
+
suggestion: 'Use a feature id, array of ids, { features: "36", padding: 16 }, or { points: true }. Pass null to clear focus.'
|
|
11804
11823
|
});
|
|
11805
11824
|
}
|
|
11806
11825
|
}
|
|
@@ -18301,7 +18320,7 @@ function compileMap(spec, options) {
|
|
|
18301
18320
|
role: "img",
|
|
18302
18321
|
keyboardNavigable: featureMarks.length > 0 || pointMarks.length > 0
|
|
18303
18322
|
};
|
|
18304
|
-
const resolvedFocus = resolveFocus(mapSpec.geo.focus, featureMarks, compileWarnings);
|
|
18323
|
+
const resolvedFocus = resolveFocus(mapSpec.geo.focus, featureMarks, pointMarks, compileWarnings);
|
|
18305
18324
|
const resolvedAnimation = resolveAnimation(mapSpec.animation);
|
|
18306
18325
|
const mapAreaY = legendPosition === "top" && legendBlockHeight > 0 ? fullArea.y + legendBlockHeight + legendReserveGap : fullArea.y;
|
|
18307
18326
|
const mapArea = {
|
|
@@ -18584,8 +18603,38 @@ function buildCategoricalMarks(opts) {
|
|
|
18584
18603
|
}
|
|
18585
18604
|
return { marks, legend };
|
|
18586
18605
|
}
|
|
18587
|
-
function resolveFocus(focus, features, warnings) {
|
|
18606
|
+
function resolveFocus(focus, features, pointMarks, warnings) {
|
|
18588
18607
|
if (focus === null || focus === void 0) return null;
|
|
18608
|
+
if (typeof focus === "object" && !Array.isArray(focus) && "points" in focus) {
|
|
18609
|
+
const filter = focus.points;
|
|
18610
|
+
const matched = filter === true ? pointMarks : pointMarks.filter((p) => p.data[filter.field] === filter.value);
|
|
18611
|
+
if (matched.length === 0) {
|
|
18612
|
+
const detail = filter === true ? "the map has no points to fit" : `no points match { field: "${filter.field}", value: ${JSON.stringify(filter.value)} }`;
|
|
18613
|
+
warnings.push({
|
|
18614
|
+
code: "FOCUS_UNMATCHED",
|
|
18615
|
+
message: `geo.focus.points is set but ${detail}`,
|
|
18616
|
+
context: {}
|
|
18617
|
+
});
|
|
18618
|
+
return null;
|
|
18619
|
+
}
|
|
18620
|
+
const padding2 = focus.padding ?? 16;
|
|
18621
|
+
let minX2 = Infinity;
|
|
18622
|
+
let minY2 = Infinity;
|
|
18623
|
+
let maxX2 = -Infinity;
|
|
18624
|
+
let maxY2 = -Infinity;
|
|
18625
|
+
for (const p of matched) {
|
|
18626
|
+
minX2 = Math.min(minX2, p.cx - p.r);
|
|
18627
|
+
minY2 = Math.min(minY2, p.cy - p.r);
|
|
18628
|
+
maxX2 = Math.max(maxX2, p.cx + p.r);
|
|
18629
|
+
maxY2 = Math.max(maxY2, p.cy + p.r);
|
|
18630
|
+
}
|
|
18631
|
+
return {
|
|
18632
|
+
target: { x: minX2, y: minY2, width: maxX2 - minX2, height: maxY2 - minY2, padding: padding2 },
|
|
18633
|
+
// No feature ids: focus-dim is a feature concept, so a points focus
|
|
18634
|
+
// dims no features (there are no "other" features to mute).
|
|
18635
|
+
ids: []
|
|
18636
|
+
};
|
|
18637
|
+
}
|
|
18589
18638
|
let ids;
|
|
18590
18639
|
let padding = 16;
|
|
18591
18640
|
if (typeof focus === "string" || typeof focus === "number") {
|