@opendata-ai/openchart-engine 6.27.0 → 6.28.2

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 (36) hide show
  1. package/dist/index.d.ts +38 -6
  2. package/dist/index.js +1040 -521
  3. package/dist/index.js.map +1 -1
  4. package/package.json +2 -2
  5. package/src/__tests__/__snapshots__/compile-snapshot.test.ts.snap +31 -4
  6. package/src/__tests__/axes.test.ts +101 -3
  7. package/src/__tests__/legend.test.ts +2 -2
  8. package/src/annotations/__tests__/compute.test.ts +175 -0
  9. package/src/annotations/position.ts +37 -1
  10. package/src/annotations/resolve-range.ts +5 -5
  11. package/src/barlist/__tests__/compile-barlist.test.ts +200 -0
  12. package/src/barlist/compile-barlist.ts +380 -0
  13. package/src/barlist/types.ts +28 -0
  14. package/src/charts/bar/__tests__/compute.test.ts +222 -0
  15. package/src/charts/bar/compute.ts +77 -44
  16. package/src/charts/bar/index.ts +1 -0
  17. package/src/charts/bar/labels.ts +3 -2
  18. package/src/charts/column/compute.ts +60 -27
  19. package/src/charts/column/index.ts +1 -0
  20. package/src/charts/column/labels.ts +2 -1
  21. package/src/charts/line/__tests__/compute.test.ts +2 -2
  22. package/src/charts/line/area.ts +25 -4
  23. package/src/charts/line/compute.ts +15 -5
  24. package/src/compile.ts +26 -1
  25. package/src/compiler/normalize.ts +25 -1
  26. package/src/compiler/types.ts +5 -3
  27. package/src/compiler/validate.ts +120 -5
  28. package/src/index.ts +5 -0
  29. package/src/layout/axes/ticks.ts +37 -8
  30. package/src/layout/axes.ts +11 -4
  31. package/src/layout/dimensions.ts +10 -4
  32. package/src/layout/scales.ts +10 -0
  33. package/src/legend/wrap.ts +1 -1
  34. package/src/tilemap/__tests__/compile-tilemap.test.ts +5 -2
  35. package/src/tilemap/compile-tilemap.ts +41 -29
  36. package/src/tooltips/compute.ts +4 -2
@@ -44,8 +44,8 @@ import type { NormalizedTileMapSpec } from './types';
44
44
  // Constants
45
45
  // ---------------------------------------------------------------------------
46
46
 
47
- const TILE_CORNER_RADIUS = 2;
48
- const TILE_STROKE_WIDTH = 0;
47
+ const TILE_CORNER_RADIUS = 6;
48
+ const TILE_STROKE_WIDTH = 1;
49
49
 
50
50
  // ---------------------------------------------------------------------------
51
51
  // Public API
@@ -143,23 +143,22 @@ export function compileTileMap(spec: unknown, options: CompileOptions): TileMapL
143
143
  const min = values.length > 0 ? Math.min(...values) : 0;
144
144
  const max = values.length > 0 ? Math.max(...values) : 100;
145
145
 
146
- // 8. Build color scale (fallback to 'blue' if palette name is invalid)
146
+ // 8. Build opacity scale: single base color, opacity encodes value
147
147
  const paletteStops = [...(SEQUENTIAL_PALETTES[tilemapSpec.palette] ?? SEQUENTIAL_PALETTES.blue)];
148
- if (isDarkMode) paletteStops.reverse();
149
-
150
- const domain = paletteStops.map((_, i) => min + (i / (paletteStops.length - 1)) * (max - min));
151
- const colorScale = scaleLinear<string>().domain(domain).range(paletteStops).clamp(true);
148
+ const baseColor = isDarkMode ? paletteStops[0] : paletteStops[paletteStops.length - 1];
149
+ const opacityRange: [number, number] = isDarkMode ? [0.15, 1] : [0.2, 1];
150
+ const opacityScale = scaleLinear<number>().domain([min, max]).range(opacityRange).clamp(true);
152
151
 
153
152
  // 9. Reserve space for gradient legend at bottom (unless hidden)
154
153
  const showLegend = tilemapSpec.legend?.show !== false;
155
- const legendBarHeight = 12;
156
- const legendLabelGap = 4;
154
+ const legendBarHeight = 6;
155
+ const legendLabelGap = 6;
157
156
  const legendTotalHeight = showLegend ? legendBarHeight + legendLabelGap + 14 : 0;
158
157
 
159
158
  // 10. Compute tile positions in the remaining area
160
159
  const legendGap = showLegend ? 8 : 0;
161
160
  const tileAreaHeight = fullArea.height - legendTotalHeight - legendGap;
162
- const tilePositions = computeTilePositions(fullArea.width, tileAreaHeight, 4);
161
+ const tilePositions = computeTilePositions(fullArea.width, tileAreaHeight, 5);
163
162
 
164
163
  // Center tile grid horizontally
165
164
  const tileGridOffsetX = fullArea.x + (fullArea.width - tilePositions.gridWidth) / 2;
@@ -173,9 +172,9 @@ export function compileTileMap(spec: unknown, options: CompileOptions): TileMapL
173
172
  // 11. Build TileMapTileMark[]
174
173
  const formatter = buildD3Formatter(tilemapSpec.valueFormat) ?? formatNumber;
175
174
  const neutralFillLight = '#e0e0e0';
176
- const neutralFillDark = '#2a2a3e';
175
+ const neutralFillDark = '#1e2a30';
177
176
  const neutralStrokeLight = '#d0d0d0';
178
- const neutralStrokeDark = '#3a3a50';
177
+ const neutralStrokeDark = 'rgba(255,255,255,0.08)';
179
178
 
180
179
  const neutralFill = isDarkMode ? neutralFillDark : neutralFillLight;
181
180
  const neutralStroke = isDarkMode ? neutralStrokeDark : neutralStrokeLight;
@@ -188,12 +187,18 @@ export function compileTileMap(spec: unknown, options: CompileOptions): TileMapL
188
187
 
189
188
  const hasData = stateValueMap.has(stateCode);
190
189
  const value = hasData ? stateValueMap.get(stateCode)! : null;
191
- const fill = hasData ? colorScale(value!) : neutralFill;
190
+ const opacity = hasData ? opacityScale(value!) : 0;
191
+ const fill = hasData ? baseColor : neutralFill;
192
+ const stroke = hasData
193
+ ? isDarkMode
194
+ ? 'rgba(255,255,255,0.1)'
195
+ : 'rgba(0,0,0,0.1)'
196
+ : neutralStroke;
192
197
  const formattedValue = hasData ? formatter(value!) : '–';
193
198
 
194
199
  const labelStyle: TextStyle = {
195
200
  fontFamily: theme.fonts.family,
196
- fontSize: tilePositions.tileSize > 24 ? 14 : 11,
201
+ fontSize: tilePositions.tileSize > 24 ? 10 : 7,
197
202
  fontWeight: 700,
198
203
  fill: '#ffffff',
199
204
  lineHeight: 1.2,
@@ -201,20 +206,24 @@ export function compileTileMap(spec: unknown, options: CompileOptions): TileMapL
201
206
 
202
207
  const valueLabelStyle: TextStyle = {
203
208
  fontFamily: theme.fonts.family,
204
- fontSize: tilePositions.tileSize > 24 ? 12 : 10,
205
- fontWeight: 400,
206
- fill: '#ffffff',
209
+ fontSize: tilePositions.tileSize > 24 ? 10 : 7,
210
+ fontWeight: 300,
211
+ fill: 'rgba(255,255,255,0.6)',
207
212
  lineHeight: 1.2,
208
213
  };
209
214
 
215
+ const tileCenterX = tileGridOffsetX + pos.x + tilePositions.tileSize / 2;
216
+ const tileTopY = tileGridOffsetY + pos.y;
217
+ const sz = tilePositions.tileSize;
218
+
210
219
  // Only show value label on larger tiles
211
220
  const valueLabel =
212
- tilePositions.tileSize < 24
221
+ sz < 24
213
222
  ? { text: '', x: 0, y: 0, style: valueLabelStyle, visible: false }
214
223
  : {
215
224
  text: formattedValue,
216
- x: tileGridOffsetX + pos.x + tilePositions.tileSize / 2,
217
- y: tileGridOffsetY + pos.y + tilePositions.tileSize / 2 + 8,
225
+ x: tileCenterX,
226
+ y: tileTopY + sz * 0.78,
218
227
  style: valueLabelStyle,
219
228
  visible: true,
220
229
  };
@@ -223,10 +232,11 @@ export function compileTileMap(spec: unknown, options: CompileOptions): TileMapL
223
232
  type: 'tile' as const,
224
233
  stateCode,
225
234
  x: tileGridOffsetX + pos.x,
226
- y: tileGridOffsetY + pos.y,
227
- size: tilePositions.tileSize,
235
+ y: tileTopY,
236
+ size: sz,
228
237
  fill,
229
- stroke: neutralStroke,
238
+ fillOpacity: hasData ? opacity : 1,
239
+ stroke,
230
240
  strokeWidth: TILE_STROKE_WIDTH,
231
241
  cornerRadius: TILE_CORNER_RADIUS,
232
242
  value: value ?? null,
@@ -234,8 +244,8 @@ export function compileTileMap(spec: unknown, options: CompileOptions): TileMapL
234
244
  hasData,
235
245
  label: {
236
246
  text: stateCode,
237
- x: tileGridOffsetX + pos.x + tilePositions.tileSize / 2,
238
- y: tileGridOffsetY + pos.y + tilePositions.tileSize / 2 - 4,
247
+ x: tileCenterX,
248
+ y: tileTopY + sz * 0.28,
239
249
  style: labelStyle,
240
250
  visible: true,
241
251
  },
@@ -269,10 +279,12 @@ export function compileTileMap(spec: unknown, options: CompileOptions): TileMapL
269
279
  let gradientLegend: GradientLegendLayout | null = null;
270
280
 
271
281
  if (showLegend) {
272
- const gradientColorStops: GradientColorStop[] = paletteStops.map((color, i) => ({
273
- offset: i / (paletteStops.length - 1),
274
- color,
275
- }));
282
+ const numStops = 5;
283
+ const gradientColorStops: GradientColorStop[] = Array.from({ length: numStops }, (_, i) => {
284
+ const t = i / (numStops - 1);
285
+ const o = opacityRange[0] + t * (opacityRange[1] - opacityRange[0]);
286
+ return { offset: t, color: baseColor, opacity: o };
287
+ });
276
288
 
277
289
  gradientLegend = {
278
290
  type: 'gradient',
@@ -59,12 +59,14 @@ function formatValue(value: unknown, fieldType?: string, format?: string): strin
59
59
 
60
60
  /** Resolve the display label for an encoding channel: title > axis.title > field name. */
61
61
  function resolveLabel(ch: EncodingChannel): string {
62
- return ch.title ?? ch.axis?.title ?? ch.field;
62
+ const ax = ch.axis || undefined;
63
+ return ch.title ?? ax?.title ?? ch.field;
63
64
  }
64
65
 
65
66
  /** Resolve the format string for an encoding channel: format > axis.format. */
66
67
  function resolveFormat(ch: EncodingChannel): string | undefined {
67
- return ch.format ?? ch.axis?.format;
68
+ const ax = ch.axis || undefined;
69
+ return ch.format ?? ax?.format;
68
70
  }
69
71
 
70
72
  /** Build tooltip fields from explicit tooltip encoding channels. */