@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 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 resolvedPosition = spec.legend?.position ?? (strategy.legendPosition === "right" ? "right" : "top");
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
- projection2.fitSize([width, height], fitGeometry);
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 (showLegend && colorEncoding && isQuantitative) {
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 (showLegend && colorEncoding) {
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
- const projection2 = createProjection(projectionType, fullArea.width, mapAreaHeight, geoCollection);
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 (showLegend && continuousContent) {
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) {