@lionad/cx-comps-tanstack-charts 0.1.0-alpha.8 → 0.1.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.mjs CHANGED
@@ -1,10 +1,10 @@
1
1
  import { define } from "@lionad/cx-definition";
2
- import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, mergeProps, normalizeClass, normalizeStyle, onBeforeUnmount, onMounted, openBlock, renderList, unref, useAttrs } from "vue";
2
+ import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, mergeProps, normalizeClass, normalizeStyle, onBeforeUnmount, onMounted, openBlock, ref, renderList, unref, useAttrs, useId, watch } from "vue";
3
3
  import { Chart } from "@tanstack/charts/vue";
4
4
  import { useCxBEM } from "@lionad/cx-vue";
5
- import { areaX, areaY, arrow, bandX, bandY, barX, barY, binX, binXY, binY, boxRows, boxX, boxY, cell, colorGradientLegend, colorLegend, cumulative, d3Curve, defaultChartTheme, defineChart, delta, deviation, differenceX, differenceY, dodgeX, dodgeY, dot, first, fold, frame, group, groupBy, hexagon, last, lineX, lineY, linearRegressionRowsX, linearRegressionRowsY, linearRegressionX, linearRegressionY, link, median, mosaicX, mosaicY, normalize, quantile, rank, ratio, rect, ridgelineX, ridgelineY, rollingWindow, ruleX, ruleY, select, stack, stackRowsX, stackRowsY, text, tickX, tickY, variance, vector, violinX, violinY, waffleX, waffleY, waterfall } from "@tanstack/charts";
5
+ import { areaX, areaY, arrow, bandX, bandY, barX, barY, binX, binXY, binY, boxRows, boxX, boxY, cell, colorGradientLegend, colorLegend, colorLegendItems, cumulative, d3Curve, defaultChartTheme, defineChart, delta, deviation, differenceX, differenceY, dodgeX, dodgeY, dot, first, fold, frame, group, groupBy, hexagon, last, lineX, lineY, linearRegressionRowsX, linearRegressionRowsY, linearRegressionX, linearRegressionY, link, median, mosaicX, mosaicY, normalize, quantile, rank, ratio, rect, ridgelineX, ridgelineY, rollingWindow, ruleX, ruleY, select, stack, stackRowsX, stackRowsY, text, tickX, tickY, variance, vector, violinX, violinY, waffleX, waffleY, waterfall } from "@tanstack/charts";
6
6
  import { d3AreaXCurve } from "@tanstack/charts/d3/area-x";
7
- import { curveBasis, curveCatmullRom, curveLinear, curveLinearClosed, curveMonotoneX, curveNatural, curveStep, curveStepAfter, curveStepBefore } from "d3-shape";
7
+ import { curveBasis, curveBumpX, curveCatmullRom, curveLinear, curveLinearClosed, curveMonotoneX, curveNatural, curveStep, curveStepAfter, curveStepBefore } from "d3-shape";
8
8
  import { scaleLinear } from "@tanstack/charts/scales/linear";
9
9
  import { scaleOrdinal } from "@tanstack/charts/scales/ordinal";
10
10
  import { scalePoint } from "@tanstack/charts/scales/point";
@@ -23,11 +23,16 @@ import { treeLayout } from "@tanstack/charts/hierarchy/tree";
23
23
  import { treemap } from "@tanstack/charts/hierarchy/treemap";
24
24
  import { forceLayout } from "@tanstack/charts/network/force";
25
25
  import { sankeyDiagram } from "@tanstack/charts/network/sankey";
26
- import { geoAlbersUsa, geoEqualEarth, geoIdentity, geoMercator, geoNaturalEarth1, geoOrthographic } from "d3-geo";
26
+ import { geoAlbersUsa, geoEqualEarth, geoEquirectangular, geoIdentity, geoMercator, geoNaturalEarth1, geoOrthographic } from "d3-geo";
27
+ import { decorative } from "@tanstack/charts/mark/decorative";
27
28
  import { tooltip } from "@tanstack/charts/tooltip";
29
+ import { viewGrid } from "@tanstack/charts/view";
30
+ import "@tanstack/charts/adapter";
31
+ import { createChartRendererAdapter } from "@tanstack/charts/adapter/renderer";
32
+ import { motion } from "@tanstack/charts/motion";
28
33
  import { compileTrigger, createTriggerRegistry } from "@lionad/cx-stream";
29
34
  //#region src/shared/chart-skeleton.vue
30
- const _sfc_main$6 = /*@__PURE__*/ defineComponent({
35
+ const _sfc_main$8 = /*@__PURE__*/ defineComponent({
31
36
  __name: "chart-skeleton",
32
37
  props: { height: { default: 320 } },
33
38
  setup(__props) {
@@ -118,6 +123,85 @@ const _sfc_main$6 = /*@__PURE__*/ defineComponent({
118
123
  }
119
124
  });
120
125
  //#endregion
126
+ //#region src/shared/chart-empty.vue
127
+ const _sfc_main$7 = /*@__PURE__*/ defineComponent({
128
+ __name: "chart-empty",
129
+ props: { height: { default: 320 } },
130
+ setup(__props) {
131
+ const props = __props;
132
+ const ns = useCxBEM("tanstack-charts-chart-empty");
133
+ const normalizedHeight = computed(() => Number.isFinite(props.height) && props.height > 0 ? props.height : 320);
134
+ const shellStyle = computed(() => ({
135
+ display: "flex",
136
+ flexDirection: "column",
137
+ alignItems: "center",
138
+ justifyContent: "center",
139
+ gap: "0.375rem",
140
+ width: "100%",
141
+ boxSizing: "border-box",
142
+ height: `${normalizedHeight.value}px`,
143
+ border: "1px dashed rgba(148, 163, 184, 0.35)",
144
+ borderRadius: "0.75rem",
145
+ color: "rgba(148, 163, 184, 0.9)",
146
+ textAlign: "center"
147
+ }));
148
+ const iconStyle = {
149
+ width: "2rem",
150
+ height: "2rem",
151
+ opacity: .7
152
+ };
153
+ const titleStyle = {
154
+ margin: 0,
155
+ fontSize: "0.875rem",
156
+ fontWeight: 500
157
+ };
158
+ const descStyle = {
159
+ margin: 0,
160
+ fontSize: "0.75rem",
161
+ opacity: .75
162
+ };
163
+ return (_ctx, _cache) => {
164
+ return openBlock(), createElementBlock(Fragment, null, [createCommentVNode(" 图表终态空态:数据集占位/不可渲染时替换 Chart 占位(空壳 svg 无反馈等同未渲染)。\n 全 inline style:保持包零 css 分发契约(同 chart-skeleton 模式) "), createElementVNode("div", {
165
+ class: normalizeClass(unref(ns).b()),
166
+ style: normalizeStyle(shellStyle.value),
167
+ "data-testid": "cx-tanstack-charts-chart-empty",
168
+ role: "status"
169
+ }, [
170
+ (openBlock(), createElementBlock("svg", {
171
+ style: iconStyle,
172
+ viewBox: "0 0 24 24",
173
+ fill: "none",
174
+ stroke: "currentColor",
175
+ "stroke-width": "1.5",
176
+ "aria-hidden": "true"
177
+ }, [..._cache[0] || (_cache[0] = [
178
+ createElementVNode("circle", {
179
+ cx: "12",
180
+ cy: "12",
181
+ r: "9"
182
+ }, null, -1),
183
+ createElementVNode("line", {
184
+ x1: "12",
185
+ y1: "7.5",
186
+ x2: "12",
187
+ y2: "13",
188
+ "stroke-linecap": "round"
189
+ }, null, -1),
190
+ createElementVNode("circle", {
191
+ cx: "12",
192
+ cy: "16.5",
193
+ r: "0.9",
194
+ fill: "currentColor",
195
+ stroke: "none"
196
+ }, null, -1)
197
+ ])])),
198
+ createElementVNode("p", { style: titleStyle }, "图表数据不可用"),
199
+ createElementVNode("p", { style: descStyle }, "数据集中缺少可渲染的数据,已跳过绘制")
200
+ ], 6)], 2112);
201
+ };
202
+ }
203
+ });
204
+ //#endregion
121
205
  //#region src/shared/streaming.ts
122
206
  /**
123
207
  * 读取 trigger 注入的流式骨架标记(_cx_streaming,下划线前缀键不会透传物料)。
@@ -140,16 +224,20 @@ const HOST_PROP_KEYS = [
140
224
  "className"
141
225
  ];
142
226
  /**
143
- * 命名数据集白名单(GenUI 数据顶层化契约):
144
- * rows 恒为主数据集(流式 trigger arrayKey),nodes/links 承载关系型图双数据集;
145
- * 物料 data 顶层与 definition 平级的这三个数组字段分馏进 datasets 表,
146
- * 供 marks 内字符串引用解析。非数组值静默忽略(流式半值防御)。
227
+ * 命名数据集分馏(GenUI 数据顶层化契约):
228
+ * 物料 data 顶层与 definition 平级的数组字段全量进 datasets 表——rows 恒为主
229
+ * 数据集(流式 trigger arrayKey),其余按语义自由命名:关系型图 nodes/links、
230
+ * geo 的 sphere/graticule/land、分层图 innerRows/outerRows、仪表 bandRows 等。
231
+ * 非数组值静默忽略(流式半值防御)。
232
+ * 排除集防串味:宿主 props(class/style Vue 绑定可为数组)、cx 内部键
233
+ * (comp 运行时节点、data-* 编辑标记、_ 前缀编辑器键)不是数据集。
147
234
  */
148
- const DATASET_KEYS = [
149
- "rows",
150
- "nodes",
151
- "links"
152
- ];
235
+ const DATASET_EXCLUDE_KEYS = /* @__PURE__ */ new Set([
236
+ "definition",
237
+ "comp",
238
+ "class",
239
+ "style"
240
+ ]);
153
241
  /**
154
242
  * 分馏 <Chart> 宿主 props:标量白名单 + class/style + ariaLabel 回退。
155
243
  *
@@ -184,9 +272,11 @@ function useChartProps(attrs) {
184
272
  spec: computed(() => attrs.definition ?? { marks: [] }),
185
273
  datasets: computed(() => {
186
274
  const table = {};
187
- for (const key of DATASET_KEYS) {
188
- const value = attrs[key];
189
- if (Array.isArray(value)) table[key] = value;
275
+ for (const [key, value] of Object.entries(attrs)) {
276
+ if (!Array.isArray(value)) continue;
277
+ if (DATASET_EXCLUDE_KEYS.has(key)) continue;
278
+ if (key.startsWith("data-") || key.startsWith("_")) continue;
279
+ table[key] = value;
190
280
  }
191
281
  return table;
192
282
  }),
@@ -283,6 +373,7 @@ function translateAxisPresentation(axis) {
283
373
  if (axis === false) return false;
284
374
  const presentation = {};
285
375
  if (axis.label !== void 0) presentation.label = axis.label;
376
+ if (axis.line !== void 0) presentation.line = axis.line;
286
377
  if (axis.ticks !== void 0) {
287
378
  const ticks = {};
288
379
  if (axis.ticks.count !== void 0) ticks.count = axis.ticks.count;
@@ -310,6 +401,80 @@ function translateAxis(spec, fallbackKind) {
310
401
  return axis;
311
402
  }
312
403
  //#endregion
404
+ //#region src/shared/translate/geometry.ts
405
+ /**
406
+ * 占位几何检测:LLM 在数据集缺席时可能产出 geometry 为占位字符串的示意 spec
407
+ * (如 "…(命名数据集)…"),或只引用不提供数据集(resolveMarkData 回退空数组)。
408
+ * 翻译层与渲染层对这些形态容错回退,占位字符串则被库静默忽略——都渲染为空壳
409
+ * svg 且无任何反馈。本模块提供「渲染必然空壳」的判定与翻译期警告,让空壳显式化。
410
+ *
411
+ * 流式安全性:占位字符串只可能出现在完整闭合的 JSON 中(流式截断停在字符层,
412
+ * 截断帧不可解析即不会产出 spec),检测不存在流式中间态误报面。
413
+ */
414
+ /** 数据集为 GeoJSON Feature 数组(rows 平铺 features 与命名 geo 数据集同形态) */
415
+ function isFeatureArray(rows) {
416
+ return rows.length > 0 && rows.every((f) => f?.type === "Feature");
417
+ }
418
+ /**
419
+ * 单数据集整体不可渲染判定:Feature 数组且全部条目几何缺失或为占位字符串。
420
+ * 混合场景(部分真实几何)不算——部分数据仍可渲染,整体空态会误杀;
421
+ * geometry: null 是 GeoJSON 合法形态,但全部条目皆 null 时渲染结果同为空壳。
422
+ */
423
+ function isPlaceholderGeometryDataset(rows) {
424
+ if (!isFeatureArray(rows)) return false;
425
+ return rows.every((f) => {
426
+ const geometry = f.geometry;
427
+ return geometry == null || typeof geometry === "string";
428
+ });
429
+ }
430
+ /**
431
+ * 组件层空态判定:整图渲染必然空壳时为 true。
432
+ *
433
+ * 判定范围收敛在纯 geoShape 图(全部 marks 均为 geoShape):geoShape 无独立于数据
434
+ * 的渲染力(数据不可用即零图元),而其余 mark 类型总有独立形状;复合图只要存在
435
+ * 一个非 geoShape mark 或一个可用 geo 数据集就返回 false,防「部分可渲染却整体
436
+ * 空态」的误杀。覆盖三种不可用形态:
437
+ * - 命名数据集占位(geometry 占位字符串/全 null)
438
+ * - 命名数据集缺失(只引用不提供,resolveMarkData 回退空数组)
439
+ * - mark 内联 data 数组本身占位(不占命名数据集表的形态)
440
+ */
441
+ function isPlaceholderChartRender(spec, datasets) {
442
+ const marks = spec?.marks;
443
+ if (!marks?.length) return false;
444
+ if (marks.some((mark) => mark.type !== "geoShape")) return false;
445
+ return marks.every((mark) => {
446
+ const rows = typeof mark.data === "string" ? datasets?.[mark.data] : mark.data;
447
+ if (rows === void 0 || rows === null) return true;
448
+ if (!Array.isArray(rows)) return false;
449
+ if (rows.length === 0) return true;
450
+ return isPlaceholderGeometryDataset(rows);
451
+ });
452
+ }
453
+ /** 翻译期警告去重:数组引用(占位)与数据集名(缺失)各自只警告一次 */
454
+ const warnedRows = /* @__PURE__ */ new WeakSet();
455
+ const warnedMissing = /* @__PURE__ */ new Set();
456
+ /**
457
+ * 翻译期占位几何警告:数据集整体占位时 console.warn 指出空壳根因与修复方向。
458
+ * 只挂在 geoShape(占位字符串的高发地——示意 spec 的外部轮廓数据集);普通 mark
459
+ * 的数据集在途是流式常态,warn 会成噪音。WeakSet 按引用去重,数据集内容更新
460
+ * (新引用)视为新数据集重新警告。
461
+ */
462
+ function warnPlaceholderGeometry(rows, datasetName, markType) {
463
+ if (!isPlaceholderGeometryDataset(rows) || warnedRows.has(rows)) return;
464
+ warnedRows.add(rows);
465
+ console.warn(`[cx-charts] ${markType} 数据集${datasetName ? ` "${datasetName}"` : ""} 的 geometry 全部缺失或为占位字符串,渲染为空壳——须以真实 GeoJSON 数据集整体替换示意占位(技能侧见「数据供给」表)。`);
466
+ }
467
+ /**
468
+ * 翻译期数据集缺失警告:geoShape 按名引用了数据集表中不存在的键——与占位字符串
469
+ * 同为「渲染空壳无反馈」的事故形态,区别只在根因(未提供 vs 提供了占位)。
470
+ * 按名去重(缺失数据集无内容引用可作 WeakSet 键),命名数据集到达后不再命中。
471
+ */
472
+ function warnMissingGeometryDataset(datasetName, markType) {
473
+ if (warnedMissing.has(datasetName)) return;
474
+ warnedMissing.add(datasetName);
475
+ console.warn(`[cx-charts] ${markType} 引用的数据集 "${datasetName}" 不存在,渲染为空壳——数据集须随物料 data 顶层提供(GenUI 数据顶层化契约)。`);
476
+ }
477
+ //#endregion
313
478
  //#region src/shared/translate/transforms.ts
314
479
  /**
315
480
  * 数据预处理管道翻译:spec 顶层 transforms 按序执行,产物以 name 注册进数据集表。
@@ -519,7 +684,6 @@ const SCALAR_KEYS = [
519
684
  "positiveFillOpacity",
520
685
  "negativeFillOpacity",
521
686
  "comparisonStroke",
522
- "cornerRadius",
523
687
  "padAngle",
524
688
  "radiusOffset",
525
689
  "baseline",
@@ -587,7 +751,7 @@ function translateFrame(spec) {
587
751
  }
588
752
  /** channel 运行时校验:只接受字段名字符串(accessor 函数 JSON 不可表达) */
589
753
  function assertChannels(spec) {
590
- const constantKeys = spec.type === "ruleX" ? /* @__PURE__ */ new Set(["x"]) : spec.type === "ruleY" ? /* @__PURE__ */ new Set(["y"]) : void 0;
754
+ const constantKeys = spec.type === "ruleX" ? /* @__PURE__ */ new Set(["x"]) : spec.type === "ruleY" ? /* @__PURE__ */ new Set(["y"]) : spec.type === "rect" ? /* @__PURE__ */ new Set(["x1", "y1"]) : void 0;
591
755
  for (const key of CHANNEL_KEYS) {
592
756
  const value = spec[key];
593
757
  if (value !== void 0 && typeof value !== "string") {
@@ -597,7 +761,9 @@ function assertChannels(spec) {
597
761
  }
598
762
  for (const key of CHANNEL_FLEX_KEYS) {
599
763
  const value = spec[key];
600
- if (value !== void 0 && typeof value !== "string" && typeof value !== "number") throw new Error(`translateMark: channel "${key}" 只支持字段名或数值,收到 ${typeof value}`);
764
+ if (value !== void 0 && typeof value !== "string" && typeof value !== "number") {
765
+ if (!(Array.isArray(value) && value.length === 4 && value.every((n) => typeof n === "number" && Number.isFinite(n)))) throw new Error(`translateMark: channel "${key}" 只支持字段名、数值或四角数组,收到 ${typeof value}`);
766
+ }
601
767
  }
602
768
  }
603
769
  /**
@@ -611,6 +777,21 @@ function resolveMarkData(data, datasets) {
611
777
  if (typeof data !== "string") return data;
612
778
  return datasets?.[data] ?? [];
613
779
  }
780
+ /** 缺省半径像素上下界(sqrt 面积映射):小值保可见、大值防巨泡覆盖全图 */
781
+ const DEFAULT_R_RANGE = [2.5, 14];
782
+ /**
783
+ * rScale 注入:库缺省 rScale 是恒等映射(字段原始值直接当像素半径,大数据值
784
+ * 渲染为巨泡)——r 为字段名时缺省注入 sqrt 映射(半径∝√值 ⇒ 面积∝值,气泡图
785
+ * 感知编码约定),domain 由库从数据推断(includeZero)。数值常量 r 是显式像素
786
+ * 意图不缩放;spec.rScale.range 覆盖缺省上下界。
787
+ * 工厂形态(无 copy 的函数)让库推断 domain;实例形态库不推断(scale-input 契约)。
788
+ * 返回 undefined 表示不注入(消费方判空挂 options)。
789
+ */
790
+ function resolveRScaleOption(spec) {
791
+ if (typeof spec.r !== "string") return void 0;
792
+ const range = spec.rScale?.range ?? DEFAULT_R_RANGE;
793
+ return { scale: () => scaleSqrt().range([range[0], range[1]]) };
794
+ }
614
795
  /** layout 声明式 → mark layout 工厂(stack/group/dodge 挂 mark.layout 字段) */
615
796
  function translateLayout(spec) {
616
797
  switch (spec.kind) {
@@ -668,10 +849,18 @@ function translateMark(spec, datasets) {
668
849
  if (!factory) throw new Error(`translateMark: 未知 mark type "${String(spec.type)}"`);
669
850
  assertChannels(spec);
670
851
  const options = {};
671
- for (const key of CHANNEL_KEYS) if (spec[key] !== void 0) options[key] = spec[key];
852
+ for (const key of CHANNEL_KEYS) {
853
+ const value = spec[key];
854
+ if (value === void 0) continue;
855
+ options[key] = typeof value === "number" ? () => value : value;
856
+ }
857
+ if (options.key === void 0) options.key = (_datum, { index }) => index;
672
858
  for (const key of CHANNEL_FLEX_KEYS) if (spec[key] !== void 0) options[key] = spec[key];
859
+ const rScale = resolveRScaleOption(spec);
860
+ if (rScale !== void 0) options.rScale = rScale;
673
861
  for (const key of STYLE_KEYS) if (spec[key] !== void 0) options[key] = spec[key];
674
862
  for (const key of SCALAR_KEYS) if (spec[key] !== void 0) options[key] = spec[key];
863
+ if ((spec.type === "barX" || spec.type === "barY") && spec.cornerRadius !== void 0 && spec.radius === void 0) options.radius = spec.cornerRadius;
675
864
  if (spec.id !== void 0) options.id = spec.id;
676
865
  if (spec.curve !== void 0) options.curve = spec.type === "violinY" ? translateAreaXCurve(spec.curve) : translateCurve(spec.curve);
677
866
  if (spec.layout !== void 0) options.layout = translateLayout(spec.layout);
@@ -755,22 +944,46 @@ function translatePolarGuide(spec) {
755
944
  "shape",
756
945
  "labels",
757
946
  "labelAngle",
758
- "labelOffset"
947
+ "labelOffset",
948
+ "id",
949
+ "className",
950
+ "labelClassName",
951
+ "stroke",
952
+ "strokeOpacity",
953
+ "strokeWidth",
954
+ "strokeDasharray",
955
+ "fill",
956
+ "fillOpacity",
957
+ "labelFill",
958
+ "labelFontSize",
959
+ "labelAnchor",
960
+ "labelBaseline",
961
+ "labelDx",
962
+ "labelDy",
963
+ "labelRotate"
759
964
  ]) if (spec[key] !== void 0) options[key] = spec[key];
760
965
  return factory(options);
761
966
  }
762
967
  /** polar axis 声明式(复用 scale 枚举;wrap 仅 angle 轴)→ PolarAngleOptions/PolarRadiusOptions */
763
- function translatePolarAxis(spec) {
968
+ function translatePolarAxis(spec, { allowRange = false } = {}) {
764
969
  if (!spec) return void 0;
765
970
  const axis = {};
766
971
  if (spec.scale) axis.scale = translateScale(spec.scale);
767
972
  if (spec.nice !== void 0) axis.nice = spec.nice;
973
+ if (allowRange && spec.range !== void 0) {
974
+ const [r0, r1] = spec.range;
975
+ if (!Number.isFinite(r0) || !Number.isFinite(r1) || r0 < 0 || r1 < r0) throw new Error(`translatePolar: radiusAxis.range 须为有限非负且递增的比例对,收到 [${r0}, ${r1}]`);
976
+ axis.range = [ratioToLength(r0), ratioToLength(r1)];
977
+ }
768
978
  return axis;
769
979
  }
770
980
  /**
771
981
  * polar 容器:{ type:'polar', marks:[radialXxx...], angleAxis, radiusAxis, radiusRatio,
772
982
  * startAngle, endAngle, inset, polarGuides } → polar({...})。
773
983
  * extraMarks:调用方预组装的 PolarMark(sunburst 等自包含层级 mark 不走 radial 直译)。
984
+ * 0.18 契约:angle/radius scale 配置收进容器级 options.scales 保留通道
985
+ * (resolvePolarLayout 读 options.scales 的 reserved angle/radius 条目),
986
+ * 不再是顶层 angle/radius 字段;definition.scales 只需 x/y null 占位。
774
987
  */
775
988
  function translatePolar(spec, datasets, extraMarks = []) {
776
989
  const options = { marks: [...(spec.marks ?? []).map((m) => translateRadialMark(m, datasets)), ...extraMarks] };
@@ -779,12 +992,28 @@ function translatePolar(spec, datasets, extraMarks = []) {
779
992
  if (spec.endAngle !== void 0) options.endAngle = spec.endAngle;
780
993
  if (spec.inset !== void 0) options.inset = spec.inset;
781
994
  if (spec.id !== void 0) options.id = spec.id;
995
+ const MATERIALIZING_TYPES = /* @__PURE__ */ new Set([
996
+ "radialBarRadius",
997
+ "radialBarAngle",
998
+ "radialLine",
999
+ "radialArea",
1000
+ "radialText",
1001
+ "radialRule",
1002
+ "radialDot"
1003
+ ]);
1004
+ const marksSpecs = spec.marks ?? [];
1005
+ const hasRadialBarRadius = marksSpecs.some((m) => m.type === "radialBarRadius");
1006
+ const hasRadialBarAngle = marksSpecs.some((m) => m.type === "radialBarAngle");
1007
+ const materializesChannel = marksSpecs.some((m) => MATERIALIZING_TYPES.has(m.type));
782
1008
  const angle = translatePolarAxis(spec.angleAxis);
783
- const radius = translatePolarAxis(spec.radiusAxis);
784
- const hasRadialBarRadius = (spec.marks ?? []).some((m) => m.type === "radialBarRadius");
785
- const hasRadialBarAngle = (spec.marks ?? []).some((m) => m.type === "radialBarAngle");
786
- options.angle = angle ?? { scale: () => hasRadialBarRadius ? scaleBand() : scalePoint() };
787
- options.radius = radius ?? { scale: hasRadialBarAngle ? () => scaleBand() : scaleLinear };
1009
+ const radius = translatePolarAxis(spec.radiusAxis, { allowRange: true });
1010
+ options.scales = materializesChannel ? {
1011
+ angle: angle ?? { scale: () => hasRadialBarRadius ? scaleBand() : scalePoint() },
1012
+ radius: radius ?? { scale: hasRadialBarAngle ? () => scaleBand() : scaleLinear }
1013
+ } : {
1014
+ angle: angle ?? null,
1015
+ radius: radius ?? null
1016
+ };
788
1017
  if (spec.polarGuides?.length) options.guides = spec.polarGuides.map(translatePolarGuide);
789
1018
  return polar(options);
790
1019
  }
@@ -850,6 +1079,27 @@ function hierarchyOptions(spec, idKey) {
850
1079
  };
851
1080
  throw new Error(`translateComposite: ${spec.type} 层级数据源必须声明 path 或 nodeId+parentId`);
852
1081
  }
1082
+ /**
1083
+ * 层级布局节点的字段名通道(treemap/sunburst 的 color/z、tree 节点 dot 的 color/r):
1084
+ * 库的 channelValues 在布局节点上平查 datum[field],而 flatHierarchyNodeContext 把源行
1085
+ * 收纳在 data 下不展开——字段名物化为 data 取值 accessor,保持「字段名引用源数据」契约。
1086
+ * sankey 不在此列:其 marks 回调 channel 按设计锁定布局行字段(key/width 等 canonical)。
1087
+ */
1088
+ function hierarchyFieldChannel(field) {
1089
+ return (node) => node?.data?.[field];
1090
+ }
1091
+ /**
1092
+ * sankey 布局行顶层只有 canonical 字段(key/width/坐标),原 datum 收纳在 .data 下;
1093
+ * 声明式样式字段名(如 stroke:"色调"、color:"id")须穿透到原 datum 解析,
1094
+ * 否则 channel 按顶层查表得 undefined 而静默退回默认样式。
1095
+ */
1096
+ function sankeyDatumField(field) {
1097
+ return (row) => {
1098
+ const record = row;
1099
+ if (record.data != null && field in record.data) return record.data[field];
1100
+ return record[field];
1101
+ };
1102
+ }
853
1103
  /** sankey:固化 marks 回调 = link(布局连线) + rect(布局节点),样式取自声明 */
854
1104
  function translateSankey(spec, datasets) {
855
1105
  const nodes = resolveMarkData(spec.nodes, datasets);
@@ -862,25 +1112,30 @@ function translateSankey(spec, datasets) {
862
1112
  source: spec.source,
863
1113
  target: spec.target,
864
1114
  value: spec.value,
865
- marks: (context) => [looseLink(context.links, {
866
- x1: "x1",
867
- y1: "y1",
868
- x2: "x2",
869
- y2: "y2",
870
- key: "key",
871
- strokeWidth: "width",
872
- lineCap: "butt",
873
- stroke: spec.stroke ?? void 0,
874
- strokeOpacity: spec.strokeOpacity ?? .55
875
- }), looseRect(context.nodes, {
876
- x1: "x0",
877
- x2: "x1",
878
- y1: "y0",
879
- y2: "y1",
880
- key: "key",
881
- color: spec.color ?? "key",
882
- fillOpacity: spec.fillOpacity ?? void 0
883
- })]
1115
+ marks: (context) => {
1116
+ const strokeField = typeof spec.stroke === "string" && context.links.some((l) => l?.data?.[spec.stroke] !== void 0) ? spec.stroke : null;
1117
+ return [looseLink(context.links, {
1118
+ x1: "x1",
1119
+ y1: "y1",
1120
+ x2: "x2",
1121
+ y2: "y2",
1122
+ key: "key",
1123
+ strokeWidth: (row) => Math.max(1, Number(row.width) || 1),
1124
+ lineCap: "butt",
1125
+ curve: d3Curve(curveBumpX),
1126
+ color: strokeField ? sankeyDatumField(strokeField) : void 0,
1127
+ stroke: strokeField ? void 0 : spec.stroke ?? void 0,
1128
+ strokeOpacity: spec.strokeOpacity ?? .55
1129
+ }), looseRect(context.nodes, {
1130
+ x1: "x0",
1131
+ x2: "x1",
1132
+ y1: "y0",
1133
+ y2: "y1",
1134
+ key: "key",
1135
+ color: typeof spec.color === "string" ? sankeyDatumField(spec.color) : spec.color ?? "key",
1136
+ fillOpacity: spec.fillOpacity ?? void 0
1137
+ })];
1138
+ }
884
1139
  };
885
1140
  if (spec.id !== void 0) options.id = spec.id;
886
1141
  if (spec.align !== void 0) options.align = spec.align;
@@ -900,8 +1155,8 @@ function translateSunburst(spec, datasets) {
900
1155
  value: spec.value
901
1156
  };
902
1157
  if (spec.id !== void 0) options.id = spec.id;
903
- if (spec.color !== void 0) options.color = spec.color;
904
- if (spec.z !== void 0) options.z = spec.z;
1158
+ if (spec.color !== void 0) options.color = hierarchyFieldChannel(spec.color);
1159
+ if (spec.z !== void 0) options.z = hierarchyFieldChannel(spec.z);
905
1160
  if (spec.visibleDepth !== void 0) options.visibleDepth = spec.visibleDepth;
906
1161
  if (spec.ringPadding !== void 0) options.ringPadding = spec.ringPadding;
907
1162
  for (const key of [
@@ -931,7 +1186,7 @@ function translateTreemap(spec, datasets) {
931
1186
  value: spec.value
932
1187
  };
933
1188
  if (spec.id !== void 0) options.id = spec.id;
934
- if (spec.color !== void 0) options.color = spec.color;
1189
+ if (spec.color !== void 0) options.color = hierarchyFieldChannel(spec.color);
935
1190
  if (spec.method !== void 0) options.method = spec.method;
936
1191
  if (spec.ratio !== void 0) options.ratio = spec.ratio;
937
1192
  if (spec.round !== void 0) options.round = spec.round;
@@ -957,6 +1212,7 @@ function translateTree(spec, datasets) {
957
1212
  if (spec.orientation !== void 0) options.orientation = spec.orientation;
958
1213
  if (spec.nodeSize !== void 0) options.nodeSize = spec.nodeSize;
959
1214
  const layout = treeLayout(rows, options);
1215
+ const nodeRScale = resolveRScaleOption(spec);
960
1216
  return [looseLink(layout.links, {
961
1217
  x1: "x1",
962
1218
  y1: "y1",
@@ -970,9 +1226,10 @@ function translateTree(spec, datasets) {
970
1226
  x: "x",
971
1227
  y: "y",
972
1228
  key: "id",
973
- r: spec.r ?? 4.5,
974
- color: spec.color ?? void 0,
975
- strokeWidth: 1.5
1229
+ r: typeof spec.r === "string" ? hierarchyFieldChannel(spec.r) : spec.r ?? 4.5,
1230
+ color: spec.color === void 0 ? void 0 : hierarchyFieldChannel(spec.color),
1231
+ strokeWidth: 1.5,
1232
+ ...nodeRScale !== void 0 ? { rScale: nodeRScale } : {}
976
1233
  })];
977
1234
  }
978
1235
  /** 缺省力集:官方示例常规四力的常量形态(函数 accessor 不可 JSON,由库缺省接管) */
@@ -1012,6 +1269,7 @@ function translateForceGraph(spec, datasets) {
1012
1269
  if (spec.iterations !== void 0) options.iterations = spec.iterations;
1013
1270
  if (spec.domainPadding !== void 0) options.domainPadding = spec.domainPadding;
1014
1271
  const graph = forceLayout(nodes, links, options);
1272
+ const nodeRScale = resolveRScaleOption(spec);
1015
1273
  return {
1016
1274
  marks: [looseLink(graph.links, {
1017
1275
  x1: "x1",
@@ -1027,36 +1285,49 @@ function translateForceGraph(spec, datasets) {
1027
1285
  key: spec.nodeKey,
1028
1286
  color: spec.color ?? void 0,
1029
1287
  r: spec.r ?? 5,
1030
- strokeWidth: 1.5
1288
+ strokeWidth: 1.5,
1289
+ ...nodeRScale !== void 0 ? { rScale: nodeRScale } : {}
1031
1290
  })],
1032
1291
  xDomain: [graph.xDomain[0], graph.xDomain[1]],
1033
1292
  yDomain: [graph.yDomain[0], graph.yDomain[1]]
1034
1293
  };
1035
1294
  }
1036
- /** d3-geo 投影名称枚举 → 工厂(descriptor.type 形态:() => projection) */
1295
+ /** d3-geo 投影名称枚举 → 工厂(descriptor.type 形态:() => projection);
1296
+ * 等距圆柱必须用 equirectangular 而非 identity:identity 的 stream 无 sphere 方法,
1297
+ * fit/渲染 Sphere 几何(球面描边层)直接抛 TypeError */
1037
1298
  const GEO_PROJECTION_FACTORIES = {
1038
1299
  mercator: geoMercator,
1039
1300
  orthographic: geoOrthographic,
1040
1301
  naturalEarth1: geoNaturalEarth1,
1041
1302
  albersUsa: geoAlbersUsa,
1042
1303
  equalEarth: geoEqualEarth,
1304
+ equirectangular: geoEquirectangular,
1043
1305
  identity: geoIdentity
1044
1306
  };
1045
- /** geoShape:projection 名称枚举 → descriptor {type, fit, inset};fit:'data' 由库拟合数据本体 */
1307
+ /** geoShape:projection 名称枚举 → descriptor {type, fit, inset};fit:'data' 由库拟合数据本体,{data:'<name>'} 拟合另一命名数据集 */
1046
1308
  function translateGeoShape(spec, datasets) {
1047
1309
  const rows = resolveMarkData(spec.data, datasets);
1310
+ const datasetName = typeof spec.data === "string" ? spec.data : void 0;
1311
+ if (datasetName && rows.length === 0 && !datasets?.[datasetName]) warnMissingGeometryDataset(datasetName, "geoShape");
1312
+ else warnPlaceholderGeometry(rows, datasetName, "geoShape");
1048
1313
  const projectionName = spec.projection ?? "mercator";
1314
+ if (typeof projectionName === "object") throw new Error("translateGeoShape: projection {$by} 仅允许出现在 facet chart 子模板内");
1049
1315
  const factory = GEO_PROJECTION_FACTORIES[projectionName];
1050
1316
  if (!factory) throw new Error(`translateGeoShape: 未知投影 "${String(projectionName)}"`);
1051
1317
  const options = { projection: {
1052
1318
  type: factory,
1053
- fit: spec.fit ?? "data",
1319
+ fit: typeof spec.fit === "object" && spec.fit !== null ? {
1320
+ type: "FeatureCollection",
1321
+ features: resolveMarkData(spec.fit.data, datasets)
1322
+ } : spec.fit ?? "data",
1054
1323
  ...spec.inset !== void 0 ? { inset: spec.inset } : {}
1055
1324
  } };
1056
1325
  if (spec.id !== void 0) options.id = spec.id;
1057
1326
  if (spec.key !== void 0) options.key = spec.key;
1058
1327
  if (spec.color !== void 0) options.color = spec.color;
1059
1328
  if (spec.r !== void 0) options.r = spec.r;
1329
+ const rScale = resolveRScaleOption(spec);
1330
+ if (rScale !== void 0) options.rScale = rScale;
1060
1331
  for (const key of [
1061
1332
  "fill",
1062
1333
  "fillOpacity",
@@ -1069,6 +1340,25 @@ function translateGeoShape(spec, datasets) {
1069
1340
  return geoShape(rows, options);
1070
1341
  }
1071
1342
  /**
1343
+ * facet 子模板按组实例化:深度遍历,把 {$by:{组名:值}} 形态字段替换为当前组映射值。
1344
+ * 未声明 $by 的字段原样保留;$by 表未命中当前组时抛错(静默回退会让分面悄悄同参)。
1345
+ */
1346
+ function instantiateFacetTemplate(node, groupKey) {
1347
+ if (Array.isArray(node)) return node.map((item) => instantiateFacetTemplate(item, groupKey));
1348
+ if (node && typeof node === "object") {
1349
+ const record = node;
1350
+ const keys = Object.keys(record);
1351
+ if (keys.length === 1 && keys[0] === "$by") {
1352
+ const table = record.$by;
1353
+ const hit = table[String(groupKey)];
1354
+ if (hit === void 0) throw new Error(`instantiateFacetTemplate: $by 映射未命中分组 "${String(groupKey)}"(可用键: ${Object.keys(table).join(", ")})`);
1355
+ return hit;
1356
+ }
1357
+ return Object.fromEntries(Object.entries(record).map(([k, v]) => [k, instantiateFacetTemplate(v, groupKey)]));
1358
+ }
1359
+ return node;
1360
+ }
1361
+ /**
1072
1362
  * facet:chart 回调固化为递归子 spec 翻译——translateChild 由 definition.ts 注入
1073
1363
  * (子 spec 翻译与顶层同路径,仅数据集换为 facet 分组行;规避 composite ↔ definition 循环 import)。
1074
1364
  * label 语义:true 显示分组 key 标签(库的函数形态不可 JSON,布尔即可覆盖声明式场景)。
@@ -1078,9 +1368,10 @@ function translateFacet(spec, datasets, translateChild) {
1078
1368
  if (!spec.by) throw new Error("translateFacet: facet 必须声明 by 分组字段");
1079
1369
  if (!spec.chart) throw new Error("translateFacet: facet 必须声明 chart 子 spec 模板");
1080
1370
  const childTemplate = spec.chart;
1371
+ const byField = spec.by;
1081
1372
  const options = {
1082
- by: spec.by,
1083
- chart: (facetRows) => translateChild(childTemplate, facetRows)
1373
+ by: byField,
1374
+ chart: (facetRows) => translateChild(instantiateFacetTemplate(childTemplate, facetRows[0]?.[byField]), facetRows)
1084
1375
  };
1085
1376
  if (spec.id !== void 0) options.id = spec.id;
1086
1377
  if (spec.columns !== void 0) options.columns = spec.columns;
@@ -1117,6 +1408,31 @@ function translateCompositeMark(spec, datasets, translateChild) {
1117
1408
  }
1118
1409
  }
1119
1410
  //#endregion
1411
+ //#region src/shared/translate/view.ts
1412
+ function translateViewGrid(spec, datasets, hooks) {
1413
+ const grid = spec.views;
1414
+ const views = grid.items.map((item) => {
1415
+ if (item.chart.views !== void 0) throw new Error(`translateViewGrid: 子视图 "${item.id}" 不支持嵌套 views`);
1416
+ return {
1417
+ id: item.id,
1418
+ row: item.row,
1419
+ column: item.column,
1420
+ ...item.share ? { share: item.share } : {},
1421
+ ...item.align ? { align: item.align } : {},
1422
+ chart: hooks.buildChild(item.chart, datasets)
1423
+ };
1424
+ });
1425
+ const composed = viewGrid({
1426
+ rows: grid.rows,
1427
+ columns: grid.columns,
1428
+ ...grid.gap !== void 0 ? { gap: grid.gap } : {},
1429
+ ...grid.rowGap !== void 0 ? { rowGap: grid.rowGap } : {},
1430
+ ...grid.columnGap !== void 0 ? { columnGap: grid.columnGap } : {},
1431
+ views
1432
+ });
1433
+ return hooks.applyHost(composed, spec);
1434
+ }
1435
+ //#endregion
1120
1436
  //#region src/shared/translate/definition.ts
1121
1437
  /**
1122
1438
  * definition 组装:声明式 spec → DomChartDefinition(可直接喂给 <Chart>)。
@@ -1125,12 +1441,19 @@ function translateCompositeMark(spec, datasets, translateChild) {
1125
1441
  * - transforms 管道先于 marks 执行,产物注册进数据集表副本(marks 字符串引用在此解析)
1126
1442
  * - polar 族(polar/pie/sunburst 全为 polar 容器且无直角 channel)未显式声明 x/y 时
1127
1443
  * 置 null 显式无轴(库对 polar mark 的 scale 类型为 never,缺省 axis 注入会抛错)
1444
+ * - 纯 geoShape 场景同理置 null(geo mark 不物化直角 scale channel,缺省轴只剩
1445
+ * 0-1 无意义刻度;混合直角 channel mark 时保留缺省轴)
1128
1446
  * - tree/forceGraph 展开为多 mark;forceGraph 回传的轴域在 x/y 未显式声明时注入
1129
1447
  * linear scale 实例(仿真坐标以 0 为中心,缺省推断 domain 不包含负半轴余量)
1448
+ * - spec.views 存在时分流 viewGrid 多视图组合(views 与 marks 互斥),各子视图以
1449
+ * child 模式递归构建——host 权属字段(tooltip/pointer/keyboard/focus/focusRing)
1450
+ * 归外层组合定义持有(官方 assertChildDefinition 拒绝子 view 携带),child 模式
1451
+ * 下显式声明即抛错;theme 仅携带 spec 差异(defaultChartTheme.background 会触发
1452
+ * 库拒绝,外层 mergeTheme 负责补齐缺省)
1130
1453
  */
1131
1454
  /** color.legend 声明 → 图例实例;true 等价 { kind:'color' } 缺省配置 */
1132
1455
  function translateLegend(legend) {
1133
- const { kind, label, placement, itemWidth, steps, width } = legend === true ? {} : legend;
1456
+ const { kind, label, placement, itemWidth, steps, width, items: itemSpec } = legend === true ? {} : legend;
1134
1457
  if (kind === "gradient") {
1135
1458
  const gradient = {};
1136
1459
  if (label !== void 0) gradient.label = label;
@@ -1144,6 +1467,15 @@ function translateLegend(legend) {
1144
1467
  if (placement !== void 0) items.placement = placement;
1145
1468
  if (itemWidth !== void 0) items.itemWidth = itemWidth;
1146
1469
  if (width !== void 0) items.width = width;
1470
+ if (itemSpec !== void 0) {
1471
+ const itemOptions = {};
1472
+ if (itemSpec.justify !== void 0) itemOptions.justify = itemSpec.justify;
1473
+ if (itemSpec.gap !== void 0) itemOptions.gap = itemSpec.gap;
1474
+ if (itemSpec.rowGap !== void 0) itemOptions.rowGap = itemSpec.rowGap;
1475
+ if (itemSpec.indicator !== void 0) itemOptions.indicator = itemSpec.indicator;
1476
+ if (itemSpec.label !== void 0) itemOptions.label = itemSpec.label;
1477
+ items.items = colorLegendItems(itemOptions);
1478
+ }
1147
1479
  return colorLegend(items);
1148
1480
  }
1149
1481
  /**
@@ -1163,7 +1495,8 @@ function translateTooltip(tooltip$1) {
1163
1495
  "visibility",
1164
1496
  "anchor",
1165
1497
  "sort",
1166
- "items"
1498
+ "items",
1499
+ "className"
1167
1500
  ]) if (tooltip$1[key] !== void 0) options[key] = tooltip$1[key];
1168
1501
  return {
1169
1502
  use: tooltip,
@@ -1188,7 +1521,7 @@ function coerceTemporalChannels(spec, table) {
1188
1521
  if (!set) fieldsByDataset.set(name, set = /* @__PURE__ */ new Set());
1189
1522
  set.add(field);
1190
1523
  };
1191
- for (const mark of spec.marks) {
1524
+ for (const mark of spec.marks ?? []) {
1192
1525
  if (xTemporal) for (const f of [
1193
1526
  "x",
1194
1527
  "x1",
@@ -1220,57 +1553,69 @@ function coerceTemporalChannels(spec, table) {
1220
1553
  return changed ? next : table;
1221
1554
  }
1222
1555
  /**
1223
- * 单 spec → definition 核心(facet 子 spec 复用):返回原始 spec 对象而非
1224
- * defineChart 产物——facet chart 回调要求返回 ChartSpec 字面量形态。
1556
+ * 单 spec → definition 核心(facet 子 spec、viewGrid 子视图复用):返回原始 spec
1557
+ * 对象而非 defineChart 产物——facet chart 回调要求返回 ChartSpec 字面量形态。
1225
1558
  * rowsOverride:facet 分组行注入(子 spec marks 未声明 data 时绑定分组行)。
1559
+ * mode 'child':viewGrid 子视图构建——剥除 host 权属字段(见文件头约定)。
1226
1560
  */
1227
- function buildDefinition(spec, datasets, rowsOverride) {
1561
+ function buildDefinition(spec, datasets, rowsOverride, mode = "host") {
1228
1562
  const base = { ...datasets };
1229
1563
  if (rowsOverride !== void 0) base.rows = rowsOverride;
1230
1564
  const table = coerceTemporalChannels(spec, spec.transforms?.length ? applyTransforms(spec.transforms, base) : base);
1565
+ const markSpecs = spec.marks ?? [];
1231
1566
  const marks = [];
1232
1567
  let derivedXDomain;
1233
1568
  let derivedYDomain;
1234
- let polarOnly = spec.marks.length > 0;
1235
- for (const mark of spec.marks) {
1569
+ let polarOnly = markSpecs.length > 0;
1570
+ let geoOnly = markSpecs.length > 0;
1571
+ const pushMark = (specMark, runtimeMark) => {
1572
+ marks.push(specMark.decorative === true ? decorative(runtimeMark) : runtimeMark);
1573
+ };
1574
+ for (const mark of spec.marks ?? []) {
1236
1575
  const bound = rowsOverride !== void 0 && mark.data === void 0 ? {
1237
1576
  ...mark,
1238
1577
  data: "rows"
1239
1578
  } : mark;
1240
1579
  if (!POLAR_FAMILY_TYPES.has(bound.type)) polarOnly = false;
1241
- if (bound.type === "polar") marks.push(translatePolar(bound, table));
1242
- else if (bound.type === "pie") marks.push(translatePie(bound, table));
1580
+ if (bound.type !== "geoShape") geoOnly = false;
1581
+ if (bound.type === "polar") pushMark(bound, translatePolar(bound, table));
1582
+ else if (bound.type === "pie") pushMark(bound, translatePie(bound, table));
1243
1583
  else if (COMPOSITE_TYPES.has(bound.type)) {
1244
- const result = translateCompositeMark(bound, table, (child, rows) => buildDefinition(child, void 0, rows));
1245
- marks.push(...result.marks);
1584
+ const result = translateCompositeMark(bound, table, (child, rows) => buildDefinition(child, table, rows));
1585
+ for (const expanded of result.marks) pushMark(bound, expanded);
1246
1586
  if (result.xDomain) derivedXDomain = result.xDomain;
1247
1587
  if (result.yDomain) derivedYDomain = result.yDomain;
1248
- } else marks.push(translateMark(bound, table));
1588
+ } else pushMark(bound, translateMark(bound, table));
1249
1589
  }
1250
- const definition = {
1590
+ if (mode === "child" && spec.theme?.background !== void 0) throw new Error("translateViewGrid 子视图不得声明 theme.background(库拒绝子图持有场景背景;背景用子图内普通 mark 表达)");
1591
+ if (mode === "child" && spec.gradients !== void 0) throw new Error("translateViewGrid 子视图不得声明 gradients(host 权属渐变资源,配置在外层 spec)");
1592
+ const theme = mode === "child" ? spec.theme === void 0 ? void 0 : { ...spec.theme } : {
1593
+ ...defaultChartTheme,
1594
+ ...spec.theme
1595
+ };
1596
+ const definition = theme === void 0 ? { marks } : {
1251
1597
  marks,
1252
- theme: {
1253
- ...defaultChartTheme,
1254
- ...spec.theme
1255
- }
1598
+ theme
1256
1599
  };
1257
- const xSpec = spec.x === void 0 && polarOnly ? null : spec.x;
1258
- const ySpec = spec.y === void 0 && polarOnly ? null : spec.y;
1600
+ const axisless = polarOnly || geoOnly;
1601
+ const xSpec = spec.x === void 0 && axisless ? null : spec.x;
1602
+ const ySpec = spec.y === void 0 && axisless ? null : spec.y;
1603
+ const scales = {};
1259
1604
  const x = translateAxis(xSpec ?? void 0, "point");
1260
1605
  const y = translateAxis(ySpec ?? void 0, "linear");
1261
- if (xSpec !== null && x) definition.x = x;
1262
- if (xSpec === null) definition.x = null;
1263
- if (ySpec !== null && y) definition.y = y;
1264
- if (ySpec === null) definition.y = null;
1265
- if (derivedXDomain && spec.x?.scale?.domain === void 0) definition.x = {
1266
- ...definition.x,
1606
+ scales.x = xSpec === null ? null : x;
1607
+ scales.y = ySpec === null ? null : y;
1608
+ definition.scales = scales;
1609
+ if (derivedXDomain && spec.x?.scale?.domain === void 0) scales.x = {
1610
+ ...x,
1267
1611
  scale: scaleLinear().domain(derivedXDomain)
1268
1612
  };
1269
- if (derivedYDomain && spec.y?.scale?.domain === void 0) definition.y = {
1270
- ...definition.y,
1613
+ if (derivedYDomain && spec.y?.scale?.domain === void 0) scales.y = {
1614
+ ...y,
1271
1615
  scale: scaleLinear().domain(derivedYDomain)
1272
1616
  };
1273
1617
  if (spec.margin !== void 0) definition.margin = spec.margin;
1618
+ if (spec.gradients !== void 0) definition.gradients = spec.gradients;
1274
1619
  if (spec.guides !== void 0) definition.guides = spec.guides;
1275
1620
  else if (polarOnly) definition.guides = false;
1276
1621
  if (spec.clip !== void 0) definition.clip = spec.clip;
@@ -1281,10 +1626,27 @@ function buildDefinition(spec, datasets, rowsOverride) {
1281
1626
  if (spec.color.legend !== void 0) color.legend = translateLegend(spec.color.legend);
1282
1627
  definition.color = color;
1283
1628
  }
1629
+ if (mode === "child") {
1630
+ for (const key of [
1631
+ "tooltip",
1632
+ "pointer",
1633
+ "keyboard",
1634
+ "focusRing",
1635
+ "focus",
1636
+ "motion"
1637
+ ]) if (spec[key] !== void 0) throw new Error(`translateViewGrid 子视图不得声明 "${key}"(host 权属字段,配置在外层 spec)`);
1638
+ return definition;
1639
+ }
1284
1640
  if (spec.pointer !== void 0) definition.pointer = spec.pointer;
1285
1641
  if (spec.keyboard !== void 0) definition.keyboard = spec.keyboard;
1286
1642
  if (spec.focusRing !== void 0) definition.focusRing = spec.focusRing;
1287
1643
  if (spec.focus !== void 0) definition.focus = spec.focus;
1644
+ if (spec.motion !== void 0) {
1645
+ const timing = {};
1646
+ if (spec.motion.transition !== void 0) timing.transition = spec.motion.transition;
1647
+ if (spec.motion.delay !== void 0) timing.delay = spec.motion.delay;
1648
+ definition.motion = timing;
1649
+ }
1288
1650
  const tooltip$2 = spec.tooltip === void 0 ? tooltip : translateTooltip(spec.tooltip);
1289
1651
  if (tooltip$2 !== void 0) definition.tooltip = tooltip$2;
1290
1652
  return definition;
@@ -1294,9 +1656,98 @@ function buildDefinition(spec, datasets, rowsOverride) {
1294
1656
  * datasets:命名数据集表(数据顶层化契约),marks 内字符串引用在此解析。
1295
1657
  */
1296
1658
  function translateChartSpec(spec, datasets) {
1659
+ if (spec.views !== void 0) {
1660
+ if (spec.marks?.length) throw new Error("translateChartSpec: views 与 marks 互斥——viewGrid 顶层不直接绘制 marks,图元属于各子视图");
1661
+ return defineChart(translateViewGrid(spec, datasets, {
1662
+ buildChild: (child, table) => defineChart(buildDefinition(child, table, void 0, "child")),
1663
+ applyHost: (composed, outer) => {
1664
+ const hosted = {
1665
+ ...composed,
1666
+ theme: {
1667
+ ...defaultChartTheme,
1668
+ ...outer.theme
1669
+ }
1670
+ };
1671
+ const tooltip$3 = outer.tooltip === void 0 ? tooltip : translateTooltip(outer.tooltip);
1672
+ if (tooltip$3 !== void 0) hosted.tooltip = tooltip$3;
1673
+ return hosted;
1674
+ }
1675
+ }));
1676
+ }
1297
1677
  return defineChart(buildDefinition(spec, datasets));
1298
1678
  }
1299
1679
  //#endregion
1680
+ //#region src/chart/src/motion-chart.vue
1681
+ const _hoisted_1 = ["innerHTML"];
1682
+ const _sfc_main$6 = /*@__PURE__*/ defineComponent({
1683
+ __name: "motion-chart",
1684
+ props: {
1685
+ definition: {},
1686
+ ariaLabel: {},
1687
+ motionOptions: {},
1688
+ height: {},
1689
+ width: {},
1690
+ aspectRatio: {},
1691
+ className: {}
1692
+ },
1693
+ setup(__props) {
1694
+ const props = __props;
1695
+ const idPrefix = `ts-chart-${useId().replaceAll(/[^a-zA-Z0-9_-]/g, "")}`;
1696
+ const hostStyle = computed(() => ({
1697
+ position: "relative",
1698
+ width: props.width === void 0 ? "100%" : `${props.width}px`,
1699
+ height: props.height ?? (props.aspectRatio === void 0 ? 320 : void 0),
1700
+ aspectRatio: props.height === void 0 ? props.aspectRatio : void 0
1701
+ }));
1702
+ /** host options 组装:与库 vue adapter toHostOptions 同构,renderer 固定为 motion() */
1703
+ function toHostOptions() {
1704
+ return {
1705
+ definition: props.definition,
1706
+ ariaLabel: props.ariaLabel,
1707
+ height: props.height,
1708
+ width: props.width,
1709
+ aspectRatio: props.aspectRatio,
1710
+ className: props.className,
1711
+ idPrefix,
1712
+ renderer: motion({
1713
+ initial: props.motionOptions?.initial ?? "always",
1714
+ resize: props.motionOptions?.resize
1715
+ })
1716
+ };
1717
+ }
1718
+ const adapter = createChartRendererAdapter(toHostOptions());
1719
+ const initialMarkup = adapter.prerender();
1720
+ const container = ref();
1721
+ onMounted(() => {
1722
+ if (!container.value) return;
1723
+ adapter.update(toHostOptions());
1724
+ adapter.mount(container.value);
1725
+ });
1726
+ watch(() => [
1727
+ props.definition,
1728
+ props.height,
1729
+ props.width,
1730
+ props.aspectRatio
1731
+ ], () => adapter.update(toHostOptions()));
1732
+ onBeforeUnmount(() => adapter.destroy());
1733
+ return (_ctx, _cache) => {
1734
+ return openBlock(), createElementBlock("div", {
1735
+ class: "ts-chart-host",
1736
+ style: normalizeStyle(hostStyle.value)
1737
+ }, [createCommentVNode(" 首帧 markup 只注入一次:mount 后 DOM 由 motion renderer 接管(含动画态),\n 若随重渲染重设 innerHTML 会摧毁动画状态,故用非响应式常量 "), createElementVNode("div", {
1738
+ ref_key: "container",
1739
+ ref: container,
1740
+ class: "ts-chart-surface",
1741
+ style: {
1742
+ "width": "100%",
1743
+ "height": "100%"
1744
+ },
1745
+ innerHTML: unref(initialMarkup)
1746
+ }, null, 8, _hoisted_1)], 4);
1747
+ };
1748
+ }
1749
+ });
1750
+ //#endregion
1300
1751
  //#region src/chart/src/index.vue
1301
1752
  const _sfc_main$5 = /*@__PURE__*/ defineComponent({
1302
1753
  name: "CxTanstackChartsChart",
@@ -1308,18 +1759,28 @@ const _sfc_main$5 = /*@__PURE__*/ defineComponent({
1308
1759
  const { spec, datasets, hostProps, ariaLabel } = useChartProps(attrs);
1309
1760
  const definition = computed(() => translateChartSpec(spec.value, datasets.value));
1310
1761
  const isStreaming = computed(() => streamingFields(attrs).includes("definition"));
1762
+ const hasStreamingFields = computed(() => streamingFields(attrs).length > 0);
1763
+ const hasPlaceholderGeometry = computed(() => !hasStreamingFields.value && isPlaceholderChartRender(spec.value, datasets.value));
1311
1764
  const skeletonHeight = computed(() => {
1312
1765
  const h = Number(attrs.height);
1313
1766
  return Number.isFinite(h) && h > 0 ? h : 320;
1314
1767
  });
1315
1768
  return (_ctx, _cache) => {
1316
- return openBlock(), createElementBlock(Fragment, null, [createCommentVNode(" 库根元素 inheritAttrs:false 且多根(host + tooltip Teleport),testid/BEM 类须由本层 wrapper 承担 "), createElementVNode("div", {
1317
- class: normalizeClass(unref(ns).b()),
1769
+ return openBlock(), createElementBlock(Fragment, null, [createCommentVNode(" 库根元素 inheritAttrs:false 且多根(host + tooltip Teleport),testid/BEM 类须由本层 wrapper 承担。\n cx-charts 为主题桥作用域类(cx-comps charts-theme 映射 design token 到 --ts-chart-N/--chart-N):\n 随组件自动声明,宿主无需手动加类——shadcn 系 spec 的 var(--chart-N, ...) 引用在此获得色板 "), createElementVNode("div", {
1770
+ class: normalizeClass([unref(ns).b(), "cx-charts"]),
1318
1771
  "data-testid": "cx-tanstack-charts-chart"
1319
- }, [createCommentVNode(" definition 未闭合期间骨架占位:空壳帧的空 marks 只渲染不可见空 svg,无反馈等同未渲染 "), isStreaming.value ? (openBlock(), createBlock(_sfc_main$6, {
1772
+ }, [createCommentVNode(" definition 未闭合期间骨架占位:空壳帧的空 marks 只渲染不可见空 svg,无反馈等同未渲染 "), isStreaming.value ? (openBlock(), createBlock(_sfc_main$8, {
1320
1773
  key: 0,
1321
1774
  height: skeletonHeight.value
1322
- }, null, 8, ["height"])) : (openBlock(), createBlock(unref(Chart), mergeProps({ key: 1 }, unref(hostProps), {
1775
+ }, null, 8, ["height"])) : hasPlaceholderGeometry.value ? (openBlock(), createElementBlock(Fragment, { key: 1 }, [createCommentVNode(" 终态占位几何空态:全部 GeoJSON 数据集均占位/缺失时渲染必为空壳,显式降级为空态反馈 "), createVNode(_sfc_main$7, { height: skeletonHeight.value }, null, 8, ["height"])], 2112)) : unref(spec).motion !== void 0 ? (openBlock(), createElementBlock(Fragment, { key: 2 }, [createCommentVNode(" motion 声明走自研挂载分支:库 vue adapter 无 motion renderer 注入口子 "), createVNode(_sfc_main$6, mergeProps({
1776
+ definition: definition.value,
1777
+ ariaLabel: unref(ariaLabel),
1778
+ "motion-options": unref(spec).motion
1779
+ }, unref(hostProps)), null, 16, [
1780
+ "definition",
1781
+ "ariaLabel",
1782
+ "motion-options"
1783
+ ])], 2112)) : (openBlock(), createBlock(unref(Chart), mergeProps({ key: 3 }, unref(hostProps), {
1323
1784
  definition: definition.value,
1324
1785
  "aria-label": unref(ariaLabel)
1325
1786
  }), null, 16, ["definition", "aria-label"]))], 2)], 2112);
@@ -1382,8 +1843,9 @@ const initialRows = () => [
1382
1843
  ];
1383
1844
  var chart_default = define({
1384
1845
  name: "图表",
1385
- description: "TanStack Charts 通用图表(cx-chart):definition 为纯 JSON 声明式投影(marks/x/y/theme/tooltip 标量子集),数据顶层化——rows/nodes/links 命名数据集与 definition 平级、marks 内字符串引用,经翻译层组装渲染;scale/curve 以枚举声明,channel 为字段名。",
1386
- key: "cx-chart",
1846
+ description: "TanStack Charts 通用图表(cx-data-chart):definition 为纯 JSON 声明式投影(marks/x/y/theme/tooltip 标量子集),数据顶层化——rows/nodes/links 命名数据集与 definition 平级、marks 内字符串引用,经翻译层组装渲染;scale/curve 以枚举声明,channel 为字段名。旧 key cx-chart 经 aliasKeys 兼容(GenUI data-charts 轨改名,ADR 对齐),next major 移除。",
1847
+ key: "cx-data-chart",
1848
+ aliasKeys: ["cx-chart"],
1387
1849
  icon: "i-tabler-chart-line",
1388
1850
  component: _sfc_main$5,
1389
1851
  props: {
@@ -1459,8 +1921,8 @@ const _sfc_main$4 = /*@__PURE__*/ defineComponent({
1459
1921
  });
1460
1922
  const definition = computed(() => translateChartSpec(spec.value));
1461
1923
  return (_ctx, _cache) => {
1462
- return openBlock(), createElementBlock(Fragment, null, [createCommentVNode(" 库根元素 inheritAttrs:false 且多根,testid/BEM 类由本层 wrapper 承担 "), createElementVNode("div", {
1463
- class: normalizeClass(unref(ns).b()),
1924
+ return openBlock(), createElementBlock(Fragment, null, [createCommentVNode(" 库根元素 inheritAttrs:false 且多根,testid/BEM 类由本层 wrapper 承担。\n cx-charts 为主题桥作用域类:宿主无需手动加类(charts-theme 映射 design token 到 --ts-chart-N/--chart-N) "), createElementVNode("div", {
1925
+ class: normalizeClass([unref(ns).b(), "cx-charts"]),
1464
1926
  "data-testid": "cx-tanstack-charts-line"
1465
1927
  }, [createVNode(unref(Chart), mergeProps(unref(hostProps), {
1466
1928
  definition: definition.value,
@@ -1597,8 +2059,8 @@ const _sfc_main$3 = /*@__PURE__*/ defineComponent({
1597
2059
  });
1598
2060
  const definition = computed(() => translateChartSpec(spec.value));
1599
2061
  return (_ctx, _cache) => {
1600
- return openBlock(), createElementBlock(Fragment, null, [createCommentVNode(" 库根元素 inheritAttrs:false 且多根,testid/BEM 类由本层 wrapper 承担 "), createElementVNode("div", {
1601
- class: normalizeClass(unref(ns).b()),
2062
+ return openBlock(), createElementBlock(Fragment, null, [createCommentVNode(" 库根元素 inheritAttrs:false 且多根,testid/BEM 类由本层 wrapper 承担。\n cx-charts 为主题桥作用域类:宿主无需手动加类(charts-theme 映射 design token 到 --ts-chart-N/--chart-N) "), createElementVNode("div", {
2063
+ class: normalizeClass([unref(ns).b(), "cx-charts"]),
1602
2064
  "data-testid": "cx-tanstack-charts-bar"
1603
2065
  }, [createVNode(unref(Chart), mergeProps(unref(hostProps), {
1604
2066
  definition: definition.value,
@@ -1681,8 +2143,8 @@ const _sfc_main$2 = /*@__PURE__*/ defineComponent({
1681
2143
  });
1682
2144
  const definition = computed(() => translateChartSpec(spec.value));
1683
2145
  return (_ctx, _cache) => {
1684
- return openBlock(), createElementBlock(Fragment, null, [createCommentVNode(" 库根元素 inheritAttrs:false 且多根,testid/BEM 类由本层 wrapper 承担 "), createElementVNode("div", {
1685
- class: normalizeClass(unref(ns).b()),
2146
+ return openBlock(), createElementBlock(Fragment, null, [createCommentVNode(" 库根元素 inheritAttrs:false 且多根,testid/BEM 类由本层 wrapper 承担。\n cx-charts 为主题桥作用域类:宿主无需手动加类(charts-theme 映射 design token 到 --ts-chart-N/--chart-N) "), createElementVNode("div", {
2147
+ class: normalizeClass([unref(ns).b(), "cx-charts"]),
1686
2148
  "data-testid": "cx-tanstack-charts-area"
1687
2149
  }, [createVNode(unref(Chart), mergeProps(unref(hostProps), {
1688
2150
  definition: definition.value,
@@ -1782,8 +2244,8 @@ const _sfc_main$1 = /*@__PURE__*/ defineComponent({
1782
2244
  });
1783
2245
  const definition = computed(() => translateChartSpec(spec.value));
1784
2246
  return (_ctx, _cache) => {
1785
- return openBlock(), createElementBlock(Fragment, null, [createCommentVNode(" 库根元素 inheritAttrs:false 且多根,testid/BEM 类由本层 wrapper 承担 "), createElementVNode("div", {
1786
- class: normalizeClass(unref(ns).b()),
2247
+ return openBlock(), createElementBlock(Fragment, null, [createCommentVNode(" 库根元素 inheritAttrs:false 且多根,testid/BEM 类由本层 wrapper 承担。\n cx-charts 为主题桥作用域类:宿主无需手动加类(charts-theme 映射 design token 到 --ts-chart-N/--chart-N) "), createElementVNode("div", {
2248
+ class: normalizeClass([unref(ns).b(), "cx-charts"]),
1787
2249
  "data-testid": "cx-tanstack-charts-dot"
1788
2250
  }, [createVNode(unref(Chart), mergeProps(unref(hostProps), {
1789
2251
  definition: definition.value,
@@ -1895,15 +2357,25 @@ function usePieChart(attrs) {
1895
2357
  const clean = rows.filter((row) => row !== null && typeof row === "object" && Number.isFinite(Number(row[valueField])) && Number(row[valueField]) >= 0);
1896
2358
  const slices = pie(clean, { value: (datum) => Number(datum[valueField]) });
1897
2359
  const ratio = typeof attrs.innerRadiusRatio === "number" && Number.isFinite(attrs.innerRadiusRatio) ? Math.min(Math.max(attrs.innerRadiusRatio, 0), .95) : 0;
1898
- return defineChart({ marks: [polar({
1899
- inset: 8,
1900
- marks: [radialArc(slices, {
1901
- color: (datum) => String(datum[nameField]),
1902
- key: (datum) => String(datum[nameField]),
1903
- innerRadius: ratio > 0 ? ({ radius }) => radius * ratio : 0,
1904
- cornerRadius: 2
1905
- })]
1906
- })] });
2360
+ return defineChart({
2361
+ marks: [polar({
2362
+ inset: 8,
2363
+ scales: {
2364
+ angle: null,
2365
+ radius: null
2366
+ },
2367
+ marks: [radialArc(slices, {
2368
+ color: (datum) => String(datum[nameField]),
2369
+ key: (datum) => String(datum[nameField]),
2370
+ innerRadius: ratio > 0 ? ({ radius }) => radius * ratio : 0,
2371
+ cornerRadius: 2
2372
+ })]
2373
+ })],
2374
+ scales: {
2375
+ x: null,
2376
+ y: null
2377
+ }
2378
+ });
1907
2379
  }),
1908
2380
  hostProps,
1909
2381
  ariaLabel
@@ -1919,8 +2391,8 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
1919
2391
  const ns = useCxBEM("tanstack-charts-pie");
1920
2392
  const { definition, hostProps, ariaLabel } = usePieChart(useAttrs());
1921
2393
  return (_ctx, _cache) => {
1922
- return openBlock(), createElementBlock(Fragment, null, [createCommentVNode(" 库根元素 inheritAttrs:false 且多根,testid/BEM 类由本层 wrapper 承担 "), createElementVNode("div", {
1923
- class: normalizeClass(unref(ns).b()),
2394
+ return openBlock(), createElementBlock(Fragment, null, [createCommentVNode(" 库根元素 inheritAttrs:false 且多根,testid/BEM 类由本层 wrapper 承担。\n cx-charts 为主题桥作用域类:宿主无需手动加类(charts-theme 映射 design token 到 --ts-chart-N/--chart-N) "), createElementVNode("div", {
2395
+ class: normalizeClass([unref(ns).b(), "cx-charts"]),
1924
2396
  "data-testid": "cx-tanstack-charts-pie"
1925
2397
  }, [createVNode(unref(Chart), mergeProps(unref(hostProps), {
1926
2398
  definition: unref(definition),
@@ -2000,26 +2472,30 @@ var pie_default = define({
2000
2472
  * arrayKey 恒为 'data'(单层字段名契约);props 声明序通道字段(x/y/curve/
2001
2473
  * name/value/innerRadiusRatio)前置、data 殿后——回放剧本序列化序使增量帧
2002
2474
  * 首行起即携带通道配置,尾随标量(height/ariaLabel)不入增量帧由终帧兜底。
2003
- * - array 1 件:通用 chart 走数据顶层化契约,主数组是顶层 rows(逐行生长);
2004
- * definition 序列化在 rows 前,rows 首行闭合时 definition 必然完整——图表
2005
- * 以完整坐标系挂载、数据逐行生长(三段式:骨架→空坐标→行生长)。
2475
+ * - array 1 件:通用 chart 走通配主数组契约(arrayKey '*')——data 下任意
2476
+ * 数组字段皆为主数组,逐字段按元素边界截断。图表数据形态多元:常规形态
2477
+ * rows、关系型 nodes+links(桑基/力导向)、嵌套环 innerRows+outerRows、
2478
+ * 仪表 bandRows+tickRows、地图 land/sphere/graticule/route,通配使各形态
2479
+ * 统一获得流式效果且新增数据形态无需改触发器。definition 序列化在任何数据
2480
+ * 数组之前,首条数据闭合时坐标系必然完整——图表以完整坐标系挂载、数据逐条
2481
+ * 生长(三段式:骨架→空坐标→数据生长)。多数据集按序列化序接力生长
2482
+ * (如 nodes 全量后 links 逐条),各自按自身传输进度截断、互不牵连。
2006
2483
  * - 不适用 0 件。计数校验:6 array + 0 scalar + 0 不适用(差集派生兜底)。
2007
2484
  *
2008
2485
  * produced 语义(增量提取器对中间态与终态走同一 buildPartial,实证
2009
2486
  * packages/stream/src/core/incremental.ts Step 5):
2010
- * - 出帧只由主数组匹配驱动;extraScanPaths(nodes/links)仅提供元素边界
2011
- * 切分点,不驱动出帧——GenUI 契约据此锁死 rows 恒为主数据集在场
2012
- * (关系型图表 rows=节点行表、links 为边次数据集),无 rows 的 spec 永不
2013
- * 出帧属契约外形态,由生成期校验门拦截。
2014
- * - emptyPassthrough:rows=[] 空数组终态透传节点,由组件空态渲染接管,
2015
- * 防流结束后 pending 永驻。
2487
+ * - 出帧由任一主数组的完整元素驱动;尚无完整元素时保持 pending(骨架期由
2488
+ * 渲染管线承担)。
2489
+ * - emptyPassthrough:任一数据数组闭合为空(如 rows=[])时终态透传节点,
2490
+ * 由组件空态渲染接管,防流结束后 pending 永驻;判定据 parse 结果中的空
2491
+ * 数组字段(截断语义下「闭合的空数组」以 [] 在场、「未开始传输」缺席)。
2016
2492
  *
2017
- * 骨架裁决:不设 skeletonFields——rows 首行闭合即出帧逐行生长,天然有实时
2018
- * 反馈;首行前的 pending 期由渲染管线承担(与预设物料同语义),物料内骨架
2493
+ * 骨架裁决:不设 skeletonFields——首条数据闭合即出帧逐条生长,天然有实时
2494
+ * 反馈;首条前的 pending 期由渲染管线承担(与预设物料同语义),物料内骨架
2019
2495
  * 判定保留兼容上游 _cx_streaming 注入。
2020
2496
  *
2021
2497
  * fallback 从简:渲染链路不过 zod,包装层无模板直访(useAttrs 平铺 +
2022
- * composable 全回退,缺席不 TypeError);rows 缺席的中间态经翻译层
2498
+ * composable 全回退,缺席不 TypeError);数据数组缺席的中间态经翻译层
2023
2499
  * resolveMarkData 回退空数组,translateChartSpec 组装不抛错(增量帧翻译
2024
2500
  * 实证进包内测试)。
2025
2501
  *
@@ -2038,47 +2514,56 @@ function keyOf(key) {
2038
2514
  if (!comp) throw new Error(`tanstack-charts 物料定义缺失: ${key}`);
2039
2515
  return comp._cx_meta.key;
2040
2516
  }
2041
- const TANSTACK_CHARTS_STREAM_TRIGGERS = [{
2042
- key: keyOf("cx-chart"),
2043
- sections: [{
2044
- kind: "array",
2045
- arrayKey: "rows",
2046
- extraScanPaths: [[
2047
- "data",
2048
- "nodes",
2049
- "*"
2050
- ], [
2051
- "data",
2052
- "links",
2053
- "*"
2054
- ]]
2055
- }],
2056
- stateBranch: { emptyPassthrough: true },
2057
- frameStride: 10
2058
- }, ...[
2517
+ const ARRAY_PRESET_KEYS = [
2059
2518
  "cx-tanstack-charts-line",
2060
2519
  "cx-tanstack-charts-bar",
2061
2520
  "cx-tanstack-charts-area",
2062
2521
  "cx-tanstack-charts-dot",
2063
2522
  "cx-tanstack-charts-pie"
2064
- ].map((key) => ({
2065
- key: keyOf(key),
2523
+ ];
2524
+ const CHART_STREAM_TRIGGER = {
2525
+ key: keyOf("cx-data-chart"),
2066
2526
  sections: [{
2067
2527
  kind: "array",
2068
- arrayKey: "data"
2528
+ arrayKey: "*"
2069
2529
  }],
2530
+ stateBranch: { emptyPassthrough: true },
2070
2531
  frameStride: 10
2071
- }))];
2532
+ };
2533
+ const TANSTACK_CHARTS_STREAM_TRIGGERS = [
2534
+ CHART_STREAM_TRIGGER,
2535
+ {
2536
+ ...CHART_STREAM_TRIGGER,
2537
+ key: "cx-chart"
2538
+ },
2539
+ ...ARRAY_PRESET_KEYS.map((key) => ({
2540
+ key: keyOf(key),
2541
+ sections: [{
2542
+ kind: "array",
2543
+ arrayKey: "data"
2544
+ }],
2545
+ frameStride: 10
2546
+ }))
2547
+ ];
2072
2548
  /** 装配 TanStack Charts 物料的 trigger 注册表(size 恒 6) */
2073
2549
  function createTanstackChartsTriggerRegistry() {
2074
2550
  const registry = createTriggerRegistry();
2075
2551
  for (const config of TANSTACK_CHARTS_STREAM_TRIGGERS) registry.register(config.key, compileTrigger(config));
2076
2552
  return registry;
2077
2553
  }
2078
- /** 增量节点的主数组(回放计数展示用);非数组增长型物料或数组缺席时返回 null */
2554
+ /**
2555
+ * 增量节点的主数组(回放计数展示用);非数组增长型物料或数组缺席时返回 null。
2556
+ * 通配主数组形态(arrayKey '*')无单一主数组,取最长数组字段为主数据集
2557
+ * (如桑基的 links 之于 nodes);并列时取序列化序靠前者。
2558
+ */
2079
2559
  function mainArrayOf(node) {
2080
2560
  const section = TANSTACK_CHARTS_STREAM_TRIGGERS.find((c) => c.key === node.key)?.sections.find((s) => s.kind === "array");
2081
2561
  if (!section || section.kind !== "array") return null;
2562
+ if (section.arrayKey === "*") {
2563
+ let longest = null;
2564
+ for (const value of Object.values(node.data ?? {})) if (Array.isArray(value) && (longest === null || value.length > longest.length)) longest = value;
2565
+ return longest;
2566
+ }
2082
2567
  const arr = node.data?.[section.arrayKey];
2083
2568
  return Array.isArray(arr) ? arr : null;
2084
2569
  }
@@ -2099,4 +2584,4 @@ const CxTanstackChartsBundle = {
2099
2584
  materials: [...CxTanstackCharts]
2100
2585
  };
2101
2586
  //#endregion
2102
- export { COMPOSITE_TYPES, CX_CHART_CURVE_NAMES, CxTanstackCharts, area_default as CxTanstackChartsArea, bar_default as CxTanstackChartsBar, CxTanstackChartsBundle, chart_default as CxTanstackChartsChart, dot_default as CxTanstackChartsDot, line_default as CxTanstackChartsLine, pie_default as CxTanstackChartsPie, POLAR_FAMILY_TYPES, TANSTACK_CHARTS_STREAM_TRIGGERS, applyTransforms, createTanstackChartsTriggerRegistry, mainArrayOf, resolveMarkData, translateAxis, translateChartSpec, translateCompositeMark, translateCurve, translateMark, translateOutputs, translatePie, translatePolar, translateReduce, translateScale, translateTransform };
2587
+ export { COMPOSITE_TYPES, CX_CHART_CURVE_NAMES, CxTanstackCharts, area_default as CxTanstackChartsArea, bar_default as CxTanstackChartsBar, CxTanstackChartsBundle, chart_default as CxTanstackChartsChart, dot_default as CxTanstackChartsDot, line_default as CxTanstackChartsLine, pie_default as CxTanstackChartsPie, POLAR_FAMILY_TYPES, TANSTACK_CHARTS_STREAM_TRIGGERS, applyTransforms, createTanstackChartsTriggerRegistry, isPlaceholderChartRender, isPlaceholderGeometryDataset, mainArrayOf, resolveMarkData, resolveRScaleOption, translateAxis, translateChartSpec, translateCompositeMark, translateCurve, translateMark, translateOutputs, translatePie, translatePolar, translateReduce, translateScale, translateTransform, warnMissingGeometryDataset, warnPlaceholderGeometry };