@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 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$7 = /*@__PURE__*/ defineComponent({
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") throw new Error(`translateMark: channel "${key}" 只支持字段名或数值,收到 ${typeof value}`);
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
- const hasRadialBarRadius = (spec.marks ?? []).some((m) => m.type === "radialBarRadius");
839
- const hasRadialBarAngle = (spec.marks ?? []).some((m) => m.type === "radialBarAngle");
840
- options.angle = angle ?? { scale: () => hasRadialBarRadius ? scaleBand() : scalePoint() };
841
- options.radius = radius ?? { scale: hasRadialBarAngle ? () => scaleBand() : scaleLinear };
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
- if (xSpec !== null && x) definition.x = x;
1418
- if (xSpec === null) definition.x = null;
1419
- if (ySpec !== null && y) definition.y = y;
1420
- if (ySpec === null) definition.y = null;
1421
- if (derivedXDomain && spec.x?.scale?.domain === void 0) definition.x = {
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) definition.y = {
1426
- ...definition.y,
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$7, {
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: 1 }, [createCommentVNode(" motion 声明走自研挂载分支:库 vue adapter 无 motion renderer 注入口子 "), createVNode(_sfc_main$6, mergeProps({
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: 2 }, unref(hostProps), {
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({ marks: [polar({
2170
- inset: 8,
2171
- marks: [radialArc(slices, {
2172
- color: (datum) => String(datum[nameField]),
2173
- key: (datum) => String(datum[nameField]),
2174
- innerRadius: ratio > 0 ? ({ radius }) => radius * ratio : 0,
2175
- cornerRadius: 2
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
- grid?: boolean;
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
- label?: string;
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
- radius?: string | number;
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
- focusRing?: boolean;
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.27",
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": "0.1.0-alpha.10",
39
- "@lionad/cx-stream": "0.1.0-alpha.14",
40
- "@lionad/cx-vue": "0.1.0-alpha.11",
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.14.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
+ }