@opendata-ai/openchart-engine 6.9.0 → 6.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -354,14 +354,6 @@ declare function validateSpec(spec: unknown): ValidationResult;
354
354
  */
355
355
  declare function compile(spec: unknown): CompileResult;
356
356
 
357
- /**
358
- * Scale computation from encoding spec + data.
359
- *
360
- * Creates D3 scales that map data values to pixel positions.
361
- * Temporal -> scaleTime(), quantitative -> scaleLinear(),
362
- * nominal/ordinal -> scaleBand() or scaleOrdinal(), depending on context.
363
- */
364
-
365
357
  /** Continuous D3 scales (linear, time, log, pow, sqrt, symlog) that support .ticks() and .nice(). */
366
358
  type D3ContinuousScale = ScaleLinear<number, number> | ScaleTime<number, number> | ScaleLogarithmic<number, number> | ScalePower<number, number> | ScaleSymLog<number, number>;
367
359
  /** Discretizing D3 scales (quantile, quantize, threshold). */
@@ -392,8 +384,8 @@ interface ResolvedScales {
392
384
  y?: ResolvedScale;
393
385
  color?: ResolvedScale;
394
386
  size?: ResolvedScale;
395
- /** Default color for single-series charts (first categorical palette color). */
396
- defaultColor?: string;
387
+ /** Default color for single-series charts (first categorical palette color or markDef.fill gradient). */
388
+ defaultColor?: string | _opendata_ai_openchart_core.GradientDef;
397
389
  }
398
390
 
399
391
  /**
package/dist/index.js CHANGED
@@ -590,7 +590,7 @@ function computeAnnotations(spec, scales, chartArea, strategy, isDark = false, o
590
590
  }
591
591
 
592
592
  // src/charts/bar/compute.ts
593
- import { abbreviateNumber, formatNumber } from "@opendata-ai/openchart-core";
593
+ import { abbreviateNumber, formatNumber, isGradientDef } from "@opendata-ai/openchart-core";
594
594
 
595
595
  // src/transforms/predicates.ts
596
596
  function isFieldPredicate(pred) {
@@ -774,6 +774,20 @@ function computeBarMarks(spec, scales, _chartArea, _strategy) {
774
774
  const categoryGroups = groupByField(spec.data, yChannel.field);
775
775
  const needsStacking = Array.from(categoryGroups.values()).some((rows) => rows.length > 1);
776
776
  if (needsStacking) {
777
+ const stackDisabled = xChannel.stack === null || xChannel.stack === false;
778
+ if (stackDisabled) {
779
+ return computeGroupedBars(
780
+ spec.data,
781
+ xChannel.field,
782
+ yChannel.field,
783
+ colorField,
784
+ xScale,
785
+ yScale,
786
+ bandwidth,
787
+ baseline,
788
+ scales
789
+ );
790
+ }
777
791
  return computeStackedBars(
778
792
  spec.data,
779
793
  xChannel.field,
@@ -834,6 +848,51 @@ function computeStackedBars(data, valueField, categoryField, colorField, xScale,
834
848
  }
835
849
  return marks;
836
850
  }
851
+ function computeGroupedBars(data, valueField, categoryField, colorField, xScale, yScale, bandwidth, baseline, scales) {
852
+ const marks = [];
853
+ const categoryGroups = groupByField(data, categoryField);
854
+ const groupIndexMap = /* @__PURE__ */ new Map();
855
+ for (const row of data) {
856
+ const key = String(row[colorField] ?? "");
857
+ if (!groupIndexMap.has(key)) {
858
+ groupIndexMap.set(key, groupIndexMap.size);
859
+ }
860
+ }
861
+ const groupCount = groupIndexMap.size;
862
+ if (groupCount === 0) return marks;
863
+ const gap = Math.min(1, bandwidth * 0.05);
864
+ const subBandHeight = Math.max((bandwidth - gap * (groupCount - 1)) / groupCount, MIN_BAR_WIDTH);
865
+ for (const [category, rows] of categoryGroups) {
866
+ const bandY = yScale(category);
867
+ if (bandY === void 0) continue;
868
+ for (const row of rows) {
869
+ const groupKey = String(row[colorField] ?? "");
870
+ const value2 = Number(row[valueField] ?? 0);
871
+ if (!Number.isFinite(value2)) continue;
872
+ const groupIndex = groupIndexMap.get(groupKey) ?? 0;
873
+ const color2 = getColor(scales, groupKey);
874
+ const xPos = value2 >= 0 ? baseline : xScale(value2);
875
+ const barWidth = Math.max(Math.abs(xScale(value2) - baseline), MIN_BAR_WIDTH);
876
+ const subY = bandY + groupIndex * (subBandHeight + gap);
877
+ const aria = {
878
+ label: `${category}, ${groupKey}: ${formatBarValue(value2)}`
879
+ };
880
+ marks.push({
881
+ type: "rect",
882
+ x: xPos,
883
+ y: subY,
884
+ width: barWidth,
885
+ height: subBandHeight,
886
+ fill: color2,
887
+ cornerRadius: 2,
888
+ data: row,
889
+ aria,
890
+ orient: "horizontal"
891
+ });
892
+ }
893
+ }
894
+ return marks;
895
+ }
837
896
  function computeColoredBars(data, valueField, categoryField, colorField, xScale, yScale, bandwidth, baseline, scales) {
838
897
  const marks = [];
839
898
  for (const row of data) {
@@ -874,9 +933,12 @@ function computeSimpleBars(data, valueField, categoryField, xScale, yScale, band
874
933
  if (bandY === void 0) continue;
875
934
  let color2;
876
935
  if (conditionalColor) {
877
- color2 = String(
878
- resolveConditionalValue(row, conditionalColor) ?? getColor(scales, "__default__")
879
- );
936
+ const resolved = resolveConditionalValue(row, conditionalColor);
937
+ if (resolved != null) {
938
+ color2 = isGradientDef(resolved) ? resolved : String(resolved);
939
+ } else {
940
+ color2 = getColor(scales, "__default__");
941
+ }
880
942
  } else if (sequentialColor) {
881
943
  color2 = getSequentialColor(scales, value2);
882
944
  } else {
@@ -907,6 +969,7 @@ function computeSimpleBars(data, valueField, categoryField, xScale, yScale, band
907
969
  import {
908
970
  buildD3Formatter,
909
971
  estimateTextWidth as estimateTextWidth2,
972
+ getRepresentativeColor,
910
973
  resolveCollisions
911
974
  } from "@opendata-ai/openchart-core";
912
975
  var SUFFIX_MULTIPLIERS = {
@@ -965,7 +1028,7 @@ function computeBarLabels(marks, _chartArea, density = "auto", labelFormat, labe
965
1028
  textAnchor = "end";
966
1029
  } else {
967
1030
  anchorX = mark.x + mark.width + LABEL_PADDING;
968
- fill = mark.fill;
1031
+ fill = getRepresentativeColor(mark.fill);
969
1032
  textAnchor = "start";
970
1033
  }
971
1034
  const anchorY = mark.y + mark.height / 2;
@@ -1044,7 +1107,7 @@ var barRenderer = (spec, scales, chartArea, strategy, _theme) => {
1044
1107
  };
1045
1108
 
1046
1109
  // src/charts/column/compute.ts
1047
- import { abbreviateNumber as abbreviateNumber2, formatNumber as formatNumber2 } from "@opendata-ai/openchart-core";
1110
+ import { abbreviateNumber as abbreviateNumber2, formatNumber as formatNumber2, isGradientDef as isGradientDef2 } from "@opendata-ai/openchart-core";
1048
1111
  var MIN_COLUMN_HEIGHT = 1;
1049
1112
  function formatColumnValue(value2) {
1050
1113
  if (Math.abs(value2) >= 1e3) return abbreviateNumber2(value2);
@@ -1072,6 +1135,20 @@ function computeColumnMarks(spec, scales, _chartArea, _strategy) {
1072
1135
  const categoryGroups = groupByField(spec.data, xChannel.field);
1073
1136
  const needsStacking = Array.from(categoryGroups.values()).some((rows) => rows.length > 1);
1074
1137
  if (needsStacking) {
1138
+ const stackDisabled = yChannel.stack === null || yChannel.stack === false;
1139
+ if (stackDisabled) {
1140
+ return computeGroupedColumns(
1141
+ spec.data,
1142
+ xChannel.field,
1143
+ yChannel.field,
1144
+ colorField,
1145
+ xScale,
1146
+ yScale,
1147
+ bandwidth,
1148
+ baseline,
1149
+ scales
1150
+ );
1151
+ }
1075
1152
  return computeStackedColumns(
1076
1153
  spec.data,
1077
1154
  xChannel.field,
@@ -1119,9 +1196,12 @@ function computeSimpleColumns(data, categoryField, valueField, xScale, yScale, b
1119
1196
  if (bandX === void 0) continue;
1120
1197
  let color2;
1121
1198
  if (conditionalColor) {
1122
- color2 = String(
1123
- resolveConditionalValue(row, conditionalColor) ?? getColor(scales, "__default__")
1124
- );
1199
+ const resolved = resolveConditionalValue(row, conditionalColor);
1200
+ if (resolved != null) {
1201
+ color2 = isGradientDef2(resolved) ? resolved : String(resolved);
1202
+ } else {
1203
+ color2 = getColor(scales, "__default__");
1204
+ }
1125
1205
  } else if (sequentialColor) {
1126
1206
  color2 = getSequentialColor(scales, value2);
1127
1207
  } else {
@@ -1179,6 +1259,55 @@ function computeColoredColumns(data, categoryField, valueField, colorField, xSca
1179
1259
  }
1180
1260
  return marks;
1181
1261
  }
1262
+ function computeGroupedColumns(data, categoryField, valueField, colorField, xScale, yScale, bandwidth, baseline, scales) {
1263
+ const marks = [];
1264
+ const categoryGroups = groupByField(data, categoryField);
1265
+ const groupIndexMap = /* @__PURE__ */ new Map();
1266
+ for (const row of data) {
1267
+ const key = String(row[colorField] ?? "");
1268
+ if (!groupIndexMap.has(key)) {
1269
+ groupIndexMap.set(key, groupIndexMap.size);
1270
+ }
1271
+ }
1272
+ const groupCount = groupIndexMap.size;
1273
+ if (groupCount === 0) return marks;
1274
+ const gap = Math.min(1, bandwidth * 0.05);
1275
+ const subBandWidth = Math.max(
1276
+ (bandwidth - gap * (groupCount - 1)) / groupCount,
1277
+ MIN_COLUMN_HEIGHT
1278
+ );
1279
+ for (const [category, rows] of categoryGroups) {
1280
+ const bandX = xScale(category);
1281
+ if (bandX === void 0) continue;
1282
+ for (const row of rows) {
1283
+ const groupKey = String(row[colorField] ?? "");
1284
+ const value2 = Number(row[valueField] ?? 0);
1285
+ if (!Number.isFinite(value2)) continue;
1286
+ const groupIndex = groupIndexMap.get(groupKey) ?? 0;
1287
+ const color2 = getColor(scales, groupKey);
1288
+ const yPos = yScale(value2);
1289
+ const columnHeight = Math.max(Math.abs(baseline - yPos), MIN_COLUMN_HEIGHT);
1290
+ const y2 = value2 >= 0 ? yPos : baseline;
1291
+ const subX = bandX + groupIndex * (subBandWidth + gap);
1292
+ const aria = {
1293
+ label: `${category}, ${groupKey}: ${formatColumnValue(value2)}`
1294
+ };
1295
+ marks.push({
1296
+ type: "rect",
1297
+ x: subX,
1298
+ y: y2,
1299
+ width: subBandWidth,
1300
+ height: columnHeight,
1301
+ fill: color2,
1302
+ cornerRadius: 2,
1303
+ data: row,
1304
+ aria,
1305
+ orient: "vertical"
1306
+ });
1307
+ }
1308
+ }
1309
+ return marks;
1310
+ }
1182
1311
  function computeStackedColumns(data, categoryField, valueField, colorField, xScale, yScale, bandwidth, _baseline, scales) {
1183
1312
  const marks = [];
1184
1313
  const categoryGroups = groupByField(data, categoryField);
@@ -1220,6 +1349,7 @@ function computeStackedColumns(data, categoryField, valueField, colorField, xSca
1220
1349
  import {
1221
1350
  buildD3Formatter as buildD3Formatter2,
1222
1351
  estimateTextWidth as estimateTextWidth3,
1352
+ getRepresentativeColor as getRepresentativeColor2,
1223
1353
  resolveCollisions as resolveCollisions2
1224
1354
  } from "@opendata-ai/openchart-core";
1225
1355
  var LABEL_FONT_SIZE2 = 10;
@@ -1258,7 +1388,7 @@ function computeColumnLabels(marks, _chartArea, density = "auto", labelFormat, l
1258
1388
  fontFamily: "system-ui, -apple-system, sans-serif",
1259
1389
  fontSize: LABEL_FONT_SIZE2,
1260
1390
  fontWeight: LABEL_FONT_WEIGHT2,
1261
- fill: mark.fill,
1391
+ fill: getRepresentativeColor2(mark.fill),
1262
1392
  lineHeight: 1.2,
1263
1393
  textAnchor: "middle",
1264
1394
  dominantBaseline: isNegative ? "hanging" : "auto"
@@ -1443,7 +1573,11 @@ function computeLollipopMarks(data, valueField, categoryField, xScale, yScale, b
1443
1573
  }
1444
1574
 
1445
1575
  // src/charts/dot/labels.ts
1446
- import { estimateTextWidth as estimateTextWidth4, resolveCollisions as resolveCollisions3 } from "@opendata-ai/openchart-core";
1576
+ import {
1577
+ estimateTextWidth as estimateTextWidth4,
1578
+ getRepresentativeColor as getRepresentativeColor3,
1579
+ resolveCollisions as resolveCollisions3
1580
+ } from "@opendata-ai/openchart-core";
1447
1581
  var LABEL_FONT_SIZE3 = 11;
1448
1582
  var LABEL_FONT_WEIGHT3 = 600;
1449
1583
  var LABEL_OFFSET_X = 10;
@@ -1470,7 +1604,7 @@ function computeDotLabels(marks, _chartArea, density = "auto", labelPrefix) {
1470
1604
  fontFamily: "system-ui, -apple-system, sans-serif",
1471
1605
  fontSize: LABEL_FONT_SIZE3,
1472
1606
  fontWeight: LABEL_FONT_WEIGHT3,
1473
- fill: mark.fill,
1607
+ fill: getRepresentativeColor3(mark.fill),
1474
1608
  lineHeight: 1.2,
1475
1609
  textAnchor: "start",
1476
1610
  dominantBaseline: "central"
@@ -1505,6 +1639,9 @@ var dotRenderer = (spec, scales, chartArea, strategy, _theme) => {
1505
1639
  return marks;
1506
1640
  };
1507
1641
 
1642
+ // src/charts/line/area.ts
1643
+ import { getRepresentativeColor as getRepresentativeColor4 } from "@opendata-ai/openchart-core";
1644
+
1508
1645
  // ../../node_modules/.bun/d3-shape@3.2.0/node_modules/d3-shape/src/constant.js
1509
1646
  function constant_default(x2) {
1510
1647
  return function constant2() {
@@ -2459,7 +2596,7 @@ function computeSingleArea(spec, scales, _chartArea) {
2459
2596
  topPath: topPathStr,
2460
2597
  fill: color2,
2461
2598
  fillOpacity: DEFAULT_FILL_OPACITY,
2462
- stroke: color2,
2599
+ stroke: getRepresentativeColor4(color2),
2463
2600
  strokeWidth: 2,
2464
2601
  seriesKey: seriesKey === "__default__" ? void 0 : seriesKey,
2465
2602
  data: validPoints.map((p) => p.row),
@@ -2546,7 +2683,7 @@ function computeStackedArea(spec, scales, chartArea) {
2546
2683
  fill: color2,
2547
2684
  fillOpacity: 0.7,
2548
2685
  // Higher opacity for stacked so layers are visible
2549
- stroke: color2,
2686
+ stroke: getRepresentativeColor4(color2),
2550
2687
  strokeWidth: 1,
2551
2688
  seriesKey,
2552
2689
  data: layer.map((d) => {
@@ -2573,6 +2710,7 @@ function computeAreaMarks(spec, scales, chartArea) {
2573
2710
  }
2574
2711
 
2575
2712
  // src/charts/line/compute.ts
2713
+ import { getRepresentativeColor as getRepresentativeColor5 } from "@opendata-ai/openchart-core";
2576
2714
  var DEFAULT_STROKE_WIDTH = 2.5;
2577
2715
  var DEFAULT_POINT_RADIUS = 3;
2578
2716
  function computeLineMarks(spec, scales, _chartArea, _strategy) {
@@ -2590,6 +2728,7 @@ function computeLineMarks(spec, scales, _chartArea, _strategy) {
2590
2728
  const marks = [];
2591
2729
  for (const [seriesKey, rows] of groups) {
2592
2730
  const color2 = isSequentialColor ? getSequentialColor(scales, _getMidValue(rows, sequentialColorField)) : getColor(scales, seriesKey);
2731
+ const strokeColor = getRepresentativeColor5(color2);
2593
2732
  const sortedRows = sortByField(rows, xChannel.field);
2594
2733
  const pointsWithData = [];
2595
2734
  const segments = [];
@@ -2638,7 +2777,7 @@ function computeLineMarks(spec, scales, _chartArea, _strategy) {
2638
2777
  type: "line",
2639
2778
  points: allPoints,
2640
2779
  path: combinedPath,
2641
- stroke: color2,
2780
+ stroke: strokeColor,
2642
2781
  strokeWidth: styleOverride?.strokeWidth ?? DEFAULT_STROKE_WIDTH,
2643
2782
  strokeDasharray,
2644
2783
  opacity: styleOverride?.opacity,
@@ -2783,6 +2922,7 @@ var areaRenderer = (spec, scales, chartArea, strategy, _theme) => {
2783
2922
  };
2784
2923
 
2785
2924
  // src/charts/pie/compute.ts
2925
+ import { isConditionalDef, isGradientDef as isGradientDef3 } from "@opendata-ai/openchart-core";
2786
2926
  var SMALL_SLICE_THRESHOLD = 0.03;
2787
2927
  var DEFAULT_PALETTE = [
2788
2928
  "#1b7fa3",
@@ -2821,6 +2961,7 @@ function computePieMarks(spec, scales, chartArea, _strategy, isDonut = false) {
2821
2961
  const encoding = spec.encoding;
2822
2962
  const valueChannel = encoding.y ?? encoding.x;
2823
2963
  const categoryField = encoding.color && "field" in encoding.color ? encoding.color.field : void 0;
2964
+ const conditionalColor = encoding.color && isConditionalDef(encoding.color) ? encoding.color : void 0;
2824
2965
  if (!valueChannel) return [];
2825
2966
  let slices = [];
2826
2967
  if (categoryField) {
@@ -2871,7 +3012,20 @@ function computePieMarks(spec, scales, chartArea, _strategy, isDonut = false) {
2871
3012
  const arcDatum = arcs[i];
2872
3013
  const slice2 = arcDatum.data;
2873
3014
  let color2;
2874
- if (scales.color && categoryField) {
3015
+ if (conditionalColor) {
3016
+ const resolved = resolveConditionalValue(
3017
+ slice2.originalRow,
3018
+ conditionalColor
3019
+ );
3020
+ if (resolved != null) {
3021
+ color2 = isGradientDef3(resolved) ? resolved : String(resolved);
3022
+ } else if (scales.color && categoryField) {
3023
+ const colorScale = scales.color.scale;
3024
+ color2 = colorScale(slice2.label);
3025
+ } else {
3026
+ color2 = DEFAULT_PALETTE[i % DEFAULT_PALETTE.length];
3027
+ }
3028
+ } else if (scales.color && categoryField) {
2875
3029
  const colorScale = scales.color.scale;
2876
3030
  color2 = colorScale(slice2.label);
2877
3031
  } else {
@@ -3009,6 +3163,7 @@ function clearRenderers() {
3009
3163
  }
3010
3164
 
3011
3165
  // src/charts/rule/index.ts
3166
+ import { getRepresentativeColor as getRepresentativeColor6 } from "@opendata-ai/openchart-core";
3012
3167
  function computeRuleMarks(spec, scales, chartArea) {
3013
3168
  const encoding = spec.encoding;
3014
3169
  const xChannel = encoding.x;
@@ -3051,7 +3206,9 @@ function computeRuleMarks(spec, scales, chartArea) {
3051
3206
  const y2Val = scaleValue(scales.y.scale, scales.y.type, row[y2Channel.field]);
3052
3207
  if (y2Val != null) y2 = y2Val;
3053
3208
  }
3054
- const color2 = colorField ? getColor(scales, String(row[colorField] ?? "__default__")) : getColor(scales, "__default__");
3209
+ const color2 = getRepresentativeColor6(
3210
+ colorField ? getColor(scales, String(row[colorField] ?? "__default__")) : getColor(scales, "__default__")
3211
+ );
3055
3212
  const strokeDashEncoding = encoding.strokeDash && "field" in encoding.strokeDash ? encoding.strokeDash : void 0;
3056
3213
  const strokeDasharray = strokeDashEncoding ? String(row[strokeDashEncoding.field] ?? "") : void 0;
3057
3214
  const aria = {
@@ -5858,6 +6015,7 @@ var scatterRenderer = (spec, scales, chartArea, strategy, _theme) => {
5858
6015
  };
5859
6016
 
5860
6017
  // src/charts/text/index.ts
6018
+ import { getRepresentativeColor as getRepresentativeColor7 } from "@opendata-ai/openchart-core";
5861
6019
  function computeTextMarks(spec, scales) {
5862
6020
  const encoding = spec.encoding;
5863
6021
  const xChannel = encoding.x;
@@ -5883,7 +6041,9 @@ function computeTextMarks(spec, scales) {
5883
6041
  }
5884
6042
  const text = String(row[textChannel.field] ?? "");
5885
6043
  if (!text) continue;
5886
- const color2 = colorField ? getColor(scales, String(row[colorField] ?? "__default__")) : getColor(scales, "__default__");
6044
+ const color2 = getRepresentativeColor7(
6045
+ colorField ? getColor(scales, String(row[colorField] ?? "__default__")) : getColor(scales, "__default__")
6046
+ );
5887
6047
  const fontSize = sizeEncoding ? Math.max(8, Math.min(48, Number(row[sizeEncoding.field]) || 12)) : 12;
5888
6048
  const aria = {
5889
6049
  label: text
@@ -5908,6 +6068,7 @@ var textRenderer = (spec, scales, _chartArea, _strategy, _theme) => {
5908
6068
  };
5909
6069
 
5910
6070
  // src/charts/tick/index.ts
6071
+ import { getRepresentativeColor as getRepresentativeColor8 } from "@opendata-ai/openchart-core";
5911
6072
  var DEFAULT_TICK_LENGTH = 18;
5912
6073
  function computeTickMarks(spec, scales, _chartArea) {
5913
6074
  const encoding = spec.encoding;
@@ -5923,7 +6084,9 @@ function computeTickMarks(spec, scales, _chartArea) {
5923
6084
  const xVal = scaleValue(scales.x.scale, scales.x.type, row[xChannel.field]);
5924
6085
  const yVal = scaleValue(scales.y.scale, scales.y.type, row[yChannel.field]);
5925
6086
  if (xVal == null || yVal == null) continue;
5926
- const color2 = colorField ? getColor(scales, String(row[colorField] ?? "__default__")) : getColor(scales, "__default__");
6087
+ const color2 = getRepresentativeColor8(
6088
+ colorField ? getColor(scales, String(row[colorField] ?? "__default__")) : getColor(scales, "__default__")
6089
+ );
5927
6090
  const aria = {
5928
6091
  label: `${row[xChannel.field]}, ${row[yChannel.field]}`
5929
6092
  };
@@ -8130,7 +8293,8 @@ function computeScales(spec, chartArea, data) {
8130
8293
  }
8131
8294
  if (encoding.x) {
8132
8295
  let xData = data;
8133
- if (spec.markType === "bar" && encoding.color && encoding.x.type === "quantitative") {
8296
+ const xStackDisabled = encoding.x.stack === null || encoding.x.stack === false;
8297
+ if (spec.markType === "bar" && encoding.color && encoding.x.type === "quantitative" && !xStackDisabled) {
8134
8298
  const yField = encoding.y?.field;
8135
8299
  const xField = encoding.x.field;
8136
8300
  if (yField) {
@@ -8158,7 +8322,8 @@ function computeScales(spec, chartArea, data) {
8158
8322
  if (encoding.y) {
8159
8323
  let yData = data;
8160
8324
  const isVerticalBar = spec.markType === "bar" && (encoding.x?.type === "nominal" || encoding.x?.type === "ordinal") && encoding.y.type === "quantitative";
8161
- if ((isVerticalBar || spec.markType === "area") && encoding.color && encoding.y.type === "quantitative") {
8325
+ const yStackDisabled = encoding.y.stack === null || encoding.y.stack === false;
8326
+ if ((isVerticalBar || spec.markType === "area") && encoding.color && encoding.y.type === "quantitative" && !yStackDisabled) {
8162
8327
  const xField = encoding.x?.field;
8163
8328
  const yField = encoding.y.field;
8164
8329
  if (xField) {
@@ -9958,7 +10123,12 @@ function compileTableLayout(spec, options, theme) {
9958
10123
  }
9959
10124
 
9960
10125
  // src/tooltips/compute.ts
9961
- import { buildTemporalFormatter as buildTemporalFormatter2, formatDate as formatDate3, formatNumber as formatNumber6 } from "@opendata-ai/openchart-core";
10126
+ import {
10127
+ buildTemporalFormatter as buildTemporalFormatter2,
10128
+ formatDate as formatDate3,
10129
+ formatNumber as formatNumber6,
10130
+ getRepresentativeColor as getRepresentativeColor9
10131
+ } from "@opendata-ai/openchart-core";
9962
10132
  function formatValue(value2, fieldType, format2) {
9963
10133
  if (value2 == null) return "";
9964
10134
  if (fieldType === "temporal" || value2 instanceof Date) {
@@ -10042,12 +10212,12 @@ function tooltipsForLine(mark, encoding, _markIndex) {
10042
10212
  }
10043
10213
  function tooltipsForPoint(mark, encoding, markIndex) {
10044
10214
  const title = getTooltipTitle(mark.data, encoding);
10045
- const fields = buildFields(mark.data, encoding, mark.fill);
10215
+ const fields = buildFields(mark.data, encoding, getRepresentativeColor9(mark.fill));
10046
10216
  return [[`point-${markIndex}`, { title, fields }]];
10047
10217
  }
10048
10218
  function tooltipsForRect(mark, encoding, markIndex) {
10049
10219
  const title = getTooltipTitle(mark.data, encoding);
10050
- const fields = buildFields(mark.data, encoding, mark.fill);
10220
+ const fields = buildFields(mark.data, encoding, getRepresentativeColor9(mark.fill));
10051
10221
  return [[`rect-${markIndex}`, { title, fields }]];
10052
10222
  }
10053
10223
  function tooltipsForArc(mark, encoding, markIndex) {
@@ -10060,14 +10230,14 @@ function tooltipsForArc(mark, encoding, markIndex) {
10060
10230
  fields.push({
10061
10231
  label: categoryName,
10062
10232
  value: formatValue(row[encoding.y.field], encoding.y.type, encoding.y.axis?.format),
10063
- color: mark.fill
10233
+ color: getRepresentativeColor9(mark.fill)
10064
10234
  });
10065
10235
  }
10066
10236
  } else if (encoding.y) {
10067
10237
  fields.push({
10068
10238
  label: encoding.y.field,
10069
10239
  value: formatValue(row[encoding.y.field], encoding.y.type, encoding.y.axis?.format),
10070
- color: mark.fill
10240
+ color: getRepresentativeColor9(mark.fill)
10071
10241
  });
10072
10242
  }
10073
10243
  const title = colorEnc ? String(row[colorEnc.field] ?? "") : void 0;
@@ -10078,7 +10248,7 @@ function tooltipsForArea(mark, encoding, _markIndex) {
10078
10248
  for (const dp of mark.dataPoints) {
10079
10249
  dp.tooltip = {
10080
10250
  title: getTooltipTitle(dp.datum, encoding),
10081
- fields: buildFields(dp.datum, encoding, mark.fill)
10251
+ fields: buildFields(dp.datum, encoding, getRepresentativeColor9(mark.fill))
10082
10252
  };
10083
10253
  }
10084
10254
  }
@@ -10506,7 +10676,7 @@ function compileChart(spec, options) {
10506
10676
  );
10507
10677
  }
10508
10678
  }
10509
- scales.defaultColor = theme.colors.categorical[0];
10679
+ scales.defaultColor = chartSpec.markDef.fill ?? theme.colors.categorical[0];
10510
10680
  const isRadial = chartSpec.markType === "arc";
10511
10681
  const axes = isRadial ? { x: void 0, y: void 0 } : computeAxes(scales, chartArea, strategy, theme, options.measureText);
10512
10682
  if (!isRadial) {