@opendata-ai/openchart-engine 8.0.0-rc.8 → 8.0.0-rc.9
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 +91 -17
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/__snapshots__/compile-snapshot.test.ts.snap +62 -0
- package/src/compile/layer.ts +4 -0
- package/src/compile.ts +54 -1
- package/src/geo/__tests__/compile-map.test.ts +124 -0
- package/src/geo/compile-map.ts +57 -22
- package/src/geo/projections.ts +21 -1
- package/src/legend/compute.ts +4 -3
package/dist/index.js
CHANGED
|
@@ -13824,7 +13824,8 @@ function computeLegendContent(spec, strategy, theme, availableWidth, availableHe
|
|
|
13824
13824
|
fill: theme.colors.text,
|
|
13825
13825
|
lineHeight: 1.3
|
|
13826
13826
|
};
|
|
13827
|
-
const
|
|
13827
|
+
const specPosition = spec.legend?.position === "top-left" ? "top" : spec.legend?.position;
|
|
13828
|
+
const resolvedPosition = specPosition ?? (strategy.legendPosition === "right" ? "right" : "top");
|
|
13828
13829
|
if (entries.length === 0) {
|
|
13829
13830
|
return emptyContent(resolvedPosition, theme);
|
|
13830
13831
|
}
|
|
@@ -15631,6 +15632,10 @@ function buildPrimarySpec(leaves, layerSpec) {
|
|
|
15631
15632
|
...leaves[0],
|
|
15632
15633
|
data: allData,
|
|
15633
15634
|
chrome: layerSpec.chrome ?? leaves[0].chrome,
|
|
15635
|
+
// Annotations come from the parent LayerSpec when present, otherwise
|
|
15636
|
+
// the first flattened leaf's. Later leaves' annotations are silently
|
|
15637
|
+
// dropped. The dual-axis path (compileLayerIndependent) explicitly
|
|
15638
|
+
// clears the secondary axis spec's annotations.
|
|
15634
15639
|
annotations: layerSpec.annotations ?? leaves[0].annotations,
|
|
15635
15640
|
labels: layerSpec.labels ?? leaves[0].labels,
|
|
15636
15641
|
legend: layerSpec.legend ?? leaves[0].legend,
|
|
@@ -17843,13 +17848,32 @@ function joinDataToFeatures(features, data, dataKeyField, featureIdField) {
|
|
|
17843
17848
|
|
|
17844
17849
|
// src/geo/projections.ts
|
|
17845
17850
|
var MERCATOR_FIT_MIN_LAT = -60;
|
|
17846
|
-
function createProjection(type, width, height, geojson) {
|
|
17851
|
+
function createProjection(type, width, height, geojson, inset = 0) {
|
|
17847
17852
|
if (type === "identity") {
|
|
17853
|
+
if (inset > 0) {
|
|
17854
|
+
return identity_default4().fitExtent(
|
|
17855
|
+
[
|
|
17856
|
+
[inset, inset],
|
|
17857
|
+
[width - inset, height - inset]
|
|
17858
|
+
],
|
|
17859
|
+
geojson
|
|
17860
|
+
);
|
|
17861
|
+
}
|
|
17848
17862
|
return identity_default4().fitSize([width, height], geojson);
|
|
17849
17863
|
}
|
|
17850
17864
|
const projection2 = type === "albersUsa" ? albersUsa_default() : type === "equalEarth" ? equalEarth_default() : mercator_default();
|
|
17851
17865
|
const fitGeometry = type === "mercator" ? excludePolarFeatures(geojson) : geojson;
|
|
17852
|
-
|
|
17866
|
+
if (inset > 0) {
|
|
17867
|
+
projection2.fitExtent(
|
|
17868
|
+
[
|
|
17869
|
+
[inset, inset],
|
|
17870
|
+
[width - inset, height - inset]
|
|
17871
|
+
],
|
|
17872
|
+
fitGeometry
|
|
17873
|
+
);
|
|
17874
|
+
} else {
|
|
17875
|
+
projection2.fitSize([width, height], fitGeometry);
|
|
17876
|
+
}
|
|
17853
17877
|
return projection2;
|
|
17854
17878
|
}
|
|
17855
17879
|
function excludePolarFeatures(geojson) {
|
|
@@ -17869,6 +17893,7 @@ function excludePolarFeatures(geojson) {
|
|
|
17869
17893
|
}
|
|
17870
17894
|
|
|
17871
17895
|
// src/geo/compile-map.ts
|
|
17896
|
+
var OVERLAY_LEGEND_INSET = 18;
|
|
17872
17897
|
function validateGeoFeatures(geo) {
|
|
17873
17898
|
if (!geo.features) {
|
|
17874
17899
|
throw new Error(
|
|
@@ -17941,11 +17966,13 @@ function compileMap(spec, options) {
|
|
|
17941
17966
|
const compileWarnings = [];
|
|
17942
17967
|
const showLegend = mapSpec.legend?.show !== false;
|
|
17943
17968
|
const colorEncoding = mapSpec.encoding.color;
|
|
17969
|
+
const showChoroplethLegend = showLegend && colorEncoding?.legend !== null;
|
|
17944
17970
|
const isQuantitative = colorEncoding?.type === "quantitative";
|
|
17945
17971
|
const legendPosition = mapSpec.legend?.position === "bottom" ? "bottom" : "top";
|
|
17972
|
+
const pointLegendOverlay = mapSpec.legend?.position === "top-left";
|
|
17946
17973
|
let legendBlockHeight = 0;
|
|
17947
17974
|
let continuousContent = null;
|
|
17948
|
-
if (
|
|
17975
|
+
if (showChoroplethLegend && colorEncoding && isQuantitative) {
|
|
17949
17976
|
const colorValues = [];
|
|
17950
17977
|
for (const row of mapSpec.data) {
|
|
17951
17978
|
const v = row[colorEncoding.field];
|
|
@@ -17968,11 +17995,11 @@ function compileMap(spec, options) {
|
|
|
17968
17995
|
const labelRowHeight = Math.ceil(theme.fonts.sizes.small * 1.3);
|
|
17969
17996
|
legendBlockHeight = continuousContent.barHeight + CONTINUOUS_LABEL_GAP + labelRowHeight;
|
|
17970
17997
|
}
|
|
17971
|
-
} else if (
|
|
17998
|
+
} else if (showChoroplethLegend && colorEncoding) {
|
|
17972
17999
|
const labelHeight = Math.ceil(theme.fonts.sizes.small * 1.3);
|
|
17973
18000
|
legendBlockHeight = Math.max(10, labelHeight) + 6;
|
|
17974
18001
|
}
|
|
17975
|
-
if (showLegend && mapSpec.points?.color) {
|
|
18002
|
+
if (showLegend && !pointLegendOverlay && mapSpec.points?.color) {
|
|
17976
18003
|
if (mapSpec.points.color.type === "quantitative") {
|
|
17977
18004
|
const labelRowHeight = Math.ceil(theme.fonts.sizes.small * 1.3);
|
|
17978
18005
|
legendBlockHeight += CONTINUOUS_BAR_HEIGHT + CONTINUOUS_LABEL_GAP + labelRowHeight + 8;
|
|
@@ -17987,7 +18014,18 @@ function compileMap(spec, options) {
|
|
|
17987
18014
|
return emptyLayout3(chrome, theme, options, watermark);
|
|
17988
18015
|
}
|
|
17989
18016
|
const projectionType = mapSpec.geo.projection;
|
|
17990
|
-
|
|
18017
|
+
let pointInset = 0;
|
|
18018
|
+
if (mapSpec.points) {
|
|
18019
|
+
const sizeRange = mapSpec.points.size?.scale?.range;
|
|
18020
|
+
pointInset = sizeRange ? sizeRange[1] : SIZE_SCALE_DEFAULTS.mapPoint.range[1];
|
|
18021
|
+
}
|
|
18022
|
+
const projection2 = createProjection(
|
|
18023
|
+
projectionType,
|
|
18024
|
+
fullArea.width,
|
|
18025
|
+
mapAreaHeight,
|
|
18026
|
+
geoCollection,
|
|
18027
|
+
pointInset
|
|
18028
|
+
);
|
|
17991
18029
|
const pathGen = path_default(projection2);
|
|
17992
18030
|
if (projectionType !== "identity") {
|
|
17993
18031
|
for (const feat of geoFeatures) {
|
|
@@ -18047,7 +18085,7 @@ function compileMap(spec, options) {
|
|
|
18047
18085
|
droppedFeatures
|
|
18048
18086
|
});
|
|
18049
18087
|
featureMarks = result.marks;
|
|
18050
|
-
if (
|
|
18088
|
+
if (showChoroplethLegend && continuousContent) {
|
|
18051
18089
|
const labelStyle = {
|
|
18052
18090
|
fontFamily: theme.fonts.family,
|
|
18053
18091
|
fontSize: theme.fonts.sizes.small,
|
|
@@ -18098,7 +18136,7 @@ function compileMap(spec, options) {
|
|
|
18098
18136
|
mapAreaHeight,
|
|
18099
18137
|
legendReserveGap,
|
|
18100
18138
|
legendPosition,
|
|
18101
|
-
showLegend,
|
|
18139
|
+
showLegend: showChoroplethLegend,
|
|
18102
18140
|
droppedFeatures
|
|
18103
18141
|
});
|
|
18104
18142
|
featureMarks = result.marks;
|
|
@@ -18242,15 +18280,15 @@ function compileMap(spec, options) {
|
|
|
18242
18280
|
}));
|
|
18243
18281
|
const bottomChoroplethHeight = legendPosition === "bottom" ? continuousLegend?.bounds.height ?? categoricalLegend?.bounds.height ?? 0 : 0;
|
|
18244
18282
|
const choroplethLegendGap = bottomChoroplethHeight > 0 ? 8 : 0;
|
|
18245
|
-
const legendX = fullArea.x;
|
|
18246
|
-
const legendY = fullArea.y + mapAreaHeight + legendReserveGap + bottomChoroplethHeight + choroplethLegendGap;
|
|
18247
|
-
const legendWidth = fullArea.width;
|
|
18283
|
+
const legendX = pointLegendOverlay ? fullArea.x + OVERLAY_LEGEND_INSET : fullArea.x;
|
|
18284
|
+
const legendY = pointLegendOverlay ? fullArea.y + OVERLAY_LEGEND_INSET : fullArea.y + mapAreaHeight + legendReserveGap + bottomChoroplethHeight + choroplethLegendGap;
|
|
18285
|
+
const legendWidth = pointLegendOverlay ? fullArea.width - OVERLAY_LEGEND_INSET * 2 : fullArea.width;
|
|
18248
18286
|
const swatchSize = 10;
|
|
18249
18287
|
const swatchGap = 6;
|
|
18250
18288
|
const entryGap = 16;
|
|
18251
18289
|
pointCategoricalLegend = {
|
|
18252
18290
|
type: "categorical",
|
|
18253
|
-
position: "bottom",
|
|
18291
|
+
position: pointLegendOverlay ? "top-left" : "bottom",
|
|
18254
18292
|
bounds: { x: legendX, y: legendY, width: legendWidth, height: swatchSize + 6 },
|
|
18255
18293
|
labelStyle,
|
|
18256
18294
|
entries,
|
|
@@ -18290,8 +18328,8 @@ function compileMap(spec, options) {
|
|
|
18290
18328
|
};
|
|
18291
18329
|
const bottomChoroplethH = legendPosition === "bottom" ? continuousLegend?.bounds.height ?? categoricalLegend?.bounds.height ?? 0 : 0;
|
|
18292
18330
|
const choroplethLegendGap = bottomChoroplethH > 0 ? 8 : 0;
|
|
18293
|
-
const legendX = fullArea.x;
|
|
18294
|
-
const legendY = fullArea.y + mapAreaHeight + legendReserveGap + bottomChoroplethH + choroplethLegendGap;
|
|
18331
|
+
const legendX = pointLegendOverlay ? fullArea.x + OVERLAY_LEGEND_INSET : fullArea.x;
|
|
18332
|
+
const legendY = pointLegendOverlay ? fullArea.y + OVERLAY_LEGEND_INSET : fullArea.y + mapAreaHeight + legendReserveGap + bottomChoroplethH + choroplethLegendGap;
|
|
18295
18333
|
const ptLabelRowHeight = Math.ceil(theme.fonts.sizes.small * 1.3);
|
|
18296
18334
|
const ptLegendHeight = ptContinuousContent.barHeight + CONTINUOUS_LABEL_GAP + ptLabelRowHeight;
|
|
18297
18335
|
const bounds = {
|
|
@@ -18309,7 +18347,7 @@ function compileMap(spec, options) {
|
|
|
18309
18347
|
pointContinuousLegend = {
|
|
18310
18348
|
type: "continuous",
|
|
18311
18349
|
mode: ptContinuousContent.mode,
|
|
18312
|
-
position: "bottom",
|
|
18350
|
+
position: pointLegendOverlay ? "top-left" : "bottom",
|
|
18313
18351
|
bounds,
|
|
18314
18352
|
labelStyle,
|
|
18315
18353
|
bar,
|
|
@@ -22795,6 +22833,38 @@ function applySparklineDefaults(spec, theme) {
|
|
|
22795
22833
|
}
|
|
22796
22834
|
return { ...spec, markDef: newMarkDef };
|
|
22797
22835
|
}
|
|
22836
|
+
function resolveScaleInvert(resolved) {
|
|
22837
|
+
if (!resolved) return void 0;
|
|
22838
|
+
const { scale, type } = resolved;
|
|
22839
|
+
if (type === "band" || type === "point") {
|
|
22840
|
+
const domain = scale.domain();
|
|
22841
|
+
const positions = domain.map((v) => {
|
|
22842
|
+
const pos = scale(v) ?? 0;
|
|
22843
|
+
return type === "band" ? pos + scale.bandwidth() / 2 : pos;
|
|
22844
|
+
});
|
|
22845
|
+
const range4 = scale.range();
|
|
22846
|
+
return {
|
|
22847
|
+
topPixel: range4[0],
|
|
22848
|
+
bottomPixel: range4[range4.length - 1],
|
|
22849
|
+
topData: 0,
|
|
22850
|
+
bottomData: 0,
|
|
22851
|
+
domain,
|
|
22852
|
+
positions
|
|
22853
|
+
};
|
|
22854
|
+
}
|
|
22855
|
+
const invert = scale.invert;
|
|
22856
|
+
if (typeof invert !== "function") return void 0;
|
|
22857
|
+
const range3 = scale.range();
|
|
22858
|
+
const topPx = range3[0];
|
|
22859
|
+
const bottomPx = range3[range3.length - 1];
|
|
22860
|
+
const topVal = invert.call(scale, topPx);
|
|
22861
|
+
const bottomVal = invert.call(scale, bottomPx);
|
|
22862
|
+
const topData = topVal instanceof Date ? +topVal : Number(topVal);
|
|
22863
|
+
const bottomData = bottomVal instanceof Date ? +bottomVal : Number(bottomVal);
|
|
22864
|
+
if (!Number.isFinite(topData) || !Number.isFinite(bottomData)) return void 0;
|
|
22865
|
+
const scaleType = type;
|
|
22866
|
+
return { topPixel: topPx, bottomPixel: bottomPx, topData, bottomData, scaleType };
|
|
22867
|
+
}
|
|
22798
22868
|
function compileChart(spec, optionsInput) {
|
|
22799
22869
|
const sugarWarnings = [];
|
|
22800
22870
|
const expandedSpec = spec && typeof spec === "object" && !Array.isArray(spec) ? expandSpecSugar(spec, sugarWarnings) : spec;
|
|
@@ -23160,6 +23230,8 @@ function compileChart(spec, optionsInput) {
|
|
|
23160
23230
|
);
|
|
23161
23231
|
assignAnimationIndices(marks, resolvedAnimation);
|
|
23162
23232
|
const chrome = footnotes ? { ...dims.chrome, footnotes } : dims.chrome;
|
|
23233
|
+
const xInvert = resolveScaleInvert(scales.x);
|
|
23234
|
+
const yInvert = resolveScaleInvert(scales.y);
|
|
23163
23235
|
return {
|
|
23164
23236
|
area: chartArea,
|
|
23165
23237
|
chrome,
|
|
@@ -23192,7 +23264,9 @@ function compileChart(spec, optionsInput) {
|
|
|
23192
23264
|
watermark,
|
|
23193
23265
|
display: chartSpec.display,
|
|
23194
23266
|
crosshair,
|
|
23195
|
-
measureText: options.measureText
|
|
23267
|
+
measureText: options.measureText,
|
|
23268
|
+
...xInvert ? { xInvert } : {},
|
|
23269
|
+
...yInvert ? { yInvert } : {}
|
|
23196
23270
|
};
|
|
23197
23271
|
}
|
|
23198
23272
|
function compileFaceted(chartSpec, renderSpec, facetChannel, options, theme, strategy, watermark, resolvedAnimation, crosshair, rawSpec, optionsInput) {
|