@opendata-ai/openchart-engine 6.13.0 → 6.15.0

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
@@ -968,11 +968,17 @@ function computeSimpleBars(data, valueField, categoryField, xScale, yScale, band
968
968
 
969
969
  // src/charts/bar/labels.ts
970
970
  import {
971
+ abbreviateNumber as abbreviateNumber2,
971
972
  buildD3Formatter,
972
973
  estimateTextWidth as estimateTextWidth2,
974
+ formatNumber as formatNumber2,
973
975
  getRepresentativeColor,
974
976
  resolveCollisions
975
977
  } from "@opendata-ai/openchart-core";
978
+ function formatBarValue2(value2) {
979
+ if (Math.abs(value2) >= 1e3) return abbreviateNumber2(value2);
980
+ return formatNumber2(value2);
981
+ }
976
982
  var SUFFIX_MULTIPLIERS = {
977
983
  K: 1e3,
978
984
  M: 1e6,
@@ -998,21 +1004,30 @@ var LABEL_FONT_SIZE = 11;
998
1004
  var LABEL_FONT_WEIGHT = 600;
999
1005
  var LABEL_PADDING = 6;
1000
1006
  var MIN_WIDTH_FOR_INSIDE_LABEL = 40;
1001
- function computeBarLabels(marks, _chartArea, density = "auto", labelFormat, labelPrefix) {
1007
+ function computeBarLabels(marks, _chartArea, density = "auto", labelFormat, labelPrefix, valueField) {
1002
1008
  if (density === "none") return [];
1003
1009
  const targetMarks = density === "endpoints" && marks.length > 1 ? [marks[0], marks[marks.length - 1]] : marks;
1004
1010
  const candidates = [];
1005
1011
  const fitsInSegment = [];
1006
1012
  const formatter = buildD3Formatter(labelFormat);
1007
1013
  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);
1014
+ let valuePart;
1015
+ const rawNum = valueField != null ? Number(mark.data[valueField]) : NaN;
1016
+ if (formatter && Number.isFinite(rawNum)) {
1017
+ valuePart = formatter(rawNum);
1018
+ } else if (Number.isFinite(rawNum)) {
1019
+ valuePart = formatBarValue2(rawNum);
1020
+ } else {
1021
+ const ariaLabel = mark.aria.label;
1022
+ const lastColon = ariaLabel.lastIndexOf(":");
1023
+ const rawValue = lastColon >= 0 ? ariaLabel.slice(lastColon + 1).trim() : "";
1024
+ if (!rawValue) continue;
1025
+ if (formatter) {
1026
+ const num = parseDisplayNumber(rawValue);
1027
+ valuePart = !Number.isNaN(num) ? formatter(num) : rawValue;
1028
+ } else {
1029
+ valuePart = rawValue;
1030
+ }
1016
1031
  }
1017
1032
  if (labelPrefix) valuePart = labelPrefix + valuePart;
1018
1033
  const textWidth = estimateTextWidth2(valuePart, LABEL_FONT_SIZE, LABEL_FONT_WEIGHT);
@@ -1097,12 +1112,14 @@ function computeBarLabels(marks, _chartArea, density = "auto", labelFormat, labe
1097
1112
  // src/charts/bar/index.ts
1098
1113
  var barRenderer = (spec, scales, chartArea, strategy, _theme) => {
1099
1114
  const marks = computeBarMarks(spec, scales, chartArea, strategy);
1115
+ const valueField = spec.encoding?.x && "field" in spec.encoding.x ? spec.encoding.x.field : void 0;
1100
1116
  const labels = computeBarLabels(
1101
1117
  marks,
1102
1118
  chartArea,
1103
1119
  spec.labels.density,
1104
1120
  spec.labels.format,
1105
- spec.labels.prefix
1121
+ spec.labels.prefix,
1122
+ valueField
1106
1123
  );
1107
1124
  for (let i = 0; i < marks.length && i < labels.length; i++) {
1108
1125
  marks[i].label = labels[i];
@@ -1111,11 +1128,11 @@ var barRenderer = (spec, scales, chartArea, strategy, _theme) => {
1111
1128
  };
1112
1129
 
1113
1130
  // src/charts/column/compute.ts
1114
- import { abbreviateNumber as abbreviateNumber2, formatNumber as formatNumber2, isGradientDef as isGradientDef2 } from "@opendata-ai/openchart-core";
1131
+ import { abbreviateNumber as abbreviateNumber3, formatNumber as formatNumber3, isGradientDef as isGradientDef2 } from "@opendata-ai/openchart-core";
1115
1132
  var MIN_COLUMN_HEIGHT = 1;
1116
1133
  function formatColumnValue(value2) {
1117
- if (Math.abs(value2) >= 1e3) return abbreviateNumber2(value2);
1118
- return formatNumber2(value2);
1134
+ if (Math.abs(value2) >= 1e3) return abbreviateNumber3(value2);
1135
+ return formatNumber3(value2);
1119
1136
  }
1120
1137
  function computeColumnMarks(spec, scales, _chartArea, _strategy) {
1121
1138
  const encoding = spec.encoding;
@@ -1359,28 +1376,43 @@ function computeStackedColumns(data, categoryField, valueField, colorField, xSca
1359
1376
 
1360
1377
  // src/charts/column/labels.ts
1361
1378
  import {
1379
+ abbreviateNumber as abbreviateNumber4,
1362
1380
  buildD3Formatter as buildD3Formatter2,
1363
1381
  estimateTextWidth as estimateTextWidth3,
1382
+ formatNumber as formatNumber4,
1364
1383
  getRepresentativeColor as getRepresentativeColor2,
1365
1384
  resolveCollisions as resolveCollisions2
1366
1385
  } from "@opendata-ai/openchart-core";
1386
+ function formatColumnValue2(value2) {
1387
+ if (Math.abs(value2) >= 1e3) return abbreviateNumber4(value2);
1388
+ return formatNumber4(value2);
1389
+ }
1367
1390
  var LABEL_FONT_SIZE2 = 10;
1368
1391
  var LABEL_FONT_WEIGHT2 = 600;
1369
1392
  var LABEL_OFFSET_Y = 6;
1370
- function computeColumnLabels(marks, _chartArea, density = "auto", labelFormat, labelPrefix) {
1393
+ function computeColumnLabels(marks, _chartArea, density = "auto", labelFormat, labelPrefix, valueField) {
1371
1394
  if (density === "none") return [];
1372
1395
  const targetMarks = density === "endpoints" && marks.length > 1 ? [marks[0], marks[marks.length - 1]] : marks;
1373
1396
  const formatter = buildD3Formatter2(labelFormat);
1374
1397
  const candidates = [];
1375
1398
  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);
1399
+ let valuePart;
1400
+ const rawNum = valueField != null ? Number(mark.data[valueField]) : NaN;
1401
+ if (formatter && Number.isFinite(rawNum)) {
1402
+ valuePart = formatter(rawNum);
1403
+ } else if (Number.isFinite(rawNum)) {
1404
+ valuePart = formatColumnValue2(rawNum);
1405
+ } else {
1406
+ const ariaLabel = mark.aria.label;
1407
+ const lastColon = ariaLabel.lastIndexOf(":");
1408
+ const rawValue = lastColon >= 0 ? ariaLabel.slice(lastColon + 1).trim() : "";
1409
+ if (!rawValue) continue;
1410
+ if (formatter) {
1411
+ const num = Number(rawValue.replace(/[^0-9.-]/g, ""));
1412
+ valuePart = !Number.isNaN(num) ? formatter(num) : rawValue;
1413
+ } else {
1414
+ valuePart = rawValue;
1415
+ }
1384
1416
  }
1385
1417
  if (labelPrefix) valuePart = labelPrefix + valuePart;
1386
1418
  const numericValue = parseFloat(valuePart);
@@ -1423,12 +1455,14 @@ function computeColumnLabels(marks, _chartArea, density = "auto", labelFormat, l
1423
1455
  // src/charts/column/index.ts
1424
1456
  var columnRenderer = (spec, scales, chartArea, strategy, _theme) => {
1425
1457
  const marks = computeColumnMarks(spec, scales, chartArea, strategy);
1458
+ const valueField = spec.encoding?.y && "field" in spec.encoding.y ? spec.encoding.y.field : void 0;
1426
1459
  const labels = computeColumnLabels(
1427
1460
  marks,
1428
1461
  chartArea,
1429
1462
  spec.labels.density,
1430
1463
  spec.labels.format,
1431
- spec.labels.prefix
1464
+ spec.labels.prefix,
1465
+ valueField
1432
1466
  );
1433
1467
  for (let i = 0; i < marks.length && i < labels.length; i++) {
1434
1468
  marks[i].label = labels[i];
@@ -1586,22 +1620,42 @@ function computeLollipopMarks(data, valueField, categoryField, xScale, yScale, b
1586
1620
 
1587
1621
  // src/charts/dot/labels.ts
1588
1622
  import {
1623
+ abbreviateNumber as abbreviateNumber5,
1624
+ buildD3Formatter as buildD3Formatter3,
1589
1625
  estimateTextWidth as estimateTextWidth4,
1626
+ formatNumber as formatNumber5,
1590
1627
  getRepresentativeColor as getRepresentativeColor3,
1591
1628
  resolveCollisions as resolveCollisions3
1592
1629
  } from "@opendata-ai/openchart-core";
1630
+ function formatDotValue(value2) {
1631
+ if (Math.abs(value2) >= 1e3) return abbreviateNumber5(value2);
1632
+ return formatNumber5(value2);
1633
+ }
1593
1634
  var LABEL_FONT_SIZE3 = 11;
1594
1635
  var LABEL_FONT_WEIGHT3 = 600;
1595
1636
  var LABEL_OFFSET_X = 10;
1596
- function computeDotLabels(marks, _chartArea, density = "auto", labelPrefix) {
1637
+ function computeDotLabels(marks, _chartArea, density = "auto", labelPrefix, labelFormat, valueField) {
1597
1638
  if (density === "none") return [];
1598
1639
  const targetMarks = density === "endpoints" && marks.length > 1 ? [marks[0], marks[marks.length - 1]] : marks;
1640
+ const formatter = buildD3Formatter3(labelFormat);
1599
1641
  const candidates = [];
1600
1642
  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;
1643
+ let valuePart;
1644
+ const rawNum = valueField != null ? Number(mark.data[valueField]) : NaN;
1645
+ if (formatter && Number.isFinite(rawNum)) {
1646
+ valuePart = formatter(rawNum);
1647
+ } else if (Number.isFinite(rawNum)) {
1648
+ valuePart = formatDotValue(rawNum);
1649
+ } else {
1650
+ const ariaLabel = mark.aria.label;
1651
+ const lastColon = ariaLabel.lastIndexOf(":");
1652
+ valuePart = lastColon >= 0 ? ariaLabel.slice(lastColon + 1).trim() : "";
1653
+ if (!valuePart) continue;
1654
+ if (formatter) {
1655
+ const num = Number(valuePart.replace(/[^0-9.-]/g, ""));
1656
+ if (!Number.isNaN(num)) valuePart = formatter(num);
1657
+ }
1658
+ }
1605
1659
  if (labelPrefix) valuePart = labelPrefix + valuePart;
1606
1660
  const textWidth = estimateTextWidth4(valuePart, LABEL_FONT_SIZE3, LABEL_FONT_WEIGHT3);
1607
1661
  const textHeight = LABEL_FONT_SIZE3 * 1.2;
@@ -1640,7 +1694,15 @@ function computeDotLabels(marks, _chartArea, density = "auto", labelPrefix) {
1640
1694
  var dotRenderer = (spec, scales, chartArea, strategy, _theme) => {
1641
1695
  const marks = computeDotMarks(spec, scales, chartArea, strategy);
1642
1696
  const pointMarks = marks.filter((m) => m.type === "point");
1643
- const labels = computeDotLabels(pointMarks, chartArea, spec.labels.density, spec.labels.prefix);
1697
+ const valueField = spec.encoding?.x && "field" in spec.encoding.x ? spec.encoding.x.field : void 0;
1698
+ const labels = computeDotLabels(
1699
+ pointMarks,
1700
+ chartArea,
1701
+ spec.labels.density,
1702
+ spec.labels.prefix,
1703
+ spec.labels.format,
1704
+ valueField
1705
+ );
1644
1706
  let labelIdx = 0;
1645
1707
  for (const mark of marks) {
1646
1708
  if (mark.type === "point" && labelIdx < labels.length) {
@@ -1651,6 +1713,9 @@ var dotRenderer = (spec, scales, chartArea, strategy, _theme) => {
1651
1713
  return marks;
1652
1714
  };
1653
1715
 
1716
+ // src/charts/line/index.ts
1717
+ import { getRepresentativeColor as getRepresentativeColor6 } from "@opendata-ai/openchart-core";
1718
+
1654
1719
  // src/charts/line/area.ts
1655
1720
  import { getRepresentativeColor as getRepresentativeColor4 } from "@opendata-ai/openchart-core";
1656
1721
 
@@ -2597,7 +2662,7 @@ function computeSingleArea(spec, scales, _chartArea) {
2597
2662
  const marks = [];
2598
2663
  for (const [seriesKey, rows] of groups) {
2599
2664
  const color2 = getColor(scales, seriesKey);
2600
- const sortedRows = sortByField(rows, xChannel.field);
2665
+ const sortedRows = xChannel.type === "nominal" || xChannel.type === "ordinal" ? rows : sortByField(rows, xChannel.field);
2601
2666
  const validPoints = [];
2602
2667
  for (const row of sortedRows) {
2603
2668
  const xVal = scaleValue(scales.x.scale, scales.x.type, row[xChannel.field]);
@@ -2646,7 +2711,7 @@ function computeStackedArea(spec, scales, chartArea) {
2646
2711
  if (!xChannel || !yChannel || !scales.x || !scales.y || !colorField) {
2647
2712
  return computeSingleArea(spec, scales, chartArea);
2648
2713
  }
2649
- const sortedData = sortByField(spec.data, xChannel.field);
2714
+ const sortedData = xChannel.type === "nominal" || xChannel.type === "ordinal" ? spec.data : sortByField(spec.data, xChannel.field);
2650
2715
  const seriesKeys = /* @__PURE__ */ new Set();
2651
2716
  const xValueSet = /* @__PURE__ */ new Set();
2652
2717
  const rowsByXSeries = /* @__PURE__ */ new Map();
@@ -2763,7 +2828,7 @@ function computeLineMarks(spec, scales, _chartArea, _strategy) {
2763
2828
  for (const [seriesKey, rows] of groups) {
2764
2829
  const color2 = isSequentialColor ? getSequentialColor(scales, _getMidValue(rows, sequentialColorField)) : getColor(scales, seriesKey);
2765
2830
  const strokeColor = getRepresentativeColor5(color2);
2766
- const sortedRows = sortByField(rows, xChannel.field);
2831
+ const sortedRows = xChannel.type === "nominal" || xChannel.type === "ordinal" ? rows : sortByField(rows, xChannel.field);
2767
2832
  const pointsWithData = [];
2768
2833
  const segments = [];
2769
2834
  let currentSegment = [];
@@ -2951,9 +3016,24 @@ var lineRenderer = (spec, scales, chartArea, strategy, _theme) => {
2951
3016
  };
2952
3017
  var areaRenderer = (spec, scales, chartArea, strategy, _theme) => {
2953
3018
  const areas = computeAreaMarks(spec, scales, chartArea);
2954
- const lines = computeLineMarks(spec, scales, chartArea, strategy);
3019
+ const encoding = spec.encoding;
3020
+ const hasColor = !!(encoding.color && "field" in encoding.color);
3021
+ const lines = hasColor ? linesFromAreas(areas) : computeLineMarks(spec, scales, chartArea, strategy);
2955
3022
  return [...areas, ...lines];
2956
3023
  };
3024
+ function linesFromAreas(areas) {
3025
+ return areas.map((a) => ({
3026
+ type: "line",
3027
+ points: a.topPoints,
3028
+ path: a.topPath,
3029
+ stroke: getRepresentativeColor6(a.fill),
3030
+ strokeWidth: a.strokeWidth ?? 1,
3031
+ seriesKey: a.seriesKey,
3032
+ data: a.data,
3033
+ dataPoints: a.dataPoints,
3034
+ aria: { label: `${a.seriesKey ?? "Series"}: line with ${a.topPoints.length} data points` }
3035
+ }));
3036
+ }
2957
3037
 
2958
3038
  // src/charts/pie/compute.ts
2959
3039
  import { isConditionalDef, isGradientDef as isGradientDef3 } from "@opendata-ai/openchart-core";
@@ -3329,7 +3409,7 @@ function clearRenderers() {
3329
3409
  }
3330
3410
 
3331
3411
  // src/charts/rule/index.ts
3332
- import { getRepresentativeColor as getRepresentativeColor6 } from "@opendata-ai/openchart-core";
3412
+ import { getRepresentativeColor as getRepresentativeColor7 } from "@opendata-ai/openchart-core";
3333
3413
  function computeRuleMarks(spec, scales, chartArea) {
3334
3414
  const encoding = spec.encoding;
3335
3415
  const xChannel = encoding.x;
@@ -3372,7 +3452,7 @@ function computeRuleMarks(spec, scales, chartArea) {
3372
3452
  const y2Val = scaleValue(scales.y.scale, scales.y.type, row[y2Channel.field]);
3373
3453
  if (y2Val != null) y2 = y2Val;
3374
3454
  }
3375
- const color2 = getRepresentativeColor6(
3455
+ const color2 = getRepresentativeColor7(
3376
3456
  colorField ? getColor(scales, String(row[colorField] ?? "__default__")) : getColor(scales, "__default__")
3377
3457
  );
3378
3458
  const strokeDashEncoding = encoding.strokeDash && "field" in encoding.strokeDash ? encoding.strokeDash : void 0;
@@ -6181,7 +6261,7 @@ var scatterRenderer = (spec, scales, chartArea, strategy, _theme) => {
6181
6261
  };
6182
6262
 
6183
6263
  // src/charts/text/index.ts
6184
- import { getRepresentativeColor as getRepresentativeColor7 } from "@opendata-ai/openchart-core";
6264
+ import { getRepresentativeColor as getRepresentativeColor8 } from "@opendata-ai/openchart-core";
6185
6265
  function computeTextMarks(spec, scales) {
6186
6266
  const encoding = spec.encoding;
6187
6267
  const xChannel = encoding.x;
@@ -6207,7 +6287,7 @@ function computeTextMarks(spec, scales) {
6207
6287
  }
6208
6288
  const text = String(row[textChannel.field] ?? "");
6209
6289
  if (!text) continue;
6210
- const color2 = getRepresentativeColor7(
6290
+ const color2 = getRepresentativeColor8(
6211
6291
  colorField ? getColor(scales, String(row[colorField] ?? "__default__")) : getColor(scales, "__default__")
6212
6292
  );
6213
6293
  const fontSize = sizeEncoding ? Math.max(8, Math.min(48, Number(row[sizeEncoding.field]) || 12)) : 12;
@@ -6234,7 +6314,7 @@ var textRenderer = (spec, scales, _chartArea, _strategy, _theme) => {
6234
6314
  };
6235
6315
 
6236
6316
  // src/charts/tick/index.ts
6237
- import { getRepresentativeColor as getRepresentativeColor8 } from "@opendata-ai/openchart-core";
6317
+ import { getRepresentativeColor as getRepresentativeColor9 } from "@opendata-ai/openchart-core";
6238
6318
  var DEFAULT_TICK_LENGTH = 18;
6239
6319
  function computeTickMarks(spec, scales, _chartArea) {
6240
6320
  const encoding = spec.encoding;
@@ -6250,7 +6330,7 @@ function computeTickMarks(spec, scales, _chartArea) {
6250
6330
  const xVal = scaleValue(scales.x.scale, scales.x.type, row[xChannel.field]);
6251
6331
  const yVal = scaleValue(scales.y.scale, scales.y.type, row[yChannel.field]);
6252
6332
  if (xVal == null || yVal == null) continue;
6253
- const color2 = getRepresentativeColor8(
6333
+ const color2 = getRepresentativeColor9(
6254
6334
  colorField ? getColor(scales, String(row[colorField] ?? "__default__")) : getColor(scales, "__default__")
6255
6335
  );
6256
6336
  const aria = {
@@ -7681,16 +7761,16 @@ var DEFAULT_COLLISION_PADDING = 5;
7681
7761
 
7682
7762
  // src/layout/axes.ts
7683
7763
  import {
7684
- abbreviateNumber as abbreviateNumber3,
7685
- buildD3Formatter as buildD3Formatter3,
7764
+ abbreviateNumber as abbreviateNumber6,
7765
+ buildD3Formatter as buildD3Formatter4,
7686
7766
  buildTemporalFormatter,
7687
7767
  estimateTextWidth as estimateTextWidth7,
7688
7768
  formatDate,
7689
- formatNumber as formatNumber3
7769
+ formatNumber as formatNumber6
7690
7770
  } from "@opendata-ai/openchart-core";
7691
7771
  var TICK_COUNTS = {
7692
- full: 8,
7693
- reduced: 5,
7772
+ full: 10,
7773
+ reduced: 7,
7694
7774
  minimal: 3
7695
7775
  };
7696
7776
  var HEIGHT_MINIMAL_THRESHOLD = 120;
@@ -7803,11 +7883,11 @@ function formatTickLabel(value2, resolvedScale) {
7803
7883
  if (NUMERIC_SCALE_TYPES.has(resolvedScale.type)) {
7804
7884
  const num = value2;
7805
7885
  if (formatStr) {
7806
- const fmt = buildD3Formatter3(formatStr);
7886
+ const fmt = buildD3Formatter4(formatStr);
7807
7887
  if (fmt) return fmt(num);
7808
7888
  }
7809
- if (Math.abs(num) >= 1e3) return abbreviateNumber3(num);
7810
- return formatNumber3(num);
7889
+ if (Math.abs(num) >= 1e3) return abbreviateNumber6(num);
7890
+ return formatNumber6(num);
7811
7891
  }
7812
7892
  return String(value2);
7813
7893
  }
@@ -8218,7 +8298,7 @@ function uniqueStrings(values) {
8218
8298
  return result;
8219
8299
  }
8220
8300
  function applyCategoricalSort(values, sort) {
8221
- if (sort === null) return values;
8301
+ if (!sort) return values;
8222
8302
  const sorted = [...values].sort((a, b) => a.localeCompare(b, void 0, { numeric: true }));
8223
8303
  if (sort === "descending") sorted.reverse();
8224
8304
  return sorted;
@@ -8236,7 +8316,7 @@ function buildTimeScale(channel, data, rangeStart, rangeEnd) {
8236
8316
  const values = parseDates(fieldValues(data, channel.field));
8237
8317
  const domain = channel.scale?.domain ? [new Date(channel.scale.domain[0]), new Date(channel.scale.domain[1])] : extent(values);
8238
8318
  const scale = time().domain(domain).range([rangeStart, rangeEnd]);
8239
- if (channel.scale?.nice !== false) {
8319
+ if (!channel.scale?.domain && channel.scale?.nice === true) {
8240
8320
  scale.nice();
8241
8321
  }
8242
8322
  applyContinuousConfig(scale, channel);
@@ -8246,7 +8326,7 @@ function buildUtcScale(channel, data, rangeStart, rangeEnd) {
8246
8326
  const values = parseDates(fieldValues(data, channel.field));
8247
8327
  const domain = channel.scale?.domain ? [new Date(channel.scale.domain[0]), new Date(channel.scale.domain[1])] : extent(values);
8248
8328
  const scale = utcTime().domain(domain).range([rangeStart, rangeEnd]);
8249
- if (channel.scale?.nice !== false) {
8329
+ if (!channel.scale?.domain && channel.scale?.nice === true) {
8250
8330
  scale.nice();
8251
8331
  }
8252
8332
  applyContinuousConfig(scale, channel);
@@ -8269,7 +8349,7 @@ function buildLinearScale(channel, data, rangeStart, rangeEnd) {
8269
8349
  }
8270
8350
  }
8271
8351
  const scale = linear2().domain([domainMin, domainMax]).range([rangeStart, rangeEnd]);
8272
- if (channel.scale?.nice !== false) {
8352
+ if (!channel.scale?.domain && channel.scale?.nice !== false) {
8273
8353
  scale.nice();
8274
8354
  }
8275
8355
  applyContinuousConfig(scale, channel);
@@ -8283,7 +8363,7 @@ function buildLogScale(channel, data, rangeStart, rangeEnd) {
8283
8363
  if (channel.scale?.base !== void 0) {
8284
8364
  scale.base(channel.scale.base);
8285
8365
  }
8286
- if (channel.scale?.nice !== false) {
8366
+ if (!channel.scale?.domain && channel.scale?.nice !== false) {
8287
8367
  scale.nice();
8288
8368
  }
8289
8369
  applyContinuousConfig(scale, channel);
@@ -8307,7 +8387,7 @@ function buildPowScale(channel, data, rangeStart, rangeEnd) {
8307
8387
  if (channel.scale?.exponent !== void 0) {
8308
8388
  scale.exponent(channel.scale.exponent);
8309
8389
  }
8310
- if (channel.scale?.nice !== false) {
8390
+ if (!channel.scale?.domain && channel.scale?.nice !== false) {
8311
8391
  scale.nice();
8312
8392
  }
8313
8393
  applyContinuousConfig(scale, channel);
@@ -8328,7 +8408,7 @@ function buildSqrtScale(channel, data, rangeStart, rangeEnd) {
8328
8408
  }
8329
8409
  }
8330
8410
  const scale = sqrt2().domain([domainMin, domainMax]).range([rangeStart, rangeEnd]);
8331
- if (channel.scale?.nice !== false) {
8411
+ if (!channel.scale?.domain && channel.scale?.nice !== false) {
8332
8412
  scale.nice();
8333
8413
  }
8334
8414
  applyContinuousConfig(scale, channel);
@@ -8352,7 +8432,7 @@ function buildSymlogScale(channel, data, rangeStart, rangeEnd) {
8352
8432
  if (channel.scale?.constant !== void 0) {
8353
8433
  scale.constant(channel.scale.constant);
8354
8434
  }
8355
- if (channel.scale?.nice !== false) {
8435
+ if (!channel.scale?.domain && channel.scale?.nice !== false) {
8356
8436
  scale.nice();
8357
8437
  }
8358
8438
  applyContinuousConfig(scale, channel);
@@ -8738,7 +8818,7 @@ function computeLegend(spec, strategy, theme, chartArea, watermark = true) {
8738
8818
  1,
8739
8819
  Math.floor((maxLegendHeight - LEGEND_PADDING * 2) / (entryHeight + 4))
8740
8820
  );
8741
- const maxEntries = spec.legend?.symbolLimit != null ? Math.min(Math.max(1, spec.legend.symbolLimit), maxFromSpace) : maxFromSpace;
8821
+ const maxEntries = spec.legend?.symbolLimit != null ? Math.max(1, spec.legend.symbolLimit) : maxFromSpace;
8742
8822
  if (entries.length > maxEntries) {
8743
8823
  entries = truncateEntries(entries, maxEntries);
8744
8824
  }
@@ -8813,10 +8893,10 @@ function computeLegend(spec, strategy, theme, chartArea, watermark = true) {
8813
8893
  // src/sankey/compile-sankey.ts
8814
8894
  import {
8815
8895
  adaptTheme as adaptTheme2,
8816
- buildD3Formatter as buildD3Formatter4,
8896
+ buildD3Formatter as buildD3Formatter5,
8817
8897
  computeChrome as computeChrome3,
8818
8898
  estimateTextWidth as estimateTextWidth10,
8819
- formatNumber as formatNumber4,
8899
+ formatNumber as formatNumber7,
8820
8900
  resolveTheme as resolveTheme2
8821
8901
  } from "@opendata-ai/openchart-core";
8822
8902
 
@@ -9670,10 +9750,10 @@ function buildSankeyLegend(nodeColorMap, colorField, data, sourceField, targetFi
9670
9750
  }
9671
9751
  function formatFlowValue(value2, valueFormat) {
9672
9752
  if (valueFormat) {
9673
- const fmt = buildD3Formatter4(valueFormat);
9753
+ const fmt = buildD3Formatter5(valueFormat);
9674
9754
  if (fmt) return fmt(value2);
9675
9755
  }
9676
- return formatNumber4(value2);
9756
+ return formatNumber7(value2);
9677
9757
  }
9678
9758
  function buildTooltipDescriptors(nodes, links, valueFormat) {
9679
9759
  const descriptors = /* @__PURE__ */ new Map();
@@ -9850,7 +9930,7 @@ function computeCategoryColors(data, column, theme, darkMode) {
9850
9930
  }
9851
9931
 
9852
9932
  // src/tables/format-cells.ts
9853
- import { buildD3Formatter as buildD3Formatter5, formatDate as formatDate2, formatNumber as formatNumber5 } from "@opendata-ai/openchart-core";
9933
+ import { buildD3Formatter as buildD3Formatter6, formatDate as formatDate2, formatNumber as formatNumber8 } from "@opendata-ai/openchart-core";
9854
9934
  function isNumericValue(value2) {
9855
9935
  if (typeof value2 === "number") return Number.isFinite(value2);
9856
9936
  return false;
@@ -9869,7 +9949,7 @@ function formatCell(value2, column) {
9869
9949
  };
9870
9950
  }
9871
9951
  if (column.format && isNumericValue(value2)) {
9872
- const formatter = buildD3Formatter5(column.format);
9952
+ const formatter = buildD3Formatter6(column.format);
9873
9953
  if (formatter) {
9874
9954
  return {
9875
9955
  value: value2,
@@ -9881,7 +9961,7 @@ function formatCell(value2, column) {
9881
9961
  if (isNumericValue(value2)) {
9882
9962
  return {
9883
9963
  value: value2,
9884
- formattedValue: formatNumber5(value2),
9964
+ formattedValue: formatNumber8(value2),
9885
9965
  style
9886
9966
  };
9887
9967
  }
@@ -9901,13 +9981,13 @@ function formatCell(value2, column) {
9901
9981
  function formatValueForSearch(value2, column) {
9902
9982
  if (value2 == null) return "";
9903
9983
  if (column.format && isNumericValue(value2)) {
9904
- const formatter = buildD3Formatter5(column.format);
9984
+ const formatter = buildD3Formatter6(column.format);
9905
9985
  if (formatter) {
9906
9986
  return formatter(value2);
9907
9987
  }
9908
9988
  }
9909
9989
  if (isNumericValue(value2)) {
9910
- return formatNumber5(value2);
9990
+ return formatNumber8(value2);
9911
9991
  }
9912
9992
  return String(value2);
9913
9993
  }
@@ -10382,8 +10462,8 @@ function compileTableLayout(spec, options, theme) {
10382
10462
  import {
10383
10463
  buildTemporalFormatter as buildTemporalFormatter2,
10384
10464
  formatDate as formatDate3,
10385
- formatNumber as formatNumber6,
10386
- getRepresentativeColor as getRepresentativeColor9
10465
+ formatNumber as formatNumber9,
10466
+ getRepresentativeColor as getRepresentativeColor10
10387
10467
  } from "@opendata-ai/openchart-core";
10388
10468
  function formatValue(value2, fieldType, format2) {
10389
10469
  if (value2 == null) return "";
@@ -10397,10 +10477,10 @@ function formatValue(value2, fieldType, format2) {
10397
10477
  try {
10398
10478
  return format(format2)(value2);
10399
10479
  } catch {
10400
- return formatNumber6(value2);
10480
+ return formatNumber9(value2);
10401
10481
  }
10402
10482
  }
10403
- return formatNumber6(value2);
10483
+ return formatNumber9(value2);
10404
10484
  }
10405
10485
  return String(value2);
10406
10486
  }
@@ -10422,11 +10502,22 @@ function buildFields(row, encoding, color2) {
10422
10502
  return buildExplicitTooltipFields(row, channels);
10423
10503
  }
10424
10504
  const fields = [];
10505
+ if (encoding.color && "field" in encoding.color) {
10506
+ fields.push({
10507
+ label: resolveLabel(encoding.color),
10508
+ value: formatValue(
10509
+ row[encoding.color.field],
10510
+ encoding.color.type,
10511
+ resolveFormat(encoding.color)
10512
+ ),
10513
+ color: color2
10514
+ });
10515
+ }
10425
10516
  if (encoding.y) {
10426
10517
  fields.push({
10427
10518
  label: resolveLabel(encoding.y),
10428
10519
  value: formatValue(row[encoding.y.field], encoding.y.type, resolveFormat(encoding.y)),
10429
- color: color2
10520
+ color: encoding.color ? void 0 : color2
10430
10521
  });
10431
10522
  }
10432
10523
  if (encoding.x) {
@@ -10460,6 +10551,16 @@ function getTooltipTitle(row, encoding) {
10460
10551
  if (encoding.y?.type === "nominal" || encoding.y?.type === "ordinal") {
10461
10552
  return String(row[encoding.y.field] ?? "");
10462
10553
  }
10554
+ if (encoding.x?.type === "quantitative" && encoding.y?.type === "quantitative") {
10555
+ const encodedFields = new Set(
10556
+ [encoding.x, encoding.y, encoding.color, encoding.size, encoding.detail].filter((ch) => !!ch && "field" in ch).map((ch) => ch.field)
10557
+ );
10558
+ for (const [key, value2] of Object.entries(row)) {
10559
+ if (!encodedFields.has(key) && typeof value2 === "string") {
10560
+ return value2;
10561
+ }
10562
+ }
10563
+ }
10463
10564
  if (encoding.color && "field" in encoding.color) {
10464
10565
  return String(row[encoding.color.field] ?? "");
10465
10566
  }
@@ -10478,12 +10579,12 @@ function tooltipsForLine(mark, encoding, _markIndex) {
10478
10579
  }
10479
10580
  function tooltipsForPoint(mark, encoding, markIndex) {
10480
10581
  const title = getTooltipTitle(mark.data, encoding);
10481
- const fields = buildFields(mark.data, encoding, getRepresentativeColor9(mark.fill));
10582
+ const fields = buildFields(mark.data, encoding, getRepresentativeColor10(mark.fill));
10482
10583
  return [[`point-${markIndex}`, { title, fields }]];
10483
10584
  }
10484
10585
  function tooltipsForRect(mark, encoding, markIndex) {
10485
10586
  const title = getTooltipTitle(mark.data, encoding);
10486
- const fields = buildFields(mark.data, encoding, getRepresentativeColor9(mark.fill));
10587
+ const fields = buildFields(mark.data, encoding, getRepresentativeColor10(mark.fill));
10487
10588
  return [[`rect-${markIndex}`, { title, fields }]];
10488
10589
  }
10489
10590
  function tooltipsForArc(mark, encoding, markIndex) {
@@ -10496,14 +10597,14 @@ function tooltipsForArc(mark, encoding, markIndex) {
10496
10597
  fields.push({
10497
10598
  label: categoryName,
10498
10599
  value: formatValue(row[encoding.y.field], encoding.y.type, resolveFormat(encoding.y)),
10499
- color: getRepresentativeColor9(mark.fill)
10600
+ color: getRepresentativeColor10(mark.fill)
10500
10601
  });
10501
10602
  }
10502
10603
  } else if (encoding.y) {
10503
10604
  fields.push({
10504
10605
  label: resolveLabel(encoding.y),
10505
10606
  value: formatValue(row[encoding.y.field], encoding.y.type, resolveFormat(encoding.y)),
10506
- color: getRepresentativeColor9(mark.fill)
10607
+ color: getRepresentativeColor10(mark.fill)
10507
10608
  });
10508
10609
  }
10509
10610
  const title = colorEnc ? String(row[colorEnc.field] ?? "") : void 0;
@@ -10514,7 +10615,7 @@ function tooltipsForArea(mark, encoding, _markIndex) {
10514
10615
  for (const dp of mark.dataPoints) {
10515
10616
  dp.tooltip = {
10516
10617
  title: getTooltipTitle(dp.datum, encoding),
10517
- fields: buildFields(dp.datum, encoding, getRepresentativeColor9(mark.fill))
10618
+ fields: buildFields(dp.datum, encoding, getRepresentativeColor10(mark.fill))
10518
10619
  };
10519
10620
  }
10520
10621
  }
@@ -11125,7 +11226,8 @@ function compileChart(spec, options) {
11125
11226
  height: options.height
11126
11227
  },
11127
11228
  animation: resolvedAnimation,
11128
- watermark
11229
+ watermark,
11230
+ measureText: options.measureText
11129
11231
  };
11130
11232
  }
11131
11233
  function compileLayer(spec, options) {