@opendata-ai/openchart-engine 7.10.0 → 8.0.0-rc.1

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 (159) hide show
  1. package/dist/index.d.ts +142 -7
  2. package/dist/index.js +16182 -10032
  3. package/dist/index.js.map +1 -1
  4. package/package.json +11 -4
  5. package/src/__tests__/__snapshots__/compile-snapshot.test.ts.snap +170 -56
  6. package/src/__tests__/annotation-behavior.test.ts +721 -0
  7. package/src/__tests__/axes.test.ts +50 -3
  8. package/src/__tests__/compile-chart.test.ts +30 -0
  9. package/src/__tests__/compile-footnotes.test.ts +277 -0
  10. package/src/__tests__/compile-layer.test.ts +120 -1
  11. package/src/__tests__/dimensions.test.ts +49 -4
  12. package/src/__tests__/helpers/invariants.ts +54 -20
  13. package/src/__tests__/known-bugs.test.ts +455 -2
  14. package/src/__tests__/legend.test.ts +120 -4
  15. package/src/__tests__/ranking-recipes.test.ts +260 -0
  16. package/src/__tests__/scales.test.ts +9 -3
  17. package/src/__tests__/series-search.test.ts +92 -0
  18. package/src/__tests__/spec-sugar.test.ts +801 -0
  19. package/src/__tests__/you-draw-it.test.ts +160 -0
  20. package/src/annotations/__tests__/compute.test.ts +207 -92
  21. package/src/annotations/__tests__/geometry.test.ts +66 -0
  22. package/src/annotations/__tests__/resolve-text.test.ts +527 -27
  23. package/src/annotations/__tests__/rich-text.test.ts +91 -0
  24. package/src/annotations/__tests__/thinning.test.ts +148 -0
  25. package/src/annotations/collisions.ts +130 -84
  26. package/src/annotations/compute.ts +83 -85
  27. package/src/annotations/constants.ts +116 -6
  28. package/src/annotations/geometry.ts +222 -77
  29. package/src/annotations/move.ts +135 -0
  30. package/src/annotations/placement.ts +62 -14
  31. package/src/annotations/resolve-range.ts +2 -0
  32. package/src/annotations/resolve-refline.ts +2 -0
  33. package/src/annotations/resolve-text.ts +312 -111
  34. package/src/annotations/rich-text.ts +65 -0
  35. package/src/annotations/thinning.ts +84 -10
  36. package/src/barlist/compile-barlist.ts +16 -6
  37. package/src/charts/_shared/format-label-value.ts +9 -8
  38. package/src/charts/bar/__tests__/compute.test.ts +57 -10
  39. package/src/charts/bar/__tests__/labels.test.ts +5 -4
  40. package/src/charts/bar/compute.ts +2 -6
  41. package/src/charts/bar/index.ts +8 -1
  42. package/src/charts/bar/labels.ts +3 -3
  43. package/src/charts/beeswarm/__tests__/compute.test.ts +280 -0
  44. package/src/charts/beeswarm/__tests__/dodge.test.ts +148 -0
  45. package/src/charts/beeswarm/compute.ts +203 -0
  46. package/src/charts/beeswarm/dodge.ts +155 -0
  47. package/src/charts/beeswarm/index.ts +32 -0
  48. package/src/charts/builtin.ts +12 -1
  49. package/src/charts/calendar/__tests__/compute.test.ts +417 -0
  50. package/src/charts/calendar/compute.ts +319 -0
  51. package/src/charts/calendar/index.ts +32 -0
  52. package/src/charts/column/__tests__/compute.test.ts +21 -7
  53. package/src/charts/column/__tests__/labels.test.ts +76 -1
  54. package/src/charts/column/compute.ts +2 -6
  55. package/src/charts/column/index.ts +10 -2
  56. package/src/charts/column/labels.ts +95 -21
  57. package/src/charts/dot/index.ts +8 -1
  58. package/src/charts/dot/labels.ts +3 -3
  59. package/src/charts/line/__tests__/compute.test.ts +15 -19
  60. package/src/charts/line/area.ts +8 -23
  61. package/src/charts/line/compute.ts +32 -2
  62. package/src/charts/parliament/__tests__/compute.test.ts +411 -0
  63. package/src/charts/parliament/compute.ts +313 -0
  64. package/src/charts/parliament/index.ts +32 -0
  65. package/src/charts/pie/__tests__/compute.test.ts +103 -0
  66. package/src/charts/pie/compute.ts +63 -8
  67. package/src/charts/pie/labels.ts +1 -0
  68. package/src/charts/range/__tests__/compute.test.ts +486 -0
  69. package/src/charts/range/compute.ts +273 -0
  70. package/src/charts/range/index.ts +82 -0
  71. package/src/charts/range/labels.ts +169 -0
  72. package/src/charts/rect/__tests__/compute.test.ts +120 -0
  73. package/src/charts/rect/index.ts +127 -0
  74. package/src/charts/scatter/compute.ts +7 -21
  75. package/src/charts/text/__tests__/compute.test.ts +307 -0
  76. package/src/charts/text/index.ts +44 -7
  77. package/src/charts/tick/__tests__/compute.test.ts +75 -0
  78. package/src/charts/tick/index.ts +7 -4
  79. package/src/charts/utils.ts +5 -3
  80. package/src/charts/waffle/__tests__/compute.test.ts +305 -0
  81. package/src/charts/waffle/compute.ts +197 -0
  82. package/src/charts/waffle/index.ts +31 -0
  83. package/src/compile/__tests__/color-scale-reverse.test.ts +89 -0
  84. package/src/compile/__tests__/data-clip.test.ts +68 -0
  85. package/src/compile/__tests__/fill-patterns.test.ts +154 -0
  86. package/src/compile/__tests__/size-scale.test.ts +108 -0
  87. package/src/compile/color-scale-range.ts +54 -33
  88. package/src/compile/data-clip.ts +21 -6
  89. package/src/compile/fill-patterns.ts +74 -0
  90. package/src/compile/layer.ts +117 -21
  91. package/src/compile/size-scale.ts +139 -0
  92. package/src/compile/spec-sugar.ts +758 -0
  93. package/src/compile/you-draw-it.ts +123 -0
  94. package/src/compile.ts +315 -103
  95. package/src/compiler/__tests__/a11y-warnings.test.ts +148 -0
  96. package/src/compiler/__tests__/fixtures/vl-parity/axis-null.json +19 -0
  97. package/src/compiler/__tests__/fixtures/vl-parity/color-value.json +20 -0
  98. package/src/compiler/__tests__/fixtures/vl-parity/count-aggregate.json +22 -0
  99. package/src/compiler/__tests__/fixtures/vl-parity/data-values.json +19 -0
  100. package/src/compiler/__tests__/fixtures/vl-parity/layer-axis-null.json +14 -0
  101. package/src/compiler/__tests__/fixtures/vl-parity/layer-count.json +34 -0
  102. package/src/compiler/__tests__/fixtures/vl-parity/layer-data-values.json +22 -0
  103. package/src/compiler/__tests__/fixtures/vl-parity/layer-legend-null.json +41 -0
  104. package/src/compiler/__tests__/fixtures/vl-parity/layer-scheme.json +38 -0
  105. package/src/compiler/__tests__/fixtures/vl-parity/layer-sort.json +28 -0
  106. package/src/compiler/__tests__/fixtures/vl-parity/layer-theta.json +28 -0
  107. package/src/compiler/__tests__/fixtures/vl-parity/layer-title.json +30 -0
  108. package/src/compiler/__tests__/fixtures/vl-parity/layer-value-def.json +29 -0
  109. package/src/compiler/__tests__/fixtures/vl-parity/legend-null.json +32 -0
  110. package/src/compiler/__tests__/fixtures/vl-parity/scheme.json +25 -0
  111. package/src/compiler/__tests__/fixtures/vl-parity/sort-by-value.json +19 -0
  112. package/src/compiler/__tests__/fixtures/vl-parity/theta-arc.json +19 -0
  113. package/src/compiler/__tests__/fixtures/vl-parity/title-subtitle.json +21 -0
  114. package/src/compiler/__tests__/fixtures/vl-parity/width-height.json +23 -0
  115. package/src/compiler/__tests__/normalize.test.ts +65 -1
  116. package/src/compiler/__tests__/validate-did-you-mean.test.ts +86 -0
  117. package/src/compiler/__tests__/validate-near-miss.test.ts +89 -0
  118. package/src/compiler/__tests__/validate.test.ts +156 -0
  119. package/src/compiler/__tests__/vl-parity.test.ts +68 -0
  120. package/src/compiler/a11y-warnings.ts +110 -0
  121. package/src/compiler/normalize.ts +150 -7
  122. package/src/compiler/types.ts +11 -0
  123. package/src/compiler/validate.ts +664 -18
  124. package/src/endpoint-labels/__tests__/compute.test.ts +88 -0
  125. package/src/endpoint-labels/compute.ts +40 -12
  126. package/src/endpoint-labels/format.ts +45 -15
  127. package/src/endpoint-labels/predict.ts +50 -36
  128. package/src/format/__tests__/field-format.test.ts +164 -0
  129. package/src/format/field-format.ts +27 -0
  130. package/src/geo/__tests__/compile-map.test.ts +621 -0
  131. package/src/geo/compile-map.ts +856 -0
  132. package/src/geo/join.ts +115 -0
  133. package/src/geo/projections.ts +45 -0
  134. package/src/geo/types.ts +24 -0
  135. package/src/graphs/__tests__/compile-graph.test.ts +25 -0
  136. package/src/graphs/compile-graph.ts +27 -18
  137. package/src/index.ts +28 -2
  138. package/src/layout/axes/ticks.ts +59 -10
  139. package/src/layout/axes.ts +30 -2
  140. package/src/layout/dimensions.ts +178 -95
  141. package/src/layout/facet.ts +14 -3
  142. package/src/layout/plan.ts +86 -35
  143. package/src/layout/scales.ts +132 -15
  144. package/src/legend/__tests__/continuous.test.ts +318 -0
  145. package/src/legend/__tests__/size.test.ts +348 -0
  146. package/src/legend/__tests__/suppression.test.ts +17 -1
  147. package/src/legend/compute.ts +126 -14
  148. package/src/legend/continuous.ts +327 -0
  149. package/src/legend/size.ts +407 -0
  150. package/src/legend/suppression.ts +8 -7
  151. package/src/legend/wrap.ts +8 -0
  152. package/src/sankey/compile-sankey.ts +31 -19
  153. package/src/tables/format-cells.ts +16 -7
  154. package/src/tilemap/__tests__/compile-tilemap.test.ts +39 -0
  155. package/src/tilemap/compile-tilemap.ts +24 -7
  156. package/src/tooltips/__tests__/compute.test.ts +2 -2
  157. package/src/tooltips/compute.ts +422 -41
  158. package/src/transforms/__tests__/conditional.test.ts +18 -0
  159. package/src/transforms/aggregate.ts +2 -1
@@ -533,5 +533,44 @@ describe('compileTileMap', () => {
533
533
  const caTile = result.tiles.find((t) => t.stateCode === 'CA')!;
534
534
  expect(caTile.formattedValue).toBe('Medical Only');
535
535
  });
536
+
537
+ it('suppresses value labels in categorical mode (showValueLabels: false)', () => {
538
+ const result = compileTileMap(categoricalRecordSpec, defaultOptions);
539
+
540
+ // All tiles should have valueLabel.visible = false in categorical mode
541
+ const dataTiles = result.tiles.filter((t) => t.hasData);
542
+ for (const tile of dataTiles) {
543
+ expect(tile.valueLabel.visible).toBe(false);
544
+ }
545
+ });
546
+ });
547
+
548
+ describe('value label visibility', () => {
549
+ it('shows value labels by default in quantitative mode (when tile size permits)', () => {
550
+ const result = compileTileMap(basicSpec, defaultOptions);
551
+
552
+ // Tiles should have valueLabel.visible = true when tileSize >= 24 in quantitative mode
553
+ const dataTiles = result.tiles.filter((t) => t.hasData);
554
+ const tilesWithVisibleLabels = dataTiles.filter((t) => t.valueLabel.visible);
555
+
556
+ // At least some tiles should have visible value labels (depends on computed tile size)
557
+ // If tileSize is large enough (>= 24), all data tiles should show value labels
558
+ if (dataTiles.length > 0 && dataTiles[0].size >= 24) {
559
+ expect(tilesWithVisibleLabels.length).toBe(dataTiles.length);
560
+ }
561
+ });
562
+
563
+ it('hides value labels when tile size is too small (< 24px)', () => {
564
+ // Use a very narrow container to force small tiles
565
+ const result = compileTileMap(basicSpec, { width: 200, height: 400 });
566
+
567
+ const dataTiles = result.tiles.filter((t) => t.hasData);
568
+ if (dataTiles.length > 0 && dataTiles[0].size < 24) {
569
+ // When tiles are small, value labels should be hidden
570
+ for (const tile of dataTiles) {
571
+ expect(tile.valueLabel.visible).toBe(false);
572
+ }
573
+ }
574
+ });
536
575
  });
537
576
  });
@@ -33,11 +33,9 @@ import type {
33
33
  } from '@opendata-ai/openchart-core';
34
34
  import {
35
35
  adaptTheme,
36
- buildD3Formatter,
37
36
  CATEGORICAL_PALETTE,
38
37
  computeChrome,
39
38
  estimateTextWidth,
40
- formatNumber,
41
39
  getBreakpoint,
42
40
  HPAD_COMPACT_FRACTION,
43
41
  HPAD_COMPACT_MIN,
@@ -46,9 +44,10 @@ import {
46
44
  SEQUENTIAL_PALETTES,
47
45
  } from '@opendata-ai/openchart-core';
48
46
  import { scaleLinear } from 'd3-scale';
49
-
47
+ import { emitSpecWarnings, expandSpecSugar } from '../compile/spec-sugar';
50
48
  import { resolveAnimation } from '../compiler/animation';
51
49
  import { compile as compileSpec } from '../compiler/index';
50
+ import { resolveFieldFormatter } from '../format/field-format';
52
51
  import { computeTilePositions, STATE_CODE_SET, STATE_NAMES, US_STATE_TILES } from './layout';
53
52
  import type { NormalizedTileMapSpec } from './types';
54
53
 
@@ -79,8 +78,19 @@ function clamp(value: number, min: number, max: number): number {
79
78
  * @throws Error if spec is invalid or not a tilemap type.
80
79
  */
81
80
  export function compileTileMap(spec: unknown, options: CompileOptions): TileMapLayout {
82
- // 1. Validate + normalize via the shared compiler pipeline
83
- const { spec: normalized } = compileSpec(spec);
81
+ // 1. Expand deprecated top-level sugar (valueFormat -> encoding.value.format)
82
+ // before validation, then validate + normalize via the shared compiler
83
+ // pipeline. Surface any normalize warnings (e.g. "only N of M rows have
84
+ // valid US state codes", mixed-type coercion) the same way the chart path
85
+ // does; without this the primary "your data did not map" diagnostic is
86
+ // computed and then dropped.
87
+ const sugarWarnings: string[] = [];
88
+ const expandedSpec =
89
+ spec && typeof spec === 'object' && !Array.isArray(spec)
90
+ ? expandSpecSugar(spec as Record<string, unknown>, sugarWarnings)
91
+ : spec;
92
+ const { spec: normalized, warnings } = compileSpec(expandedSpec);
93
+ emitSpecWarnings([...sugarWarnings, ...warnings], options.onWarn);
84
94
 
85
95
  if (!('type' in normalized) || normalized.type !== 'tilemap') {
86
96
  throw new Error(
@@ -208,7 +218,11 @@ function compileQuantitative(
208
218
  const legendY = tileGridOffsetY + tilePositions.gridHeight + legendGap;
209
219
  const legendWidth = tilePositions.gridWidth;
210
220
 
211
- const formatter = buildD3Formatter(tilemapSpec.valueFormat) ?? formatNumber;
221
+ // Encoding-level format (v8 canonical) wins over deprecated top-level valueFormat
222
+ const formatter = resolveFieldFormatter({
223
+ surfaceFormat: tilemapSpec.encoding.value.format ?? tilemapSpec.valueFormat,
224
+ values: Array.from(stateValueMap.values()),
225
+ });
212
226
  const neutralFill = isDarkMode ? '#1e2a30' : '#e0e0e0';
213
227
  const neutralStroke = isDarkMode ? 'rgba(255,255,255,0.08)' : '#d0d0d0';
214
228
 
@@ -432,6 +446,7 @@ function compileCategorical(
432
446
  theme,
433
447
  isDarkMode: !!isDarkMode,
434
448
  category: hasData ? category : undefined,
449
+ showValueLabels: false,
435
450
  }),
436
451
  );
437
452
  }
@@ -526,6 +541,7 @@ interface TileMarkOptions {
526
541
  theme: ResolvedTheme;
527
542
  isDarkMode: boolean;
528
543
  category?: string | null;
544
+ showValueLabels?: boolean;
529
545
  }
530
546
 
531
547
  function buildTileMark(opts: TileMarkOptions): TileMapTileMark {
@@ -544,6 +560,7 @@ function buildTileMark(opts: TileMarkOptions): TileMapTileMark {
544
560
  theme,
545
561
  isDarkMode,
546
562
  category,
563
+ showValueLabels = true,
547
564
  } = opts;
548
565
  const tileCenterX = gridOffsetX + pos.x + tileSize / 2;
549
566
  const tileTopY = gridOffsetY + pos.y;
@@ -553,7 +570,7 @@ function buildTileMark(opts: TileMarkOptions): TileMapTileMark {
553
570
 
554
571
  // A two-line layout (code over value) only fits once tiles are large enough;
555
572
  // below that we drop the value and center the code so nothing overflows.
556
- const showValue = tileSize >= 24;
573
+ const showValue = showValueLabels && tileSize >= 24;
557
574
 
558
575
  // Scale the code font with the tile so a 2-char label always fits its box,
559
576
  // rather than snapping between two fixed sizes. A 2-char string is ~1.2x the
@@ -141,10 +141,10 @@ describe('computeTooltipDescriptors', () => {
141
141
  // Should have fields for y (value) and x (date)
142
142
  expect(content.fields.length).toBeGreaterThanOrEqual(1);
143
143
 
144
- // The value field should be formatted with commas
144
+ // The value field should use compact notation (smart default formatting)
145
145
  const valueField = content.fields.find((f) => f.label === 'value');
146
146
  expect(valueField).toBeDefined();
147
- expect(valueField!.value).toBe('1,500');
147
+ expect(valueField!.value).toBe('1.5k');
148
148
  });
149
149
 
150
150
  it('tooltip title uses temporal x-axis value', () => {