@lionad/cx-comps-tanstack-charts 0.1.0-alpha.18 → 0.1.0-alpha.20

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.
@@ -0,0 +1,15 @@
1
+ import type { CxChartSpec } from '../../shared/translate';
2
+ interface Props {
3
+ /** 运行时 DomChartDefinition(translateChartSpec 产物,已含 chart 级 motion 时序) */
4
+ definition: Record<string, unknown>;
5
+ ariaLabel: string;
6
+ /** spec.motion 的 renderer 级选项(initial/resize);transition/delay 已在 definition.motion */
7
+ motionOptions?: CxChartSpec['motion'];
8
+ height?: number;
9
+ width?: number;
10
+ aspectRatio?: number;
11
+ className?: string;
12
+ }
13
+ declare const __VLS_export: import("vue").DefineComponent<Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
14
+ declare const _default: typeof __VLS_export;
15
+ export default _default;
package/dist/index.mjs CHANGED
@@ -1,10 +1,10 @@
1
1
  import { define } from "@lionad/cx-definition";
2
- import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, mergeProps, normalizeClass, normalizeStyle, onBeforeUnmount, onMounted, openBlock, renderList, unref, useAttrs } from "vue";
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
5
  import { areaX, areaY, arrow, bandX, bandY, barX, barY, binX, binXY, binY, boxRows, boxX, boxY, cell, colorGradientLegend, colorLegend, cumulative, d3Curve, defaultChartTheme, defineChart, delta, deviation, differenceX, differenceY, dodgeX, dodgeY, dot, first, fold, frame, group, groupBy, hexagon, last, lineX, lineY, linearRegressionRowsX, linearRegressionRowsY, linearRegressionX, linearRegressionY, link, median, mosaicX, mosaicY, normalize, quantile, rank, ratio, rect, ridgelineX, ridgelineY, rollingWindow, ruleX, ruleY, select, stack, stackRowsX, stackRowsY, text, tickX, tickY, variance, vector, violinX, violinY, waffleX, waffleY, waterfall } from "@tanstack/charts";
6
6
  import { d3AreaXCurve } from "@tanstack/charts/d3/area-x";
7
- import { curveBasis, curveCatmullRom, curveLinear, curveLinearClosed, curveMonotoneX, curveNatural, curveStep, curveStepAfter, curveStepBefore } from "d3-shape";
7
+ import { curveBasis, curveBumpX, curveCatmullRom, curveLinear, curveLinearClosed, curveMonotoneX, curveNatural, curveStep, curveStepAfter, curveStepBefore } from "d3-shape";
8
8
  import { scaleLinear } from "@tanstack/charts/scales/linear";
9
9
  import { scaleOrdinal } from "@tanstack/charts/scales/ordinal";
10
10
  import { scalePoint } from "@tanstack/charts/scales/point";
@@ -27,9 +27,12 @@ import { geoAlbersUsa, geoEqualEarth, geoEquirectangular, geoIdentity, geoMercat
27
27
  import { decorative } from "@tanstack/charts/mark/decorative";
28
28
  import { tooltip } from "@tanstack/charts/tooltip";
29
29
  import { viewGrid } from "@tanstack/charts/view";
30
+ import "@tanstack/charts/adapter";
31
+ import { createChartRendererAdapter } from "@tanstack/charts/adapter/renderer";
32
+ import { motion } from "@tanstack/charts/motion";
30
33
  import { compileTrigger, createTriggerRegistry } from "@lionad/cx-stream";
31
34
  //#region src/shared/chart-skeleton.vue
32
- const _sfc_main$6 = /*@__PURE__*/ defineComponent({
35
+ const _sfc_main$7 = /*@__PURE__*/ defineComponent({
33
36
  __name: "chart-skeleton",
34
37
  props: { height: { default: 320 } },
35
38
  setup(__props) {
@@ -910,6 +913,18 @@ function hierarchyOptions(spec, idKey) {
910
913
  function hierarchyFieldChannel(field) {
911
914
  return (node) => node?.data?.[field];
912
915
  }
916
+ /**
917
+ * sankey 布局行顶层只有 canonical 字段(key/width/坐标),原 datum 收纳在 .data 下;
918
+ * 声明式样式字段名(如 stroke:"色调"、color:"id")须穿透到原 datum 解析,
919
+ * 否则 channel 按顶层查表得 undefined 而静默退回默认样式。
920
+ */
921
+ function sankeyDatumField(field) {
922
+ return (row) => {
923
+ const record = row;
924
+ if (record.data != null && field in record.data) return record.data[field];
925
+ return record[field];
926
+ };
927
+ }
913
928
  /** sankey:固化 marks 回调 = link(布局连线) + rect(布局节点),样式取自声明 */
914
929
  function translateSankey(spec, datasets) {
915
930
  const nodes = resolveMarkData(spec.nodes, datasets);
@@ -922,25 +937,30 @@ function translateSankey(spec, datasets) {
922
937
  source: spec.source,
923
938
  target: spec.target,
924
939
  value: spec.value,
925
- marks: (context) => [looseLink(context.links, {
926
- x1: "x1",
927
- y1: "y1",
928
- x2: "x2",
929
- y2: "y2",
930
- key: "key",
931
- strokeWidth: "width",
932
- lineCap: "butt",
933
- stroke: spec.stroke ?? void 0,
934
- strokeOpacity: spec.strokeOpacity ?? .55
935
- }), looseRect(context.nodes, {
936
- x1: "x0",
937
- x2: "x1",
938
- y1: "y0",
939
- y2: "y1",
940
- key: "key",
941
- color: spec.color ?? "key",
942
- fillOpacity: spec.fillOpacity ?? void 0
943
- })]
940
+ marks: (context) => {
941
+ const strokeField = typeof spec.stroke === "string" && context.links.some((l) => l?.data?.[spec.stroke] !== void 0) ? spec.stroke : null;
942
+ return [looseLink(context.links, {
943
+ x1: "x1",
944
+ y1: "y1",
945
+ x2: "x2",
946
+ y2: "y2",
947
+ key: "key",
948
+ strokeWidth: (row) => Math.max(1, Number(row.width) || 1),
949
+ lineCap: "butt",
950
+ curve: d3Curve(curveBumpX),
951
+ color: strokeField ? sankeyDatumField(strokeField) : void 0,
952
+ stroke: strokeField ? void 0 : spec.stroke ?? void 0,
953
+ strokeOpacity: spec.strokeOpacity ?? .55
954
+ }), looseRect(context.nodes, {
955
+ x1: "x0",
956
+ x2: "x1",
957
+ y1: "y0",
958
+ y2: "y1",
959
+ key: "key",
960
+ color: typeof spec.color === "string" ? sankeyDatumField(spec.color) : spec.color ?? "key",
961
+ fillOpacity: spec.fillOpacity ?? void 0
962
+ })];
963
+ }
944
964
  };
945
965
  if (spec.id !== void 0) options.id = spec.id;
946
966
  if (spec.align !== void 0) options.align = spec.align;
@@ -1422,7 +1442,8 @@ function buildDefinition(spec, datasets, rowsOverride, mode = "host") {
1422
1442
  "pointer",
1423
1443
  "keyboard",
1424
1444
  "focusRing",
1425
- "focus"
1445
+ "focus",
1446
+ "motion"
1426
1447
  ]) if (spec[key] !== void 0) throw new Error(`translateViewGrid 子视图不得声明 "${key}"(host 权属字段,配置在外层 spec)`);
1427
1448
  return definition;
1428
1449
  }
@@ -1430,6 +1451,12 @@ function buildDefinition(spec, datasets, rowsOverride, mode = "host") {
1430
1451
  if (spec.keyboard !== void 0) definition.keyboard = spec.keyboard;
1431
1452
  if (spec.focusRing !== void 0) definition.focusRing = spec.focusRing;
1432
1453
  if (spec.focus !== void 0) definition.focus = spec.focus;
1454
+ if (spec.motion !== void 0) {
1455
+ const timing = {};
1456
+ if (spec.motion.transition !== void 0) timing.transition = spec.motion.transition;
1457
+ if (spec.motion.delay !== void 0) timing.delay = spec.motion.delay;
1458
+ definition.motion = timing;
1459
+ }
1433
1460
  const tooltip$2 = spec.tooltip === void 0 ? tooltip : translateTooltip(spec.tooltip);
1434
1461
  if (tooltip$2 !== void 0) definition.tooltip = tooltip$2;
1435
1462
  return definition;
@@ -1460,6 +1487,77 @@ function translateChartSpec(spec, datasets) {
1460
1487
  return defineChart(buildDefinition(spec, datasets));
1461
1488
  }
1462
1489
  //#endregion
1490
+ //#region src/chart/src/motion-chart.vue
1491
+ const _hoisted_1 = ["innerHTML"];
1492
+ const _sfc_main$6 = /*@__PURE__*/ defineComponent({
1493
+ __name: "motion-chart",
1494
+ props: {
1495
+ definition: {},
1496
+ ariaLabel: {},
1497
+ motionOptions: {},
1498
+ height: {},
1499
+ width: {},
1500
+ aspectRatio: {},
1501
+ className: {}
1502
+ },
1503
+ setup(__props) {
1504
+ const props = __props;
1505
+ const idPrefix = `ts-chart-${useId().replaceAll(/[^a-zA-Z0-9_-]/g, "")}`;
1506
+ const hostStyle = computed(() => ({
1507
+ position: "relative",
1508
+ width: props.width === void 0 ? "100%" : `${props.width}px`,
1509
+ height: props.height ?? (props.aspectRatio === void 0 ? 320 : void 0),
1510
+ aspectRatio: props.height === void 0 ? props.aspectRatio : void 0
1511
+ }));
1512
+ /** host options 组装:与库 vue adapter toHostOptions 同构,renderer 固定为 motion() */
1513
+ function toHostOptions() {
1514
+ return {
1515
+ definition: props.definition,
1516
+ ariaLabel: props.ariaLabel,
1517
+ height: props.height,
1518
+ width: props.width,
1519
+ aspectRatio: props.aspectRatio,
1520
+ className: props.className,
1521
+ idPrefix,
1522
+ renderer: motion({
1523
+ initial: props.motionOptions?.initial ?? true,
1524
+ resize: props.motionOptions?.resize
1525
+ })
1526
+ };
1527
+ }
1528
+ const adapter = createChartRendererAdapter(toHostOptions());
1529
+ const initialMarkup = adapter.prerender();
1530
+ const container = ref();
1531
+ onMounted(() => {
1532
+ if (!container.value) return;
1533
+ adapter.update(toHostOptions());
1534
+ adapter.mount(container.value);
1535
+ });
1536
+ watch(() => [
1537
+ props.definition,
1538
+ props.height,
1539
+ props.width,
1540
+ props.aspectRatio
1541
+ ], () => adapter.update(toHostOptions()));
1542
+ onBeforeUnmount(() => adapter.destroy());
1543
+ return (_ctx, _cache) => {
1544
+ return openBlock(), createElementBlock("div", {
1545
+ class: "ts-chart-host",
1546
+ style: normalizeStyle(hostStyle.value)
1547
+ }, [createCommentVNode(" 首帧 markup 只注入一次:mount 后 DOM 由 motion renderer 接管(含动画态),\n 若随重渲染重设 innerHTML 会摧毁动画状态,故用非响应式常量 "), createElementVNode("div", {
1548
+ ref_key: "container",
1549
+ ref: container,
1550
+ class: "ts-chart-surface",
1551
+ style: {
1552
+ "width": "100%",
1553
+ "height": "100%"
1554
+ },
1555
+ innerHTML: unref(initialMarkup)
1556
+ }, null, 8, _hoisted_1)], 4);
1557
+ };
1558
+ }
1559
+ });
1560
+ //#endregion
1463
1561
  //#region src/chart/src/index.vue
1464
1562
  const _sfc_main$5 = /*@__PURE__*/ defineComponent({
1465
1563
  name: "CxTanstackChartsChart",
@@ -1479,10 +1577,18 @@ const _sfc_main$5 = /*@__PURE__*/ defineComponent({
1479
1577
  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", {
1480
1578
  class: normalizeClass([unref(ns).b(), "cx-charts"]),
1481
1579
  "data-testid": "cx-tanstack-charts-chart"
1482
- }, [createCommentVNode(" definition 未闭合期间骨架占位:空壳帧的空 marks 只渲染不可见空 svg,无反馈等同未渲染 "), isStreaming.value ? (openBlock(), createBlock(_sfc_main$6, {
1580
+ }, [createCommentVNode(" definition 未闭合期间骨架占位:空壳帧的空 marks 只渲染不可见空 svg,无反馈等同未渲染 "), isStreaming.value ? (openBlock(), createBlock(_sfc_main$7, {
1483
1581
  key: 0,
1484
1582
  height: skeletonHeight.value
1485
- }, null, 8, ["height"])) : (openBlock(), createBlock(unref(Chart), mergeProps({ key: 1 }, unref(hostProps), {
1583
+ }, null, 8, ["height"])) : unref(spec).motion !== void 0 ? (openBlock(), createElementBlock(Fragment, { key: 1 }, [createCommentVNode(" motion 声明走自研挂载分支:库 vue adapter 无 motion renderer 注入口子 "), createVNode(_sfc_main$6, mergeProps({
1584
+ definition: definition.value,
1585
+ ariaLabel: unref(ariaLabel),
1586
+ "motion-options": unref(spec).motion
1587
+ }, unref(hostProps)), null, 16, [
1588
+ "definition",
1589
+ "ariaLabel",
1590
+ "motion-options"
1591
+ ])], 2112)) : (openBlock(), createBlock(unref(Chart), mergeProps({ key: 2 }, unref(hostProps), {
1486
1592
  definition: definition.value,
1487
1593
  "aria-label": unref(ariaLabel)
1488
1594
  }), null, 16, ["definition", "aria-label"]))], 2)], 2112);
@@ -550,4 +550,27 @@ export interface CxChartSpec {
550
550
  keyboard?: boolean;
551
551
  focusRing?: boolean;
552
552
  focus?: 'nearest' | 'nearest-x' | 'nearest-y' | 'group-x' | 'group-y';
553
+ /**
554
+ * 动效声明(可 JSON 化子集)。声明即走 motion 挂载分支:库 vue adapter 未暴露
555
+ * motion renderer 注入,cx 组件层自组装 renderer adapter。transition/delay 翻译为
556
+ * 库 definition.motion 的 chart 级默认时序;initial/resize 为 renderer 级选项。
557
+ * mark 级 per-datum motion 回调不可 JSON 化,不收。
558
+ */
559
+ motion?: {
560
+ transition?: {
561
+ type: 'tween';
562
+ duration?: number;
563
+ easing?: 'linear' | 'ease' | 'ease-in' | 'ease-out' | 'ease-in-out';
564
+ } | {
565
+ type: 'spring';
566
+ stiffness?: number;
567
+ damping?: number;
568
+ mass?: number;
569
+ restSpeed?: number;
570
+ restDelta?: number;
571
+ };
572
+ delay?: number;
573
+ initial?: boolean | 'always';
574
+ resize?: boolean;
575
+ };
553
576
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lionad/cx-comps-tanstack-charts",
3
- "version": "0.1.0-alpha.18",
3
+ "version": "0.1.0-alpha.20",
4
4
  "description": "cx material components wrapping TanStack Charts (@tanstack/charts/vue)",
5
5
  "keywords": [
6
6
  "charts",