@lionad/cx-comps-tanstack-charts 0.1.0-alpha.27 → 0.1.0-alpha.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.mjs +203 -30
- package/dist/shared/chart-empty.vue.d.ts +8 -0
- package/dist/shared/translate/geometry.d.ts +19 -0
- package/dist/shared/translate/index.d.ts +1 -0
- package/dist/shared/translate/polar.d.ts +3 -0
- package/dist/shared/translate/types.d.ts +86 -4
- package/package.json +10 -10
package/dist/index.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import { define } from "@lionad/cx-definition";
|
|
|
2
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
7
|
import { curveBasis, curveBumpX, curveCatmullRom, curveLinear, curveLinearClosed, curveMonotoneX, curveNatural, curveStep, curveStepAfter, curveStepBefore } from "d3-shape";
|
|
8
8
|
import { scaleLinear } from "@tanstack/charts/scales/linear";
|
|
@@ -32,7 +32,7 @@ import { createChartRendererAdapter } from "@tanstack/charts/adapter/renderer";
|
|
|
32
32
|
import { motion } from "@tanstack/charts/motion";
|
|
33
33
|
import { compileTrigger, createTriggerRegistry } from "@lionad/cx-stream";
|
|
34
34
|
//#region src/shared/chart-skeleton.vue
|
|
35
|
-
const _sfc_main$
|
|
35
|
+
const _sfc_main$8 = /*@__PURE__*/ defineComponent({
|
|
36
36
|
__name: "chart-skeleton",
|
|
37
37
|
props: { height: { default: 320 } },
|
|
38
38
|
setup(__props) {
|
|
@@ -123,6 +123,85 @@ const _sfc_main$7 = /*@__PURE__*/ defineComponent({
|
|
|
123
123
|
}
|
|
124
124
|
});
|
|
125
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
|
|
126
205
|
//#region src/shared/streaming.ts
|
|
127
206
|
/**
|
|
128
207
|
* 读取 trigger 注入的流式骨架标记(_cx_streaming,下划线前缀键不会透传物料)。
|
|
@@ -294,6 +373,7 @@ function translateAxisPresentation(axis) {
|
|
|
294
373
|
if (axis === false) return false;
|
|
295
374
|
const presentation = {};
|
|
296
375
|
if (axis.label !== void 0) presentation.label = axis.label;
|
|
376
|
+
if (axis.line !== void 0) presentation.line = axis.line;
|
|
297
377
|
if (axis.ticks !== void 0) {
|
|
298
378
|
const ticks = {};
|
|
299
379
|
if (axis.ticks.count !== void 0) ticks.count = axis.ticks.count;
|
|
@@ -321,6 +401,55 @@ function translateAxis(spec, fallbackKind) {
|
|
|
321
401
|
return axis;
|
|
322
402
|
}
|
|
323
403
|
//#endregion
|
|
404
|
+
//#region src/shared/translate/geometry.ts
|
|
405
|
+
/**
|
|
406
|
+
* 占位几何检测:LLM 在数据集缺席时可能产出 geometry 为占位字符串的示意 spec
|
|
407
|
+
* (如 "…(命名数据集)…")。翻译层与渲染层对缺失数据容错回退空数组,占位字符串
|
|
408
|
+
* 则被库静默忽略——两者都渲染为空壳 svg 且无任何反馈。本模块提供「数据集整体
|
|
409
|
+
* 不可渲染」的判定与翻译期警告,让空壳显式化为空态反馈。
|
|
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
|
+
* 组件层空态判定:数据集表中存在 GeoJSON 形态数据集,且全部占位。
|
|
432
|
+
* 存在任一可用数据集时返回 false(部分数据仍可渲染)。
|
|
433
|
+
*/
|
|
434
|
+
function hasOnlyPlaceholderGeometry(datasets) {
|
|
435
|
+
if (!datasets) return false;
|
|
436
|
+
const geoSets = Object.values(datasets).filter((arr) => arr !== void 0 && isFeatureArray(arr));
|
|
437
|
+
return geoSets.length > 0 && geoSets.every((arr) => isPlaceholderGeometryDataset(arr));
|
|
438
|
+
}
|
|
439
|
+
/** 翻译期警告去重:同数组引用只警告一次(Vue computed 重算与多 mark 共享数据集会重复调用) */
|
|
440
|
+
const warned = /* @__PURE__ */ new WeakSet();
|
|
441
|
+
/**
|
|
442
|
+
* 翻译期占位几何警告:数据集整体占位时 console.warn 指出空壳根因与修复方向。
|
|
443
|
+
* 只挂在 geoShape(占位字符串的高发地——示意 spec 的外部轮廓数据集);普通 mark
|
|
444
|
+
* 的数据集在途是流式常态,warn 会成噪音。WeakSet 按引用去重,数据集内容更新
|
|
445
|
+
* (新引用)视为新数据集重新警告。
|
|
446
|
+
*/
|
|
447
|
+
function warnPlaceholderGeometry(rows, datasetName, markType) {
|
|
448
|
+
if (!isPlaceholderGeometryDataset(rows) || warned.has(rows)) return;
|
|
449
|
+
warned.add(rows);
|
|
450
|
+
console.warn(`[cx-charts] ${markType} 数据集${datasetName ? ` "${datasetName}"` : ""} 的 geometry 全部缺失或为占位字符串,渲染为空壳——须以真实 GeoJSON 数据集整体替换示意占位(技能侧见「数据供给」表)。`);
|
|
451
|
+
}
|
|
452
|
+
//#endregion
|
|
324
453
|
//#region src/shared/translate/transforms.ts
|
|
325
454
|
/**
|
|
326
455
|
* 数据预处理管道翻译:spec 顶层 transforms 按序执行,产物以 name 注册进数据集表。
|
|
@@ -530,7 +659,6 @@ const SCALAR_KEYS = [
|
|
|
530
659
|
"positiveFillOpacity",
|
|
531
660
|
"negativeFillOpacity",
|
|
532
661
|
"comparisonStroke",
|
|
533
|
-
"cornerRadius",
|
|
534
662
|
"padAngle",
|
|
535
663
|
"radiusOffset",
|
|
536
664
|
"baseline",
|
|
@@ -608,7 +736,9 @@ function assertChannels(spec) {
|
|
|
608
736
|
}
|
|
609
737
|
for (const key of CHANNEL_FLEX_KEYS) {
|
|
610
738
|
const value = spec[key];
|
|
611
|
-
if (value !== void 0 && typeof value !== "string" && typeof value !== "number")
|
|
739
|
+
if (value !== void 0 && typeof value !== "string" && typeof value !== "number") {
|
|
740
|
+
if (!(Array.isArray(value) && value.length === 4 && value.every((n) => typeof n === "number" && Number.isFinite(n)))) throw new Error(`translateMark: channel "${key}" 只支持字段名、数值或四角数组,收到 ${typeof value}`);
|
|
741
|
+
}
|
|
612
742
|
}
|
|
613
743
|
}
|
|
614
744
|
/**
|
|
@@ -705,6 +835,7 @@ function translateMark(spec, datasets) {
|
|
|
705
835
|
if (rScale !== void 0) options.rScale = rScale;
|
|
706
836
|
for (const key of STYLE_KEYS) if (spec[key] !== void 0) options[key] = spec[key];
|
|
707
837
|
for (const key of SCALAR_KEYS) if (spec[key] !== void 0) options[key] = spec[key];
|
|
838
|
+
if ((spec.type === "barX" || spec.type === "barY") && spec.cornerRadius !== void 0 && spec.radius === void 0) options.radius = spec.cornerRadius;
|
|
708
839
|
if (spec.id !== void 0) options.id = spec.id;
|
|
709
840
|
if (spec.curve !== void 0) options.curve = spec.type === "violinY" ? translateAreaXCurve(spec.curve) : translateCurve(spec.curve);
|
|
710
841
|
if (spec.layout !== void 0) options.layout = translateLayout(spec.layout);
|
|
@@ -825,6 +956,9 @@ function translatePolarAxis(spec, { allowRange = false } = {}) {
|
|
|
825
956
|
* polar 容器:{ type:'polar', marks:[radialXxx...], angleAxis, radiusAxis, radiusRatio,
|
|
826
957
|
* startAngle, endAngle, inset, polarGuides } → polar({...})。
|
|
827
958
|
* extraMarks:调用方预组装的 PolarMark(sunburst 等自包含层级 mark 不走 radial 直译)。
|
|
959
|
+
* 0.18 契约:angle/radius scale 配置收进容器级 options.scales 保留通道
|
|
960
|
+
* (resolvePolarLayout 读 options.scales 的 reserved angle/radius 条目),
|
|
961
|
+
* 不再是顶层 angle/radius 字段;definition.scales 只需 x/y null 占位。
|
|
828
962
|
*/
|
|
829
963
|
function translatePolar(spec, datasets, extraMarks = []) {
|
|
830
964
|
const options = { marks: [...(spec.marks ?? []).map((m) => translateRadialMark(m, datasets)), ...extraMarks] };
|
|
@@ -833,12 +967,28 @@ function translatePolar(spec, datasets, extraMarks = []) {
|
|
|
833
967
|
if (spec.endAngle !== void 0) options.endAngle = spec.endAngle;
|
|
834
968
|
if (spec.inset !== void 0) options.inset = spec.inset;
|
|
835
969
|
if (spec.id !== void 0) options.id = spec.id;
|
|
970
|
+
const MATERIALIZING_TYPES = /* @__PURE__ */ new Set([
|
|
971
|
+
"radialBarRadius",
|
|
972
|
+
"radialBarAngle",
|
|
973
|
+
"radialLine",
|
|
974
|
+
"radialArea",
|
|
975
|
+
"radialText",
|
|
976
|
+
"radialRule",
|
|
977
|
+
"radialDot"
|
|
978
|
+
]);
|
|
979
|
+
const marksSpecs = spec.marks ?? [];
|
|
980
|
+
const hasRadialBarRadius = marksSpecs.some((m) => m.type === "radialBarRadius");
|
|
981
|
+
const hasRadialBarAngle = marksSpecs.some((m) => m.type === "radialBarAngle");
|
|
982
|
+
const materializesChannel = marksSpecs.some((m) => MATERIALIZING_TYPES.has(m.type));
|
|
836
983
|
const angle = translatePolarAxis(spec.angleAxis);
|
|
837
984
|
const radius = translatePolarAxis(spec.radiusAxis, { allowRange: true });
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
985
|
+
options.scales = materializesChannel ? {
|
|
986
|
+
angle: angle ?? { scale: () => hasRadialBarRadius ? scaleBand() : scalePoint() },
|
|
987
|
+
radius: radius ?? { scale: hasRadialBarAngle ? () => scaleBand() : scaleLinear }
|
|
988
|
+
} : {
|
|
989
|
+
angle: angle ?? null,
|
|
990
|
+
radius: radius ?? null
|
|
991
|
+
};
|
|
842
992
|
if (spec.polarGuides?.length) options.guides = spec.polarGuides.map(translatePolarGuide);
|
|
843
993
|
return polar(options);
|
|
844
994
|
}
|
|
@@ -1132,6 +1282,7 @@ const GEO_PROJECTION_FACTORIES = {
|
|
|
1132
1282
|
/** geoShape:projection 名称枚举 → descriptor {type, fit, inset};fit:'data' 由库拟合数据本体,{data:'<name>'} 拟合另一命名数据集 */
|
|
1133
1283
|
function translateGeoShape(spec, datasets) {
|
|
1134
1284
|
const rows = resolveMarkData(spec.data, datasets);
|
|
1285
|
+
warnPlaceholderGeometry(rows, typeof spec.data === "string" ? spec.data : void 0, "geoShape");
|
|
1135
1286
|
const projectionName = spec.projection ?? "mercator";
|
|
1136
1287
|
if (typeof projectionName === "object") throw new Error("translateGeoShape: projection {$by} 仅允许出现在 facet chart 子模板内");
|
|
1137
1288
|
const factory = GEO_PROJECTION_FACTORIES[projectionName];
|
|
@@ -1275,7 +1426,7 @@ function translateViewGrid(spec, datasets, hooks) {
|
|
|
1275
1426
|
*/
|
|
1276
1427
|
/** color.legend 声明 → 图例实例;true 等价 { kind:'color' } 缺省配置 */
|
|
1277
1428
|
function translateLegend(legend) {
|
|
1278
|
-
const { kind, label, placement, itemWidth, steps, width } = legend === true ? {} : legend;
|
|
1429
|
+
const { kind, label, placement, itemWidth, steps, width, items: itemSpec } = legend === true ? {} : legend;
|
|
1279
1430
|
if (kind === "gradient") {
|
|
1280
1431
|
const gradient = {};
|
|
1281
1432
|
if (label !== void 0) gradient.label = label;
|
|
@@ -1289,6 +1440,15 @@ function translateLegend(legend) {
|
|
|
1289
1440
|
if (placement !== void 0) items.placement = placement;
|
|
1290
1441
|
if (itemWidth !== void 0) items.itemWidth = itemWidth;
|
|
1291
1442
|
if (width !== void 0) items.width = width;
|
|
1443
|
+
if (itemSpec !== void 0) {
|
|
1444
|
+
const itemOptions = {};
|
|
1445
|
+
if (itemSpec.justify !== void 0) itemOptions.justify = itemSpec.justify;
|
|
1446
|
+
if (itemSpec.gap !== void 0) itemOptions.gap = itemSpec.gap;
|
|
1447
|
+
if (itemSpec.rowGap !== void 0) itemOptions.rowGap = itemSpec.rowGap;
|
|
1448
|
+
if (itemSpec.indicator !== void 0) itemOptions.indicator = itemSpec.indicator;
|
|
1449
|
+
if (itemSpec.label !== void 0) itemOptions.label = itemSpec.label;
|
|
1450
|
+
items.items = colorLegendItems(itemOptions);
|
|
1451
|
+
}
|
|
1292
1452
|
return colorLegend(items);
|
|
1293
1453
|
}
|
|
1294
1454
|
/**
|
|
@@ -1401,6 +1561,7 @@ function buildDefinition(spec, datasets, rowsOverride, mode = "host") {
|
|
|
1401
1561
|
} else pushMark(bound, translateMark(bound, table));
|
|
1402
1562
|
}
|
|
1403
1563
|
if (mode === "child" && spec.theme?.background !== void 0) throw new Error("translateViewGrid 子视图不得声明 theme.background(库拒绝子图持有场景背景;背景用子图内普通 mark 表达)");
|
|
1564
|
+
if (mode === "child" && spec.gradients !== void 0) throw new Error("translateViewGrid 子视图不得声明 gradients(host 权属渐变资源,配置在外层 spec)");
|
|
1404
1565
|
const theme = mode === "child" ? spec.theme === void 0 ? void 0 : { ...spec.theme } : {
|
|
1405
1566
|
...defaultChartTheme,
|
|
1406
1567
|
...spec.theme
|
|
@@ -1412,21 +1573,22 @@ function buildDefinition(spec, datasets, rowsOverride, mode = "host") {
|
|
|
1412
1573
|
const axisless = polarOnly || geoOnly;
|
|
1413
1574
|
const xSpec = spec.x === void 0 && axisless ? null : spec.x;
|
|
1414
1575
|
const ySpec = spec.y === void 0 && axisless ? null : spec.y;
|
|
1576
|
+
const scales = {};
|
|
1415
1577
|
const x = translateAxis(xSpec ?? void 0, "point");
|
|
1416
1578
|
const y = translateAxis(ySpec ?? void 0, "linear");
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
if (
|
|
1421
|
-
|
|
1422
|
-
...definition.x,
|
|
1579
|
+
scales.x = xSpec === null ? null : x;
|
|
1580
|
+
scales.y = ySpec === null ? null : y;
|
|
1581
|
+
definition.scales = scales;
|
|
1582
|
+
if (derivedXDomain && spec.x?.scale?.domain === void 0) scales.x = {
|
|
1583
|
+
...x,
|
|
1423
1584
|
scale: scaleLinear().domain(derivedXDomain)
|
|
1424
1585
|
};
|
|
1425
|
-
if (derivedYDomain && spec.y?.scale?.domain === void 0)
|
|
1426
|
-
...
|
|
1586
|
+
if (derivedYDomain && spec.y?.scale?.domain === void 0) scales.y = {
|
|
1587
|
+
...y,
|
|
1427
1588
|
scale: scaleLinear().domain(derivedYDomain)
|
|
1428
1589
|
};
|
|
1429
1590
|
if (spec.margin !== void 0) definition.margin = spec.margin;
|
|
1591
|
+
if (spec.gradients !== void 0) definition.gradients = spec.gradients;
|
|
1430
1592
|
if (spec.guides !== void 0) definition.guides = spec.guides;
|
|
1431
1593
|
else if (polarOnly) definition.guides = false;
|
|
1432
1594
|
if (spec.clip !== void 0) definition.clip = spec.clip;
|
|
@@ -1570,6 +1732,7 @@ const _sfc_main$5 = /*@__PURE__*/ defineComponent({
|
|
|
1570
1732
|
const { spec, datasets, hostProps, ariaLabel } = useChartProps(attrs);
|
|
1571
1733
|
const definition = computed(() => translateChartSpec(spec.value, datasets.value));
|
|
1572
1734
|
const isStreaming = computed(() => streamingFields(attrs).includes("definition"));
|
|
1735
|
+
const hasPlaceholderGeometry = computed(() => hasOnlyPlaceholderGeometry(datasets.value));
|
|
1573
1736
|
const skeletonHeight = computed(() => {
|
|
1574
1737
|
const h = Number(attrs.height);
|
|
1575
1738
|
return Number.isFinite(h) && h > 0 ? h : 320;
|
|
@@ -1578,10 +1741,10 @@ const _sfc_main$5 = /*@__PURE__*/ defineComponent({
|
|
|
1578
1741
|
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", {
|
|
1579
1742
|
class: normalizeClass([unref(ns).b(), "cx-charts"]),
|
|
1580
1743
|
"data-testid": "cx-tanstack-charts-chart"
|
|
1581
|
-
}, [createCommentVNode(" definition 未闭合期间骨架占位:空壳帧的空 marks 只渲染不可见空 svg,无反馈等同未渲染 "), isStreaming.value ? (openBlock(), createBlock(_sfc_main$
|
|
1744
|
+
}, [createCommentVNode(" definition 未闭合期间骨架占位:空壳帧的空 marks 只渲染不可见空 svg,无反馈等同未渲染 "), isStreaming.value ? (openBlock(), createBlock(_sfc_main$8, {
|
|
1582
1745
|
key: 0,
|
|
1583
1746
|
height: skeletonHeight.value
|
|
1584
|
-
}, null, 8, ["height"])) : unref(spec).motion !== void 0 ? (openBlock(), createElementBlock(Fragment, { key:
|
|
1747
|
+
}, 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({
|
|
1585
1748
|
definition: definition.value,
|
|
1586
1749
|
ariaLabel: unref(ariaLabel),
|
|
1587
1750
|
"motion-options": unref(spec).motion
|
|
@@ -1589,7 +1752,7 @@ const _sfc_main$5 = /*@__PURE__*/ defineComponent({
|
|
|
1589
1752
|
"definition",
|
|
1590
1753
|
"ariaLabel",
|
|
1591
1754
|
"motion-options"
|
|
1592
|
-
])], 2112)) : (openBlock(), createBlock(unref(Chart), mergeProps({ key:
|
|
1755
|
+
])], 2112)) : (openBlock(), createBlock(unref(Chart), mergeProps({ key: 3 }, unref(hostProps), {
|
|
1593
1756
|
definition: definition.value,
|
|
1594
1757
|
"aria-label": unref(ariaLabel)
|
|
1595
1758
|
}), null, 16, ["definition", "aria-label"]))], 2)], 2112);
|
|
@@ -2166,15 +2329,25 @@ function usePieChart(attrs) {
|
|
|
2166
2329
|
const clean = rows.filter((row) => row !== null && typeof row === "object" && Number.isFinite(Number(row[valueField])) && Number(row[valueField]) >= 0);
|
|
2167
2330
|
const slices = pie(clean, { value: (datum) => Number(datum[valueField]) });
|
|
2168
2331
|
const ratio = typeof attrs.innerRadiusRatio === "number" && Number.isFinite(attrs.innerRadiusRatio) ? Math.min(Math.max(attrs.innerRadiusRatio, 0), .95) : 0;
|
|
2169
|
-
return defineChart({
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2332
|
+
return defineChart({
|
|
2333
|
+
marks: [polar({
|
|
2334
|
+
inset: 8,
|
|
2335
|
+
scales: {
|
|
2336
|
+
angle: null,
|
|
2337
|
+
radius: null
|
|
2338
|
+
},
|
|
2339
|
+
marks: [radialArc(slices, {
|
|
2340
|
+
color: (datum) => String(datum[nameField]),
|
|
2341
|
+
key: (datum) => String(datum[nameField]),
|
|
2342
|
+
innerRadius: ratio > 0 ? ({ radius }) => radius * ratio : 0,
|
|
2343
|
+
cornerRadius: 2
|
|
2344
|
+
})]
|
|
2345
|
+
})],
|
|
2346
|
+
scales: {
|
|
2347
|
+
x: null,
|
|
2348
|
+
y: null
|
|
2349
|
+
}
|
|
2350
|
+
});
|
|
2178
2351
|
}),
|
|
2179
2352
|
hostProps,
|
|
2180
2353
|
ariaLabel
|
|
@@ -2383,4 +2556,4 @@ const CxTanstackChartsBundle = {
|
|
|
2383
2556
|
materials: [...CxTanstackCharts]
|
|
2384
2557
|
};
|
|
2385
2558
|
//#endregion
|
|
2386
|
-
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, resolveRScaleOption, translateAxis, translateChartSpec, translateCompositeMark, translateCurve, translateMark, translateOutputs, translatePie, translatePolar, translateReduce, translateScale, translateTransform };
|
|
2559
|
+
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, hasOnlyPlaceholderGeometry, isPlaceholderGeometryDataset, mainArrayOf, resolveMarkData, resolveRScaleOption, translateAxis, translateChartSpec, translateCompositeMark, translateCurve, translateMark, translateOutputs, translatePie, translatePolar, translateReduce, translateScale, translateTransform, warnPlaceholderGeometry };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
type __VLS_Props = {
|
|
2
|
+
height?: number;
|
|
3
|
+
};
|
|
4
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
|
|
5
|
+
height: number;
|
|
6
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
7
|
+
declare const _default: typeof __VLS_export;
|
|
8
|
+
export default _default;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { CxChartDatasets } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* 单数据集整体不可渲染判定:Feature 数组且全部条目几何缺失或为占位字符串。
|
|
4
|
+
* 混合场景(部分真实几何)不算——部分数据仍可渲染,整体空态会误杀;
|
|
5
|
+
* geometry: null 是 GeoJSON 合法形态,但全部条目皆 null 时渲染结果同为空壳。
|
|
6
|
+
*/
|
|
7
|
+
export declare function isPlaceholderGeometryDataset(rows: readonly unknown[]): boolean;
|
|
8
|
+
/**
|
|
9
|
+
* 组件层空态判定:数据集表中存在 GeoJSON 形态数据集,且全部占位。
|
|
10
|
+
* 存在任一可用数据集时返回 false(部分数据仍可渲染)。
|
|
11
|
+
*/
|
|
12
|
+
export declare function hasOnlyPlaceholderGeometry(datasets: CxChartDatasets): boolean;
|
|
13
|
+
/**
|
|
14
|
+
* 翻译期占位几何警告:数据集整体占位时 console.warn 指出空壳根因与修复方向。
|
|
15
|
+
* 只挂在 geoShape(占位字符串的高发地——示意 spec 的外部轮廓数据集);普通 mark
|
|
16
|
+
* 的数据集在途是流式常态,warn 会成噪音。WeakSet 按引用去重,数据集内容更新
|
|
17
|
+
* (新引用)视为新数据集重新警告。
|
|
18
|
+
*/
|
|
19
|
+
export declare function warnPlaceholderGeometry(rows: readonly unknown[], datasetName: string | undefined, markType: string): void;
|
|
@@ -8,6 +8,7 @@ export type { CxChartAxisSpec, CxChartCurveName, CxChartDatasets, CxChartForceSp
|
|
|
8
8
|
export { CX_CHART_CURVE_NAMES, translateCurve } from './curve';
|
|
9
9
|
export { translateScale } from './scale';
|
|
10
10
|
export { translateAxis } from './axis';
|
|
11
|
+
export { hasOnlyPlaceholderGeometry, isPlaceholderGeometryDataset, warnPlaceholderGeometry } from './geometry';
|
|
11
12
|
export { resolveMarkData, resolveRScaleOption, translateMark } from './mark';
|
|
12
13
|
export { applyTransforms, translateOutputs, translateReduce, translateTransform } from './transforms';
|
|
13
14
|
export { POLAR_FAMILY_TYPES, translatePie, translatePolar } from './polar';
|
|
@@ -4,6 +4,9 @@ import type { CxChartDatasets, CxChartMarkSpec } from './types';
|
|
|
4
4
|
* polar 容器:{ type:'polar', marks:[radialXxx...], angleAxis, radiusAxis, radiusRatio,
|
|
5
5
|
* startAngle, endAngle, inset, polarGuides } → polar({...})。
|
|
6
6
|
* extraMarks:调用方预组装的 PolarMark(sunburst 等自包含层级 mark 不走 radial 直译)。
|
|
7
|
+
* 0.18 契约:angle/radius scale 配置收进容器级 options.scales 保留通道
|
|
8
|
+
* (resolvePolarLayout 读 options.scales 的 reserved angle/radius 条目),
|
|
9
|
+
* 不再是顶层 angle/radius 字段;definition.scales 只需 x/y null 占位。
|
|
7
10
|
*/
|
|
8
11
|
export declare function translatePolar(spec: CxChartMarkSpec, datasets: CxChartDatasets | undefined, extraMarks?: readonly unknown[]): ChartMark<unknown, any, any>;
|
|
9
12
|
/**
|
|
@@ -38,11 +38,47 @@ export type CxChartScaleSpec = {
|
|
|
38
38
|
kind: 'ordinal';
|
|
39
39
|
domain?: string[];
|
|
40
40
|
};
|
|
41
|
+
/** 网格线/轴基线渲染中立描边样式(官方 ChartGuideLineStyle 的 JSON 投影) */
|
|
42
|
+
export interface CxChartGuideLineStyle {
|
|
43
|
+
stroke?: string;
|
|
44
|
+
strokeOpacity?: number;
|
|
45
|
+
strokeWidth?: number;
|
|
46
|
+
strokeDasharray?: string;
|
|
47
|
+
lineCap?: 'butt' | 'round' | 'square';
|
|
48
|
+
}
|
|
49
|
+
/** 渐变 stop(0–1 objectBoundingBox;offset 回退时由渲染器向前钳制) */
|
|
50
|
+
export interface CxChartGradientStop {
|
|
51
|
+
offset: number;
|
|
52
|
+
color: string;
|
|
53
|
+
opacity?: number;
|
|
54
|
+
}
|
|
55
|
+
/** 线性渐变:type 可省;坐标缺省垂直自底向上 (0,1)→(0,0) */
|
|
56
|
+
export interface CxChartLinearGradient {
|
|
57
|
+
type?: 'linear';
|
|
58
|
+
id: string;
|
|
59
|
+
stops: CxChartGradientStop[];
|
|
60
|
+
x1?: number;
|
|
61
|
+
y1?: number;
|
|
62
|
+
x2?: number;
|
|
63
|
+
y2?: number;
|
|
64
|
+
}
|
|
65
|
+
/** 径向渐变:type 必填;cx/cy/r 缺省 0.5,fx/fy 缺省继承中心 */
|
|
66
|
+
export interface CxChartRadialGradient {
|
|
67
|
+
type: 'radial';
|
|
68
|
+
id: string;
|
|
69
|
+
stops: CxChartGradientStop[];
|
|
70
|
+
cx?: number;
|
|
71
|
+
cy?: number;
|
|
72
|
+
r?: number;
|
|
73
|
+
fx?: number;
|
|
74
|
+
fy?: number;
|
|
75
|
+
}
|
|
41
76
|
export interface CxChartAxisSpec {
|
|
42
77
|
scale?: CxChartScaleSpec;
|
|
43
78
|
nice?: boolean | number;
|
|
44
79
|
reverse?: boolean;
|
|
45
|
-
|
|
80
|
+
/** true 画缺省网格;对象形态覆盖网格线描边呈现(0.18 ChartGuideLineStyle) */
|
|
81
|
+
grid?: boolean | CxChartGuideLineStyle;
|
|
46
82
|
/**
|
|
47
83
|
* 半径像素 range 的比例对(相对 polar 最终半径,0–1),仅 polar 容器的
|
|
48
84
|
* radiusAxis 消费;物化为官方 PolarLength 回调数组(resize 时随最终半径重解析)。
|
|
@@ -50,7 +86,17 @@ export interface CxChartAxisSpec {
|
|
|
50
86
|
*/
|
|
51
87
|
range?: [number, number];
|
|
52
88
|
axis?: false | {
|
|
53
|
-
|
|
89
|
+
/** 轴标题:字符串缺省呈现;对象形态定制字号/字重/填充/偏移(0.18 ChartAxisLabelOptions) */
|
|
90
|
+
label?: string | {
|
|
91
|
+
text: string;
|
|
92
|
+
offset?: number | 'auto';
|
|
93
|
+
fontSize?: number;
|
|
94
|
+
fontWeight?: number;
|
|
95
|
+
fill?: string;
|
|
96
|
+
opacity?: number;
|
|
97
|
+
};
|
|
98
|
+
/** 轴基线:缺省不画;true 画缺省基线,对象形态覆盖基线描边(0.18 axis.line) */
|
|
99
|
+
line?: boolean | CxChartGuideLineStyle;
|
|
54
100
|
ticks?: {
|
|
55
101
|
count?: number;
|
|
56
102
|
size?: number;
|
|
@@ -136,7 +182,13 @@ export interface CxChartMarkSpec {
|
|
|
136
182
|
range?: [number, number];
|
|
137
183
|
};
|
|
138
184
|
angle?: string | number;
|
|
139
|
-
|
|
185
|
+
/**
|
|
186
|
+
* 双语义:radial 系是 radius channel(字段名/数值常量);直角 barX/barY 是圆角
|
|
187
|
+
* BarRadius——数值全角、[tl,tr,br,bl] 四角差异化(对象形态 {end,stack} 不收,
|
|
188
|
+
* 防与 channel 语义混淆)。cornerRadius 是 polar 族字段名(radialArc),直角系
|
|
189
|
+
* 由翻译层别名归一到本字段(显式 radius 优先)。
|
|
190
|
+
*/
|
|
191
|
+
radius?: string | number | readonly [number, number, number, number];
|
|
140
192
|
width?: string | number;
|
|
141
193
|
height?: string | number;
|
|
142
194
|
length?: string | number;
|
|
@@ -513,6 +565,12 @@ export interface CxChartSpec {
|
|
|
513
565
|
};
|
|
514
566
|
guides?: boolean;
|
|
515
567
|
clip?: boolean;
|
|
568
|
+
/**
|
|
569
|
+
* 渐变资源(0.18 ChartGradient 投影):mark 以 fill/stroke 'url(#id)' 引用。
|
|
570
|
+
* host 权属资源——views 子图声明即拒绝(官方 child 契约),归外层组合定义。
|
|
571
|
+
* 坐标与 stop offset 为 0–1 objectBoundingBox;linear 缺省垂直自底向上。
|
|
572
|
+
*/
|
|
573
|
+
gradients?: (CxChartLinearGradient | CxChartRadialGradient)[];
|
|
516
574
|
/** 数据预处理管道(顶层声明,marks 之前执行;产物注册进 datasets 表) */
|
|
517
575
|
transforms?: CxChartTransformSpec[];
|
|
518
576
|
/** color scale 声明式(domain/range 直可 JSON;legend 为图例声明) */
|
|
@@ -526,6 +584,24 @@ export interface CxChartSpec {
|
|
|
526
584
|
itemWidth?: number;
|
|
527
585
|
steps?: number;
|
|
528
586
|
width?: number;
|
|
587
|
+
/** 条目子集定制(仅 kind:'color';物化为 colorLegendItems,0.18 契约) */
|
|
588
|
+
items?: {
|
|
589
|
+
justify?: 'start' | 'center' | 'stretch';
|
|
590
|
+
gap?: number;
|
|
591
|
+
rowGap?: number;
|
|
592
|
+
indicator?: {
|
|
593
|
+
shape?: 'dot' | 'square' | 'line' | 'line-dot';
|
|
594
|
+
width?: number;
|
|
595
|
+
height?: number;
|
|
596
|
+
gap?: number;
|
|
597
|
+
};
|
|
598
|
+
label?: {
|
|
599
|
+
fontSize?: number;
|
|
600
|
+
fontWeight?: number;
|
|
601
|
+
fill?: string;
|
|
602
|
+
fillOpacity?: number;
|
|
603
|
+
};
|
|
604
|
+
};
|
|
529
605
|
};
|
|
530
606
|
};
|
|
531
607
|
tooltip?: boolean | {
|
|
@@ -551,7 +627,13 @@ export interface CxChartSpec {
|
|
|
551
627
|
};
|
|
552
628
|
pointer?: boolean;
|
|
553
629
|
keyboard?: boolean;
|
|
554
|
-
|
|
630
|
+
/** 焦点环:true/false 显隐;对象形态定制几何与描边填充(0.18 ChartFocusRingOptions) */
|
|
631
|
+
focusRing?: boolean | {
|
|
632
|
+
radius?: number;
|
|
633
|
+
fill?: string;
|
|
634
|
+
stroke?: string;
|
|
635
|
+
strokeWidth?: number;
|
|
636
|
+
};
|
|
555
637
|
focus?: 'nearest' | 'nearest-x' | 'nearest-y' | 'group-x' | 'group-y';
|
|
556
638
|
/**
|
|
557
639
|
* 动效声明(可 JSON 化子集)。声明即走 motion 挂载分支:库 vue adapter 未暴露
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lionad/cx-comps-tanstack-charts",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.29",
|
|
4
4
|
"description": "cx material components wrapping TanStack Charts (@tanstack/charts/vue)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"charts",
|
|
@@ -34,16 +34,20 @@
|
|
|
34
34
|
"publishConfig": {
|
|
35
35
|
"access": "public"
|
|
36
36
|
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "vp pack && vue-tsc -p tsconfig.build.json",
|
|
39
|
+
"typecheck": "vue-tsgo --tsdk ../../node_modules/.pnpm/typescript@7.0.2/node_modules/typescript --noEmit"
|
|
40
|
+
},
|
|
37
41
|
"dependencies": {
|
|
38
|
-
"@lionad/cx-definition": "
|
|
39
|
-
"@lionad/cx-stream": "
|
|
40
|
-
"@lionad/cx-vue": "
|
|
42
|
+
"@lionad/cx-definition": "workspace:*",
|
|
43
|
+
"@lionad/cx-stream": "workspace:*",
|
|
44
|
+
"@lionad/cx-vue": "workspace:*",
|
|
41
45
|
"d3-geo": "^3.1.1",
|
|
42
46
|
"d3-scale": "^4.0.2",
|
|
43
47
|
"d3-shape": "^3.2.0"
|
|
44
48
|
},
|
|
45
49
|
"devDependencies": {
|
|
46
|
-
"@tanstack/charts": "^0.
|
|
50
|
+
"@tanstack/charts": "^0.18.0",
|
|
47
51
|
"@types/d3-geo": "^3.1.1",
|
|
48
52
|
"@types/d3-scale": "^4.0.9",
|
|
49
53
|
"@types/d3-shape": "^3.1.8",
|
|
@@ -56,9 +60,5 @@
|
|
|
56
60
|
"@tanstack/charts": "^0.14.0",
|
|
57
61
|
"@vueuse/core": "^13.0.0 || ^14.0.0",
|
|
58
62
|
"vue": "^3.5.0"
|
|
59
|
-
},
|
|
60
|
-
"scripts": {
|
|
61
|
-
"build": "vp pack && vue-tsc -p tsconfig.build.json",
|
|
62
|
-
"typecheck": "vue-tsgo --tsdk ../../node_modules/.pnpm/typescript@7.0.2/node_modules/typescript --noEmit"
|
|
63
63
|
}
|
|
64
|
-
}
|
|
64
|
+
}
|