@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.
@@ -7,6 +7,7 @@
7
7
  */
8
8
 
9
9
  import type { Encoding, Mark, MarkAria, TextMarkLayout } from '@opendata-ai/openchart-core';
10
+ import { getRepresentativeColor } from '@opendata-ai/openchart-core';
10
11
 
11
12
  import type { NormalizedChartSpec } from '../../compiler/types';
12
13
  import type { ResolvedScales } from '../../layout/scales';
@@ -52,9 +53,11 @@ export function computeTextMarks(
52
53
  const text = String(row[textChannel.field] ?? '');
53
54
  if (!text) continue;
54
55
 
55
- const color = colorField
56
- ? getColor(scales, String(row[colorField] ?? '__default__'))
57
- : getColor(scales, '__default__');
56
+ const color = getRepresentativeColor(
57
+ colorField
58
+ ? getColor(scales, String(row[colorField] ?? '__default__'))
59
+ : getColor(scales, '__default__'),
60
+ );
58
61
 
59
62
  const fontSize = sizeEncoding
60
63
  ? Math.max(8, Math.min(48, Number(row[sizeEncoding.field]) || 12))
@@ -7,6 +7,7 @@
7
7
  */
8
8
 
9
9
  import type { Encoding, Mark, MarkAria, Rect, TickMarkLayout } from '@opendata-ai/openchart-core';
10
+ import { getRepresentativeColor } from '@opendata-ai/openchart-core';
10
11
 
11
12
  import type { NormalizedChartSpec } from '../../compiler/types';
12
13
  import type { ResolvedScales } from '../../layout/scales';
@@ -48,9 +49,11 @@ export function computeTickMarks(
48
49
  const yVal = scaleValue(scales.y.scale, scales.y.type, row[yChannel.field]);
49
50
  if (xVal == null || yVal == null) continue;
50
51
 
51
- const color = colorField
52
- ? getColor(scales, String(row[colorField] ?? '__default__'))
53
- : getColor(scales, '__default__');
52
+ const color = getRepresentativeColor(
53
+ colorField
54
+ ? getColor(scales, String(row[colorField] ?? '__default__'))
55
+ : getColor(scales, '__default__'),
56
+ );
54
57
 
55
58
  const aria: MarkAria = {
56
59
  label: `${row[xChannel.field]}, ${row[yChannel.field]}`,
@@ -5,7 +5,7 @@
5
5
  * data grouping, color lookup, and shared constants.
6
6
  */
7
7
 
8
- import type { DataRow } from '@opendata-ai/openchart-core';
8
+ import type { DataRow, GradientDef } from '@opendata-ai/openchart-core';
9
9
  import type { ScaleBand, ScaleLinear, ScalePoint, ScaleTime } from 'd3-scale';
10
10
  import type { D3Scale, ResolvedScales } from '../layout/scales';
11
11
 
@@ -143,7 +143,7 @@ export function getColor(
143
143
  key: string,
144
144
  _index?: number,
145
145
  fallback: string = DEFAULT_COLOR,
146
- ): string {
146
+ ): string | GradientDef {
147
147
  if (scales.color && key !== '__default__') {
148
148
  const colorScale = scales.color.scale as (v: string) => string;
149
149
  return colorScale(key);
@@ -159,7 +159,7 @@ export function getSequentialColor(
159
159
  scales: ResolvedScales,
160
160
  value: number,
161
161
  fallback: string = DEFAULT_COLOR,
162
- ): string {
162
+ ): string | GradientDef {
163
163
  if (scales.color?.type === 'sequential') {
164
164
  const colorScale = scales.color.scale as unknown as (v: number) => string;
165
165
  return colorScale(value);
package/src/compile.ts CHANGED
@@ -386,8 +386,9 @@ export function compileChart(spec: unknown, options: CompileOptions): ChartLayou
386
386
  }
387
387
  }
388
388
 
389
- // Set default color for single-series charts (no color encoding)
390
- scales.defaultColor = theme.colors.categorical[0];
389
+ // Set default color for single-series charts. If the user set a fill on the mark def
390
+ // (string or gradient), that takes priority over the theme's first categorical color.
391
+ scales.defaultColor = chartSpec.markDef.fill ?? theme.colors.categorical[0];
391
392
 
392
393
  // Arc charts (pie/donut) don't use axes or gridlines
393
394
  const isRadial = chartSpec.markType === 'arc';
@@ -99,8 +99,8 @@ export interface ResolvedScales {
99
99
  y?: ResolvedScale;
100
100
  color?: ResolvedScale;
101
101
  size?: ResolvedScale;
102
- /** Default color for single-series charts (first categorical palette color). */
103
- defaultColor?: string;
102
+ /** Default color for single-series charts (first categorical palette color or markDef.fill gradient). */
103
+ defaultColor?: string | import('@opendata-ai/openchart-core').GradientDef;
104
104
  }
105
105
 
106
106
  // ---------------------------------------------------------------------------
@@ -622,7 +622,13 @@ export function computeScales(
622
622
  // For stacked bars, the x-domain needs the max category sum, not max individual value.
623
623
  // Without this, stacked bars would clip past the chart area.
624
624
  let xData = data;
625
- if (spec.markType === 'bar' && encoding.color && encoding.x.type === 'quantitative') {
625
+ const xStackDisabled = encoding.x.stack === null || encoding.x.stack === false;
626
+ if (
627
+ spec.markType === 'bar' &&
628
+ encoding.color &&
629
+ encoding.x.type === 'quantitative' &&
630
+ !xStackDisabled
631
+ ) {
626
632
  const yField = encoding.y?.field;
627
633
  const xField = encoding.x.field;
628
634
  if (yField) {
@@ -660,10 +666,12 @@ export function computeScales(
660
666
  spec.markType === 'bar' &&
661
667
  (encoding.x?.type === 'nominal' || encoding.x?.type === 'ordinal') &&
662
668
  encoding.y.type === 'quantitative';
669
+ const yStackDisabled = encoding.y.stack === null || encoding.y.stack === false;
663
670
  if (
664
671
  (isVerticalBar || spec.markType === 'area') &&
665
672
  encoding.color &&
666
- encoding.y.type === 'quantitative'
673
+ encoding.y.type === 'quantitative' &&
674
+ !yStackDisabled
667
675
  ) {
668
676
  const xField = encoding.x?.field;
669
677
  const yField = encoding.y.field;
@@ -19,7 +19,12 @@ import type {
19
19
  TooltipContent,
20
20
  TooltipField,
21
21
  } from '@opendata-ai/openchart-core';
22
- import { buildTemporalFormatter, formatDate, formatNumber } from '@opendata-ai/openchart-core';
22
+ import {
23
+ buildTemporalFormatter,
24
+ formatDate,
25
+ formatNumber,
26
+ getRepresentativeColor,
27
+ } from '@opendata-ai/openchart-core';
23
28
  import { format as d3Format } from 'd3-format';
24
29
 
25
30
  import type { NormalizedChartSpec } from '../compiler/types';
@@ -155,7 +160,7 @@ function tooltipsForPoint(
155
160
  markIndex: number,
156
161
  ): Array<[string, TooltipContent]> {
157
162
  const title = getTooltipTitle(mark.data, encoding);
158
- const fields = buildFields(mark.data, encoding, mark.fill);
163
+ const fields = buildFields(mark.data, encoding, getRepresentativeColor(mark.fill));
159
164
 
160
165
  return [[`point-${markIndex}`, { title, fields }]];
161
166
  }
@@ -166,7 +171,7 @@ function tooltipsForRect(
166
171
  markIndex: number,
167
172
  ): Array<[string, TooltipContent]> {
168
173
  const title = getTooltipTitle(mark.data, encoding);
169
- const fields = buildFields(mark.data, encoding, mark.fill);
174
+ const fields = buildFields(mark.data, encoding, getRepresentativeColor(mark.fill));
170
175
 
171
176
  return [[`rect-${markIndex}`, { title, fields }]];
172
177
  }
@@ -187,14 +192,14 @@ function tooltipsForArc(
187
192
  fields.push({
188
193
  label: categoryName,
189
194
  value: formatValue(row[encoding.y.field], encoding.y.type, encoding.y.axis?.format),
190
- color: mark.fill,
195
+ color: getRepresentativeColor(mark.fill),
191
196
  });
192
197
  }
193
198
  } else if (encoding.y) {
194
199
  fields.push({
195
200
  label: encoding.y.field,
196
201
  value: formatValue(row[encoding.y.field], encoding.y.type, encoding.y.axis?.format),
197
- color: mark.fill,
202
+ color: getRepresentativeColor(mark.fill),
198
203
  });
199
204
  }
200
205
 
@@ -214,7 +219,7 @@ function tooltipsForArea(
214
219
  for (const dp of mark.dataPoints) {
215
220
  dp.tooltip = {
216
221
  title: getTooltipTitle(dp.datum, encoding),
217
- fields: buildFields(dp.datum, encoding, mark.fill),
222
+ fields: buildFields(dp.datum, encoding, getRepresentativeColor(mark.fill)),
218
223
  };
219
224
  }
220
225
  }