@lionad/cx-comps-tanstack-charts 0.1.0-alpha.26 → 0.1.0-alpha.28

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";
@@ -294,6 +294,7 @@ function translateAxisPresentation(axis) {
294
294
  if (axis === false) return false;
295
295
  const presentation = {};
296
296
  if (axis.label !== void 0) presentation.label = axis.label;
297
+ if (axis.line !== void 0) presentation.line = axis.line;
297
298
  if (axis.ticks !== void 0) {
298
299
  const ticks = {};
299
300
  if (axis.ticks.count !== void 0) ticks.count = axis.ticks.count;
@@ -530,7 +531,6 @@ const SCALAR_KEYS = [
530
531
  "positiveFillOpacity",
531
532
  "negativeFillOpacity",
532
533
  "comparisonStroke",
533
- "cornerRadius",
534
534
  "padAngle",
535
535
  "radiusOffset",
536
536
  "baseline",
@@ -608,7 +608,9 @@ function assertChannels(spec) {
608
608
  }
609
609
  for (const key of CHANNEL_FLEX_KEYS) {
610
610
  const value = spec[key];
611
- if (value !== void 0 && typeof value !== "string" && typeof value !== "number") throw new Error(`translateMark: channel "${key}" 只支持字段名或数值,收到 ${typeof value}`);
611
+ if (value !== void 0 && typeof value !== "string" && typeof value !== "number") {
612
+ if (!(Array.isArray(value) && value.length === 4 && value.every((n) => typeof n === "number" && Number.isFinite(n)))) throw new Error(`translateMark: channel "${key}" 只支持字段名、数值或四角数组,收到 ${typeof value}`);
613
+ }
612
614
  }
613
615
  }
614
616
  /**
@@ -705,6 +707,7 @@ function translateMark(spec, datasets) {
705
707
  if (rScale !== void 0) options.rScale = rScale;
706
708
  for (const key of STYLE_KEYS) if (spec[key] !== void 0) options[key] = spec[key];
707
709
  for (const key of SCALAR_KEYS) if (spec[key] !== void 0) options[key] = spec[key];
710
+ if ((spec.type === "barX" || spec.type === "barY") && spec.cornerRadius !== void 0 && spec.radius === void 0) options.radius = spec.cornerRadius;
708
711
  if (spec.id !== void 0) options.id = spec.id;
709
712
  if (spec.curve !== void 0) options.curve = spec.type === "violinY" ? translateAreaXCurve(spec.curve) : translateCurve(spec.curve);
710
713
  if (spec.layout !== void 0) options.layout = translateLayout(spec.layout);
@@ -825,6 +828,9 @@ function translatePolarAxis(spec, { allowRange = false } = {}) {
825
828
  * polar 容器:{ type:'polar', marks:[radialXxx...], angleAxis, radiusAxis, radiusRatio,
826
829
  * startAngle, endAngle, inset, polarGuides } → polar({...})。
827
830
  * extraMarks:调用方预组装的 PolarMark(sunburst 等自包含层级 mark 不走 radial 直译)。
831
+ * 0.18 契约:angle/radius scale 配置收进容器级 options.scales 保留通道
832
+ * (resolvePolarLayout 读 options.scales 的 reserved angle/radius 条目),
833
+ * 不再是顶层 angle/radius 字段;definition.scales 只需 x/y null 占位。
828
834
  */
829
835
  function translatePolar(spec, datasets, extraMarks = []) {
830
836
  const options = { marks: [...(spec.marks ?? []).map((m) => translateRadialMark(m, datasets)), ...extraMarks] };
@@ -833,12 +839,28 @@ function translatePolar(spec, datasets, extraMarks = []) {
833
839
  if (spec.endAngle !== void 0) options.endAngle = spec.endAngle;
834
840
  if (spec.inset !== void 0) options.inset = spec.inset;
835
841
  if (spec.id !== void 0) options.id = spec.id;
842
+ const MATERIALIZING_TYPES = /* @__PURE__ */ new Set([
843
+ "radialBarRadius",
844
+ "radialBarAngle",
845
+ "radialLine",
846
+ "radialArea",
847
+ "radialText",
848
+ "radialRule",
849
+ "radialDot"
850
+ ]);
851
+ const marksSpecs = spec.marks ?? [];
852
+ const hasRadialBarRadius = marksSpecs.some((m) => m.type === "radialBarRadius");
853
+ const hasRadialBarAngle = marksSpecs.some((m) => m.type === "radialBarAngle");
854
+ const materializesChannel = marksSpecs.some((m) => MATERIALIZING_TYPES.has(m.type));
836
855
  const angle = translatePolarAxis(spec.angleAxis);
837
856
  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 };
857
+ options.scales = materializesChannel ? {
858
+ angle: angle ?? { scale: () => hasRadialBarRadius ? scaleBand() : scalePoint() },
859
+ radius: radius ?? { scale: hasRadialBarAngle ? () => scaleBand() : scaleLinear }
860
+ } : {
861
+ angle: angle ?? null,
862
+ radius: radius ?? null
863
+ };
842
864
  if (spec.polarGuides?.length) options.guides = spec.polarGuides.map(translatePolarGuide);
843
865
  return polar(options);
844
866
  }
@@ -1275,7 +1297,7 @@ function translateViewGrid(spec, datasets, hooks) {
1275
1297
  */
1276
1298
  /** color.legend 声明 → 图例实例;true 等价 { kind:'color' } 缺省配置 */
1277
1299
  function translateLegend(legend) {
1278
- const { kind, label, placement, itemWidth, steps, width } = legend === true ? {} : legend;
1300
+ const { kind, label, placement, itemWidth, steps, width, items: itemSpec } = legend === true ? {} : legend;
1279
1301
  if (kind === "gradient") {
1280
1302
  const gradient = {};
1281
1303
  if (label !== void 0) gradient.label = label;
@@ -1289,6 +1311,15 @@ function translateLegend(legend) {
1289
1311
  if (placement !== void 0) items.placement = placement;
1290
1312
  if (itemWidth !== void 0) items.itemWidth = itemWidth;
1291
1313
  if (width !== void 0) items.width = width;
1314
+ if (itemSpec !== void 0) {
1315
+ const itemOptions = {};
1316
+ if (itemSpec.justify !== void 0) itemOptions.justify = itemSpec.justify;
1317
+ if (itemSpec.gap !== void 0) itemOptions.gap = itemSpec.gap;
1318
+ if (itemSpec.rowGap !== void 0) itemOptions.rowGap = itemSpec.rowGap;
1319
+ if (itemSpec.indicator !== void 0) itemOptions.indicator = itemSpec.indicator;
1320
+ if (itemSpec.label !== void 0) itemOptions.label = itemSpec.label;
1321
+ items.items = colorLegendItems(itemOptions);
1322
+ }
1292
1323
  return colorLegend(items);
1293
1324
  }
1294
1325
  /**
@@ -1401,6 +1432,7 @@ function buildDefinition(spec, datasets, rowsOverride, mode = "host") {
1401
1432
  } else pushMark(bound, translateMark(bound, table));
1402
1433
  }
1403
1434
  if (mode === "child" && spec.theme?.background !== void 0) throw new Error("translateViewGrid 子视图不得声明 theme.background(库拒绝子图持有场景背景;背景用子图内普通 mark 表达)");
1435
+ if (mode === "child" && spec.gradients !== void 0) throw new Error("translateViewGrid 子视图不得声明 gradients(host 权属渐变资源,配置在外层 spec)");
1404
1436
  const theme = mode === "child" ? spec.theme === void 0 ? void 0 : { ...spec.theme } : {
1405
1437
  ...defaultChartTheme,
1406
1438
  ...spec.theme
@@ -1412,21 +1444,22 @@ function buildDefinition(spec, datasets, rowsOverride, mode = "host") {
1412
1444
  const axisless = polarOnly || geoOnly;
1413
1445
  const xSpec = spec.x === void 0 && axisless ? null : spec.x;
1414
1446
  const ySpec = spec.y === void 0 && axisless ? null : spec.y;
1447
+ const scales = {};
1415
1448
  const x = translateAxis(xSpec ?? void 0, "point");
1416
1449
  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,
1450
+ scales.x = xSpec === null ? null : x;
1451
+ scales.y = ySpec === null ? null : y;
1452
+ definition.scales = scales;
1453
+ if (derivedXDomain && spec.x?.scale?.domain === void 0) scales.x = {
1454
+ ...x,
1423
1455
  scale: scaleLinear().domain(derivedXDomain)
1424
1456
  };
1425
- if (derivedYDomain && spec.y?.scale?.domain === void 0) definition.y = {
1426
- ...definition.y,
1457
+ if (derivedYDomain && spec.y?.scale?.domain === void 0) scales.y = {
1458
+ ...y,
1427
1459
  scale: scaleLinear().domain(derivedYDomain)
1428
1460
  };
1429
1461
  if (spec.margin !== void 0) definition.margin = spec.margin;
1462
+ if (spec.gradients !== void 0) definition.gradients = spec.gradients;
1430
1463
  if (spec.guides !== void 0) definition.guides = spec.guides;
1431
1464
  else if (polarOnly) definition.guides = false;
1432
1465
  if (spec.clip !== void 0) definition.clip = spec.clip;
@@ -2166,15 +2199,25 @@ function usePieChart(attrs) {
2166
2199
  const clean = rows.filter((row) => row !== null && typeof row === "object" && Number.isFinite(Number(row[valueField])) && Number(row[valueField]) >= 0);
2167
2200
  const slices = pie(clean, { value: (datum) => Number(datum[valueField]) });
2168
2201
  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
- })] });
2202
+ return defineChart({
2203
+ marks: [polar({
2204
+ inset: 8,
2205
+ scales: {
2206
+ angle: null,
2207
+ radius: null
2208
+ },
2209
+ marks: [radialArc(slices, {
2210
+ color: (datum) => String(datum[nameField]),
2211
+ key: (datum) => String(datum[nameField]),
2212
+ innerRadius: ratio > 0 ? ({ radius }) => radius * ratio : 0,
2213
+ cornerRadius: 2
2214
+ })]
2215
+ })],
2216
+ scales: {
2217
+ x: null,
2218
+ y: null
2219
+ }
2220
+ });
2178
2221
  }),
2179
2222
  hostProps,
2180
2223
  ariaLabel
@@ -2313,28 +2356,37 @@ function keyOf(key) {
2313
2356
  if (!comp) throw new Error(`tanstack-charts 物料定义缺失: ${key}`);
2314
2357
  return comp._cx_meta.key;
2315
2358
  }
2316
- const TANSTACK_CHARTS_STREAM_TRIGGERS = [{
2317
- key: keyOf("cx-data-chart"),
2318
- sections: [{
2319
- kind: "array",
2320
- arrayKey: "*"
2321
- }],
2322
- stateBranch: { emptyPassthrough: true },
2323
- frameStride: 10
2324
- }, ...[
2359
+ const ARRAY_PRESET_KEYS = [
2325
2360
  "cx-tanstack-charts-line",
2326
2361
  "cx-tanstack-charts-bar",
2327
2362
  "cx-tanstack-charts-area",
2328
2363
  "cx-tanstack-charts-dot",
2329
2364
  "cx-tanstack-charts-pie"
2330
- ].map((key) => ({
2331
- key: keyOf(key),
2365
+ ];
2366
+ const CHART_STREAM_TRIGGER = {
2367
+ key: keyOf("cx-data-chart"),
2332
2368
  sections: [{
2333
2369
  kind: "array",
2334
- arrayKey: "data"
2370
+ arrayKey: "*"
2335
2371
  }],
2372
+ stateBranch: { emptyPassthrough: true },
2336
2373
  frameStride: 10
2337
- }))];
2374
+ };
2375
+ const TANSTACK_CHARTS_STREAM_TRIGGERS = [
2376
+ CHART_STREAM_TRIGGER,
2377
+ {
2378
+ ...CHART_STREAM_TRIGGER,
2379
+ key: "cx-chart"
2380
+ },
2381
+ ...ARRAY_PRESET_KEYS.map((key) => ({
2382
+ key: keyOf(key),
2383
+ sections: [{
2384
+ kind: "array",
2385
+ arrayKey: "data"
2386
+ }],
2387
+ frameStride: 10
2388
+ }))
2389
+ ];
2338
2390
  /** 装配 TanStack Charts 物料的 trigger 注册表(size 恒 6) */
2339
2391
  function createTanstackChartsTriggerRegistry() {
2340
2392
  const registry = createTriggerRegistry();
@@ -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.26",
3
+ "version": "0.1.0-alpha.28",
4
4
  "description": "cx material components wrapping TanStack Charts (@tanstack/charts/vue)",
5
5
  "keywords": [
6
6
  "charts",
@@ -43,7 +43,7 @@
43
43
  "d3-shape": "^3.2.0"
44
44
  },
45
45
  "devDependencies": {
46
- "@tanstack/charts": "^0.14.0",
46
+ "@tanstack/charts": "^0.18.0",
47
47
  "@types/d3-geo": "^3.1.1",
48
48
  "@types/d3-scale": "^4.0.9",
49
49
  "@types/d3-shape": "^3.1.8",