@opendata-ai/openchart-engine 6.10.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) {
@@ -933,9 +933,12 @@ function computeSimpleBars(data, valueField, categoryField, xScale, yScale, band
933
933
  if (bandY === void 0) continue;
934
934
  let color2;
935
935
  if (conditionalColor) {
936
- color2 = String(
937
- resolveConditionalValue(row, conditionalColor) ?? getColor(scales, "__default__")
938
- );
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
+ }
939
942
  } else if (sequentialColor) {
940
943
  color2 = getSequentialColor(scales, value2);
941
944
  } else {
@@ -966,6 +969,7 @@ function computeSimpleBars(data, valueField, categoryField, xScale, yScale, band
966
969
  import {
967
970
  buildD3Formatter,
968
971
  estimateTextWidth as estimateTextWidth2,
972
+ getRepresentativeColor,
969
973
  resolveCollisions
970
974
  } from "@opendata-ai/openchart-core";
971
975
  var SUFFIX_MULTIPLIERS = {
@@ -1024,7 +1028,7 @@ function computeBarLabels(marks, _chartArea, density = "auto", labelFormat, labe
1024
1028
  textAnchor = "end";
1025
1029
  } else {
1026
1030
  anchorX = mark.x + mark.width + LABEL_PADDING;
1027
- fill = mark.fill;
1031
+ fill = getRepresentativeColor(mark.fill);
1028
1032
  textAnchor = "start";
1029
1033
  }
1030
1034
  const anchorY = mark.y + mark.height / 2;
@@ -1103,7 +1107,7 @@ var barRenderer = (spec, scales, chartArea, strategy, _theme) => {
1103
1107
  };
1104
1108
 
1105
1109
  // src/charts/column/compute.ts
1106
- 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";
1107
1111
  var MIN_COLUMN_HEIGHT = 1;
1108
1112
  function formatColumnValue(value2) {
1109
1113
  if (Math.abs(value2) >= 1e3) return abbreviateNumber2(value2);
@@ -1192,9 +1196,12 @@ function computeSimpleColumns(data, categoryField, valueField, xScale, yScale, b
1192
1196
  if (bandX === void 0) continue;
1193
1197
  let color2;
1194
1198
  if (conditionalColor) {
1195
- color2 = String(
1196
- resolveConditionalValue(row, conditionalColor) ?? getColor(scales, "__default__")
1197
- );
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
+ }
1198
1205
  } else if (sequentialColor) {
1199
1206
  color2 = getSequentialColor(scales, value2);
1200
1207
  } else {
@@ -1342,6 +1349,7 @@ function computeStackedColumns(data, categoryField, valueField, colorField, xSca
1342
1349
  import {
1343
1350
  buildD3Formatter as buildD3Formatter2,
1344
1351
  estimateTextWidth as estimateTextWidth3,
1352
+ getRepresentativeColor as getRepresentativeColor2,
1345
1353
  resolveCollisions as resolveCollisions2
1346
1354
  } from "@opendata-ai/openchart-core";
1347
1355
  var LABEL_FONT_SIZE2 = 10;
@@ -1380,7 +1388,7 @@ function computeColumnLabels(marks, _chartArea, density = "auto", labelFormat, l
1380
1388
  fontFamily: "system-ui, -apple-system, sans-serif",
1381
1389
  fontSize: LABEL_FONT_SIZE2,
1382
1390
  fontWeight: LABEL_FONT_WEIGHT2,
1383
- fill: mark.fill,
1391
+ fill: getRepresentativeColor2(mark.fill),
1384
1392
  lineHeight: 1.2,
1385
1393
  textAnchor: "middle",
1386
1394
  dominantBaseline: isNegative ? "hanging" : "auto"
@@ -1565,7 +1573,11 @@ function computeLollipopMarks(data, valueField, categoryField, xScale, yScale, b
1565
1573
  }
1566
1574
 
1567
1575
  // src/charts/dot/labels.ts
1568
- 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";
1569
1581
  var LABEL_FONT_SIZE3 = 11;
1570
1582
  var LABEL_FONT_WEIGHT3 = 600;
1571
1583
  var LABEL_OFFSET_X = 10;
@@ -1592,7 +1604,7 @@ function computeDotLabels(marks, _chartArea, density = "auto", labelPrefix) {
1592
1604
  fontFamily: "system-ui, -apple-system, sans-serif",
1593
1605
  fontSize: LABEL_FONT_SIZE3,
1594
1606
  fontWeight: LABEL_FONT_WEIGHT3,
1595
- fill: mark.fill,
1607
+ fill: getRepresentativeColor3(mark.fill),
1596
1608
  lineHeight: 1.2,
1597
1609
  textAnchor: "start",
1598
1610
  dominantBaseline: "central"
@@ -1627,6 +1639,9 @@ var dotRenderer = (spec, scales, chartArea, strategy, _theme) => {
1627
1639
  return marks;
1628
1640
  };
1629
1641
 
1642
+ // src/charts/line/area.ts
1643
+ import { getRepresentativeColor as getRepresentativeColor4 } from "@opendata-ai/openchart-core";
1644
+
1630
1645
  // ../../node_modules/.bun/d3-shape@3.2.0/node_modules/d3-shape/src/constant.js
1631
1646
  function constant_default(x2) {
1632
1647
  return function constant2() {
@@ -2581,7 +2596,7 @@ function computeSingleArea(spec, scales, _chartArea) {
2581
2596
  topPath: topPathStr,
2582
2597
  fill: color2,
2583
2598
  fillOpacity: DEFAULT_FILL_OPACITY,
2584
- stroke: color2,
2599
+ stroke: getRepresentativeColor4(color2),
2585
2600
  strokeWidth: 2,
2586
2601
  seriesKey: seriesKey === "__default__" ? void 0 : seriesKey,
2587
2602
  data: validPoints.map((p) => p.row),
@@ -2668,7 +2683,7 @@ function computeStackedArea(spec, scales, chartArea) {
2668
2683
  fill: color2,
2669
2684
  fillOpacity: 0.7,
2670
2685
  // Higher opacity for stacked so layers are visible
2671
- stroke: color2,
2686
+ stroke: getRepresentativeColor4(color2),
2672
2687
  strokeWidth: 1,
2673
2688
  seriesKey,
2674
2689
  data: layer.map((d) => {
@@ -2695,6 +2710,7 @@ function computeAreaMarks(spec, scales, chartArea) {
2695
2710
  }
2696
2711
 
2697
2712
  // src/charts/line/compute.ts
2713
+ import { getRepresentativeColor as getRepresentativeColor5 } from "@opendata-ai/openchart-core";
2698
2714
  var DEFAULT_STROKE_WIDTH = 2.5;
2699
2715
  var DEFAULT_POINT_RADIUS = 3;
2700
2716
  function computeLineMarks(spec, scales, _chartArea, _strategy) {
@@ -2712,6 +2728,7 @@ function computeLineMarks(spec, scales, _chartArea, _strategy) {
2712
2728
  const marks = [];
2713
2729
  for (const [seriesKey, rows] of groups) {
2714
2730
  const color2 = isSequentialColor ? getSequentialColor(scales, _getMidValue(rows, sequentialColorField)) : getColor(scales, seriesKey);
2731
+ const strokeColor = getRepresentativeColor5(color2);
2715
2732
  const sortedRows = sortByField(rows, xChannel.field);
2716
2733
  const pointsWithData = [];
2717
2734
  const segments = [];
@@ -2760,7 +2777,7 @@ function computeLineMarks(spec, scales, _chartArea, _strategy) {
2760
2777
  type: "line",
2761
2778
  points: allPoints,
2762
2779
  path: combinedPath,
2763
- stroke: color2,
2780
+ stroke: strokeColor,
2764
2781
  strokeWidth: styleOverride?.strokeWidth ?? DEFAULT_STROKE_WIDTH,
2765
2782
  strokeDasharray,
2766
2783
  opacity: styleOverride?.opacity,
@@ -2905,6 +2922,7 @@ var areaRenderer = (spec, scales, chartArea, strategy, _theme) => {
2905
2922
  };
2906
2923
 
2907
2924
  // src/charts/pie/compute.ts
2925
+ import { isConditionalDef, isGradientDef as isGradientDef3 } from "@opendata-ai/openchart-core";
2908
2926
  var SMALL_SLICE_THRESHOLD = 0.03;
2909
2927
  var DEFAULT_PALETTE = [
2910
2928
  "#1b7fa3",
@@ -2943,6 +2961,7 @@ function computePieMarks(spec, scales, chartArea, _strategy, isDonut = false) {
2943
2961
  const encoding = spec.encoding;
2944
2962
  const valueChannel = encoding.y ?? encoding.x;
2945
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;
2946
2965
  if (!valueChannel) return [];
2947
2966
  let slices = [];
2948
2967
  if (categoryField) {
@@ -2993,7 +3012,20 @@ function computePieMarks(spec, scales, chartArea, _strategy, isDonut = false) {
2993
3012
  const arcDatum = arcs[i];
2994
3013
  const slice2 = arcDatum.data;
2995
3014
  let color2;
2996
- 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) {
2997
3029
  const colorScale = scales.color.scale;
2998
3030
  color2 = colorScale(slice2.label);
2999
3031
  } else {
@@ -3131,6 +3163,7 @@ function clearRenderers() {
3131
3163
  }
3132
3164
 
3133
3165
  // src/charts/rule/index.ts
3166
+ import { getRepresentativeColor as getRepresentativeColor6 } from "@opendata-ai/openchart-core";
3134
3167
  function computeRuleMarks(spec, scales, chartArea) {
3135
3168
  const encoding = spec.encoding;
3136
3169
  const xChannel = encoding.x;
@@ -3173,7 +3206,9 @@ function computeRuleMarks(spec, scales, chartArea) {
3173
3206
  const y2Val = scaleValue(scales.y.scale, scales.y.type, row[y2Channel.field]);
3174
3207
  if (y2Val != null) y2 = y2Val;
3175
3208
  }
3176
- 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
+ );
3177
3212
  const strokeDashEncoding = encoding.strokeDash && "field" in encoding.strokeDash ? encoding.strokeDash : void 0;
3178
3213
  const strokeDasharray = strokeDashEncoding ? String(row[strokeDashEncoding.field] ?? "") : void 0;
3179
3214
  const aria = {
@@ -5980,6 +6015,7 @@ var scatterRenderer = (spec, scales, chartArea, strategy, _theme) => {
5980
6015
  };
5981
6016
 
5982
6017
  // src/charts/text/index.ts
6018
+ import { getRepresentativeColor as getRepresentativeColor7 } from "@opendata-ai/openchart-core";
5983
6019
  function computeTextMarks(spec, scales) {
5984
6020
  const encoding = spec.encoding;
5985
6021
  const xChannel = encoding.x;
@@ -6005,7 +6041,9 @@ function computeTextMarks(spec, scales) {
6005
6041
  }
6006
6042
  const text = String(row[textChannel.field] ?? "");
6007
6043
  if (!text) continue;
6008
- 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
+ );
6009
6047
  const fontSize = sizeEncoding ? Math.max(8, Math.min(48, Number(row[sizeEncoding.field]) || 12)) : 12;
6010
6048
  const aria = {
6011
6049
  label: text
@@ -6030,6 +6068,7 @@ var textRenderer = (spec, scales, _chartArea, _strategy, _theme) => {
6030
6068
  };
6031
6069
 
6032
6070
  // src/charts/tick/index.ts
6071
+ import { getRepresentativeColor as getRepresentativeColor8 } from "@opendata-ai/openchart-core";
6033
6072
  var DEFAULT_TICK_LENGTH = 18;
6034
6073
  function computeTickMarks(spec, scales, _chartArea) {
6035
6074
  const encoding = spec.encoding;
@@ -6045,7 +6084,9 @@ function computeTickMarks(spec, scales, _chartArea) {
6045
6084
  const xVal = scaleValue(scales.x.scale, scales.x.type, row[xChannel.field]);
6046
6085
  const yVal = scaleValue(scales.y.scale, scales.y.type, row[yChannel.field]);
6047
6086
  if (xVal == null || yVal == null) continue;
6048
- 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
+ );
6049
6090
  const aria = {
6050
6091
  label: `${row[xChannel.field]}, ${row[yChannel.field]}`
6051
6092
  };
@@ -10082,7 +10123,12 @@ function compileTableLayout(spec, options, theme) {
10082
10123
  }
10083
10124
 
10084
10125
  // src/tooltips/compute.ts
10085
- 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";
10086
10132
  function formatValue(value2, fieldType, format2) {
10087
10133
  if (value2 == null) return "";
10088
10134
  if (fieldType === "temporal" || value2 instanceof Date) {
@@ -10166,12 +10212,12 @@ function tooltipsForLine(mark, encoding, _markIndex) {
10166
10212
  }
10167
10213
  function tooltipsForPoint(mark, encoding, markIndex) {
10168
10214
  const title = getTooltipTitle(mark.data, encoding);
10169
- const fields = buildFields(mark.data, encoding, mark.fill);
10215
+ const fields = buildFields(mark.data, encoding, getRepresentativeColor9(mark.fill));
10170
10216
  return [[`point-${markIndex}`, { title, fields }]];
10171
10217
  }
10172
10218
  function tooltipsForRect(mark, encoding, markIndex) {
10173
10219
  const title = getTooltipTitle(mark.data, encoding);
10174
- const fields = buildFields(mark.data, encoding, mark.fill);
10220
+ const fields = buildFields(mark.data, encoding, getRepresentativeColor9(mark.fill));
10175
10221
  return [[`rect-${markIndex}`, { title, fields }]];
10176
10222
  }
10177
10223
  function tooltipsForArc(mark, encoding, markIndex) {
@@ -10184,14 +10230,14 @@ function tooltipsForArc(mark, encoding, markIndex) {
10184
10230
  fields.push({
10185
10231
  label: categoryName,
10186
10232
  value: formatValue(row[encoding.y.field], encoding.y.type, encoding.y.axis?.format),
10187
- color: mark.fill
10233
+ color: getRepresentativeColor9(mark.fill)
10188
10234
  });
10189
10235
  }
10190
10236
  } else if (encoding.y) {
10191
10237
  fields.push({
10192
10238
  label: encoding.y.field,
10193
10239
  value: formatValue(row[encoding.y.field], encoding.y.type, encoding.y.axis?.format),
10194
- color: mark.fill
10240
+ color: getRepresentativeColor9(mark.fill)
10195
10241
  });
10196
10242
  }
10197
10243
  const title = colorEnc ? String(row[colorEnc.field] ?? "") : void 0;
@@ -10202,7 +10248,7 @@ function tooltipsForArea(mark, encoding, _markIndex) {
10202
10248
  for (const dp of mark.dataPoints) {
10203
10249
  dp.tooltip = {
10204
10250
  title: getTooltipTitle(dp.datum, encoding),
10205
- fields: buildFields(dp.datum, encoding, mark.fill)
10251
+ fields: buildFields(dp.datum, encoding, getRepresentativeColor9(mark.fill))
10206
10252
  };
10207
10253
  }
10208
10254
  }
@@ -10630,7 +10676,7 @@ function compileChart(spec, options) {
10630
10676
  );
10631
10677
  }
10632
10678
  }
10633
- scales.defaultColor = theme.colors.categorical[0];
10679
+ scales.defaultColor = chartSpec.markDef.fill ?? theme.colors.categorical[0];
10634
10680
  const isRadial = chartSpec.markType === "arc";
10635
10681
  const axes = isRadial ? { x: void 0, y: void 0 } : computeAxes(scales, chartArea, strategy, theme, options.measureText);
10636
10682
  if (!isRadial) {