@opendata-ai/openchart-engine 8.0.0-rc.6 → 8.0.0-rc.8

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.js CHANGED
@@ -244,7 +244,10 @@ function refreshConnector(connector, bounds, markerRadius) {
244
244
  }
245
245
  const exit = connectorExit(bounds, endpoint.x, endpoint.y);
246
246
  if (!exit) return void 0;
247
- const from = { x: exit.x, y: exit.y };
247
+ const from = {
248
+ x: exit.x + (connector.fromOffset?.dx ?? 0),
249
+ y: exit.y + (connector.fromOffset?.dy ?? 0)
250
+ };
248
251
  const gap = connectorPullbackGap(markerRadius, connector.arrow);
249
252
  const cdx = endpoint.x - from.x;
250
253
  const cdy = endpoint.y - from.y;
@@ -506,6 +509,10 @@ function resolveTextAnnotation(annotation, scales, chartArea, isDark, measure =
506
509
  from,
507
510
  to,
508
511
  endpoint,
512
+ fromOffset: annotation.connectorOffset?.from ? {
513
+ dx: annotation.connectorOffset.from.dx ?? 0,
514
+ dy: annotation.connectorOffset.from.dy ?? 0
515
+ } : void 0,
509
516
  stroke: connectorStroke,
510
517
  style: connectorStyle,
511
518
  arrow: connectorArrow,
@@ -10729,6 +10736,7 @@ import {
10729
10736
  MARK_ENCODING_RULES,
10730
10737
  MARK_TYPES,
10731
10738
  resolveSchemeName as resolveSchemeName2,
10739
+ SEQUENTIAL_PALETTES,
10732
10740
  SUPPORTED_SCHEME_NAMES
10733
10741
  } from "@opendata-ai/openchart-core";
10734
10742
  var VALID_FIELD_TYPES = /* @__PURE__ */ new Set(["quantitative", "temporal", "nominal", "ordinal"]);
@@ -10787,6 +10795,53 @@ function didYouMean(field, columns) {
10787
10795
  const match = nearestColumn(field, columns);
10788
10796
  return match ? ` Did you mean "${match}"?` : "";
10789
10797
  }
10798
+ function checkSchemeName(channelObj, channelPath, errors) {
10799
+ const scale = channelObj.scale;
10800
+ if (scale && typeof scale.scheme === "string" && !resolveSchemeName2(scale.scheme)) {
10801
+ errors.push({
10802
+ message: `Spec error: ${channelPath}.scale.scheme "${scale.scheme}" is not a supported scheme name`,
10803
+ path: `${channelPath}.scale.scheme`,
10804
+ code: "INVALID_VALUE",
10805
+ suggestion: `Use one of the supported scheme names: ${[...SUPPORTED_SCHEME_NAMES].join(", ")}. Or provide explicit colors via scale.range.`
10806
+ });
10807
+ }
10808
+ }
10809
+ function checkSchemeUnused(encoding, errors, familyNote, suggestion) {
10810
+ for (const [channel, ch] of Object.entries(encoding)) {
10811
+ if (!ch || typeof ch !== "object" || Array.isArray(ch)) continue;
10812
+ const scale = ch.scale;
10813
+ if (scale && typeof scale.scheme === "string") {
10814
+ errors.push({
10815
+ message: `Spec error: encoding.${channel}.scale.scheme has no effect \u2014 ${familyNote}`,
10816
+ path: `encoding.${channel}.scale.scheme`,
10817
+ code: "INVALID_VALUE",
10818
+ suggestion
10819
+ });
10820
+ }
10821
+ }
10822
+ }
10823
+ var MAP_PALETTE_NAMES = Object.keys(SEQUENTIAL_PALETTES).join(", ");
10824
+ function checkMapSchemeName(channelObj, channelPath, errors) {
10825
+ const scale = channelObj.scale;
10826
+ if (!scale || typeof scale.scheme !== "string") return;
10827
+ if (channelObj.type === "nominal" || channelObj.type === "ordinal") {
10828
+ errors.push({
10829
+ message: `Spec error: ${channelPath}.scale.scheme has no effect \u2014 categorical map colors come from scale.range and scale.domain`,
10830
+ path: `${channelPath}.scale.scheme`,
10831
+ code: "INVALID_VALUE",
10832
+ suggestion: "Remove scale.scheme. To customize categorical map colors, provide explicit colors via scale.range (with scale.domain for category order)."
10833
+ });
10834
+ return;
10835
+ }
10836
+ if (!(scale.scheme in SEQUENTIAL_PALETTES)) {
10837
+ errors.push({
10838
+ message: `Spec error: ${channelPath}.scale.scheme "${scale.scheme}" is not a supported map palette`,
10839
+ path: `${channelPath}.scale.scheme`,
10840
+ code: "INVALID_VALUE",
10841
+ suggestion: `Maps support the sequential palettes: ${MAP_PALETTE_NAMES}. The default is "blue".`
10842
+ });
10843
+ }
10844
+ }
10790
10845
  var VALID_RANGE_STYLES = /* @__PURE__ */ new Set(["dumbbell", "arrow", "bar"]);
10791
10846
  function validateRangeSpec(spec, errors) {
10792
10847
  const encoding = spec.encoding;
@@ -11148,15 +11203,7 @@ function validateChartSpec(spec, errors) {
11148
11203
  suggestion: `Use one of: ${[...VALID_FIELD_TYPES].join(", ")}`
11149
11204
  });
11150
11205
  }
11151
- const channelScale = channelObj.scale;
11152
- if (channelScale && typeof channelScale.scheme === "string" && !resolveSchemeName2(channelScale.scheme)) {
11153
- errors.push({
11154
- message: `Spec error: encoding.${channel}.scale.scheme "${channelScale.scheme}" is not a supported scheme name`,
11155
- path: `encoding.${channel}.scale.scheme`,
11156
- code: "INVALID_VALUE",
11157
- suggestion: `Use one of the supported scheme names: ${[...SUPPORTED_SCHEME_NAMES].join(", ")}. Or provide explicit colors via scale.range.`
11158
- });
11159
- }
11206
+ checkSchemeName(channelObj, `encoding.${channel}`, errors);
11160
11207
  if (channelRule && channelObj.type && channelRule.allowedTypes.length > 0) {
11161
11208
  if (!channelRule.allowedTypes.includes(channelObj.type)) {
11162
11209
  errors.push({
@@ -11482,7 +11529,7 @@ function validateGraphSpec(spec, errors) {
11482
11529
  message: `Spec error: edges[${i}].source "${edge.source}" does not reference an existing node id`,
11483
11530
  path: `edges[${i}].source`,
11484
11531
  code: "DATA_FIELD_MISSING",
11485
- suggestion: `Use one of the existing node ids: ${[...nodeIds].slice(0, 5).join(", ")}${nodeIds.size > 5 ? "..." : ""}`
11532
+ suggestion: `Use one of the existing node ids: ${[...nodeIds].slice(0, 5).join(", ")}${nodeIds.size > 5 ? "..." : ""}.${didYouMean(edge.source, [...nodeIds])}`
11486
11533
  });
11487
11534
  }
11488
11535
  if (typeof edge.target !== "string" || edge.target === "") {
@@ -11497,7 +11544,7 @@ function validateGraphSpec(spec, errors) {
11497
11544
  message: `Spec error: edges[${i}].target "${edge.target}" does not reference an existing node id`,
11498
11545
  path: `edges[${i}].target`,
11499
11546
  code: "DATA_FIELD_MISSING",
11500
- suggestion: `Use one of the existing node ids: ${[...nodeIds].slice(0, 5).join(", ")}${nodeIds.size > 5 ? "..." : ""}`
11547
+ suggestion: `Use one of the existing node ids: ${[...nodeIds].slice(0, 5).join(", ")}${nodeIds.size > 5 ? "..." : ""}.${didYouMean(edge.target, [...nodeIds])}`
11501
11548
  });
11502
11549
  }
11503
11550
  }
@@ -11515,7 +11562,7 @@ function validateGraphSpec(spec, errors) {
11515
11562
  message: `Spec error: encoding.${channel}.field "${ch.field}" does not exist on nodes. Available fields: ${[...nodeFields].join(", ")}`,
11516
11563
  path: `encoding.${channel}.field`,
11517
11564
  code: "DATA_FIELD_MISSING",
11518
- suggestion: `Use one of the node fields: ${[...nodeFields].join(", ")}`
11565
+ suggestion: `Use one of the node fields: ${[...nodeFields].join(", ")}.${didYouMean(ch.field, [...nodeFields])}`
11519
11566
  });
11520
11567
  }
11521
11568
  }
@@ -11527,10 +11574,16 @@ function validateGraphSpec(spec, errors) {
11527
11574
  message: `Spec error: encoding.${channel}.field "${ch.field}" does not exist on edges. Available fields: ${[...edgeFields].join(", ")}`,
11528
11575
  path: `encoding.${channel}.field`,
11529
11576
  code: "DATA_FIELD_MISSING",
11530
- suggestion: `Use one of the edge fields: ${[...edgeFields].join(", ")}`
11577
+ suggestion: `Use one of the edge fields: ${[...edgeFields].join(", ")}.${didYouMean(ch.field, [...edgeFields])}`
11531
11578
  });
11532
11579
  }
11533
11580
  }
11581
+ checkSchemeUnused(
11582
+ encoding,
11583
+ errors,
11584
+ "graph colors come from scale.range and scale.domain",
11585
+ "Remove scale.scheme. To customize graph colors, provide explicit colors via scale.range."
11586
+ );
11534
11587
  }
11535
11588
  if (spec.layout && typeof spec.layout === "object") {
11536
11589
  const layout = spec.layout;
@@ -11584,7 +11637,8 @@ function validateSankeySpec(spec, errors) {
11584
11637
  }
11585
11638
  const encoding = spec.encoding;
11586
11639
  const dataColumns = new Set(Object.keys(firstRow));
11587
- const availableColumns = [...dataColumns].join(", ");
11640
+ const columnList = [...dataColumns];
11641
+ const availableColumns = columnList.join(", ");
11588
11642
  for (const channel of ["source", "target", "value"]) {
11589
11643
  const ch = encoding[channel];
11590
11644
  if (!ch || typeof ch !== "object") {
@@ -11592,7 +11646,7 @@ function validateSankeySpec(spec, errors) {
11592
11646
  message: `Spec error: sankey encoding requires "${channel}" channel`,
11593
11647
  path: `encoding.${channel}`,
11594
11648
  code: "MISSING_FIELD",
11595
- suggestion: `Add encoding.${channel} with a field from your data (${availableColumns}). Example: ${channel}: { field: "${[...dataColumns][0] ?? "myField"}", type: "${channel === "value" ? "quantitative" : "nominal"}" }`
11649
+ suggestion: `Add encoding.${channel} with a field from your data (${availableColumns}). Example: ${channel}: { field: "${columnList[0] ?? "myField"}", type: "${channel === "value" ? "quantitative" : "nominal"}" }`
11596
11650
  });
11597
11651
  continue;
11598
11652
  }
@@ -11610,10 +11664,16 @@ function validateSankeySpec(spec, errors) {
11610
11664
  message: `Spec error: encoding.${channel}.field "${ch.field}" does not exist in data. Available columns: ${availableColumns}`,
11611
11665
  path: `encoding.${channel}.field`,
11612
11666
  code: "DATA_FIELD_MISSING",
11613
- suggestion: `Use one of the available data columns: ${availableColumns}`
11667
+ suggestion: `Use one of the available data columns: ${availableColumns}.${didYouMean(ch.field, columnList)}`
11614
11668
  });
11615
11669
  }
11616
11670
  }
11671
+ checkSchemeUnused(
11672
+ encoding,
11673
+ errors,
11674
+ "sankey node colors cycle the theme categorical palette",
11675
+ "Remove scale.scheme. To customize sankey colors, set theme.colors.categorical."
11676
+ );
11617
11677
  if (spec.darkMode !== void 0 && !VALID_DARK_MODES.has(spec.darkMode)) {
11618
11678
  errors.push({
11619
11679
  message: 'Spec error: darkMode must be "auto", "force", or "off"',
@@ -11673,7 +11733,8 @@ function validateTileMapSpec(spec, errors) {
11673
11733
  }
11674
11734
  const encoding = spec.encoding;
11675
11735
  const dataColumns = new Set(Object.keys(firstRow));
11676
- const availableColumns = [...dataColumns].join(", ");
11736
+ const columnList = [...dataColumns];
11737
+ const availableColumns = columnList.join(", ");
11677
11738
  for (const channel of ["state", "value"]) {
11678
11739
  const ch = encoding[channel];
11679
11740
  if (!ch || typeof ch !== "object") {
@@ -11681,7 +11742,7 @@ function validateTileMapSpec(spec, errors) {
11681
11742
  message: `Spec error: tilemap encoding requires "${channel}" channel`,
11682
11743
  path: `encoding.${channel}`,
11683
11744
  code: "MISSING_FIELD",
11684
- suggestion: `Add encoding.${channel} with a field from your data (${availableColumns}). Example: ${channel}: { field: "${[...dataColumns][0] ?? "myField"}", type: "${channel === "value" ? "quantitative" : "nominal"}" }`
11745
+ suggestion: `Add encoding.${channel} with a field from your data (${availableColumns}). Example: ${channel}: { field: "${columnList[0] ?? "myField"}", type: "${channel === "value" ? "quantitative" : "nominal"}" }`
11685
11746
  });
11686
11747
  continue;
11687
11748
  }
@@ -11699,11 +11760,19 @@ function validateTileMapSpec(spec, errors) {
11699
11760
  message: `Spec error: encoding.${channel}.field "${ch.field}" does not exist in data. Available columns: ${availableColumns}`,
11700
11761
  path: `encoding.${channel}.field`,
11701
11762
  code: "DATA_FIELD_MISSING",
11702
- suggestion: `Use one of the available data columns: ${availableColumns}`
11763
+ suggestion: `Use one of the available data columns: ${availableColumns}.${didYouMean(ch.field, columnList)}`
11703
11764
  });
11704
11765
  }
11705
11766
  }
11706
11767
  }
11768
+ if (spec.encoding && typeof spec.encoding === "object") {
11769
+ checkSchemeUnused(
11770
+ spec.encoding,
11771
+ errors,
11772
+ "tilemap fills come from the top-level palette property",
11773
+ `Remove scale.scheme and set the top-level palette property instead, e.g. palette: "green". Supported: ${MAP_PALETTE_NAMES}.`
11774
+ );
11775
+ }
11707
11776
  if (spec.darkMode !== void 0 && !VALID_DARK_MODES.has(spec.darkMode)) {
11708
11777
  errors.push({
11709
11778
  message: 'Spec error: darkMode must be "auto", "force", or "off"',
@@ -11742,6 +11811,16 @@ function validateMapSpec(spec, errors) {
11742
11811
  });
11743
11812
  return;
11744
11813
  }
11814
+ const mapEncoding = spec.encoding;
11815
+ if (mapEncoding.color && typeof mapEncoding.color === "object") {
11816
+ checkMapSchemeName(mapEncoding.color, "encoding.color", errors);
11817
+ }
11818
+ if (spec.points && typeof spec.points === "object") {
11819
+ const pointsLayer = spec.points;
11820
+ if (pointsLayer.color && typeof pointsLayer.color === "object") {
11821
+ checkMapSchemeName(pointsLayer.color, "points.color", errors);
11822
+ }
11823
+ }
11745
11824
  if ("focus" in geo && geo.focus !== void 0 && geo.focus !== null) {
11746
11825
  const focus = geo.focus;
11747
11826
  if (typeof focus === "string" || typeof focus === "number") {
@@ -11951,7 +12030,8 @@ function validateBarListSpec(spec, errors) {
11951
12030
  }
11952
12031
  const encoding = spec.encoding;
11953
12032
  const dataColumns = new Set(Object.keys(firstRow));
11954
- const availableColumns = [...dataColumns].join(", ");
12033
+ const columnList = [...dataColumns];
12034
+ const availableColumns = columnList.join(", ");
11955
12035
  for (const channel of ["label", "value"]) {
11956
12036
  const ch = encoding[channel];
11957
12037
  if (!ch || typeof ch !== "object") {
@@ -11959,7 +12039,7 @@ function validateBarListSpec(spec, errors) {
11959
12039
  message: `Spec error: barlist encoding requires "${channel}" channel`,
11960
12040
  path: `encoding.${channel}`,
11961
12041
  code: "MISSING_FIELD",
11962
- suggestion: `Add encoding.${channel} with a field from your data (${availableColumns}). Example: ${channel}: { field: "${[...dataColumns][0] ?? "myField"}", type: "${channel === "value" ? "quantitative" : "nominal"}" }`
12042
+ suggestion: `Add encoding.${channel} with a field from your data (${availableColumns}). Example: ${channel}: { field: "${columnList[0] ?? "myField"}", type: "${channel === "value" ? "quantitative" : "nominal"}" }`
11963
12043
  });
11964
12044
  continue;
11965
12045
  }
@@ -11977,7 +12057,7 @@ function validateBarListSpec(spec, errors) {
11977
12057
  message: `Spec error: encoding.${channel}.field "${ch.field}" does not exist in data. Available columns: ${availableColumns}`,
11978
12058
  path: `encoding.${channel}.field`,
11979
12059
  code: "DATA_FIELD_MISSING",
11980
- suggestion: `Use one of the available data columns: ${availableColumns}`
12060
+ suggestion: `Use one of the available data columns: ${availableColumns}.${didYouMean(ch.field, columnList)}`
11981
12061
  });
11982
12062
  }
11983
12063
  }
@@ -11990,10 +12070,16 @@ function validateBarListSpec(spec, errors) {
11990
12070
  message: `Spec error: encoding.${channel}.field "${field}" does not exist in data. Available columns: ${availableColumns}`,
11991
12071
  path: `encoding.${channel}.field`,
11992
12072
  code: "DATA_FIELD_MISSING",
11993
- suggestion: `Use one of the available data columns: ${availableColumns}`
12073
+ suggestion: `Use one of the available data columns: ${availableColumns}.${didYouMean(field, columnList)}`
11994
12074
  });
11995
12075
  }
11996
12076
  }
12077
+ checkSchemeUnused(
12078
+ encoding,
12079
+ errors,
12080
+ "bar list colors cycle the built-in barlist palette",
12081
+ "Remove scale.scheme. Bar list colors are not configurable via encoding; they cycle the built-in palette."
12082
+ );
11997
12083
  if (spec.darkMode !== void 0 && !VALID_DARK_MODES.has(spec.darkMode)) {
11998
12084
  errors.push({
11999
12085
  message: 'Spec error: darkMode must be "auto", "force", or "off"',
@@ -15779,7 +15865,7 @@ import {
15779
15865
  HPAD_COMPACT_FRACTION as HPAD_COMPACT_FRACTION2,
15780
15866
  HPAD_COMPACT_MIN as HPAD_COMPACT_MIN2,
15781
15867
  resolveTheme as resolveTheme3,
15782
- SEQUENTIAL_PALETTES
15868
+ SEQUENTIAL_PALETTES as SEQUENTIAL_PALETTES2
15783
15869
  } from "@opendata-ai/openchart-core";
15784
15870
 
15785
15871
  // ../../node_modules/.bun/d3-geo@3.1.1/node_modules/d3-geo/src/math.js
@@ -18064,7 +18150,7 @@ function compileMap(spec, options) {
18064
18150
  }
18065
18151
  if (colorValues.length > 0) {
18066
18152
  const palette = pts.color.scale?.scheme ?? "blue";
18067
- const paletteStops = [...SEQUENTIAL_PALETTES[palette] ?? SEQUENTIAL_PALETTES.blue];
18153
+ const paletteStops = [...SEQUENTIAL_PALETTES2[palette] ?? SEQUENTIAL_PALETTES2.blue];
18068
18154
  const qScale = quantile2().domain(colorValues).range(paletteStops);
18069
18155
  pointColorScale = (val) => {
18070
18156
  const v = Number(val);
@@ -18421,7 +18507,7 @@ function buildQuantitativeMarks(opts) {
18421
18507
  if (!Number.isNaN(v)) values.push(v);
18422
18508
  }
18423
18509
  }
18424
- const paletteStops = [...SEQUENTIAL_PALETTES[palette] ?? SEQUENTIAL_PALETTES.blue];
18510
+ const paletteStops = [...SEQUENTIAL_PALETTES2[palette] ?? SEQUENTIAL_PALETTES2.blue];
18425
18511
  const colorScale = quantile2().domain(values).range(paletteStops);
18426
18512
  const marks = [];
18427
18513
  let animIndex = 0;
@@ -18625,13 +18711,15 @@ function resolveFocus(focus, features, pointMarks, warnings) {
18625
18711
  let maxX2 = -Infinity;
18626
18712
  let maxY2 = -Infinity;
18627
18713
  for (const p of matched) {
18628
- minX2 = Math.min(minX2, p.cx - p.r);
18629
- minY2 = Math.min(minY2, p.cy - p.r);
18630
- maxX2 = Math.max(maxX2, p.cx + p.r);
18631
- maxY2 = Math.max(maxY2, p.cy + p.r);
18714
+ minX2 = Math.min(minX2, p.cx);
18715
+ minY2 = Math.min(minY2, p.cy);
18716
+ maxX2 = Math.max(maxX2, p.cx);
18717
+ maxY2 = Math.max(maxY2, p.cy);
18632
18718
  }
18719
+ const w = Math.max(maxX2 - minX2, 1);
18720
+ const h = Math.max(maxY2 - minY2, 1);
18633
18721
  return {
18634
- target: { x: minX2, y: minY2, width: maxX2 - minX2, height: maxY2 - minY2, padding: padding2 },
18722
+ target: { x: minX2, y: minY2, width: w, height: h, padding: padding2 },
18635
18723
  // No feature ids: focus-dim is a feature concept, so a points focus
18636
18724
  // dims no features (there are no "other" features to mute).
18637
18725
  ids: []
@@ -21349,7 +21437,7 @@ import {
21349
21437
  HPAD_COMPACT_MIN as HPAD_COMPACT_MIN4,
21350
21438
  pickLabelColor as pickLabelColor4,
21351
21439
  resolveTheme as resolveTheme6,
21352
- SEQUENTIAL_PALETTES as SEQUENTIAL_PALETTES2
21440
+ SEQUENTIAL_PALETTES as SEQUENTIAL_PALETTES3
21353
21441
  } from "@opendata-ai/openchart-core";
21354
21442
  var TILE_CORNER_RADIUS = 6;
21355
21443
  var TILE_STROKE_WIDTH = 1;
@@ -21427,7 +21515,7 @@ function compileQuantitative(tilemapSpec, options, theme, chrome, fullArea, isDa
21427
21515
  const values = Array.from(stateValueMap.values());
21428
21516
  const min4 = values.length > 0 ? Math.min(...values) : 0;
21429
21517
  const max4 = values.length > 0 ? Math.max(...values) : 100;
21430
- const paletteStops = [...SEQUENTIAL_PALETTES2[tilemapSpec.palette] ?? SEQUENTIAL_PALETTES2.blue];
21518
+ const paletteStops = [...SEQUENTIAL_PALETTES3[tilemapSpec.palette] ?? SEQUENTIAL_PALETTES3.blue];
21431
21519
  const baseColor = isDarkMode ? paletteStops[0] : paletteStops[paletteStops.length - 1];
21432
21520
  const opacityRange = isDarkMode ? [0.15, 1] : [0.2, 1];
21433
21521
  const opacityScale = linear2().domain([min4, max4]).range(opacityRange).clamp(true);