@lionad/cx-comps-tanstack-charts 0.1.0-alpha.6
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/README.md +8 -0
- package/dist/index.mjs +2095 -0
- package/package.json +64 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,2095 @@
|
|
|
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";
|
|
3
|
+
import { Chart } from "@tanstack/charts/vue";
|
|
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";
|
|
6
|
+
import { d3AreaXCurve } from "@tanstack/charts/d3/area-x";
|
|
7
|
+
import { curveBasis, curveCatmullRom, curveLinear, curveLinearClosed, curveMonotoneX, curveNatural, curveStep, curveStepAfter, curveStepBefore } from "d3-shape";
|
|
8
|
+
import { scaleLinear } from "@tanstack/charts/scales/linear";
|
|
9
|
+
import { scaleOrdinal } from "@tanstack/charts/scales/ordinal";
|
|
10
|
+
import { scalePoint } from "@tanstack/charts/scales/point";
|
|
11
|
+
import { scaleBand } from "@tanstack/charts/scales/band";
|
|
12
|
+
import { scaleLog, scalePow, scaleSqrt, scaleSymlog, scaleTime, scaleUtc } from "d3-scale";
|
|
13
|
+
import { contour } from "@tanstack/charts/spatial/contour";
|
|
14
|
+
import { delaunayLink } from "@tanstack/charts/spatial/delaunay";
|
|
15
|
+
import { densityContour } from "@tanstack/charts/spatial/density";
|
|
16
|
+
import { hexbin } from "@tanstack/charts/spatial/hexbin";
|
|
17
|
+
import { voronoi } from "@tanstack/charts/spatial/voronoi";
|
|
18
|
+
import { angleGrid, pie, polar, radialArc, radialArea, radialBarAngle, radialBarRadius, radialDot, radialGrid, radialLine, radialRule, radialText } from "@tanstack/charts/polar";
|
|
19
|
+
import { facet } from "@tanstack/charts/facet";
|
|
20
|
+
import { geoShape } from "@tanstack/charts/geo";
|
|
21
|
+
import { sunburst } from "@tanstack/charts/hierarchy/sunburst";
|
|
22
|
+
import { treeLayout } from "@tanstack/charts/hierarchy/tree";
|
|
23
|
+
import { treemap } from "@tanstack/charts/hierarchy/treemap";
|
|
24
|
+
import { forceLayout } from "@tanstack/charts/network/force";
|
|
25
|
+
import { sankeyDiagram } from "@tanstack/charts/network/sankey";
|
|
26
|
+
import { geoAlbersUsa, geoEqualEarth, geoIdentity, geoMercator, geoNaturalEarth1, geoOrthographic } from "d3-geo";
|
|
27
|
+
import { compileTrigger, createTriggerRegistry } from "@lionad/cx-stream";
|
|
28
|
+
//#region src/shared/chart-skeleton.vue
|
|
29
|
+
const _sfc_main$6 = /*@__PURE__*/ defineComponent({
|
|
30
|
+
__name: "chart-skeleton",
|
|
31
|
+
props: { height: { default: 320 } },
|
|
32
|
+
setup(__props) {
|
|
33
|
+
const props = __props;
|
|
34
|
+
const ns = useCxBEM("tanstack-charts-chart-skeleton");
|
|
35
|
+
const normalizedHeight = computed(() => Number.isFinite(props.height) && props.height > 0 ? props.height : 320);
|
|
36
|
+
const HEIGHTS = [
|
|
37
|
+
"42%",
|
|
38
|
+
"68%",
|
|
39
|
+
"55%",
|
|
40
|
+
"82%",
|
|
41
|
+
"61%"
|
|
42
|
+
];
|
|
43
|
+
const barHeights = computed(() => HEIGHTS);
|
|
44
|
+
const shellStyle = computed(() => ({
|
|
45
|
+
display: "flex",
|
|
46
|
+
flexDirection: "column",
|
|
47
|
+
justifyContent: "flex-end",
|
|
48
|
+
width: "100%",
|
|
49
|
+
boxSizing: "border-box",
|
|
50
|
+
height: `${normalizedHeight.value}px`,
|
|
51
|
+
padding: "1rem 1rem 0",
|
|
52
|
+
border: "1px solid rgba(148, 163, 184, 0.25)",
|
|
53
|
+
borderRadius: "0.75rem"
|
|
54
|
+
}));
|
|
55
|
+
const plotStyle = {
|
|
56
|
+
display: "flex",
|
|
57
|
+
alignItems: "flex-end",
|
|
58
|
+
justifyContent: "space-evenly",
|
|
59
|
+
gap: "8%",
|
|
60
|
+
flex: "1",
|
|
61
|
+
minHeight: "0"
|
|
62
|
+
};
|
|
63
|
+
const barStyle = {
|
|
64
|
+
flex: "1",
|
|
65
|
+
maxWidth: "3rem",
|
|
66
|
+
borderRadius: "0.375rem 0.375rem 0 0",
|
|
67
|
+
background: "linear-gradient(180deg, rgba(148, 163, 184, 0.32) 0%, rgba(148, 163, 184, 0.18) 100%)"
|
|
68
|
+
};
|
|
69
|
+
const baselineStyle = {
|
|
70
|
+
height: "2px",
|
|
71
|
+
borderRadius: "1px",
|
|
72
|
+
background: "rgba(148, 163, 184, 0.35)"
|
|
73
|
+
};
|
|
74
|
+
const bars = [];
|
|
75
|
+
const animations = [];
|
|
76
|
+
function setBarRef(el, i) {
|
|
77
|
+
bars[i] = el instanceof Element ? el : el?.$el ?? null;
|
|
78
|
+
}
|
|
79
|
+
onMounted(() => {
|
|
80
|
+
bars.forEach((bar, i) => {
|
|
81
|
+
if (!bar || typeof bar.animate !== "function") return;
|
|
82
|
+
animations.push(bar.animate([
|
|
83
|
+
{ opacity: .55 },
|
|
84
|
+
{ opacity: 1 },
|
|
85
|
+
{ opacity: .55 }
|
|
86
|
+
], {
|
|
87
|
+
duration: 1400,
|
|
88
|
+
iterations: Infinity,
|
|
89
|
+
delay: i * 150,
|
|
90
|
+
easing: "ease-in-out"
|
|
91
|
+
}));
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
onBeforeUnmount(() => {
|
|
95
|
+
for (const animation of animations) animation.cancel();
|
|
96
|
+
animations.length = 0;
|
|
97
|
+
bars.length = 0;
|
|
98
|
+
});
|
|
99
|
+
return (_ctx, _cache) => {
|
|
100
|
+
return openBlock(), createElementBlock(Fragment, null, [createCommentVNode(" 图表流式骨架:definition 未闭合期间替换 Chart 占位,柱条错落脉冲暗示图表生长。\n 全 inline style + WAAPI 脉冲:保持包零 css 分发契约(样式不经 style.css 装配) "), createElementVNode("div", {
|
|
101
|
+
class: normalizeClass(unref(ns).b()),
|
|
102
|
+
style: normalizeStyle(shellStyle.value),
|
|
103
|
+
"data-testid": "cx-tanstack-charts-chart-skeleton",
|
|
104
|
+
"aria-hidden": "true"
|
|
105
|
+
}, [createElementVNode("div", { style: plotStyle }, [(openBlock(true), createElementBlock(Fragment, null, renderList(barHeights.value, (height, i) => {
|
|
106
|
+
return openBlock(), createElementBlock("div", {
|
|
107
|
+
key: i,
|
|
108
|
+
ref_for: true,
|
|
109
|
+
ref: (el) => setBarRef(el, i),
|
|
110
|
+
style: normalizeStyle({
|
|
111
|
+
...barStyle,
|
|
112
|
+
height
|
|
113
|
+
})
|
|
114
|
+
}, null, 4);
|
|
115
|
+
}), 128))]), createElementVNode("div", { style: baselineStyle })], 6)], 2112);
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
//#endregion
|
|
120
|
+
//#region src/shared/streaming.ts
|
|
121
|
+
/**
|
|
122
|
+
* 读取 trigger 注入的流式骨架标记(_cx_streaming,下划线前缀键不会透传物料)。
|
|
123
|
+
* 标记列出未闭合的 skeleton 顶层字段;包装层据此在主体未闭合期间渲染骨架。
|
|
124
|
+
*/
|
|
125
|
+
function streamingFields(attrs) {
|
|
126
|
+
const fields = attrs._cx_streaming;
|
|
127
|
+
return Array.isArray(fields) ? fields : [];
|
|
128
|
+
}
|
|
129
|
+
//#endregion
|
|
130
|
+
//#region src/shared/use-chart-props.ts
|
|
131
|
+
/** <Chart> 宿主标量 props 白名单(回调 function props v1 不暴露:JSON 不可表达) */
|
|
132
|
+
const HOST_PROP_KEYS = [
|
|
133
|
+
"ariaDescription",
|
|
134
|
+
"height",
|
|
135
|
+
"width",
|
|
136
|
+
"aspectRatio",
|
|
137
|
+
"tabIndex",
|
|
138
|
+
"idPrefix",
|
|
139
|
+
"className"
|
|
140
|
+
];
|
|
141
|
+
/**
|
|
142
|
+
* 命名数据集白名单(GenUI 数据顶层化契约):
|
|
143
|
+
* rows 恒为主数据集(流式 trigger arrayKey),nodes/links 承载关系型图双数据集;
|
|
144
|
+
* 物料 data 顶层与 definition 平级的这三个数组字段分馏进 datasets 表,
|
|
145
|
+
* 供 marks 内字符串引用解析。非数组值静默忽略(流式半值防御)。
|
|
146
|
+
*/
|
|
147
|
+
const DATASET_KEYS = [
|
|
148
|
+
"rows",
|
|
149
|
+
"nodes",
|
|
150
|
+
"links"
|
|
151
|
+
];
|
|
152
|
+
/**
|
|
153
|
+
* 分馏 <Chart> 宿主 props:标量白名单 + class/style + ariaLabel 回退。
|
|
154
|
+
*
|
|
155
|
+
* 剥离 cx 内部键(comp 运行时节点、data-* 编辑标记、_ 前缀编辑器键),与 vtu/EP 桥接同形。
|
|
156
|
+
* class/style 保留进 hostProps(cx 编辑器样式类落到 Chart 根宿主 div)。
|
|
157
|
+
* ariaLabel 是 Chart 必填 prop,缺省回退物料中文名;它与 hostProps 分开返回,
|
|
158
|
+
* 由包装层显式绑定,否则模板类型检查把 v-bind 的 Record 当作 required 键缺席。
|
|
159
|
+
* 预设物料与通用 chart 共用本函数——差异只在 spec 来源(definition 键 vs 通道组装)。
|
|
160
|
+
*/
|
|
161
|
+
function useChartHostProps(attrs) {
|
|
162
|
+
return {
|
|
163
|
+
hostProps: computed(() => {
|
|
164
|
+
const result = {};
|
|
165
|
+
for (const key of HOST_PROP_KEYS) if (attrs[key] !== void 0) result[key] = attrs[key];
|
|
166
|
+
if (attrs.class !== void 0) result.class = attrs.class;
|
|
167
|
+
if (attrs.style !== void 0) result.style = attrs.style;
|
|
168
|
+
return result;
|
|
169
|
+
}),
|
|
170
|
+
ariaLabel: computed(() => attrs.ariaLabel || "图表")
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* 通用 chart 物料的分馏:definition 键 → spec,外加宿主 props。
|
|
175
|
+
*
|
|
176
|
+
* definition 键从透传集中分馏——Chart 组件的 definition prop 只接受运行时产物,
|
|
177
|
+
* 物料侧的 JSON spec 须经翻译层组装后由包装层显式绑定,不能随 v-bind 原样透传。
|
|
178
|
+
* 返回独立 computed 而非整体 ref:包装层分别绑定,避免解构丢响应性。
|
|
179
|
+
*/
|
|
180
|
+
function useChartProps(attrs) {
|
|
181
|
+
const { hostProps, ariaLabel } = useChartHostProps(attrs);
|
|
182
|
+
return {
|
|
183
|
+
spec: computed(() => attrs.definition ?? { marks: [] }),
|
|
184
|
+
datasets: computed(() => {
|
|
185
|
+
const table = {};
|
|
186
|
+
for (const key of DATASET_KEYS) {
|
|
187
|
+
const value = attrs[key];
|
|
188
|
+
if (Array.isArray(value)) table[key] = value;
|
|
189
|
+
}
|
|
190
|
+
return table;
|
|
191
|
+
}),
|
|
192
|
+
hostProps,
|
|
193
|
+
ariaLabel
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
//#endregion
|
|
197
|
+
//#region src/shared/translate/curve.ts
|
|
198
|
+
const CURVE_FACTORIES = {
|
|
199
|
+
linear: curveLinear,
|
|
200
|
+
linearClosed: curveLinearClosed,
|
|
201
|
+
monotoneX: curveMonotoneX,
|
|
202
|
+
step: curveStep,
|
|
203
|
+
stepAfter: curveStepAfter,
|
|
204
|
+
stepBefore: curveStepBefore,
|
|
205
|
+
basis: curveBasis,
|
|
206
|
+
natural: curveNatural,
|
|
207
|
+
catmullRom: curveCatmullRom
|
|
208
|
+
};
|
|
209
|
+
/** curve 枚举白名单:预设物料组装 spec 前的运行时校验(JSON 输入不受 TS 约束) */
|
|
210
|
+
const CX_CHART_CURVE_NAMES = Object.keys(CURVE_FACTORIES);
|
|
211
|
+
function resolveCurveFactory(name) {
|
|
212
|
+
const factory = CURVE_FACTORIES[name];
|
|
213
|
+
if (!factory) throw new Error(`translateCurve: 未知 curve 枚举 "${String(name)}"`);
|
|
214
|
+
return factory;
|
|
215
|
+
}
|
|
216
|
+
/** curve 枚举 → ChartCurve(line+area 函数对);未知枚举显式抛错(JSON 输入不受 TS 约束,运行时防御) */
|
|
217
|
+
function translateCurve(name) {
|
|
218
|
+
return d3Curve(resolveCurveFactory(name));
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* curve 枚举 → AreaXCurve:violinY 等水平展开面积 mark 的专用包装
|
|
222
|
+
* (ViolinYCurve = AreaXCurve,d3Curve 产物不提供 areaX 生成器)。
|
|
223
|
+
*/
|
|
224
|
+
function translateAreaXCurve(name) {
|
|
225
|
+
return d3AreaXCurve(resolveCurveFactory(name));
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* curve 枚举 → 原生 d3 CurveFactory:polar radial 系(radialLine/radialArea)
|
|
229
|
+
* 的 curve 参数直接消费 d3 工厂而非 ChartCurve 包装(库签名实证 CurveFactory)。
|
|
230
|
+
*/
|
|
231
|
+
function translateRadialCurve(name) {
|
|
232
|
+
return resolveCurveFactory(name);
|
|
233
|
+
}
|
|
234
|
+
//#endregion
|
|
235
|
+
//#region src/shared/translate/scale.ts
|
|
236
|
+
/**
|
|
237
|
+
* scale 声明式 → ChartScaleInput。
|
|
238
|
+
* 无 domain 时返回工厂形态(库从 marks channel 推断 domain);
|
|
239
|
+
* 有 domain 时返回实例(保留配置,库不再推断)。
|
|
240
|
+
* 时间系(utc/time)domain 为 ISO 8601 字符串,翻译为 Date 实例(JSON 不可表达 Date)。
|
|
241
|
+
*/
|
|
242
|
+
function translateScale(spec) {
|
|
243
|
+
switch (spec.kind) {
|
|
244
|
+
case "linear": return spec.domain ? scaleLinear().domain(spec.domain) : scaleLinear;
|
|
245
|
+
case "utc":
|
|
246
|
+
case "time": {
|
|
247
|
+
const factory = spec.kind === "utc" ? scaleUtc : scaleTime;
|
|
248
|
+
if (!spec.domain) return factory;
|
|
249
|
+
const domain = [new Date(spec.domain[0]), new Date(spec.domain[1])];
|
|
250
|
+
return factory().domain(domain);
|
|
251
|
+
}
|
|
252
|
+
case "log": return spec.domain ? scaleLog().domain(spec.domain) : scaleLog;
|
|
253
|
+
case "sqrt": return spec.domain ? scaleSqrt().domain(spec.domain) : scaleSqrt;
|
|
254
|
+
case "symlog": return spec.domain ? scaleSymlog().domain(spec.domain) : scaleSymlog;
|
|
255
|
+
case "pow": {
|
|
256
|
+
const create = () => {
|
|
257
|
+
const instance = scalePow();
|
|
258
|
+
return spec.exponent === void 0 ? instance : instance.exponent(spec.exponent);
|
|
259
|
+
};
|
|
260
|
+
if (!spec.domain) return create;
|
|
261
|
+
return create().domain(spec.domain);
|
|
262
|
+
}
|
|
263
|
+
case "point":
|
|
264
|
+
if (spec.domain) {
|
|
265
|
+
const instance = scalePoint().domain(spec.domain);
|
|
266
|
+
return spec.padding === void 0 ? instance : instance.padding(spec.padding);
|
|
267
|
+
}
|
|
268
|
+
return () => spec.padding === void 0 ? scalePoint() : scalePoint().padding(spec.padding);
|
|
269
|
+
case "band":
|
|
270
|
+
if (spec.domain) {
|
|
271
|
+
const instance = scaleBand().domain(spec.domain);
|
|
272
|
+
return spec.padding === void 0 ? instance : instance.padding(spec.padding);
|
|
273
|
+
}
|
|
274
|
+
return () => spec.padding === void 0 ? scaleBand() : scaleBand().padding(spec.padding);
|
|
275
|
+
case "ordinal": return spec.domain ? scaleOrdinal().domain(spec.domain) : scaleOrdinal;
|
|
276
|
+
default: throw new Error(`translateScale: 未知 scale kind "${String(spec.kind)}"`);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
//#endregion
|
|
280
|
+
//#region src/shared/translate/axis.ts
|
|
281
|
+
function translateAxisPresentation(axis) {
|
|
282
|
+
if (axis === false) return false;
|
|
283
|
+
const presentation = {};
|
|
284
|
+
if (axis.label !== void 0) presentation.label = axis.label;
|
|
285
|
+
if (axis.ticks !== void 0) {
|
|
286
|
+
const ticks = {};
|
|
287
|
+
if (axis.ticks.count !== void 0) ticks.count = axis.ticks.count;
|
|
288
|
+
if (axis.ticks.size !== void 0) ticks.size = axis.ticks.size;
|
|
289
|
+
if (axis.ticks.padding !== void 0) ticks.padding = axis.ticks.padding;
|
|
290
|
+
if (axis.ticks.values !== void 0) ticks.values = axis.ticks.values;
|
|
291
|
+
presentation.ticks = ticks;
|
|
292
|
+
}
|
|
293
|
+
if (axis.tickLabels !== void 0) presentation.tickLabels = axis.tickLabels;
|
|
294
|
+
return presentation;
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* axis 声明式 → ChartAxisOptions;scale 缺省按轴位回退(x→point、y→linear)。
|
|
298
|
+
* undefined(含流式中间态:definition 开容器后 x/y 字段尚未传到)注入缺省 axis——
|
|
299
|
+
* 库对「mark 物化 channel 但 axis 无 scale 配置」抛错,缺席不等于无轴。
|
|
300
|
+
* null 原样保留(polar 等 scale 值为 never 的 definition 显式无轴)。
|
|
301
|
+
*/
|
|
302
|
+
function translateAxis(spec, fallbackKind) {
|
|
303
|
+
if (spec === null) return null;
|
|
304
|
+
const axis = { scale: spec?.scale ? translateScale(spec.scale) : fallbackKind === "point" ? () => scalePoint() : scaleLinear };
|
|
305
|
+
if (spec?.nice !== void 0) axis.nice = spec.nice;
|
|
306
|
+
if (spec?.reverse !== void 0) axis.reverse = spec.reverse;
|
|
307
|
+
if (spec?.grid !== void 0) axis.grid = spec.grid;
|
|
308
|
+
if (spec?.axis !== void 0) axis.axis = translateAxisPresentation(spec.axis);
|
|
309
|
+
return axis;
|
|
310
|
+
}
|
|
311
|
+
//#endregion
|
|
312
|
+
//#region src/shared/translate/transforms.ts
|
|
313
|
+
/**
|
|
314
|
+
* 数据预处理管道翻译:spec 顶层 transforms 按序执行,产物以 name 注册进数据集表。
|
|
315
|
+
* 纯函数翻译(同一输入恒同输出),流式中间态 rows 部分到达时按当时行集重算——
|
|
316
|
+
* 逐行生长语义在派生数据集上同样成立。
|
|
317
|
+
*
|
|
318
|
+
* 断言桥:transform 函数泛型按 datum 类型参数化,JSON 翻译层 datum 恒为 unknown——
|
|
319
|
+
* 运行时字段名字符串 accessor 由库内部解析(TransformValue 支持字符串),TS 泛型
|
|
320
|
+
* 对 unknown 不协变,统一经 call 桥接(与 translateChartSpec 的 defineChart 断言同性质)。
|
|
321
|
+
*/
|
|
322
|
+
const call = (fn) => (source, options) => fn(source, options);
|
|
323
|
+
/** reduce 声明式 → 库归约器;字符串枚举 count/sum/mean/min/max 原样透传,其余映射函数引用 */
|
|
324
|
+
function translateReduce(reduce) {
|
|
325
|
+
if (typeof reduce === "object" && reduce !== null) return quantile(reduce.quantile);
|
|
326
|
+
switch (reduce) {
|
|
327
|
+
case "count":
|
|
328
|
+
case "sum":
|
|
329
|
+
case "mean":
|
|
330
|
+
case "min":
|
|
331
|
+
case "max": return reduce;
|
|
332
|
+
case "median": return median;
|
|
333
|
+
case "first": return first;
|
|
334
|
+
case "last": return last;
|
|
335
|
+
case "variance": return variance;
|
|
336
|
+
case "deviation": return deviation;
|
|
337
|
+
case "delta": return delta;
|
|
338
|
+
case "ratio": return ratio;
|
|
339
|
+
default: throw new Error(`translateReduce: 未知 reduce "${String(reduce)}"`);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
function translateOutputs(outputs) {
|
|
343
|
+
const result = {};
|
|
344
|
+
for (const [name, spec] of Object.entries(outputs)) result[name] = {
|
|
345
|
+
value: spec.value,
|
|
346
|
+
reduce: translateReduce(spec.reduce)
|
|
347
|
+
};
|
|
348
|
+
return result;
|
|
349
|
+
}
|
|
350
|
+
/** 单步 transform → 行数组;源数据经 data 名查 datasets 表(缺省 rows,未命中回退空数组) */
|
|
351
|
+
function translateTransform(spec, datasets) {
|
|
352
|
+
const source = datasets[spec.data ?? "rows"] ?? [];
|
|
353
|
+
const rest = spec;
|
|
354
|
+
const pick = (...keys) => {
|
|
355
|
+
const out = {};
|
|
356
|
+
for (const k of keys) if (rest[k] !== void 0) out[k] = rest[k];
|
|
357
|
+
return out;
|
|
358
|
+
};
|
|
359
|
+
switch (spec.kind) {
|
|
360
|
+
case "groupBy": return call(groupBy)(source, {
|
|
361
|
+
by: spec.by,
|
|
362
|
+
outputs: translateOutputs(spec.outputs)
|
|
363
|
+
});
|
|
364
|
+
case "binX": return call(binX)(source, {
|
|
365
|
+
value: spec.value,
|
|
366
|
+
...pick("by", "thresholds", "domain"),
|
|
367
|
+
...spec.outputs !== void 0 ? { outputs: translateOutputs(spec.outputs) } : {}
|
|
368
|
+
});
|
|
369
|
+
case "binY": return call(binY)(source, {
|
|
370
|
+
value: spec.value,
|
|
371
|
+
...pick("by", "thresholds", "domain"),
|
|
372
|
+
...spec.outputs !== void 0 ? { outputs: translateOutputs(spec.outputs) } : {}
|
|
373
|
+
});
|
|
374
|
+
case "binXY": return call(binXY)(source, {
|
|
375
|
+
x: spec.x,
|
|
376
|
+
y: spec.y,
|
|
377
|
+
...pick("by", "xThresholds", "yThresholds", "xDomain", "yDomain"),
|
|
378
|
+
...spec.outputs !== void 0 ? { outputs: translateOutputs(spec.outputs) } : {}
|
|
379
|
+
});
|
|
380
|
+
case "rollingWindow": return call(rollingWindow)(source, {
|
|
381
|
+
size: spec.size,
|
|
382
|
+
outputs: translateOutputs(spec.outputs),
|
|
383
|
+
...pick("by", "anchor", "partial", "orderBy", "order")
|
|
384
|
+
});
|
|
385
|
+
case "normalize": return call(normalize)(source, {
|
|
386
|
+
value: spec.value,
|
|
387
|
+
...pick("by", "as", "basis")
|
|
388
|
+
});
|
|
389
|
+
case "cumulative": return call(cumulative)(source, {
|
|
390
|
+
outputs: translateOutputs(spec.outputs),
|
|
391
|
+
...pick("by", "orderBy", "order")
|
|
392
|
+
});
|
|
393
|
+
case "fold": return call(fold)(source, {
|
|
394
|
+
fields: spec.fields,
|
|
395
|
+
...pick("as")
|
|
396
|
+
});
|
|
397
|
+
case "rank": return call(rank)(source, {
|
|
398
|
+
value: spec.value,
|
|
399
|
+
...pick("by", "order", "ties", "as")
|
|
400
|
+
});
|
|
401
|
+
case "select": return call(select)(source, {
|
|
402
|
+
select: spec.select,
|
|
403
|
+
...pick("by", "value")
|
|
404
|
+
});
|
|
405
|
+
case "stackRowsY": return call(stackRowsY)(source, {
|
|
406
|
+
x: spec.x,
|
|
407
|
+
y: spec.y,
|
|
408
|
+
...pick("z", "order", "offset", "reverse", "anchor")
|
|
409
|
+
});
|
|
410
|
+
case "stackRowsX": return call(stackRowsX)(source, {
|
|
411
|
+
x: spec.x,
|
|
412
|
+
y: spec.y,
|
|
413
|
+
...pick("z", "order", "offset", "reverse", "anchor")
|
|
414
|
+
});
|
|
415
|
+
case "waterfall": return call(waterfall)(source, {
|
|
416
|
+
value: spec.value,
|
|
417
|
+
...pick("by", "total", "orderBy", "order")
|
|
418
|
+
});
|
|
419
|
+
case "mosaicY": return call(mosaicY)(source, {
|
|
420
|
+
x: spec.x,
|
|
421
|
+
y: spec.y,
|
|
422
|
+
value: spec.value,
|
|
423
|
+
...pick("xOrder", "yOrder")
|
|
424
|
+
});
|
|
425
|
+
case "mosaicX": return call(mosaicX)(source, {
|
|
426
|
+
x: spec.x,
|
|
427
|
+
y: spec.y,
|
|
428
|
+
value: spec.value,
|
|
429
|
+
...pick("xOrder", "yOrder")
|
|
430
|
+
});
|
|
431
|
+
case "linearRegressionRowsY": return call(linearRegressionRowsY)(source, {
|
|
432
|
+
x: spec.x,
|
|
433
|
+
y: spec.y,
|
|
434
|
+
...pick("z", "ci", "samples")
|
|
435
|
+
});
|
|
436
|
+
case "linearRegressionRowsX": return call(linearRegressionRowsX)(source, {
|
|
437
|
+
x: spec.x,
|
|
438
|
+
y: spec.y,
|
|
439
|
+
...pick("z", "ci", "samples")
|
|
440
|
+
});
|
|
441
|
+
case "boxRows": return call(boxRows)(source, {
|
|
442
|
+
category: spec.category,
|
|
443
|
+
value: spec.value
|
|
444
|
+
});
|
|
445
|
+
case "pie": return call(pie)(source, {
|
|
446
|
+
value: spec.value,
|
|
447
|
+
...spec.padAngle !== void 0 ? { gapAngle: spec.padAngle } : {},
|
|
448
|
+
...pick("startAngle", "endAngle")
|
|
449
|
+
});
|
|
450
|
+
default: throw new Error(`translateTransform: 未知 transform kind "${String(spec.kind)}"`);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* 执行 transforms 管道:产物以 name 注册进 datasets 副本(不污染调用方表;
|
|
455
|
+
* 源数据集引用未命中时回退空数组——与 resolveMarkData 同一容错语义)。
|
|
456
|
+
*/
|
|
457
|
+
function applyTransforms(transforms, datasets) {
|
|
458
|
+
const table = { ...datasets };
|
|
459
|
+
for (const spec of transforms) table[spec.name] = translateTransform(spec, table);
|
|
460
|
+
return table;
|
|
461
|
+
}
|
|
462
|
+
//#endregion
|
|
463
|
+
//#region src/shared/translate/mark.ts
|
|
464
|
+
/** 字段名 channel 白名单(运行时校验只接受字符串;accessor 函数 JSON 不可表达) */
|
|
465
|
+
const CHANNEL_KEYS = [
|
|
466
|
+
"x",
|
|
467
|
+
"y",
|
|
468
|
+
"x1",
|
|
469
|
+
"x2",
|
|
470
|
+
"y1",
|
|
471
|
+
"y2",
|
|
472
|
+
"z",
|
|
473
|
+
"color",
|
|
474
|
+
"key",
|
|
475
|
+
"text"
|
|
476
|
+
];
|
|
477
|
+
/** 弹性 channel(字段名字符串或数值常量:radial 的 angle/radius、violin 的 width 等) */
|
|
478
|
+
const CHANNEL_FLEX_KEYS = [
|
|
479
|
+
"r",
|
|
480
|
+
"angle",
|
|
481
|
+
"radius",
|
|
482
|
+
"width",
|
|
483
|
+
"height",
|
|
484
|
+
"length",
|
|
485
|
+
"rotate"
|
|
486
|
+
];
|
|
487
|
+
/** 通用样式键(原样透传,多余键对 mark 工厂无害——按名读取) */
|
|
488
|
+
const STYLE_KEYS = [
|
|
489
|
+
"stroke",
|
|
490
|
+
"strokeWidth",
|
|
491
|
+
"strokeOpacity",
|
|
492
|
+
"strokeDasharray",
|
|
493
|
+
"fill",
|
|
494
|
+
"fillOpacity",
|
|
495
|
+
"fontSize",
|
|
496
|
+
"fontWeight",
|
|
497
|
+
"dx",
|
|
498
|
+
"dy",
|
|
499
|
+
"points",
|
|
500
|
+
"opacity"
|
|
501
|
+
];
|
|
502
|
+
/** mark 专有标量选项(原样透传;枚举合法性由库运行时与生成期校验门双重兜底) */
|
|
503
|
+
const SCALAR_KEYS = [
|
|
504
|
+
"span",
|
|
505
|
+
"overlap",
|
|
506
|
+
"unit",
|
|
507
|
+
"round",
|
|
508
|
+
"gap",
|
|
509
|
+
"columns",
|
|
510
|
+
"headLength",
|
|
511
|
+
"headAngle",
|
|
512
|
+
"lineCap",
|
|
513
|
+
"anchor",
|
|
514
|
+
"ci",
|
|
515
|
+
"samples",
|
|
516
|
+
"positiveFill",
|
|
517
|
+
"negativeFill",
|
|
518
|
+
"positiveFillOpacity",
|
|
519
|
+
"negativeFillOpacity",
|
|
520
|
+
"comparisonStroke",
|
|
521
|
+
"cornerRadius",
|
|
522
|
+
"padAngle",
|
|
523
|
+
"radiusOffset",
|
|
524
|
+
"baseline",
|
|
525
|
+
"inset"
|
|
526
|
+
];
|
|
527
|
+
const MARK_FACTORIES = {
|
|
528
|
+
lineY,
|
|
529
|
+
lineX,
|
|
530
|
+
areaY,
|
|
531
|
+
areaX,
|
|
532
|
+
barY,
|
|
533
|
+
barX,
|
|
534
|
+
ruleY,
|
|
535
|
+
ruleX,
|
|
536
|
+
dot,
|
|
537
|
+
text,
|
|
538
|
+
tickX,
|
|
539
|
+
tickY,
|
|
540
|
+
bandY,
|
|
541
|
+
bandX,
|
|
542
|
+
rect,
|
|
543
|
+
cell,
|
|
544
|
+
link,
|
|
545
|
+
arrow,
|
|
546
|
+
vector,
|
|
547
|
+
hexagon,
|
|
548
|
+
boxY,
|
|
549
|
+
boxX,
|
|
550
|
+
violinY,
|
|
551
|
+
violinX,
|
|
552
|
+
ridgelineY,
|
|
553
|
+
ridgelineX,
|
|
554
|
+
waffleY,
|
|
555
|
+
waffleX,
|
|
556
|
+
differenceY,
|
|
557
|
+
differenceX,
|
|
558
|
+
linearRegressionY,
|
|
559
|
+
linearRegressionX
|
|
560
|
+
};
|
|
561
|
+
/**
|
|
562
|
+
* spatial 系 mark 工厂(子路径导入,签名与主入口 mark 同构:(source, options))。
|
|
563
|
+
* contour 是网格数据等值线(无 x/y channel,width/height 为网格行列数);
|
|
564
|
+
* densityContour 是散点 KDE 等值线(x/y channel + bandwidth/cellSize/thresholds)。
|
|
565
|
+
*/
|
|
566
|
+
const SPATIAL_FACTORIES = {
|
|
567
|
+
voronoi,
|
|
568
|
+
hexbin,
|
|
569
|
+
contour,
|
|
570
|
+
delaunayLink,
|
|
571
|
+
density: densityContour
|
|
572
|
+
};
|
|
573
|
+
/** frame 无数据形态(纯装饰 mark):签名无 source 参数,特判不走数据解析 */
|
|
574
|
+
function translateFrame(spec) {
|
|
575
|
+
const options = {};
|
|
576
|
+
for (const key of [
|
|
577
|
+
"fill",
|
|
578
|
+
"stroke",
|
|
579
|
+
"fillOpacity",
|
|
580
|
+
"strokeOpacity",
|
|
581
|
+
"strokeWidth",
|
|
582
|
+
"inset",
|
|
583
|
+
"radius"
|
|
584
|
+
]) if (spec[key] !== void 0) options[key] = spec[key];
|
|
585
|
+
return frame(options);
|
|
586
|
+
}
|
|
587
|
+
/** channel 运行时校验:只接受字段名字符串(accessor 函数 JSON 不可表达) */
|
|
588
|
+
function assertChannels(spec) {
|
|
589
|
+
const constantKeys = spec.type === "ruleX" ? /* @__PURE__ */ new Set(["x"]) : spec.type === "ruleY" ? /* @__PURE__ */ new Set(["y"]) : void 0;
|
|
590
|
+
for (const key of CHANNEL_KEYS) {
|
|
591
|
+
const value = spec[key];
|
|
592
|
+
if (value !== void 0 && typeof value !== "string") {
|
|
593
|
+
if (constantKeys?.has(key) && typeof value === "number") continue;
|
|
594
|
+
throw new Error(`translateMark: channel "${key}" 只支持字段名字符串,收到 ${typeof value}(mark type=${spec.type})`);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
for (const key of CHANNEL_FLEX_KEYS) {
|
|
598
|
+
const value = spec[key];
|
|
599
|
+
if (value !== void 0 && typeof value !== "string" && typeof value !== "number") throw new Error(`translateMark: channel "${key}" 只支持字段名或数值,收到 ${typeof value}`);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
/**
|
|
603
|
+
* mark data 解析:字符串 → datasets 查表;数组原样透传。
|
|
604
|
+
* 未命中一律回退空数组——流式中间态(definition 先闭合、数据集在途;多数据集
|
|
605
|
+
* 部分到达)与 LLM 笔误在运行时不可区分,渲染层容错优先(与「渲染链路不过 zod,
|
|
606
|
+
* fallback 从简」契约一致);笔误显式化归生成期校验(spec 渲染断言门)。
|
|
607
|
+
*/
|
|
608
|
+
function resolveMarkData(data, datasets) {
|
|
609
|
+
if (data === void 0) return [];
|
|
610
|
+
if (typeof data !== "string") return data;
|
|
611
|
+
return datasets?.[data] ?? [];
|
|
612
|
+
}
|
|
613
|
+
/** layout 声明式 → mark layout 工厂(stack/group/dodge 挂 mark.layout 字段) */
|
|
614
|
+
function translateLayout(spec) {
|
|
615
|
+
switch (spec.kind) {
|
|
616
|
+
case "stack": {
|
|
617
|
+
const options = {};
|
|
618
|
+
if (spec.order !== void 0) options.order = spec.order;
|
|
619
|
+
if (spec.offset !== void 0) options.offset = spec.offset;
|
|
620
|
+
if (spec.reverse !== void 0) options.reverse = spec.reverse;
|
|
621
|
+
if (spec.anchor !== void 0) options.anchor = spec.anchor;
|
|
622
|
+
return stack(options);
|
|
623
|
+
}
|
|
624
|
+
case "group": return group(spec.padding === void 0 ? void 0 : { padding: spec.padding });
|
|
625
|
+
case "dodgeX": {
|
|
626
|
+
const options = {};
|
|
627
|
+
if (spec.anchor !== void 0) options.anchor = spec.anchor;
|
|
628
|
+
if (spec.padding !== void 0) options.padding = spec.padding;
|
|
629
|
+
return dodgeX(options);
|
|
630
|
+
}
|
|
631
|
+
case "dodgeY": {
|
|
632
|
+
const options = {};
|
|
633
|
+
if (spec.anchor !== void 0) options.anchor = spec.anchor;
|
|
634
|
+
if (spec.padding !== void 0) options.padding = spec.padding;
|
|
635
|
+
return dodgeY(options);
|
|
636
|
+
}
|
|
637
|
+
default: throw new Error(`translateLayout: 未知 layout kind "${String(spec.kind)}"`);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
/** spatial 专有标量(hexbin 的 binWidth/outputs、contour 的 thresholds/smooth)按存在性透传 */
|
|
641
|
+
function translateSpatialMark(spec, datasets) {
|
|
642
|
+
const factory = SPATIAL_FACTORIES[spec.type];
|
|
643
|
+
if (!factory) throw new Error(`translateSpatialMark: 未知 spatial mark type "${String(spec.type)}"`);
|
|
644
|
+
assertChannels(spec);
|
|
645
|
+
const options = {};
|
|
646
|
+
for (const key of CHANNEL_KEYS) if (spec[key] !== void 0) options[key] = spec[key];
|
|
647
|
+
for (const key of CHANNEL_FLEX_KEYS) if (spec[key] !== void 0) options[key] = spec[key];
|
|
648
|
+
for (const key of STYLE_KEYS) if (spec[key] !== void 0) options[key] = spec[key];
|
|
649
|
+
if (spec.id !== void 0) options.id = spec.id;
|
|
650
|
+
if (spec.value !== void 0) options.value = spec.value;
|
|
651
|
+
if (spec.binWidth !== void 0) options.binWidth = spec.binWidth;
|
|
652
|
+
if (spec.thresholds !== void 0) options.thresholds = spec.thresholds;
|
|
653
|
+
if (spec.smooth !== void 0) options.smooth = spec.smooth;
|
|
654
|
+
if (spec.bandwidth !== void 0) options.bandwidth = spec.bandwidth;
|
|
655
|
+
if (spec.cellSize !== void 0) options.cellSize = spec.cellSize;
|
|
656
|
+
if (spec.outputs !== void 0) options.outputs = translateOutputs(spec.outputs);
|
|
657
|
+
return factory(resolveMarkData(spec.data, datasets), options);
|
|
658
|
+
}
|
|
659
|
+
/**
|
|
660
|
+
* 单 mark 声明式 → ChartMark;多余样式键对 mark 工厂无害(按名读取)。
|
|
661
|
+
* polar/pie/sankey 等容器与复合 type 不在此处(polar.ts / composite.ts 分支处理)。
|
|
662
|
+
*/
|
|
663
|
+
function translateMark(spec, datasets) {
|
|
664
|
+
if (spec.type === "frame") return translateFrame(spec);
|
|
665
|
+
if (spec.type in SPATIAL_FACTORIES) return translateSpatialMark(spec, datasets);
|
|
666
|
+
const factory = MARK_FACTORIES[spec.type];
|
|
667
|
+
if (!factory) throw new Error(`translateMark: 未知 mark type "${String(spec.type)}"`);
|
|
668
|
+
assertChannels(spec);
|
|
669
|
+
const options = {};
|
|
670
|
+
for (const key of CHANNEL_KEYS) if (spec[key] !== void 0) options[key] = spec[key];
|
|
671
|
+
for (const key of CHANNEL_FLEX_KEYS) if (spec[key] !== void 0) options[key] = spec[key];
|
|
672
|
+
for (const key of STYLE_KEYS) if (spec[key] !== void 0) options[key] = spec[key];
|
|
673
|
+
for (const key of SCALAR_KEYS) if (spec[key] !== void 0) options[key] = spec[key];
|
|
674
|
+
if (spec.id !== void 0) options.id = spec.id;
|
|
675
|
+
if (spec.curve !== void 0) options.curve = spec.type === "violinY" ? translateAreaXCurve(spec.curve) : translateCurve(spec.curve);
|
|
676
|
+
if (spec.layout !== void 0) options.layout = translateLayout(spec.layout);
|
|
677
|
+
return factory(resolveMarkData(spec.data, datasets), options);
|
|
678
|
+
}
|
|
679
|
+
//#endregion
|
|
680
|
+
//#region src/shared/translate/polar.ts
|
|
681
|
+
/**
|
|
682
|
+
* polar 嵌套 mark 翻译:polar 是 marks 数组内的容器(非 definition 顶层字段),
|
|
683
|
+
* 子 mark 为 radial 系;官方 PolarLength 的函数形态(({radius}) => radius * r)在
|
|
684
|
+
* JSON 中以 *Ratio 数值表达,翻译层转函数。
|
|
685
|
+
*/
|
|
686
|
+
const RADIAL_FACTORIES = {
|
|
687
|
+
radialArc,
|
|
688
|
+
radialBarRadius,
|
|
689
|
+
radialBarAngle,
|
|
690
|
+
radialLine,
|
|
691
|
+
radialArea,
|
|
692
|
+
radialDot,
|
|
693
|
+
radialText,
|
|
694
|
+
radialRule
|
|
695
|
+
};
|
|
696
|
+
/** 比例数值 → PolarLength 函数;undefined 缺省不传(库缺省回退) */
|
|
697
|
+
function ratioToLength(ratio) {
|
|
698
|
+
if (ratio === void 0) return void 0;
|
|
699
|
+
return ({ radius }) => radius * ratio;
|
|
700
|
+
}
|
|
701
|
+
/** radial 子 mark 声明式 → PolarMark(channel:angle/radius 弹性,样式透传) */
|
|
702
|
+
function translateRadialMark(spec, datasets) {
|
|
703
|
+
const factory = RADIAL_FACTORIES[spec.type];
|
|
704
|
+
if (!factory) throw new Error(`translatePolar: 未知 radial mark type "${String(spec.type)}"`);
|
|
705
|
+
const options = {};
|
|
706
|
+
for (const key of [
|
|
707
|
+
"angle",
|
|
708
|
+
"angle1",
|
|
709
|
+
"angle2",
|
|
710
|
+
"radius",
|
|
711
|
+
"radius1",
|
|
712
|
+
"radius2",
|
|
713
|
+
"key",
|
|
714
|
+
"z",
|
|
715
|
+
"color",
|
|
716
|
+
"text",
|
|
717
|
+
"startAngle",
|
|
718
|
+
"endAngle",
|
|
719
|
+
"padAngle"
|
|
720
|
+
]) if (spec[key] !== void 0) options[key] = spec[key];
|
|
721
|
+
for (const key of [
|
|
722
|
+
"stroke",
|
|
723
|
+
"strokeWidth",
|
|
724
|
+
"strokeOpacity",
|
|
725
|
+
"strokeDasharray",
|
|
726
|
+
"fill",
|
|
727
|
+
"fillOpacity",
|
|
728
|
+
"fontSize",
|
|
729
|
+
"fontWeight",
|
|
730
|
+
"opacity",
|
|
731
|
+
"cornerRadius",
|
|
732
|
+
"points",
|
|
733
|
+
"r",
|
|
734
|
+
"dx",
|
|
735
|
+
"dy",
|
|
736
|
+
"anchor",
|
|
737
|
+
"baseline",
|
|
738
|
+
"rotate",
|
|
739
|
+
"radiusOffset"
|
|
740
|
+
]) if (spec[key] !== void 0) options[key] = spec[key];
|
|
741
|
+
if (spec.id !== void 0) options.id = spec.id;
|
|
742
|
+
if (spec.curve !== void 0) options.curve = translateRadialCurve(spec.curve);
|
|
743
|
+
if (spec.innerRadiusRatio !== void 0) options.innerRadius = ratioToLength(spec.innerRadiusRatio);
|
|
744
|
+
if (spec.outerRadiusRatio !== void 0) options.outerRadius = ratioToLength(spec.outerRadiusRatio);
|
|
745
|
+
return factory(resolveMarkData(spec.data, datasets), options);
|
|
746
|
+
}
|
|
747
|
+
/** polar guides 声明式 → PolarGuide */
|
|
748
|
+
function translatePolarGuide(spec) {
|
|
749
|
+
const factory = spec.kind === "radialGrid" ? radialGrid : angleGrid;
|
|
750
|
+
const options = {};
|
|
751
|
+
for (const key of [
|
|
752
|
+
"values",
|
|
753
|
+
"ticks",
|
|
754
|
+
"shape",
|
|
755
|
+
"labels",
|
|
756
|
+
"labelAngle",
|
|
757
|
+
"labelOffset"
|
|
758
|
+
]) if (spec[key] !== void 0) options[key] = spec[key];
|
|
759
|
+
return factory(options);
|
|
760
|
+
}
|
|
761
|
+
/** polar axis 声明式(复用 scale 枚举;wrap 仅 angle 轴)→ PolarAngleOptions/PolarRadiusOptions */
|
|
762
|
+
function translatePolarAxis(spec) {
|
|
763
|
+
if (!spec) return void 0;
|
|
764
|
+
const axis = {};
|
|
765
|
+
if (spec.scale) axis.scale = translateScale(spec.scale);
|
|
766
|
+
if (spec.nice !== void 0) axis.nice = spec.nice;
|
|
767
|
+
return axis;
|
|
768
|
+
}
|
|
769
|
+
/**
|
|
770
|
+
* polar 容器:{ type:'polar', marks:[radialXxx...], angleAxis, radiusAxis, radiusRatio,
|
|
771
|
+
* startAngle, endAngle, inset, polarGuides } → polar({...})。
|
|
772
|
+
* extraMarks:调用方预组装的 PolarMark(sunburst 等自包含层级 mark 不走 radial 直译)。
|
|
773
|
+
*/
|
|
774
|
+
function translatePolar(spec, datasets, extraMarks = []) {
|
|
775
|
+
const options = { marks: [...(spec.marks ?? []).map((m) => translateRadialMark(m, datasets)), ...extraMarks] };
|
|
776
|
+
if (spec.radiusRatio !== void 0) options.radiusRatio = spec.radiusRatio;
|
|
777
|
+
if (spec.startAngle !== void 0) options.startAngle = spec.startAngle;
|
|
778
|
+
if (spec.endAngle !== void 0) options.endAngle = spec.endAngle;
|
|
779
|
+
if (spec.inset !== void 0) options.inset = spec.inset;
|
|
780
|
+
if (spec.id !== void 0) options.id = spec.id;
|
|
781
|
+
const angle = translatePolarAxis(spec.angleAxis);
|
|
782
|
+
const radius = translatePolarAxis(spec.radiusAxis);
|
|
783
|
+
const hasRadialBarRadius = (spec.marks ?? []).some((m) => m.type === "radialBarRadius");
|
|
784
|
+
const hasRadialBarAngle = (spec.marks ?? []).some((m) => m.type === "radialBarAngle");
|
|
785
|
+
options.angle = angle ?? { scale: () => hasRadialBarRadius ? scaleBand() : scalePoint() };
|
|
786
|
+
options.radius = radius ?? { scale: hasRadialBarAngle ? () => scaleBand() : scaleLinear };
|
|
787
|
+
if (spec.polarGuides?.length) options.guides = spec.polarGuides.map(translatePolarGuide);
|
|
788
|
+
return polar(options);
|
|
789
|
+
}
|
|
790
|
+
/**
|
|
791
|
+
* pie 命名复合:{ type:'pie', data, value, key, color, innerRadiusRatio, ... } →
|
|
792
|
+
* pie() 角度分配预处理 + polar(radialArc) 一体展开(donut = innerRadiusRatio > 0)。
|
|
793
|
+
* startAngle/endAngle/padAngle 并入 transform options(transform 侧字段名 gapAngle;
|
|
794
|
+
* 容器层同名字段只作用于 angle scale 映射,radialArc 直接读 datum 弧度——
|
|
795
|
+
* 不传 transform 则声明静默失效)。返回 polar 容器 mark,definition 层按 polar 族
|
|
796
|
+
* 约定处理无轴(见 definition.ts)。
|
|
797
|
+
*/
|
|
798
|
+
function translatePie(spec, datasets) {
|
|
799
|
+
const rows = resolveMarkData(spec.data, datasets);
|
|
800
|
+
if (!spec.value) throw new Error("translatePie: pie 复合 mark 必须有 value 字段");
|
|
801
|
+
const transformOptions = { value: spec.value };
|
|
802
|
+
if (spec.padAngle !== void 0) transformOptions.gapAngle = spec.padAngle;
|
|
803
|
+
if (spec.startAngle !== void 0) transformOptions.startAngle = spec.startAngle;
|
|
804
|
+
if (spec.endAngle !== void 0) transformOptions.endAngle = spec.endAngle;
|
|
805
|
+
return translatePolar({
|
|
806
|
+
type: "polar",
|
|
807
|
+
marks: [{
|
|
808
|
+
type: "radialArc",
|
|
809
|
+
data: pie(rows, transformOptions),
|
|
810
|
+
key: spec.key,
|
|
811
|
+
color: spec.color,
|
|
812
|
+
cornerRadius: spec.cornerRadius,
|
|
813
|
+
innerRadiusRatio: spec.innerRadiusRatio,
|
|
814
|
+
outerRadiusRatio: spec.outerRadiusRatio,
|
|
815
|
+
stroke: spec.stroke,
|
|
816
|
+
strokeWidth: spec.strokeWidth,
|
|
817
|
+
fillOpacity: spec.fillOpacity,
|
|
818
|
+
opacity: spec.opacity
|
|
819
|
+
}],
|
|
820
|
+
radiusRatio: spec.radiusRatio,
|
|
821
|
+
inset: spec.inset,
|
|
822
|
+
polarGuides: spec.polarGuides
|
|
823
|
+
}, datasets);
|
|
824
|
+
}
|
|
825
|
+
/** polar 族 type 判定(definition 组装层据此应用无轴约定) */
|
|
826
|
+
const POLAR_FAMILY_TYPES = /* @__PURE__ */ new Set([
|
|
827
|
+
"polar",
|
|
828
|
+
"pie",
|
|
829
|
+
"sunburst"
|
|
830
|
+
]);
|
|
831
|
+
//#endregion
|
|
832
|
+
//#region src/shared/translate/composite.ts
|
|
833
|
+
const looseLink = link;
|
|
834
|
+
const looseRect = rect;
|
|
835
|
+
const looseDot = dot;
|
|
836
|
+
/**
|
|
837
|
+
* 层级数据源声明:path 模式(delimiter 拆分)或 nodeId/parentId 平铺模式。
|
|
838
|
+
* idKey:库内命名不一致——treeLayout 平铺模式键名是 id,sunburst/treemap 是 nodeId。
|
|
839
|
+
*/
|
|
840
|
+
function hierarchyOptions(spec, idKey) {
|
|
841
|
+
if (spec.path) {
|
|
842
|
+
const options = { path: spec.path };
|
|
843
|
+
if (spec.delimiter !== void 0) options.delimiter = spec.delimiter;
|
|
844
|
+
return options;
|
|
845
|
+
}
|
|
846
|
+
if (spec.nodeId && spec.parentId) return {
|
|
847
|
+
[idKey]: spec.nodeId,
|
|
848
|
+
parentId: spec.parentId
|
|
849
|
+
};
|
|
850
|
+
throw new Error(`translateComposite: ${spec.type} 层级数据源必须声明 path 或 nodeId+parentId`);
|
|
851
|
+
}
|
|
852
|
+
/** sankey:固化 marks 回调 = link(布局连线) + rect(布局节点),样式取自声明 */
|
|
853
|
+
function translateSankey(spec, datasets) {
|
|
854
|
+
const nodes = resolveMarkData(spec.nodes, datasets);
|
|
855
|
+
const links = resolveMarkData(spec.links, datasets);
|
|
856
|
+
if (!spec.nodeKey || !spec.source || !spec.target || !spec.value) throw new Error("translateSankey: sankey 必须声明 nodeKey/source/target/value");
|
|
857
|
+
const options = {
|
|
858
|
+
nodes,
|
|
859
|
+
links,
|
|
860
|
+
nodeKey: spec.nodeKey,
|
|
861
|
+
source: spec.source,
|
|
862
|
+
target: spec.target,
|
|
863
|
+
value: spec.value,
|
|
864
|
+
marks: (context) => [looseLink(context.links, {
|
|
865
|
+
x1: "x1",
|
|
866
|
+
y1: "y1",
|
|
867
|
+
x2: "x2",
|
|
868
|
+
y2: "y2",
|
|
869
|
+
key: "key",
|
|
870
|
+
strokeWidth: "width",
|
|
871
|
+
lineCap: "butt",
|
|
872
|
+
stroke: spec.stroke ?? void 0,
|
|
873
|
+
strokeOpacity: spec.strokeOpacity ?? .55
|
|
874
|
+
}), looseRect(context.nodes, {
|
|
875
|
+
x1: "x0",
|
|
876
|
+
x2: "x1",
|
|
877
|
+
y1: "y0",
|
|
878
|
+
y2: "y1",
|
|
879
|
+
key: "key",
|
|
880
|
+
color: spec.color ?? "key",
|
|
881
|
+
fillOpacity: spec.fillOpacity ?? void 0
|
|
882
|
+
})]
|
|
883
|
+
};
|
|
884
|
+
if (spec.id !== void 0) options.id = spec.id;
|
|
885
|
+
if (spec.align !== void 0) options.align = spec.align;
|
|
886
|
+
if (spec.linkKey !== void 0) options.linkKey = spec.linkKey;
|
|
887
|
+
if (spec.nodeWidth !== void 0) options.nodeWidth = spec.nodeWidth;
|
|
888
|
+
if (spec.nodePadding !== void 0) options.nodePadding = spec.nodePadding;
|
|
889
|
+
if (spec.iterations !== void 0) options.iterations = spec.iterations;
|
|
890
|
+
if (spec.inset !== void 0) options.inset = spec.inset;
|
|
891
|
+
return sankeyDiagram(options);
|
|
892
|
+
}
|
|
893
|
+
/** sunburst:返回 PolarMark,按 polar 族约定包一层 polar 容器(与 pie 同模式) */
|
|
894
|
+
function translateSunburst(spec, datasets) {
|
|
895
|
+
const rows = resolveMarkData(spec.data, datasets);
|
|
896
|
+
if (!spec.value) throw new Error("translateSunburst: sunburst 必须有 value 字段");
|
|
897
|
+
const options = {
|
|
898
|
+
...hierarchyOptions(spec, "nodeId"),
|
|
899
|
+
value: spec.value
|
|
900
|
+
};
|
|
901
|
+
if (spec.id !== void 0) options.id = spec.id;
|
|
902
|
+
if (spec.color !== void 0) options.color = spec.color;
|
|
903
|
+
if (spec.z !== void 0) options.z = spec.z;
|
|
904
|
+
if (spec.visibleDepth !== void 0) options.visibleDepth = spec.visibleDepth;
|
|
905
|
+
if (spec.ringPadding !== void 0) options.ringPadding = spec.ringPadding;
|
|
906
|
+
for (const key of [
|
|
907
|
+
"stroke",
|
|
908
|
+
"strokeWidth",
|
|
909
|
+
"strokeOpacity",
|
|
910
|
+
"fillOpacity",
|
|
911
|
+
"opacity"
|
|
912
|
+
]) if (spec[key] !== void 0) options[key] = spec[key];
|
|
913
|
+
const mark = sunburst(rows, options);
|
|
914
|
+
return translatePolar({
|
|
915
|
+
type: "polar",
|
|
916
|
+
marks: [],
|
|
917
|
+
radiusRatio: spec.radiusRatio,
|
|
918
|
+
startAngle: spec.startAngle,
|
|
919
|
+
endAngle: spec.endAngle,
|
|
920
|
+
inset: spec.inset,
|
|
921
|
+
polarGuides: spec.polarGuides
|
|
922
|
+
}, datasets, [mark]);
|
|
923
|
+
}
|
|
924
|
+
/** treemap:自包含 mark,直接返回 ChartMark */
|
|
925
|
+
function translateTreemap(spec, datasets) {
|
|
926
|
+
const rows = resolveMarkData(spec.data, datasets);
|
|
927
|
+
if (!spec.value) throw new Error("translateTreemap: treemap 必须有 value 字段");
|
|
928
|
+
const options = {
|
|
929
|
+
...hierarchyOptions(spec, "nodeId"),
|
|
930
|
+
value: spec.value
|
|
931
|
+
};
|
|
932
|
+
if (spec.id !== void 0) options.id = spec.id;
|
|
933
|
+
if (spec.color !== void 0) options.color = spec.color;
|
|
934
|
+
if (spec.method !== void 0) options.method = spec.method;
|
|
935
|
+
if (spec.ratio !== void 0) options.ratio = spec.ratio;
|
|
936
|
+
if (spec.round !== void 0) options.round = spec.round;
|
|
937
|
+
if (spec.paddingInner !== void 0) options.paddingInner = spec.paddingInner;
|
|
938
|
+
if (spec.paddingOuter !== void 0) options.paddingOuter = spec.paddingOuter;
|
|
939
|
+
if (typeof spec.label === "string") options.label = spec.label;
|
|
940
|
+
if (spec.labelPadding !== void 0) options.labelPadding = spec.labelPadding;
|
|
941
|
+
if (spec.labelFontSize !== void 0) options.labelFontSize = spec.labelFontSize;
|
|
942
|
+
if (spec.labelFontWeight !== void 0) options.labelFontWeight = spec.labelFontWeight;
|
|
943
|
+
if (spec.inset !== void 0) options.inset = spec.inset;
|
|
944
|
+
for (const key of [
|
|
945
|
+
"fillOpacity",
|
|
946
|
+
"stroke",
|
|
947
|
+
"strokeOpacity",
|
|
948
|
+
"strokeWidth"
|
|
949
|
+
]) if (spec[key] !== void 0) options[key] = spec[key];
|
|
950
|
+
return treemap(rows, options);
|
|
951
|
+
}
|
|
952
|
+
/** tree:treeLayout 纯布局 + 固化 link(连接线)+dot(节点) 双 mark 组合 */
|
|
953
|
+
function translateTree(spec, datasets) {
|
|
954
|
+
const rows = resolveMarkData(spec.data, datasets);
|
|
955
|
+
const options = { ...hierarchyOptions(spec, "id") };
|
|
956
|
+
if (spec.orientation !== void 0) options.orientation = spec.orientation;
|
|
957
|
+
if (spec.nodeSize !== void 0) options.nodeSize = spec.nodeSize;
|
|
958
|
+
const layout = treeLayout(rows, options);
|
|
959
|
+
return [looseLink(layout.links, {
|
|
960
|
+
x1: "x1",
|
|
961
|
+
y1: "y1",
|
|
962
|
+
x2: "x2",
|
|
963
|
+
y2: "y2",
|
|
964
|
+
key: "id",
|
|
965
|
+
stroke: spec.stroke ?? void 0,
|
|
966
|
+
strokeOpacity: spec.strokeOpacity ?? .55,
|
|
967
|
+
strokeWidth: spec.strokeWidth ?? void 0
|
|
968
|
+
}), looseDot(layout.nodes, {
|
|
969
|
+
x: "x",
|
|
970
|
+
y: "y",
|
|
971
|
+
key: "id",
|
|
972
|
+
r: spec.r ?? 4.5,
|
|
973
|
+
color: spec.color ?? void 0,
|
|
974
|
+
strokeWidth: 1.5
|
|
975
|
+
})];
|
|
976
|
+
}
|
|
977
|
+
/** 缺省力集:官方示例常规四力的常量形态(函数 accessor 不可 JSON,由库缺省接管) */
|
|
978
|
+
const DEFAULT_FORCES = [
|
|
979
|
+
{ type: "link" },
|
|
980
|
+
{ type: "manyBody" },
|
|
981
|
+
{
|
|
982
|
+
type: "center",
|
|
983
|
+
x: 0,
|
|
984
|
+
y: 0
|
|
985
|
+
},
|
|
986
|
+
{ type: "collide" }
|
|
987
|
+
];
|
|
988
|
+
function translateForce(spec) {
|
|
989
|
+
const options = { type: spec.type };
|
|
990
|
+
const rest = spec;
|
|
991
|
+
for (const key of [
|
|
992
|
+
"distance",
|
|
993
|
+
"strength",
|
|
994
|
+
"x",
|
|
995
|
+
"y",
|
|
996
|
+
"radius"
|
|
997
|
+
]) if (rest[key] !== void 0) options[key] = rest[key];
|
|
998
|
+
return options;
|
|
999
|
+
}
|
|
1000
|
+
/** forceGraph:forceLayout 仿真 + 固化 link(连线)+dot(节点);轴域从布局结果推导回传 */
|
|
1001
|
+
function translateForceGraph(spec, datasets) {
|
|
1002
|
+
const nodes = resolveMarkData(spec.nodes, datasets);
|
|
1003
|
+
const links = resolveMarkData(spec.links, datasets);
|
|
1004
|
+
if (!spec.nodeKey || !spec.source || !spec.target) throw new Error("translateForceGraph: forceGraph 必须声明 nodeKey/source/target");
|
|
1005
|
+
const options = {
|
|
1006
|
+
nodeKey: spec.nodeKey,
|
|
1007
|
+
source: spec.source,
|
|
1008
|
+
target: spec.target,
|
|
1009
|
+
forces: (spec.forces ?? DEFAULT_FORCES).map(translateForce)
|
|
1010
|
+
};
|
|
1011
|
+
if (spec.iterations !== void 0) options.iterations = spec.iterations;
|
|
1012
|
+
if (spec.domainPadding !== void 0) options.domainPadding = spec.domainPadding;
|
|
1013
|
+
const graph = forceLayout(nodes, links, options);
|
|
1014
|
+
return {
|
|
1015
|
+
marks: [looseLink(graph.links, {
|
|
1016
|
+
x1: "x1",
|
|
1017
|
+
y1: "y1",
|
|
1018
|
+
x2: "x2",
|
|
1019
|
+
y2: "y2",
|
|
1020
|
+
stroke: spec.stroke ?? void 0,
|
|
1021
|
+
strokeOpacity: spec.strokeOpacity ?? .6,
|
|
1022
|
+
strokeWidth: spec.strokeWidth ?? void 0
|
|
1023
|
+
}), looseDot(graph.nodes, {
|
|
1024
|
+
x: "x",
|
|
1025
|
+
y: "y",
|
|
1026
|
+
key: spec.nodeKey,
|
|
1027
|
+
color: spec.color ?? void 0,
|
|
1028
|
+
r: spec.r ?? 5,
|
|
1029
|
+
strokeWidth: 1.5
|
|
1030
|
+
})],
|
|
1031
|
+
xDomain: [graph.xDomain[0], graph.xDomain[1]],
|
|
1032
|
+
yDomain: [graph.yDomain[0], graph.yDomain[1]]
|
|
1033
|
+
};
|
|
1034
|
+
}
|
|
1035
|
+
/** d3-geo 投影名称枚举 → 工厂(descriptor.type 形态:() => projection) */
|
|
1036
|
+
const GEO_PROJECTION_FACTORIES = {
|
|
1037
|
+
mercator: geoMercator,
|
|
1038
|
+
orthographic: geoOrthographic,
|
|
1039
|
+
naturalEarth1: geoNaturalEarth1,
|
|
1040
|
+
albersUsa: geoAlbersUsa,
|
|
1041
|
+
equalEarth: geoEqualEarth,
|
|
1042
|
+
identity: geoIdentity
|
|
1043
|
+
};
|
|
1044
|
+
/** geoShape:projection 名称枚举 → descriptor {type, fit, inset};fit:'data' 由库拟合数据本体 */
|
|
1045
|
+
function translateGeoShape(spec, datasets) {
|
|
1046
|
+
const rows = resolveMarkData(spec.data, datasets);
|
|
1047
|
+
const projectionName = spec.projection ?? "mercator";
|
|
1048
|
+
const factory = GEO_PROJECTION_FACTORIES[projectionName];
|
|
1049
|
+
if (!factory) throw new Error(`translateGeoShape: 未知投影 "${String(projectionName)}"`);
|
|
1050
|
+
const options = { projection: {
|
|
1051
|
+
type: factory,
|
|
1052
|
+
fit: spec.fit ?? "data",
|
|
1053
|
+
...spec.inset !== void 0 ? { inset: spec.inset } : {}
|
|
1054
|
+
} };
|
|
1055
|
+
if (spec.id !== void 0) options.id = spec.id;
|
|
1056
|
+
if (spec.key !== void 0) options.key = spec.key;
|
|
1057
|
+
if (spec.color !== void 0) options.color = spec.color;
|
|
1058
|
+
if (spec.r !== void 0) options.r = spec.r;
|
|
1059
|
+
for (const key of [
|
|
1060
|
+
"fill",
|
|
1061
|
+
"fillOpacity",
|
|
1062
|
+
"stroke",
|
|
1063
|
+
"strokeOpacity",
|
|
1064
|
+
"strokeWidth",
|
|
1065
|
+
"strokeDasharray",
|
|
1066
|
+
"opacity"
|
|
1067
|
+
]) if (spec[key] !== void 0) options[key] = spec[key];
|
|
1068
|
+
return geoShape(rows, options);
|
|
1069
|
+
}
|
|
1070
|
+
/**
|
|
1071
|
+
* facet:chart 回调固化为递归子 spec 翻译——translateChild 由 definition.ts 注入
|
|
1072
|
+
* (子 spec 翻译与顶层同路径,仅数据集换为 facet 分组行;规避 composite ↔ definition 循环 import)。
|
|
1073
|
+
* label 语义:true 显示分组 key 标签(库的函数形态不可 JSON,布尔即可覆盖声明式场景)。
|
|
1074
|
+
*/
|
|
1075
|
+
function translateFacet(spec, datasets, translateChild) {
|
|
1076
|
+
const rows = resolveMarkData(spec.data, datasets);
|
|
1077
|
+
if (!spec.by) throw new Error("translateFacet: facet 必须声明 by 分组字段");
|
|
1078
|
+
if (!spec.chart) throw new Error("translateFacet: facet 必须声明 chart 子 spec 模板");
|
|
1079
|
+
const childTemplate = spec.chart;
|
|
1080
|
+
const options = {
|
|
1081
|
+
by: spec.by,
|
|
1082
|
+
chart: (facetRows) => translateChild(childTemplate, facetRows)
|
|
1083
|
+
};
|
|
1084
|
+
if (spec.id !== void 0) options.id = spec.id;
|
|
1085
|
+
if (spec.columns !== void 0) options.columns = spec.columns;
|
|
1086
|
+
if (spec.minWidth !== void 0) options.minWidth = spec.minWidth;
|
|
1087
|
+
if (spec.gap !== void 0) options.gap = spec.gap;
|
|
1088
|
+
if (typeof spec.label === "boolean") options.label = spec.label;
|
|
1089
|
+
if (spec.axes !== void 0) options.axes = spec.axes;
|
|
1090
|
+
return facet(rows, options);
|
|
1091
|
+
}
|
|
1092
|
+
/** 复合 type 守卫(definition 组装层据此分流;sunburst 虽属 polar 族约定,分发仍走此处) */
|
|
1093
|
+
const COMPOSITE_TYPES = /* @__PURE__ */ new Set([
|
|
1094
|
+
"sankey",
|
|
1095
|
+
"sunburst",
|
|
1096
|
+
"treemap",
|
|
1097
|
+
"tree",
|
|
1098
|
+
"forceGraph",
|
|
1099
|
+
"geoShape",
|
|
1100
|
+
"facet"
|
|
1101
|
+
]);
|
|
1102
|
+
/**
|
|
1103
|
+
* 复合 mark 分发。tree/forceGraph 展开为多 mark;forceGraph 额外回传轴域推导结果。
|
|
1104
|
+
* translateChild 仅 facet 需要。
|
|
1105
|
+
*/
|
|
1106
|
+
function translateCompositeMark(spec, datasets, translateChild) {
|
|
1107
|
+
switch (spec.type) {
|
|
1108
|
+
case "sankey": return { marks: [translateSankey(spec, datasets)] };
|
|
1109
|
+
case "sunburst": return { marks: [translateSunburst(spec, datasets)] };
|
|
1110
|
+
case "treemap": return { marks: [translateTreemap(spec, datasets)] };
|
|
1111
|
+
case "tree": return { marks: translateTree(spec, datasets) };
|
|
1112
|
+
case "forceGraph": return translateForceGraph(spec, datasets);
|
|
1113
|
+
case "geoShape": return { marks: [translateGeoShape(spec, datasets)] };
|
|
1114
|
+
case "facet": return { marks: [translateFacet(spec, datasets, translateChild)] };
|
|
1115
|
+
default: throw new Error(`translateCompositeMark: 未知复合 mark type "${String(spec.type)}"`);
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
//#endregion
|
|
1119
|
+
//#region src/shared/translate/definition.ts
|
|
1120
|
+
/**
|
|
1121
|
+
* definition 组装:声明式 spec → DomChartDefinition(可直接喂给 <Chart>)。
|
|
1122
|
+
*
|
|
1123
|
+
* 组装约定:
|
|
1124
|
+
* - transforms 管道先于 marks 执行,产物注册进数据集表副本(marks 字符串引用在此解析)
|
|
1125
|
+
* - polar 族(polar/pie/sunburst 全为 polar 容器且无直角 channel)未显式声明 x/y 时
|
|
1126
|
+
* 置 null 显式无轴(库对 polar mark 的 scale 类型为 never,缺省 axis 注入会抛错)
|
|
1127
|
+
* - tree/forceGraph 展开为多 mark;forceGraph 回传的轴域在 x/y 未显式声明时注入
|
|
1128
|
+
* linear scale 实例(仿真坐标以 0 为中心,缺省推断 domain 不包含负半轴余量)
|
|
1129
|
+
*/
|
|
1130
|
+
/** color.legend 声明 → 图例实例;true 等价 { kind:'color' } 缺省配置 */
|
|
1131
|
+
function translateLegend(legend) {
|
|
1132
|
+
const { kind, label, placement, itemWidth, steps, width } = legend === true ? {} : legend;
|
|
1133
|
+
if (kind === "gradient") {
|
|
1134
|
+
const gradient = {};
|
|
1135
|
+
if (label !== void 0) gradient.label = label;
|
|
1136
|
+
if (placement !== void 0) gradient.placement = placement;
|
|
1137
|
+
if (steps !== void 0) gradient.steps = steps;
|
|
1138
|
+
if (width !== void 0) gradient.width = width;
|
|
1139
|
+
return colorGradientLegend(gradient);
|
|
1140
|
+
}
|
|
1141
|
+
const items = {};
|
|
1142
|
+
if (label !== void 0) items.label = label;
|
|
1143
|
+
if (placement !== void 0) items.placement = placement;
|
|
1144
|
+
if (itemWidth !== void 0) items.itemWidth = itemWidth;
|
|
1145
|
+
if (width !== void 0) items.width = width;
|
|
1146
|
+
return colorLegend(items);
|
|
1147
|
+
}
|
|
1148
|
+
/** tooltip 声明:true→缺省开启(undefined),false→关闭,对象→标量子集原样透传 */
|
|
1149
|
+
function translateTooltip(tooltip) {
|
|
1150
|
+
if (tooltip === true) return void 0;
|
|
1151
|
+
if (tooltip === false) return false;
|
|
1152
|
+
const options = {};
|
|
1153
|
+
for (const key of [
|
|
1154
|
+
"placement",
|
|
1155
|
+
"offset",
|
|
1156
|
+
"sticky",
|
|
1157
|
+
"visibility",
|
|
1158
|
+
"anchor",
|
|
1159
|
+
"sort",
|
|
1160
|
+
"items"
|
|
1161
|
+
]) if (tooltip[key] !== void 0) options[key] = tooltip[key];
|
|
1162
|
+
return options;
|
|
1163
|
+
}
|
|
1164
|
+
/**
|
|
1165
|
+
* 时间轴 channel 纠偏:utc/time scale 要求 channel 值为 Date 实例,而 JSON spec 只能
|
|
1166
|
+
* 携带 ISO 字符串——翻译层按轴 scale 声明收集绑定字段,把数据集行映射为 Date 副本
|
|
1167
|
+
* (不改写调用方行对象)。null 原样保留(nullable channel 缺口语义)。
|
|
1168
|
+
*/
|
|
1169
|
+
function coerceTemporalChannels(spec, table) {
|
|
1170
|
+
const temporal = (axis) => axis != null && (axis.scale?.kind === "utc" || axis.scale?.kind === "time");
|
|
1171
|
+
const xTemporal = temporal(spec.x);
|
|
1172
|
+
const yTemporal = temporal(spec.y);
|
|
1173
|
+
if (!xTemporal && !yTemporal) return table;
|
|
1174
|
+
const fieldsByDataset = /* @__PURE__ */ new Map();
|
|
1175
|
+
const bind = (dataRef, field) => {
|
|
1176
|
+
const name = typeof dataRef === "string" ? dataRef : "rows";
|
|
1177
|
+
if (typeof field !== "string") return;
|
|
1178
|
+
let set = fieldsByDataset.get(name);
|
|
1179
|
+
if (!set) fieldsByDataset.set(name, set = /* @__PURE__ */ new Set());
|
|
1180
|
+
set.add(field);
|
|
1181
|
+
};
|
|
1182
|
+
for (const mark of spec.marks) {
|
|
1183
|
+
if (xTemporal) for (const f of [
|
|
1184
|
+
"x",
|
|
1185
|
+
"x1",
|
|
1186
|
+
"x2"
|
|
1187
|
+
]) bind(mark.data, mark[f]);
|
|
1188
|
+
if (yTemporal) for (const f of [
|
|
1189
|
+
"y",
|
|
1190
|
+
"y1",
|
|
1191
|
+
"y2"
|
|
1192
|
+
]) bind(mark.data, mark[f]);
|
|
1193
|
+
}
|
|
1194
|
+
let changed = false;
|
|
1195
|
+
const next = { ...table };
|
|
1196
|
+
for (const [name, fields] of fieldsByDataset) {
|
|
1197
|
+
const rows = table[name];
|
|
1198
|
+
if (!rows?.length) continue;
|
|
1199
|
+
changed = true;
|
|
1200
|
+
next[name] = rows.map((row) => {
|
|
1201
|
+
if (row == null || typeof row !== "object") return row;
|
|
1202
|
+
const source = row;
|
|
1203
|
+
const copy = { ...source };
|
|
1204
|
+
for (const field of fields) {
|
|
1205
|
+
const value = source[field];
|
|
1206
|
+
if (typeof value === "string") copy[field] = new Date(value);
|
|
1207
|
+
}
|
|
1208
|
+
return copy;
|
|
1209
|
+
});
|
|
1210
|
+
}
|
|
1211
|
+
return changed ? next : table;
|
|
1212
|
+
}
|
|
1213
|
+
/**
|
|
1214
|
+
* 单 spec → definition 核心(facet 子 spec 复用):返回原始 spec 对象而非
|
|
1215
|
+
* defineChart 产物——facet chart 回调要求返回 ChartSpec 字面量形态。
|
|
1216
|
+
* rowsOverride:facet 分组行注入(子 spec marks 未声明 data 时绑定分组行)。
|
|
1217
|
+
*/
|
|
1218
|
+
function buildDefinition(spec, datasets, rowsOverride) {
|
|
1219
|
+
const base = { ...datasets };
|
|
1220
|
+
if (rowsOverride !== void 0) base.rows = rowsOverride;
|
|
1221
|
+
const table = coerceTemporalChannels(spec, spec.transforms?.length ? applyTransforms(spec.transforms, base) : base);
|
|
1222
|
+
const marks = [];
|
|
1223
|
+
let derivedXDomain;
|
|
1224
|
+
let derivedYDomain;
|
|
1225
|
+
let polarOnly = spec.marks.length > 0;
|
|
1226
|
+
for (const mark of spec.marks) {
|
|
1227
|
+
const bound = rowsOverride !== void 0 && mark.data === void 0 ? {
|
|
1228
|
+
...mark,
|
|
1229
|
+
data: "rows"
|
|
1230
|
+
} : mark;
|
|
1231
|
+
if (!POLAR_FAMILY_TYPES.has(bound.type)) polarOnly = false;
|
|
1232
|
+
if (bound.type === "polar") marks.push(translatePolar(bound, table));
|
|
1233
|
+
else if (bound.type === "pie") marks.push(translatePie(bound, table));
|
|
1234
|
+
else if (COMPOSITE_TYPES.has(bound.type)) {
|
|
1235
|
+
const result = translateCompositeMark(bound, table, (child, rows) => buildDefinition(child, void 0, rows));
|
|
1236
|
+
marks.push(...result.marks);
|
|
1237
|
+
if (result.xDomain) derivedXDomain = result.xDomain;
|
|
1238
|
+
if (result.yDomain) derivedYDomain = result.yDomain;
|
|
1239
|
+
} else marks.push(translateMark(bound, table));
|
|
1240
|
+
}
|
|
1241
|
+
const definition = {
|
|
1242
|
+
marks,
|
|
1243
|
+
theme: {
|
|
1244
|
+
...defaultChartTheme,
|
|
1245
|
+
...spec.theme
|
|
1246
|
+
}
|
|
1247
|
+
};
|
|
1248
|
+
const xSpec = spec.x === void 0 && polarOnly ? null : spec.x;
|
|
1249
|
+
const ySpec = spec.y === void 0 && polarOnly ? null : spec.y;
|
|
1250
|
+
const x = translateAxis(xSpec ?? void 0, "point");
|
|
1251
|
+
const y = translateAxis(ySpec ?? void 0, "linear");
|
|
1252
|
+
if (xSpec !== null && x) definition.x = x;
|
|
1253
|
+
if (xSpec === null) definition.x = null;
|
|
1254
|
+
if (ySpec !== null && y) definition.y = y;
|
|
1255
|
+
if (ySpec === null) definition.y = null;
|
|
1256
|
+
if (derivedXDomain && spec.x?.scale?.domain === void 0) definition.x = {
|
|
1257
|
+
...definition.x,
|
|
1258
|
+
scale: scaleLinear().domain(derivedXDomain)
|
|
1259
|
+
};
|
|
1260
|
+
if (derivedYDomain && spec.y?.scale?.domain === void 0) definition.y = {
|
|
1261
|
+
...definition.y,
|
|
1262
|
+
scale: scaleLinear().domain(derivedYDomain)
|
|
1263
|
+
};
|
|
1264
|
+
if (spec.margin !== void 0) definition.margin = spec.margin;
|
|
1265
|
+
if (spec.guides !== void 0) definition.guides = spec.guides;
|
|
1266
|
+
else if (polarOnly) definition.guides = false;
|
|
1267
|
+
if (spec.clip !== void 0) definition.clip = spec.clip;
|
|
1268
|
+
if (spec.color !== void 0) {
|
|
1269
|
+
const color = {};
|
|
1270
|
+
if (spec.color.domain !== void 0) color.domain = spec.color.domain;
|
|
1271
|
+
if (spec.color.range !== void 0) color.range = spec.color.range;
|
|
1272
|
+
if (spec.color.legend !== void 0) color.legend = translateLegend(spec.color.legend);
|
|
1273
|
+
definition.color = color;
|
|
1274
|
+
}
|
|
1275
|
+
if (spec.pointer !== void 0) definition.pointer = spec.pointer;
|
|
1276
|
+
if (spec.keyboard !== void 0) definition.keyboard = spec.keyboard;
|
|
1277
|
+
if (spec.focusRing !== void 0) definition.focusRing = spec.focusRing;
|
|
1278
|
+
if (spec.focus !== void 0) definition.focus = spec.focus;
|
|
1279
|
+
if (spec.tooltip !== void 0) {
|
|
1280
|
+
const tooltip = translateTooltip(spec.tooltip);
|
|
1281
|
+
if (tooltip !== void 0) definition.tooltip = tooltip;
|
|
1282
|
+
}
|
|
1283
|
+
return definition;
|
|
1284
|
+
}
|
|
1285
|
+
/**
|
|
1286
|
+
* 声明式 spec → DomChartDefinition(物料渲染入口)。
|
|
1287
|
+
* datasets:命名数据集表(数据顶层化契约),marks 内字符串引用在此解析。
|
|
1288
|
+
*/
|
|
1289
|
+
function translateChartSpec(spec, datasets) {
|
|
1290
|
+
return defineChart(buildDefinition(spec, datasets));
|
|
1291
|
+
}
|
|
1292
|
+
//#endregion
|
|
1293
|
+
//#region src/chart/src/index.vue
|
|
1294
|
+
const _sfc_main$5 = /*@__PURE__*/ defineComponent({
|
|
1295
|
+
name: "CxTanstackChartsChart",
|
|
1296
|
+
inheritAttrs: false,
|
|
1297
|
+
__name: "index",
|
|
1298
|
+
setup(__props) {
|
|
1299
|
+
const ns = useCxBEM("tanstack-charts-chart");
|
|
1300
|
+
const attrs = useAttrs();
|
|
1301
|
+
const { spec, datasets, hostProps, ariaLabel } = useChartProps(attrs);
|
|
1302
|
+
const definition = computed(() => translateChartSpec(spec.value, datasets.value));
|
|
1303
|
+
const isStreaming = computed(() => streamingFields(attrs).includes("definition"));
|
|
1304
|
+
const skeletonHeight = computed(() => {
|
|
1305
|
+
const h = Number(attrs.height);
|
|
1306
|
+
return Number.isFinite(h) && h > 0 ? h : 320;
|
|
1307
|
+
});
|
|
1308
|
+
return (_ctx, _cache) => {
|
|
1309
|
+
return openBlock(), createElementBlock(Fragment, null, [createCommentVNode(" 库根元素 inheritAttrs:false 且多根(host + tooltip Teleport),testid/BEM 类须由本层 wrapper 承担 "), createElementVNode("div", {
|
|
1310
|
+
class: normalizeClass(unref(ns).b()),
|
|
1311
|
+
"data-testid": "cx-tanstack-charts-chart"
|
|
1312
|
+
}, [createCommentVNode(" definition 未闭合期间骨架占位:空壳帧的空 marks 只渲染不可见空 svg,无反馈等同未渲染 "), isStreaming.value ? (openBlock(), createBlock(_sfc_main$6, {
|
|
1313
|
+
key: 0,
|
|
1314
|
+
height: skeletonHeight.value
|
|
1315
|
+
}, null, 8, ["height"])) : (openBlock(), createBlock(unref(Chart), mergeProps({ key: 1 }, unref(hostProps), {
|
|
1316
|
+
definition: definition.value,
|
|
1317
|
+
"aria-label": unref(ariaLabel)
|
|
1318
|
+
}), null, 16, ["definition", "aria-label"]))], 2)], 2112);
|
|
1319
|
+
};
|
|
1320
|
+
}
|
|
1321
|
+
});
|
|
1322
|
+
//#endregion
|
|
1323
|
+
//#region src/chart/index.ts
|
|
1324
|
+
/** 验收页 initial 样本:月度折线(lineY + point/linear 轴 + 网格),示范数据顶层化契约——
|
|
1325
|
+
* rows 顶层数据集 + marks 字符串引用,流式回放可逐行生长 */
|
|
1326
|
+
const initialDefinition = () => ({
|
|
1327
|
+
marks: [{
|
|
1328
|
+
type: "lineY",
|
|
1329
|
+
data: "rows",
|
|
1330
|
+
x: "month",
|
|
1331
|
+
y: "value",
|
|
1332
|
+
stroke: "#2563eb",
|
|
1333
|
+
strokeWidth: 2.5,
|
|
1334
|
+
curve: "monotoneX",
|
|
1335
|
+
points: true
|
|
1336
|
+
}, {
|
|
1337
|
+
type: "ruleY",
|
|
1338
|
+
data: [70],
|
|
1339
|
+
stroke: "#e06e49",
|
|
1340
|
+
strokeWidth: 2,
|
|
1341
|
+
strokeDasharray: "6 6"
|
|
1342
|
+
}],
|
|
1343
|
+
x: { scale: { kind: "point" } },
|
|
1344
|
+
y: {
|
|
1345
|
+
scale: { kind: "linear" },
|
|
1346
|
+
grid: true,
|
|
1347
|
+
nice: true
|
|
1348
|
+
}
|
|
1349
|
+
});
|
|
1350
|
+
const initialRows = () => [
|
|
1351
|
+
{
|
|
1352
|
+
month: "Jan",
|
|
1353
|
+
value: 40
|
|
1354
|
+
},
|
|
1355
|
+
{
|
|
1356
|
+
month: "Feb",
|
|
1357
|
+
value: 62
|
|
1358
|
+
},
|
|
1359
|
+
{
|
|
1360
|
+
month: "Mar",
|
|
1361
|
+
value: 55
|
|
1362
|
+
},
|
|
1363
|
+
{
|
|
1364
|
+
month: "Apr",
|
|
1365
|
+
value: 78
|
|
1366
|
+
},
|
|
1367
|
+
{
|
|
1368
|
+
month: "May",
|
|
1369
|
+
value: 70
|
|
1370
|
+
},
|
|
1371
|
+
{
|
|
1372
|
+
month: "Jun",
|
|
1373
|
+
value: 92
|
|
1374
|
+
}
|
|
1375
|
+
];
|
|
1376
|
+
var chart_default = define({
|
|
1377
|
+
name: "图表",
|
|
1378
|
+
description: "TanStack Charts 通用图表(cx-chart):definition 为纯 JSON 声明式投影(marks/x/y/theme/tooltip 标量子集),数据顶层化——rows/nodes/links 命名数据集与 definition 平级、marks 内字符串引用,经翻译层组装渲染;scale/curve 以枚举声明,channel 为字段名。",
|
|
1379
|
+
key: "cx-chart",
|
|
1380
|
+
icon: "i-tabler-chart-line",
|
|
1381
|
+
component: _sfc_main$5,
|
|
1382
|
+
props: {
|
|
1383
|
+
definition: {
|
|
1384
|
+
name: "图表定义",
|
|
1385
|
+
type: "json",
|
|
1386
|
+
initial: initialDefinition
|
|
1387
|
+
},
|
|
1388
|
+
rows: {
|
|
1389
|
+
name: "数据集",
|
|
1390
|
+
type: "json",
|
|
1391
|
+
initial: initialRows
|
|
1392
|
+
},
|
|
1393
|
+
height: {
|
|
1394
|
+
name: "高度",
|
|
1395
|
+
type: "number",
|
|
1396
|
+
initial: 320
|
|
1397
|
+
},
|
|
1398
|
+
ariaLabel: {
|
|
1399
|
+
name: "无障碍标签",
|
|
1400
|
+
type: "short",
|
|
1401
|
+
initial: "月度趋势图"
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
});
|
|
1405
|
+
//#endregion
|
|
1406
|
+
//#region src/shared/use-preset-chart.ts
|
|
1407
|
+
/**
|
|
1408
|
+
* 预设物料的 spec 组装:把扁平通道 props(data 行数组 + x/y 字段名 + curve 枚举)
|
|
1409
|
+
* 投影为单 mark 的 CxChartSpec,与通用 chart 物料共用翻译层与宿主 props 分馏。
|
|
1410
|
+
*
|
|
1411
|
+
* 通道缺席回退固定字段名(x→'x'、y→'y')而非省略:translateMark 对非字符串 channel
|
|
1412
|
+
* 显式抛错,流式中间态/编辑器清空场景下回退保证规格恒可翻译;data 缺席回退空数组
|
|
1413
|
+
* (mark 渲染为空而非抛错)。meta.props 的 initial 三元组(data 行键/x 值/y 值)
|
|
1414
|
+
* 必须自洽——initial 不参与运行时校验,漂移无任何报错(vtu 既有教训)。
|
|
1415
|
+
*/
|
|
1416
|
+
function usePresetChart(attrs, options) {
|
|
1417
|
+
const { hostProps, ariaLabel } = useChartHostProps(attrs);
|
|
1418
|
+
return {
|
|
1419
|
+
spec: computed(() => {
|
|
1420
|
+
const mark = {
|
|
1421
|
+
type: options.markType,
|
|
1422
|
+
data: Array.isArray(attrs.data) ? attrs.data : [],
|
|
1423
|
+
x: typeof attrs.x === "string" && attrs.x ? attrs.x : "x",
|
|
1424
|
+
y: typeof attrs.y === "string" && attrs.y ? attrs.y : "y"
|
|
1425
|
+
};
|
|
1426
|
+
if (options.withCurve && typeof attrs.curve === "string" && CX_CHART_CURVE_NAMES.includes(attrs.curve)) mark.curve = attrs.curve;
|
|
1427
|
+
return {
|
|
1428
|
+
marks: [mark],
|
|
1429
|
+
x: { scale: { kind: options.xScale } },
|
|
1430
|
+
y: {
|
|
1431
|
+
scale: { kind: "linear" },
|
|
1432
|
+
grid: true
|
|
1433
|
+
}
|
|
1434
|
+
};
|
|
1435
|
+
}),
|
|
1436
|
+
hostProps,
|
|
1437
|
+
ariaLabel
|
|
1438
|
+
};
|
|
1439
|
+
}
|
|
1440
|
+
//#endregion
|
|
1441
|
+
//#region src/line/src/index.vue
|
|
1442
|
+
const _sfc_main$4 = /*@__PURE__*/ defineComponent({
|
|
1443
|
+
name: "CxTanstackChartsLine",
|
|
1444
|
+
inheritAttrs: false,
|
|
1445
|
+
__name: "index",
|
|
1446
|
+
setup(__props) {
|
|
1447
|
+
const ns = useCxBEM("tanstack-charts-line");
|
|
1448
|
+
const { spec, hostProps, ariaLabel } = usePresetChart(useAttrs(), {
|
|
1449
|
+
markType: "lineY",
|
|
1450
|
+
xScale: "point",
|
|
1451
|
+
withCurve: true
|
|
1452
|
+
});
|
|
1453
|
+
const definition = computed(() => translateChartSpec(spec.value));
|
|
1454
|
+
return (_ctx, _cache) => {
|
|
1455
|
+
return openBlock(), createElementBlock(Fragment, null, [createCommentVNode(" 库根元素 inheritAttrs:false 且多根,testid/BEM 类由本层 wrapper 承担 "), createElementVNode("div", {
|
|
1456
|
+
class: normalizeClass(unref(ns).b()),
|
|
1457
|
+
"data-testid": "cx-tanstack-charts-line"
|
|
1458
|
+
}, [createVNode(unref(Chart), mergeProps(unref(hostProps), {
|
|
1459
|
+
definition: definition.value,
|
|
1460
|
+
"aria-label": unref(ariaLabel)
|
|
1461
|
+
}), null, 16, ["definition", "aria-label"])], 2)], 2112);
|
|
1462
|
+
};
|
|
1463
|
+
}
|
|
1464
|
+
});
|
|
1465
|
+
//#endregion
|
|
1466
|
+
//#region src/shared/curve-options.ts
|
|
1467
|
+
/**
|
|
1468
|
+
* curve select 的 options 在 line/area 两个预设间共享:
|
|
1469
|
+
* 抽常量防双侧漂移——选项值集须与翻译层 CURVE_FACTORIES 枚举保持一致,
|
|
1470
|
+
* 一致性由 presets 测试的「curve select 值集与翻译层曲线枚举一致」锁定。
|
|
1471
|
+
*/
|
|
1472
|
+
const CURVE_SELECT_OPTIONS = [
|
|
1473
|
+
{
|
|
1474
|
+
label: "单调平滑",
|
|
1475
|
+
value: "monotoneX"
|
|
1476
|
+
},
|
|
1477
|
+
{
|
|
1478
|
+
label: "直线",
|
|
1479
|
+
value: "linear"
|
|
1480
|
+
},
|
|
1481
|
+
{
|
|
1482
|
+
label: "阶梯",
|
|
1483
|
+
value: "step"
|
|
1484
|
+
},
|
|
1485
|
+
{
|
|
1486
|
+
label: "先横后纵",
|
|
1487
|
+
value: "stepAfter"
|
|
1488
|
+
},
|
|
1489
|
+
{
|
|
1490
|
+
label: "先纵后横",
|
|
1491
|
+
value: "stepBefore"
|
|
1492
|
+
},
|
|
1493
|
+
{
|
|
1494
|
+
label: "B 样条",
|
|
1495
|
+
value: "basis"
|
|
1496
|
+
},
|
|
1497
|
+
{
|
|
1498
|
+
label: "自然样条",
|
|
1499
|
+
value: "natural"
|
|
1500
|
+
}
|
|
1501
|
+
];
|
|
1502
|
+
//#endregion
|
|
1503
|
+
//#region src/line/index.ts
|
|
1504
|
+
/** 验收页 initial 样本:月度访问趋势;x/y 字段名与 data 行键严格自洽 */
|
|
1505
|
+
const initialData$4 = () => [
|
|
1506
|
+
{
|
|
1507
|
+
month: "1月",
|
|
1508
|
+
visits: 3200
|
|
1509
|
+
},
|
|
1510
|
+
{
|
|
1511
|
+
month: "2月",
|
|
1512
|
+
visits: 4100
|
|
1513
|
+
},
|
|
1514
|
+
{
|
|
1515
|
+
month: "3月",
|
|
1516
|
+
visits: 3800
|
|
1517
|
+
},
|
|
1518
|
+
{
|
|
1519
|
+
month: "4月",
|
|
1520
|
+
visits: 5200
|
|
1521
|
+
},
|
|
1522
|
+
{
|
|
1523
|
+
month: "5月",
|
|
1524
|
+
visits: 4900
|
|
1525
|
+
},
|
|
1526
|
+
{
|
|
1527
|
+
month: "6月",
|
|
1528
|
+
visits: 6300
|
|
1529
|
+
},
|
|
1530
|
+
{
|
|
1531
|
+
month: "7月",
|
|
1532
|
+
visits: 7100
|
|
1533
|
+
},
|
|
1534
|
+
{
|
|
1535
|
+
month: "8月",
|
|
1536
|
+
visits: 6800
|
|
1537
|
+
}
|
|
1538
|
+
];
|
|
1539
|
+
var line_default = define({
|
|
1540
|
+
name: "折线图",
|
|
1541
|
+
description: "TanStack Charts 折线图预设:data 行数组 + x/y 字段名 + curve 枚举,单 mark(lineY)规格经翻译层组装渲染。",
|
|
1542
|
+
key: "cx-tanstack-charts-line",
|
|
1543
|
+
icon: "i-tabler-chart-line",
|
|
1544
|
+
component: _sfc_main$4,
|
|
1545
|
+
props: {
|
|
1546
|
+
x: {
|
|
1547
|
+
name: "X 字段",
|
|
1548
|
+
type: "short",
|
|
1549
|
+
initial: "month"
|
|
1550
|
+
},
|
|
1551
|
+
y: {
|
|
1552
|
+
name: "Y 字段",
|
|
1553
|
+
type: "short",
|
|
1554
|
+
initial: "visits"
|
|
1555
|
+
},
|
|
1556
|
+
curve: {
|
|
1557
|
+
name: "曲线插值",
|
|
1558
|
+
type: "select",
|
|
1559
|
+
initial: "monotoneX",
|
|
1560
|
+
options: CURVE_SELECT_OPTIONS
|
|
1561
|
+
},
|
|
1562
|
+
data: {
|
|
1563
|
+
name: "数据",
|
|
1564
|
+
type: "json",
|
|
1565
|
+
initial: initialData$4
|
|
1566
|
+
},
|
|
1567
|
+
height: {
|
|
1568
|
+
name: "高度",
|
|
1569
|
+
type: "number",
|
|
1570
|
+
initial: 320
|
|
1571
|
+
},
|
|
1572
|
+
ariaLabel: {
|
|
1573
|
+
name: "无障碍标签",
|
|
1574
|
+
type: "short",
|
|
1575
|
+
initial: "折线图"
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
});
|
|
1579
|
+
//#endregion
|
|
1580
|
+
//#region src/bar/src/index.vue
|
|
1581
|
+
const _sfc_main$3 = /*@__PURE__*/ defineComponent({
|
|
1582
|
+
name: "CxTanstackChartsBar",
|
|
1583
|
+
inheritAttrs: false,
|
|
1584
|
+
__name: "index",
|
|
1585
|
+
setup(__props) {
|
|
1586
|
+
const ns = useCxBEM("tanstack-charts-bar");
|
|
1587
|
+
const { spec, hostProps, ariaLabel } = usePresetChart(useAttrs(), {
|
|
1588
|
+
markType: "barY",
|
|
1589
|
+
xScale: "band"
|
|
1590
|
+
});
|
|
1591
|
+
const definition = computed(() => translateChartSpec(spec.value));
|
|
1592
|
+
return (_ctx, _cache) => {
|
|
1593
|
+
return openBlock(), createElementBlock(Fragment, null, [createCommentVNode(" 库根元素 inheritAttrs:false 且多根,testid/BEM 类由本层 wrapper 承担 "), createElementVNode("div", {
|
|
1594
|
+
class: normalizeClass(unref(ns).b()),
|
|
1595
|
+
"data-testid": "cx-tanstack-charts-bar"
|
|
1596
|
+
}, [createVNode(unref(Chart), mergeProps(unref(hostProps), {
|
|
1597
|
+
definition: definition.value,
|
|
1598
|
+
"aria-label": unref(ariaLabel)
|
|
1599
|
+
}), null, 16, ["definition", "aria-label"])], 2)], 2112);
|
|
1600
|
+
};
|
|
1601
|
+
}
|
|
1602
|
+
});
|
|
1603
|
+
//#endregion
|
|
1604
|
+
//#region src/bar/index.ts
|
|
1605
|
+
/** 验收页 initial 样本:类目销量对比;x/y 字段名与 data 行键严格自洽 */
|
|
1606
|
+
const initialData$3 = () => [
|
|
1607
|
+
{
|
|
1608
|
+
product: "手机",
|
|
1609
|
+
sales: 860
|
|
1610
|
+
},
|
|
1611
|
+
{
|
|
1612
|
+
product: "平板",
|
|
1613
|
+
sales: 520
|
|
1614
|
+
},
|
|
1615
|
+
{
|
|
1616
|
+
product: "笔记本",
|
|
1617
|
+
sales: 640
|
|
1618
|
+
},
|
|
1619
|
+
{
|
|
1620
|
+
product: "耳机",
|
|
1621
|
+
sales: 430
|
|
1622
|
+
},
|
|
1623
|
+
{
|
|
1624
|
+
product: "手表",
|
|
1625
|
+
sales: 280
|
|
1626
|
+
}
|
|
1627
|
+
];
|
|
1628
|
+
var bar_default = define({
|
|
1629
|
+
name: "柱状图",
|
|
1630
|
+
description: "TanStack Charts 柱状图预设:data 行数组 + x/y 字段名,单 mark(barY + band 轴)规格经翻译层组装渲染。",
|
|
1631
|
+
key: "cx-tanstack-charts-bar",
|
|
1632
|
+
icon: "i-tabler-chart-bar",
|
|
1633
|
+
component: _sfc_main$3,
|
|
1634
|
+
props: {
|
|
1635
|
+
x: {
|
|
1636
|
+
name: "X 字段",
|
|
1637
|
+
type: "short",
|
|
1638
|
+
initial: "product"
|
|
1639
|
+
},
|
|
1640
|
+
y: {
|
|
1641
|
+
name: "Y 字段",
|
|
1642
|
+
type: "short",
|
|
1643
|
+
initial: "sales"
|
|
1644
|
+
},
|
|
1645
|
+
data: {
|
|
1646
|
+
name: "数据",
|
|
1647
|
+
type: "json",
|
|
1648
|
+
initial: initialData$3
|
|
1649
|
+
},
|
|
1650
|
+
height: {
|
|
1651
|
+
name: "高度",
|
|
1652
|
+
type: "number",
|
|
1653
|
+
initial: 320
|
|
1654
|
+
},
|
|
1655
|
+
ariaLabel: {
|
|
1656
|
+
name: "无障碍标签",
|
|
1657
|
+
type: "short",
|
|
1658
|
+
initial: "柱状图"
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
});
|
|
1662
|
+
//#endregion
|
|
1663
|
+
//#region src/area/src/index.vue
|
|
1664
|
+
const _sfc_main$2 = /*@__PURE__*/ defineComponent({
|
|
1665
|
+
name: "CxTanstackChartsArea",
|
|
1666
|
+
inheritAttrs: false,
|
|
1667
|
+
__name: "index",
|
|
1668
|
+
setup(__props) {
|
|
1669
|
+
const ns = useCxBEM("tanstack-charts-area");
|
|
1670
|
+
const { spec, hostProps, ariaLabel } = usePresetChart(useAttrs(), {
|
|
1671
|
+
markType: "areaY",
|
|
1672
|
+
xScale: "point",
|
|
1673
|
+
withCurve: true
|
|
1674
|
+
});
|
|
1675
|
+
const definition = computed(() => translateChartSpec(spec.value));
|
|
1676
|
+
return (_ctx, _cache) => {
|
|
1677
|
+
return openBlock(), createElementBlock(Fragment, null, [createCommentVNode(" 库根元素 inheritAttrs:false 且多根,testid/BEM 类由本层 wrapper 承担 "), createElementVNode("div", {
|
|
1678
|
+
class: normalizeClass(unref(ns).b()),
|
|
1679
|
+
"data-testid": "cx-tanstack-charts-area"
|
|
1680
|
+
}, [createVNode(unref(Chart), mergeProps(unref(hostProps), {
|
|
1681
|
+
definition: definition.value,
|
|
1682
|
+
"aria-label": unref(ariaLabel)
|
|
1683
|
+
}), null, 16, ["definition", "aria-label"])], 2)], 2112);
|
|
1684
|
+
};
|
|
1685
|
+
}
|
|
1686
|
+
});
|
|
1687
|
+
//#endregion
|
|
1688
|
+
//#region src/area/index.ts
|
|
1689
|
+
/** 验收页 initial 样本:周活跃趋势;x/y 字段名与 data 行键严格自洽 */
|
|
1690
|
+
const initialData$2 = () => [
|
|
1691
|
+
{
|
|
1692
|
+
week: "W1",
|
|
1693
|
+
active: 1200
|
|
1694
|
+
},
|
|
1695
|
+
{
|
|
1696
|
+
week: "W2",
|
|
1697
|
+
active: 1800
|
|
1698
|
+
},
|
|
1699
|
+
{
|
|
1700
|
+
week: "W3",
|
|
1701
|
+
active: 1500
|
|
1702
|
+
},
|
|
1703
|
+
{
|
|
1704
|
+
week: "W4",
|
|
1705
|
+
active: 2400
|
|
1706
|
+
},
|
|
1707
|
+
{
|
|
1708
|
+
week: "W5",
|
|
1709
|
+
active: 2100
|
|
1710
|
+
},
|
|
1711
|
+
{
|
|
1712
|
+
week: "W6",
|
|
1713
|
+
active: 2900
|
|
1714
|
+
},
|
|
1715
|
+
{
|
|
1716
|
+
week: "W7",
|
|
1717
|
+
active: 3300
|
|
1718
|
+
},
|
|
1719
|
+
{
|
|
1720
|
+
week: "W8",
|
|
1721
|
+
active: 3100
|
|
1722
|
+
}
|
|
1723
|
+
];
|
|
1724
|
+
var area_default = define({
|
|
1725
|
+
name: "面积图",
|
|
1726
|
+
description: "TanStack Charts 面积图预设:data 行数组 + x/y 字段名 + curve 枚举,单 mark(areaY)规格经翻译层组装渲染。",
|
|
1727
|
+
key: "cx-tanstack-charts-area",
|
|
1728
|
+
icon: "i-tabler-chart-area",
|
|
1729
|
+
component: _sfc_main$2,
|
|
1730
|
+
props: {
|
|
1731
|
+
x: {
|
|
1732
|
+
name: "X 字段",
|
|
1733
|
+
type: "short",
|
|
1734
|
+
initial: "week"
|
|
1735
|
+
},
|
|
1736
|
+
y: {
|
|
1737
|
+
name: "Y 字段",
|
|
1738
|
+
type: "short",
|
|
1739
|
+
initial: "active"
|
|
1740
|
+
},
|
|
1741
|
+
curve: {
|
|
1742
|
+
name: "曲线插值",
|
|
1743
|
+
type: "select",
|
|
1744
|
+
initial: "monotoneX",
|
|
1745
|
+
options: CURVE_SELECT_OPTIONS
|
|
1746
|
+
},
|
|
1747
|
+
data: {
|
|
1748
|
+
name: "数据",
|
|
1749
|
+
type: "json",
|
|
1750
|
+
initial: initialData$2
|
|
1751
|
+
},
|
|
1752
|
+
height: {
|
|
1753
|
+
name: "高度",
|
|
1754
|
+
type: "number",
|
|
1755
|
+
initial: 320
|
|
1756
|
+
},
|
|
1757
|
+
ariaLabel: {
|
|
1758
|
+
name: "无障碍标签",
|
|
1759
|
+
type: "short",
|
|
1760
|
+
initial: "面积图"
|
|
1761
|
+
}
|
|
1762
|
+
}
|
|
1763
|
+
});
|
|
1764
|
+
//#endregion
|
|
1765
|
+
//#region src/dot/src/index.vue
|
|
1766
|
+
const _sfc_main$1 = /*@__PURE__*/ defineComponent({
|
|
1767
|
+
name: "CxTanstackChartsDot",
|
|
1768
|
+
inheritAttrs: false,
|
|
1769
|
+
__name: "index",
|
|
1770
|
+
setup(__props) {
|
|
1771
|
+
const ns = useCxBEM("tanstack-charts-dot");
|
|
1772
|
+
const { spec, hostProps, ariaLabel } = usePresetChart(useAttrs(), {
|
|
1773
|
+
markType: "dot",
|
|
1774
|
+
xScale: "linear"
|
|
1775
|
+
});
|
|
1776
|
+
const definition = computed(() => translateChartSpec(spec.value));
|
|
1777
|
+
return (_ctx, _cache) => {
|
|
1778
|
+
return openBlock(), createElementBlock(Fragment, null, [createCommentVNode(" 库根元素 inheritAttrs:false 且多根,testid/BEM 类由本层 wrapper 承担 "), createElementVNode("div", {
|
|
1779
|
+
class: normalizeClass(unref(ns).b()),
|
|
1780
|
+
"data-testid": "cx-tanstack-charts-dot"
|
|
1781
|
+
}, [createVNode(unref(Chart), mergeProps(unref(hostProps), {
|
|
1782
|
+
definition: definition.value,
|
|
1783
|
+
"aria-label": unref(ariaLabel)
|
|
1784
|
+
}), null, 16, ["definition", "aria-label"])], 2)], 2112);
|
|
1785
|
+
};
|
|
1786
|
+
}
|
|
1787
|
+
});
|
|
1788
|
+
//#endregion
|
|
1789
|
+
//#region src/dot/index.ts
|
|
1790
|
+
/** 验收页 initial 样本:身高体重数值散点;x/y 字段名与 data 行键严格自洽 */
|
|
1791
|
+
const initialData$1 = () => [
|
|
1792
|
+
{
|
|
1793
|
+
height: 162,
|
|
1794
|
+
weight: 55
|
|
1795
|
+
},
|
|
1796
|
+
{
|
|
1797
|
+
height: 165,
|
|
1798
|
+
weight: 58
|
|
1799
|
+
},
|
|
1800
|
+
{
|
|
1801
|
+
height: 168,
|
|
1802
|
+
weight: 62
|
|
1803
|
+
},
|
|
1804
|
+
{
|
|
1805
|
+
height: 170,
|
|
1806
|
+
weight: 60
|
|
1807
|
+
},
|
|
1808
|
+
{
|
|
1809
|
+
height: 172,
|
|
1810
|
+
weight: 68
|
|
1811
|
+
},
|
|
1812
|
+
{
|
|
1813
|
+
height: 175,
|
|
1814
|
+
weight: 72
|
|
1815
|
+
},
|
|
1816
|
+
{
|
|
1817
|
+
height: 178,
|
|
1818
|
+
weight: 70
|
|
1819
|
+
},
|
|
1820
|
+
{
|
|
1821
|
+
height: 180,
|
|
1822
|
+
weight: 78
|
|
1823
|
+
},
|
|
1824
|
+
{
|
|
1825
|
+
height: 183,
|
|
1826
|
+
weight: 82
|
|
1827
|
+
},
|
|
1828
|
+
{
|
|
1829
|
+
height: 185,
|
|
1830
|
+
weight: 80
|
|
1831
|
+
}
|
|
1832
|
+
];
|
|
1833
|
+
var dot_default = define({
|
|
1834
|
+
name: "散点图",
|
|
1835
|
+
description: "TanStack Charts 散点图预设:data 行数组 + x/y 数值字段名,单 mark(dot + 双 linear 轴)规格经翻译层组装渲染。",
|
|
1836
|
+
key: "cx-tanstack-charts-dot",
|
|
1837
|
+
icon: "i-tabler-chart-dots",
|
|
1838
|
+
component: _sfc_main$1,
|
|
1839
|
+
props: {
|
|
1840
|
+
x: {
|
|
1841
|
+
name: "X 字段",
|
|
1842
|
+
type: "short",
|
|
1843
|
+
initial: "height"
|
|
1844
|
+
},
|
|
1845
|
+
y: {
|
|
1846
|
+
name: "Y 字段",
|
|
1847
|
+
type: "short",
|
|
1848
|
+
initial: "weight"
|
|
1849
|
+
},
|
|
1850
|
+
data: {
|
|
1851
|
+
name: "数据",
|
|
1852
|
+
type: "json",
|
|
1853
|
+
initial: initialData$1
|
|
1854
|
+
},
|
|
1855
|
+
height: {
|
|
1856
|
+
name: "高度",
|
|
1857
|
+
type: "number",
|
|
1858
|
+
initial: 320
|
|
1859
|
+
},
|
|
1860
|
+
ariaLabel: {
|
|
1861
|
+
name: "无障碍标签",
|
|
1862
|
+
type: "short",
|
|
1863
|
+
initial: "散点图"
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
});
|
|
1867
|
+
//#endregion
|
|
1868
|
+
//#region src/shared/use-pie-chart.ts
|
|
1869
|
+
/**
|
|
1870
|
+
* 饼图物料的 definition 组装:扁平通道 props(data/name/value/innerRadiusRatio)
|
|
1871
|
+
* → pie() 角度分配 → polar(radialArc) 扇区弧。
|
|
1872
|
+
*
|
|
1873
|
+
* 与笛卡尔预设的翻译层路径不同:pie 涉及运行时函数(pie 变换、innerRadius 比例函数),
|
|
1874
|
+
* 属 JSON 不可表达值的物料侧桥接,故在物料内直接组装、不经 CxChartSpec 声明式投影。
|
|
1875
|
+
*
|
|
1876
|
+
* 鲁棒性(JSON 输入不受 TS 约束):
|
|
1877
|
+
* - 负值/非数值行在 pie 前过滤——pie 对负值显式抛 TypeError,不过滤会炸整棵渲染树;
|
|
1878
|
+
* - innerRadiusRatio 钳制 [0, 0.95]——1.0 完全空心渲染不可见,属无效配置而非有效极端;
|
|
1879
|
+
* - name/value 字段缺席回退 'name'/'value'(与笛卡尔预设的回退策略同形)。
|
|
1880
|
+
*/
|
|
1881
|
+
function usePieChart(attrs) {
|
|
1882
|
+
const { hostProps, ariaLabel } = useChartHostProps(attrs);
|
|
1883
|
+
return {
|
|
1884
|
+
definition: computed(() => {
|
|
1885
|
+
const rows = Array.isArray(attrs.data) ? attrs.data : [];
|
|
1886
|
+
const nameField = typeof attrs.name === "string" && attrs.name ? attrs.name : "name";
|
|
1887
|
+
const valueField = typeof attrs.value === "string" && attrs.value ? attrs.value : "value";
|
|
1888
|
+
const clean = rows.filter((row) => row !== null && typeof row === "object" && Number.isFinite(Number(row[valueField])) && Number(row[valueField]) >= 0);
|
|
1889
|
+
const slices = pie(clean, { value: (datum) => Number(datum[valueField]) });
|
|
1890
|
+
const ratio = typeof attrs.innerRadiusRatio === "number" && Number.isFinite(attrs.innerRadiusRatio) ? Math.min(Math.max(attrs.innerRadiusRatio, 0), .95) : 0;
|
|
1891
|
+
return defineChart({ marks: [polar({
|
|
1892
|
+
inset: 8,
|
|
1893
|
+
marks: [radialArc(slices, {
|
|
1894
|
+
color: (datum) => String(datum[nameField]),
|
|
1895
|
+
key: (datum) => String(datum[nameField]),
|
|
1896
|
+
innerRadius: ratio > 0 ? ({ radius }) => radius * ratio : 0,
|
|
1897
|
+
cornerRadius: 2
|
|
1898
|
+
})]
|
|
1899
|
+
})] });
|
|
1900
|
+
}),
|
|
1901
|
+
hostProps,
|
|
1902
|
+
ariaLabel
|
|
1903
|
+
};
|
|
1904
|
+
}
|
|
1905
|
+
//#endregion
|
|
1906
|
+
//#region src/pie/src/index.vue
|
|
1907
|
+
const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
1908
|
+
name: "CxTanstackChartsPie",
|
|
1909
|
+
inheritAttrs: false,
|
|
1910
|
+
__name: "index",
|
|
1911
|
+
setup(__props) {
|
|
1912
|
+
const ns = useCxBEM("tanstack-charts-pie");
|
|
1913
|
+
const { definition, hostProps, ariaLabel } = usePieChart(useAttrs());
|
|
1914
|
+
return (_ctx, _cache) => {
|
|
1915
|
+
return openBlock(), createElementBlock(Fragment, null, [createCommentVNode(" 库根元素 inheritAttrs:false 且多根,testid/BEM 类由本层 wrapper 承担 "), createElementVNode("div", {
|
|
1916
|
+
class: normalizeClass(unref(ns).b()),
|
|
1917
|
+
"data-testid": "cx-tanstack-charts-pie"
|
|
1918
|
+
}, [createVNode(unref(Chart), mergeProps(unref(hostProps), {
|
|
1919
|
+
definition: unref(definition),
|
|
1920
|
+
"aria-label": unref(ariaLabel)
|
|
1921
|
+
}), null, 16, ["definition", "aria-label"])], 2)], 2112);
|
|
1922
|
+
};
|
|
1923
|
+
}
|
|
1924
|
+
});
|
|
1925
|
+
//#endregion
|
|
1926
|
+
//#region src/pie/index.ts
|
|
1927
|
+
/** 验收页 initial 样本:流量来源占比;name/value 字段名与 data 行键严格自洽 */
|
|
1928
|
+
const initialData = () => [
|
|
1929
|
+
{
|
|
1930
|
+
name: "搜索",
|
|
1931
|
+
value: 62
|
|
1932
|
+
},
|
|
1933
|
+
{
|
|
1934
|
+
name: "社交",
|
|
1935
|
+
value: 18
|
|
1936
|
+
},
|
|
1937
|
+
{
|
|
1938
|
+
name: "电商",
|
|
1939
|
+
value: 12
|
|
1940
|
+
},
|
|
1941
|
+
{
|
|
1942
|
+
name: "视频",
|
|
1943
|
+
value: 8
|
|
1944
|
+
}
|
|
1945
|
+
];
|
|
1946
|
+
var pie_default = define({
|
|
1947
|
+
name: "饼图",
|
|
1948
|
+
description: "TanStack Charts 饼图预设:data 行数组 + name/value 字段名,pie 变换 + polar(radialArc) 物料级组装;innerRadiusRatio > 0 呈环形。",
|
|
1949
|
+
key: "cx-tanstack-charts-pie",
|
|
1950
|
+
icon: "i-tabler-chart-pie",
|
|
1951
|
+
component: _sfc_main,
|
|
1952
|
+
props: {
|
|
1953
|
+
name: {
|
|
1954
|
+
name: "类目字段",
|
|
1955
|
+
type: "short",
|
|
1956
|
+
initial: "name"
|
|
1957
|
+
},
|
|
1958
|
+
value: {
|
|
1959
|
+
name: "数值字段",
|
|
1960
|
+
type: "short",
|
|
1961
|
+
initial: "value"
|
|
1962
|
+
},
|
|
1963
|
+
innerRadiusRatio: {
|
|
1964
|
+
name: "空心比",
|
|
1965
|
+
type: "number",
|
|
1966
|
+
initial: 0
|
|
1967
|
+
},
|
|
1968
|
+
data: {
|
|
1969
|
+
name: "数据",
|
|
1970
|
+
type: "json",
|
|
1971
|
+
initial: initialData
|
|
1972
|
+
},
|
|
1973
|
+
height: {
|
|
1974
|
+
name: "高度",
|
|
1975
|
+
type: "number",
|
|
1976
|
+
initial: 320
|
|
1977
|
+
},
|
|
1978
|
+
ariaLabel: {
|
|
1979
|
+
name: "无障碍标签",
|
|
1980
|
+
type: "short",
|
|
1981
|
+
initial: "占比饼图"
|
|
1982
|
+
}
|
|
1983
|
+
}
|
|
1984
|
+
});
|
|
1985
|
+
//#endregion
|
|
1986
|
+
//#region src/stream-triggers.ts
|
|
1987
|
+
/**
|
|
1988
|
+
* TanStack Charts 物料流式增量配置:6 件全适用——数组增长型 5 件 + 标量主体 1 件。
|
|
1989
|
+
*
|
|
1990
|
+
* 形态判定(6 件全 array):
|
|
1991
|
+
* - array 5 件:line/bar/area/dot/pie 预设的 data 是行数组(答复内容主体),
|
|
1992
|
+
* 截断点落在行边界逐项揭示(折线逐点生长、饼图逐扇区生长)。
|
|
1993
|
+
* arrayKey 恒为 'data'(单层字段名契约);props 声明序通道字段(x/y/curve/
|
|
1994
|
+
* name/value/innerRadiusRatio)前置、data 殿后——回放剧本序列化序使增量帧
|
|
1995
|
+
* 首行起即携带通道配置,尾随标量(height/ariaLabel)不入增量帧由终帧兜底。
|
|
1996
|
+
* - array 1 件:通用 chart 走数据顶层化契约,主数组是顶层 rows(逐行生长);
|
|
1997
|
+
* definition 序列化在 rows 前,rows 首行闭合时 definition 必然完整——图表
|
|
1998
|
+
* 以完整坐标系挂载、数据逐行生长(三段式:骨架→空坐标→行生长)。
|
|
1999
|
+
* - 不适用 0 件。计数校验:6 array + 0 scalar + 0 不适用(差集派生兜底)。
|
|
2000
|
+
*
|
|
2001
|
+
* produced 语义(增量提取器对中间态与终态走同一 buildPartial,实证
|
|
2002
|
+
* packages/stream/src/core/incremental.ts Step 5):
|
|
2003
|
+
* - 出帧只由主数组匹配驱动;extraScanPaths(nodes/links)仅提供元素边界
|
|
2004
|
+
* 切分点,不驱动出帧——GenUI 契约据此锁死 rows 恒为主数据集在场
|
|
2005
|
+
* (关系型图表 rows=节点行表、links 为边次数据集),无 rows 的 spec 永不
|
|
2006
|
+
* 出帧属契约外形态,由生成期校验门拦截。
|
|
2007
|
+
* - emptyPassthrough:rows=[] 空数组终态透传节点,由组件空态渲染接管,
|
|
2008
|
+
* 防流结束后 pending 永驻。
|
|
2009
|
+
*
|
|
2010
|
+
* 骨架裁决:不设 skeletonFields——rows 首行闭合即出帧逐行生长,天然有实时
|
|
2011
|
+
* 反馈;首行前的 pending 期由渲染管线承担(与预设物料同语义),物料内骨架
|
|
2012
|
+
* 判定保留兼容上游 _cx_streaming 注入。
|
|
2013
|
+
*
|
|
2014
|
+
* fallback 从简:渲染链路不过 zod,包装层无模板直访(useAttrs 平铺 +
|
|
2015
|
+
* composable 全回退,缺席不 TypeError);rows 缺席的中间态经翻译层
|
|
2016
|
+
* resolveMarkData 回退空数组,translateChartSpec 组装不抛错(增量帧翻译
|
|
2017
|
+
* 实证进包内测试)。
|
|
2018
|
+
*
|
|
2019
|
+
* key 取自物料 meta 原值(keyOf 查询,定义缺失即显式抛错)。
|
|
2020
|
+
*/
|
|
2021
|
+
const ALL_MATERIALS = [
|
|
2022
|
+
chart_default,
|
|
2023
|
+
line_default,
|
|
2024
|
+
bar_default,
|
|
2025
|
+
area_default,
|
|
2026
|
+
dot_default,
|
|
2027
|
+
pie_default
|
|
2028
|
+
];
|
|
2029
|
+
function keyOf(key) {
|
|
2030
|
+
const comp = ALL_MATERIALS.find((c) => c._cx_meta?.key === key);
|
|
2031
|
+
if (!comp) throw new Error(`tanstack-charts 物料定义缺失: ${key}`);
|
|
2032
|
+
return comp._cx_meta.key;
|
|
2033
|
+
}
|
|
2034
|
+
const TANSTACK_CHARTS_STREAM_TRIGGERS = [{
|
|
2035
|
+
key: keyOf("cx-chart"),
|
|
2036
|
+
sections: [{
|
|
2037
|
+
kind: "array",
|
|
2038
|
+
arrayKey: "rows",
|
|
2039
|
+
extraScanPaths: [[
|
|
2040
|
+
"data",
|
|
2041
|
+
"nodes",
|
|
2042
|
+
"*"
|
|
2043
|
+
], [
|
|
2044
|
+
"data",
|
|
2045
|
+
"links",
|
|
2046
|
+
"*"
|
|
2047
|
+
]]
|
|
2048
|
+
}],
|
|
2049
|
+
stateBranch: { emptyPassthrough: true },
|
|
2050
|
+
frameStride: 10
|
|
2051
|
+
}, ...[
|
|
2052
|
+
"cx-tanstack-charts-line",
|
|
2053
|
+
"cx-tanstack-charts-bar",
|
|
2054
|
+
"cx-tanstack-charts-area",
|
|
2055
|
+
"cx-tanstack-charts-dot",
|
|
2056
|
+
"cx-tanstack-charts-pie"
|
|
2057
|
+
].map((key) => ({
|
|
2058
|
+
key: keyOf(key),
|
|
2059
|
+
sections: [{
|
|
2060
|
+
kind: "array",
|
|
2061
|
+
arrayKey: "data"
|
|
2062
|
+
}],
|
|
2063
|
+
frameStride: 10
|
|
2064
|
+
}))];
|
|
2065
|
+
/** 装配 TanStack Charts 物料的 trigger 注册表(size 恒 6) */
|
|
2066
|
+
function createTanstackChartsTriggerRegistry() {
|
|
2067
|
+
const registry = createTriggerRegistry();
|
|
2068
|
+
for (const config of TANSTACK_CHARTS_STREAM_TRIGGERS) registry.register(config.key, compileTrigger(config));
|
|
2069
|
+
return registry;
|
|
2070
|
+
}
|
|
2071
|
+
/** 增量节点的主数组(回放计数展示用);非数组增长型物料或数组缺席时返回 null */
|
|
2072
|
+
function mainArrayOf(node) {
|
|
2073
|
+
const section = TANSTACK_CHARTS_STREAM_TRIGGERS.find((c) => c.key === node.key)?.sections.find((s) => s.kind === "array");
|
|
2074
|
+
if (!section || section.kind !== "array") return null;
|
|
2075
|
+
const arr = node.data?.[section.arrayKey];
|
|
2076
|
+
return Array.isArray(arr) ? arr : null;
|
|
2077
|
+
}
|
|
2078
|
+
//#endregion
|
|
2079
|
+
//#region src/index.ts
|
|
2080
|
+
/** TanStack Charts 物料数组:通用 chart + 笛卡尔预设(line/bar/area/dot)+ 饼图 */
|
|
2081
|
+
const CxTanstackCharts = [
|
|
2082
|
+
chart_default,
|
|
2083
|
+
line_default,
|
|
2084
|
+
bar_default,
|
|
2085
|
+
area_default,
|
|
2086
|
+
dot_default,
|
|
2087
|
+
pie_default
|
|
2088
|
+
];
|
|
2089
|
+
/** TanStack Charts 物料 bundle:自描述单元,供装配方(cx-nuxt 等)按 bundle 装配 */
|
|
2090
|
+
const CxTanstackChartsBundle = {
|
|
2091
|
+
name: "tanstack-charts",
|
|
2092
|
+
materials: [...CxTanstackCharts]
|
|
2093
|
+
};
|
|
2094
|
+
//#endregion
|
|
2095
|
+
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 };
|