@opendata-ai/openchart-engine 6.25.1 → 6.25.3

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
@@ -4,7 +4,7 @@ import {
4
4
  adaptTheme as adaptTheme3,
5
5
  BREAKPOINT_COMPACT_MAX as BREAKPOINT_COMPACT_MAX2,
6
6
  computeLabelBounds,
7
- estimateTextWidth as estimateTextWidth13,
7
+ estimateTextWidth as estimateTextWidth14,
8
8
  generateAltText,
9
9
  generateDataTable,
10
10
  getAxisTitleOffset as getAxisTitleOffset2,
@@ -7921,6 +7921,7 @@ import {
7921
7921
  abbreviateNumber as abbreviateNumber2,
7922
7922
  buildD3Formatter as buildD3Formatter4,
7923
7923
  buildTemporalFormatter,
7924
+ estimateTextWidth as estimateTextWidth8,
7924
7925
  formatDate,
7925
7926
  formatNumber as formatNumber2
7926
7927
  } from "@opendata-ai/openchart-core";
@@ -8015,16 +8016,41 @@ function scaleSupportsTickCount(resolvedScale) {
8015
8016
  const scale = resolvedScale.scale;
8016
8017
  return "ticks" in scale && typeof scale.ticks === "function";
8017
8018
  }
8018
- function categoricalTicks(resolvedScale, density) {
8019
+ function categoricalTicks(resolvedScale, density, orientation = "horizontal", bandwidth, labelAngle, fontSize, fontWeight, measureText) {
8019
8020
  const scale = resolvedScale.scale;
8020
8021
  const domain = scale.domain();
8021
8022
  const explicitTickCount = resolvedScale.channel.axis?.tickCount;
8022
- const maxTicks = explicitTickCount ?? TICK_COUNTS[density];
8023
8023
  let selectedValues = domain;
8024
- const shouldThinBand = resolvedScale.type === "band" && (explicitTickCount || density !== "full");
8025
- if ((resolvedScale.type !== "band" || shouldThinBand) && domain.length > maxTicks) {
8026
- const step = Math.ceil(domain.length / maxTicks);
8027
- selectedValues = domain.filter((_, i) => i % step === 0);
8024
+ if (resolvedScale.type === "band" && orientation === "horizontal") {
8025
+ if (bandwidth !== void 0 && bandwidth > 0 && fontSize !== void 0) {
8026
+ const maxLabelWidth = domain.reduce((max4, v) => {
8027
+ const w = measureText ? measureText(v, fontSize, fontWeight ?? 400).width : estimateTextWidth8(v, fontSize, fontWeight ?? 400);
8028
+ return Math.max(max4, w);
8029
+ }, 0);
8030
+ const angleRad = labelAngle !== void 0 ? Math.abs(labelAngle) * Math.PI / 180 : 0;
8031
+ const footprint = angleRad > 0 ? maxLabelWidth * Math.abs(Math.cos(angleRad)) : maxLabelWidth;
8032
+ const minGap = fontSize * 0.5;
8033
+ if (footprint + minGap > bandwidth) {
8034
+ const maxFitting = Math.max(1, Math.floor(bandwidth / (footprint + minGap)));
8035
+ const cap = explicitTickCount ?? Math.min(domain.length, Math.max(maxFitting, TICK_COUNTS[density]));
8036
+ if (domain.length > cap) {
8037
+ const step = Math.ceil(domain.length / cap);
8038
+ selectedValues = domain.filter((_, i) => i % step === 0);
8039
+ }
8040
+ }
8041
+ } else {
8042
+ const maxTicks = explicitTickCount ?? TICK_COUNTS[density];
8043
+ if ((explicitTickCount || density !== "full") && domain.length > maxTicks) {
8044
+ const step = Math.ceil(domain.length / maxTicks);
8045
+ selectedValues = domain.filter((_, i) => i % step === 0);
8046
+ }
8047
+ }
8048
+ } else if (resolvedScale.type !== "band") {
8049
+ const maxTicks = explicitTickCount ?? TICK_COUNTS[density];
8050
+ if (domain.length > maxTicks) {
8051
+ const step = Math.ceil(domain.length / maxTicks);
8052
+ selectedValues = domain.filter((_, i) => i % step === 0);
8053
+ }
8028
8054
  }
8029
8055
  const ticks2 = selectedValues.map((value2) => {
8030
8056
  const bandScale = resolvedScale.type === "band" ? scale : null;
@@ -8145,7 +8171,17 @@ function computeAxes(scales, chartArea, strategy, theme, measureText) {
8145
8171
  if (axisConfig?.values) {
8146
8172
  allTicks = resolveExplicitTicks(axisConfig.values, scales.x);
8147
8173
  } else if (!isContinuousX) {
8148
- allTicks = categoricalTicks(scales.x, xDensity);
8174
+ const xBandwidth = scales.x.type === "band" ? scales.x.scale.bandwidth() : void 0;
8175
+ allTicks = categoricalTicks(
8176
+ scales.x,
8177
+ xDensity,
8178
+ "horizontal",
8179
+ xBandwidth,
8180
+ axisConfig?.labelAngle,
8181
+ fontSize,
8182
+ fontWeight,
8183
+ measureText
8184
+ );
8149
8185
  } else {
8150
8186
  allTicks = continuousTicks(scales.x, xDensity, xTargetCount);
8151
8187
  }
@@ -8213,7 +8249,7 @@ function computeAxes(scales, chartArea, strategy, theme, measureText) {
8213
8249
  if (axisConfig?.values) {
8214
8250
  allTicks = resolveExplicitTicks(axisConfig.values, scales.y);
8215
8251
  } else if (!isContinuousY) {
8216
- allTicks = categoricalTicks(scales.y, yDensity);
8252
+ allTicks = categoricalTicks(scales.y, yDensity, "vertical");
8217
8253
  } else {
8218
8254
  allTicks = continuousTicks(scales.y, yDensity, yTargetCount);
8219
8255
  }
@@ -8270,7 +8306,7 @@ import {
8270
8306
  AXIS_TITLE_TRAILING_PAD,
8271
8307
  BREAKPOINT_COMPACT_MAX,
8272
8308
  computeChrome as computeChrome2,
8273
- estimateTextWidth as estimateTextWidth9,
8309
+ estimateTextWidth as estimateTextWidth10,
8274
8310
  getAxisTitleOffset,
8275
8311
  HPAD_COMPACT_FRACTION,
8276
8312
  HPAD_COMPACT_MIN,
@@ -8285,7 +8321,7 @@ import {
8285
8321
  } from "@opendata-ai/openchart-core";
8286
8322
 
8287
8323
  // src/legend/wrap.ts
8288
- import { COMPACT_WIDTH, estimateTextWidth as estimateTextWidth8 } from "@opendata-ai/openchart-core";
8324
+ import { COMPACT_WIDTH, estimateTextWidth as estimateTextWidth9 } from "@opendata-ai/openchart-core";
8289
8325
  var SWATCH_SIZE2 = 12;
8290
8326
  var SWATCH_GAP2 = 6;
8291
8327
  var ENTRY_GAP2 = 16;
@@ -8304,7 +8340,7 @@ function measureLegendWrap(entries, maxWidth, labelStyle, maxRows, entryGap = EN
8304
8340
  let fittingCount = entries.length;
8305
8341
  let fittingCountLocked = false;
8306
8342
  for (let i = 0; i < entries.length; i++) {
8307
- const labelWidth = estimateTextWidth8(
8343
+ const labelWidth = estimateTextWidth9(
8308
8344
  entries[i].label,
8309
8345
  labelStyle.fontSize,
8310
8346
  labelStyle.fontWeight
@@ -8376,7 +8412,7 @@ function computeDimensions(spec, options, legendLayout, theme, strategy, waterma
8376
8412
  if (xField) {
8377
8413
  for (const row of spec.data) {
8378
8414
  const label = String(row[xField] ?? "");
8379
- const w = estimateTextWidth9(label, theme.fonts.sizes.axisTick, theme.fonts.weights.normal);
8415
+ const w = estimateTextWidth10(label, theme.fonts.sizes.axisTick, theme.fonts.weights.normal);
8380
8416
  if (w > maxLabelWidth) maxLabelWidth = w;
8381
8417
  }
8382
8418
  }
@@ -8406,7 +8442,7 @@ function computeDimensions(spec, options, legendLayout, theme, strategy, waterma
8406
8442
  const label = String(row[colorField] ?? "");
8407
8443
  if (!seen.has(label)) {
8408
8444
  seen.add(label);
8409
- const w = estimateTextWidth9(label, 11, 600);
8445
+ const w = estimateTextWidth10(label, 11, 600);
8410
8446
  if (w > maxLabelWidth) maxLabelWidth = w;
8411
8447
  }
8412
8448
  }
@@ -8426,7 +8462,7 @@ function computeDimensions(spec, options, legendLayout, theme, strategy, waterma
8426
8462
  const maxXStr = String(maxX);
8427
8463
  for (const ann of spec.annotations) {
8428
8464
  if (ann.type === "text" && String(ann.x) === maxXStr) {
8429
- const textWidth = estimateTextWidth9(ann.text, ann.fontSize ?? 11, ann.fontWeight ?? 600);
8465
+ const textWidth = estimateTextWidth10(ann.text, ann.fontSize ?? 11, ann.fontWeight ?? 600);
8430
8466
  const dx = ann.offset?.dx ?? 0;
8431
8467
  const anchor = ann.anchor ?? "auto";
8432
8468
  const baseRightExtent = anchor === "left" ? textWidth : (
@@ -8450,7 +8486,7 @@ function computeDimensions(spec, options, legendLayout, theme, strategy, waterma
8450
8486
  let maxLabelWidth = 0;
8451
8487
  for (const row of spec.data) {
8452
8488
  const label = String(row[yField] ?? "");
8453
- const w = estimateTextWidth9(label, theme.fonts.sizes.axisTick, theme.fonts.weights.normal);
8489
+ const w = estimateTextWidth10(label, theme.fonts.sizes.axisTick, theme.fonts.weights.normal);
8454
8490
  if (w > maxLabelWidth) maxLabelWidth = w;
8455
8491
  }
8456
8492
  if (maxLabelWidth > 0) {
@@ -8486,7 +8522,7 @@ function computeDimensions(spec, options, legendLayout, theme, strategy, waterma
8486
8522
  }
8487
8523
  const negPrefix = spec.data.some((r) => Number(r[yField]) < 0) ? "-" : "";
8488
8524
  const labelEst = negPrefix + sampleLabel;
8489
- const labelWidth = estimateTextWidth9(
8525
+ const labelWidth = estimateTextWidth10(
8490
8526
  labelEst,
8491
8527
  theme.fonts.sizes.axisTick,
8492
8528
  theme.fonts.weights.normal
@@ -9007,7 +9043,7 @@ function computeScales(spec, chartArea, data) {
9007
9043
  }
9008
9044
 
9009
9045
  // src/legend/compute.ts
9010
- import { BRAND_RESERVE_WIDTH as BRAND_RESERVE_WIDTH2, COMPACT_WIDTH as COMPACT_WIDTH2, estimateTextWidth as estimateTextWidth10 } from "@opendata-ai/openchart-core";
9046
+ import { BRAND_RESERVE_WIDTH as BRAND_RESERVE_WIDTH2, COMPACT_WIDTH as COMPACT_WIDTH2, estimateTextWidth as estimateTextWidth11 } from "@opendata-ai/openchart-core";
9011
9047
  var LEGEND_PADDING = 8;
9012
9048
  var LEGEND_RIGHT_WIDTH = 120;
9013
9049
  var RIGHT_LEGEND_MAX_HEIGHT_RATIO = 0.4;
@@ -9123,7 +9159,7 @@ function computeLegend(spec, strategy, theme, chartArea, watermark = true) {
9123
9159
  }
9124
9160
  if (resolvedPosition === "right" || resolvedPosition === "bottom-right") {
9125
9161
  const maxLabelWidth = Math.max(
9126
- ...entries.map((e) => estimateTextWidth10(e.label, labelStyle.fontSize, labelStyle.fontWeight))
9162
+ ...entries.map((e) => estimateTextWidth11(e.label, labelStyle.fontSize, labelStyle.fontWeight))
9127
9163
  );
9128
9164
  const legendWidth = Math.min(
9129
9165
  LEGEND_RIGHT_WIDTH,
@@ -9183,7 +9219,7 @@ function computeLegend(spec, strategy, theme, chartArea, watermark = true) {
9183
9219
  entries = truncateEntries(entries, fittingCount);
9184
9220
  }
9185
9221
  const totalWidth = entries.reduce((sum2, entry) => {
9186
- const labelWidth = estimateTextWidth10(entry.label, labelStyle.fontSize, labelStyle.fontWeight);
9222
+ const labelWidth = estimateTextWidth11(entry.label, labelStyle.fontSize, labelStyle.fontWeight);
9187
9223
  return sum2 + SWATCH_SIZE2 + SWATCH_GAP2 + labelWidth + effectiveEntryGap;
9188
9224
  }, 0);
9189
9225
  const { rowCount } = measureLegendWrap(
@@ -9218,7 +9254,7 @@ import {
9218
9254
  adaptTheme as adaptTheme2,
9219
9255
  buildD3Formatter as buildD3Formatter5,
9220
9256
  computeChrome as computeChrome3,
9221
- estimateTextWidth as estimateTextWidth11,
9257
+ estimateTextWidth as estimateTextWidth12,
9222
9258
  formatNumber as formatNumber3,
9223
9259
  resolveTheme as resolveTheme2
9224
9260
  } from "@opendata-ai/openchart-core";
@@ -9899,7 +9935,7 @@ function compileSankey(spec, options) {
9899
9935
  if (labelsLeft) continue;
9900
9936
  const labelX = (node.x1 ?? nodeWidth) + LABEL_GAP;
9901
9937
  const labelText = node.label ?? node.id;
9902
- const labelWidth = estimateTextWidth11(labelText, labelFontSize, labelFontWeight);
9938
+ const labelWidth = estimateTextWidth12(labelText, labelFontSize, labelFontWeight);
9903
9939
  const overflow = labelX + labelWidth - rightEdge;
9904
9940
  if (overflow > maxOverflow) maxOverflow = overflow;
9905
9941
  }
@@ -10161,7 +10197,7 @@ function emptyLayout(area, chrome, theme, options, watermark) {
10161
10197
  }
10162
10198
 
10163
10199
  // src/tables/compile-table.ts
10164
- import { computeChrome as computeChrome4, estimateTextWidth as estimateTextWidth12 } from "@opendata-ai/openchart-core";
10200
+ import { computeChrome as computeChrome4, estimateTextWidth as estimateTextWidth13 } from "@opendata-ai/openchart-core";
10165
10201
 
10166
10202
  // src/tables/bar-column.ts
10167
10203
  var NEGATIVE_BAR_COLOR = "#c44e52";
@@ -10576,13 +10612,13 @@ function estimateColumnWidth(col, data, fontSize) {
10576
10612
  if (col.image) return (col.image.width ?? 24) + PADDING;
10577
10613
  if (col.flag) return 60;
10578
10614
  const label = col.label ?? col.key;
10579
- const headerWidth = estimateTextWidth12(label, fontSize, 600) + PADDING;
10615
+ const headerWidth = estimateTextWidth13(label, fontSize, 600) + PADDING;
10580
10616
  const sampleSize = Math.min(100, data.length);
10581
10617
  let maxDataWidth = 0;
10582
10618
  for (let i = 0; i < sampleSize; i++) {
10583
10619
  const val = data[i][col.key];
10584
10620
  const text = val == null ? "" : String(val);
10585
- const width = estimateTextWidth12(text, fontSize, 400) + PADDING;
10621
+ const width = estimateTextWidth13(text, fontSize, 400) + PADDING;
10586
10622
  if (width > maxDataWidth) maxDataWidth = width;
10587
10623
  }
10588
10624
  return Math.max(MIN_WIDTH, headerWidth, maxDataWidth);
@@ -11544,7 +11580,12 @@ function compileLayer(spec, options) {
11544
11580
  for (const entry of mergedLegendEntries) {
11545
11581
  seenLabels.add(entry.label);
11546
11582
  }
11547
- for (const leaf of leaves) {
11583
+ const indexedLeaves = leaves.map((leaf, i) => ({
11584
+ leaf,
11585
+ zIndex: leaf.zIndex ?? i
11586
+ }));
11587
+ indexedLeaves.sort((a, b) => a.zIndex - b.zIndex);
11588
+ for (const { leaf } of indexedLeaves) {
11548
11589
  const leafLayout = compileChart(leaf, options);
11549
11590
  allMarks.push(...leafLayout.marks);
11550
11591
  for (const entry of leafLayout.legend.entries) {
@@ -11573,7 +11614,7 @@ function estimateYAxisLabelWidth(data, encoding, baseFontSize) {
11573
11614
  let maxWidth = 0;
11574
11615
  for (const row of data) {
11575
11616
  const label = String(row[yField] ?? "");
11576
- const w = estimateTextWidth13(label, baseFontSize, 400);
11617
+ const w = estimateTextWidth14(label, baseFontSize, 400);
11577
11618
  if (w > maxWidth) maxWidth = w;
11578
11619
  }
11579
11620
  return maxWidth > 0 ? maxWidth + 10 : 40;
@@ -11602,7 +11643,7 @@ function estimateYAxisLabelWidth(data, encoding, baseFontSize) {
11602
11643
  }
11603
11644
  const hasNeg = data.some((r) => Number(r[yField]) < 0);
11604
11645
  const labelEst = (hasNeg ? "-" : "") + sampleLabel;
11605
- return estimateTextWidth13(labelEst, baseFontSize, 400) + 10;
11646
+ return estimateTextWidth14(labelEst, baseFontSize, 400) + 10;
11606
11647
  }
11607
11648
  function compileLayerIndependent(leaves, layerSpec, options) {
11608
11649
  if (leaves.length > 2) {
@@ -11755,6 +11796,9 @@ function compileLayerIndependent(leaves, layerSpec, options) {
11755
11796
  mergedTooltips.set(key, value2);
11756
11797
  }
11757
11798
  }
11799
+ const z0 = leaf0.zIndex ?? 0;
11800
+ const z1 = leaf1.zIndex ?? 1;
11801
+ const marks = z0 <= z1 ? [...adjustedMarks0, ...taggedMarks1] : [...taggedMarks1, ...adjustedMarks0];
11758
11802
  return {
11759
11803
  ...layout0,
11760
11804
  axes: {
@@ -11762,7 +11806,7 @@ function compileLayerIndependent(leaves, layerSpec, options) {
11762
11806
  y: layout0.axes.y,
11763
11807
  y2: y2Axis
11764
11808
  },
11765
- marks: [...adjustedMarks0, ...taggedMarks1],
11809
+ marks,
11766
11810
  legend: {
11767
11811
  ...layout0.legend,
11768
11812
  entries: mergedLegendEntries