@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/chart/src/motion-chart.vue.d.ts +15 -0
- package/dist/index.mjs +633 -148
- package/dist/shared/chart-empty.vue.d.ts +8 -0
- package/dist/shared/translate/geometry.d.ts +32 -0
- package/dist/shared/translate/index.d.ts +4 -3
- package/dist/shared/translate/mark.d.ts +9 -0
- package/dist/shared/translate/polar.d.ts +3 -0
- package/dist/shared/translate/types.d.ts +203 -11
- package/dist/shared/translate/view.d.ts +16 -0
- package/dist/shared/use-chart-props.d.ts +1 -1
- package/dist/stream-triggers.d.ts +5 -1
- package/package.json +6 -6
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$
|
|
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
|
-
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
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
|
|
149
|
-
"
|
|
150
|
-
"
|
|
151
|
-
"
|
|
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
|
|
188
|
-
|
|
189
|
-
if (
|
|
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")
|
|
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)
|
|
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
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
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) =>
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
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 =
|
|
1235
|
-
|
|
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
|
|
1242
|
-
|
|
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,
|
|
1245
|
-
|
|
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
|
|
1588
|
+
} else pushMark(bound, translateMark(bound, table));
|
|
1249
1589
|
}
|
|
1250
|
-
|
|
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
|
|
1258
|
-
const
|
|
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
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
if (
|
|
1265
|
-
|
|
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)
|
|
1270
|
-
...
|
|
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
|
|
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$
|
|
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(),
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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({
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
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
|
|
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
|
|
2004
|
-
*
|
|
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
|
-
* -
|
|
2011
|
-
*
|
|
2012
|
-
*
|
|
2013
|
-
*
|
|
2014
|
-
*
|
|
2015
|
-
* 防流结束后 pending 永驻。
|
|
2487
|
+
* - 出帧由任一主数组的完整元素驱动;尚无完整元素时保持 pending(骨架期由
|
|
2488
|
+
* 渲染管线承担)。
|
|
2489
|
+
* - emptyPassthrough:任一数据数组闭合为空(如 rows=[])时终态透传节点,
|
|
2490
|
+
* 由组件空态渲染接管,防流结束后 pending 永驻;判定据 parse 结果中的空
|
|
2491
|
+
* 数组字段(截断语义下「闭合的空数组」以 [] 在场、「未开始传输」缺席)。
|
|
2016
2492
|
*
|
|
2017
|
-
* 骨架裁决:不设 skeletonFields
|
|
2018
|
-
*
|
|
2493
|
+
* 骨架裁决:不设 skeletonFields——首条数据闭合即出帧逐条生长,天然有实时
|
|
2494
|
+
* 反馈;首条前的 pending 期由渲染管线承担(与预设物料同语义),物料内骨架
|
|
2019
2495
|
* 判定保留兼容上游 _cx_streaming 注入。
|
|
2020
2496
|
*
|
|
2021
2497
|
* fallback 从简:渲染链路不过 zod,包装层无模板直访(useAttrs 平铺 +
|
|
2022
|
-
* composable 全回退,缺席不 TypeError
|
|
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
|
|
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
|
-
]
|
|
2065
|
-
|
|
2523
|
+
];
|
|
2524
|
+
const CHART_STREAM_TRIGGER = {
|
|
2525
|
+
key: keyOf("cx-data-chart"),
|
|
2066
2526
|
sections: [{
|
|
2067
2527
|
kind: "array",
|
|
2068
|
-
arrayKey: "
|
|
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
|
-
/**
|
|
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 };
|