@opendata-ai/openchart-engine 6.13.0 → 6.15.1

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
@@ -727,6 +727,13 @@ function getSequentialColor(scales, value2, fallback = DEFAULT_COLOR) {
727
727
  }
728
728
 
729
729
  // src/charts/bar/compute.ts
730
+ function orientGradientForHorizontalBar(grad) {
731
+ if (grad.gradient !== "linear") return grad;
732
+ const lg = grad;
733
+ const isDefaultVertical = (lg.x1 === void 0 || lg.x1 === 0) && (lg.y1 === void 0 || lg.y1 === 0) && (lg.x2 === void 0 || lg.x2 === 0) && (lg.y2 === void 0 || lg.y2 === 1);
734
+ if (!isDefaultVertical) return grad;
735
+ return { ...lg, x1: 0, y1: 0, x2: 1, y2: 0 };
736
+ }
730
737
  var MIN_BAR_WIDTH = 1;
731
738
  function formatBarValue(value2) {
732
739
  if (Math.abs(value2) >= 1e3) return abbreviateNumber(value2);
@@ -837,7 +844,7 @@ function computeStackedBars(data, valueField, categoryField, colorField, xScale,
837
844
  y: bandY,
838
845
  width: barWidth,
839
846
  height: bandwidth,
840
- fill: color2,
847
+ fill: isGradientDef(color2) ? orientGradientForHorizontalBar(color2) : color2,
841
848
  cornerRadius: 0,
842
849
  data: row,
843
850
  aria,
@@ -884,7 +891,7 @@ function computeGroupedBars(data, valueField, categoryField, colorField, xScale,
884
891
  y: subY,
885
892
  width: barWidth,
886
893
  height: subBandHeight,
887
- fill: color2,
894
+ fill: isGradientDef(color2) ? orientGradientForHorizontalBar(color2) : color2,
888
895
  cornerRadius: 2,
889
896
  data: row,
890
897
  aria,
@@ -915,7 +922,7 @@ function computeColoredBars(data, valueField, categoryField, colorField, xScale,
915
922
  y: bandY,
916
923
  width: barWidth,
917
924
  height: bandwidth,
918
- fill: color2,
925
+ fill: isGradientDef(color2) ? orientGradientForHorizontalBar(color2) : color2,
919
926
  cornerRadius: 2,
920
927
  data: row,
921
928
  aria,
@@ -956,7 +963,7 @@ function computeSimpleBars(data, valueField, categoryField, xScale, yScale, band
956
963
  y: bandY,
957
964
  width: barWidth,
958
965
  height: bandwidth,
959
- fill: color2,
966
+ fill: isGradientDef(color2) ? orientGradientForHorizontalBar(color2) : color2,
960
967
  cornerRadius: 2,
961
968
  data: row,
962
969
  aria,
@@ -968,11 +975,17 @@ function computeSimpleBars(data, valueField, categoryField, xScale, yScale, band
968
975
 
969
976
  // src/charts/bar/labels.ts
970
977
  import {
978
+ abbreviateNumber as abbreviateNumber2,
971
979
  buildD3Formatter,
972
980
  estimateTextWidth as estimateTextWidth2,
981
+ formatNumber as formatNumber2,
973
982
  getRepresentativeColor,
974
983
  resolveCollisions
975
984
  } from "@opendata-ai/openchart-core";
985
+ function formatBarValue2(value2) {
986
+ if (Math.abs(value2) >= 1e3) return abbreviateNumber2(value2);
987
+ return formatNumber2(value2);
988
+ }
976
989
  var SUFFIX_MULTIPLIERS = {
977
990
  K: 1e3,
978
991
  M: 1e6,
@@ -998,21 +1011,30 @@ var LABEL_FONT_SIZE = 11;
998
1011
  var LABEL_FONT_WEIGHT = 600;
999
1012
  var LABEL_PADDING = 6;
1000
1013
  var MIN_WIDTH_FOR_INSIDE_LABEL = 40;
1001
- function computeBarLabels(marks, _chartArea, density = "auto", labelFormat, labelPrefix) {
1014
+ function computeBarLabels(marks, _chartArea, density = "auto", labelFormat, labelPrefix, valueField) {
1002
1015
  if (density === "none") return [];
1003
1016
  const targetMarks = density === "endpoints" && marks.length > 1 ? [marks[0], marks[marks.length - 1]] : marks;
1004
1017
  const candidates = [];
1005
1018
  const fitsInSegment = [];
1006
1019
  const formatter = buildD3Formatter(labelFormat);
1007
1020
  for (const mark of targetMarks) {
1008
- const ariaLabel = mark.aria.label;
1009
- const lastColon = ariaLabel.lastIndexOf(":");
1010
- const rawValue = lastColon >= 0 ? ariaLabel.slice(lastColon + 1).trim() : "";
1011
- if (!rawValue) continue;
1012
- let valuePart = rawValue;
1013
- if (formatter) {
1014
- const num = parseDisplayNumber(rawValue);
1015
- if (!Number.isNaN(num)) valuePart = formatter(num);
1021
+ let valuePart;
1022
+ const rawNum = valueField != null ? Number(mark.data[valueField]) : NaN;
1023
+ if (formatter && Number.isFinite(rawNum)) {
1024
+ valuePart = formatter(rawNum);
1025
+ } else if (Number.isFinite(rawNum)) {
1026
+ valuePart = formatBarValue2(rawNum);
1027
+ } else {
1028
+ const ariaLabel = mark.aria.label;
1029
+ const lastColon = ariaLabel.lastIndexOf(":");
1030
+ const rawValue = lastColon >= 0 ? ariaLabel.slice(lastColon + 1).trim() : "";
1031
+ if (!rawValue) continue;
1032
+ if (formatter) {
1033
+ const num = parseDisplayNumber(rawValue);
1034
+ valuePart = !Number.isNaN(num) ? formatter(num) : rawValue;
1035
+ } else {
1036
+ valuePart = rawValue;
1037
+ }
1016
1038
  }
1017
1039
  if (labelPrefix) valuePart = labelPrefix + valuePart;
1018
1040
  const textWidth = estimateTextWidth2(valuePart, LABEL_FONT_SIZE, LABEL_FONT_WEIGHT);
@@ -1097,12 +1119,14 @@ function computeBarLabels(marks, _chartArea, density = "auto", labelFormat, labe
1097
1119
  // src/charts/bar/index.ts
1098
1120
  var barRenderer = (spec, scales, chartArea, strategy, _theme) => {
1099
1121
  const marks = computeBarMarks(spec, scales, chartArea, strategy);
1122
+ const valueField = spec.encoding?.x && "field" in spec.encoding.x ? spec.encoding.x.field : void 0;
1100
1123
  const labels = computeBarLabels(
1101
1124
  marks,
1102
1125
  chartArea,
1103
1126
  spec.labels.density,
1104
1127
  spec.labels.format,
1105
- spec.labels.prefix
1128
+ spec.labels.prefix,
1129
+ valueField
1106
1130
  );
1107
1131
  for (let i = 0; i < marks.length && i < labels.length; i++) {
1108
1132
  marks[i].label = labels[i];
@@ -1111,11 +1135,11 @@ var barRenderer = (spec, scales, chartArea, strategy, _theme) => {
1111
1135
  };
1112
1136
 
1113
1137
  // src/charts/column/compute.ts
1114
- import { abbreviateNumber as abbreviateNumber2, formatNumber as formatNumber2, isGradientDef as isGradientDef2 } from "@opendata-ai/openchart-core";
1138
+ import { abbreviateNumber as abbreviateNumber3, formatNumber as formatNumber3, isGradientDef as isGradientDef2 } from "@opendata-ai/openchart-core";
1115
1139
  var MIN_COLUMN_HEIGHT = 1;
1116
1140
  function formatColumnValue(value2) {
1117
- if (Math.abs(value2) >= 1e3) return abbreviateNumber2(value2);
1118
- return formatNumber2(value2);
1141
+ if (Math.abs(value2) >= 1e3) return abbreviateNumber3(value2);
1142
+ return formatNumber3(value2);
1119
1143
  }
1120
1144
  function computeColumnMarks(spec, scales, _chartArea, _strategy) {
1121
1145
  const encoding = spec.encoding;
@@ -1359,28 +1383,43 @@ function computeStackedColumns(data, categoryField, valueField, colorField, xSca
1359
1383
 
1360
1384
  // src/charts/column/labels.ts
1361
1385
  import {
1386
+ abbreviateNumber as abbreviateNumber4,
1362
1387
  buildD3Formatter as buildD3Formatter2,
1363
1388
  estimateTextWidth as estimateTextWidth3,
1389
+ formatNumber as formatNumber4,
1364
1390
  getRepresentativeColor as getRepresentativeColor2,
1365
1391
  resolveCollisions as resolveCollisions2
1366
1392
  } from "@opendata-ai/openchart-core";
1393
+ function formatColumnValue2(value2) {
1394
+ if (Math.abs(value2) >= 1e3) return abbreviateNumber4(value2);
1395
+ return formatNumber4(value2);
1396
+ }
1367
1397
  var LABEL_FONT_SIZE2 = 10;
1368
1398
  var LABEL_FONT_WEIGHT2 = 600;
1369
1399
  var LABEL_OFFSET_Y = 6;
1370
- function computeColumnLabels(marks, _chartArea, density = "auto", labelFormat, labelPrefix) {
1400
+ function computeColumnLabels(marks, _chartArea, density = "auto", labelFormat, labelPrefix, valueField) {
1371
1401
  if (density === "none") return [];
1372
1402
  const targetMarks = density === "endpoints" && marks.length > 1 ? [marks[0], marks[marks.length - 1]] : marks;
1373
1403
  const formatter = buildD3Formatter2(labelFormat);
1374
1404
  const candidates = [];
1375
1405
  for (const mark of targetMarks) {
1376
- const ariaLabel = mark.aria.label;
1377
- const lastColon = ariaLabel.lastIndexOf(":");
1378
- const rawValue = lastColon >= 0 ? ariaLabel.slice(lastColon + 1).trim() : "";
1379
- if (!rawValue) continue;
1380
- let valuePart = rawValue;
1381
- if (formatter) {
1382
- const num = Number(rawValue.replace(/[^0-9.-]/g, ""));
1383
- if (!Number.isNaN(num)) valuePart = formatter(num);
1406
+ let valuePart;
1407
+ const rawNum = valueField != null ? Number(mark.data[valueField]) : NaN;
1408
+ if (formatter && Number.isFinite(rawNum)) {
1409
+ valuePart = formatter(rawNum);
1410
+ } else if (Number.isFinite(rawNum)) {
1411
+ valuePart = formatColumnValue2(rawNum);
1412
+ } else {
1413
+ const ariaLabel = mark.aria.label;
1414
+ const lastColon = ariaLabel.lastIndexOf(":");
1415
+ const rawValue = lastColon >= 0 ? ariaLabel.slice(lastColon + 1).trim() : "";
1416
+ if (!rawValue) continue;
1417
+ if (formatter) {
1418
+ const num = Number(rawValue.replace(/[^0-9.-]/g, ""));
1419
+ valuePart = !Number.isNaN(num) ? formatter(num) : rawValue;
1420
+ } else {
1421
+ valuePart = rawValue;
1422
+ }
1384
1423
  }
1385
1424
  if (labelPrefix) valuePart = labelPrefix + valuePart;
1386
1425
  const numericValue = parseFloat(valuePart);
@@ -1423,12 +1462,14 @@ function computeColumnLabels(marks, _chartArea, density = "auto", labelFormat, l
1423
1462
  // src/charts/column/index.ts
1424
1463
  var columnRenderer = (spec, scales, chartArea, strategy, _theme) => {
1425
1464
  const marks = computeColumnMarks(spec, scales, chartArea, strategy);
1465
+ const valueField = spec.encoding?.y && "field" in spec.encoding.y ? spec.encoding.y.field : void 0;
1426
1466
  const labels = computeColumnLabels(
1427
1467
  marks,
1428
1468
  chartArea,
1429
1469
  spec.labels.density,
1430
1470
  spec.labels.format,
1431
- spec.labels.prefix
1471
+ spec.labels.prefix,
1472
+ valueField
1432
1473
  );
1433
1474
  for (let i = 0; i < marks.length && i < labels.length; i++) {
1434
1475
  marks[i].label = labels[i];
@@ -1586,22 +1627,42 @@ function computeLollipopMarks(data, valueField, categoryField, xScale, yScale, b
1586
1627
 
1587
1628
  // src/charts/dot/labels.ts
1588
1629
  import {
1630
+ abbreviateNumber as abbreviateNumber5,
1631
+ buildD3Formatter as buildD3Formatter3,
1589
1632
  estimateTextWidth as estimateTextWidth4,
1633
+ formatNumber as formatNumber5,
1590
1634
  getRepresentativeColor as getRepresentativeColor3,
1591
1635
  resolveCollisions as resolveCollisions3
1592
1636
  } from "@opendata-ai/openchart-core";
1637
+ function formatDotValue(value2) {
1638
+ if (Math.abs(value2) >= 1e3) return abbreviateNumber5(value2);
1639
+ return formatNumber5(value2);
1640
+ }
1593
1641
  var LABEL_FONT_SIZE3 = 11;
1594
1642
  var LABEL_FONT_WEIGHT3 = 600;
1595
1643
  var LABEL_OFFSET_X = 10;
1596
- function computeDotLabels(marks, _chartArea, density = "auto", labelPrefix) {
1644
+ function computeDotLabels(marks, _chartArea, density = "auto", labelPrefix, labelFormat, valueField) {
1597
1645
  if (density === "none") return [];
1598
1646
  const targetMarks = density === "endpoints" && marks.length > 1 ? [marks[0], marks[marks.length - 1]] : marks;
1647
+ const formatter = buildD3Formatter3(labelFormat);
1599
1648
  const candidates = [];
1600
1649
  for (const mark of targetMarks) {
1601
- const ariaLabel = mark.aria.label;
1602
- const lastColon = ariaLabel.lastIndexOf(":");
1603
- let valuePart = lastColon >= 0 ? ariaLabel.slice(lastColon + 1).trim() : "";
1604
- if (!valuePart) continue;
1650
+ let valuePart;
1651
+ const rawNum = valueField != null ? Number(mark.data[valueField]) : NaN;
1652
+ if (formatter && Number.isFinite(rawNum)) {
1653
+ valuePart = formatter(rawNum);
1654
+ } else if (Number.isFinite(rawNum)) {
1655
+ valuePart = formatDotValue(rawNum);
1656
+ } else {
1657
+ const ariaLabel = mark.aria.label;
1658
+ const lastColon = ariaLabel.lastIndexOf(":");
1659
+ valuePart = lastColon >= 0 ? ariaLabel.slice(lastColon + 1).trim() : "";
1660
+ if (!valuePart) continue;
1661
+ if (formatter) {
1662
+ const num = Number(valuePart.replace(/[^0-9.-]/g, ""));
1663
+ if (!Number.isNaN(num)) valuePart = formatter(num);
1664
+ }
1665
+ }
1605
1666
  if (labelPrefix) valuePart = labelPrefix + valuePart;
1606
1667
  const textWidth = estimateTextWidth4(valuePart, LABEL_FONT_SIZE3, LABEL_FONT_WEIGHT3);
1607
1668
  const textHeight = LABEL_FONT_SIZE3 * 1.2;
@@ -1640,7 +1701,15 @@ function computeDotLabels(marks, _chartArea, density = "auto", labelPrefix) {
1640
1701
  var dotRenderer = (spec, scales, chartArea, strategy, _theme) => {
1641
1702
  const marks = computeDotMarks(spec, scales, chartArea, strategy);
1642
1703
  const pointMarks = marks.filter((m) => m.type === "point");
1643
- const labels = computeDotLabels(pointMarks, chartArea, spec.labels.density, spec.labels.prefix);
1704
+ const valueField = spec.encoding?.x && "field" in spec.encoding.x ? spec.encoding.x.field : void 0;
1705
+ const labels = computeDotLabels(
1706
+ pointMarks,
1707
+ chartArea,
1708
+ spec.labels.density,
1709
+ spec.labels.prefix,
1710
+ spec.labels.format,
1711
+ valueField
1712
+ );
1644
1713
  let labelIdx = 0;
1645
1714
  for (const mark of marks) {
1646
1715
  if (mark.type === "point" && labelIdx < labels.length) {
@@ -1651,6 +1720,9 @@ var dotRenderer = (spec, scales, chartArea, strategy, _theme) => {
1651
1720
  return marks;
1652
1721
  };
1653
1722
 
1723
+ // src/charts/line/index.ts
1724
+ import { getRepresentativeColor as getRepresentativeColor6 } from "@opendata-ai/openchart-core";
1725
+
1654
1726
  // src/charts/line/area.ts
1655
1727
  import { getRepresentativeColor as getRepresentativeColor4 } from "@opendata-ai/openchart-core";
1656
1728
 
@@ -2597,7 +2669,7 @@ function computeSingleArea(spec, scales, _chartArea) {
2597
2669
  const marks = [];
2598
2670
  for (const [seriesKey, rows] of groups) {
2599
2671
  const color2 = getColor(scales, seriesKey);
2600
- const sortedRows = sortByField(rows, xChannel.field);
2672
+ const sortedRows = xChannel.type === "nominal" || xChannel.type === "ordinal" ? rows : sortByField(rows, xChannel.field);
2601
2673
  const validPoints = [];
2602
2674
  for (const row of sortedRows) {
2603
2675
  const xVal = scaleValue(scales.x.scale, scales.x.type, row[xChannel.field]);
@@ -2646,7 +2718,7 @@ function computeStackedArea(spec, scales, chartArea) {
2646
2718
  if (!xChannel || !yChannel || !scales.x || !scales.y || !colorField) {
2647
2719
  return computeSingleArea(spec, scales, chartArea);
2648
2720
  }
2649
- const sortedData = sortByField(spec.data, xChannel.field);
2721
+ const sortedData = xChannel.type === "nominal" || xChannel.type === "ordinal" ? spec.data : sortByField(spec.data, xChannel.field);
2650
2722
  const seriesKeys = /* @__PURE__ */ new Set();
2651
2723
  const xValueSet = /* @__PURE__ */ new Set();
2652
2724
  const rowsByXSeries = /* @__PURE__ */ new Map();
@@ -2763,7 +2835,7 @@ function computeLineMarks(spec, scales, _chartArea, _strategy) {
2763
2835
  for (const [seriesKey, rows] of groups) {
2764
2836
  const color2 = isSequentialColor ? getSequentialColor(scales, _getMidValue(rows, sequentialColorField)) : getColor(scales, seriesKey);
2765
2837
  const strokeColor = getRepresentativeColor5(color2);
2766
- const sortedRows = sortByField(rows, xChannel.field);
2838
+ const sortedRows = xChannel.type === "nominal" || xChannel.type === "ordinal" ? rows : sortByField(rows, xChannel.field);
2767
2839
  const pointsWithData = [];
2768
2840
  const segments = [];
2769
2841
  let currentSegment = [];
@@ -2951,9 +3023,24 @@ var lineRenderer = (spec, scales, chartArea, strategy, _theme) => {
2951
3023
  };
2952
3024
  var areaRenderer = (spec, scales, chartArea, strategy, _theme) => {
2953
3025
  const areas = computeAreaMarks(spec, scales, chartArea);
2954
- const lines = computeLineMarks(spec, scales, chartArea, strategy);
3026
+ const encoding = spec.encoding;
3027
+ const hasColor = !!(encoding.color && "field" in encoding.color);
3028
+ const lines = hasColor ? linesFromAreas(areas) : computeLineMarks(spec, scales, chartArea, strategy);
2955
3029
  return [...areas, ...lines];
2956
3030
  };
3031
+ function linesFromAreas(areas) {
3032
+ return areas.map((a) => ({
3033
+ type: "line",
3034
+ points: a.topPoints,
3035
+ path: a.topPath,
3036
+ stroke: getRepresentativeColor6(a.fill),
3037
+ strokeWidth: a.strokeWidth ?? 1,
3038
+ seriesKey: a.seriesKey,
3039
+ data: a.data,
3040
+ dataPoints: a.dataPoints,
3041
+ aria: { label: `${a.seriesKey ?? "Series"}: line with ${a.topPoints.length} data points` }
3042
+ }));
3043
+ }
2957
3044
 
2958
3045
  // src/charts/pie/compute.ts
2959
3046
  import { isConditionalDef, isGradientDef as isGradientDef3 } from "@opendata-ai/openchart-core";
@@ -3329,7 +3416,7 @@ function clearRenderers() {
3329
3416
  }
3330
3417
 
3331
3418
  // src/charts/rule/index.ts
3332
- import { getRepresentativeColor as getRepresentativeColor6 } from "@opendata-ai/openchart-core";
3419
+ import { getRepresentativeColor as getRepresentativeColor7 } from "@opendata-ai/openchart-core";
3333
3420
  function computeRuleMarks(spec, scales, chartArea) {
3334
3421
  const encoding = spec.encoding;
3335
3422
  const xChannel = encoding.x;
@@ -3372,7 +3459,7 @@ function computeRuleMarks(spec, scales, chartArea) {
3372
3459
  const y2Val = scaleValue(scales.y.scale, scales.y.type, row[y2Channel.field]);
3373
3460
  if (y2Val != null) y2 = y2Val;
3374
3461
  }
3375
- const color2 = getRepresentativeColor6(
3462
+ const color2 = getRepresentativeColor7(
3376
3463
  colorField ? getColor(scales, String(row[colorField] ?? "__default__")) : getColor(scales, "__default__")
3377
3464
  );
3378
3465
  const strokeDashEncoding = encoding.strokeDash && "field" in encoding.strokeDash ? encoding.strokeDash : void 0;
@@ -6181,7 +6268,7 @@ var scatterRenderer = (spec, scales, chartArea, strategy, _theme) => {
6181
6268
  };
6182
6269
 
6183
6270
  // src/charts/text/index.ts
6184
- import { getRepresentativeColor as getRepresentativeColor7 } from "@opendata-ai/openchart-core";
6271
+ import { getRepresentativeColor as getRepresentativeColor8 } from "@opendata-ai/openchart-core";
6185
6272
  function computeTextMarks(spec, scales) {
6186
6273
  const encoding = spec.encoding;
6187
6274
  const xChannel = encoding.x;
@@ -6207,7 +6294,7 @@ function computeTextMarks(spec, scales) {
6207
6294
  }
6208
6295
  const text = String(row[textChannel.field] ?? "");
6209
6296
  if (!text) continue;
6210
- const color2 = getRepresentativeColor7(
6297
+ const color2 = getRepresentativeColor8(
6211
6298
  colorField ? getColor(scales, String(row[colorField] ?? "__default__")) : getColor(scales, "__default__")
6212
6299
  );
6213
6300
  const fontSize = sizeEncoding ? Math.max(8, Math.min(48, Number(row[sizeEncoding.field]) || 12)) : 12;
@@ -6234,7 +6321,7 @@ var textRenderer = (spec, scales, _chartArea, _strategy, _theme) => {
6234
6321
  };
6235
6322
 
6236
6323
  // src/charts/tick/index.ts
6237
- import { getRepresentativeColor as getRepresentativeColor8 } from "@opendata-ai/openchart-core";
6324
+ import { getRepresentativeColor as getRepresentativeColor9 } from "@opendata-ai/openchart-core";
6238
6325
  var DEFAULT_TICK_LENGTH = 18;
6239
6326
  function computeTickMarks(spec, scales, _chartArea) {
6240
6327
  const encoding = spec.encoding;
@@ -6250,7 +6337,7 @@ function computeTickMarks(spec, scales, _chartArea) {
6250
6337
  const xVal = scaleValue(scales.x.scale, scales.x.type, row[xChannel.field]);
6251
6338
  const yVal = scaleValue(scales.y.scale, scales.y.type, row[yChannel.field]);
6252
6339
  if (xVal == null || yVal == null) continue;
6253
- const color2 = getRepresentativeColor8(
6340
+ const color2 = getRepresentativeColor9(
6254
6341
  colorField ? getColor(scales, String(row[colorField] ?? "__default__")) : getColor(scales, "__default__")
6255
6342
  );
6256
6343
  const aria = {
@@ -6668,7 +6755,7 @@ function validateChartSpec(spec, errors) {
6668
6755
  message: `Spec error: encoding.${channel} must have a "field" string`,
6669
6756
  path: `encoding.${channel}.field`,
6670
6757
  code: "MISSING_FIELD",
6671
- suggestion: `Add a field name from your data columns: ${availableColumns}`
6758
+ suggestion: `For constant colors, use mark.fill (e.g., mark: { type: "bar", fill: "#1b7fa3" }) instead of encoding.${channel}. Encoding channels require a data field: ${availableColumns}`
6672
6759
  });
6673
6760
  continue;
6674
6761
  }
@@ -7681,16 +7768,16 @@ var DEFAULT_COLLISION_PADDING = 5;
7681
7768
 
7682
7769
  // src/layout/axes.ts
7683
7770
  import {
7684
- abbreviateNumber as abbreviateNumber3,
7685
- buildD3Formatter as buildD3Formatter3,
7771
+ abbreviateNumber as abbreviateNumber6,
7772
+ buildD3Formatter as buildD3Formatter4,
7686
7773
  buildTemporalFormatter,
7687
7774
  estimateTextWidth as estimateTextWidth7,
7688
7775
  formatDate,
7689
- formatNumber as formatNumber3
7776
+ formatNumber as formatNumber6
7690
7777
  } from "@opendata-ai/openchart-core";
7691
7778
  var TICK_COUNTS = {
7692
- full: 8,
7693
- reduced: 5,
7779
+ full: 10,
7780
+ reduced: 7,
7694
7781
  minimal: 3
7695
7782
  };
7696
7783
  var HEIGHT_MINIMAL_THRESHOLD = 120;
@@ -7803,11 +7890,11 @@ function formatTickLabel(value2, resolvedScale) {
7803
7890
  if (NUMERIC_SCALE_TYPES.has(resolvedScale.type)) {
7804
7891
  const num = value2;
7805
7892
  if (formatStr) {
7806
- const fmt = buildD3Formatter3(formatStr);
7893
+ const fmt = buildD3Formatter4(formatStr);
7807
7894
  if (fmt) return fmt(num);
7808
7895
  }
7809
- if (Math.abs(num) >= 1e3) return abbreviateNumber3(num);
7810
- return formatNumber3(num);
7896
+ if (Math.abs(num) >= 1e3) return abbreviateNumber6(num);
7897
+ return formatNumber6(num);
7811
7898
  }
7812
7899
  return String(value2);
7813
7900
  }
@@ -8218,7 +8305,7 @@ function uniqueStrings(values) {
8218
8305
  return result;
8219
8306
  }
8220
8307
  function applyCategoricalSort(values, sort) {
8221
- if (sort === null) return values;
8308
+ if (!sort) return values;
8222
8309
  const sorted = [...values].sort((a, b) => a.localeCompare(b, void 0, { numeric: true }));
8223
8310
  if (sort === "descending") sorted.reverse();
8224
8311
  return sorted;
@@ -8236,7 +8323,7 @@ function buildTimeScale(channel, data, rangeStart, rangeEnd) {
8236
8323
  const values = parseDates(fieldValues(data, channel.field));
8237
8324
  const domain = channel.scale?.domain ? [new Date(channel.scale.domain[0]), new Date(channel.scale.domain[1])] : extent(values);
8238
8325
  const scale = time().domain(domain).range([rangeStart, rangeEnd]);
8239
- if (channel.scale?.nice !== false) {
8326
+ if (!channel.scale?.domain && channel.scale?.nice === true) {
8240
8327
  scale.nice();
8241
8328
  }
8242
8329
  applyContinuousConfig(scale, channel);
@@ -8246,7 +8333,7 @@ function buildUtcScale(channel, data, rangeStart, rangeEnd) {
8246
8333
  const values = parseDates(fieldValues(data, channel.field));
8247
8334
  const domain = channel.scale?.domain ? [new Date(channel.scale.domain[0]), new Date(channel.scale.domain[1])] : extent(values);
8248
8335
  const scale = utcTime().domain(domain).range([rangeStart, rangeEnd]);
8249
- if (channel.scale?.nice !== false) {
8336
+ if (!channel.scale?.domain && channel.scale?.nice === true) {
8250
8337
  scale.nice();
8251
8338
  }
8252
8339
  applyContinuousConfig(scale, channel);
@@ -8269,7 +8356,7 @@ function buildLinearScale(channel, data, rangeStart, rangeEnd) {
8269
8356
  }
8270
8357
  }
8271
8358
  const scale = linear2().domain([domainMin, domainMax]).range([rangeStart, rangeEnd]);
8272
- if (channel.scale?.nice !== false) {
8359
+ if (!channel.scale?.domain && channel.scale?.nice !== false) {
8273
8360
  scale.nice();
8274
8361
  }
8275
8362
  applyContinuousConfig(scale, channel);
@@ -8283,7 +8370,7 @@ function buildLogScale(channel, data, rangeStart, rangeEnd) {
8283
8370
  if (channel.scale?.base !== void 0) {
8284
8371
  scale.base(channel.scale.base);
8285
8372
  }
8286
- if (channel.scale?.nice !== false) {
8373
+ if (!channel.scale?.domain && channel.scale?.nice !== false) {
8287
8374
  scale.nice();
8288
8375
  }
8289
8376
  applyContinuousConfig(scale, channel);
@@ -8307,7 +8394,7 @@ function buildPowScale(channel, data, rangeStart, rangeEnd) {
8307
8394
  if (channel.scale?.exponent !== void 0) {
8308
8395
  scale.exponent(channel.scale.exponent);
8309
8396
  }
8310
- if (channel.scale?.nice !== false) {
8397
+ if (!channel.scale?.domain && channel.scale?.nice !== false) {
8311
8398
  scale.nice();
8312
8399
  }
8313
8400
  applyContinuousConfig(scale, channel);
@@ -8328,7 +8415,7 @@ function buildSqrtScale(channel, data, rangeStart, rangeEnd) {
8328
8415
  }
8329
8416
  }
8330
8417
  const scale = sqrt2().domain([domainMin, domainMax]).range([rangeStart, rangeEnd]);
8331
- if (channel.scale?.nice !== false) {
8418
+ if (!channel.scale?.domain && channel.scale?.nice !== false) {
8332
8419
  scale.nice();
8333
8420
  }
8334
8421
  applyContinuousConfig(scale, channel);
@@ -8352,7 +8439,7 @@ function buildSymlogScale(channel, data, rangeStart, rangeEnd) {
8352
8439
  if (channel.scale?.constant !== void 0) {
8353
8440
  scale.constant(channel.scale.constant);
8354
8441
  }
8355
- if (channel.scale?.nice !== false) {
8442
+ if (!channel.scale?.domain && channel.scale?.nice !== false) {
8356
8443
  scale.nice();
8357
8444
  }
8358
8445
  applyContinuousConfig(scale, channel);
@@ -8738,7 +8825,7 @@ function computeLegend(spec, strategy, theme, chartArea, watermark = true) {
8738
8825
  1,
8739
8826
  Math.floor((maxLegendHeight - LEGEND_PADDING * 2) / (entryHeight + 4))
8740
8827
  );
8741
- const maxEntries = spec.legend?.symbolLimit != null ? Math.min(Math.max(1, spec.legend.symbolLimit), maxFromSpace) : maxFromSpace;
8828
+ const maxEntries = spec.legend?.symbolLimit != null ? Math.max(1, spec.legend.symbolLimit) : maxFromSpace;
8742
8829
  if (entries.length > maxEntries) {
8743
8830
  entries = truncateEntries(entries, maxEntries);
8744
8831
  }
@@ -8813,10 +8900,10 @@ function computeLegend(spec, strategy, theme, chartArea, watermark = true) {
8813
8900
  // src/sankey/compile-sankey.ts
8814
8901
  import {
8815
8902
  adaptTheme as adaptTheme2,
8816
- buildD3Formatter as buildD3Formatter4,
8903
+ buildD3Formatter as buildD3Formatter5,
8817
8904
  computeChrome as computeChrome3,
8818
8905
  estimateTextWidth as estimateTextWidth10,
8819
- formatNumber as formatNumber4,
8906
+ formatNumber as formatNumber7,
8820
8907
  resolveTheme as resolveTheme2
8821
8908
  } from "@opendata-ai/openchart-core";
8822
8909
 
@@ -9670,10 +9757,10 @@ function buildSankeyLegend(nodeColorMap, colorField, data, sourceField, targetFi
9670
9757
  }
9671
9758
  function formatFlowValue(value2, valueFormat) {
9672
9759
  if (valueFormat) {
9673
- const fmt = buildD3Formatter4(valueFormat);
9760
+ const fmt = buildD3Formatter5(valueFormat);
9674
9761
  if (fmt) return fmt(value2);
9675
9762
  }
9676
- return formatNumber4(value2);
9763
+ return formatNumber7(value2);
9677
9764
  }
9678
9765
  function buildTooltipDescriptors(nodes, links, valueFormat) {
9679
9766
  const descriptors = /* @__PURE__ */ new Map();
@@ -9850,7 +9937,7 @@ function computeCategoryColors(data, column, theme, darkMode) {
9850
9937
  }
9851
9938
 
9852
9939
  // src/tables/format-cells.ts
9853
- import { buildD3Formatter as buildD3Formatter5, formatDate as formatDate2, formatNumber as formatNumber5 } from "@opendata-ai/openchart-core";
9940
+ import { buildD3Formatter as buildD3Formatter6, formatDate as formatDate2, formatNumber as formatNumber8 } from "@opendata-ai/openchart-core";
9854
9941
  function isNumericValue(value2) {
9855
9942
  if (typeof value2 === "number") return Number.isFinite(value2);
9856
9943
  return false;
@@ -9869,7 +9956,7 @@ function formatCell(value2, column) {
9869
9956
  };
9870
9957
  }
9871
9958
  if (column.format && isNumericValue(value2)) {
9872
- const formatter = buildD3Formatter5(column.format);
9959
+ const formatter = buildD3Formatter6(column.format);
9873
9960
  if (formatter) {
9874
9961
  return {
9875
9962
  value: value2,
@@ -9881,7 +9968,7 @@ function formatCell(value2, column) {
9881
9968
  if (isNumericValue(value2)) {
9882
9969
  return {
9883
9970
  value: value2,
9884
- formattedValue: formatNumber5(value2),
9971
+ formattedValue: formatNumber8(value2),
9885
9972
  style
9886
9973
  };
9887
9974
  }
@@ -9901,13 +9988,13 @@ function formatCell(value2, column) {
9901
9988
  function formatValueForSearch(value2, column) {
9902
9989
  if (value2 == null) return "";
9903
9990
  if (column.format && isNumericValue(value2)) {
9904
- const formatter = buildD3Formatter5(column.format);
9991
+ const formatter = buildD3Formatter6(column.format);
9905
9992
  if (formatter) {
9906
9993
  return formatter(value2);
9907
9994
  }
9908
9995
  }
9909
9996
  if (isNumericValue(value2)) {
9910
- return formatNumber5(value2);
9997
+ return formatNumber8(value2);
9911
9998
  }
9912
9999
  return String(value2);
9913
10000
  }
@@ -10382,8 +10469,8 @@ function compileTableLayout(spec, options, theme) {
10382
10469
  import {
10383
10470
  buildTemporalFormatter as buildTemporalFormatter2,
10384
10471
  formatDate as formatDate3,
10385
- formatNumber as formatNumber6,
10386
- getRepresentativeColor as getRepresentativeColor9
10472
+ formatNumber as formatNumber9,
10473
+ getRepresentativeColor as getRepresentativeColor10
10387
10474
  } from "@opendata-ai/openchart-core";
10388
10475
  function formatValue(value2, fieldType, format2) {
10389
10476
  if (value2 == null) return "";
@@ -10397,10 +10484,10 @@ function formatValue(value2, fieldType, format2) {
10397
10484
  try {
10398
10485
  return format(format2)(value2);
10399
10486
  } catch {
10400
- return formatNumber6(value2);
10487
+ return formatNumber9(value2);
10401
10488
  }
10402
10489
  }
10403
- return formatNumber6(value2);
10490
+ return formatNumber9(value2);
10404
10491
  }
10405
10492
  return String(value2);
10406
10493
  }
@@ -10422,11 +10509,22 @@ function buildFields(row, encoding, color2) {
10422
10509
  return buildExplicitTooltipFields(row, channels);
10423
10510
  }
10424
10511
  const fields = [];
10512
+ if (encoding.color && "field" in encoding.color) {
10513
+ fields.push({
10514
+ label: resolveLabel(encoding.color),
10515
+ value: formatValue(
10516
+ row[encoding.color.field],
10517
+ encoding.color.type,
10518
+ resolveFormat(encoding.color)
10519
+ ),
10520
+ color: color2
10521
+ });
10522
+ }
10425
10523
  if (encoding.y) {
10426
10524
  fields.push({
10427
10525
  label: resolveLabel(encoding.y),
10428
10526
  value: formatValue(row[encoding.y.field], encoding.y.type, resolveFormat(encoding.y)),
10429
- color: color2
10527
+ color: encoding.color ? void 0 : color2
10430
10528
  });
10431
10529
  }
10432
10530
  if (encoding.x) {
@@ -10460,6 +10558,16 @@ function getTooltipTitle(row, encoding) {
10460
10558
  if (encoding.y?.type === "nominal" || encoding.y?.type === "ordinal") {
10461
10559
  return String(row[encoding.y.field] ?? "");
10462
10560
  }
10561
+ if (encoding.x?.type === "quantitative" && encoding.y?.type === "quantitative") {
10562
+ const encodedFields = new Set(
10563
+ [encoding.x, encoding.y, encoding.color, encoding.size, encoding.detail].filter((ch) => !!ch && "field" in ch).map((ch) => ch.field)
10564
+ );
10565
+ for (const [key, value2] of Object.entries(row)) {
10566
+ if (!encodedFields.has(key) && typeof value2 === "string") {
10567
+ return value2;
10568
+ }
10569
+ }
10570
+ }
10463
10571
  if (encoding.color && "field" in encoding.color) {
10464
10572
  return String(row[encoding.color.field] ?? "");
10465
10573
  }
@@ -10478,12 +10586,12 @@ function tooltipsForLine(mark, encoding, _markIndex) {
10478
10586
  }
10479
10587
  function tooltipsForPoint(mark, encoding, markIndex) {
10480
10588
  const title = getTooltipTitle(mark.data, encoding);
10481
- const fields = buildFields(mark.data, encoding, getRepresentativeColor9(mark.fill));
10589
+ const fields = buildFields(mark.data, encoding, getRepresentativeColor10(mark.fill));
10482
10590
  return [[`point-${markIndex}`, { title, fields }]];
10483
10591
  }
10484
10592
  function tooltipsForRect(mark, encoding, markIndex) {
10485
10593
  const title = getTooltipTitle(mark.data, encoding);
10486
- const fields = buildFields(mark.data, encoding, getRepresentativeColor9(mark.fill));
10594
+ const fields = buildFields(mark.data, encoding, getRepresentativeColor10(mark.fill));
10487
10595
  return [[`rect-${markIndex}`, { title, fields }]];
10488
10596
  }
10489
10597
  function tooltipsForArc(mark, encoding, markIndex) {
@@ -10496,14 +10604,14 @@ function tooltipsForArc(mark, encoding, markIndex) {
10496
10604
  fields.push({
10497
10605
  label: categoryName,
10498
10606
  value: formatValue(row[encoding.y.field], encoding.y.type, resolveFormat(encoding.y)),
10499
- color: getRepresentativeColor9(mark.fill)
10607
+ color: getRepresentativeColor10(mark.fill)
10500
10608
  });
10501
10609
  }
10502
10610
  } else if (encoding.y) {
10503
10611
  fields.push({
10504
10612
  label: resolveLabel(encoding.y),
10505
10613
  value: formatValue(row[encoding.y.field], encoding.y.type, resolveFormat(encoding.y)),
10506
- color: getRepresentativeColor9(mark.fill)
10614
+ color: getRepresentativeColor10(mark.fill)
10507
10615
  });
10508
10616
  }
10509
10617
  const title = colorEnc ? String(row[colorEnc.field] ?? "") : void 0;
@@ -10514,7 +10622,7 @@ function tooltipsForArea(mark, encoding, _markIndex) {
10514
10622
  for (const dp of mark.dataPoints) {
10515
10623
  dp.tooltip = {
10516
10624
  title: getTooltipTitle(dp.datum, encoding),
10517
- fields: buildFields(dp.datum, encoding, getRepresentativeColor9(mark.fill))
10625
+ fields: buildFields(dp.datum, encoding, getRepresentativeColor10(mark.fill))
10518
10626
  };
10519
10627
  }
10520
10628
  }
@@ -11125,7 +11233,8 @@ function compileChart(spec, options) {
11125
11233
  height: options.height
11126
11234
  },
11127
11235
  animation: resolvedAnimation,
11128
- watermark
11236
+ watermark,
11237
+ measureText: options.measureText
11129
11238
  };
11130
11239
  }
11131
11240
  function compileLayer(spec, options) {