@opendata-ai/openchart-engine 7.7.0 → 7.9.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
@@ -1,5 +1,5 @@
1
1
  import * as _opendata_ai_openchart_core from '@opendata-ai/openchart-core';
2
- import { LegendLayout, ResolvedChrome, TooltipContent, A11yMetadata, ResolvedTheme, CompileOptions, ChartLayout, LayerSpec, CompileTableOptions, TableLayout, AnimationSpec, ResolvedAnimation, DataRow, BarListEncoding, ThemeConfig, DarkMode, TileMapEncoding, TileMapPalette, LegendConfig, MarkType, MarkDef, Encoding, ChromeText, Annotation, LabelConfig, Display, ColumnConfig, GraphSpec, GraphEncoding, GraphLayoutConfig, NodeOverride, SankeyEncoding, SankeyNodeAlign, SankeyLinkColor, ScaleType, EncodingChannel, Rect, LayoutStrategy, VizSpec, Mark, BinTransform, CalculateTransform, ConditionalValueDef, FilterPredicate, TimeUnitTransform, Transform } from '@opendata-ai/openchart-core';
2
+ import { LegendLayout, ResolvedChrome, TooltipContent, A11yMetadata, ResolvedTheme, CompileOptions, ChartLayout, LayerSpec, CompileTableOptions, TableLayout, ResolvedAnimationPhase, AnimationSpec, ResolvedAnimation, Point, DataRow, BarListEncoding, ThemeConfig, DarkMode, TileMapEncoding, TileMapPalette, LegendConfig, MarkType, MarkDef, Encoding, ChromeText, Annotation, LabelConfig, Display, ColumnConfig, GraphSpec, GraphEncoding, GraphLayoutConfig, NodeOverride, SankeyEncoding, SankeyNodeAlign, SankeyLinkColor, ScaleType, EncodingChannel, Rect, LayoutStrategy, VizSpec, Mark, BinTransform, CalculateTransform, ConditionalValueDef, FilterPredicate, TimeUnitTransform, Transform } from '@opendata-ai/openchart-core';
3
3
  export { BarListLayout, BarListSpec, ChartLayout, ChartSpec, CompileOptions, CompileTableOptions, GraphLayout, GraphSpec, LayerSpec, SankeyLayout, SankeySpec, TableLayout, TableSpec, TileMapLayout, TileMapSpec, VizSpec } from '@opendata-ai/openchart-core';
4
4
  import { ScaleLinear, ScaleTime, ScaleLogarithmic, ScalePower, ScaleSymLog, ScaleBand, ScalePoint, ScaleOrdinal, ScaleQuantile, ScaleQuantize, ScaleThreshold } from 'd3-scale';
5
5
 
@@ -189,11 +189,24 @@ declare function compileBarList(spec: unknown, options: CompileOptions): _openda
189
189
  * Animation resolver: normalizes AnimationSpec into fully resolved config.
190
190
  *
191
191
  * Handles the shorthand forms:
192
- * - true -> { enter: true } -> full defaults
193
- * - { enter: { duration: 800 } } -> merge with defaults
192
+ * - true -> all three phases with defaults
193
+ * - { enter: { duration: 800 } } -> merge with defaults per phase
194
194
  * - false/undefined -> undefined (no animation)
195
+ *
196
+ * Each phase resolves independently:
197
+ * - true/config-object -> resolved phase merged onto that phase's defaults
198
+ * - false -> phase absent
199
+ * - undefined -> phase present with defaults (animation: {} behaves like true)
200
+ *
201
+ * Returns undefined only when ALL phases resolve absent.
195
202
  */
196
203
 
204
+ /** Default values for entrance animation. */
205
+ declare const ENTER_DEFAULTS: ResolvedAnimationPhase;
206
+ /** Default values for update (data transition) animation. */
207
+ declare const UPDATE_DEFAULTS: ResolvedAnimationPhase;
208
+ /** Default values for exit animation. */
209
+ declare const EXIT_DEFAULTS: ResolvedAnimationPhase;
197
210
  /**
198
211
  * Resolve an AnimationSpec into a fully resolved config with all defaults filled.
199
212
  * Returns undefined if animation is disabled (false or omitted).
@@ -204,6 +217,52 @@ declare function resolveAnimation(spec: AnimationSpec | undefined): ResolvedAnim
204
217
  */
205
218
  declare function clampStaggerDelay(delay: number, elementCount: number): number;
206
219
 
220
+ /**
221
+ * Mark key utilities for data-update transitions.
222
+ *
223
+ * Keys uniquely identify marks across data updates so the renderer can
224
+ * match old marks to new marks and animate the difference. Each chart
225
+ * type stamps keys during its compute phase; these helpers provide the
226
+ * shared serialization and deduplication logic.
227
+ */
228
+ /**
229
+ * Serialize a datum value into a stable key fragment.
230
+ * - Date -> epoch ms string (avoids locale-dependent Date.toString)
231
+ * - number/string/boolean -> String(v)
232
+ * - null/undefined -> null sentinel
233
+ */
234
+ declare function serializeKeyValue(v: unknown): string;
235
+ /**
236
+ * Append occurrence suffixes to duplicate keys, in order.
237
+ *
238
+ * Only keys that appear more than once get suffixed. Unique keys pass
239
+ * through unchanged so keys remain human-readable when possible.
240
+ * Uses SOH (\x01) as separator to avoid collisions with real data values.
241
+ */
242
+ declare function dedupeKeys(keys: string[]): string[];
243
+
244
+ /**
245
+ * Curve interpolation mapping and path builders.
246
+ *
247
+ * Maps Vega-Lite-style interpolation strings to d3-shape curve factories.
248
+ * Used by both line and area chart computations.
249
+ *
250
+ * Also exports pure path-builder helpers (`buildLinePath`, `buildAreaPath`)
251
+ * so downstream consumers (e.g. transition interpolators) can reconstruct
252
+ * SVG paths from point arrays without pulling in chart-specific code.
253
+ */
254
+
255
+ /**
256
+ * Build an SVG path string from an array of points using d3 line generator.
257
+ * Accepts an optional interpolation string (defaults to 'monotone').
258
+ */
259
+ declare function buildLinePath(points: Point[], interpolate?: string): string;
260
+ /**
261
+ * Build an SVG path string for a filled area from top and bottom point arrays.
262
+ * Accepts an optional interpolation string (defaults to 'monotone').
263
+ */
264
+ declare function buildAreaPath(topPoints: Point[], bottomPoints: Point[], interpolate?: string): string;
265
+
207
266
  /**
208
267
  * Internal normalized barlist spec type used by the compilation pipeline.
209
268
  */
@@ -733,4 +792,4 @@ declare function runTimeUnit(data: DataRow[], transform: TimeUnitTransform): Dat
733
792
  */
734
793
  declare function runTransforms(data: DataRow[], transforms: Transform[]): DataRow[];
735
794
 
736
- export { type AnnotationContext, type AnnotationMeasureTextFn, type ChartRenderer, type CompileResult, type CompiledGraphEdge, type CompiledGraphNode, type GraphCompilation, type NormalizedBarListSpec, type NormalizedChartSpec, type NormalizedChrome, type NormalizedGraphSpec, type NormalizedSankeySpec, type NormalizedSpec, type NormalizedTableSpec, type NormalizedTileMapSpec, type ObstacleKind, type PlacementObstacle, type SimulationConfig, type ValidationError, type ValidationErrorCode, type ValidationResult, clampStaggerDelay, clearRenderers, compile, compileBarList, compileChart, compileGraph, compileLayer, compileSankey, compileTable, compileTileMap, evaluatePredicate, getChartRenderer, heuristicMeasure, isConditionalValueDef, normalizeSpec, registerChartRenderer, resolveAnimation, resolveConditionalValue, runBin, runCalculate, runFilter, runTimeUnit, runTransforms, validateSpec };
795
+ export { type AnnotationContext, type AnnotationMeasureTextFn, type ChartRenderer, type CompileResult, type CompiledGraphEdge, type CompiledGraphNode, ENTER_DEFAULTS, EXIT_DEFAULTS, type GraphCompilation, type NormalizedBarListSpec, type NormalizedChartSpec, type NormalizedChrome, type NormalizedGraphSpec, type NormalizedSankeySpec, type NormalizedSpec, type NormalizedTableSpec, type NormalizedTileMapSpec, type ObstacleKind, type PlacementObstacle, type SimulationConfig, UPDATE_DEFAULTS, type ValidationError, type ValidationErrorCode, type ValidationResult, buildAreaPath, buildLinePath, clampStaggerDelay, clearRenderers, compile, compileBarList, compileChart, compileGraph, compileLayer, compileSankey, compileTable, compileTileMap, dedupeKeys, evaluatePredicate, getChartRenderer, heuristicMeasure, isConditionalValueDef, normalizeSpec, registerChartRenderer, resolveAnimation, resolveConditionalValue, runBin, runCalculate, runFilter, runTimeUnit, runTransforms, serializeKeyValue, validateSpec };
package/dist/index.js CHANGED
@@ -2125,6 +2125,32 @@ function computeEndpointLabels(spec, marks, theme, chartArea, strategy, endpoint
2125
2125
  // src/charts/bar/compute.ts
2126
2126
  import { isGradientDef, NARROW_VIEWPORT_MAX } from "@opendata-ai/openchart-core";
2127
2127
 
2128
+ // src/compiler/keys.ts
2129
+ var NULL_KEY = "\u2205";
2130
+ function serializeKeyValue(v) {
2131
+ if (v === null || v === void 0) return NULL_KEY;
2132
+ if (v instanceof Date) return String(v.getTime());
2133
+ return String(v);
2134
+ }
2135
+ function dedupeKeys(keys) {
2136
+ const counts = /* @__PURE__ */ new Map();
2137
+ for (const k of keys) {
2138
+ counts.set(k, (counts.get(k) ?? 0) + 1);
2139
+ }
2140
+ const seen = /* @__PURE__ */ new Map();
2141
+ const result = [];
2142
+ for (const k of keys) {
2143
+ if (counts.get(k) > 1) {
2144
+ const idx = seen.get(k) ?? 0;
2145
+ result.push(`${k}${idx}`);
2146
+ seen.set(k, idx + 1);
2147
+ } else {
2148
+ result.push(k);
2149
+ }
2150
+ }
2151
+ return result;
2152
+ }
2153
+
2128
2154
  // src/transforms/predicates.ts
2129
2155
  function isFieldPredicate(pred) {
2130
2156
  return "field" in pred;
@@ -2374,8 +2400,21 @@ function computeBarMarks(spec, scales, _chartArea, _strategy, containerWidth) {
2374
2400
  );
2375
2401
  }
2376
2402
  }
2403
+ const categoryField = yChannel.field;
2404
+ stampBarKeys(marks, categoryField, colorField);
2377
2405
  return applyMarkDefOverrides(marks, spec, bandwidth);
2378
2406
  }
2407
+ function stampBarKeys(marks, categoryField, colorField) {
2408
+ const rawKeys = marks.map((m) => {
2409
+ const series = colorField ? String(m.data[colorField] ?? "") : "";
2410
+ const cat = serializeKeyValue(m.data[categoryField]);
2411
+ return `${series}|${cat}`;
2412
+ });
2413
+ const keys = dedupeKeys(rawKeys);
2414
+ for (let i = 0; i < marks.length; i++) {
2415
+ marks[i].key = keys[i];
2416
+ }
2417
+ }
2379
2418
  function computeStackedBars(data, valueField, categoryField, colorField, xScale, yScale, bandwidth, _baseline, scales, stackMode = "zero") {
2380
2419
  const marks = [];
2381
2420
  const categoryGroups = groupByField(data, categoryField);
@@ -2873,8 +2912,21 @@ function computeColumnMarks(spec, scales, _chartArea, _strategy) {
2873
2912
  conditionalColor
2874
2913
  );
2875
2914
  }
2915
+ const categoryField = xChannel.field;
2916
+ stampColumnKeys(marks, categoryField, colorField);
2876
2917
  return applyMarkDefOverrides2(marks, spec, bandwidth);
2877
2918
  }
2919
+ function stampColumnKeys(marks, categoryField, colorField) {
2920
+ const rawKeys = marks.map((m) => {
2921
+ const series = colorField ? String(m.data[colorField] ?? "") : "";
2922
+ const cat = serializeKeyValue(m.data[categoryField]);
2923
+ return `${series}|${cat}`;
2924
+ });
2925
+ const keys = dedupeKeys(rawKeys);
2926
+ for (let i = 0; i < marks.length; i++) {
2927
+ marks[i].key = keys[i];
2928
+ }
2929
+ }
2878
2930
  function computeSimpleColumns(data, categoryField, valueField, xScale, yScale, bandwidth, baseline, scales, sequentialColor = false, conditionalColor) {
2879
2931
  const marks = [];
2880
2932
  for (const row of data) {
@@ -3198,8 +3250,9 @@ function computeDotMarks(spec, scales, _chartArea, _strategy) {
3198
3250
  const colorEnc = encoding.color && "field" in encoding.color ? encoding.color : void 0;
3199
3251
  const isSequentialColor = colorEnc?.type === "quantitative";
3200
3252
  const colorField = isSequentialColor ? void 0 : colorEnc?.field;
3253
+ let marks;
3201
3254
  if (colorField) {
3202
- return computeDumbbellMarks(
3255
+ marks = computeDumbbellMarks(
3203
3256
  spec.data,
3204
3257
  xChannel.field,
3205
3258
  yChannel.field,
@@ -3209,18 +3262,30 @@ function computeDotMarks(spec, scales, _chartArea, _strategy) {
3209
3262
  bandwidth,
3210
3263
  scales
3211
3264
  );
3265
+ } else {
3266
+ marks = computeLollipopMarks(
3267
+ spec.data,
3268
+ xChannel.field,
3269
+ yChannel.field,
3270
+ xScale,
3271
+ yScale,
3272
+ bandwidth,
3273
+ baseline,
3274
+ scales,
3275
+ isSequentialColor
3276
+ );
3212
3277
  }
3213
- return computeLollipopMarks(
3214
- spec.data,
3215
- xChannel.field,
3216
- yChannel.field,
3217
- xScale,
3218
- yScale,
3219
- bandwidth,
3220
- baseline,
3221
- scales,
3222
- isSequentialColor
3223
- );
3278
+ const pointMarks = marks.filter((m) => m.type === "point");
3279
+ const rawKeys = pointMarks.map((m) => {
3280
+ const cat = serializeKeyValue(m.data[yChannel.field]);
3281
+ const series = colorField ? String(m.data[colorField] ?? "") : "";
3282
+ return series ? `${series}|${cat}` : cat;
3283
+ });
3284
+ const keys = dedupeKeys(rawKeys);
3285
+ for (let i = 0; i < pointMarks.length; i++) {
3286
+ pointMarks[i].key = keys[i];
3287
+ }
3288
+ return marks;
3224
3289
  }
3225
3290
  function computeDumbbellMarks(data, valueField, categoryField, colorField, xScale, yScale, bandwidth, scales) {
3226
3291
  const marks = [];
@@ -4335,6 +4400,23 @@ function resolveCurve(interpolate) {
4335
4400
  if (!interpolate) return monotoneX;
4336
4401
  return CURVE_MAP[interpolate] ?? monotoneX;
4337
4402
  }
4403
+ function buildLinePath(points, interpolate) {
4404
+ if (points.length === 0) return "";
4405
+ const curve = resolveCurve(interpolate);
4406
+ const generator = line_default().x((d) => d.x).y((d) => d.y).curve(curve);
4407
+ return generator(points) ?? "";
4408
+ }
4409
+ function buildAreaPath(topPoints, bottomPoints, interpolate) {
4410
+ if (topPoints.length === 0) return "";
4411
+ const curve = resolveCurve(interpolate);
4412
+ const paired = topPoints.map((tp, i) => ({
4413
+ x: tp.x,
4414
+ yTop: tp.y,
4415
+ yBottom: bottomPoints[i]?.y ?? 0
4416
+ }));
4417
+ const generator = area_default().x((d) => d.x).y0((d) => d.yBottom).y1((d) => d.yTop).curve(curve);
4418
+ return generator(paired) ?? "";
4419
+ }
4338
4420
 
4339
4421
  // src/charts/line/area.ts
4340
4422
  var DEFAULT_FILL_OPACITY = 0.15;
@@ -4436,8 +4518,15 @@ function computeSingleArea(spec, scales, _chartArea) {
4436
4518
  fillValue = buildGradientFill(colorStr, defaultGradientStops);
4437
4519
  fillOpacity = 1;
4438
4520
  }
4521
+ const keyField = encoding.key && "field" in encoding.key ? encoding.key.field : void 0;
4522
+ const rawPointKeys = validPoints.map(
4523
+ (p) => keyField ? serializeKeyValue(p.row[keyField]) : serializeKeyValue(p.row[xChannel.field])
4524
+ );
4439
4525
  marks.push({
4440
4526
  type: "area",
4527
+ key: seriesKey === "__default__" ? "area" : seriesKey,
4528
+ pointKeys: dedupeKeys(rawPointKeys),
4529
+ interpolate: spec.markDef.interpolate ?? "monotone",
4441
4530
  topPoints,
4442
4531
  bottomPoints,
4443
4532
  path: pathStr,
@@ -4536,8 +4625,12 @@ function computeStackedArea(spec, scales, chartArea, darkMode) {
4536
4625
  fillValue = buildGradientFill(colorStr, stackedStops);
4537
4626
  fillOpacity = 1;
4538
4627
  }
4628
+ const rawPointKeys = validPoints.map((_p, idx) => serializeKeyValue(layer[idx]?.data.__x__));
4539
4629
  marks.push({
4540
4630
  type: "area",
4631
+ key: seriesKey,
4632
+ pointKeys: dedupeKeys(rawPointKeys),
4633
+ interpolate: spec.markDef.interpolate ?? "monotone",
4541
4634
  topPoints,
4542
4635
  bottomPoints,
4543
4636
  path: pathStr,
@@ -4570,6 +4663,10 @@ function computeAreaMarks(spec, scales, chartArea, darkMode) {
4570
4663
  } else {
4571
4664
  marks = computeSingleArea(spec, scales, chartArea);
4572
4665
  }
4666
+ const areaKeys = dedupeKeys(marks.map((m) => m.key));
4667
+ marks.forEach((m, i) => {
4668
+ m.key = areaKeys[i];
4669
+ });
4573
4670
  const highlight = spec.highlight ?? [];
4574
4671
  if (highlight.length > 0) {
4575
4672
  const highlightSet = new Set(highlight);
@@ -4655,8 +4752,15 @@ function computeLineMarks(spec, scales, _chartArea, _strategy) {
4655
4752
  let strokeDasharray;
4656
4753
  if (styleOverride?.lineStyle === "dashed") strokeDasharray = "6 4";
4657
4754
  else if (styleOverride?.lineStyle === "dotted") strokeDasharray = "2 3";
4755
+ const keyField = encoding.key && "field" in encoding.key ? encoding.key.field : void 0;
4756
+ const rawPointKeys = pointsWithData.map(
4757
+ (p) => keyField ? serializeKeyValue(p.row[keyField]) : serializeKeyValue(p.row[xChannel.field])
4758
+ );
4658
4759
  const lineMark = {
4659
4760
  type: "line",
4761
+ key: seriesStyleKey ?? "series",
4762
+ pointKeys: dedupeKeys(rawPointKeys),
4763
+ interpolate: spec.markDef.interpolate ?? "monotone",
4660
4764
  points: allPoints,
4661
4765
  path: combinedPath,
4662
4766
  stroke: strokeColor,
@@ -4695,9 +4799,11 @@ function computeLineMarks(spec, scales, _chartArea, _strategy) {
4695
4799
  }
4696
4800
  const hollow = isEndpoints && visible;
4697
4801
  const pointColorStr = getRepresentativeColor5(pointColor);
4802
+ const pointKey = keyField ? serializeKeyValue(p.row[keyField]) : seriesStyleKey ? `${seriesStyleKey}|${serializeKeyValue(p.row[xChannel.field])}` : serializeKeyValue(p.row[xChannel.field]);
4698
4803
  if (isSingleEndpoint) {
4699
4804
  bucket.push({
4700
4805
  type: "point",
4806
+ key: pointKey,
4701
4807
  cx: p.x,
4702
4808
  cy: p.y,
4703
4809
  r: 3.5,
@@ -4712,6 +4818,7 @@ function computeLineMarks(spec, scales, _chartArea, _strategy) {
4712
4818
  }
4713
4819
  const pointMark = {
4714
4820
  type: "point",
4821
+ key: pointKey,
4715
4822
  cx: p.x,
4716
4823
  cy: p.y,
4717
4824
  r: visible ? DEFAULT_POINT_RADIUS : 0,
@@ -4728,7 +4835,20 @@ function computeLineMarks(spec, scales, _chartArea, _strategy) {
4728
4835
  }
4729
4836
  }
4730
4837
  }
4731
- return [...mutedMarks, ...highlightedMarks];
4838
+ const allMarks = [...mutedMarks, ...highlightedMarks];
4839
+ const lineMarks = allMarks.filter((m) => m.type === "line");
4840
+ const lineKeys = dedupeKeys(lineMarks.map((m) => m.key));
4841
+ lineMarks.forEach((m, i) => {
4842
+ m.key = lineKeys[i];
4843
+ });
4844
+ const pointMarks = allMarks.filter((m) => m.type === "point");
4845
+ if (pointMarks.length > 0) {
4846
+ const pointKeys = dedupeKeys(pointMarks.map((m) => m.key ?? ""));
4847
+ pointMarks.forEach((m, i) => {
4848
+ m.key = pointKeys[i];
4849
+ });
4850
+ }
4851
+ return allMarks;
4732
4852
  }
4733
4853
  function _getMidValue(rows, field) {
4734
4854
  const values = rows.map((r) => Number(r[field])).filter(Number.isFinite);
@@ -5028,6 +5148,14 @@ function computePieMarks(spec, scales, chartArea, _strategy, isDonut = false) {
5028
5148
  aria
5029
5149
  });
5030
5150
  }
5151
+ const rawKeys = marks.map((m) => {
5152
+ const label = categoryField ? String(m.data[categoryField] ?? "") : String(m.data.label ?? m.data.name ?? m.data.category ?? "");
5153
+ return serializeKeyValue(label);
5154
+ });
5155
+ const keys = dedupeKeys(rawKeys);
5156
+ for (let i = 0; i < marks.length; i++) {
5157
+ marks[i].key = keys[i];
5158
+ }
5031
5159
  return marks;
5032
5160
  }
5033
5161
 
@@ -5202,6 +5330,12 @@ function computeRuleMarks(spec, scales, chartArea) {
5202
5330
  aria
5203
5331
  });
5204
5332
  }
5333
+ const posField = xChannel?.field ?? yChannel?.field;
5334
+ const rawKeys = marks.map((m, i) => posField ? serializeKeyValue(m.data[posField]) : String(i));
5335
+ const keys = dedupeKeys(rawKeys);
5336
+ for (let i = 0; i < marks.length; i++) {
5337
+ marks[i].key = keys[i];
5338
+ }
5205
5339
  return marks;
5206
5340
  }
5207
5341
  var ruleRenderer = (spec, scales, chartArea, _strategy, _theme) => {
@@ -7639,6 +7773,8 @@ function computeScatterMarks(spec, scales, _chartArea, _strategy) {
7639
7773
  const [radiusMin, radiusMax] = explicitRange ?? [MIN_BUBBLE_RADIUS, MAX_BUBBLE_RADIUS];
7640
7774
  sizeScale = sqrt2().domain([sizeMin, sizeMax]).range([radiusMin, radiusMax]).clamp(true);
7641
7775
  }
7776
+ const keyEnc = encoding.key && "field" in encoding.key ? encoding.key : void 0;
7777
+ const keyField = keyEnc?.field;
7642
7778
  const marks = [];
7643
7779
  for (const row of spec.data) {
7644
7780
  const rawX = row[xChannel.field];
@@ -7682,6 +7818,16 @@ function computeScatterMarks(spec, scales, _chartArea, _strategy) {
7682
7818
  aria
7683
7819
  });
7684
7820
  }
7821
+ const rawKeys = marks.map((m) => {
7822
+ if (keyField) return serializeKeyValue(m.data[keyField]);
7823
+ const cat = colorField ? String(m.data[colorField] ?? "") : "";
7824
+ const xVal = serializeKeyValue(m.data[xChannel.field]);
7825
+ return cat ? `${cat}|${xVal}` : xVal;
7826
+ });
7827
+ const keys = dedupeKeys(rawKeys);
7828
+ for (let i = 0; i < marks.length; i++) {
7829
+ marks[i].key = keys[i];
7830
+ }
7685
7831
  return marks;
7686
7832
  }
7687
7833
 
@@ -7799,6 +7945,12 @@ function computeTextMarks(spec, scales) {
7799
7945
  aria
7800
7946
  });
7801
7947
  }
7948
+ const posField = xChannel?.field ?? yChannel?.field;
7949
+ const rawKeys = marks.map((m, i) => posField ? serializeKeyValue(m.data[posField]) : String(i));
7950
+ const keys = dedupeKeys(rawKeys);
7951
+ for (let i = 0; i < marks.length; i++) {
7952
+ marks[i].key = keys[i];
7953
+ }
7802
7954
  return marks;
7803
7955
  }
7804
7956
  var textRenderer = (spec, scales, _chartArea, _strategy, _theme) => {
@@ -7841,6 +7993,12 @@ function computeTickMarks(spec, scales, _chartArea) {
7841
7993
  aria
7842
7994
  });
7843
7995
  }
7996
+ const posField = isHorizontal ? xChannel.field : yChannel.field;
7997
+ const rawKeys = marks.map((m) => serializeKeyValue(m.data[posField]));
7998
+ const keys = dedupeKeys(rawKeys);
7999
+ for (let i = 0; i < marks.length; i++) {
8000
+ marks[i].key = keys[i];
8001
+ }
7844
8002
  return marks;
7845
8003
  }
7846
8004
  var tickRenderer = (spec, scales, chartArea, _strategy, _theme) => {
@@ -7893,69 +8051,70 @@ var ENTER_DEFAULTS = {
7893
8051
  duration: 500,
7894
8052
  ease: "smooth",
7895
8053
  staggerDelay: 80,
7896
- staggerOrder: "index",
7897
- annotationDelay: 200
8054
+ staggerOrder: "index"
7898
8055
  };
8056
+ var UPDATE_DEFAULTS = {
8057
+ duration: 500,
8058
+ ease: "smooth",
8059
+ staggerDelay: 0,
8060
+ staggerOrder: "index"
8061
+ };
8062
+ var EXIT_DEFAULTS = {
8063
+ duration: 300,
8064
+ ease: "smooth",
8065
+ staggerDelay: 0,
8066
+ staggerOrder: "index"
8067
+ };
8068
+ var DEFAULT_ANNOTATION_DELAY = 200;
7899
8069
  var MAX_TOTAL_STAGGER_MS = 2e3;
7900
8070
  function resolveAnimation(spec) {
7901
8071
  if (spec === void 0 || spec === false) return void 0;
7902
8072
  if (spec === true) {
7903
8073
  return {
7904
- enabled: true,
7905
- duration: ENTER_DEFAULTS.duration,
7906
- ease: ENTER_DEFAULTS.ease,
7907
- staggerDelay: ENTER_DEFAULTS.staggerDelay,
7908
- staggerOrder: ENTER_DEFAULTS.staggerOrder,
7909
- annotationDelay: ENTER_DEFAULTS.annotationDelay
8074
+ enter: { ...ENTER_DEFAULTS },
8075
+ update: { ...UPDATE_DEFAULTS },
8076
+ exit: { ...EXIT_DEFAULTS },
8077
+ annotationDelay: DEFAULT_ANNOTATION_DELAY
7910
8078
  };
7911
8079
  }
7912
8080
  const config = spec;
7913
- if (config.enter === false || config.enter === void 0 && !hasAnyPhase(config)) {
7914
- return void 0;
7915
- }
7916
- const enterConfig = resolvePhaseConfig(config.enter);
8081
+ const enter = resolvePhase(config.enter, ENTER_DEFAULTS);
8082
+ const update = resolvePhase(config.update, UPDATE_DEFAULTS);
8083
+ const exit = resolvePhase(config.exit, EXIT_DEFAULTS);
8084
+ if (!enter && !update && !exit) return void 0;
7917
8085
  return {
7918
- enabled: true,
7919
- duration: enterConfig.duration,
7920
- ease: enterConfig.ease,
7921
- staggerDelay: enterConfig.staggerDelay,
7922
- staggerOrder: enterConfig.staggerOrder,
7923
- annotationDelay: config.annotationDelay ?? ENTER_DEFAULTS.annotationDelay
8086
+ enter: enter ?? void 0,
8087
+ update: update ?? void 0,
8088
+ exit: exit ?? void 0,
8089
+ annotationDelay: config.annotationDelay ?? DEFAULT_ANNOTATION_DELAY
7924
8090
  };
7925
8091
  }
7926
8092
  function clampStaggerDelay(delay, elementCount) {
7927
8093
  if (elementCount <= 1) return 0;
7928
8094
  return Math.min(delay, MAX_TOTAL_STAGGER_MS / elementCount);
7929
8095
  }
7930
- function hasAnyPhase(config) {
7931
- return config.enter !== void 0 || config.update !== void 0 || config.exit !== void 0;
7932
- }
7933
- function resolvePhaseConfig(phase) {
8096
+ function resolvePhase(phase, defaults) {
8097
+ if (phase === false) return null;
7934
8098
  if (phase === void 0 || phase === true) {
7935
- return {
7936
- duration: ENTER_DEFAULTS.duration,
7937
- ease: ENTER_DEFAULTS.ease,
7938
- staggerDelay: ENTER_DEFAULTS.staggerDelay,
7939
- staggerOrder: ENTER_DEFAULTS.staggerOrder
7940
- };
8099
+ return { ...defaults };
7941
8100
  }
7942
8101
  const cfg = phase;
7943
- const stagger = resolveStagger(cfg.stagger);
8102
+ const stagger = resolveStagger(cfg.stagger, defaults);
7944
8103
  return {
7945
- duration: cfg.duration ?? ENTER_DEFAULTS.duration,
7946
- ease: cfg.ease ?? ENTER_DEFAULTS.ease,
8104
+ duration: cfg.duration ?? defaults.duration,
8105
+ ease: cfg.ease ?? defaults.ease,
7947
8106
  staggerDelay: stagger.delay,
7948
8107
  staggerOrder: stagger.order
7949
8108
  };
7950
8109
  }
7951
- function resolveStagger(stagger) {
8110
+ function resolveStagger(stagger, defaults) {
7952
8111
  if (stagger === false) return { delay: 0, order: "index" };
7953
8112
  if (stagger === void 0 || stagger === true) {
7954
- return { delay: ENTER_DEFAULTS.staggerDelay, order: ENTER_DEFAULTS.staggerOrder };
8113
+ return { delay: defaults.staggerDelay, order: defaults.staggerOrder };
7955
8114
  }
7956
8115
  return {
7957
- delay: stagger.delay ?? ENTER_DEFAULTS.staggerDelay,
7958
- order: stagger.order ?? ENTER_DEFAULTS.staggerOrder
8116
+ delay: stagger.delay ?? defaults.staggerDelay,
8117
+ order: stagger.order ?? defaults.staggerOrder
7959
8118
  };
7960
8119
  }
7961
8120
 
@@ -8160,7 +8319,7 @@ function inferFieldType(data, field) {
8160
8319
  }
8161
8320
  function inferEncodingTypes(encoding, data, warnings) {
8162
8321
  const result = { ...encoding };
8163
- for (const channel of ["x", "y", "color", "size", "detail"]) {
8322
+ for (const channel of ["x", "y", "color", "size", "detail", "key"]) {
8164
8323
  const spec = result[channel];
8165
8324
  if (!spec) continue;
8166
8325
  if ("condition" in spec) continue;
@@ -9799,8 +9958,8 @@ function getMarkPrimaryValue(mark) {
9799
9958
  }
9800
9959
  }
9801
9960
  function assignAnimationIndices(marks, animation) {
9802
- if (!animation?.enabled) return;
9803
- if (animation.staggerOrder === "value") {
9961
+ if (!animation?.enter) return;
9962
+ if (animation.enter.staggerOrder === "value") {
9804
9963
  const indexed = marks.map((m, i) => ({ mark: m, idx: i }));
9805
9964
  indexed.sort((a, b) => {
9806
9965
  const av = getMarkPrimaryValue(a.mark);
@@ -14782,6 +14941,7 @@ import {
14782
14941
  computeChrome as computeChrome7,
14783
14942
  estimateTextWidth as estimateTextWidth20,
14784
14943
  formatNumber as formatNumber8,
14944
+ pickLabelColor as pickLabelColor2,
14785
14945
  resolveTheme as resolveTheme5,
14786
14946
  SEQUENTIAL_PALETTES
14787
14947
  } from "@opendata-ai/openchart-core";
@@ -14897,7 +15057,8 @@ function compileQuantitative(tilemapSpec, options, theme, chrome, fullArea, isDa
14897
15057
  value: value2,
14898
15058
  formattedValue,
14899
15059
  hasData,
14900
- theme
15060
+ theme,
15061
+ isDarkMode: !!isDarkMode
14901
15062
  })
14902
15063
  );
14903
15064
  }
@@ -15024,6 +15185,7 @@ function compileCategorical(tilemapSpec, options, theme, chrome, fullArea, isDar
15024
15185
  formattedValue,
15025
15186
  hasData,
15026
15187
  theme,
15188
+ isDarkMode: !!isDarkMode,
15027
15189
  category: hasData ? category : void 0
15028
15190
  })
15029
15191
  );
@@ -15096,22 +15258,25 @@ function buildTileMark(opts) {
15096
15258
  formattedValue,
15097
15259
  hasData,
15098
15260
  theme,
15261
+ isDarkMode,
15099
15262
  category
15100
15263
  } = opts;
15101
15264
  const tileCenterX = gridOffsetX + pos.x + tileSize / 2;
15102
15265
  const tileTopY = gridOffsetY + pos.y;
15266
+ const textColor = hasData ? pickLabelColor2(fill, isDarkMode) : "#ffffff";
15267
+ const isLightText = textColor === "#ffffff";
15103
15268
  const labelStyle = {
15104
15269
  fontFamily: theme.fonts.family,
15105
15270
  fontSize: tileSize > 24 ? 10 : 7,
15106
15271
  fontWeight: 700,
15107
- fill: "#ffffff",
15272
+ fill: textColor,
15108
15273
  lineHeight: 1.2
15109
15274
  };
15110
15275
  const valueLabelStyle = {
15111
15276
  fontFamily: theme.fonts.family,
15112
15277
  fontSize: tileSize > 24 ? 10 : 7,
15113
15278
  fontWeight: 300,
15114
- fill: "rgba(255,255,255,0.6)",
15279
+ fill: isLightText ? "rgba(255,255,255,0.6)" : "rgba(0,0,0,0.45)",
15115
15280
  lineHeight: 1.2
15116
15281
  };
15117
15282
  const valueLabel = tileSize < 24 ? { text: "", x: 0, y: 0, style: valueLabelStyle, visible: false } : {
@@ -16433,7 +16598,7 @@ function compileFaceted(chartSpec, renderSpec, facetChannel, options, theme, str
16433
16598
  encoding: panelEnc
16434
16599
  };
16435
16600
  const panelScales = computeScales(panelSpecWithDomains, gridPanel.area, panelData);
16436
- applyColorScaleRange(panelScales, panelSpecWithDomains.encoding, theme);
16601
+ applyColorScaleRange(panelScales, panelSpecWithDomains.encoding, theme, renderSpec.highlight);
16437
16602
  panelScales.defaultColor = chartSpec.markDef.fill ?? chartSpec.markDef.stroke ?? theme.colors.categorical[0];
16438
16603
  const isLeftCol = gridPanel.col === 0;
16439
16604
  const isBottomRow = gridPanel.row === grid.rows - 1;
@@ -16566,6 +16731,11 @@ function compileBarList2(spec, options) {
16566
16731
  return compileBarList(spec, options);
16567
16732
  }
16568
16733
  export {
16734
+ ENTER_DEFAULTS,
16735
+ EXIT_DEFAULTS,
16736
+ UPDATE_DEFAULTS,
16737
+ buildAreaPath,
16738
+ buildLinePath,
16569
16739
  clampStaggerDelay,
16570
16740
  clearRenderers,
16571
16741
  compile,
@@ -16576,6 +16746,7 @@ export {
16576
16746
  compileSankey2 as compileSankey,
16577
16747
  compileTable,
16578
16748
  compileTileMap2 as compileTileMap,
16749
+ dedupeKeys,
16579
16750
  evaluatePredicate,
16580
16751
  getChartRenderer,
16581
16752
  heuristicMeasure,
@@ -16589,6 +16760,7 @@ export {
16589
16760
  runFilter,
16590
16761
  runTimeUnit,
16591
16762
  runTransforms,
16763
+ serializeKeyValue,
16592
16764
  validateSpec
16593
16765
  };
16594
16766
  //# sourceMappingURL=index.js.map