@lionad/cx-comps-tanstack-charts 0.1.0-alpha.9 → 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,12 +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
27
  import { decorative } from "@tanstack/charts/mark/decorative";
28
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";
29
33
  import { compileTrigger, createTriggerRegistry } from "@lionad/cx-stream";
30
34
  //#region src/shared/chart-skeleton.vue
31
- const _sfc_main$6 = /*@__PURE__*/ defineComponent({
35
+ const _sfc_main$8 = /*@__PURE__*/ defineComponent({
32
36
  __name: "chart-skeleton",
33
37
  props: { height: { default: 320 } },
34
38
  setup(__props) {
@@ -119,6 +123,85 @@ const _sfc_main$6 = /*@__PURE__*/ defineComponent({
119
123
  }
120
124
  });
121
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
122
205
  //#region src/shared/streaming.ts
123
206
  /**
124
207
  * 读取 trigger 注入的流式骨架标记(_cx_streaming,下划线前缀键不会透传物料)。
@@ -141,16 +224,20 @@ const HOST_PROP_KEYS = [
141
224
  "className"
142
225
  ];
143
226
  /**
144
- * 命名数据集白名单(GenUI 数据顶层化契约):
145
- * rows 恒为主数据集(流式 trigger arrayKey),nodes/links 承载关系型图双数据集;
146
- * 物料 data 顶层与 definition 平级的这三个数组字段分馏进 datasets 表,
147
- * 供 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-* 编辑标记、_ 前缀编辑器键)不是数据集。
148
234
  */
149
- const DATASET_KEYS = [
150
- "rows",
151
- "nodes",
152
- "links"
153
- ];
235
+ const DATASET_EXCLUDE_KEYS = /* @__PURE__ */ new Set([
236
+ "definition",
237
+ "comp",
238
+ "class",
239
+ "style"
240
+ ]);
154
241
  /**
155
242
  * 分馏 <Chart> 宿主 props:标量白名单 + class/style + ariaLabel 回退。
156
243
  *
@@ -185,9 +272,11 @@ function useChartProps(attrs) {
185
272
  spec: computed(() => attrs.definition ?? { marks: [] }),
186
273
  datasets: computed(() => {
187
274
  const table = {};
188
- for (const key of DATASET_KEYS) {
189
- const value = attrs[key];
190
- 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;
191
280
  }
192
281
  return table;
193
282
  }),
@@ -284,6 +373,7 @@ function translateAxisPresentation(axis) {
284
373
  if (axis === false) return false;
285
374
  const presentation = {};
286
375
  if (axis.label !== void 0) presentation.label = axis.label;
376
+ if (axis.line !== void 0) presentation.line = axis.line;
287
377
  if (axis.ticks !== void 0) {
288
378
  const ticks = {};
289
379
  if (axis.ticks.count !== void 0) ticks.count = axis.ticks.count;
@@ -311,6 +401,80 @@ function translateAxis(spec, fallbackKind) {
311
401
  return axis;
312
402
  }
313
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
314
478
  //#region src/shared/translate/transforms.ts
315
479
  /**
316
480
  * 数据预处理管道翻译:spec 顶层 transforms 按序执行,产物以 name 注册进数据集表。
@@ -520,7 +684,6 @@ const SCALAR_KEYS = [
520
684
  "positiveFillOpacity",
521
685
  "negativeFillOpacity",
522
686
  "comparisonStroke",
523
- "cornerRadius",
524
687
  "padAngle",
525
688
  "radiusOffset",
526
689
  "baseline",
@@ -588,7 +751,7 @@ function translateFrame(spec) {
588
751
  }
589
752
  /** channel 运行时校验:只接受字段名字符串(accessor 函数 JSON 不可表达) */
590
753
  function assertChannels(spec) {
591
- 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;
592
755
  for (const key of CHANNEL_KEYS) {
593
756
  const value = spec[key];
594
757
  if (value !== void 0 && typeof value !== "string") {
@@ -598,7 +761,9 @@ function assertChannels(spec) {
598
761
  }
599
762
  for (const key of CHANNEL_FLEX_KEYS) {
600
763
  const value = spec[key];
601
- 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
+ }
602
767
  }
603
768
  }
604
769
  /**
@@ -612,6 +777,21 @@ function resolveMarkData(data, datasets) {
612
777
  if (typeof data !== "string") return data;
613
778
  return datasets?.[data] ?? [];
614
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
+ }
615
795
  /** layout 声明式 → mark layout 工厂(stack/group/dodge 挂 mark.layout 字段) */
616
796
  function translateLayout(spec) {
617
797
  switch (spec.kind) {
@@ -669,10 +849,18 @@ function translateMark(spec, datasets) {
669
849
  if (!factory) throw new Error(`translateMark: 未知 mark type "${String(spec.type)}"`);
670
850
  assertChannels(spec);
671
851
  const options = {};
672
- 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;
673
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;
674
861
  for (const key of STYLE_KEYS) if (spec[key] !== void 0) options[key] = spec[key];
675
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;
676
864
  if (spec.id !== void 0) options.id = spec.id;
677
865
  if (spec.curve !== void 0) options.curve = spec.type === "violinY" ? translateAreaXCurve(spec.curve) : translateCurve(spec.curve);
678
866
  if (spec.layout !== void 0) options.layout = translateLayout(spec.layout);
@@ -756,22 +944,46 @@ function translatePolarGuide(spec) {
756
944
  "shape",
757
945
  "labels",
758
946
  "labelAngle",
759
- "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"
760
964
  ]) if (spec[key] !== void 0) options[key] = spec[key];
761
965
  return factory(options);
762
966
  }
763
967
  /** polar axis 声明式(复用 scale 枚举;wrap 仅 angle 轴)→ PolarAngleOptions/PolarRadiusOptions */
764
- function translatePolarAxis(spec) {
968
+ function translatePolarAxis(spec, { allowRange = false } = {}) {
765
969
  if (!spec) return void 0;
766
970
  const axis = {};
767
971
  if (spec.scale) axis.scale = translateScale(spec.scale);
768
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
+ }
769
978
  return axis;
770
979
  }
771
980
  /**
772
981
  * polar 容器:{ type:'polar', marks:[radialXxx...], angleAxis, radiusAxis, radiusRatio,
773
982
  * startAngle, endAngle, inset, polarGuides } → polar({...})。
774
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 占位。
775
987
  */
776
988
  function translatePolar(spec, datasets, extraMarks = []) {
777
989
  const options = { marks: [...(spec.marks ?? []).map((m) => translateRadialMark(m, datasets)), ...extraMarks] };
@@ -780,12 +992,28 @@ function translatePolar(spec, datasets, extraMarks = []) {
780
992
  if (spec.endAngle !== void 0) options.endAngle = spec.endAngle;
781
993
  if (spec.inset !== void 0) options.inset = spec.inset;
782
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));
783
1008
  const angle = translatePolarAxis(spec.angleAxis);
784
- const radius = translatePolarAxis(spec.radiusAxis);
785
- const hasRadialBarRadius = (spec.marks ?? []).some((m) => m.type === "radialBarRadius");
786
- const hasRadialBarAngle = (spec.marks ?? []).some((m) => m.type === "radialBarAngle");
787
- options.angle = angle ?? { scale: () => hasRadialBarRadius ? scaleBand() : scalePoint() };
788
- 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
+ };
789
1017
  if (spec.polarGuides?.length) options.guides = spec.polarGuides.map(translatePolarGuide);
790
1018
  return polar(options);
791
1019
  }
@@ -851,6 +1079,27 @@ function hierarchyOptions(spec, idKey) {
851
1079
  };
852
1080
  throw new Error(`translateComposite: ${spec.type} 层级数据源必须声明 path 或 nodeId+parentId`);
853
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
+ }
854
1103
  /** sankey:固化 marks 回调 = link(布局连线) + rect(布局节点),样式取自声明 */
855
1104
  function translateSankey(spec, datasets) {
856
1105
  const nodes = resolveMarkData(spec.nodes, datasets);
@@ -863,25 +1112,30 @@ function translateSankey(spec, datasets) {
863
1112
  source: spec.source,
864
1113
  target: spec.target,
865
1114
  value: spec.value,
866
- marks: (context) => [looseLink(context.links, {
867
- x1: "x1",
868
- y1: "y1",
869
- x2: "x2",
870
- y2: "y2",
871
- key: "key",
872
- strokeWidth: "width",
873
- lineCap: "butt",
874
- stroke: spec.stroke ?? void 0,
875
- strokeOpacity: spec.strokeOpacity ?? .55
876
- }), looseRect(context.nodes, {
877
- x1: "x0",
878
- x2: "x1",
879
- y1: "y0",
880
- y2: "y1",
881
- key: "key",
882
- color: spec.color ?? "key",
883
- fillOpacity: spec.fillOpacity ?? void 0
884
- })]
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
+ }
885
1139
  };
886
1140
  if (spec.id !== void 0) options.id = spec.id;
887
1141
  if (spec.align !== void 0) options.align = spec.align;
@@ -901,8 +1155,8 @@ function translateSunburst(spec, datasets) {
901
1155
  value: spec.value
902
1156
  };
903
1157
  if (spec.id !== void 0) options.id = spec.id;
904
- if (spec.color !== void 0) options.color = spec.color;
905
- 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);
906
1160
  if (spec.visibleDepth !== void 0) options.visibleDepth = spec.visibleDepth;
907
1161
  if (spec.ringPadding !== void 0) options.ringPadding = spec.ringPadding;
908
1162
  for (const key of [
@@ -932,7 +1186,7 @@ function translateTreemap(spec, datasets) {
932
1186
  value: spec.value
933
1187
  };
934
1188
  if (spec.id !== void 0) options.id = spec.id;
935
- if (spec.color !== void 0) options.color = spec.color;
1189
+ if (spec.color !== void 0) options.color = hierarchyFieldChannel(spec.color);
936
1190
  if (spec.method !== void 0) options.method = spec.method;
937
1191
  if (spec.ratio !== void 0) options.ratio = spec.ratio;
938
1192
  if (spec.round !== void 0) options.round = spec.round;
@@ -958,6 +1212,7 @@ function translateTree(spec, datasets) {
958
1212
  if (spec.orientation !== void 0) options.orientation = spec.orientation;
959
1213
  if (spec.nodeSize !== void 0) options.nodeSize = spec.nodeSize;
960
1214
  const layout = treeLayout(rows, options);
1215
+ const nodeRScale = resolveRScaleOption(spec);
961
1216
  return [looseLink(layout.links, {
962
1217
  x1: "x1",
963
1218
  y1: "y1",
@@ -971,9 +1226,10 @@ function translateTree(spec, datasets) {
971
1226
  x: "x",
972
1227
  y: "y",
973
1228
  key: "id",
974
- r: spec.r ?? 4.5,
975
- color: spec.color ?? void 0,
976
- 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 } : {}
977
1233
  })];
978
1234
  }
979
1235
  /** 缺省力集:官方示例常规四力的常量形态(函数 accessor 不可 JSON,由库缺省接管) */
@@ -1013,6 +1269,7 @@ function translateForceGraph(spec, datasets) {
1013
1269
  if (spec.iterations !== void 0) options.iterations = spec.iterations;
1014
1270
  if (spec.domainPadding !== void 0) options.domainPadding = spec.domainPadding;
1015
1271
  const graph = forceLayout(nodes, links, options);
1272
+ const nodeRScale = resolveRScaleOption(spec);
1016
1273
  return {
1017
1274
  marks: [looseLink(graph.links, {
1018
1275
  x1: "x1",
@@ -1028,36 +1285,49 @@ function translateForceGraph(spec, datasets) {
1028
1285
  key: spec.nodeKey,
1029
1286
  color: spec.color ?? void 0,
1030
1287
  r: spec.r ?? 5,
1031
- strokeWidth: 1.5
1288
+ strokeWidth: 1.5,
1289
+ ...nodeRScale !== void 0 ? { rScale: nodeRScale } : {}
1032
1290
  })],
1033
1291
  xDomain: [graph.xDomain[0], graph.xDomain[1]],
1034
1292
  yDomain: [graph.yDomain[0], graph.yDomain[1]]
1035
1293
  };
1036
1294
  }
1037
- /** d3-geo 投影名称枚举 → 工厂(descriptor.type 形态:() => projection) */
1295
+ /** d3-geo 投影名称枚举 → 工厂(descriptor.type 形态:() => projection);
1296
+ * 等距圆柱必须用 equirectangular 而非 identity:identity 的 stream 无 sphere 方法,
1297
+ * fit/渲染 Sphere 几何(球面描边层)直接抛 TypeError */
1038
1298
  const GEO_PROJECTION_FACTORIES = {
1039
1299
  mercator: geoMercator,
1040
1300
  orthographic: geoOrthographic,
1041
1301
  naturalEarth1: geoNaturalEarth1,
1042
1302
  albersUsa: geoAlbersUsa,
1043
1303
  equalEarth: geoEqualEarth,
1304
+ equirectangular: geoEquirectangular,
1044
1305
  identity: geoIdentity
1045
1306
  };
1046
- /** geoShape:projection 名称枚举 → descriptor {type, fit, inset};fit:'data' 由库拟合数据本体 */
1307
+ /** geoShape:projection 名称枚举 → descriptor {type, fit, inset};fit:'data' 由库拟合数据本体,{data:'<name>'} 拟合另一命名数据集 */
1047
1308
  function translateGeoShape(spec, datasets) {
1048
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");
1049
1313
  const projectionName = spec.projection ?? "mercator";
1314
+ if (typeof projectionName === "object") throw new Error("translateGeoShape: projection {$by} 仅允许出现在 facet chart 子模板内");
1050
1315
  const factory = GEO_PROJECTION_FACTORIES[projectionName];
1051
1316
  if (!factory) throw new Error(`translateGeoShape: 未知投影 "${String(projectionName)}"`);
1052
1317
  const options = { projection: {
1053
1318
  type: factory,
1054
- 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",
1055
1323
  ...spec.inset !== void 0 ? { inset: spec.inset } : {}
1056
1324
  } };
1057
1325
  if (spec.id !== void 0) options.id = spec.id;
1058
1326
  if (spec.key !== void 0) options.key = spec.key;
1059
1327
  if (spec.color !== void 0) options.color = spec.color;
1060
1328
  if (spec.r !== void 0) options.r = spec.r;
1329
+ const rScale = resolveRScaleOption(spec);
1330
+ if (rScale !== void 0) options.rScale = rScale;
1061
1331
  for (const key of [
1062
1332
  "fill",
1063
1333
  "fillOpacity",
@@ -1070,6 +1340,25 @@ function translateGeoShape(spec, datasets) {
1070
1340
  return geoShape(rows, options);
1071
1341
  }
1072
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
+ /**
1073
1362
  * facet:chart 回调固化为递归子 spec 翻译——translateChild 由 definition.ts 注入
1074
1363
  * (子 spec 翻译与顶层同路径,仅数据集换为 facet 分组行;规避 composite ↔ definition 循环 import)。
1075
1364
  * label 语义:true 显示分组 key 标签(库的函数形态不可 JSON,布尔即可覆盖声明式场景)。
@@ -1079,9 +1368,10 @@ function translateFacet(spec, datasets, translateChild) {
1079
1368
  if (!spec.by) throw new Error("translateFacet: facet 必须声明 by 分组字段");
1080
1369
  if (!spec.chart) throw new Error("translateFacet: facet 必须声明 chart 子 spec 模板");
1081
1370
  const childTemplate = spec.chart;
1371
+ const byField = spec.by;
1082
1372
  const options = {
1083
- by: spec.by,
1084
- chart: (facetRows) => translateChild(childTemplate, facetRows)
1373
+ by: byField,
1374
+ chart: (facetRows) => translateChild(instantiateFacetTemplate(childTemplate, facetRows[0]?.[byField]), facetRows)
1085
1375
  };
1086
1376
  if (spec.id !== void 0) options.id = spec.id;
1087
1377
  if (spec.columns !== void 0) options.columns = spec.columns;
@@ -1118,6 +1408,31 @@ function translateCompositeMark(spec, datasets, translateChild) {
1118
1408
  }
1119
1409
  }
1120
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
1121
1436
  //#region src/shared/translate/definition.ts
1122
1437
  /**
1123
1438
  * definition 组装:声明式 spec → DomChartDefinition(可直接喂给 <Chart>)。
@@ -1126,12 +1441,19 @@ function translateCompositeMark(spec, datasets, translateChild) {
1126
1441
  * - transforms 管道先于 marks 执行,产物注册进数据集表副本(marks 字符串引用在此解析)
1127
1442
  * - polar 族(polar/pie/sunburst 全为 polar 容器且无直角 channel)未显式声明 x/y 时
1128
1443
  * 置 null 显式无轴(库对 polar mark 的 scale 类型为 never,缺省 axis 注入会抛错)
1444
+ * - 纯 geoShape 场景同理置 null(geo mark 不物化直角 scale channel,缺省轴只剩
1445
+ * 0-1 无意义刻度;混合直角 channel mark 时保留缺省轴)
1129
1446
  * - tree/forceGraph 展开为多 mark;forceGraph 回传的轴域在 x/y 未显式声明时注入
1130
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 负责补齐缺省)
1131
1453
  */
1132
1454
  /** color.legend 声明 → 图例实例;true 等价 { kind:'color' } 缺省配置 */
1133
1455
  function translateLegend(legend) {
1134
- const { kind, label, placement, itemWidth, steps, width } = legend === true ? {} : legend;
1456
+ const { kind, label, placement, itemWidth, steps, width, items: itemSpec } = legend === true ? {} : legend;
1135
1457
  if (kind === "gradient") {
1136
1458
  const gradient = {};
1137
1459
  if (label !== void 0) gradient.label = label;
@@ -1145,6 +1467,15 @@ function translateLegend(legend) {
1145
1467
  if (placement !== void 0) items.placement = placement;
1146
1468
  if (itemWidth !== void 0) items.itemWidth = itemWidth;
1147
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
+ }
1148
1479
  return colorLegend(items);
1149
1480
  }
1150
1481
  /**
@@ -1164,7 +1495,8 @@ function translateTooltip(tooltip$1) {
1164
1495
  "visibility",
1165
1496
  "anchor",
1166
1497
  "sort",
1167
- "items"
1498
+ "items",
1499
+ "className"
1168
1500
  ]) if (tooltip$1[key] !== void 0) options[key] = tooltip$1[key];
1169
1501
  return {
1170
1502
  use: tooltip,
@@ -1189,7 +1521,7 @@ function coerceTemporalChannels(spec, table) {
1189
1521
  if (!set) fieldsByDataset.set(name, set = /* @__PURE__ */ new Set());
1190
1522
  set.add(field);
1191
1523
  };
1192
- for (const mark of spec.marks) {
1524
+ for (const mark of spec.marks ?? []) {
1193
1525
  if (xTemporal) for (const f of [
1194
1526
  "x",
1195
1527
  "x1",
@@ -1221,60 +1553,69 @@ function coerceTemporalChannels(spec, table) {
1221
1553
  return changed ? next : table;
1222
1554
  }
1223
1555
  /**
1224
- * 单 spec → definition 核心(facet 子 spec 复用):返回原始 spec 对象而非
1225
- * defineChart 产物——facet chart 回调要求返回 ChartSpec 字面量形态。
1556
+ * 单 spec → definition 核心(facet 子 spec、viewGrid 子视图复用):返回原始 spec
1557
+ * 对象而非 defineChart 产物——facet chart 回调要求返回 ChartSpec 字面量形态。
1226
1558
  * rowsOverride:facet 分组行注入(子 spec marks 未声明 data 时绑定分组行)。
1559
+ * mode 'child':viewGrid 子视图构建——剥除 host 权属字段(见文件头约定)。
1227
1560
  */
1228
- function buildDefinition(spec, datasets, rowsOverride) {
1561
+ function buildDefinition(spec, datasets, rowsOverride, mode = "host") {
1229
1562
  const base = { ...datasets };
1230
1563
  if (rowsOverride !== void 0) base.rows = rowsOverride;
1231
1564
  const table = coerceTemporalChannels(spec, spec.transforms?.length ? applyTransforms(spec.transforms, base) : base);
1565
+ const markSpecs = spec.marks ?? [];
1232
1566
  const marks = [];
1233
1567
  let derivedXDomain;
1234
1568
  let derivedYDomain;
1235
- let polarOnly = spec.marks.length > 0;
1569
+ let polarOnly = markSpecs.length > 0;
1570
+ let geoOnly = markSpecs.length > 0;
1236
1571
  const pushMark = (specMark, runtimeMark) => {
1237
1572
  marks.push(specMark.decorative === true ? decorative(runtimeMark) : runtimeMark);
1238
1573
  };
1239
- for (const mark of spec.marks) {
1574
+ for (const mark of spec.marks ?? []) {
1240
1575
  const bound = rowsOverride !== void 0 && mark.data === void 0 ? {
1241
1576
  ...mark,
1242
1577
  data: "rows"
1243
1578
  } : mark;
1244
1579
  if (!POLAR_FAMILY_TYPES.has(bound.type)) polarOnly = false;
1580
+ if (bound.type !== "geoShape") geoOnly = false;
1245
1581
  if (bound.type === "polar") pushMark(bound, translatePolar(bound, table));
1246
1582
  else if (bound.type === "pie") pushMark(bound, translatePie(bound, table));
1247
1583
  else if (COMPOSITE_TYPES.has(bound.type)) {
1248
- const result = translateCompositeMark(bound, table, (child, rows) => buildDefinition(child, void 0, rows));
1584
+ const result = translateCompositeMark(bound, table, (child, rows) => buildDefinition(child, table, rows));
1249
1585
  for (const expanded of result.marks) pushMark(bound, expanded);
1250
1586
  if (result.xDomain) derivedXDomain = result.xDomain;
1251
1587
  if (result.yDomain) derivedYDomain = result.yDomain;
1252
1588
  } else pushMark(bound, translateMark(bound, table));
1253
1589
  }
1254
- 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 } : {
1255
1597
  marks,
1256
- theme: {
1257
- ...defaultChartTheme,
1258
- ...spec.theme
1259
- }
1598
+ theme
1260
1599
  };
1261
- const xSpec = spec.x === void 0 && polarOnly ? null : spec.x;
1262
- 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 = {};
1263
1604
  const x = translateAxis(xSpec ?? void 0, "point");
1264
1605
  const y = translateAxis(ySpec ?? void 0, "linear");
1265
- if (xSpec !== null && x) definition.x = x;
1266
- if (xSpec === null) definition.x = null;
1267
- if (ySpec !== null && y) definition.y = y;
1268
- if (ySpec === null) definition.y = null;
1269
- if (derivedXDomain && spec.x?.scale?.domain === void 0) definition.x = {
1270
- ...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,
1271
1611
  scale: scaleLinear().domain(derivedXDomain)
1272
1612
  };
1273
- if (derivedYDomain && spec.y?.scale?.domain === void 0) definition.y = {
1274
- ...definition.y,
1613
+ if (derivedYDomain && spec.y?.scale?.domain === void 0) scales.y = {
1614
+ ...y,
1275
1615
  scale: scaleLinear().domain(derivedYDomain)
1276
1616
  };
1277
1617
  if (spec.margin !== void 0) definition.margin = spec.margin;
1618
+ if (spec.gradients !== void 0) definition.gradients = spec.gradients;
1278
1619
  if (spec.guides !== void 0) definition.guides = spec.guides;
1279
1620
  else if (polarOnly) definition.guides = false;
1280
1621
  if (spec.clip !== void 0) definition.clip = spec.clip;
@@ -1285,10 +1626,27 @@ function buildDefinition(spec, datasets, rowsOverride) {
1285
1626
  if (spec.color.legend !== void 0) color.legend = translateLegend(spec.color.legend);
1286
1627
  definition.color = color;
1287
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
+ }
1288
1640
  if (spec.pointer !== void 0) definition.pointer = spec.pointer;
1289
1641
  if (spec.keyboard !== void 0) definition.keyboard = spec.keyboard;
1290
1642
  if (spec.focusRing !== void 0) definition.focusRing = spec.focusRing;
1291
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
+ }
1292
1650
  const tooltip$2 = spec.tooltip === void 0 ? tooltip : translateTooltip(spec.tooltip);
1293
1651
  if (tooltip$2 !== void 0) definition.tooltip = tooltip$2;
1294
1652
  return definition;
@@ -1298,9 +1656,98 @@ function buildDefinition(spec, datasets, rowsOverride) {
1298
1656
  * datasets:命名数据集表(数据顶层化契约),marks 内字符串引用在此解析。
1299
1657
  */
1300
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
+ }
1301
1677
  return defineChart(buildDefinition(spec, datasets));
1302
1678
  }
1303
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
1304
1751
  //#region src/chart/src/index.vue
1305
1752
  const _sfc_main$5 = /*@__PURE__*/ defineComponent({
1306
1753
  name: "CxTanstackChartsChart",
@@ -1312,18 +1759,28 @@ const _sfc_main$5 = /*@__PURE__*/ defineComponent({
1312
1759
  const { spec, datasets, hostProps, ariaLabel } = useChartProps(attrs);
1313
1760
  const definition = computed(() => translateChartSpec(spec.value, datasets.value));
1314
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));
1315
1764
  const skeletonHeight = computed(() => {
1316
1765
  const h = Number(attrs.height);
1317
1766
  return Number.isFinite(h) && h > 0 ? h : 320;
1318
1767
  });
1319
1768
  return (_ctx, _cache) => {
1320
- return openBlock(), createElementBlock(Fragment, null, [createCommentVNode(" 库根元素 inheritAttrs:false 且多根(host + tooltip Teleport),testid/BEM 类须由本层 wrapper 承担 "), createElementVNode("div", {
1321
- 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"]),
1322
1771
  "data-testid": "cx-tanstack-charts-chart"
1323
- }, [createCommentVNode(" definition 未闭合期间骨架占位:空壳帧的空 marks 只渲染不可见空 svg,无反馈等同未渲染 "), isStreaming.value ? (openBlock(), createBlock(_sfc_main$6, {
1772
+ }, [createCommentVNode(" definition 未闭合期间骨架占位:空壳帧的空 marks 只渲染不可见空 svg,无反馈等同未渲染 "), isStreaming.value ? (openBlock(), createBlock(_sfc_main$8, {
1324
1773
  key: 0,
1325
1774
  height: skeletonHeight.value
1326
- }, 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), {
1327
1784
  definition: definition.value,
1328
1785
  "aria-label": unref(ariaLabel)
1329
1786
  }), null, 16, ["definition", "aria-label"]))], 2)], 2112);
@@ -1386,8 +1843,9 @@ const initialRows = () => [
1386
1843
  ];
1387
1844
  var chart_default = define({
1388
1845
  name: "图表",
1389
- description: "TanStack Charts 通用图表(cx-chart):definition 为纯 JSON 声明式投影(marks/x/y/theme/tooltip 标量子集),数据顶层化——rows/nodes/links 命名数据集与 definition 平级、marks 内字符串引用,经翻译层组装渲染;scale/curve 以枚举声明,channel 为字段名。",
1390
- 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"],
1391
1849
  icon: "i-tabler-chart-line",
1392
1850
  component: _sfc_main$5,
1393
1851
  props: {
@@ -1463,8 +1921,8 @@ const _sfc_main$4 = /*@__PURE__*/ defineComponent({
1463
1921
  });
1464
1922
  const definition = computed(() => translateChartSpec(spec.value));
1465
1923
  return (_ctx, _cache) => {
1466
- return openBlock(), createElementBlock(Fragment, null, [createCommentVNode(" 库根元素 inheritAttrs:false 且多根,testid/BEM 类由本层 wrapper 承担 "), createElementVNode("div", {
1467
- 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"]),
1468
1926
  "data-testid": "cx-tanstack-charts-line"
1469
1927
  }, [createVNode(unref(Chart), mergeProps(unref(hostProps), {
1470
1928
  definition: definition.value,
@@ -1601,8 +2059,8 @@ const _sfc_main$3 = /*@__PURE__*/ defineComponent({
1601
2059
  });
1602
2060
  const definition = computed(() => translateChartSpec(spec.value));
1603
2061
  return (_ctx, _cache) => {
1604
- return openBlock(), createElementBlock(Fragment, null, [createCommentVNode(" 库根元素 inheritAttrs:false 且多根,testid/BEM 类由本层 wrapper 承担 "), createElementVNode("div", {
1605
- 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"]),
1606
2064
  "data-testid": "cx-tanstack-charts-bar"
1607
2065
  }, [createVNode(unref(Chart), mergeProps(unref(hostProps), {
1608
2066
  definition: definition.value,
@@ -1685,8 +2143,8 @@ const _sfc_main$2 = /*@__PURE__*/ defineComponent({
1685
2143
  });
1686
2144
  const definition = computed(() => translateChartSpec(spec.value));
1687
2145
  return (_ctx, _cache) => {
1688
- return openBlock(), createElementBlock(Fragment, null, [createCommentVNode(" 库根元素 inheritAttrs:false 且多根,testid/BEM 类由本层 wrapper 承担 "), createElementVNode("div", {
1689
- 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"]),
1690
2148
  "data-testid": "cx-tanstack-charts-area"
1691
2149
  }, [createVNode(unref(Chart), mergeProps(unref(hostProps), {
1692
2150
  definition: definition.value,
@@ -1786,8 +2244,8 @@ const _sfc_main$1 = /*@__PURE__*/ defineComponent({
1786
2244
  });
1787
2245
  const definition = computed(() => translateChartSpec(spec.value));
1788
2246
  return (_ctx, _cache) => {
1789
- return openBlock(), createElementBlock(Fragment, null, [createCommentVNode(" 库根元素 inheritAttrs:false 且多根,testid/BEM 类由本层 wrapper 承担 "), createElementVNode("div", {
1790
- 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"]),
1791
2249
  "data-testid": "cx-tanstack-charts-dot"
1792
2250
  }, [createVNode(unref(Chart), mergeProps(unref(hostProps), {
1793
2251
  definition: definition.value,
@@ -1899,15 +2357,25 @@ function usePieChart(attrs) {
1899
2357
  const clean = rows.filter((row) => row !== null && typeof row === "object" && Number.isFinite(Number(row[valueField])) && Number(row[valueField]) >= 0);
1900
2358
  const slices = pie(clean, { value: (datum) => Number(datum[valueField]) });
1901
2359
  const ratio = typeof attrs.innerRadiusRatio === "number" && Number.isFinite(attrs.innerRadiusRatio) ? Math.min(Math.max(attrs.innerRadiusRatio, 0), .95) : 0;
1902
- return defineChart({ marks: [polar({
1903
- inset: 8,
1904
- marks: [radialArc(slices, {
1905
- color: (datum) => String(datum[nameField]),
1906
- key: (datum) => String(datum[nameField]),
1907
- innerRadius: ratio > 0 ? ({ radius }) => radius * ratio : 0,
1908
- cornerRadius: 2
1909
- })]
1910
- })] });
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
+ });
1911
2379
  }),
1912
2380
  hostProps,
1913
2381
  ariaLabel
@@ -1923,8 +2391,8 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
1923
2391
  const ns = useCxBEM("tanstack-charts-pie");
1924
2392
  const { definition, hostProps, ariaLabel } = usePieChart(useAttrs());
1925
2393
  return (_ctx, _cache) => {
1926
- return openBlock(), createElementBlock(Fragment, null, [createCommentVNode(" 库根元素 inheritAttrs:false 且多根,testid/BEM 类由本层 wrapper 承担 "), createElementVNode("div", {
1927
- 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"]),
1928
2396
  "data-testid": "cx-tanstack-charts-pie"
1929
2397
  }, [createVNode(unref(Chart), mergeProps(unref(hostProps), {
1930
2398
  definition: unref(definition),
@@ -2004,26 +2472,30 @@ var pie_default = define({
2004
2472
  * arrayKey 恒为 'data'(单层字段名契约);props 声明序通道字段(x/y/curve/
2005
2473
  * name/value/innerRadiusRatio)前置、data 殿后——回放剧本序列化序使增量帧
2006
2474
  * 首行起即携带通道配置,尾随标量(height/ariaLabel)不入增量帧由终帧兜底。
2007
- * - array 1 件:通用 chart 走数据顶层化契约,主数组是顶层 rows(逐行生长);
2008
- * definition 序列化在 rows 前,rows 首行闭合时 definition 必然完整——图表
2009
- * 以完整坐标系挂载、数据逐行生长(三段式:骨架→空坐标→行生长)。
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 逐条),各自按自身传输进度截断、互不牵连。
2010
2483
  * - 不适用 0 件。计数校验:6 array + 0 scalar + 0 不适用(差集派生兜底)。
2011
2484
  *
2012
2485
  * produced 语义(增量提取器对中间态与终态走同一 buildPartial,实证
2013
2486
  * packages/stream/src/core/incremental.ts Step 5):
2014
- * - 出帧只由主数组匹配驱动;extraScanPaths(nodes/links)仅提供元素边界
2015
- * 切分点,不驱动出帧——GenUI 契约据此锁死 rows 恒为主数据集在场
2016
- * (关系型图表 rows=节点行表、links 为边次数据集),无 rows 的 spec 永不
2017
- * 出帧属契约外形态,由生成期校验门拦截。
2018
- * - emptyPassthrough:rows=[] 空数组终态透传节点,由组件空态渲染接管,
2019
- * 防流结束后 pending 永驻。
2487
+ * - 出帧由任一主数组的完整元素驱动;尚无完整元素时保持 pending(骨架期由
2488
+ * 渲染管线承担)。
2489
+ * - emptyPassthrough:任一数据数组闭合为空(如 rows=[])时终态透传节点,
2490
+ * 由组件空态渲染接管,防流结束后 pending 永驻;判定据 parse 结果中的空
2491
+ * 数组字段(截断语义下「闭合的空数组」以 [] 在场、「未开始传输」缺席)。
2020
2492
  *
2021
- * 骨架裁决:不设 skeletonFields——rows 首行闭合即出帧逐行生长,天然有实时
2022
- * 反馈;首行前的 pending 期由渲染管线承担(与预设物料同语义),物料内骨架
2493
+ * 骨架裁决:不设 skeletonFields——首条数据闭合即出帧逐条生长,天然有实时
2494
+ * 反馈;首条前的 pending 期由渲染管线承担(与预设物料同语义),物料内骨架
2023
2495
  * 判定保留兼容上游 _cx_streaming 注入。
2024
2496
  *
2025
2497
  * fallback 从简:渲染链路不过 zod,包装层无模板直访(useAttrs 平铺 +
2026
- * composable 全回退,缺席不 TypeError);rows 缺席的中间态经翻译层
2498
+ * composable 全回退,缺席不 TypeError);数据数组缺席的中间态经翻译层
2027
2499
  * resolveMarkData 回退空数组,translateChartSpec 组装不抛错(增量帧翻译
2028
2500
  * 实证进包内测试)。
2029
2501
  *
@@ -2042,47 +2514,56 @@ function keyOf(key) {
2042
2514
  if (!comp) throw new Error(`tanstack-charts 物料定义缺失: ${key}`);
2043
2515
  return comp._cx_meta.key;
2044
2516
  }
2045
- const TANSTACK_CHARTS_STREAM_TRIGGERS = [{
2046
- key: keyOf("cx-chart"),
2047
- sections: [{
2048
- kind: "array",
2049
- arrayKey: "rows",
2050
- extraScanPaths: [[
2051
- "data",
2052
- "nodes",
2053
- "*"
2054
- ], [
2055
- "data",
2056
- "links",
2057
- "*"
2058
- ]]
2059
- }],
2060
- stateBranch: { emptyPassthrough: true },
2061
- frameStride: 10
2062
- }, ...[
2517
+ const ARRAY_PRESET_KEYS = [
2063
2518
  "cx-tanstack-charts-line",
2064
2519
  "cx-tanstack-charts-bar",
2065
2520
  "cx-tanstack-charts-area",
2066
2521
  "cx-tanstack-charts-dot",
2067
2522
  "cx-tanstack-charts-pie"
2068
- ].map((key) => ({
2069
- key: keyOf(key),
2523
+ ];
2524
+ const CHART_STREAM_TRIGGER = {
2525
+ key: keyOf("cx-data-chart"),
2070
2526
  sections: [{
2071
2527
  kind: "array",
2072
- arrayKey: "data"
2528
+ arrayKey: "*"
2073
2529
  }],
2530
+ stateBranch: { emptyPassthrough: true },
2074
2531
  frameStride: 10
2075
- }))];
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
+ ];
2076
2548
  /** 装配 TanStack Charts 物料的 trigger 注册表(size 恒 6) */
2077
2549
  function createTanstackChartsTriggerRegistry() {
2078
2550
  const registry = createTriggerRegistry();
2079
2551
  for (const config of TANSTACK_CHARTS_STREAM_TRIGGERS) registry.register(config.key, compileTrigger(config));
2080
2552
  return registry;
2081
2553
  }
2082
- /** 增量节点的主数组(回放计数展示用);非数组增长型物料或数组缺席时返回 null */
2554
+ /**
2555
+ * 增量节点的主数组(回放计数展示用);非数组增长型物料或数组缺席时返回 null。
2556
+ * 通配主数组形态(arrayKey '*')无单一主数组,取最长数组字段为主数据集
2557
+ * (如桑基的 links 之于 nodes);并列时取序列化序靠前者。
2558
+ */
2083
2559
  function mainArrayOf(node) {
2084
2560
  const section = TANSTACK_CHARTS_STREAM_TRIGGERS.find((c) => c.key === node.key)?.sections.find((s) => s.kind === "array");
2085
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
+ }
2086
2567
  const arr = node.data?.[section.arrayKey];
2087
2568
  return Array.isArray(arr) ? arr : null;
2088
2569
  }
@@ -2103,4 +2584,4 @@ const CxTanstackChartsBundle = {
2103
2584
  materials: [...CxTanstackCharts]
2104
2585
  };
2105
2586
  //#endregion
2106
- 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 };