@opendata-ai/openchart-engine 6.27.0 → 6.28.2

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.
Files changed (36) hide show
  1. package/dist/index.d.ts +38 -6
  2. package/dist/index.js +1040 -521
  3. package/dist/index.js.map +1 -1
  4. package/package.json +2 -2
  5. package/src/__tests__/__snapshots__/compile-snapshot.test.ts.snap +31 -4
  6. package/src/__tests__/axes.test.ts +101 -3
  7. package/src/__tests__/legend.test.ts +2 -2
  8. package/src/annotations/__tests__/compute.test.ts +175 -0
  9. package/src/annotations/position.ts +37 -1
  10. package/src/annotations/resolve-range.ts +5 -5
  11. package/src/barlist/__tests__/compile-barlist.test.ts +200 -0
  12. package/src/barlist/compile-barlist.ts +380 -0
  13. package/src/barlist/types.ts +28 -0
  14. package/src/charts/bar/__tests__/compute.test.ts +222 -0
  15. package/src/charts/bar/compute.ts +77 -44
  16. package/src/charts/bar/index.ts +1 -0
  17. package/src/charts/bar/labels.ts +3 -2
  18. package/src/charts/column/compute.ts +60 -27
  19. package/src/charts/column/index.ts +1 -0
  20. package/src/charts/column/labels.ts +2 -1
  21. package/src/charts/line/__tests__/compute.test.ts +2 -2
  22. package/src/charts/line/area.ts +25 -4
  23. package/src/charts/line/compute.ts +15 -5
  24. package/src/compile.ts +26 -1
  25. package/src/compiler/normalize.ts +25 -1
  26. package/src/compiler/types.ts +5 -3
  27. package/src/compiler/validate.ts +120 -5
  28. package/src/index.ts +5 -0
  29. package/src/layout/axes/ticks.ts +37 -8
  30. package/src/layout/axes.ts +11 -4
  31. package/src/layout/dimensions.ts +10 -4
  32. package/src/layout/scales.ts +10 -0
  33. package/src/legend/wrap.ts +1 -1
  34. package/src/tilemap/__tests__/compile-tilemap.test.ts +5 -2
  35. package/src/tilemap/compile-tilemap.ts +41 -29
  36. package/src/tooltips/compute.ts +4 -2
package/dist/index.js CHANGED
@@ -1,17 +1,17 @@
1
1
  // src/compile.ts
2
2
  import {
3
3
  AXIS_TITLE_TRAILING_PAD as AXIS_TITLE_TRAILING_PAD2,
4
- adaptTheme as adaptTheme4,
4
+ adaptTheme as adaptTheme5,
5
5
  BREAKPOINT_COMPACT_MAX as BREAKPOINT_COMPACT_MAX2,
6
6
  computeLabelBounds,
7
- estimateTextWidth as estimateTextWidth15,
7
+ estimateTextWidth as estimateTextWidth16,
8
8
  generateAltText,
9
9
  generateDataTable,
10
10
  getAxisTitleOffset as getAxisTitleOffset2,
11
11
  getBreakpoint,
12
12
  getHeightClass,
13
13
  getLayoutStrategy,
14
- resolveTheme as resolveTheme4,
14
+ resolveTheme as resolveTheme5,
15
15
  TICK_LABEL_OFFSET
16
16
  } from "@opendata-ai/openchart-core";
17
17
 
@@ -3692,6 +3692,22 @@ function resolvePosition(value2, scale) {
3692
3692
  }
3693
3693
  return null;
3694
3694
  }
3695
+ function resolvePositionEdge(value2, scale, edge) {
3696
+ const center2 = resolvePosition(value2, scale);
3697
+ if (center2 === null || !scale) return null;
3698
+ const type = scale.type;
3699
+ if (type === "point") {
3700
+ const s = scale.scale;
3701
+ const halfStep = (s.step?.() ?? 0) / 2;
3702
+ return edge === "start" ? center2 - halfStep : center2 + halfStep;
3703
+ }
3704
+ if (type === "band") {
3705
+ const s = scale.scale;
3706
+ const halfBw = (s.bandwidth?.() ?? 0) / 2;
3707
+ return edge === "start" ? center2 - halfBw : center2 + halfBw;
3708
+ }
3709
+ return center2;
3710
+ }
3695
3711
 
3696
3712
  // src/annotations/collisions.ts
3697
3713
  function generateNudgeCandidates(selfBounds, obstacles, padding) {
@@ -3924,15 +3940,15 @@ function resolveRangeAnnotation(annotation, scales, chartArea, isDark) {
3924
3940
  let width = chartArea.width;
3925
3941
  let height = chartArea.height;
3926
3942
  if (annotation.x1 !== void 0 && annotation.x2 !== void 0) {
3927
- const x1px = resolvePosition(annotation.x1, scales.x);
3928
- const x2px = resolvePosition(annotation.x2, scales.x);
3943
+ const x1px = resolvePositionEdge(annotation.x1, scales.x, "start");
3944
+ const x2px = resolvePositionEdge(annotation.x2, scales.x, "end");
3929
3945
  if (x1px === null || x2px === null) return null;
3930
3946
  x2 = Math.min(x1px, x2px);
3931
3947
  width = Math.abs(x2px - x1px);
3932
3948
  }
3933
3949
  if (annotation.y1 !== void 0 && annotation.y2 !== void 0) {
3934
- const y1px = resolvePosition(annotation.y1, scales.y);
3935
- const y2px = resolvePosition(annotation.y2, scales.y);
3950
+ const y1px = resolvePositionEdge(annotation.y1, scales.y, "end");
3951
+ const y2px = resolvePositionEdge(annotation.y2, scales.y, "start");
3936
3952
  if (y1px === null || y2px === null) return null;
3937
3953
  y2 = Math.min(y1px, y2px);
3938
3954
  height = Math.abs(y2px - y1px);
@@ -4296,8 +4312,9 @@ function computeBarMarks(spec, scales, _chartArea, _strategy) {
4296
4312
  const conditionalColor = encoding.color && isConditionalValueDef(encoding.color) ? encoding.color : void 0;
4297
4313
  const colorField = colorEnc?.field;
4298
4314
  const isSequentialColor = colorEnc?.type === "quantitative";
4315
+ let marks;
4299
4316
  if (!colorField || isSequentialColor) {
4300
- return computeSimpleBars(
4317
+ marks = computeSimpleBars(
4301
4318
  spec.data,
4302
4319
  xChannel.field,
4303
4320
  yChannel.field,
@@ -4309,13 +4326,40 @@ function computeBarMarks(spec, scales, _chartArea, _strategy) {
4309
4326
  isSequentialColor,
4310
4327
  conditionalColor
4311
4328
  );
4312
- }
4313
- const categoryGroups = groupByField(spec.data, yChannel.field);
4314
- const needsStacking = Array.from(categoryGroups.values()).some((rows) => rows.length > 1);
4315
- if (needsStacking) {
4316
- const stackDisabled = xChannel.stack === null || xChannel.stack === false;
4317
- if (stackDisabled) {
4318
- return computeGroupedBars(
4329
+ } else {
4330
+ const categoryGroups = groupByField(spec.data, yChannel.field);
4331
+ const needsStacking = Array.from(categoryGroups.values()).some((rows) => rows.length > 1);
4332
+ if (needsStacking) {
4333
+ const stackDisabled = xChannel.stack === null || xChannel.stack === false;
4334
+ if (stackDisabled) {
4335
+ marks = computeGroupedBars(
4336
+ spec.data,
4337
+ xChannel.field,
4338
+ yChannel.field,
4339
+ colorField,
4340
+ xScale,
4341
+ yScale,
4342
+ bandwidth,
4343
+ baseline,
4344
+ scales
4345
+ );
4346
+ } else {
4347
+ const stackMode = xChannel.stack === "normalize" ? "normalize" : xChannel.stack === "center" ? "center" : "zero";
4348
+ marks = computeStackedBars(
4349
+ spec.data,
4350
+ xChannel.field,
4351
+ yChannel.field,
4352
+ colorField,
4353
+ xScale,
4354
+ yScale,
4355
+ bandwidth,
4356
+ baseline,
4357
+ scales,
4358
+ stackMode
4359
+ );
4360
+ }
4361
+ } else {
4362
+ marks = computeColoredBars(
4319
4363
  spec.data,
4320
4364
  xChannel.field,
4321
4365
  yChannel.field,
@@ -4327,31 +4371,8 @@ function computeBarMarks(spec, scales, _chartArea, _strategy) {
4327
4371
  scales
4328
4372
  );
4329
4373
  }
4330
- const stackMode = xChannel.stack === "normalize" ? "normalize" : xChannel.stack === "center" ? "center" : "zero";
4331
- return computeStackedBars(
4332
- spec.data,
4333
- xChannel.field,
4334
- yChannel.field,
4335
- colorField,
4336
- xScale,
4337
- yScale,
4338
- bandwidth,
4339
- baseline,
4340
- scales,
4341
- stackMode
4342
- );
4343
4374
  }
4344
- return computeColoredBars(
4345
- spec.data,
4346
- xChannel.field,
4347
- yChannel.field,
4348
- colorField,
4349
- xScale,
4350
- yScale,
4351
- bandwidth,
4352
- baseline,
4353
- scales
4354
- );
4375
+ return applyMarkDefOverrides(marks, spec, bandwidth);
4355
4376
  }
4356
4377
  function computeStackedBars(data, valueField, categoryField, colorField, xScale, yScale, bandwidth, _baseline, scales, stackMode = "zero") {
4357
4378
  const marks = [];
@@ -4470,6 +4491,27 @@ function computeColoredBars(data, valueField, categoryField, colorField, xScale,
4470
4491
  }
4471
4492
  return marks;
4472
4493
  }
4494
+ function applyMarkDefOverrides(marks, spec, bandwidth) {
4495
+ const { markDef } = spec;
4496
+ const fixedSize = markDef.size;
4497
+ const crSpec = markDef.cornerRadius;
4498
+ if (fixedSize == null && crSpec == null) return marks;
4499
+ for (const mark of marks) {
4500
+ if (fixedSize != null && mark.stackGroup === void 0) {
4501
+ const barHeight = Math.min(fixedSize, bandwidth);
4502
+ const offset = (bandwidth - barHeight) / 2;
4503
+ mark.y = mark.y + offset;
4504
+ mark.height = barHeight;
4505
+ }
4506
+ const effectiveHeight = mark.height;
4507
+ if (crSpec === "pill") {
4508
+ mark.cornerRadius = effectiveHeight / 2;
4509
+ } else if (typeof crSpec === "number") {
4510
+ mark.cornerRadius = crSpec;
4511
+ }
4512
+ }
4513
+ return marks;
4514
+ }
4473
4515
  function computeSimpleBars(data, valueField, categoryField, xScale, yScale, bandwidth, baseline, scales, sequentialColor = false, conditionalColor) {
4474
4516
  const marks = [];
4475
4517
  for (const row of data) {
@@ -4556,7 +4598,7 @@ var LABEL_FONT_SIZE = 11;
4556
4598
  var LABEL_FONT_WEIGHT = 600;
4557
4599
  var LABEL_PADDING = 6;
4558
4600
  var MIN_WIDTH_FOR_INSIDE_LABEL = 40;
4559
- function computeBarLabels(marks, _chartArea, density = "auto", labelFormat, labelPrefix, valueField) {
4601
+ function computeBarLabels(marks, _chartArea, density = "auto", labelFormat, labelPrefix, valueField, labelColor) {
4560
4602
  const targetMarks = filterByDensity(marks, density);
4561
4603
  const candidates = [];
4562
4604
  const fitsInSegment = [];
@@ -4607,11 +4649,11 @@ function computeBarLabels(marks, _chartArea, density = "auto", labelFormat, labe
4607
4649
  } else {
4608
4650
  if (isNegative) {
4609
4651
  anchorX = mark.x - LABEL_PADDING;
4610
- fill = getRepresentativeColor(mark.fill);
4652
+ fill = labelColor ?? getRepresentativeColor(mark.fill);
4611
4653
  textAnchor = "end";
4612
4654
  } else {
4613
4655
  anchorX = mark.x + mark.width + LABEL_PADDING;
4614
- fill = getRepresentativeColor(mark.fill);
4656
+ fill = labelColor ?? getRepresentativeColor(mark.fill);
4615
4657
  textAnchor = "start";
4616
4658
  }
4617
4659
  }
@@ -4684,7 +4726,8 @@ var barRenderer = (spec, scales, chartArea, strategy, _theme) => {
4684
4726
  spec.labels.density,
4685
4727
  spec.labels.format,
4686
4728
  spec.labels.prefix,
4687
- valueField
4729
+ valueField,
4730
+ spec.labels.color
4688
4731
  );
4689
4732
  for (let i = 0; i < marks.length && i < labels.length; i++) {
4690
4733
  marks[i].label = labels[i];
@@ -4713,13 +4756,14 @@ function computeColumnMarks(spec, scales, _chartArea, _strategy) {
4713
4756
  const conditionalColor = encoding.color && isConditionalValueDef(encoding.color) ? encoding.color : void 0;
4714
4757
  const colorField = colorEnc?.field;
4715
4758
  const isSequentialColor = colorEnc?.type === "quantitative";
4759
+ let marks;
4716
4760
  if (colorField && !isSequentialColor) {
4717
4761
  const categoryGroups = groupByField(spec.data, xChannel.field);
4718
4762
  const needsStacking = Array.from(categoryGroups.values()).some((rows) => rows.length > 1);
4719
4763
  if (needsStacking) {
4720
4764
  const stackDisabled = yChannel.stack === null || yChannel.stack === false;
4721
4765
  if (stackDisabled) {
4722
- return computeGroupedColumns(
4766
+ marks = computeGroupedColumns(
4723
4767
  spec.data,
4724
4768
  xChannel.field,
4725
4769
  yChannel.field,
@@ -4730,9 +4774,23 @@ function computeColumnMarks(spec, scales, _chartArea, _strategy) {
4730
4774
  baseline,
4731
4775
  scales
4732
4776
  );
4777
+ } else {
4778
+ const stackMode = yChannel.stack === "normalize" ? "normalize" : yChannel.stack === "center" ? "center" : "zero";
4779
+ marks = computeStackedColumns(
4780
+ spec.data,
4781
+ xChannel.field,
4782
+ yChannel.field,
4783
+ colorField,
4784
+ xScale,
4785
+ yScale,
4786
+ bandwidth,
4787
+ baseline,
4788
+ scales,
4789
+ stackMode
4790
+ );
4733
4791
  }
4734
- const stackMode = yChannel.stack === "normalize" ? "normalize" : yChannel.stack === "center" ? "center" : "zero";
4735
- return computeStackedColumns(
4792
+ } else {
4793
+ marks = computeColoredColumns(
4736
4794
  spec.data,
4737
4795
  xChannel.field,
4738
4796
  yChannel.field,
@@ -4741,34 +4799,24 @@ function computeColumnMarks(spec, scales, _chartArea, _strategy) {
4741
4799
  yScale,
4742
4800
  bandwidth,
4743
4801
  baseline,
4744
- scales,
4745
- stackMode
4802
+ scales
4746
4803
  );
4747
4804
  }
4748
- return computeColoredColumns(
4805
+ } else {
4806
+ marks = computeSimpleColumns(
4749
4807
  spec.data,
4750
4808
  xChannel.field,
4751
4809
  yChannel.field,
4752
- colorField,
4753
4810
  xScale,
4754
4811
  yScale,
4755
4812
  bandwidth,
4756
4813
  baseline,
4757
- scales
4814
+ scales,
4815
+ isSequentialColor,
4816
+ conditionalColor
4758
4817
  );
4759
4818
  }
4760
- return computeSimpleColumns(
4761
- spec.data,
4762
- xChannel.field,
4763
- yChannel.field,
4764
- xScale,
4765
- yScale,
4766
- bandwidth,
4767
- baseline,
4768
- scales,
4769
- isSequentialColor,
4770
- conditionalColor
4771
- );
4819
+ return applyMarkDefOverrides2(marks, spec, bandwidth);
4772
4820
  }
4773
4821
  function computeSimpleColumns(data, categoryField, valueField, xScale, yScale, bandwidth, baseline, scales, sequentialColor = false, conditionalColor) {
4774
4822
  const marks = [];
@@ -4934,6 +4982,27 @@ function computeStackedColumns(data, categoryField, valueField, colorField, xSca
4934
4982
  }
4935
4983
  return marks;
4936
4984
  }
4985
+ function applyMarkDefOverrides2(marks, spec, bandwidth) {
4986
+ const { markDef } = spec;
4987
+ const fixedSize = markDef.size;
4988
+ const crSpec = markDef.cornerRadius;
4989
+ if (fixedSize == null && crSpec == null) return marks;
4990
+ for (const mark of marks) {
4991
+ if (fixedSize != null && mark.stackGroup === void 0) {
4992
+ const barWidth = Math.min(fixedSize, bandwidth);
4993
+ const offset = (bandwidth - barWidth) / 2;
4994
+ mark.x = mark.x + offset;
4995
+ mark.width = barWidth;
4996
+ }
4997
+ const effectiveWidth = mark.width;
4998
+ if (crSpec === "pill") {
4999
+ mark.cornerRadius = effectiveWidth / 2;
5000
+ } else if (typeof crSpec === "number") {
5001
+ mark.cornerRadius = crSpec;
5002
+ }
5003
+ }
5004
+ return marks;
5005
+ }
4937
5006
 
4938
5007
  // src/charts/column/labels.ts
4939
5008
  import {
@@ -4945,7 +5014,7 @@ import {
4945
5014
  var LABEL_FONT_SIZE2 = 10;
4946
5015
  var LABEL_FONT_WEIGHT2 = 600;
4947
5016
  var LABEL_OFFSET_Y = 6;
4948
- function computeColumnLabels(marks, _chartArea, density = "auto", labelFormat, labelPrefix, valueField) {
5017
+ function computeColumnLabels(marks, _chartArea, density = "auto", labelFormat, labelPrefix, valueField, labelColor) {
4949
5018
  const targetMarks = filterByDensity(marks, density);
4950
5019
  const formatter = buildD3Formatter2(labelFormat);
4951
5020
  const candidates = [];
@@ -4986,7 +5055,7 @@ function computeColumnLabels(marks, _chartArea, density = "auto", labelFormat, l
4986
5055
  fontFamily: "system-ui, -apple-system, sans-serif",
4987
5056
  fontSize: LABEL_FONT_SIZE2,
4988
5057
  fontWeight: LABEL_FONT_WEIGHT2,
4989
- fill: getRepresentativeColor2(mark.fill),
5058
+ fill: labelColor ?? getRepresentativeColor2(mark.fill),
4990
5059
  lineHeight: 1.2,
4991
5060
  textAnchor: "middle",
4992
5061
  dominantBaseline: isNegative ? "hanging" : "auto"
@@ -5016,7 +5085,8 @@ var columnRenderer = (spec, scales, chartArea, strategy, _theme) => {
5016
5085
  spec.labels.density,
5017
5086
  spec.labels.format,
5018
5087
  spec.labels.prefix,
5019
- valueField
5088
+ valueField,
5089
+ spec.labels.color
5020
5090
  );
5021
5091
  for (let i = 0; i < marks.length && i < labels.length; i++) {
5022
5092
  marks[i].label = labels[i];
@@ -5339,9 +5409,26 @@ function computeSingleArea(spec, scales, _chartArea) {
5339
5409
  const ariaLabel = seriesKey === "__default__" ? `Area with ${validPoints.length} data points` : `${seriesKey}: area with ${validPoints.length} data points`;
5340
5410
  const aria = { label: ariaLabel };
5341
5411
  const markFill = spec.markDef.fill;
5342
- const fillValue = markFill != null ? markFill : color2;
5343
- const defaultFillOpacity = y2Channel ? 0.25 : DEFAULT_FILL_OPACITY;
5344
- const fillOpacity = isGradientDef3(fillValue) ? 1 : spec.markDef.opacity ?? defaultFillOpacity;
5412
+ let fillValue;
5413
+ let fillOpacity;
5414
+ if (markFill != null) {
5415
+ fillValue = markFill;
5416
+ fillOpacity = isGradientDef3(markFill) ? 1 : spec.markDef.opacity ?? (y2Channel ? 0.25 : DEFAULT_FILL_OPACITY);
5417
+ } else {
5418
+ const colorStr = getRepresentativeColor4(color2);
5419
+ fillValue = {
5420
+ gradient: "linear",
5421
+ x1: 0,
5422
+ y1: 0,
5423
+ x2: 0,
5424
+ y2: 1,
5425
+ stops: [
5426
+ { offset: 0, color: colorStr, opacity: 0.12 },
5427
+ { offset: 1, color: colorStr, opacity: 0 }
5428
+ ]
5429
+ };
5430
+ fillOpacity = 1;
5431
+ }
5345
5432
  marks.push({
5346
5433
  type: "area",
5347
5434
  topPoints,
@@ -5351,7 +5438,7 @@ function computeSingleArea(spec, scales, _chartArea) {
5351
5438
  fill: fillValue,
5352
5439
  fillOpacity,
5353
5440
  stroke: getRepresentativeColor4(isGradientDef3(fillValue) ? color2 : fillValue),
5354
- strokeWidth: spec.display === "sparkline" ? 1.25 : 2,
5441
+ strokeWidth: spec.markDef.strokeWidth ?? (spec.display === "sparkline" ? 1.25 : 1.5),
5355
5442
  seriesKey: seriesKey === "__default__" ? void 0 : seriesKey,
5356
5443
  data: validPoints.map((p) => p.row),
5357
5444
  dataPoints: validPoints.map((p) => ({ x: p.x, y: p.yTop, datum: p.row })),
@@ -5467,7 +5554,7 @@ function computeAreaMarks(spec, scales, chartArea) {
5467
5554
 
5468
5555
  // src/charts/line/compute.ts
5469
5556
  import { getRepresentativeColor as getRepresentativeColor5 } from "@opendata-ai/openchart-core";
5470
- var DEFAULT_STROKE_WIDTH = 2.5;
5557
+ var DEFAULT_STROKE_WIDTH = 1.5;
5471
5558
  var SPARKLINE_STROKE_WIDTH = 1.25;
5472
5559
  var DEFAULT_POINT_RADIUS = 3;
5473
5560
  function computeLineMarks(spec, scales, _chartArea, _strategy) {
@@ -5535,7 +5622,7 @@ function computeLineMarks(spec, scales, _chartArea, _strategy) {
5535
5622
  points: allPoints,
5536
5623
  path: combinedPath,
5537
5624
  stroke: strokeColor,
5538
- strokeWidth: styleOverride?.strokeWidth ?? (spec.display === "sparkline" ? SPARKLINE_STROKE_WIDTH : DEFAULT_STROKE_WIDTH),
5625
+ strokeWidth: styleOverride?.strokeWidth ?? spec.markDef.strokeWidth ?? (spec.display === "sparkline" ? SPARKLINE_STROKE_WIDTH : DEFAULT_STROKE_WIDTH),
5539
5626
  strokeDasharray,
5540
5627
  opacity: styleOverride?.opacity,
5541
5628
  seriesKey: seriesStyleKey,
@@ -5545,25 +5632,30 @@ function computeLineMarks(spec, scales, _chartArea, _strategy) {
5545
5632
  };
5546
5633
  marks.push(lineMark);
5547
5634
  const markPoint = spec.markDef.point;
5548
- const showPoints = markPoint === true || markPoint === "transparent" || isSequentialColor;
5635
+ const showPoints = markPoint === true || markPoint === "transparent" || markPoint === "endpoints" || isSequentialColor;
5549
5636
  if (showPoints) {
5550
5637
  const isTransparent = markPoint === "transparent";
5638
+ const isEndpoints = markPoint === "endpoints";
5551
5639
  const seriesShowPoints = styleOverride?.showPoints !== false;
5640
+ const lastIdx = pointsWithData.length - 1;
5552
5641
  for (let i = 0; i < pointsWithData.length; i++) {
5553
5642
  const p = pointsWithData[i];
5554
- const visible = seriesShowPoints && !isTransparent;
5643
+ const isEndpoint = i === 0 || i === lastIdx;
5644
+ const visible = seriesShowPoints && !isTransparent && (!isEndpoints || isEndpoint);
5555
5645
  let pointColor = color2;
5556
5646
  if (isSequentialColor) {
5557
5647
  const val = Number(p.row[sequentialColorField]);
5558
5648
  pointColor = Number.isFinite(val) ? getSequentialColor(scales, val) : color2;
5559
5649
  }
5650
+ const hollow = isEndpoints && visible;
5651
+ const pointColorStr = getRepresentativeColor5(pointColor);
5560
5652
  const pointMark = {
5561
5653
  type: "point",
5562
5654
  cx: p.x,
5563
5655
  cy: p.y,
5564
5656
  r: visible ? DEFAULT_POINT_RADIUS : 0,
5565
- fill: pointColor,
5566
- stroke: visible ? "#ffffff" : "transparent",
5657
+ fill: hollow ? "transparent" : pointColorStr,
5658
+ stroke: hollow ? pointColorStr : visible ? "#ffffff" : "transparent",
5567
5659
  strokeWidth: visible ? 1.5 : 0,
5568
5660
  fillOpacity: isTransparent ? 0 : 1,
5569
5661
  data: p.row,
@@ -6293,248 +6385,75 @@ function registerBuiltinRenderers() {
6293
6385
  }
6294
6386
  registerBuiltinRenderers();
6295
6387
 
6296
- // src/charts/post-process.ts
6297
- function computeMarkObstacles(marks, scales) {
6298
- if (scales.y?.type === "band") {
6299
- return computeBandRowObstacles(marks, scales);
6388
+ // src/barlist/compile-barlist.ts
6389
+ import {
6390
+ adaptTheme,
6391
+ buildD3Formatter as buildD3Formatter4,
6392
+ computeChrome,
6393
+ estimateTextWidth as estimateTextWidth7,
6394
+ formatNumber as formatNumber2,
6395
+ resolveTheme
6396
+ } from "@opendata-ai/openchart-core";
6397
+
6398
+ // src/compiler/animation.ts
6399
+ var ENTER_DEFAULTS = {
6400
+ duration: 500,
6401
+ ease: "smooth",
6402
+ staggerDelay: 80,
6403
+ staggerOrder: "index",
6404
+ annotationDelay: 200
6405
+ };
6406
+ var MAX_TOTAL_STAGGER_MS = 2e3;
6407
+ function resolveAnimation(spec) {
6408
+ if (spec === void 0 || spec === false) return void 0;
6409
+ if (spec === true) {
6410
+ return {
6411
+ enabled: true,
6412
+ duration: ENTER_DEFAULTS.duration,
6413
+ ease: ENTER_DEFAULTS.ease,
6414
+ staggerDelay: ENTER_DEFAULTS.staggerDelay,
6415
+ staggerOrder: ENTER_DEFAULTS.staggerOrder,
6416
+ annotationDelay: ENTER_DEFAULTS.annotationDelay
6417
+ };
6300
6418
  }
6301
- const obstacles = [];
6302
- for (const mark of marks) {
6303
- if (mark.type === "rect") {
6304
- const rm = mark;
6305
- obstacles.push({ x: rm.x, y: rm.y, width: rm.width, height: rm.height });
6306
- } else if (mark.type === "point") {
6307
- const pm = mark;
6308
- obstacles.push({
6309
- x: pm.cx - pm.r,
6310
- y: pm.cy - pm.r,
6311
- width: pm.r * 2,
6312
- height: pm.r * 2
6313
- });
6314
- }
6419
+ const config = spec;
6420
+ if (config.enter === false || config.enter === void 0 && !hasAnyPhase(config)) {
6421
+ return void 0;
6315
6422
  }
6316
- return obstacles;
6423
+ const enterConfig = resolvePhaseConfig(config.enter);
6424
+ return {
6425
+ enabled: true,
6426
+ duration: enterConfig.duration,
6427
+ ease: enterConfig.ease,
6428
+ staggerDelay: enterConfig.staggerDelay,
6429
+ staggerOrder: enterConfig.staggerOrder,
6430
+ annotationDelay: config.annotationDelay ?? ENTER_DEFAULTS.annotationDelay
6431
+ };
6317
6432
  }
6318
- function computeBandRowObstacles(marks, scales) {
6319
- const rows = /* @__PURE__ */ new Map();
6320
- for (const mark of marks) {
6321
- let cy;
6322
- let left2;
6323
- let right2;
6324
- if (mark.type === "point") {
6325
- const pm = mark;
6326
- cy = pm.cy;
6327
- left2 = pm.cx - pm.r;
6328
- right2 = pm.cx + pm.r;
6329
- } else if (mark.type === "rect") {
6330
- const rm = mark;
6331
- cy = rm.y + rm.height / 2;
6332
- left2 = rm.x;
6333
- right2 = rm.x + rm.width;
6334
- } else {
6335
- continue;
6336
- }
6337
- const key = Math.round(cy);
6338
- const existing = rows.get(key);
6339
- if (existing) {
6340
- existing.minX = Math.min(existing.minX, left2);
6341
- existing.maxX = Math.max(existing.maxX, right2);
6342
- } else {
6343
- rows.set(key, { minX: left2, maxX: right2, bandY: cy });
6344
- }
6345
- }
6346
- const bandScale = scales.y.scale;
6347
- const bandwidth = bandScale.bandwidth?.() ?? 0;
6348
- if (bandwidth === 0) return [];
6349
- const obstacles = [];
6350
- for (const { minX, maxX, bandY } of rows.values()) {
6351
- obstacles.push({
6352
- x: minX,
6353
- y: bandY - bandwidth / 2,
6354
- width: maxX - minX,
6355
- height: bandwidth
6356
- });
6357
- }
6358
- return obstacles;
6433
+ function clampStaggerDelay(delay, elementCount) {
6434
+ if (elementCount <= 1) return 0;
6435
+ return Math.min(delay, MAX_TOTAL_STAGGER_MS / elementCount);
6359
6436
  }
6360
- function resolveRendererKey(markType, encoding, markDef) {
6361
- if (markType === "bar") {
6362
- const xType = encoding.x?.type;
6363
- const yType = encoding.y?.type;
6364
- const isVertical = (xType === "nominal" || xType === "ordinal" || xType === "temporal") && yType === "quantitative";
6365
- if (isVertical) {
6366
- return "bar:vertical";
6367
- }
6368
- } else if (markType === "arc") {
6369
- const innerRadius = markDef.innerRadius;
6370
- if (innerRadius && innerRadius > 0) {
6371
- return "arc:donut";
6372
- }
6373
- }
6374
- return markType;
6437
+ function hasAnyPhase(config) {
6438
+ return config.enter !== void 0 || config.update !== void 0 || config.exit !== void 0;
6375
6439
  }
6376
- function getMarkPrimaryValue(mark) {
6377
- switch (mark.type) {
6378
- case "rect":
6379
- return mark.height;
6380
- // bar height is the primary value encoding
6381
- case "point":
6382
- return mark.cy;
6383
- // y position for scatter
6384
- case "arc":
6385
- return mark.endAngle - mark.startAngle;
6386
- // arc angle extent
6387
- case "line":
6388
- case "area":
6389
- return 0;
6390
- // series marks don't have individual values
6391
- default:
6392
- return 0;
6440
+ function resolvePhaseConfig(phase) {
6441
+ if (phase === void 0 || phase === true) {
6442
+ return {
6443
+ duration: ENTER_DEFAULTS.duration,
6444
+ ease: ENTER_DEFAULTS.ease,
6445
+ staggerDelay: ENTER_DEFAULTS.staggerDelay,
6446
+ staggerOrder: ENTER_DEFAULTS.staggerOrder
6447
+ };
6393
6448
  }
6394
- }
6395
- function assignAnimationIndices(marks, animation) {
6396
- if (!animation?.enabled) return;
6397
- if (animation.staggerOrder === "value") {
6398
- const indexed = marks.map((m, i) => ({ mark: m, idx: i }));
6399
- indexed.sort((a, b) => {
6400
- const av = getMarkPrimaryValue(a.mark);
6401
- const bv = getMarkPrimaryValue(b.mark);
6402
- return av - bv;
6403
- });
6404
- for (let i = 0; i < indexed.length; i++) {
6405
- const m = indexed[i].mark;
6406
- if (m.type === "rect" && m.stackGroup) continue;
6407
- m.animationIndex = i;
6408
- }
6409
- }
6410
- const groupIndexMap = /* @__PURE__ */ new Map();
6411
- const groupStackPos = /* @__PURE__ */ new Map();
6412
- let nextGroupIndex = 0;
6413
- for (const mark of marks) {
6414
- if (mark.type === "rect" && mark.stackGroup) {
6415
- const rect = mark;
6416
- const group = rect.stackGroup;
6417
- if (!groupIndexMap.has(group)) {
6418
- groupIndexMap.set(group, nextGroupIndex++);
6419
- }
6420
- rect.animationIndex = groupIndexMap.get(group);
6421
- const pos = groupStackPos.get(group) ?? 0;
6422
- rect.stackPos = pos;
6423
- groupStackPos.set(group, pos + 1);
6424
- }
6425
- }
6426
- }
6427
-
6428
- // src/compile/color-scale-range.ts
6429
- function applyColorScaleRange(scales, encoding, theme) {
6430
- if (!scales.color) return;
6431
- const hasExplicitRange = !!(encoding.color && "field" in encoding.color && encoding.color.scale?.range?.length);
6432
- if (hasExplicitRange) return;
6433
- if (scales.color.type === "sequential") {
6434
- const seqStops = Object.values(theme.colors.sequential)[0] ?? theme.colors.categorical;
6435
- scales.color.scale.range([
6436
- seqStops[0],
6437
- seqStops[seqStops.length - 1]
6438
- ]);
6439
- } else {
6440
- scales.color.scale.range(theme.colors.categorical);
6441
- }
6442
- }
6443
-
6444
- // src/compile/data-clip.ts
6445
- function filterClippedDomains(data, encoding) {
6446
- let result = data;
6447
- for (const channel of ["x", "y"]) {
6448
- const enc = encoding[channel];
6449
- if (!enc?.scale?.clip || !enc.scale.domain) continue;
6450
- const domain = enc.scale.domain;
6451
- const field = enc.field;
6452
- if (Array.isArray(domain) && domain.length === 2 && typeof domain[0] === "number") {
6453
- const [lo, hi] = domain;
6454
- result = result.filter((row) => {
6455
- const v = Number(row[field]);
6456
- return Number.isFinite(v) && v >= lo && v <= hi;
6457
- });
6458
- }
6459
- }
6460
- return result;
6461
- }
6462
-
6463
- // src/compile/watermark-obstacle.ts
6464
- import { BRAND_RESERVE_WIDTH } from "@opendata-ai/openchart-core";
6465
- var WATERMARK_HEIGHT = 30;
6466
- var X_AXIS_EXTENT_WITH_LABEL = 48;
6467
- var X_AXIS_EXTENT_TICKS_ONLY = 26;
6468
- function computeWatermarkObstacle(dims, watermark, axes, theme) {
6469
- if (!watermark) return null;
6470
- const chartArea = dims.chartArea;
6471
- const brandPadding = theme.spacing.padding;
6472
- const brandX = dims.total.width - brandPadding - BRAND_RESERVE_WIDTH;
6473
- const xAxisExtent = axes.x?.label ? X_AXIS_EXTENT_WITH_LABEL : axes.x ? X_AXIS_EXTENT_TICKS_ONLY : 0;
6474
- const firstBottomChrome = dims.chrome.source ?? dims.chrome.byline ?? dims.chrome.footer;
6475
- const brandY = firstBottomChrome ? chartArea.y + chartArea.height + xAxisExtent + firstBottomChrome.y : chartArea.y + chartArea.height + xAxisExtent + theme.spacing.chartToFooter;
6476
- return { x: brandX, y: brandY, width: BRAND_RESERVE_WIDTH, height: WATERMARK_HEIGHT };
6477
- }
6478
-
6479
- // src/compiler/animation.ts
6480
- var ENTER_DEFAULTS = {
6481
- duration: 500,
6482
- ease: "smooth",
6483
- staggerDelay: 80,
6484
- staggerOrder: "index",
6485
- annotationDelay: 200
6486
- };
6487
- var MAX_TOTAL_STAGGER_MS = 2e3;
6488
- function resolveAnimation(spec) {
6489
- if (spec === void 0 || spec === false) return void 0;
6490
- if (spec === true) {
6491
- return {
6492
- enabled: true,
6493
- duration: ENTER_DEFAULTS.duration,
6494
- ease: ENTER_DEFAULTS.ease,
6495
- staggerDelay: ENTER_DEFAULTS.staggerDelay,
6496
- staggerOrder: ENTER_DEFAULTS.staggerOrder,
6497
- annotationDelay: ENTER_DEFAULTS.annotationDelay
6498
- };
6499
- }
6500
- const config = spec;
6501
- if (config.enter === false || config.enter === void 0 && !hasAnyPhase(config)) {
6502
- return void 0;
6503
- }
6504
- const enterConfig = resolvePhaseConfig(config.enter);
6505
- return {
6506
- enabled: true,
6507
- duration: enterConfig.duration,
6508
- ease: enterConfig.ease,
6509
- staggerDelay: enterConfig.staggerDelay,
6510
- staggerOrder: enterConfig.staggerOrder,
6511
- annotationDelay: config.annotationDelay ?? ENTER_DEFAULTS.annotationDelay
6512
- };
6513
- }
6514
- function clampStaggerDelay(delay, elementCount) {
6515
- if (elementCount <= 1) return 0;
6516
- return Math.min(delay, MAX_TOTAL_STAGGER_MS / elementCount);
6517
- }
6518
- function hasAnyPhase(config) {
6519
- return config.enter !== void 0 || config.update !== void 0 || config.exit !== void 0;
6520
- }
6521
- function resolvePhaseConfig(phase) {
6522
- if (phase === void 0 || phase === true) {
6523
- return {
6524
- duration: ENTER_DEFAULTS.duration,
6525
- ease: ENTER_DEFAULTS.ease,
6526
- staggerDelay: ENTER_DEFAULTS.staggerDelay,
6527
- staggerOrder: ENTER_DEFAULTS.staggerOrder
6528
- };
6529
- }
6530
- const cfg = phase;
6531
- const stagger = resolveStagger(cfg.stagger);
6532
- return {
6533
- duration: cfg.duration ?? ENTER_DEFAULTS.duration,
6534
- ease: cfg.ease ?? ENTER_DEFAULTS.ease,
6535
- staggerDelay: stagger.delay,
6536
- staggerOrder: stagger.order
6537
- };
6449
+ const cfg = phase;
6450
+ const stagger = resolveStagger(cfg.stagger);
6451
+ return {
6452
+ duration: cfg.duration ?? ENTER_DEFAULTS.duration,
6453
+ ease: cfg.ease ?? ENTER_DEFAULTS.ease,
6454
+ staggerDelay: stagger.delay,
6455
+ staggerOrder: stagger.order
6456
+ };
6538
6457
  }
6539
6458
  function resolveStagger(stagger) {
6540
6459
  if (stagger === false) return { delay: 0, order: "index" };
@@ -6549,6 +6468,7 @@ function resolveStagger(stagger) {
6549
6468
 
6550
6469
  // src/compiler/normalize.ts
6551
6470
  import {
6471
+ isBarListSpec,
6552
6472
  isChartSpec,
6553
6473
  isGraphSpec,
6554
6474
  isLayerSpec,
@@ -6806,7 +6726,8 @@ function normalizeLabels(labels) {
6806
6726
  density: labels.density ?? "auto",
6807
6727
  format: labels.format ?? "",
6808
6728
  prefix: labels.prefix ?? "",
6809
- offsets: labels.offsets
6729
+ offsets: labels.offsets,
6730
+ color: labels.color
6810
6731
  };
6811
6732
  }
6812
6733
  function normalizeChartSpec(spec, warnings) {
@@ -6952,6 +6873,22 @@ function normalizeTileMapSpec(spec, warnings) {
6952
6873
  valueFormat: spec.valueFormat
6953
6874
  };
6954
6875
  }
6876
+ function normalizeBarListSpec(spec, _warnings) {
6877
+ return {
6878
+ type: "barlist",
6879
+ data: spec.data,
6880
+ encoding: spec.encoding,
6881
+ barHeight: spec.barHeight ?? 6,
6882
+ cornerRadius: spec.cornerRadius ?? "pill",
6883
+ maxItems: spec.maxItems ?? 20,
6884
+ chrome: normalizeChrome(spec.chrome),
6885
+ theme: spec.theme ?? {},
6886
+ darkMode: spec.darkMode ?? "off",
6887
+ watermark: spec.watermark ?? true,
6888
+ animation: spec.animation ?? true,
6889
+ valueFormat: spec.valueFormat
6890
+ };
6891
+ }
6955
6892
  function normalizeSpec(spec, warnings = []) {
6956
6893
  if (isLayerSpec(spec)) {
6957
6894
  const leaves = flattenLayers(spec);
@@ -6975,8 +6912,11 @@ function normalizeSpec(spec, warnings = []) {
6975
6912
  if (isTileMapSpec(spec)) {
6976
6913
  return normalizeTileMapSpec(spec, warnings);
6977
6914
  }
6915
+ if (isBarListSpec(spec)) {
6916
+ return normalizeBarListSpec(spec, warnings);
6917
+ }
6978
6918
  throw new Error(
6979
- `Unknown spec shape. Expected mark (chart), layer, type: 'table', type: 'graph', type: 'sankey', or type: 'tilemap'.`
6919
+ `Unknown spec shape. Expected mark (chart), layer, type: 'table', type: 'graph', type: 'sankey', type: 'tilemap', or type: 'barlist'.`
6980
6920
  );
6981
6921
  }
6982
6922
  function flattenLayers(spec, parentData, parentEncoding, parentTransforms, parentWatermark) {
@@ -7616,6 +7556,98 @@ function validateTileMapSpec(spec, errors) {
7616
7556
  });
7617
7557
  }
7618
7558
  }
7559
+ function validateBarListSpec(spec, errors) {
7560
+ if (!Array.isArray(spec.data)) {
7561
+ errors.push({
7562
+ message: 'Spec error: barlist spec requires a "data" array',
7563
+ path: "data",
7564
+ code: "INVALID_TYPE",
7565
+ suggestion: 'Provide data as an array of objects, e.g. data: [{ label: "Category A", value: 42 }]'
7566
+ });
7567
+ return;
7568
+ }
7569
+ if (spec.data.length === 0) {
7570
+ errors.push({
7571
+ message: 'Spec error: "data" array must be non-empty',
7572
+ path: "data",
7573
+ code: "EMPTY_DATA",
7574
+ suggestion: "Add at least one data row"
7575
+ });
7576
+ return;
7577
+ }
7578
+ const firstRow = spec.data[0];
7579
+ if (typeof firstRow !== "object" || firstRow === null || Array.isArray(firstRow)) {
7580
+ errors.push({
7581
+ message: 'Spec error: each item in "data" must be a plain object',
7582
+ path: "data[0]",
7583
+ code: "INVALID_TYPE",
7584
+ suggestion: 'Each data item should be an object, e.g. { label: "Category A", value: 42 }'
7585
+ });
7586
+ return;
7587
+ }
7588
+ if (!spec.encoding || typeof spec.encoding !== "object") {
7589
+ errors.push({
7590
+ message: 'Spec error: barlist spec requires an "encoding" object with label and value channels',
7591
+ path: "encoding",
7592
+ code: "MISSING_FIELD",
7593
+ suggestion: 'Add an encoding object, e.g. encoding: { label: { field: "name", type: "nominal" }, value: { field: "count", type: "quantitative" } }'
7594
+ });
7595
+ return;
7596
+ }
7597
+ const encoding = spec.encoding;
7598
+ const dataColumns = new Set(Object.keys(firstRow));
7599
+ const availableColumns = [...dataColumns].join(", ");
7600
+ for (const channel of ["label", "value"]) {
7601
+ const ch = encoding[channel];
7602
+ if (!ch || typeof ch !== "object") {
7603
+ errors.push({
7604
+ message: `Spec error: barlist encoding requires "${channel}" channel`,
7605
+ path: `encoding.${channel}`,
7606
+ code: "MISSING_FIELD",
7607
+ suggestion: `Add encoding.${channel} with a field from your data (${availableColumns}). Example: ${channel}: { field: "${[...dataColumns][0] ?? "myField"}", type: "${channel === "value" ? "quantitative" : "nominal"}" }`
7608
+ });
7609
+ continue;
7610
+ }
7611
+ if (!ch.field || typeof ch.field !== "string") {
7612
+ errors.push({
7613
+ message: `Spec error: encoding.${channel} must have a "field" string`,
7614
+ path: `encoding.${channel}.field`,
7615
+ code: "MISSING_FIELD",
7616
+ suggestion: `Add a field name from your data columns: ${availableColumns}`
7617
+ });
7618
+ continue;
7619
+ }
7620
+ if (!dataColumns.has(ch.field)) {
7621
+ errors.push({
7622
+ message: `Spec error: encoding.${channel}.field "${ch.field}" does not exist in data. Available columns: ${availableColumns}`,
7623
+ path: `encoding.${channel}.field`,
7624
+ code: "DATA_FIELD_MISSING",
7625
+ suggestion: `Use one of the available data columns: ${availableColumns}`
7626
+ });
7627
+ }
7628
+ }
7629
+ for (const channel of ["subtitle", "color", "tooltip"]) {
7630
+ const ch = encoding[channel];
7631
+ if (!ch) continue;
7632
+ const field = ch.field;
7633
+ if (field && typeof field === "string" && !dataColumns.has(field)) {
7634
+ errors.push({
7635
+ message: `Spec error: encoding.${channel}.field "${field}" does not exist in data. Available columns: ${availableColumns}`,
7636
+ path: `encoding.${channel}.field`,
7637
+ code: "DATA_FIELD_MISSING",
7638
+ suggestion: `Use one of the available data columns: ${availableColumns}`
7639
+ });
7640
+ }
7641
+ }
7642
+ if (spec.darkMode !== void 0 && !VALID_DARK_MODES.has(spec.darkMode)) {
7643
+ errors.push({
7644
+ message: 'Spec error: darkMode must be "auto", "force", or "off"',
7645
+ path: "darkMode",
7646
+ code: "INVALID_VALUE",
7647
+ suggestion: 'Use one of: "auto" (system preference), "force" (always dark), or "off" (always light)'
7648
+ });
7649
+ }
7650
+ }
7619
7651
  function validateLayerSpec(spec, errors) {
7620
7652
  const layer = spec.layer;
7621
7653
  if (layer.length === 0) {
@@ -7688,105 +7720,550 @@ function validateLayerSpec(spec, errors) {
7688
7720
  }
7689
7721
  }
7690
7722
  }
7691
- }
7692
- function validateSpec(spec) {
7693
- const errors = [];
7694
- if (!spec || typeof spec !== "object" || Array.isArray(spec)) {
7695
- return {
7696
- valid: false,
7697
- errors: [
7698
- {
7699
- message: "Spec error: spec must be a non-null object",
7700
- code: "INVALID_TYPE",
7701
- suggestion: 'Pass a spec object with at least a "mark" field for charts, e.g. { mark: "line", data: [...], encoding: {...} }'
7702
- }
7703
- ],
7704
- normalized: null
7705
- };
7723
+ }
7724
+ function validateSpec(spec) {
7725
+ const errors = [];
7726
+ if (!spec || typeof spec !== "object" || Array.isArray(spec)) {
7727
+ return {
7728
+ valid: false,
7729
+ errors: [
7730
+ {
7731
+ message: "Spec error: spec must be a non-null object",
7732
+ code: "INVALID_TYPE",
7733
+ suggestion: 'Pass a spec object with at least a "mark" field for charts, e.g. { mark: "line", data: [...], encoding: {...} }'
7734
+ }
7735
+ ],
7736
+ normalized: null
7737
+ };
7738
+ }
7739
+ const obj = spec;
7740
+ const hasLayer = "layer" in obj && Array.isArray(obj.layer);
7741
+ const hasMark = "mark" in obj;
7742
+ const isTable = obj.type === "table";
7743
+ const isGraph = obj.type === "graph";
7744
+ const isSankey = obj.type === "sankey";
7745
+ const isTileMap = obj.type === "tilemap";
7746
+ const isBarList = obj.type === "barlist";
7747
+ const isLayer = hasLayer && !isTable && !isGraph && !isSankey && !isTileMap && !isBarList;
7748
+ const isChart = hasMark && !hasLayer && !isTable && !isGraph && !isSankey && !isTileMap && !isBarList;
7749
+ if (!isChart && !isTable && !isGraph && !isSankey && !isTileMap && !isBarList && !isLayer) {
7750
+ return {
7751
+ valid: false,
7752
+ errors: [
7753
+ {
7754
+ message: 'Spec error: spec must have a "mark" field for charts, a "layer" array for layered charts, or a "type" field for tables/graphs/sankey/tilemap/barlist',
7755
+ path: "mark",
7756
+ code: "MISSING_FIELD",
7757
+ suggestion: `Add a "mark" field for charts (e.g. mark: "bar"), a "layer" array for layered charts, or a "type" field (type: "table", type: "graph", type: "sankey", type: "tilemap", or type: "barlist"). Valid mark types: ${[...MARK_TYPES].join(", ")}`
7758
+ }
7759
+ ],
7760
+ normalized: null
7761
+ };
7762
+ }
7763
+ if (isLayer) {
7764
+ validateLayerSpec(obj, errors);
7765
+ }
7766
+ if (isChart) {
7767
+ const mark = obj.mark;
7768
+ let markValue;
7769
+ if (typeof mark === "string") {
7770
+ markValue = mark;
7771
+ } else if (mark && typeof mark === "object" && !Array.isArray(mark)) {
7772
+ markValue = mark.type;
7773
+ }
7774
+ if (!markValue || !MARK_TYPES.has(markValue)) {
7775
+ return {
7776
+ valid: false,
7777
+ errors: [
7778
+ {
7779
+ message: `Spec error: "${markValue ?? String(mark)}" is not a valid mark type. Valid mark types: ${[...MARK_TYPES].join(", ")}`,
7780
+ path: "mark",
7781
+ code: "INVALID_VALUE",
7782
+ suggestion: `Change mark to one of: ${[...MARK_TYPES].join(", ")}`
7783
+ }
7784
+ ],
7785
+ normalized: null
7786
+ };
7787
+ }
7788
+ validateChartSpec(obj, errors);
7789
+ } else if (isTable) {
7790
+ validateTableSpec(obj, errors);
7791
+ } else if (isGraph) {
7792
+ validateGraphSpec(obj, errors);
7793
+ } else if (isSankey) {
7794
+ validateSankeySpec(obj, errors);
7795
+ } else if (isTileMap) {
7796
+ validateTileMapSpec(obj, errors);
7797
+ } else if (isBarList) {
7798
+ validateBarListSpec(obj, errors);
7799
+ }
7800
+ if (errors.length > 0) {
7801
+ return { valid: false, errors, normalized: null };
7802
+ }
7803
+ return {
7804
+ valid: true,
7805
+ errors: [],
7806
+ normalized: spec
7807
+ };
7808
+ }
7809
+
7810
+ // src/compiler/index.ts
7811
+ function compile(spec) {
7812
+ const validation = validateSpec(spec);
7813
+ if (!validation.valid || !validation.normalized) {
7814
+ const errorMessages = validation.errors.map((e) => e.message).join("\n");
7815
+ throw new Error(`Invalid spec:
7816
+ ${errorMessages}`);
7817
+ }
7818
+ const warnings = [];
7819
+ const normalized = normalizeSpec(validation.normalized, warnings);
7820
+ return { spec: normalized, warnings };
7821
+ }
7822
+
7823
+ // src/barlist/compile-barlist.ts
7824
+ var DEFAULT_ROW_GAP = 8;
7825
+ var LABEL_BAR_GAP = 12;
7826
+ var BAR_VALUE_GAP = 12;
7827
+ var VALUE_WIDTH = 56;
7828
+ var LABEL_FONT_SIZE6 = 13;
7829
+ var LABEL_FONT_WEIGHT6 = 500;
7830
+ var SUBTITLE_FONT_SIZE = 12;
7831
+ var SUBTITLE_FONT_WEIGHT = 400;
7832
+ var VALUE_FONT_SIZE = 12;
7833
+ var VALUE_FONT_WEIGHT = 400;
7834
+ var BARLIST_COLORS = ["#06b6d4", "#34d399", "#fbbf24", "#f472b6", "#a78bfa"];
7835
+ function compileBarList(spec, options) {
7836
+ const { spec: normalized } = compile(spec);
7837
+ if (!("type" in normalized) || normalized.type !== "barlist") {
7838
+ throw new Error(
7839
+ "compileBarList received a non-barlist spec. Use compileChart, compileTable, compileGraph, compileSankey, or compileTileMap instead."
7840
+ );
7841
+ }
7842
+ const barlistSpec = normalized;
7843
+ const rawWatermark = spec.watermark;
7844
+ const watermark = rawWatermark !== void 0 ? barlistSpec.watermark : options.watermark ?? true;
7845
+ const mergedThemeConfig = options.theme ? { ...barlistSpec.theme, ...options.theme } : barlistSpec.theme;
7846
+ const lightTheme = resolveTheme(mergedThemeConfig);
7847
+ let theme = lightTheme;
7848
+ if (options.darkMode) {
7849
+ theme = adaptTheme(theme);
7850
+ }
7851
+ const chrome = computeChrome(
7852
+ {
7853
+ title: barlistSpec.chrome.title,
7854
+ subtitle: barlistSpec.chrome.subtitle,
7855
+ source: barlistSpec.chrome.source,
7856
+ byline: barlistSpec.chrome.byline,
7857
+ footer: barlistSpec.chrome.footer
7858
+ },
7859
+ theme,
7860
+ options.width,
7861
+ options.measureText,
7862
+ "full",
7863
+ void 0,
7864
+ watermark
7865
+ );
7866
+ const padding = theme.spacing.padding;
7867
+ const fullArea = {
7868
+ x: padding,
7869
+ y: padding + chrome.topHeight,
7870
+ width: options.width - padding * 2,
7871
+ height: options.height - chrome.topHeight - chrome.bottomHeight - padding * 2
7872
+ };
7873
+ if (fullArea.width <= 0 || fullArea.height <= 0) {
7874
+ return emptyLayout(chrome, theme, options, watermark);
7875
+ }
7876
+ const labelField = barlistSpec.encoding.label.field;
7877
+ const valueField = barlistSpec.encoding.value.field;
7878
+ const subtitleField = barlistSpec.encoding.subtitle?.field;
7879
+ const colorField = barlistSpec.encoding.color?.field;
7880
+ const barHeight = barlistSpec.barHeight;
7881
+ const cornerRadius = barlistSpec.cornerRadius === "pill" ? barHeight / 2 : barlistSpec.cornerRadius;
7882
+ const rowContentHeight = Math.max(barHeight, LABEL_FONT_SIZE6 * 1.4);
7883
+ const rowHeight = rowContentHeight + DEFAULT_ROW_GAP;
7884
+ const maxFittingRows = Math.max(1, Math.floor(fullArea.height / rowHeight));
7885
+ const validRows = barlistSpec.data.filter((row) => {
7886
+ const val = row[valueField];
7887
+ return val !== null && val !== void 0 && !Number.isNaN(Number(val));
7888
+ }).sort((a, b) => Number(b[valueField]) - Number(a[valueField])).slice(0, Math.min(barlistSpec.maxItems, maxFittingRows));
7889
+ if (validRows.length === 0) {
7890
+ return emptyLayout(chrome, theme, options, watermark);
7891
+ }
7892
+ const maxValue = Math.max(...validRows.map((r) => Math.abs(Number(r[valueField]))));
7893
+ const colorMap = /* @__PURE__ */ new Map();
7894
+ let colorIndex = 0;
7895
+ const palette = BARLIST_COLORS;
7896
+ function getColor2(row, idx) {
7897
+ if (colorField) {
7898
+ const key = String(row[colorField] ?? "");
7899
+ if (!colorMap.has(key)) {
7900
+ colorMap.set(key, palette[colorIndex % palette.length]);
7901
+ colorIndex++;
7902
+ }
7903
+ return colorMap.get(key);
7904
+ }
7905
+ return palette[idx % palette.length];
7906
+ }
7907
+ const formatter = buildD3Formatter4(barlistSpec.valueFormat) ?? formatNumber2;
7908
+ const measureText = options.measureText ?? ((text, fontSize) => ({
7909
+ width: estimateTextWidth7(text, fontSize),
7910
+ height: fontSize
7911
+ }));
7912
+ const perRowLabelWidths = /* @__PURE__ */ new Map();
7913
+ let maxCombinedWidth = 0;
7914
+ for (let i = 0; i < validRows.length; i++) {
7915
+ const row = validRows[i];
7916
+ const label = String(row[labelField] ?? "");
7917
+ const labelW = measureText(label, LABEL_FONT_SIZE6, LABEL_FONT_WEIGHT6).width;
7918
+ perRowLabelWidths.set(i, labelW);
7919
+ let combined = labelW + 4;
7920
+ if (subtitleField && row[subtitleField] != null) {
7921
+ const subtitle = String(row[subtitleField]);
7922
+ combined = labelW + 6 + measureText(subtitle, SUBTITLE_FONT_SIZE, SUBTITLE_FONT_WEIGHT).width + 4;
7923
+ }
7924
+ maxCombinedWidth = Math.max(maxCombinedWidth, combined);
7925
+ }
7926
+ const isNarrow = fullArea.width < 400;
7927
+ const labelBarGap = isNarrow ? 8 : LABEL_BAR_GAP;
7928
+ const barValueGap = isNarrow ? 6 : BAR_VALUE_GAP;
7929
+ const valueWidth = isNarrow ? 44 : VALUE_WIDTH;
7930
+ const maxLabelPct = isNarrow ? 0.35 : 0.4;
7931
+ const labelWidth = Math.max(50, Math.min(maxCombinedWidth, fullArea.width * maxLabelPct));
7932
+ const barAreaWidth = fullArea.width - labelWidth - labelBarGap - barValueGap - valueWidth;
7933
+ const labelColor = theme.colors.text;
7934
+ const subtitleColor = options.darkMode ? "rgba(255,255,255,0.5)" : "rgba(0,0,0,0.45)";
7935
+ const valueColor = options.darkMode ? "rgba(255,255,255,0.6)" : "rgba(0,0,0,0.55)";
7936
+ const rows = [];
7937
+ for (let i = 0; i < validRows.length; i++) {
7938
+ const row = validRows[i];
7939
+ const value2 = Number(row[valueField]);
7940
+ const labelText = String(row[labelField] ?? "");
7941
+ const formattedValue = formatter(value2);
7942
+ const barColor = getColor2(row, i);
7943
+ const pct = maxValue > 0 ? Math.abs(value2) / maxValue : 0;
7944
+ const rowY = fullArea.y + i * rowHeight;
7945
+ const centerY = rowY + rowContentHeight / 2;
7946
+ const labelX = fullArea.x;
7947
+ const labelStyle = {
7948
+ fontFamily: theme.fonts.family,
7949
+ fontSize: LABEL_FONT_SIZE6,
7950
+ fontWeight: LABEL_FONT_WEIGHT6,
7951
+ fill: labelColor,
7952
+ lineHeight: 1.4
7953
+ };
7954
+ let subtitle;
7955
+ if (subtitleField && row[subtitleField] != null) {
7956
+ const subtitleText = String(row[subtitleField]);
7957
+ const subtitleX = labelX + (perRowLabelWidths.get(i) ?? 0) + 6;
7958
+ subtitle = {
7959
+ text: subtitleText,
7960
+ x: subtitleX,
7961
+ y: centerY,
7962
+ style: {
7963
+ fontFamily: theme.fonts.family,
7964
+ fontSize: SUBTITLE_FONT_SIZE,
7965
+ fontWeight: SUBTITLE_FONT_WEIGHT,
7966
+ fill: subtitleColor,
7967
+ lineHeight: 1.4
7968
+ },
7969
+ visible: true
7970
+ };
7971
+ }
7972
+ const trackX = fullArea.x + labelWidth + labelBarGap;
7973
+ const trackY = centerY - barHeight / 2;
7974
+ const trackWidth = Math.max(barAreaWidth, 0);
7975
+ const barWidth = Math.max(pct * trackWidth, 0);
7976
+ const valueLabelX = trackX + trackWidth + barValueGap + valueWidth;
7977
+ const valueLabelStyle = {
7978
+ fontFamily: `${theme.fonts.family}, ui-monospace, monospace`,
7979
+ fontSize: VALUE_FONT_SIZE,
7980
+ fontWeight: VALUE_FONT_WEIGHT,
7981
+ fill: valueColor,
7982
+ lineHeight: 1.4
7983
+ };
7984
+ const rowMark = {
7985
+ type: "barlist-row",
7986
+ index: i,
7987
+ y: rowY,
7988
+ height: rowHeight,
7989
+ label: {
7990
+ text: labelText,
7991
+ x: labelX,
7992
+ y: centerY,
7993
+ style: labelStyle,
7994
+ visible: true
7995
+ },
7996
+ subtitle,
7997
+ track: {
7998
+ x: trackX,
7999
+ y: trackY,
8000
+ width: trackWidth,
8001
+ height: barHeight,
8002
+ cornerRadius
8003
+ },
8004
+ bar: {
8005
+ x: trackX,
8006
+ y: trackY,
8007
+ width: barWidth,
8008
+ height: barHeight,
8009
+ cornerRadius,
8010
+ fill: barColor
8011
+ },
8012
+ valueLabel: {
8013
+ text: formattedValue,
8014
+ x: valueLabelX,
8015
+ y: centerY,
8016
+ style: valueLabelStyle,
8017
+ visible: true
8018
+ },
8019
+ value: value2,
8020
+ formattedValue,
8021
+ aria: {
8022
+ role: "listitem",
8023
+ label: `${labelText}: ${formattedValue}`
8024
+ },
8025
+ animationIndex: i,
8026
+ data: row
8027
+ };
8028
+ rows.push(rowMark);
8029
+ }
8030
+ const tooltipDescriptors = /* @__PURE__ */ new Map();
8031
+ for (const row of rows) {
8032
+ const fields = [
8033
+ { label: barlistSpec.encoding.value.title ?? valueField, value: row.formattedValue }
8034
+ ];
8035
+ tooltipDescriptors.set(String(row.index), {
8036
+ title: row.label.text,
8037
+ fields
8038
+ });
8039
+ }
8040
+ const a11y = {
8041
+ altText: `Bar list showing ${rows.length} items ranked by ${valueField}`,
8042
+ dataTableFallback: rows.map((r) => [r.label.text, r.formattedValue]),
8043
+ role: "list",
8044
+ keyboardNavigable: rows.length > 0
8045
+ };
8046
+ const resolvedAnimation = resolveAnimation(barlistSpec.animation);
8047
+ return {
8048
+ area: fullArea,
8049
+ chrome,
8050
+ rows,
8051
+ tooltipDescriptors,
8052
+ a11y,
8053
+ theme,
8054
+ width: options.width,
8055
+ height: options.height,
8056
+ animation: resolvedAnimation,
8057
+ watermark,
8058
+ measureText
8059
+ };
8060
+ }
8061
+ function emptyLayout(chrome, theme, options, watermark) {
8062
+ return {
8063
+ area: { x: 0, y: 0, width: 0, height: 0 },
8064
+ chrome,
8065
+ rows: [],
8066
+ tooltipDescriptors: /* @__PURE__ */ new Map(),
8067
+ a11y: {
8068
+ altText: "Empty bar list",
8069
+ dataTableFallback: [],
8070
+ role: "list",
8071
+ keyboardNavigable: false
8072
+ },
8073
+ theme,
8074
+ width: options.width,
8075
+ height: options.height,
8076
+ watermark,
8077
+ animation: void 0,
8078
+ measureText: options.measureText ?? ((text, fontSize) => ({ width: estimateTextWidth7(text, fontSize), height: fontSize }))
8079
+ };
8080
+ }
8081
+
8082
+ // src/charts/post-process.ts
8083
+ function computeMarkObstacles(marks, scales) {
8084
+ if (scales.y?.type === "band") {
8085
+ return computeBandRowObstacles(marks, scales);
8086
+ }
8087
+ const obstacles = [];
8088
+ for (const mark of marks) {
8089
+ if (mark.type === "rect") {
8090
+ const rm = mark;
8091
+ obstacles.push({ x: rm.x, y: rm.y, width: rm.width, height: rm.height });
8092
+ } else if (mark.type === "point") {
8093
+ const pm = mark;
8094
+ obstacles.push({
8095
+ x: pm.cx - pm.r,
8096
+ y: pm.cy - pm.r,
8097
+ width: pm.r * 2,
8098
+ height: pm.r * 2
8099
+ });
8100
+ }
8101
+ }
8102
+ return obstacles;
8103
+ }
8104
+ function computeBandRowObstacles(marks, scales) {
8105
+ const rows = /* @__PURE__ */ new Map();
8106
+ for (const mark of marks) {
8107
+ let cy;
8108
+ let left2;
8109
+ let right2;
8110
+ if (mark.type === "point") {
8111
+ const pm = mark;
8112
+ cy = pm.cy;
8113
+ left2 = pm.cx - pm.r;
8114
+ right2 = pm.cx + pm.r;
8115
+ } else if (mark.type === "rect") {
8116
+ const rm = mark;
8117
+ cy = rm.y + rm.height / 2;
8118
+ left2 = rm.x;
8119
+ right2 = rm.x + rm.width;
8120
+ } else {
8121
+ continue;
8122
+ }
8123
+ const key = Math.round(cy);
8124
+ const existing = rows.get(key);
8125
+ if (existing) {
8126
+ existing.minX = Math.min(existing.minX, left2);
8127
+ existing.maxX = Math.max(existing.maxX, right2);
8128
+ } else {
8129
+ rows.set(key, { minX: left2, maxX: right2, bandY: cy });
8130
+ }
8131
+ }
8132
+ const bandScale = scales.y.scale;
8133
+ const bandwidth = bandScale.bandwidth?.() ?? 0;
8134
+ if (bandwidth === 0) return [];
8135
+ const obstacles = [];
8136
+ for (const { minX, maxX, bandY } of rows.values()) {
8137
+ obstacles.push({
8138
+ x: minX,
8139
+ y: bandY - bandwidth / 2,
8140
+ width: maxX - minX,
8141
+ height: bandwidth
8142
+ });
7706
8143
  }
7707
- const obj = spec;
7708
- const hasLayer = "layer" in obj && Array.isArray(obj.layer);
7709
- const hasMark = "mark" in obj;
7710
- const isTable = obj.type === "table";
7711
- const isGraph = obj.type === "graph";
7712
- const isSankey = obj.type === "sankey";
7713
- const isTileMap = obj.type === "tilemap";
7714
- const isLayer = hasLayer && !isTable && !isGraph && !isSankey && !isTileMap;
7715
- const isChart = hasMark && !hasLayer && !isTable && !isGraph && !isSankey && !isTileMap;
7716
- if (!isChart && !isTable && !isGraph && !isSankey && !isTileMap && !isLayer) {
7717
- return {
7718
- valid: false,
7719
- errors: [
7720
- {
7721
- message: 'Spec error: spec must have a "mark" field for charts, a "layer" array for layered charts, or a "type" field for tables/graphs/sankey/tilemap',
7722
- path: "mark",
7723
- code: "MISSING_FIELD",
7724
- suggestion: `Add a "mark" field for charts (e.g. mark: "bar"), a "layer" array for layered charts, or a "type" field for tables/graphs/sankey/tilemap (type: "table", type: "graph", type: "sankey", or type: "tilemap"). Valid mark types: ${[...MARK_TYPES].join(", ")}`
7725
- }
7726
- ],
7727
- normalized: null
7728
- };
8144
+ return obstacles;
8145
+ }
8146
+ function resolveRendererKey(markType, encoding, markDef) {
8147
+ if (markType === "bar") {
8148
+ const xType = encoding.x?.type;
8149
+ const yType = encoding.y?.type;
8150
+ const isVertical = (xType === "nominal" || xType === "ordinal" || xType === "temporal") && yType === "quantitative";
8151
+ if (isVertical) {
8152
+ return "bar:vertical";
8153
+ }
8154
+ } else if (markType === "arc") {
8155
+ const innerRadius = markDef.innerRadius;
8156
+ if (innerRadius && innerRadius > 0) {
8157
+ return "arc:donut";
8158
+ }
7729
8159
  }
7730
- if (isLayer) {
7731
- validateLayerSpec(obj, errors);
8160
+ return markType;
8161
+ }
8162
+ function getMarkPrimaryValue(mark) {
8163
+ switch (mark.type) {
8164
+ case "rect":
8165
+ return mark.height;
8166
+ // bar height is the primary value encoding
8167
+ case "point":
8168
+ return mark.cy;
8169
+ // y position for scatter
8170
+ case "arc":
8171
+ return mark.endAngle - mark.startAngle;
8172
+ // arc angle extent
8173
+ case "line":
8174
+ case "area":
8175
+ return 0;
8176
+ // series marks don't have individual values
8177
+ default:
8178
+ return 0;
7732
8179
  }
7733
- if (isChart) {
7734
- const mark = obj.mark;
7735
- let markValue;
7736
- if (typeof mark === "string") {
7737
- markValue = mark;
7738
- } else if (mark && typeof mark === "object" && !Array.isArray(mark)) {
7739
- markValue = mark.type;
8180
+ }
8181
+ function assignAnimationIndices(marks, animation) {
8182
+ if (!animation?.enabled) return;
8183
+ if (animation.staggerOrder === "value") {
8184
+ const indexed = marks.map((m, i) => ({ mark: m, idx: i }));
8185
+ indexed.sort((a, b) => {
8186
+ const av = getMarkPrimaryValue(a.mark);
8187
+ const bv = getMarkPrimaryValue(b.mark);
8188
+ return av - bv;
8189
+ });
8190
+ for (let i = 0; i < indexed.length; i++) {
8191
+ const m = indexed[i].mark;
8192
+ if (m.type === "rect" && m.stackGroup) continue;
8193
+ m.animationIndex = i;
7740
8194
  }
7741
- if (!markValue || !MARK_TYPES.has(markValue)) {
7742
- return {
7743
- valid: false,
7744
- errors: [
7745
- {
7746
- message: `Spec error: "${markValue ?? String(mark)}" is not a valid mark type. Valid mark types: ${[...MARK_TYPES].join(", ")}`,
7747
- path: "mark",
7748
- code: "INVALID_VALUE",
7749
- suggestion: `Change mark to one of: ${[...MARK_TYPES].join(", ")}`
7750
- }
7751
- ],
7752
- normalized: null
7753
- };
8195
+ }
8196
+ const groupIndexMap = /* @__PURE__ */ new Map();
8197
+ const groupStackPos = /* @__PURE__ */ new Map();
8198
+ let nextGroupIndex = 0;
8199
+ for (const mark of marks) {
8200
+ if (mark.type === "rect" && mark.stackGroup) {
8201
+ const rect = mark;
8202
+ const group = rect.stackGroup;
8203
+ if (!groupIndexMap.has(group)) {
8204
+ groupIndexMap.set(group, nextGroupIndex++);
8205
+ }
8206
+ rect.animationIndex = groupIndexMap.get(group);
8207
+ const pos = groupStackPos.get(group) ?? 0;
8208
+ rect.stackPos = pos;
8209
+ groupStackPos.set(group, pos + 1);
7754
8210
  }
7755
- validateChartSpec(obj, errors);
7756
- } else if (isTable) {
7757
- validateTableSpec(obj, errors);
7758
- } else if (isGraph) {
7759
- validateGraphSpec(obj, errors);
7760
- } else if (isSankey) {
7761
- validateSankeySpec(obj, errors);
7762
- } else if (isTileMap) {
7763
- validateTileMapSpec(obj, errors);
7764
8211
  }
7765
- if (errors.length > 0) {
7766
- return { valid: false, errors, normalized: null };
8212
+ }
8213
+
8214
+ // src/compile/color-scale-range.ts
8215
+ function applyColorScaleRange(scales, encoding, theme) {
8216
+ if (!scales.color) return;
8217
+ const hasExplicitRange = !!(encoding.color && "field" in encoding.color && encoding.color.scale?.range?.length);
8218
+ if (hasExplicitRange) return;
8219
+ if (scales.color.type === "sequential") {
8220
+ const seqStops = Object.values(theme.colors.sequential)[0] ?? theme.colors.categorical;
8221
+ scales.color.scale.range([
8222
+ seqStops[0],
8223
+ seqStops[seqStops.length - 1]
8224
+ ]);
8225
+ } else {
8226
+ scales.color.scale.range(theme.colors.categorical);
7767
8227
  }
7768
- return {
7769
- valid: true,
7770
- errors: [],
7771
- normalized: spec
7772
- };
7773
8228
  }
7774
8229
 
7775
- // src/compiler/index.ts
7776
- function compile(spec) {
7777
- const validation = validateSpec(spec);
7778
- if (!validation.valid || !validation.normalized) {
7779
- const errorMessages = validation.errors.map((e) => e.message).join("\n");
7780
- throw new Error(`Invalid spec:
7781
- ${errorMessages}`);
8230
+ // src/compile/data-clip.ts
8231
+ function filterClippedDomains(data, encoding) {
8232
+ let result = data;
8233
+ for (const channel of ["x", "y"]) {
8234
+ const enc = encoding[channel];
8235
+ if (!enc?.scale?.clip || !enc.scale.domain) continue;
8236
+ const domain = enc.scale.domain;
8237
+ const field = enc.field;
8238
+ if (Array.isArray(domain) && domain.length === 2 && typeof domain[0] === "number") {
8239
+ const [lo, hi] = domain;
8240
+ result = result.filter((row) => {
8241
+ const v = Number(row[field]);
8242
+ return Number.isFinite(v) && v >= lo && v <= hi;
8243
+ });
8244
+ }
7782
8245
  }
7783
- const warnings = [];
7784
- const normalized = normalizeSpec(validation.normalized, warnings);
7785
- return { spec: normalized, warnings };
8246
+ return result;
8247
+ }
8248
+
8249
+ // src/compile/watermark-obstacle.ts
8250
+ import { BRAND_RESERVE_WIDTH } from "@opendata-ai/openchart-core";
8251
+ var WATERMARK_HEIGHT = 30;
8252
+ var X_AXIS_EXTENT_WITH_LABEL = 48;
8253
+ var X_AXIS_EXTENT_TICKS_ONLY = 26;
8254
+ function computeWatermarkObstacle(dims, watermark, axes, theme) {
8255
+ if (!watermark) return null;
8256
+ const chartArea = dims.chartArea;
8257
+ const brandPadding = theme.spacing.padding;
8258
+ const brandX = dims.total.width - brandPadding - BRAND_RESERVE_WIDTH;
8259
+ const xAxisExtent = axes.x?.label ? X_AXIS_EXTENT_WITH_LABEL : axes.x ? X_AXIS_EXTENT_TICKS_ONLY : 0;
8260
+ const firstBottomChrome = dims.chrome.source ?? dims.chrome.byline ?? dims.chrome.footer;
8261
+ const brandY = firstBottomChrome ? chartArea.y + chartArea.height + xAxisExtent + firstBottomChrome.y : chartArea.y + chartArea.height + xAxisExtent + theme.spacing.chartToFooter;
8262
+ return { x: brandX, y: brandY, width: BRAND_RESERVE_WIDTH, height: WATERMARK_HEIGHT };
7786
8263
  }
7787
8264
 
7788
8265
  // src/graphs/compile-graph.ts
7789
- import { adaptTheme, computeChrome, resolveTheme } from "@opendata-ai/openchart-core";
8266
+ import { adaptTheme as adaptTheme2, computeChrome as computeChrome2, resolveTheme as resolveTheme2 } from "@opendata-ai/openchart-core";
7790
8267
 
7791
8268
  // src/graphs/encoding.ts
7792
8269
  var DEFAULT_NODE_RADIUS = 5;
@@ -8090,9 +8567,9 @@ function compileGraph(spec, options) {
8090
8567
  const rawWatermark = spec.watermark;
8091
8568
  const watermark = rawWatermark !== void 0 ? graphSpec.watermark : options.watermark ?? true;
8092
8569
  const mergedThemeConfig = options.theme ? { ...graphSpec.theme, ...options.theme } : graphSpec.theme;
8093
- let theme = resolveTheme(mergedThemeConfig);
8570
+ let theme = resolveTheme2(mergedThemeConfig);
8094
8571
  if (options.darkMode) {
8095
- theme = adaptTheme(theme);
8572
+ theme = adaptTheme2(theme);
8096
8573
  }
8097
8574
  const compiledNodes = resolveNodeVisuals(
8098
8575
  graphSpec.nodes,
@@ -8146,7 +8623,7 @@ function compileGraph(spec, options) {
8146
8623
  linkStrength: graphSpec.layout.linkStrength,
8147
8624
  centerForce: graphSpec.layout.centerForce
8148
8625
  };
8149
- const chrome = computeChrome(
8626
+ const chrome = computeChrome2(
8150
8627
  {
8151
8628
  title: graphSpec.chrome.title,
8152
8629
  subtitle: graphSpec.chrome.subtitle,
@@ -8180,11 +8657,11 @@ function compileGraph(spec, options) {
8180
8657
  var DEFAULT_COLLISION_PADDING = 5;
8181
8658
 
8182
8659
  // src/layout/axes/thinning.ts
8183
- import { estimateTextWidth as estimateTextWidth7 } from "@opendata-ai/openchart-core";
8660
+ import { estimateTextWidth as estimateTextWidth8 } from "@opendata-ai/openchart-core";
8184
8661
  var MIN_TICK_GAP_FACTOR = 1;
8185
8662
  var MIN_TICK_COUNT = 2;
8186
8663
  function measureLabel(text, fontSize, fontWeight, measureText) {
8187
- return measureText ? measureText(text, fontSize, fontWeight).width : estimateTextWidth7(text, fontSize, fontWeight);
8664
+ return measureText ? measureText(text, fontSize, fontWeight).width : estimateTextWidth8(text, fontSize, fontWeight);
8188
8665
  }
8189
8666
  function ticksOverlap(ticks2, fontSize, fontWeight, measureText, orientation = "horizontal") {
8190
8667
  if (ticks2.length < 2) return false;
@@ -8226,16 +8703,16 @@ function thinTicksUntilFit(ticks2, fontSize, fontWeight, measureText, orientatio
8226
8703
  // src/layout/axes/ticks.ts
8227
8704
  import {
8228
8705
  abbreviateNumber as abbreviateNumber2,
8229
- buildD3Formatter as buildD3Formatter4,
8706
+ buildD3Formatter as buildD3Formatter5,
8230
8707
  buildTemporalFormatter,
8231
- estimateTextWidth as estimateTextWidth8,
8708
+ estimateTextWidth as estimateTextWidth9,
8232
8709
  formatDate,
8233
- formatNumber as formatNumber2
8710
+ formatNumber as formatNumber3
8234
8711
  } from "@opendata-ai/openchart-core";
8235
8712
  var Y_PX_PER_TICK = {
8236
- full: 55,
8237
- reduced: 90,
8238
- minimal: 140
8713
+ full: 40,
8714
+ reduced: 70,
8715
+ minimal: 120
8239
8716
  };
8240
8717
  var X_PX_PER_TICK = {
8241
8718
  full: 110,
@@ -8275,7 +8752,8 @@ var NUMERIC_SCALE_TYPES = /* @__PURE__ */ new Set([
8275
8752
  ]);
8276
8753
  var TEMPORAL_SCALE_TYPES = /* @__PURE__ */ new Set(["time", "utc"]);
8277
8754
  function formatTickLabel(value2, resolvedScale) {
8278
- const formatStr = resolvedScale.channel.axis?.format;
8755
+ const axisConfig = resolvedScale.channel.axis || void 0;
8756
+ const formatStr = axisConfig?.format;
8279
8757
  if (TEMPORAL_SCALE_TYPES.has(resolvedScale.type)) {
8280
8758
  const temporalFmt = buildTemporalFormatter(formatStr);
8281
8759
  if (temporalFmt) return temporalFmt(value2);
@@ -8285,11 +8763,11 @@ function formatTickLabel(value2, resolvedScale) {
8285
8763
  if (NUMERIC_SCALE_TYPES.has(resolvedScale.type)) {
8286
8764
  const num = value2;
8287
8765
  if (formatStr) {
8288
- const fmt = buildD3Formatter4(formatStr);
8766
+ const fmt = buildD3Formatter5(formatStr);
8289
8767
  if (fmt) return fmt(num);
8290
8768
  }
8291
8769
  if (Math.abs(num) >= 1e3) return abbreviateNumber2(num);
8292
- return formatNumber2(num);
8770
+ return formatNumber3(num);
8293
8771
  }
8294
8772
  return String(value2);
8295
8773
  }
@@ -8303,8 +8781,8 @@ function continuousTicks(resolvedScale, density, targetCount) {
8303
8781
  label: formatTickLabel(value2, resolvedScale)
8304
8782
  }));
8305
8783
  }
8306
- const explicitCount = resolvedScale.channel.axis?.tickCount;
8307
- const count = explicitCount ?? targetCount ?? TICK_COUNTS[density];
8784
+ const axCfg = resolvedScale.channel.axis || void 0;
8785
+ const count = axCfg?.tickCount ?? targetCount ?? TICK_COUNTS[density];
8308
8786
  return buildContinuousTicks(resolvedScale, count);
8309
8787
  }
8310
8788
  function buildContinuousTicks(resolvedScale, count) {
@@ -8313,7 +8791,21 @@ function buildContinuousTicks(resolvedScale, count) {
8313
8791
  return continuousTicks(resolvedScale, "full");
8314
8792
  }
8315
8793
  const raw = scale.ticks(count);
8316
- return raw.map((value2) => ({
8794
+ let ticks2 = raw;
8795
+ if (resolvedScale.type === "log" && raw.length > count) {
8796
+ const base = resolvedScale.channel.scale?.base ?? 10;
8797
+ const logBase = Math.log(base);
8798
+ const powered = raw.filter((v) => {
8799
+ const n = v;
8800
+ if (n <= 0) return false;
8801
+ const exp = Math.log(n) / logBase;
8802
+ return Math.abs(exp - Math.round(exp)) < 1e-9;
8803
+ });
8804
+ if (powered.length >= 2) {
8805
+ ticks2 = powered;
8806
+ }
8807
+ }
8808
+ return ticks2.map((value2) => ({
8317
8809
  value: value2,
8318
8810
  position: scale(value2),
8319
8811
  label: formatTickLabel(value2, resolvedScale)
@@ -8326,12 +8818,13 @@ function scaleSupportsTickCount(resolvedScale) {
8326
8818
  function categoricalTicks(resolvedScale, density, orientation = "horizontal", bandwidth, labelAngle, fontSize, fontWeight, measureText, subtitleContext) {
8327
8819
  const scale = resolvedScale.scale;
8328
8820
  const domain = scale.domain();
8329
- const explicitTickCount = resolvedScale.channel.axis?.tickCount;
8821
+ const catAxisCfg = resolvedScale.channel.axis || void 0;
8822
+ const explicitTickCount = catAxisCfg?.tickCount;
8330
8823
  let selectedValues = domain;
8331
8824
  if (resolvedScale.type === "band" && orientation === "horizontal") {
8332
8825
  if (bandwidth !== void 0 && bandwidth > 0 && fontSize !== void 0) {
8333
8826
  const maxLabelWidth = domain.reduce((max4, v) => {
8334
- const w = measureText ? measureText(v, fontSize, fontWeight ?? 400).width : estimateTextWidth8(v, fontSize, fontWeight ?? 400);
8827
+ const w = measureText ? measureText(v, fontSize, fontWeight ?? 400).width : estimateTextWidth9(v, fontSize, fontWeight ?? 400);
8335
8828
  return Math.max(max4, w);
8336
8829
  }, 0);
8337
8830
  const angleRad = labelAngle !== void 0 ? Math.abs(labelAngle) * Math.PI / 180 : 0;
@@ -8416,8 +8909,8 @@ function resolveExplicitTicks(values, resolvedScale) {
8416
8909
  }
8417
8910
 
8418
8911
  // src/layout/axes.ts
8419
- var HEIGHT_MINIMAL_THRESHOLD = 120;
8420
- var HEIGHT_REDUCED_THRESHOLD = 200;
8912
+ var HEIGHT_MINIMAL_THRESHOLD = 80;
8913
+ var HEIGHT_REDUCED_THRESHOLD = 100;
8421
8914
  var WIDTH_MINIMAL_THRESHOLD = 150;
8422
8915
  var WIDTH_REDUCED_THRESHOLD = 300;
8423
8916
  var DENSITY_ORDER = ["full", "reduced", "minimal"];
@@ -8493,7 +8986,7 @@ function computeAxes(scales, chartArea, strategy, theme, measureText, dataContex
8493
8986
  };
8494
8987
  const { fontSize } = tickLabelStyle;
8495
8988
  const { fontWeight } = tickLabelStyle;
8496
- if (scales.x && !dataContext?.skipX) {
8989
+ if (scales.x && !dataContext?.skipX && scales.x.channel.axis !== false) {
8497
8990
  const axisConfig = scales.x.channel.axis;
8498
8991
  const isContinuousX = scales.x.type !== "band" && scales.x.type !== "point" && scales.x.type !== "ordinal";
8499
8992
  const xTargetCount = isContinuousX ? targetTickCount(chartArea.width, xDensity, "x") : void 0;
@@ -8571,7 +9064,7 @@ function computeAxes(scales, chartArea, strategy, theme, measureText, dataContex
8571
9064
  labelFlush: axisConfig?.labelFlush
8572
9065
  };
8573
9066
  }
8574
- if (scales.y && !dataContext?.skipY) {
9067
+ if (scales.y && !dataContext?.skipY && scales.y.channel.axis !== false) {
8575
9068
  const axisConfig = scales.y.channel.axis;
8576
9069
  const isContinuousY = scales.y.type !== "band" && scales.y.type !== "point" && scales.y.type !== "ordinal";
8577
9070
  const yTargetCount = isContinuousY ? targetTickCount(chartArea.height, yDensity, "y") : void 0;
@@ -8647,8 +9140,8 @@ function computeAxes(scales, chartArea, strategy, theme, measureText, dataContex
8647
9140
  import {
8648
9141
  AXIS_TITLE_TRAILING_PAD,
8649
9142
  BREAKPOINT_COMPACT_MAX,
8650
- computeChrome as computeChrome2,
8651
- estimateTextWidth as estimateTextWidth10,
9143
+ computeChrome as computeChrome3,
9144
+ estimateTextWidth as estimateTextWidth11,
8652
9145
  getAxisTitleOffset,
8653
9146
  HPAD_COMPACT_FRACTION,
8654
9147
  HPAD_COMPACT_MIN,
@@ -8663,12 +9156,12 @@ import {
8663
9156
  } from "@opendata-ai/openchart-core";
8664
9157
 
8665
9158
  // src/legend/wrap.ts
8666
- import { COMPACT_WIDTH, estimateTextWidth as estimateTextWidth9 } from "@opendata-ai/openchart-core";
9159
+ import { COMPACT_WIDTH, estimateTextWidth as estimateTextWidth10 } from "@opendata-ai/openchart-core";
8667
9160
  var SWATCH_SIZE2 = 12;
8668
9161
  var SWATCH_GAP2 = 6;
8669
9162
  var ENTRY_GAP2 = 16;
8670
9163
  var ENTRY_GAP_COMPACT = 10;
8671
- var LEGEND_GAP = 4;
9164
+ var LEGEND_GAP = 8;
8672
9165
  function legendGap(width) {
8673
9166
  return width < COMPACT_WIDTH ? 0 : LEGEND_GAP;
8674
9167
  }
@@ -8682,7 +9175,7 @@ function measureLegendWrap(entries, maxWidth, labelStyle, maxRows, entryGap = EN
8682
9175
  let fittingCount = entries.length;
8683
9176
  let fittingCountLocked = false;
8684
9177
  for (let i = 0; i < entries.length; i++) {
8685
- const labelWidth = estimateTextWidth9(
9178
+ const labelWidth = estimateTextWidth10(
8686
9179
  entries[i].label,
8687
9180
  labelStyle.fontSize,
8688
9181
  labelStyle.fontWeight
@@ -8728,7 +9221,9 @@ function getMinChartDims(display) {
8728
9221
  }
8729
9222
  function getSparklinePad(spec) {
8730
9223
  const strokeWidth = spec.markDef.strokeWidth ?? 2;
8731
- return Math.max(strokeWidth / 2 + 1, 2);
9224
+ const hasPoints = !!spec.markDef.point;
9225
+ const pointRadius = hasPoints ? 3 : 0;
9226
+ return Math.max(strokeWidth / 2 + 1, pointRadius + 1, 2);
8732
9227
  }
8733
9228
  function computeDimensions(spec, options, legendLayout, theme, strategy, watermark = true) {
8734
9229
  const { width, height } = options;
@@ -8741,7 +9236,7 @@ function computeDimensions(spec, options, legendLayout, theme, strategy, waterma
8741
9236
  if (isSparkline && !userExplicit.chrome) {
8742
9237
  chromeMode = "hidden";
8743
9238
  }
8744
- const chrome = computeChrome2(
9239
+ const chrome = computeChrome3(
8745
9240
  chromeToInput(spec.chrome),
8746
9241
  theme,
8747
9242
  width,
@@ -8782,11 +9277,12 @@ function computeDimensions(spec, options, legendLayout, theme, strategy, waterma
8782
9277
  const total = { x: 0, y: 0, width, height };
8783
9278
  const isRadial = spec.markType === "arc";
8784
9279
  const encoding = spec.encoding;
8785
- const xAxis = encoding.x?.axis;
9280
+ const xAxisSuppressed = encoding.x?.axis === false;
9281
+ const xAxis = !xAxisSuppressed && encoding.x?.axis;
8786
9282
  const hasXAxisLabel = !!xAxis?.title;
8787
9283
  const xTickAngle = xAxis?.labelAngle;
8788
9284
  let xAxisHeight;
8789
- if (isRadial) {
9285
+ if (isRadial || xAxisSuppressed) {
8790
9286
  xAxisHeight = 0;
8791
9287
  } else if (xTickAngle && Math.abs(xTickAngle) > 10) {
8792
9288
  const angleRad = Math.abs(xTickAngle) * (Math.PI / 180);
@@ -8795,7 +9291,7 @@ function computeDimensions(spec, options, legendLayout, theme, strategy, waterma
8795
9291
  if (xField) {
8796
9292
  for (const row of spec.data) {
8797
9293
  const label = String(row[xField] ?? "");
8798
- const w = estimateTextWidth10(label, theme.fonts.sizes.axisTick, theme.fonts.weights.normal);
9294
+ const w = estimateTextWidth11(label, theme.fonts.sizes.axisTick, theme.fonts.weights.normal);
8799
9295
  if (w > maxLabelWidth) maxLabelWidth = w;
8800
9296
  }
8801
9297
  }
@@ -8825,7 +9321,7 @@ function computeDimensions(spec, options, legendLayout, theme, strategy, waterma
8825
9321
  const label = String(row[colorField] ?? "");
8826
9322
  if (!seen.has(label)) {
8827
9323
  seen.add(label);
8828
- const w = estimateTextWidth10(label, 11, 600);
9324
+ const w = estimateTextWidth11(label, 11, 600);
8829
9325
  if (w > maxLabelWidth) maxLabelWidth = w;
8830
9326
  }
8831
9327
  }
@@ -8845,7 +9341,7 @@ function computeDimensions(spec, options, legendLayout, theme, strategy, waterma
8845
9341
  const maxXStr = String(maxX);
8846
9342
  for (const ann of spec.annotations) {
8847
9343
  if (ann.type === "text" && String(ann.x) === maxXStr) {
8848
- const textWidth = estimateTextWidth10(ann.text, ann.fontSize ?? 11, ann.fontWeight ?? 600);
9344
+ const textWidth = estimateTextWidth11(ann.text, ann.fontSize ?? 11, ann.fontWeight ?? 600);
8849
9345
  const dx = ann.offset?.dx ?? 0;
8850
9346
  const anchor = ann.anchor ?? "auto";
8851
9347
  const baseRightExtent = anchor === "left" ? textWidth : (
@@ -8863,19 +9359,20 @@ function computeDimensions(spec, options, legendLayout, theme, strategy, waterma
8863
9359
  }
8864
9360
  }
8865
9361
  }
8866
- if (encoding.y && !isRadial) {
9362
+ const yAxisSuppressed = encoding.y?.axis === false;
9363
+ if (encoding.y && !isRadial && !yAxisSuppressed) {
8867
9364
  if (spec.markType === "bar" || spec.markType === "circle" || spec.markType === "lollipop" || encoding.y.type === "nominal" || encoding.y.type === "ordinal") {
8868
9365
  const yField = encoding.y.field;
8869
9366
  const yLabelField = encoding.y.axis?.labelField;
8870
9367
  let maxLabelWidth = 0;
8871
9368
  for (const row of spec.data) {
8872
9369
  const label = String(row[yField] ?? "");
8873
- let w = estimateTextWidth10(label, theme.fonts.sizes.axisTick, theme.fonts.weights.normal);
9370
+ let w = estimateTextWidth11(label, theme.fonts.sizes.axisTick, theme.fonts.weights.normal);
8874
9371
  if (yLabelField) {
8875
9372
  const subtitle = String(row[yLabelField] ?? "");
8876
9373
  if (subtitle) {
8877
9374
  const gap = theme.fonts.sizes.axisTick * 0.6;
8878
- const subtitleWidth = estimateTextWidth10(
9375
+ const subtitleWidth = estimateTextWidth11(
8879
9376
  subtitle,
8880
9377
  theme.fonts.sizes.axisTick,
8881
9378
  theme.fonts.weights.normal
@@ -8918,7 +9415,7 @@ function computeDimensions(spec, options, legendLayout, theme, strategy, waterma
8918
9415
  }
8919
9416
  const negPrefix = spec.data.some((r) => Number(r[yField]) < 0) ? "-" : "";
8920
9417
  const labelEst = negPrefix + sampleLabel;
8921
- const labelWidth = estimateTextWidth10(
9418
+ const labelWidth = estimateTextWidth11(
8922
9419
  labelEst,
8923
9420
  theme.fonts.sizes.axisTick,
8924
9421
  theme.fonts.weights.normal
@@ -8955,7 +9452,7 @@ function computeDimensions(spec, options, legendLayout, theme, strategy, waterma
8955
9452
  const minDims = getMinChartDims(spec.display);
8956
9453
  if ((chartArea.width < minDims.width || chartArea.height < minDims.height) && chromeMode !== "hidden") {
8957
9454
  const fallbackMode = chromeMode === "full" ? "compact" : "hidden";
8958
- const fallbackChrome = computeChrome2(
9455
+ const fallbackChrome = computeChrome3(
8959
9456
  chromeToInput(spec.chrome),
8960
9457
  theme,
8961
9458
  width,
@@ -9090,6 +9587,13 @@ function buildLinearScale(channel, data, rangeStart, rangeEnd) {
9090
9587
  const scale = linear2().domain([domainMin, domainMax]).range([rangeStart, rangeEnd]);
9091
9588
  if (!channel.scale?.domain && channel.scale?.nice !== false) {
9092
9589
  scale.nice();
9590
+ if (channel.scale?.zero === false) {
9591
+ const [nicedMin, nicedMax] = scale.domain();
9592
+ if (nicedMin < domainMin || nicedMax > domainMax) {
9593
+ scale.domain([domainMin, domainMax]);
9594
+ scale.nice(20);
9595
+ }
9596
+ }
9093
9597
  }
9094
9598
  applyContinuousConfig(scale, channel);
9095
9599
  return { scale, type: "linear", channel };
@@ -9440,7 +9944,7 @@ function computeScales(spec, chartArea, data) {
9440
9944
  }
9441
9945
 
9442
9946
  // src/legend/compute.ts
9443
- import { BRAND_RESERVE_WIDTH as BRAND_RESERVE_WIDTH2, COMPACT_WIDTH as COMPACT_WIDTH2, estimateTextWidth as estimateTextWidth11 } from "@opendata-ai/openchart-core";
9947
+ import { BRAND_RESERVE_WIDTH as BRAND_RESERVE_WIDTH2, COMPACT_WIDTH as COMPACT_WIDTH2, estimateTextWidth as estimateTextWidth12 } from "@opendata-ai/openchart-core";
9444
9948
  var LEGEND_PADDING = 8;
9445
9949
  var LEGEND_RIGHT_WIDTH = 120;
9446
9950
  var RIGHT_LEGEND_MAX_HEIGHT_RATIO = 0.4;
@@ -9557,7 +10061,7 @@ function computeLegend(spec, strategy, theme, chartArea, watermark = true) {
9557
10061
  }
9558
10062
  if (resolvedPosition === "right" || resolvedPosition === "bottom-right") {
9559
10063
  const maxLabelWidth = Math.max(
9560
- ...entries.map((e) => estimateTextWidth11(e.label, labelStyle.fontSize, labelStyle.fontWeight))
10064
+ ...entries.map((e) => estimateTextWidth12(e.label, labelStyle.fontSize, labelStyle.fontWeight))
9561
10065
  );
9562
10066
  const legendWidth = Math.min(
9563
10067
  LEGEND_RIGHT_WIDTH,
@@ -9617,7 +10121,7 @@ function computeLegend(spec, strategy, theme, chartArea, watermark = true) {
9617
10121
  entries = truncateEntries(entries, fittingCount);
9618
10122
  }
9619
10123
  const totalWidth = entries.reduce((sum2, entry) => {
9620
- const labelWidth = estimateTextWidth11(entry.label, labelStyle.fontSize, labelStyle.fontWeight);
10124
+ const labelWidth = estimateTextWidth12(entry.label, labelStyle.fontSize, labelStyle.fontWeight);
9621
10125
  return sum2 + SWATCH_SIZE2 + SWATCH_GAP2 + labelWidth + effectiveEntryGap;
9622
10126
  }, 0);
9623
10127
  const { rowCount } = measureLegendWrap(
@@ -9649,12 +10153,12 @@ function computeLegend(spec, strategy, theme, chartArea, watermark = true) {
9649
10153
 
9650
10154
  // src/sankey/compile-sankey.ts
9651
10155
  import {
9652
- adaptTheme as adaptTheme2,
9653
- buildD3Formatter as buildD3Formatter5,
9654
- computeChrome as computeChrome3,
9655
- estimateTextWidth as estimateTextWidth12,
9656
- formatNumber as formatNumber3,
9657
- resolveTheme as resolveTheme2
10156
+ adaptTheme as adaptTheme3,
10157
+ buildD3Formatter as buildD3Formatter6,
10158
+ computeChrome as computeChrome4,
10159
+ estimateTextWidth as estimateTextWidth13,
10160
+ formatNumber as formatNumber4,
10161
+ resolveTheme as resolveTheme3
9658
10162
  } from "@opendata-ai/openchart-core";
9659
10163
 
9660
10164
  // ../../node_modules/.bun/d3-array@2.12.1/node_modules/d3-array/src/max.js
@@ -10237,16 +10741,16 @@ function compileSankey(spec, options) {
10237
10741
  const rawWatermark = spec.watermark;
10238
10742
  const watermark = rawWatermark !== void 0 ? sankeySpec.watermark : options.watermark ?? true;
10239
10743
  const mergedThemeConfig = options.theme ? { ...sankeySpec.theme, ...options.theme } : sankeySpec.theme;
10240
- const lightTheme = resolveTheme2(mergedThemeConfig);
10744
+ const lightTheme = resolveTheme3(mergedThemeConfig);
10241
10745
  let theme = lightTheme;
10242
10746
  if (options.darkMode) {
10243
- theme = adaptTheme2(theme);
10747
+ theme = adaptTheme3(theme);
10244
10748
  theme = {
10245
10749
  ...theme,
10246
10750
  colors: { ...theme.colors, categorical: lightTheme.colors.categorical }
10247
10751
  };
10248
10752
  }
10249
- const chrome = computeChrome3(
10753
+ const chrome = computeChrome4(
10250
10754
  {
10251
10755
  title: sankeySpec.chrome.title,
10252
10756
  subtitle: sankeySpec.chrome.subtitle,
@@ -10269,7 +10773,7 @@ function compileSankey(spec, options) {
10269
10773
  height: options.height - chrome.topHeight - chrome.bottomHeight - padding * 2
10270
10774
  };
10271
10775
  if (fullArea.width <= 0 || fullArea.height <= 0) {
10272
- return emptyLayout(fullArea, chrome, theme, options, watermark);
10776
+ return emptyLayout2(fullArea, chrome, theme, options, watermark);
10273
10777
  }
10274
10778
  const sourceField = sankeySpec.encoding.source.field;
10275
10779
  const targetField = sankeySpec.encoding.target.field;
@@ -10305,7 +10809,7 @@ function compileSankey(spec, options) {
10305
10809
  height: fullArea.height - legend.bounds.height - legendGap2
10306
10810
  };
10307
10811
  if (area.height <= 0) {
10308
- return emptyLayout(area, chrome, theme, options, watermark);
10812
+ return emptyLayout2(area, chrome, theme, options, watermark);
10309
10813
  }
10310
10814
  const labelFontSize = theme.fonts.sizes.small;
10311
10815
  const labelFontWeight = theme.fonts.weights.normal;
@@ -10333,7 +10837,7 @@ function compileSankey(spec, options) {
10333
10837
  if (labelsLeft) continue;
10334
10838
  const labelX = (node.x1 ?? nodeWidth) + LABEL_GAP;
10335
10839
  const labelText = node.label ?? node.id;
10336
- const labelWidth = estimateTextWidth12(labelText, labelFontSize, labelFontWeight);
10840
+ const labelWidth = estimateTextWidth13(labelText, labelFontSize, labelFontWeight);
10337
10841
  const overflow = labelX + labelWidth - rightEdge;
10338
10842
  if (overflow > maxOverflow) maxOverflow = overflow;
10339
10843
  }
@@ -10523,10 +11027,10 @@ function buildSankeyLegend(nodeColorMap, colorField, data, sourceField, targetFi
10523
11027
  }
10524
11028
  function formatFlowValue(value2, valueFormat) {
10525
11029
  if (valueFormat) {
10526
- const fmt = buildD3Formatter5(valueFormat);
11030
+ const fmt = buildD3Formatter6(valueFormat);
10527
11031
  if (fmt) return fmt(value2);
10528
11032
  }
10529
- return formatNumber3(value2);
11033
+ return formatNumber4(value2);
10530
11034
  }
10531
11035
  function buildTooltipDescriptors(nodes, links, valueFormat) {
10532
11036
  const descriptors = /* @__PURE__ */ new Map();
@@ -10557,7 +11061,7 @@ function buildTooltipDescriptors(nodes, links, valueFormat) {
10557
11061
  }
10558
11062
  return descriptors;
10559
11063
  }
10560
- function emptyLayout(area, chrome, theme, options, watermark) {
11064
+ function emptyLayout2(area, chrome, theme, options, watermark) {
10561
11065
  return {
10562
11066
  area,
10563
11067
  chrome,
@@ -10595,7 +11099,7 @@ function emptyLayout(area, chrome, theme, options, watermark) {
10595
11099
  }
10596
11100
 
10597
11101
  // src/tables/compile-table.ts
10598
- import { computeChrome as computeChrome4, estimateTextWidth as estimateTextWidth13 } from "@opendata-ai/openchart-core";
11102
+ import { computeChrome as computeChrome5, estimateTextWidth as estimateTextWidth14 } from "@opendata-ai/openchart-core";
10599
11103
 
10600
11104
  // src/tables/bar-column.ts
10601
11105
  var NEGATIVE_BAR_COLOR = "#c44e52";
@@ -10712,7 +11216,7 @@ function computeCategoryColors(data, column, theme, darkMode) {
10712
11216
  }
10713
11217
 
10714
11218
  // src/tables/format-cells.ts
10715
- import { buildD3Formatter as buildD3Formatter6, formatDate as formatDate2, formatNumber as formatNumber4 } from "@opendata-ai/openchart-core";
11219
+ import { buildD3Formatter as buildD3Formatter7, formatDate as formatDate2, formatNumber as formatNumber5 } from "@opendata-ai/openchart-core";
10716
11220
  function isNumericValue(value2) {
10717
11221
  if (typeof value2 === "number") return Number.isFinite(value2);
10718
11222
  return false;
@@ -10731,7 +11235,7 @@ function formatCell(value2, column) {
10731
11235
  };
10732
11236
  }
10733
11237
  if (column.format && isNumericValue(value2)) {
10734
- const formatter = buildD3Formatter6(column.format);
11238
+ const formatter = buildD3Formatter7(column.format);
10735
11239
  if (formatter) {
10736
11240
  return {
10737
11241
  value: value2,
@@ -10743,7 +11247,7 @@ function formatCell(value2, column) {
10743
11247
  if (isNumericValue(value2)) {
10744
11248
  return {
10745
11249
  value: value2,
10746
- formattedValue: formatNumber4(value2),
11250
+ formattedValue: formatNumber5(value2),
10747
11251
  style
10748
11252
  };
10749
11253
  }
@@ -10763,13 +11267,13 @@ function formatCell(value2, column) {
10763
11267
  function formatValueForSearch(value2, column) {
10764
11268
  if (value2 == null) return "";
10765
11269
  if (column.format && isNumericValue(value2)) {
10766
- const formatter = buildD3Formatter6(column.format);
11270
+ const formatter = buildD3Formatter7(column.format);
10767
11271
  if (formatter) {
10768
11272
  return formatter(value2);
10769
11273
  }
10770
11274
  }
10771
11275
  if (isNumericValue(value2)) {
10772
- return formatNumber4(value2);
11276
+ return formatNumber5(value2);
10773
11277
  }
10774
11278
  return String(value2);
10775
11279
  }
@@ -11010,13 +11514,13 @@ function estimateColumnWidth(col, data, fontSize) {
11010
11514
  if (col.image) return (col.image.width ?? 24) + PADDING;
11011
11515
  if (col.flag) return 60;
11012
11516
  const label = col.label ?? col.key;
11013
- const headerWidth = estimateTextWidth13(label, fontSize, 600) + PADDING;
11517
+ const headerWidth = estimateTextWidth14(label, fontSize, 600) + PADDING;
11014
11518
  const sampleSize = Math.min(100, data.length);
11015
11519
  let maxDataWidth = 0;
11016
11520
  for (let i = 0; i < sampleSize; i++) {
11017
11521
  const val = data[i][col.key];
11018
11522
  const text = val == null ? "" : String(val);
11019
- const width = estimateTextWidth13(text, fontSize, 400) + PADDING;
11523
+ const width = estimateTextWidth14(text, fontSize, 400) + PADDING;
11020
11524
  if (width > maxDataWidth) maxDataWidth = width;
11021
11525
  }
11022
11526
  return Math.max(MIN_WIDTH, headerWidth, maxDataWidth);
@@ -11200,7 +11704,7 @@ function compileTableLayout(spec, options, theme) {
11200
11704
  return { id: rowId, cells, data: row };
11201
11705
  });
11202
11706
  const watermark = spec.watermark;
11203
- const chrome = computeChrome4(
11707
+ const chrome = computeChrome5(
11204
11708
  {
11205
11709
  title: spec.chrome.title,
11206
11710
  subtitle: spec.chrome.subtitle,
@@ -11242,16 +11746,16 @@ function compileTableLayout(spec, options, theme) {
11242
11746
 
11243
11747
  // src/tilemap/compile-tilemap.ts
11244
11748
  import {
11245
- adaptTheme as adaptTheme3,
11246
- buildD3Formatter as buildD3Formatter7,
11247
- computeChrome as computeChrome5,
11248
- estimateTextWidth as estimateTextWidth14,
11249
- formatNumber as formatNumber5,
11250
- resolveTheme as resolveTheme3,
11749
+ adaptTheme as adaptTheme4,
11750
+ buildD3Formatter as buildD3Formatter8,
11751
+ computeChrome as computeChrome6,
11752
+ estimateTextWidth as estimateTextWidth15,
11753
+ formatNumber as formatNumber6,
11754
+ resolveTheme as resolveTheme4,
11251
11755
  SEQUENTIAL_PALETTES
11252
11756
  } from "@opendata-ai/openchart-core";
11253
- var TILE_CORNER_RADIUS = 2;
11254
- var TILE_STROKE_WIDTH = 0;
11757
+ var TILE_CORNER_RADIUS = 6;
11758
+ var TILE_STROKE_WIDTH = 1;
11255
11759
  function compileTileMap(spec, options) {
11256
11760
  const { spec: normalized } = compile(spec);
11257
11761
  if (!("type" in normalized) || normalized.type !== "tilemap") {
@@ -11263,13 +11767,13 @@ function compileTileMap(spec, options) {
11263
11767
  const rawWatermark = spec.watermark;
11264
11768
  const watermark = rawWatermark !== void 0 ? tilemapSpec.watermark : options.watermark ?? true;
11265
11769
  const mergedThemeConfig = options.theme ? { ...tilemapSpec.theme, ...options.theme } : tilemapSpec.theme;
11266
- const lightTheme = resolveTheme3(mergedThemeConfig);
11770
+ const lightTheme = resolveTheme4(mergedThemeConfig);
11267
11771
  let theme = lightTheme;
11268
11772
  if (options.darkMode) {
11269
- theme = adaptTheme3(theme);
11773
+ theme = adaptTheme4(theme);
11270
11774
  }
11271
11775
  const isDarkMode = options.darkMode;
11272
- const chrome = computeChrome5(
11776
+ const chrome = computeChrome6(
11273
11777
  {
11274
11778
  title: tilemapSpec.chrome.title,
11275
11779
  subtitle: tilemapSpec.chrome.subtitle,
@@ -11292,7 +11796,7 @@ function compileTileMap(spec, options) {
11292
11796
  height: options.height - chrome.topHeight - chrome.bottomHeight - padding * 2
11293
11797
  };
11294
11798
  if (fullArea.width <= 0 || fullArea.height <= 0) {
11295
- return emptyLayout2(chrome, theme, options, watermark);
11799
+ return emptyLayout3(chrome, theme, options, watermark);
11296
11800
  }
11297
11801
  const stateField = tilemapSpec.encoding.state.field;
11298
11802
  const valueField = tilemapSpec.encoding.value.field;
@@ -11311,26 +11815,26 @@ function compileTileMap(spec, options) {
11311
11815
  const min4 = values.length > 0 ? Math.min(...values) : 0;
11312
11816
  const max4 = values.length > 0 ? Math.max(...values) : 100;
11313
11817
  const paletteStops = [...SEQUENTIAL_PALETTES[tilemapSpec.palette] ?? SEQUENTIAL_PALETTES.blue];
11314
- if (isDarkMode) paletteStops.reverse();
11315
- const domain = paletteStops.map((_, i) => min4 + i / (paletteStops.length - 1) * (max4 - min4));
11316
- const colorScale = linear2().domain(domain).range(paletteStops).clamp(true);
11818
+ const baseColor = isDarkMode ? paletteStops[0] : paletteStops[paletteStops.length - 1];
11819
+ const opacityRange = isDarkMode ? [0.15, 1] : [0.2, 1];
11820
+ const opacityScale = linear2().domain([min4, max4]).range(opacityRange).clamp(true);
11317
11821
  const showLegend = tilemapSpec.legend?.show !== false;
11318
- const legendBarHeight = 12;
11319
- const legendLabelGap = 4;
11822
+ const legendBarHeight = 6;
11823
+ const legendLabelGap = 6;
11320
11824
  const legendTotalHeight = showLegend ? legendBarHeight + legendLabelGap + 14 : 0;
11321
11825
  const legendGap2 = showLegend ? 8 : 0;
11322
11826
  const tileAreaHeight = fullArea.height - legendTotalHeight - legendGap2;
11323
- const tilePositions = computeTilePositions(fullArea.width, tileAreaHeight, 4);
11827
+ const tilePositions = computeTilePositions(fullArea.width, tileAreaHeight, 5);
11324
11828
  const tileGridOffsetX = fullArea.x + (fullArea.width - tilePositions.gridWidth) / 2;
11325
11829
  const tileGridOffsetY = fullArea.y;
11326
11830
  const legendX = tileGridOffsetX;
11327
11831
  const legendY = tileGridOffsetY + tilePositions.gridHeight + legendGap2;
11328
11832
  const legendWidth = tilePositions.gridWidth;
11329
- const formatter = buildD3Formatter7(tilemapSpec.valueFormat) ?? formatNumber5;
11833
+ const formatter = buildD3Formatter8(tilemapSpec.valueFormat) ?? formatNumber6;
11330
11834
  const neutralFillLight = "#e0e0e0";
11331
- const neutralFillDark = "#2a2a3e";
11835
+ const neutralFillDark = "#1e2a30";
11332
11836
  const neutralStrokeLight = "#d0d0d0";
11333
- const neutralStrokeDark = "#3a3a50";
11837
+ const neutralStrokeDark = "rgba(255,255,255,0.08)";
11334
11838
  const neutralFill = isDarkMode ? neutralFillDark : neutralFillLight;
11335
11839
  const neutralStroke = isDarkMode ? neutralStrokeDark : neutralStrokeLight;
11336
11840
  const tiles = [];
@@ -11339,26 +11843,31 @@ function compileTileMap(spec, options) {
11339
11843
  if (!pos) continue;
11340
11844
  const hasData = stateValueMap.has(stateCode);
11341
11845
  const value2 = hasData ? stateValueMap.get(stateCode) : null;
11342
- const fill = hasData ? colorScale(value2) : neutralFill;
11846
+ const opacity = hasData ? opacityScale(value2) : 0;
11847
+ const fill = hasData ? baseColor : neutralFill;
11848
+ const stroke = hasData ? isDarkMode ? "rgba(255,255,255,0.1)" : "rgba(0,0,0,0.1)" : neutralStroke;
11343
11849
  const formattedValue = hasData ? formatter(value2) : "\u2013";
11344
11850
  const labelStyle = {
11345
11851
  fontFamily: theme.fonts.family,
11346
- fontSize: tilePositions.tileSize > 24 ? 14 : 11,
11852
+ fontSize: tilePositions.tileSize > 24 ? 10 : 7,
11347
11853
  fontWeight: 700,
11348
11854
  fill: "#ffffff",
11349
11855
  lineHeight: 1.2
11350
11856
  };
11351
11857
  const valueLabelStyle = {
11352
11858
  fontFamily: theme.fonts.family,
11353
- fontSize: tilePositions.tileSize > 24 ? 12 : 10,
11354
- fontWeight: 400,
11355
- fill: "#ffffff",
11859
+ fontSize: tilePositions.tileSize > 24 ? 10 : 7,
11860
+ fontWeight: 300,
11861
+ fill: "rgba(255,255,255,0.6)",
11356
11862
  lineHeight: 1.2
11357
11863
  };
11358
- const valueLabel = tilePositions.tileSize < 24 ? { text: "", x: 0, y: 0, style: valueLabelStyle, visible: false } : {
11864
+ const tileCenterX = tileGridOffsetX + pos.x + tilePositions.tileSize / 2;
11865
+ const tileTopY = tileGridOffsetY + pos.y;
11866
+ const sz = tilePositions.tileSize;
11867
+ const valueLabel = sz < 24 ? { text: "", x: 0, y: 0, style: valueLabelStyle, visible: false } : {
11359
11868
  text: formattedValue,
11360
- x: tileGridOffsetX + pos.x + tilePositions.tileSize / 2,
11361
- y: tileGridOffsetY + pos.y + tilePositions.tileSize / 2 + 8,
11869
+ x: tileCenterX,
11870
+ y: tileTopY + sz * 0.78,
11362
11871
  style: valueLabelStyle,
11363
11872
  visible: true
11364
11873
  };
@@ -11366,10 +11875,11 @@ function compileTileMap(spec, options) {
11366
11875
  type: "tile",
11367
11876
  stateCode,
11368
11877
  x: tileGridOffsetX + pos.x,
11369
- y: tileGridOffsetY + pos.y,
11370
- size: tilePositions.tileSize,
11878
+ y: tileTopY,
11879
+ size: sz,
11371
11880
  fill,
11372
- stroke: neutralStroke,
11881
+ fillOpacity: hasData ? opacity : 1,
11882
+ stroke,
11373
11883
  strokeWidth: TILE_STROKE_WIDTH,
11374
11884
  cornerRadius: TILE_CORNER_RADIUS,
11375
11885
  value: value2 ?? null,
@@ -11377,8 +11887,8 @@ function compileTileMap(spec, options) {
11377
11887
  hasData,
11378
11888
  label: {
11379
11889
  text: stateCode,
11380
- x: tileGridOffsetX + pos.x + tilePositions.tileSize / 2,
11381
- y: tileGridOffsetY + pos.y + tilePositions.tileSize / 2 - 4,
11890
+ x: tileCenterX,
11891
+ y: tileTopY + sz * 0.28,
11382
11892
  style: labelStyle,
11383
11893
  visible: true
11384
11894
  },
@@ -11404,10 +11914,12 @@ function compileTileMap(spec, options) {
11404
11914
  }
11405
11915
  let gradientLegend = null;
11406
11916
  if (showLegend) {
11407
- const gradientColorStops = paletteStops.map((color2, i) => ({
11408
- offset: i / (paletteStops.length - 1),
11409
- color: color2
11410
- }));
11917
+ const numStops = 5;
11918
+ const gradientColorStops = Array.from({ length: numStops }, (_, i) => {
11919
+ const t = i / (numStops - 1);
11920
+ const o = opacityRange[0] + t * (opacityRange[1] - opacityRange[0]);
11921
+ return { offset: t, color: baseColor, opacity: o };
11922
+ });
11411
11923
  gradientLegend = {
11412
11924
  type: "gradient",
11413
11925
  position: "bottom",
@@ -11456,10 +11968,10 @@ function compileTileMap(spec, options) {
11456
11968
  height: options.height,
11457
11969
  animation: resolvedAnimation,
11458
11970
  watermark,
11459
- measureText: options.measureText ?? ((text, fontSize) => ({ width: estimateTextWidth14(text, fontSize), height: fontSize }))
11971
+ measureText: options.measureText ?? ((text, fontSize) => ({ width: estimateTextWidth15(text, fontSize), height: fontSize }))
11460
11972
  };
11461
11973
  }
11462
- function emptyLayout2(chrome, theme, options, watermark) {
11974
+ function emptyLayout3(chrome, theme, options, watermark) {
11463
11975
  return {
11464
11976
  area: { x: 0, y: 0, width: 0, height: 0 },
11465
11977
  chrome,
@@ -11491,7 +12003,7 @@ function emptyLayout2(chrome, theme, options, watermark) {
11491
12003
  height: options.height,
11492
12004
  watermark,
11493
12005
  animation: void 0,
11494
- measureText: options.measureText ?? ((text, fontSize) => ({ width: estimateTextWidth14(text, fontSize), height: fontSize }))
12006
+ measureText: options.measureText ?? ((text, fontSize) => ({ width: estimateTextWidth15(text, fontSize), height: fontSize }))
11495
12007
  };
11496
12008
  }
11497
12009
 
@@ -11499,7 +12011,7 @@ function emptyLayout2(chrome, theme, options, watermark) {
11499
12011
  import {
11500
12012
  buildTemporalFormatter as buildTemporalFormatter2,
11501
12013
  formatDate as formatDate3,
11502
- formatNumber as formatNumber6,
12014
+ formatNumber as formatNumber7,
11503
12015
  getRepresentativeColor as getRepresentativeColor10
11504
12016
  } from "@opendata-ai/openchart-core";
11505
12017
  function formatValue(value2, fieldType, format2) {
@@ -11514,18 +12026,20 @@ function formatValue(value2, fieldType, format2) {
11514
12026
  try {
11515
12027
  return format(format2)(value2);
11516
12028
  } catch {
11517
- return formatNumber6(value2);
12029
+ return formatNumber7(value2);
11518
12030
  }
11519
12031
  }
11520
- return formatNumber6(value2);
12032
+ return formatNumber7(value2);
11521
12033
  }
11522
12034
  return String(value2);
11523
12035
  }
11524
12036
  function resolveLabel(ch) {
11525
- return ch.title ?? ch.axis?.title ?? ch.field;
12037
+ const ax = ch.axis || void 0;
12038
+ return ch.title ?? ax?.title ?? ch.field;
11526
12039
  }
11527
12040
  function resolveFormat(ch) {
11528
- return ch.format ?? ch.axis?.format;
12041
+ const ax = ch.axis || void 0;
12042
+ return ch.format ?? ax?.format;
11529
12043
  }
11530
12044
  function buildExplicitTooltipFields(row, channels) {
11531
12045
  return channels.map((ch) => ({
@@ -12382,9 +12896,9 @@ function compileChart(spec, options) {
12382
12896
  chartSpec = { ...chartSpec, watermark: false };
12383
12897
  }
12384
12898
  const mergedThemeConfig = options.theme ? { ...chartSpec.theme, ...options.theme } : chartSpec.theme;
12385
- let theme = resolveTheme4(mergedThemeConfig);
12899
+ let theme = resolveTheme5(mergedThemeConfig);
12386
12900
  if (options.darkMode) {
12387
- theme = adaptTheme4(theme);
12901
+ theme = adaptTheme5(theme);
12388
12902
  }
12389
12903
  const preliminaryArea = {
12390
12904
  x: 0,
@@ -12570,7 +13084,7 @@ function estimateYAxisLabelWidth(data, encoding, baseFontSize) {
12570
13084
  let maxWidth = 0;
12571
13085
  for (const row of data) {
12572
13086
  const label = String(row[yField] ?? "");
12573
- const w = estimateTextWidth15(label, baseFontSize, 400);
13087
+ const w = estimateTextWidth16(label, baseFontSize, 400);
12574
13088
  if (w > maxWidth) maxWidth = w;
12575
13089
  }
12576
13090
  return maxWidth > 0 ? maxWidth + 10 : 40;
@@ -12599,7 +13113,7 @@ function estimateYAxisLabelWidth(data, encoding, baseFontSize) {
12599
13113
  }
12600
13114
  const hasNeg = data.some((r) => Number(r[yField]) < 0);
12601
13115
  const labelEst = (hasNeg ? "-" : "") + sampleLabel;
12602
- return estimateTextWidth15(labelEst, baseFontSize, 400) + 10;
13116
+ return estimateTextWidth16(labelEst, baseFontSize, 400) + 10;
12603
13117
  }
12604
13118
  function compileLayerIndependent(leaves, layerSpec, options) {
12605
13119
  if (leaves.length > 2) {
@@ -12616,10 +13130,11 @@ function compileLayerIndependent(leaves, layerSpec, options) {
12616
13130
  `Dual-axis charts require matching x-field types across layers. Layer 0 has '${xType0}', layer 1 has '${xType1}'.`
12617
13131
  );
12618
13132
  }
12619
- const theme = resolveTheme4(layerSpec.theme ?? leaf1.theme);
13133
+ const theme = resolveTheme5(layerSpec.theme ?? leaf1.theme);
12620
13134
  const axisFontSize = theme.fonts?.sizes?.axisTick ?? 11;
12621
13135
  const rightAxisWidth = estimateYAxisLabelWidth(leaf1.data, leaf1.encoding, axisFontSize);
12622
- const hasRightAxisTitle = !!leaf1.encoding?.y?.axis?.title;
13136
+ const yAxisConfig = leaf1.encoding?.y?.axis || void 0;
13137
+ const hasRightAxisTitle = !!yAxisConfig?.title;
12623
13138
  const tickExtent = TICK_LABEL_OFFSET + rightAxisWidth;
12624
13139
  const bodyFontSize = theme.fonts?.sizes?.body ?? 13;
12625
13140
  const axisTitleOffset = getAxisTitleOffset2(options.width);
@@ -12881,9 +13396,9 @@ function compileTable(spec, options) {
12881
13396
  }
12882
13397
  const tableSpec = normalized;
12883
13398
  const mergedThemeConfig = options.theme ? { ...tableSpec.theme, ...options.theme } : tableSpec.theme;
12884
- let theme = resolveTheme4(mergedThemeConfig);
13399
+ let theme = resolveTheme5(mergedThemeConfig);
12885
13400
  if (options.darkMode) {
12886
- theme = adaptTheme4(theme);
13401
+ theme = adaptTheme5(theme);
12887
13402
  }
12888
13403
  const rawWatermark = spec.watermark;
12889
13404
  const watermark = rawWatermark !== void 0 ? tableSpec.watermark : options.watermark ?? true;
@@ -12898,10 +13413,14 @@ function compileSankey2(spec, options) {
12898
13413
  function compileTileMap2(spec, options) {
12899
13414
  return compileTileMap(spec, options);
12900
13415
  }
13416
+ function compileBarList2(spec, options) {
13417
+ return compileBarList(spec, options);
13418
+ }
12901
13419
  export {
12902
13420
  clampStaggerDelay,
12903
13421
  clearRenderers,
12904
13422
  compile,
13423
+ compileBarList2 as compileBarList,
12905
13424
  compileChart,
12906
13425
  compileGraph2 as compileGraph,
12907
13426
  compileLayer,