@opendata-ai/openchart-engine 8.0.0-rc.19 → 8.0.0-rc.20

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
@@ -9,6 +9,7 @@ import {
9
9
  getHeightClass,
10
10
  getLayoutStrategy,
11
11
  isAxislessMark as isAxislessMark3,
12
+ MAX_SR_TABLE_ROWS,
12
13
  resolveTheme as resolveTheme7
13
14
  } from "@opendata-ai/openchart-core";
14
15
 
@@ -10103,6 +10104,12 @@ function resolveAnimation(spec) {
10103
10104
  const enter = resolvePhase(config.enter, ENTER_DEFAULTS);
10104
10105
  const update = resolvePhase(config.update, UPDATE_DEFAULTS);
10105
10106
  const exit = resolvePhase(config.exit, EXIT_DEFAULTS);
10107
+ if (update && config.update && typeof config.update === "object" && !Array.isArray(config.update)) {
10108
+ const cap = config.update.maxMarks;
10109
+ if (typeof cap === "number" && Number.isFinite(cap) && cap >= 1) {
10110
+ update.maxMarks = Math.floor(cap);
10111
+ }
10112
+ }
10106
10113
  if (!enter && !update && !exit) return void 0;
10107
10114
  return {
10108
10115
  enter: enter ?? void 0,
@@ -11021,6 +11028,21 @@ function validateCalendarSpec(spec, errors) {
11021
11028
  seenDays.set(dayKey, i);
11022
11029
  }
11023
11030
  }
11031
+ var VALID_RENDER_MODES = /* @__PURE__ */ new Set(["auto", "svg", "canvas"]);
11032
+ function validateMarkRenderMode(spec, errors) {
11033
+ const mark = spec.mark;
11034
+ if (!mark || typeof mark !== "object" || Array.isArray(mark)) return;
11035
+ const render = mark.render;
11036
+ if (render === void 0) return;
11037
+ if (typeof render !== "string" || !VALID_RENDER_MODES.has(render)) {
11038
+ errors.push({
11039
+ message: `Spec error: mark.render ${JSON.stringify(render)} is not valid. Must be one of: auto, svg, canvas`,
11040
+ path: "mark.render",
11041
+ code: "INVALID_VALUE",
11042
+ suggestion: 'Use mark: { type: "point", render: "auto" } (default), "canvas" to force the canvas mark layer, or "svg" to force one circle per point.'
11043
+ });
11044
+ }
11045
+ }
11024
11046
  function validateChartSpec(spec, errors) {
11025
11047
  const markType = typeof spec.mark === "string" ? spec.mark : spec.mark?.type;
11026
11048
  if (!Array.isArray(spec.data)) {
@@ -11126,6 +11148,7 @@ function validateChartSpec(spec, errors) {
11126
11148
  if (markType === "calendar") {
11127
11149
  validateCalendarSpec(spec, errors);
11128
11150
  }
11151
+ validateMarkRenderMode(spec, errors);
11129
11152
  if (Array.isArray(spec.transform)) {
11130
11153
  const transforms = spec.transform;
11131
11154
  for (let i = 0; i < transforms.length; i++) {
@@ -12243,6 +12266,22 @@ function validateLayerSpec(spec, errors) {
12243
12266
  }
12244
12267
  }
12245
12268
  }
12269
+ function validateAnimation(spec, errors) {
12270
+ const animation = spec.animation;
12271
+ if (!animation || typeof animation !== "object" || Array.isArray(animation)) return;
12272
+ const update = animation.update;
12273
+ if (!update || typeof update !== "object" || Array.isArray(update)) return;
12274
+ const maxMarks = update.maxMarks;
12275
+ if (maxMarks === void 0) return;
12276
+ if (typeof maxMarks !== "number" || !Number.isFinite(maxMarks) || maxMarks < 1) {
12277
+ errors.push({
12278
+ message: `Spec error: animation.update.maxMarks must be a number >= 1, got ${JSON.stringify(maxMarks)}`,
12279
+ path: "animation.update.maxMarks",
12280
+ code: "INVALID_VALUE",
12281
+ suggestion: "Set animation.update.maxMarks to the largest mark count that should still tween, e.g. animation: { update: { maxMarks: 5000 } }. Omit it to use the default of 500."
12282
+ });
12283
+ }
12284
+ }
12246
12285
  function validateSpec(spec) {
12247
12286
  const errors = [];
12248
12287
  if (!spec || typeof spec !== "object" || Array.isArray(spec)) {
@@ -12351,6 +12390,9 @@ function validateSpec(spec) {
12351
12390
  } else if (isBarList) {
12352
12391
  validateBarListSpec(obj, errors);
12353
12392
  }
12393
+ if (!isGraph) {
12394
+ validateAnimation(obj, errors);
12395
+ }
12354
12396
  if (errors.length > 0) {
12355
12397
  return { valid: false, errors, normalized: null };
12356
12398
  }
@@ -15317,11 +15359,12 @@ function compileLayer(spec, options, compileChart2) {
15317
15359
  const singleSpec = buildPrimarySpec(leaves, spec);
15318
15360
  return compileChart2(singleSpec, options);
15319
15361
  }
15362
+ const layerOptions = { ...options, layered: true };
15320
15363
  if (spec.resolve?.scale?.y === "independent") {
15321
- return compileLayerIndependent(leaves, spec, options, compileChart2);
15364
+ return compileLayerIndependent(leaves, spec, layerOptions, compileChart2);
15322
15365
  }
15323
15366
  const primarySpec = buildPrimarySpec(leaves, spec);
15324
- const primaryLayout = compileChart2(primarySpec, options);
15367
+ const primaryLayout = compileChart2(primarySpec, layerOptions);
15325
15368
  const allMarks = [];
15326
15369
  const seenLabels = /* @__PURE__ */ new Set();
15327
15370
  const pLegend = primaryLayout.legend;
@@ -15334,7 +15377,7 @@ function compileLayer(spec, options, compileChart2) {
15334
15377
  zIndex: leaf.zIndex ?? i
15335
15378
  }));
15336
15379
  indexedLeaves.sort((a, b) => a.zIndex - b.zIndex);
15337
- const leafOptions = { ...options, frozenChartArea: primaryLayout.area };
15380
+ const leafOptions = { ...layerOptions, frozenChartArea: primaryLayout.area };
15338
15381
  const sharedDomains = computeSharedDomains(leaves);
15339
15382
  for (const { leaf } of indexedLeaves) {
15340
15383
  const themedLeaf = {
@@ -15939,6 +15982,34 @@ function collectContrastWarnings(scales, markType, theme) {
15939
15982
  return warnings;
15940
15983
  }
15941
15984
 
15985
+ // src/compiler/mark-render-mode.ts
15986
+ var AUTO_CANVAS_THRESHOLD = 1e3;
15987
+ var AUTO_ENABLED = true;
15988
+ function resolveMarkRenderMode(args, warnings = []) {
15989
+ const { markDef, markType, pointCount, display, faceted, layered } = args;
15990
+ const requested = markDef?.render ?? "auto";
15991
+ if (requested === "svg") return "svg";
15992
+ const refusal = describeRefusal(markType, display, faceted, layered);
15993
+ if (refusal) {
15994
+ if (requested === "canvas") {
15995
+ warnings.push(
15996
+ `Chart warning: mark.render "canvas" is not supported ${refusal}; rendering marks as SVG instead.`
15997
+ );
15998
+ }
15999
+ return "svg";
16000
+ }
16001
+ if (requested === "canvas") return "canvas";
16002
+ if (!AUTO_ENABLED) return "svg";
16003
+ return pointCount > AUTO_CANVAS_THRESHOLD ? "canvas" : "svg";
16004
+ }
16005
+ function describeRefusal(markType, display, faceted, layered) {
16006
+ if (markType !== "point") return `for ${markType} marks (point marks only)`;
16007
+ if (faceted) return "on faceted charts";
16008
+ if (layered) return "on layered charts";
16009
+ if (display === "sparkline") return "on sparklines";
16010
+ return void 0;
16011
+ }
16012
+
15942
16013
  // src/compiler/sparkline-defaults.ts
15943
16014
  var TREND_DEADBAND = 5e-4;
15944
16015
  function computeTrend(values) {
@@ -23633,6 +23704,19 @@ function compileChart(spec, optionsInput) {
23633
23704
  const renderer = getChartRenderer(rendererKey);
23634
23705
  const marks = renderer ? renderer(renderSpec, scales, chartArea, strategy, theme, options.width) : [];
23635
23706
  applyFillPatterns(marks, chartSpec.markDef, theme);
23707
+ const markRenderModeWarnings = [];
23708
+ const markRenderMode = resolveMarkRenderMode(
23709
+ {
23710
+ markDef: chartSpec.markDef,
23711
+ markType: chartSpec.markType,
23712
+ pointCount: marks.reduce((n, m) => m.type === "point" ? n + 1 : n, 0),
23713
+ display: chartSpec.display,
23714
+ faceted: false,
23715
+ layered: options.layered ?? false
23716
+ },
23717
+ markRenderModeWarnings
23718
+ );
23719
+ emitSpecWarnings(markRenderModeWarnings, options.onWarn);
23636
23720
  const xFieldForYdi = chartSpec.encoding.x && "field" in chartSpec.encoding.x ? chartSpec.encoding.x.field : void 0;
23637
23721
  const youDrawIt = resolveYouDrawIt(chartSpec.youDrawIt, marks, scales, chartArea, xFieldForYdi);
23638
23722
  const endpointLabels = computeEndpointLabels(
@@ -23734,6 +23818,7 @@ function compileChart(spec, optionsInput) {
23734
23818
  a11y: {
23735
23819
  altText,
23736
23820
  dataTableFallback,
23821
+ ...chartSpec.data.length > MAX_SR_TABLE_ROWS ? { totalRows: chartSpec.data.length } : {},
23737
23822
  role: "img",
23738
23823
  keyboardNavigable: marks.length > 0,
23739
23824
  ...chartSpec.a11y?.hidden ? { hidden: true } : {}
@@ -23752,7 +23837,8 @@ function compileChart(spec, optionsInput) {
23752
23837
  crosshair,
23753
23838
  measureText: options.measureText,
23754
23839
  ...xInvert ? { xInvert } : {},
23755
- ...yInvert ? { yInvert } : {}
23840
+ ...yInvert ? { yInvert } : {},
23841
+ ...markRenderMode === "canvas" ? { markRenderMode } : {}
23756
23842
  };
23757
23843
  }
23758
23844
  function compileFaceted(chartSpec, renderSpec, facetChannel, facetDirection, options, theme, strategy, watermark, resolvedAnimation, crosshair, rawSpec, optionsInput) {
@@ -24007,6 +24093,7 @@ function compileFaceted(chartSpec, renderSpec, facetChannel, facetDirection, opt
24007
24093
  a11y: {
24008
24094
  altText,
24009
24095
  dataTableFallback,
24096
+ ...chartSpec.data.length > MAX_SR_TABLE_ROWS ? { totalRows: chartSpec.data.length } : {},
24010
24097
  role: "img",
24011
24098
  keyboardNavigable: allMarks.length > 0,
24012
24099
  ...chartSpec.a11y?.hidden ? { hidden: true } : {}