@lionad/cx-comps-tanstack-charts 0.1.0-alpha.10 → 0.1.0-alpha.12

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
@@ -26,6 +26,7 @@ import { sankeyDiagram } from "@tanstack/charts/network/sankey";
26
26
  import { geoAlbersUsa, geoEqualEarth, geoIdentity, geoMercator, geoNaturalEarth1, geoOrthographic } from "d3-geo";
27
27
  import { decorative } from "@tanstack/charts/mark/decorative";
28
28
  import { tooltip } from "@tanstack/charts/tooltip";
29
+ import { viewGrid } from "@tanstack/charts/view";
29
30
  import { compileTrigger, createTriggerRegistry } from "@lionad/cx-stream";
30
31
  //#region src/shared/chart-skeleton.vue
31
32
  const _sfc_main$6 = /*@__PURE__*/ defineComponent({
@@ -588,7 +589,7 @@ function translateFrame(spec) {
588
589
  }
589
590
  /** channel 运行时校验:只接受字段名字符串(accessor 函数 JSON 不可表达) */
590
591
  function assertChannels(spec) {
591
- const constantKeys = spec.type === "ruleX" ? /* @__PURE__ */ new Set(["x"]) : spec.type === "ruleY" ? /* @__PURE__ */ new Set(["y"]) : void 0;
592
+ const constantKeys = spec.type === "ruleX" ? /* @__PURE__ */ new Set(["x"]) : spec.type === "ruleY" ? /* @__PURE__ */ new Set(["y"]) : spec.type === "rect" ? /* @__PURE__ */ new Set(["x1", "y1"]) : void 0;
592
593
  for (const key of CHANNEL_KEYS) {
593
594
  const value = spec[key];
594
595
  if (value !== void 0 && typeof value !== "string") {
@@ -669,7 +670,11 @@ function translateMark(spec, datasets) {
669
670
  if (!factory) throw new Error(`translateMark: 未知 mark type "${String(spec.type)}"`);
670
671
  assertChannels(spec);
671
672
  const options = {};
672
- for (const key of CHANNEL_KEYS) if (spec[key] !== void 0) options[key] = spec[key];
673
+ for (const key of CHANNEL_KEYS) {
674
+ const value = spec[key];
675
+ if (value === void 0) continue;
676
+ options[key] = typeof value === "number" ? () => value : value;
677
+ }
673
678
  for (const key of CHANNEL_FLEX_KEYS) if (spec[key] !== void 0) options[key] = spec[key];
674
679
  for (const key of STYLE_KEYS) if (spec[key] !== void 0) options[key] = spec[key];
675
680
  for (const key of SCALAR_KEYS) if (spec[key] !== void 0) options[key] = spec[key];
@@ -761,11 +766,16 @@ function translatePolarGuide(spec) {
761
766
  return factory(options);
762
767
  }
763
768
  /** polar axis 声明式(复用 scale 枚举;wrap 仅 angle 轴)→ PolarAngleOptions/PolarRadiusOptions */
764
- function translatePolarAxis(spec) {
769
+ function translatePolarAxis(spec, { allowRange = false } = {}) {
765
770
  if (!spec) return void 0;
766
771
  const axis = {};
767
772
  if (spec.scale) axis.scale = translateScale(spec.scale);
768
773
  if (spec.nice !== void 0) axis.nice = spec.nice;
774
+ if (allowRange && spec.range !== void 0) {
775
+ const [r0, r1] = spec.range;
776
+ if (!Number.isFinite(r0) || !Number.isFinite(r1) || r0 < 0 || r1 < r0) throw new Error(`translatePolar: radiusAxis.range 须为有限非负且递增的比例对,收到 [${r0}, ${r1}]`);
777
+ axis.range = [ratioToLength(r0), ratioToLength(r1)];
778
+ }
769
779
  return axis;
770
780
  }
771
781
  /**
@@ -781,7 +791,7 @@ function translatePolar(spec, datasets, extraMarks = []) {
781
791
  if (spec.inset !== void 0) options.inset = spec.inset;
782
792
  if (spec.id !== void 0) options.id = spec.id;
783
793
  const angle = translatePolarAxis(spec.angleAxis);
784
- const radius = translatePolarAxis(spec.radiusAxis);
794
+ const radius = translatePolarAxis(spec.radiusAxis, { allowRange: true });
785
795
  const hasRadialBarRadius = (spec.marks ?? []).some((m) => m.type === "radialBarRadius");
786
796
  const hasRadialBarAngle = (spec.marks ?? []).some((m) => m.type === "radialBarAngle");
787
797
  options.angle = angle ?? { scale: () => hasRadialBarRadius ? scaleBand() : scalePoint() };
@@ -1118,6 +1128,31 @@ function translateCompositeMark(spec, datasets, translateChild) {
1118
1128
  }
1119
1129
  }
1120
1130
  //#endregion
1131
+ //#region src/shared/translate/view.ts
1132
+ function translateViewGrid(spec, datasets, hooks) {
1133
+ const grid = spec.views;
1134
+ const views = grid.items.map((item) => {
1135
+ if (item.chart.views !== void 0) throw new Error(`translateViewGrid: 子视图 "${item.id}" 不支持嵌套 views`);
1136
+ return {
1137
+ id: item.id,
1138
+ row: item.row,
1139
+ column: item.column,
1140
+ ...item.share ? { share: item.share } : {},
1141
+ ...item.align ? { align: item.align } : {},
1142
+ chart: hooks.buildChild(item.chart, datasets)
1143
+ };
1144
+ });
1145
+ const composed = viewGrid({
1146
+ rows: grid.rows,
1147
+ columns: grid.columns,
1148
+ ...grid.gap !== void 0 ? { gap: grid.gap } : {},
1149
+ ...grid.rowGap !== void 0 ? { rowGap: grid.rowGap } : {},
1150
+ ...grid.columnGap !== void 0 ? { columnGap: grid.columnGap } : {},
1151
+ views
1152
+ });
1153
+ return hooks.applyHost(composed, spec);
1154
+ }
1155
+ //#endregion
1121
1156
  //#region src/shared/translate/definition.ts
1122
1157
  /**
1123
1158
  * definition 组装:声明式 spec → DomChartDefinition(可直接喂给 <Chart>)。
@@ -1128,6 +1163,11 @@ function translateCompositeMark(spec, datasets, translateChild) {
1128
1163
  * 置 null 显式无轴(库对 polar mark 的 scale 类型为 never,缺省 axis 注入会抛错)
1129
1164
  * - tree/forceGraph 展开为多 mark;forceGraph 回传的轴域在 x/y 未显式声明时注入
1130
1165
  * linear scale 实例(仿真坐标以 0 为中心,缺省推断 domain 不包含负半轴余量)
1166
+ * - spec.views 存在时分流 viewGrid 多视图组合(views 与 marks 互斥),各子视图以
1167
+ * child 模式递归构建——host 权属字段(tooltip/pointer/keyboard/focus/focusRing)
1168
+ * 归外层组合定义持有(官方 assertChildDefinition 拒绝子 view 携带),child 模式
1169
+ * 下显式声明即抛错;theme 仅携带 spec 差异(defaultChartTheme.background 会触发
1170
+ * 库拒绝,外层 mergeTheme 负责补齐缺省)
1131
1171
  */
1132
1172
  /** color.legend 声明 → 图例实例;true 等价 { kind:'color' } 缺省配置 */
1133
1173
  function translateLegend(legend) {
@@ -1189,7 +1229,7 @@ function coerceTemporalChannels(spec, table) {
1189
1229
  if (!set) fieldsByDataset.set(name, set = /* @__PURE__ */ new Set());
1190
1230
  set.add(field);
1191
1231
  };
1192
- for (const mark of spec.marks) {
1232
+ for (const mark of spec.marks ?? []) {
1193
1233
  if (xTemporal) for (const f of [
1194
1234
  "x",
1195
1235
  "x1",
@@ -1221,22 +1261,24 @@ function coerceTemporalChannels(spec, table) {
1221
1261
  return changed ? next : table;
1222
1262
  }
1223
1263
  /**
1224
- * 单 spec → definition 核心(facet 子 spec 复用):返回原始 spec 对象而非
1225
- * defineChart 产物——facet chart 回调要求返回 ChartSpec 字面量形态。
1264
+ * 单 spec → definition 核心(facet 子 spec、viewGrid 子视图复用):返回原始 spec
1265
+ * 对象而非 defineChart 产物——facet chart 回调要求返回 ChartSpec 字面量形态。
1226
1266
  * rowsOverride:facet 分组行注入(子 spec marks 未声明 data 时绑定分组行)。
1267
+ * mode 'child':viewGrid 子视图构建——剥除 host 权属字段(见文件头约定)。
1227
1268
  */
1228
- function buildDefinition(spec, datasets, rowsOverride) {
1269
+ function buildDefinition(spec, datasets, rowsOverride, mode = "host") {
1229
1270
  const base = { ...datasets };
1230
1271
  if (rowsOverride !== void 0) base.rows = rowsOverride;
1231
1272
  const table = coerceTemporalChannels(spec, spec.transforms?.length ? applyTransforms(spec.transforms, base) : base);
1273
+ const markSpecs = spec.marks ?? [];
1232
1274
  const marks = [];
1233
1275
  let derivedXDomain;
1234
1276
  let derivedYDomain;
1235
- let polarOnly = spec.marks.length > 0;
1277
+ let polarOnly = markSpecs.length > 0;
1236
1278
  const pushMark = (specMark, runtimeMark) => {
1237
1279
  marks.push(specMark.decorative === true ? decorative(runtimeMark) : runtimeMark);
1238
1280
  };
1239
- for (const mark of spec.marks) {
1281
+ for (const mark of spec.marks ?? []) {
1240
1282
  const bound = rowsOverride !== void 0 && mark.data === void 0 ? {
1241
1283
  ...mark,
1242
1284
  data: "rows"
@@ -1245,18 +1287,20 @@ function buildDefinition(spec, datasets, rowsOverride) {
1245
1287
  if (bound.type === "polar") pushMark(bound, translatePolar(bound, table));
1246
1288
  else if (bound.type === "pie") pushMark(bound, translatePie(bound, table));
1247
1289
  else if (COMPOSITE_TYPES.has(bound.type)) {
1248
- const result = translateCompositeMark(bound, table, (child, rows) => buildDefinition(child, void 0, rows));
1290
+ const result = translateCompositeMark(bound, table, (child, rows) => buildDefinition(child, table, rows));
1249
1291
  for (const expanded of result.marks) pushMark(bound, expanded);
1250
1292
  if (result.xDomain) derivedXDomain = result.xDomain;
1251
1293
  if (result.yDomain) derivedYDomain = result.yDomain;
1252
1294
  } else pushMark(bound, translateMark(bound, table));
1253
1295
  }
1254
- const definition = {
1296
+ if (mode === "child" && spec.theme?.background !== void 0) throw new Error("translateViewGrid 子视图不得声明 theme.background(库拒绝子图持有场景背景;背景用子图内普通 mark 表达)");
1297
+ const theme = mode === "child" ? spec.theme === void 0 ? void 0 : { ...spec.theme } : {
1298
+ ...defaultChartTheme,
1299
+ ...spec.theme
1300
+ };
1301
+ const definition = theme === void 0 ? { marks } : {
1255
1302
  marks,
1256
- theme: {
1257
- ...defaultChartTheme,
1258
- ...spec.theme
1259
- }
1303
+ theme
1260
1304
  };
1261
1305
  const xSpec = spec.x === void 0 && polarOnly ? null : spec.x;
1262
1306
  const ySpec = spec.y === void 0 && polarOnly ? null : spec.y;
@@ -1285,6 +1329,16 @@ function buildDefinition(spec, datasets, rowsOverride) {
1285
1329
  if (spec.color.legend !== void 0) color.legend = translateLegend(spec.color.legend);
1286
1330
  definition.color = color;
1287
1331
  }
1332
+ if (mode === "child") {
1333
+ for (const key of [
1334
+ "tooltip",
1335
+ "pointer",
1336
+ "keyboard",
1337
+ "focusRing",
1338
+ "focus"
1339
+ ]) if (spec[key] !== void 0) throw new Error(`translateViewGrid 子视图不得声明 "${key}"(host 权属字段,配置在外层 spec)`);
1340
+ return definition;
1341
+ }
1288
1342
  if (spec.pointer !== void 0) definition.pointer = spec.pointer;
1289
1343
  if (spec.keyboard !== void 0) definition.keyboard = spec.keyboard;
1290
1344
  if (spec.focusRing !== void 0) definition.focusRing = spec.focusRing;
@@ -1298,6 +1352,24 @@ function buildDefinition(spec, datasets, rowsOverride) {
1298
1352
  * datasets:命名数据集表(数据顶层化契约),marks 内字符串引用在此解析。
1299
1353
  */
1300
1354
  function translateChartSpec(spec, datasets) {
1355
+ if (spec.views !== void 0) {
1356
+ if (spec.marks?.length) throw new Error("translateChartSpec: views 与 marks 互斥——viewGrid 顶层不直接绘制 marks,图元属于各子视图");
1357
+ return defineChart(translateViewGrid(spec, datasets, {
1358
+ buildChild: (child, table) => defineChart(buildDefinition(child, table, void 0, "child")),
1359
+ applyHost: (composed, outer) => {
1360
+ const hosted = {
1361
+ ...composed,
1362
+ theme: {
1363
+ ...defaultChartTheme,
1364
+ ...outer.theme
1365
+ }
1366
+ };
1367
+ const tooltip$3 = outer.tooltip === void 0 ? tooltip : translateTooltip(outer.tooltip);
1368
+ if (tooltip$3 !== void 0) hosted.tooltip = tooltip$3;
1369
+ return hosted;
1370
+ }
1371
+ }));
1372
+ }
1301
1373
  return defineChart(buildDefinition(spec, datasets));
1302
1374
  }
1303
1375
  //#endregion
@@ -2,9 +2,9 @@
2
2
  * 声明式 JSON → TanStack Charts 运行时定义的翻译层(桶文件)。
3
3
  * 模块分工:types(声明式契约)/ curve / scale / axis / mark(基础+spatial)/
4
4
  * transforms(数据预处理管道)/ polar(极坐标族)/ composite(七类命名复合)/
5
- * definition(translateChartSpec 组装入口)。
5
+ * view(viewGrid 多视图组合)/ definition(translateChartSpec 组装入口)。
6
6
  */
7
- export type { CxChartAxisSpec, CxChartCurveName, CxChartDatasets, CxChartForceSpec, CxChartLayoutSpec, CxChartMarkSpec, CxChartMarkType, CxChartPolarGuideSpec, CxChartReduce, CxChartScaleSpec, CxChartSpec, CxChartTransformOutputs, CxChartTransformSpec, } from './types';
7
+ export type { CxChartAxisSpec, CxChartCurveName, CxChartDatasets, CxChartForceSpec, CxChartLayoutSpec, CxChartMarkSpec, CxChartMarkType, CxChartPolarGuideSpec, CxChartReduce, CxChartScaleSpec, CxChartSpec, CxChartTransformOutputs, CxChartTransformSpec, CxChartViewGridSpec, CxChartViewItem, CxChartViewLink, CxChartViewTrack, } from './types';
8
8
  export { CX_CHART_CURVE_NAMES, translateCurve } from './curve';
9
9
  export { translateScale } from './scale';
10
10
  export { translateAxis } from './axis';
@@ -43,6 +43,12 @@ export interface CxChartAxisSpec {
43
43
  nice?: boolean | number;
44
44
  reverse?: boolean;
45
45
  grid?: boolean;
46
+ /**
47
+ * 半径像素 range 的比例对(相对 polar 最终半径,0–1),仅 polar 容器的
48
+ * radiusAxis 消费;物化为官方 PolarLength 回调数组(resize 时随最终半径重解析)。
49
+ * 用途:rose/径向条等需要「语义零映射到内径偏移」而非物理圆心的场景。
50
+ */
51
+ range?: [number, number];
46
52
  axis?: false | {
47
53
  label?: string;
48
54
  ticks?: {
@@ -89,7 +95,8 @@ export type CxChartMarkType = 'lineY' | 'lineX' | 'areaY' | 'areaX' | 'barY' | '
89
95
  /**
90
96
  * 单 mark 声明式。平铺契约(LLM 友好):专有标量字段全部可选、按名透传,
91
97
  * 不适用的字段被对应工厂忽略;channel 只接受字段名字符串(弹性 channel
92
- * angle/radius/width/height/length/rotate/r 另接受数值常量)。
98
+ * angle/radius/width/height/length/rotate/r 另接受数值常量;x1/y1 仅在
99
+ * rect 基线场景接受数值常量——对应官方直方图零基线写法 y1:()=>0)。
93
100
  */
94
101
  export interface CxChartMarkSpec {
95
102
  type: CxChartMarkType;
@@ -110,8 +117,8 @@ export interface CxChartMarkSpec {
110
117
  decorative?: boolean;
111
118
  x?: string;
112
119
  y?: string;
113
- x1?: string;
114
- y1?: string;
120
+ x1?: string | number;
121
+ y1?: string | number;
115
122
  x2?: string;
116
123
  y2?: string;
117
124
  z?: string;
@@ -423,8 +430,49 @@ export type CxChartTransformSpec = {
423
430
  };
424
431
  /** 命名数据集表:物料 data 顶层除 definition 外的数组字段(GenUI 契约 rows/nodes/links) */
425
432
  export type CxChartDatasets = Record<string, readonly unknown[] | undefined>;
433
+ /** viewGrid 网格轨道:size 固定像素 / grow 按比例分配剩余空间(min/max 钳制) */
434
+ export type CxChartViewTrack = {
435
+ id: string;
436
+ size: number;
437
+ } | {
438
+ id: string;
439
+ grow: number;
440
+ min?: number;
441
+ max?: number;
442
+ };
443
+ /** viewGrid 轴链接目标:share 共享 scale(domain/序/方向/bandwidth 须一致,不一致显式失败);align 仅对齐 plot 端点、domain 各自独立 */
444
+ export interface CxChartViewLink {
445
+ x?: string;
446
+ y?: string;
447
+ }
448
+ export interface CxChartViewItem {
449
+ id: string;
450
+ /** 轨道引用:row/column 分别指向 views.rows/views.columns 的轨道 id */
451
+ row: string;
452
+ column: string;
453
+ share?: CxChartViewLink;
454
+ align?: CxChartViewLink;
455
+ /**
456
+ * 子视图 spec(marks/x/y/color/margin/guides/transforms 等归属子图);
457
+ * host 权属字段(tooltip/pointer/keyboard/focus/focusRing)归外层组合定义,
458
+ * 子视图声明即拒绝(官方 assertChildDefinition 契约);不支持嵌套 views。
459
+ */
460
+ chart: CxChartSpec;
461
+ }
462
+ /** viewGrid 多视图组合(非重叠网格;叠层/内嵌等组合形态官方 composeViews 支持、契约暂不暴露) */
463
+ export interface CxChartViewGridSpec {
464
+ rows: CxChartViewTrack[];
465
+ columns: CxChartViewTrack[];
466
+ gap?: number;
467
+ rowGap?: number;
468
+ columnGap?: number;
469
+ items: CxChartViewItem[];
470
+ }
426
471
  export interface CxChartSpec {
427
- marks: CxChartMarkSpec[];
472
+ /** viewGrid 顶层省略(views 与 marks 互斥);其余场景必填 */
473
+ marks?: CxChartMarkSpec[];
474
+ /** 多视图组合:存在时 marks 须为空,各子视图在网格单元内独立成图、共享外层 host */
475
+ views?: CxChartViewGridSpec;
428
476
  x?: CxChartAxisSpec | null;
429
477
  y?: CxChartAxisSpec | null;
430
478
  theme?: Partial<ChartTheme>;
@@ -0,0 +1,16 @@
1
+ import type { CxChartDatasets, CxChartSpec } from './types';
2
+ /**
3
+ * viewGrid 多视图组合:spec.views → 官方 viewGrid(命名视图 + 网格轨道 + 轴链接)。
4
+ *
5
+ * 权属分界(官方 assertChildDefinition 契约):子 view 保留 marks/scales/guides/
6
+ * margin/color/clip;host 字段(tooltip/keyboard/pointer/focus/focusRing/motion/
7
+ * gradients/theme.background)只能由外层组合定义持有,子 view 携带即被库拒绝。
8
+ * 子 view 经 buildChild 以 child 模式递归构建(剥除 host 字段,见 definition.ts);
9
+ * 顶层 host 字段(theme 合并、tooltip 缺省注入)由 applyHost 施加于组合产物。
10
+ * hooks 回调注入保持依赖单向(definition.ts → view.ts,同 composite 先例)。
11
+ */
12
+ export interface CxChartViewGridHooks {
13
+ buildChild: (child: CxChartSpec, datasets: CxChartDatasets | undefined) => unknown;
14
+ applyHost: (composed: Record<string, unknown>, spec: CxChartSpec) => Record<string, unknown>;
15
+ }
16
+ export declare function translateViewGrid(spec: CxChartSpec, datasets: CxChartDatasets | undefined, hooks: CxChartViewGridHooks): Record<string, unknown>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lionad/cx-comps-tanstack-charts",
3
- "version": "0.1.0-alpha.10",
3
+ "version": "0.1.0-alpha.12",
4
4
  "description": "cx material components wrapping TanStack Charts (@tanstack/charts/vue)",
5
5
  "keywords": [
6
6
  "charts",