@zerodep-dev/vue-tsx-pro-ui 0.3.2 → 0.4.0

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.
Files changed (133) hide show
  1. package/dist/charts/AreaChart.d.ts +1 -1
  2. package/dist/charts/AreaChart.js +6 -48
  3. package/dist/charts/AreaChart.js.map +1 -1
  4. package/dist/charts/BarChart.js +6 -49
  5. package/dist/charts/BarChart.js.map +1 -1
  6. package/dist/charts/ChordChart.d.ts +7 -2
  7. package/dist/charts/ChordChart.js +151 -60
  8. package/dist/charts/ChordChart.js.map +1 -1
  9. package/dist/charts/ComboChart.d.ts +2 -1
  10. package/dist/charts/ComboChart.js +17 -93
  11. package/dist/charts/ComboChart.js.map +1 -1
  12. package/dist/charts/DonutChart.d.ts +6 -1
  13. package/dist/charts/DonutChart.js +134 -51
  14. package/dist/charts/DonutChart.js.map +1 -1
  15. package/dist/charts/FunnelChart.js.map +1 -1
  16. package/dist/charts/GraphChart.d.ts +22 -10
  17. package/dist/charts/GraphChart.js +415 -175
  18. package/dist/charts/GraphChart.js.map +1 -1
  19. package/dist/charts/LineChart.d.ts +1 -29
  20. package/dist/charts/LineChart.js +6 -54
  21. package/dist/charts/LineChart.js.map +1 -1
  22. package/dist/charts/MapChart.d.ts +21 -48
  23. package/dist/charts/MapChart.js +426 -353
  24. package/dist/charts/MapChart.js.map +1 -1
  25. package/dist/charts/RadarChart.js.map +1 -1
  26. package/dist/charts/SankeyChart.d.ts +7 -2
  27. package/dist/charts/SankeyChart.js +164 -76
  28. package/dist/charts/SankeyChart.js.map +1 -1
  29. package/dist/charts/ScatterChart.d.ts +7 -1
  30. package/dist/charts/ScatterChart.js +45 -30
  31. package/dist/charts/ScatterChart.js.map +1 -1
  32. package/dist/charts/TimelineChart.d.ts +8 -1
  33. package/dist/charts/TimelineChart.js +141 -89
  34. package/dist/charts/TimelineChart.js.map +1 -1
  35. package/dist/charts/TreemapChart.d.ts +6 -0
  36. package/dist/charts/TreemapChart.js +174 -58
  37. package/dist/charts/TreemapChart.js.map +1 -1
  38. package/dist/charts/WaterfallChart.d.ts +6 -7
  39. package/dist/charts/WaterfallChart.js +46 -51
  40. package/dist/charts/WaterfallChart.js.map +1 -1
  41. package/dist/charts/XYChart.d.ts +44 -35
  42. package/dist/charts/XYChart.js +493 -281
  43. package/dist/charts/XYChart.js.map +1 -1
  44. package/dist/charts/axis-ticks.d.ts +1 -1
  45. package/dist/charts/axis-ticks.js +4 -5
  46. package/dist/charts/axis-ticks.js.map +1 -1
  47. package/dist/charts/flow.d.ts +4 -11
  48. package/dist/charts/flow.js +4 -4
  49. package/dist/charts/flow.js.map +1 -1
  50. package/dist/charts/frame.d.ts +6 -30
  51. package/dist/charts/frame.js +6 -99
  52. package/dist/charts/frame.js.map +1 -1
  53. package/dist/charts/geo/projection.d.ts +45 -0
  54. package/dist/charts/geo/projection.js +201 -0
  55. package/dist/charts/geo/projection.js.map +1 -0
  56. package/dist/charts/geo/topojson.d.ts +50 -0
  57. package/dist/charts/geo/topojson.js +119 -0
  58. package/dist/charts/geo/topojson.js.map +1 -0
  59. package/dist/charts/index.d.ts +21 -9
  60. package/dist/charts/index.js +19 -8
  61. package/dist/charts/label-layout.js.map +1 -1
  62. package/dist/charts/layout/chord.d.ts +44 -0
  63. package/dist/charts/layout/chord.js +45 -0
  64. package/dist/charts/layout/chord.js.map +1 -0
  65. package/dist/charts/layout/graph.d.ts +49 -0
  66. package/dist/charts/layout/graph.js +268 -0
  67. package/dist/charts/layout/graph.js.map +1 -0
  68. package/dist/charts/layout/sankey.d.ts +79 -0
  69. package/dist/charts/layout/sankey.js +200 -0
  70. package/dist/charts/layout/sankey.js.map +1 -0
  71. package/dist/charts/layout/treemap.d.ts +46 -0
  72. package/dist/charts/layout/treemap.js +152 -0
  73. package/dist/charts/layout/treemap.js.map +1 -0
  74. package/dist/charts/marks.d.ts +79 -0
  75. package/dist/charts/marks.js +231 -0
  76. package/dist/charts/marks.js.map +1 -0
  77. package/dist/charts/plot.d.ts +107 -0
  78. package/dist/charts/plot.js +231 -0
  79. package/dist/charts/plot.js.map +1 -0
  80. package/dist/charts/scale.d.ts +29 -0
  81. package/dist/charts/scale.js +244 -0
  82. package/dist/charts/scale.js.map +1 -0
  83. package/dist/charts/shape.d.ts +46 -0
  84. package/dist/charts/shape.js +131 -0
  85. package/dist/charts/shape.js.map +1 -0
  86. package/dist/charts/stack.d.ts +17 -0
  87. package/dist/charts/stack.js +35 -0
  88. package/dist/charts/stack.js.map +1 -0
  89. package/dist/charts/text-size.d.ts +12 -0
  90. package/dist/charts/text-size.js +32 -0
  91. package/dist/charts/text-size.js.map +1 -0
  92. package/dist/display/VirtualList.d.ts +1 -2
  93. package/dist/display/VirtualList.js +11 -21
  94. package/dist/display/VirtualList.js.map +1 -1
  95. package/dist/input/Combobox.d.ts +1 -1
  96. package/dist/input/Combobox.js.map +1 -1
  97. package/dist/input/ListPopover.d.ts +1 -1
  98. package/dist/input/ListPopover.js.map +1 -1
  99. package/dist/input/Listbox.d.ts +1 -2
  100. package/dist/input/Listbox.js +7 -19
  101. package/dist/input/Listbox.js.map +1 -1
  102. package/dist/input/Select.d.ts +1 -1
  103. package/dist/input/Select.js.map +1 -1
  104. package/dist/maps/china-provinces.js +35680 -0
  105. package/dist/maps/china-provinces.js.map +1 -0
  106. package/dist/maps/china.d.ts +13 -0
  107. package/dist/maps/china.js +8 -0
  108. package/dist/maps/china.js.map +1 -0
  109. package/dist/maps/index.d.ts +9 -0
  110. package/dist/maps/index.js +3 -0
  111. package/dist/maps/world-countries.js +22273 -0
  112. package/dist/maps/world-countries.js.map +1 -0
  113. package/dist/maps/world.d.ts +13 -0
  114. package/dist/maps/world.js +8 -0
  115. package/dist/maps/world.js.map +1 -0
  116. package/dist/overlay/CommandPalette.d.ts +1 -1
  117. package/dist/overlay/CommandPalette.js.map +1 -1
  118. package/dist/overlay/detached.js +14 -1
  119. package/dist/overlay/detached.js.map +1 -1
  120. package/dist/patterns/QueryTable.js +0 -2
  121. package/dist/patterns/QueryTable.js.map +1 -1
  122. package/dist/table/DataTable.js +1 -1
  123. package/dist/table/DataTable.js.map +1 -1
  124. package/dist/table/DataTableView.d.ts +0 -3
  125. package/dist/table/DataTableView.js +32 -26
  126. package/dist/table/DataTableView.js.map +1 -1
  127. package/dist/table/table-styles.d.ts +1 -1
  128. package/dist/table/table-styles.js +0 -13
  129. package/dist/table/table-styles.js.map +1 -1
  130. package/package.json +7 -8
  131. package/dist/charts/single.d.ts +0 -41
  132. package/dist/charts/single.js +0 -66
  133. package/dist/charts/single.js.map +0 -1
@@ -1,34 +1,11 @@
1
- import { colors } from "../config/theme/tokens.js";
1
+ import { chartBars, chartDots, chartLines } from "./marks.js";
2
2
  import { seriesNumber } from "./series.js";
3
3
  import { XYChart } from "./XYChart.js";
4
- import { CURVES } from "./LineChart.js";
5
4
  import { computed, createVNode } from "vue";
6
5
  import { _component, bindable, mergeJsxProps, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
7
- import { GroupedBar, Line, Scatter, StackedBar } from "@unovis/ts";
8
6
  //#region src/charts/ComboChart.tsx
9
- /**
10
- * 组合图:同一组类目上既有柱又有线(金额与比率、数量与单价),单位不同的系列画在右侧的第二数值轴上。
11
- *
12
- * <ComboChart
13
- * aria-label="月度销售与完成率"
14
- * data={months}
15
- * index="month"
16
- * series={[
17
- * { key: 'planned', label: '计划销售' },
18
- * { key: 'actual', label: '实际销售' },
19
- * { key: 'rate', label: '完成率', type: 'line', axis: 'secondary' },
20
- * ]}
21
- * unit="万元"
22
- * secondaryFormat={{ style: 'percent' }}
23
- * />
24
- *
25
- * 柱子并排(stacked 时叠起),线可以带数据点。第二数值轴的刻度与主轴对齐(同一组网格线),
26
- * 它的单位写在右上方,提示框、数据表里按各自的格式与单位显示。其余(图例、目标线、标注、区间、下钻)同 XYChart。
27
- */
28
7
  /** 组合图的一个系列 */
29
8
  /** 未声明的属性落到外层 div(role="figure")上;aria-label 是图表的名称 */
30
- /** 数据点的直径(px) */
31
- var DOT_SIZE = 8;
32
9
  /** 组合图:柱 + 线,可以有第二数值轴 */
33
10
  var ComboChart = /*#__PURE__*/ _component((_props, _ctx) => {
34
11
  const _cache = [];
@@ -44,11 +21,12 @@ var ComboChart = /*#__PURE__*/ _component((_props, _ctx) => {
44
21
  const isLine = (key) => series.value.find((item) => item.key === key)?.type === "line";
45
22
  const secondaryKeys = computed(() => series.value.filter((item) => item.axis === "secondary").map((item) => item.key));
46
23
  const kind = computed(() => ({
24
+ band: true,
47
25
  skeleton: "bars",
48
26
  stacked: stacked.value,
27
+ stackSeries: (item) => !isLine(item.key),
49
28
  crosshairDots: true,
50
29
  crosshairSeries: (item) => isLine(item.key),
51
- itemSelector: stacked.value ? StackedBar.selectors.bar : GroupedBar.selectors.bar,
52
30
  secondary: secondaryKeys.value.length > 0 ? {
53
31
  keys: secondaryKeys.value,
54
32
  format: secondaryFormat.value,
@@ -61,74 +39,20 @@ var ComboChart = /*#__PURE__*/ _component((_props, _ctx) => {
61
39
  const barValues = values(bars);
62
40
  return [...stacked.value ? [barValues.reduce((sum, value) => sum + Math.max(value, 0), 0), barValues.reduce((sum, value) => sum + Math.min(value, 0), 0)] : barValues, ...values(lines)];
63
41
  }),
64
- components: ({ series: shown, x, y, instance, events }) => {
65
- const bars = shown.filter((item) => !isLine(item.key));
66
- const lines = shown.filter((item) => isLine(item.key));
67
- const components = [];
68
- if (bars.length > 0) {
69
- const common = {
70
- x,
71
- y: bars.map(y),
72
- color: (_row, position) => bars[position]?.color,
73
- roundedCorners: radius.value,
74
- events
75
- };
76
- if (stacked.value) {
77
- const stack = instance("stacked", () => new StackedBar({
78
- x,
79
- y: []
80
- }));
81
- stack.setConfig({
82
- ...common,
83
- barPadding: .4,
84
- barMaxWidth: 48
85
- });
86
- components.push(stack);
87
- } else {
88
- const group = instance("grouped", () => new GroupedBar({
89
- x,
90
- y: []
91
- }));
92
- group.setConfig({
93
- ...common,
94
- groupPadding: .3,
95
- barPadding: .1,
96
- groupMaxWidth: 96,
97
- barMinHeight: 0
98
- });
99
- components.push(group);
100
- }
101
- }
102
- if (lines.length > 0) {
103
- const line = instance("line", () => new Line({
104
- x,
105
- y: []
106
- }));
107
- line.setConfig({
108
- x,
109
- y: lines.map(y),
110
- color: (_rows, position) => lines[position]?.color,
111
- curveType: CURVES[curve.value],
112
- lineWidth: 2
113
- });
114
- components.push(line);
115
- if (dots.value !== "none") for (const item of lines) {
116
- const points = instance(`dots:${item.key}`, () => new Scatter({
117
- x,
118
- y: () => 0
119
- }));
120
- points.setConfig({
121
- x,
122
- y: y(item),
123
- size: DOT_SIZE,
124
- color: dots.value === "hollow" ? colors.surface : item.color,
125
- strokeColor: item.color,
126
- strokeWidth: dots.value === "hollow" ? 2 : 0
127
- });
128
- components.push(points);
129
- }
130
- }
131
- return components;
42
+ render: (context) => {
43
+ const bars = context.series.filter((item) => !isLine(item.key));
44
+ const lines = context.series.filter((item) => isLine(item.key));
45
+ return [
46
+ chartBars(context, bars, {
47
+ stacked: stacked.value,
48
+ radius: radius.value
49
+ }),
50
+ chartLines(context, lines, {
51
+ curve: curve.value,
52
+ width: 2
53
+ }),
54
+ dots.value !== "none" && chartDots(context, lines, dots.value)
55
+ ];
132
56
  }
133
57
  }));
134
58
  return () => createVNode(XYChart, mergeJsxProps(rest, {
@@ -1 +1 @@
1
- {"version":3,"file":"ComboChart.js","names":["_vtp_readonlyProp","_vtp_bindable","_vtp_restProps","_vue_computed","_createVNode","_mergeProps"],"sources":["../../src/charts/ComboChart.tsx"],"sourcesContent":["/**\n * 组合图:同一组类目上既有柱又有线(金额与比率、数量与单价),单位不同的系列画在右侧的第二数值轴上。\n *\n * <ComboChart\n * aria-label=\"月度销售与完成率\"\n * data={months}\n * index=\"month\"\n * series={[\n * { key: 'planned', label: '计划销售' },\n * { key: 'actual', label: '实际销售' },\n * { key: 'rate', label: '完成率', type: 'line', axis: 'secondary' },\n * ]}\n * unit=\"万元\"\n * secondaryFormat={{ style: 'percent' }}\n * />\n *\n * 柱子并排(stacked 时叠起),线可以带数据点。第二数值轴的刻度与主轴对齐(同一组网格线),\n * 它的单位写在右上方,提示框、数据表里按各自的格式与单位显示。其余(图例、目标线、标注、区间、下钻)同 XYChart。\n */\nimport { GroupedBar, Line, Scatter, StackedBar, type XYComponentCore } from '@unovis/ts';\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport type { NumericFormat } from '../config/lang/format';\nimport { colors } from '../config/theme/tokens';\nimport { CURVES, type LineCurves, type LineDots } from './LineChart';\nimport type { ChartSeries } from './series';\nimport { seriesNumber } from './series';\nimport { XYChart, type XYChartKind, type XYChartProps } from './XYChart';\n\n/** 组合图的一个系列 */\nexport interface ComboSeries<T> extends ChartSeries<T> {\n /** 画法:bar 柱(默认)、line 线 */\n type?: 'bar' | 'line';\n /** 数值轴:primary 左侧的主轴(默认),secondary 右侧的第二数值轴(单位不同的量) */\n axis?: 'primary' | 'secondary';\n}\n\n/** 未声明的属性落到外层 div(role=\"figure\")上;aria-label 是图表的名称 */\nexport interface ComboChartProps<T extends object> extends Omit<XYChartProps<T>, 'series'> {\n /** 系列:柱或线,画在主轴或第二数值轴上 */\n series: readonly ComboSeries<T>[];\n /** 第二数值轴的格式(比率写 `{ style: 'percent' }`) */\n secondaryFormat?: NumericFormat;\n /** 第二数值轴的单位:显示在右上方 */\n secondaryUnit?: string;\n /** 柱子堆叠:同一类目的柱叠成一根;默认并排 */\n stacked?: boolean;\n /** 线的形状,默认 linear */\n curve?: keyof LineCurves;\n /** 线上的数据点,默认 filled */\n dots?: keyof LineDots;\n /** 柱子末端的圆角(px),默认 4 */\n radius?: number;\n}\n\n/** 数据点的直径(px) */\nconst DOT_SIZE = 8;\n\n/** 组合图:柱 + 线,可以有第二数值轴 */\nexport const ComboChart = _component(\n <T extends object>({\n series,\n secondaryFormat,\n secondaryUnit,\n stacked = false,\n curve = 'linear',\n dots = 'filled',\n radius = 4,\n _hiddenSeries: hiddenSeries = _bindable<string[]>([]),\n ...rest\n }: ComboChartProps<T>) => {\n const isLine = (key: string) => series.find((item) => item.key === key)?.type === 'line';\n const secondaryKeys = _derived(\n series.filter((item) => item.axis === 'secondary').map((item) => item.key as string),\n );\n\n const kind = _derived<XYChartKind<T>>({\n skeleton: 'bars',\n stacked,\n crosshairDots: true,\n // 十字线只在线上画圆点,柱子不画\n crosshairSeries: (item) => isLine(item.key),\n itemSelector: stacked ? StackedBar.selectors.bar : GroupedBar.selectors.bar,\n secondary:\n secondaryKeys.length > 0\n ? { keys: secondaryKeys, format: secondaryFormat, unit: secondaryUnit }\n : undefined,\n // 主轴的范围:堆叠的柱按正负各自的总和,线按各自的值\n domainValues: (rows, shown) =>\n rows.flatMap((row) => {\n const bars = shown.filter((item) => !isLine(item.key));\n const lines = shown.filter((item) => isLine(item.key));\n const values = (list: typeof shown) =>\n list.map((item) => seriesNumber(row, item.key) ?? 0);\n const barValues = values(bars);\n return [\n ...(stacked\n ? [\n barValues.reduce((sum, value) => sum + Math.max(value, 0), 0),\n barValues.reduce((sum, value) => sum + Math.min(value, 0), 0),\n ]\n : barValues),\n ...values(lines),\n ];\n }),\n components: ({ series: shown, x, y, instance, events }) => {\n const bars = shown.filter((item) => !isLine(item.key));\n const lines = shown.filter((item) => isLine(item.key));\n const components: XYComponentCore<T>[] = [];\n if (bars.length > 0) {\n const common = {\n x,\n y: bars.map(y),\n color: (_row: T, position: number) => bars[position]?.color,\n roundedCorners: radius,\n events,\n };\n if (stacked) {\n const stack = instance('stacked', () => new StackedBar<T>({ x, y: [] }));\n stack.setConfig({ ...common, barPadding: 0.4, barMaxWidth: 48 });\n components.push(stack);\n } else {\n const group = instance('grouped', () => new GroupedBar<T>({ x, y: [] }));\n group.setConfig({\n ...common,\n groupPadding: 0.3,\n barPadding: 0.1,\n groupMaxWidth: 96,\n barMinHeight: 0,\n });\n components.push(group);\n }\n }\n if (lines.length > 0) {\n const line = instance('line', () => new Line<T>({ x, y: [] }));\n line.setConfig({\n x,\n y: lines.map(y),\n color: (_rows: T[], position: number) => lines[position]?.color,\n curveType: CURVES[curve],\n lineWidth: 2,\n });\n components.push(line);\n if (dots !== 'none')\n for (const item of lines) {\n const points = instance(`dots:${item.key}`, () => new Scatter<T>({ x, y: () => 0 }));\n points.setConfig({\n x,\n y: y(item),\n size: DOT_SIZE,\n color: dots === 'hollow' ? colors.surface : item.color,\n strokeColor: item.color,\n strokeWidth: dots === 'hollow' ? 2 : 0,\n });\n components.push(points);\n }\n }\n return components;\n },\n });\n return () => <XYChart {...rest} series={series} _hiddenSeries={hiddenSeries} kind={kind} />;\n },\n);\n"],"mappings":"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"}
1
+ {"version":3,"file":"ComboChart.js","names":["_vtp_readonlyProp","_vtp_bindable","_vtp_restProps","_vue_computed","_createVNode","_mergeProps"],"sources":["../../src/charts/ComboChart.tsx"],"sourcesContent":["/**\n * 组合图:同一组类目上既有柱又有线(金额与比率、数量与单价),单位不同的系列画在右侧的第二数值轴上。\n *\n * <ComboChart\n * aria-label=\"月度销售与完成率\"\n * data={months}\n * index=\"month\"\n * series={[\n * { key: 'planned', label: '计划销售' },\n * { key: 'actual', label: '实际销售' },\n * { key: 'rate', label: '完成率', type: 'line', axis: 'secondary' },\n * ]}\n * unit=\"万元\"\n * secondaryFormat={{ style: 'percent' }}\n * />\n *\n * 柱子并排(stacked 时叠起),线可以带数据点。第二数值轴的刻度与主轴对齐(同一组网格线),\n * 它的单位写在右上方,提示框、数据表里按各自的格式与单位显示。其余(图例、目标线、标注、区间、下钻)同 XYChart。\n */\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport type { NumericFormat } from '../config/lang/format';\nimport type { LineDots } from './LineChart';\nimport { chartBars, chartDots, chartLines } from './marks';\nimport type { ChartSeries } from './series';\nimport { seriesNumber } from './series';\nimport type { LineCurves } from './shape';\nimport { XYChart, type XYChartKind, type XYChartProps } from './XYChart';\n\n/** 组合图的一个系列 */\nexport interface ComboSeries<T> extends ChartSeries<T> {\n /** 画法:bar 柱(默认)、line 线 */\n type?: 'bar' | 'line';\n /** 数值轴:primary 左侧的主轴(默认),secondary 右侧的第二数值轴(单位不同的量) */\n axis?: 'primary' | 'secondary';\n}\n\n/** 未声明的属性落到外层 div(role=\"figure\")上;aria-label 是图表的名称 */\nexport interface ComboChartProps<T extends object> extends Omit<XYChartProps<T>, 'series'> {\n /** 系列:柱或线,画在主轴或第二数值轴上 */\n series: readonly ComboSeries<T>[];\n /** 第二数值轴的格式(比率写 `{ style: 'percent' }`) */\n secondaryFormat?: NumericFormat;\n /** 第二数值轴的单位:显示在右上方 */\n secondaryUnit?: string;\n /** 柱子堆叠:同一类目的柱叠成一根;默认并排 */\n stacked?: boolean;\n /** 线的形状,默认 linear */\n curve?: keyof LineCurves;\n /** 线上的数据点,默认 filled */\n dots?: keyof LineDots;\n /** 柱子末端的圆角(px),默认 4 */\n radius?: number;\n}\n\n/** 组合图:柱 + 线,可以有第二数值轴 */\nexport const ComboChart = _component(\n <T extends object>({\n series,\n secondaryFormat,\n secondaryUnit,\n stacked = false,\n curve = 'linear',\n dots = 'filled',\n radius = 4,\n _hiddenSeries: hiddenSeries = _bindable<string[]>([]),\n ...rest\n }: ComboChartProps<T>) => {\n const isLine = (key: string) => series.find((item) => item.key === key)?.type === 'line';\n const secondaryKeys = _derived(\n series.filter((item) => item.axis === 'secondary').map((item) => item.key as string),\n );\n\n const kind = _derived<XYChartKind<T>>({\n band: true,\n skeleton: 'bars',\n stacked,\n // 只有柱叠起,线按各自的值\n stackSeries: (item) => !isLine(item.key),\n crosshairDots: true,\n // 十字线只在线上画圆点,柱子不画\n crosshairSeries: (item) => isLine(item.key),\n secondary:\n secondaryKeys.length > 0\n ? { keys: secondaryKeys, format: secondaryFormat, unit: secondaryUnit }\n : undefined,\n // 主轴的范围:堆叠的柱按正负各自的总和,线按各自的值\n domainValues: (rows, shown) =>\n rows.flatMap((row) => {\n const bars = shown.filter((item) => !isLine(item.key));\n const lines = shown.filter((item) => isLine(item.key));\n const values = (list: typeof shown) =>\n list.map((item) => seriesNumber(row, item.key) ?? 0);\n const barValues = values(bars);\n return [\n ...(stacked\n ? [\n barValues.reduce((sum, value) => sum + Math.max(value, 0), 0),\n barValues.reduce((sum, value) => sum + Math.min(value, 0), 0),\n ]\n : barValues),\n ...values(lines),\n ];\n }),\n render: (context) => {\n const bars = context.series.filter((item) => !isLine(item.key));\n const lines = context.series.filter((item) => isLine(item.key));\n return [\n chartBars(context, bars, { stacked, radius }),\n chartLines(context, lines, { curve, width: 2 }),\n dots !== 'none' && chartDots(context, lines, dots),\n ];\n },\n });\n return () => <XYChart {...rest} series={series} _hiddenSeries={hiddenSeries} kind={kind} />;\n },\n);\n"],"mappings":";;;;;;;;;AAuDA,IAAa,aAAa,0BACxB,QAAA,SAU0B;CAAA,MAAA,SAAA,CAAA;CAAA,MAAA,SAAAA,aAAA,QAAA,QAAA;CAAA,MAAA,kBAAAA,aAAA,QAAA,iBAAA;CAAA,MAAA,gBAAAA,aAAA,QAAA,eAAA;CAAA,MAAA,UAAAA,aAAA,QAAA,WANd,KAAK;CAAA,MAAA,QAAAA,aAAA,QAAA,SACP,QAAQ;CAAA,MAAA,OAAAA,aAAA,QAAA,QACT,QAAQ;CAAA,MAAA,SAAAA,aAAA,QAAA,UACN,CAAC;CAAA,MAAA,eAAAC,SAAA,QAAA,uBACwC,CAAA,GAAE,KAAA,IAAA;CAAA,MACjD,OAAIC,UAAA,QAAA,MAAA,IAAA;CAEP,MAAM,UAAU,QAAgB,OAAA,MAAO,MAAM,SAAS,KAAK,QAAQ,GAAG,CAAC,EAAE,SAAS;CAClF,MAAM,gBAAaC,eACjB,OAAA,MAAO,QAAQ,SAAS,KAAK,SAAS,WAAW,CAAC,CAAC,KAAK,SAAS,KAAK,GAAa,CAAC;CAGtF,MAAM,OAAIA,gBAA4B;EACpC,MAAM;EACN,UAAU;EACV,SAAO,QAAA;EAEP,cAAc,SAAS,CAAC,OAAO,KAAK,GAAG;EACvC,eAAe;EAEf,kBAAkB,SAAS,OAAO,KAAK,GAAG;EAC1C,WACE,cAAA,MAAc,SAAS,IACnB;GAAE,MAAI,cAAA;GAAiB,QAAM,gBAAA;GAAmB,MAAI,cAAA;EAAgB,IACpE,KAAA;EAEN,eAAe,MAAM,UACnB,KAAK,SAAS,QAAQ;GACpB,MAAM,OAAO,MAAM,QAAQ,SAAS,CAAC,OAAO,KAAK,GAAG,CAAC;GACrD,MAAM,QAAQ,MAAM,QAAQ,SAAS,OAAO,KAAK,GAAG,CAAC;GACrD,MAAM,UAAU,SACd,KAAK,KAAK,SAAS,aAAa,KAAK,KAAK,GAAG,KAAK,CAAC;GACrD,MAAM,YAAY,OAAO,IAAI;GAC7B,OAAO,CACL,GAAI,QAAA,QACA,CACE,UAAU,QAAQ,KAAK,UAAU,MAAM,KAAK,IAAI,OAAO,CAAC,GAAG,CAAC,GAC5D,UAAU,QAAQ,KAAK,UAAU,MAAM,KAAK,IAAI,OAAO,CAAC,GAAG,CAAC,CAAC,IAE/D,WACJ,GAAG,OAAO,KAAK,CAAC;EAEpB,CAAC;EACH,SAAS,YAAY;GACnB,MAAM,OAAO,QAAQ,OAAO,QAAQ,SAAS,CAAC,OAAO,KAAK,GAAG,CAAC;GAC9D,MAAM,QAAQ,QAAQ,OAAO,QAAQ,SAAS,OAAO,KAAK,GAAG,CAAC;GAC9D,OAAO;IACL,UAAU,SAAS,MAAM;KAAE,SAAO,QAAA;KAAE,QAAM,OAAA;IAAC,CAAC;IAC5C,WAAW,SAAS,OAAO;KAAE,OAAK,MAAA;KAAE,OAAO;IAAE,CAAC;IAC9C,KAAA,UAAS,UAAU,UAAU,SAAS,OAAK,KAAA,KAAM;GAAC;EAEtD;CACF,EAAC;CACD,aAAOC,YAAA,SAAAC,cAAmB,MAAI;EAAA,UAAA,OAAA;EAAA,iBAAA,aAAA;EAAA,0BAAA,OAAA,OAAA,OAAA,MAAA,WAAA,aAAA,QAAA;EAAA,QAAA,KAAA;CAAA,CAAA,GAAA,IAAA;AAChC,GAAC;CAAA,MAAA;CAAA,OAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;CAAA;AAAA,CACH"}
@@ -57,8 +57,13 @@ export interface DonutChartProps<T extends object> extends PartProps<'div', 'rol
57
57
  */
58
58
  onItemClick?: (row: T, index: number) => void;
59
59
  }
60
- /** DonutChart 的样式:skeleton */
60
+ /** DonutChart 的样式:skeleton、扇区、中间的文字 */
61
61
  export declare const donutChartStyles: Readonly<{
62
+ background: css.CompiledStyles;
63
+ centerLabel: css.CompiledStyles;
64
+ centerValue: css.CompiledStyles;
65
+ ring: css.CompiledStyles;
66
+ segment: css.CompiledStyles;
62
67
  skeleton: css.CompiledStyles;
63
68
  }>;
64
69
  /** 环形图 */
@@ -1,33 +1,55 @@
1
+ import { svgNumber } from "../utils/data/number.js";
1
2
  import { toNumber } from "../utils/data/numeric.js";
3
+ import { t } from "../config/theme/tokens.js";
2
4
  import { useLang } from "../config/context.js";
3
5
  import { useFormat } from "../config/lang/format.js";
4
6
  import { Skeleton } from "../feedback/Skeleton.js";
7
+ import { ChartTooltipBox, chartSvgStyles, pointerIn, usePlotArea } from "./plot.js";
8
+ import { arcPath } from "./shape.js";
5
9
  import { ChartFrame, chartTooltip, useChartValueText } from "./frame.js";
6
10
  import { resolveColor, seriesValue } from "./series.js";
7
- import { useSingleChart } from "./single.js";
8
11
  import { computed, createVNode, shallowRef } from "vue";
9
12
  import { _component, bindable, mergeJsxProps, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
10
13
  import { css } from "@zerodep-dev/vue-tsx-pro-tsx/styles";
11
- import { Donut } from "@unovis/ts";
12
14
  //#region src/charts/DonutChart.tsx
13
- /**
14
- * 环形图(arcWidth 为 0 时是饼图):每一行一个扇区,中间写合计;悬停扇区出提示框(数值与占比),点图例隐藏一项。
15
- *
16
- * <DonutChart aria-label="访问来源" data={channels} index="name" value="visits" />
17
- * <DonutChart data={budget} index="dept" value="amount" unit="万元" valueFormat={{ maximumFractionDigits: 0 }} />
18
- *
19
- * 颜色默认按顺序取图表色板(itemColor 可以按行给);值可以是 number、bigint、Decimal,提示框与数据表按原值显示。
20
- * 图例、单位、加载与空状态、给读屏的数据表(类目、数值、占比)同其他图表(见 ChartFrame)。
21
- */
22
15
  /** 未声明的属性落到外层 div(role="figure")上,aria-label 是图表的名称 */
23
- /** DonutChart 的样式:skeleton */
24
- var donutChartStyles = css.create({ skeleton: {
25
- display: "block",
26
- height: "100%",
27
- aspectRatio: "1",
28
- marginInline: "auto",
29
- borderRadius: "50%"
30
- } });
16
+ /** DonutChart 的样式:skeleton、扇区、中间的文字 */
17
+ var donutChartStyles = css.create({
18
+ ring: {
19
+ transformBox: "fill-box",
20
+ transformOrigin: "center",
21
+ opacity: {
22
+ default: 1,
23
+ "@starting-style": 0
24
+ },
25
+ scale: {
26
+ default: "1",
27
+ "@starting-style": "0.9"
28
+ },
29
+ transition: `opacity ${t.durations.lg} ${t.easings.standard}, scale ${t.durations.lg} ${t.easings.standard}`
30
+ },
31
+ background: { fill: "_fill" },
32
+ segment: {
33
+ stroke: "_surface",
34
+ strokeWidth: 1
35
+ },
36
+ centerValue: {
37
+ fill: "_text",
38
+ fontSize: "_xl",
39
+ fontWeight: "_semibold"
40
+ },
41
+ centerLabel: {
42
+ fill: "_textMuted",
43
+ fontSize: "_xs"
44
+ },
45
+ skeleton: {
46
+ display: "block",
47
+ height: "100%",
48
+ aspectRatio: "1",
49
+ marginInline: "auto",
50
+ borderRadius: "50%"
51
+ }
52
+ });
31
53
  /** 扇区的数据:一行加上它的名字与颜色 */
32
54
  /** 环形图 */
33
55
  var DonutChart = /*#__PURE__*/ _component((_props, _ctx) => {
@@ -72,40 +94,96 @@ var DonutChart = /*#__PURE__*/ _component((_props, _ctx) => {
72
94
  function toggle(name) {
73
95
  hiddenItems.value = hiddenItems.value.includes(name) ? hiddenItems.value.filter((item) => item !== name) : [...hiddenItems.value, name];
74
96
  }
75
- let plot = shallowRef(null);
76
- const chart = useSingleChart(() => ({
77
- element: plot.value,
78
- ready: !loading.value && data.value.length > 0,
79
- tooltip: tooltip.value,
80
- build: (instance) => {
81
- const donut = instance("donut", () => new Donut({ value: () => 0 }));
82
- donut.setConfig({
83
- value: (slice) => slice.value === void 0 ? 0 : toNumber(slice.value),
84
- id: (slice) => slice.name,
85
- color: (slice) => slice.color,
86
- arcWidth: arcWidth.value,
87
- padAngle: arcWidth.value > 0 ? .01 : 0,
88
- cornerRadius: arcWidth.value > 0 ? 3 : 0,
89
- centralLabel: centerLabel.value === false ? void 0 : centerLabel.value ?? format.number(total.value, valueFormat.value),
90
- centralSubLabel: centerLabel.value === false ? void 0 : centerSubLabel.value ?? lang.chartTotal,
91
- showBackground: true,
92
- events: { [Donut.selectors.segment]: { click: (arc) => onItemClick.value?.(arc.data.row, data.value.indexOf(arc.data.row)) } }
93
- });
97
+ const area = usePlotArea();
98
+ let hovered = shallowRef(null);
99
+ /** 扇区的位置:从 12 点钟方向顺时针,按数值分角度;扇区之间留一点缝 */
100
+ const arcs = computed(() => {
101
+ const pad = arcWidth.value > 0 && visible.value.length > 1 ? .012 : 0;
102
+ let angle = 0;
103
+ return visible.value.map((slice) => {
104
+ const amount = slice.value === void 0 ? 0 : Math.max(toNumber(slice.value), 0);
105
+ const sweep = total.value > 0 ? amount / total.value * Math.PI * 2 : 0;
106
+ const start = angle;
107
+ angle += sweep;
94
108
  return {
95
- component: donut,
96
- data: [...visible.value],
97
- triggers: { [Donut.selectors.segment]: (arc) => chart.tooltip(chartTooltip(arc.data.name, [{
98
- label: lang.chartValue,
99
- color: arc.data.color,
100
- value: valueText(arc.data.value)
101
- }, {
102
- label: lang.chartShare,
103
- color: "transparent",
104
- value: shareText(arc.data.value)
105
- }])) }
109
+ slice,
110
+ start: start + pad / 2,
111
+ end: Math.max(start + pad / 2, angle - pad / 2)
106
112
  };
113
+ });
114
+ });
115
+ function onPointermove(event) {
116
+ const target = event.target.closest?.("[data-index]");
117
+ if (!target) {
118
+ hovered.value = null;
119
+ return;
107
120
  }
108
- }));
121
+ const { x, y } = pointerIn(event, event.currentTarget);
122
+ hovered.value = {
123
+ index: Number(target.getAttribute("data-index")),
124
+ x,
125
+ y
126
+ };
127
+ }
128
+ function onClick(event) {
129
+ const target = event.target.closest?.("[data-index]");
130
+ const slice = target && arcs.value[Number(target.getAttribute("data-index"))]?.slice;
131
+ if (slice) onItemClick.value?.(slice.row, data.value.indexOf(slice.row));
132
+ }
133
+ function renderPlot() {
134
+ const { width, height } = area;
135
+ const outer = Math.max(0, Math.min(width, height) / 2 - 8);
136
+ const inner = arcWidth.value > 0 ? Math.max(0, outer - arcWidth.value) : 0;
137
+ const center = centerLabel.value === false || inner < 24 ? void 0 : centerLabel.value ?? format.number(total.value, valueFormat.value);
138
+ return createVNode("svg", mergeJsxProps({
139
+ "width": width,
140
+ "height": height
141
+ }, css.attrs(chartSvgStyles.svg), {
142
+ "onPointermove": onPointermove,
143
+ "onPointerleave": _cache[0] || (_cache[0] = () => hovered.value = null),
144
+ "onClick": onClick
145
+ }), [createVNode("g", { "transform": `translate(${svgNumber(width / 2)},${svgNumber(height / 2)})` }, [createVNode("g", css.attrs(donutChartStyles.ring), [inner > 0 && createVNode("path", mergeJsxProps({ "d": arcPath({
146
+ inner,
147
+ outer,
148
+ start: 0,
149
+ end: Math.PI * 2
150
+ }) }, css.attrs(donutChartStyles.background)), null), arcs.value.map((arc, index) => createVNode("path", mergeJsxProps({
151
+ "key": arc.slice.name,
152
+ "data-part": "segment",
153
+ "data-index": index,
154
+ "d": arcPath({
155
+ inner,
156
+ outer: hovered.value?.index === index ? outer + 6 : outer,
157
+ start: arc.start,
158
+ end: arc.end,
159
+ corner: arcWidth.value > 0 ? 3 : 0
160
+ })
161
+ }, css.attrs(donutChartStyles.segment, chartSvgStyles.fill(arc.slice.color), chartSvgStyles.dimmable, hovered.value !== null && hovered.value.index !== index && chartSvgStyles.dimmed)), null))]), center !== void 0 && createVNode("g", mergeJsxProps({ "data-part": "center-label" }, css.attrs(chartSvgStyles.noPointer)), [createVNode("text", mergeJsxProps({
162
+ "y": -2,
163
+ "text-anchor": "middle"
164
+ }, css.attrs(donutChartStyles.centerValue)), [center]), createVNode("text", mergeJsxProps({
165
+ "y": 18,
166
+ "text-anchor": "middle"
167
+ }, css.attrs(donutChartStyles.centerLabel)), [centerSubLabel.value ?? lang.chartTotal])])])]);
168
+ }
169
+ const tip = () => {
170
+ const current = hovered.value;
171
+ const slice = current && arcs.value[current.index]?.slice;
172
+ if (!tooltip.value || !current || !slice) return null;
173
+ return {
174
+ x: current.x,
175
+ y: current.y,
176
+ content: chartTooltip(slice.name, [{
177
+ label: lang.chartValue,
178
+ color: slice.color,
179
+ value: valueText(slice.value)
180
+ }, {
181
+ label: lang.chartShare,
182
+ color: "transparent",
183
+ value: shareText(slice.value)
184
+ }])
185
+ };
186
+ };
109
187
  return () => createVNode(ChartFrame, mergeJsxProps(rest, {
110
188
  "unit": unit.value,
111
189
  "height": height.value,
@@ -132,7 +210,12 @@ var DonutChart = /*#__PURE__*/ _component((_props, _ctx) => {
132
210
  cells: [slice.value === void 0 ? lang.emptyValue : format.number(slice.value, valueFormat.value), shareText(slice.value)]
133
211
  }))
134
212
  },
135
- "plotRef": _cache[0] || (_cache[0] = (element) => plot.value = element)
213
+ "plotRef": area.ref,
214
+ "plot": area.ready ? [renderPlot(), createVNode(ChartTooltipBox, {
215
+ "key": "tooltip",
216
+ "tip": tip(),
217
+ "width": area.width
218
+ }, null)] : null
136
219
  }), null);
137
220
  }, {
138
221
  name: "DonutChart",
@@ -1 +1 @@
1
- {"version":3,"file":"DonutChart.js","names":["_vtp_readonlyProp","_vtp_bindable","_vtp_restProps","_vue_computed","_vue_shallowRef","_createVNode","_mergeProps"],"sources":["../../src/charts/DonutChart.tsx"],"sourcesContent":["/**\n * 环形图(arcWidth 为 0 时是饼图):每一行一个扇区,中间写合计;悬停扇区出提示框(数值与占比),点图例隐藏一项。\n *\n * <DonutChart aria-label=\"访问来源\" data={channels} index=\"name\" value=\"visits\" />\n * <DonutChart data={budget} index=\"dept\" value=\"amount\" unit=\"万元\" valueFormat={{ maximumFractionDigits: 0 }} />\n *\n * 颜色默认按顺序取图表色板(itemColor 可以按行给);值可以是 number、bigint、Decimal,提示框与数据表按原值显示。\n * 图例、单位、加载与空状态、给读屏的数据表(类目、数值、占比)同其他图表(见 ChartFrame)。\n */\nimport { Donut } from '@unovis/ts';\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport { css } from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport type { VNodeChild } from 'vue';\nimport { useLang } from '../config/context';\nimport { useFormat, type NumericFormat } from '../config/lang/format';\nimport { Skeleton } from '../feedback/Skeleton';\nimport { toNumber, type NumericValue } from '../utils/data/numeric';\nimport type { PartProps } from '../utils/props';\nimport { ChartFrame, chartTooltip, useChartValueText, type ChartFrameParts } from './frame';\nimport { useSingleChart, type SingleChart } from './single';\nimport { resolveColor, seriesValue, type ChartColor, type NumericKey } from './series';\n\n/** 未声明的属性落到外层 div(role=\"figure\")上,aria-label 是图表的名称 */\nexport interface DonutChartProps<T extends object>\n extends PartProps<'div', 'role' | 'aria-busy'>, ChartFrameParts {\n /** 数据:每一行一个扇区 */\n data: readonly T[];\n /** 名称字段:图例、提示框、数据表里显示,也是隐藏一项时记的 key */\n index: keyof T & string;\n /** 数值字段 */\n value: NumericKey<T>;\n /** 每一项的颜色;不写时按顺序取图表色板 */\n itemColor?: (row: T, index: number) => ChartColor | undefined;\n /** 数值的格式(可缩放,如以万元为单位):中间的合计、提示框、数据表共用 */\n valueFormat?: NumericFormat;\n /** 单位:显示在左上方,数值后面带上 */\n unit?: string;\n /**\n * 绘图区的高度;数字按 px\n * @default 240\n */\n height?: number | string;\n /**\n * 环的粗细(px);0 是饼图\n * @default 28\n */\n arcWidth?: number;\n /** 中间的大字;默认是显示中各项的合计,false 时不写 */\n centerLabel?: string | false;\n /** 中间大字下面的小字;默认是“合计” */\n centerSubLabel?: string;\n /**\n * 图例\n * @default true\n */\n legend?: boolean;\n /**\n * 悬停时的提示框\n * @default true\n */\n tooltip?: boolean;\n /**\n * 加载中:绘图区显示一个圆形的骨架,带 aria-busy\n * @default false\n */\n loading?: boolean;\n /** 隐藏的项(存名称):点图例时写回,父级可以写 _hiddenItems={names} */\n _hiddenItems?: string[];\n /** 没有数据时显示的内容,默认是“暂无数据” */\n empty?: VNodeChild;\n /**\n * 点了一个扇区(下钻):参数是这一行与它在 data 里的位置;写了它时指针是手形。\n * 点图是鼠标的捷径:同样的去处在页面上还要有链接或按钮\n */\n onItemClick?: (row: T, index: number) => void;\n}\n\n/** DonutChart 的样式:skeleton */\nexport const donutChartStyles = css.create({\n skeleton: {\n display: 'block',\n height: '100%',\n aspectRatio: '1',\n marginInline: 'auto',\n borderRadius: '50%',\n },\n});\n\n/** 扇区的数据:一行加上它的名字与颜色 */\ninterface Slice<T> {\n row: T;\n name: string;\n color: string;\n value: NumericValue | undefined;\n}\n\n/** 环形图 */\nexport const DonutChart = _component(\n <T extends object>({\n data,\n index,\n value,\n itemColor,\n valueFormat,\n unit,\n height = 240,\n arcWidth = 28,\n centerLabel,\n centerSubLabel,\n legend = true,\n tooltip = true,\n loading = false,\n onItemClick,\n _hiddenItems: hiddenItems = _bindable<string[]>([]),\n empty,\n ...rest\n }: DonutChartProps<T>) => {\n const lang = useLang();\n const format = useFormat();\n\n // 颜色按原来的顺序定:隐藏一项时其余扇区不换色\n const slices = _derived.by((): Slice<T>[] => {\n return data.map((row, position) => ({\n row,\n name: String(row[index] ?? ''),\n color: resolveColor(itemColor?.(row, position), position),\n value: seriesValue(row, value),\n }));\n });\n const visible = _derived(slices.filter((slice) => !hiddenItems.includes(slice.name)));\n const total = _derived(\n visible.reduce(\n (sum, slice) => sum + (slice.value === undefined ? 0 : toNumber(slice.value)),\n 0,\n ),\n );\n\n const valueText = useChartValueText(() => ({ valueFormat, unit }));\n const shareText = (amount: NumericValue | undefined) =>\n amount === undefined || total === 0\n ? lang.emptyValue\n : format.number(toNumber(amount) / total, { style: 'percent', maximumFractionDigits: 1 });\n\n function toggle(name: string) {\n hiddenItems = hiddenItems.includes(name)\n ? hiddenItems.filter((item) => item !== name)\n : [...hiddenItems, name];\n }\n\n let plot = _state.raw<HTMLElement | null>(null);\n const chart: SingleChart = useSingleChart<Slice<T>[]>(() => ({\n element: plot,\n ready: !loading && data.length > 0,\n tooltip,\n build: (instance) => {\n const donut = instance('donut', () => new Donut<Slice<T>>({ value: () => 0 }));\n donut.setConfig({\n value: (slice) => (slice.value === undefined ? 0 : toNumber(slice.value)),\n id: (slice) => slice.name,\n color: (slice) => slice.color,\n arcWidth,\n padAngle: arcWidth > 0 ? 0.01 : 0,\n cornerRadius: arcWidth > 0 ? 3 : 0,\n centralLabel:\n centerLabel === false ? undefined : (centerLabel ?? format.number(total, valueFormat)),\n centralSubLabel: centerLabel === false ? undefined : (centerSubLabel ?? lang.chartTotal),\n showBackground: true,\n events: {\n [Donut.selectors.segment]: {\n click: (arc: { data: Slice<T> }) =>\n onItemClick?.(arc.data.row, data.indexOf(arc.data.row)),\n },\n },\n });\n return {\n component: donut,\n data: [...visible],\n triggers: {\n [Donut.selectors.segment]: (arc: { data: Slice<T> }) =>\n chart.tooltip(\n chartTooltip(arc.data.name, [\n {\n label: lang.chartValue,\n color: arc.data.color,\n value: valueText(arc.data.value),\n },\n {\n label: lang.chartShare,\n color: 'transparent',\n value: shareText(arc.data.value),\n },\n ]),\n ),\n },\n };\n },\n }));\n\n return () => (\n <ChartFrame\n {...rest}\n unit={unit}\n height={height}\n legend={\n legend\n ? slices.map((slice) => ({ key: slice.name, label: slice.name, color: slice.color }))\n : []\n }\n hiddenKeys={hiddenItems}\n onLegendToggle={toggle}\n loading={loading}\n skeleton={<Skeleton sx={donutChartStyles.skeleton} />}\n clickable={onItemClick != null}\n isEmpty={data.length === 0}\n empty={empty}\n table={{\n columns: [\n lang.chartCategory,\n unit ? `${lang.chartValue}(${unit})` : lang.chartValue,\n lang.chartShare,\n ],\n rows: visible.map((slice) => ({\n header: slice.name,\n cells: [\n slice.value === undefined ? lang.emptyValue : format.number(slice.value, valueFormat),\n shareText(slice.value),\n ],\n })),\n }}\n plotRef={(element) => (plot = element)}\n />\n );\n },\n);\n"],"mappings":"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"}
1
+ {"version":3,"file":"DonutChart.js","names":["_vtp_readonlyProp","_vtp_bindable","_vtp_restProps","_vue_computed","_vue_shallowRef","_createVNode","_mergeProps"],"sources":["../../src/charts/DonutChart.tsx"],"sourcesContent":["/**\n * 环形图(arcWidth 为 0 时是饼图):每一行一个扇区,中间写合计;悬停扇区出提示框(数值与占比),点图例隐藏一项。\n *\n * <DonutChart aria-label=\"访问来源\" data={channels} index=\"name\" value=\"visits\" />\n * <DonutChart data={budget} index=\"dept\" value=\"amount\" unit=\"万元\" valueFormat={{ maximumFractionDigits: 0 }} />\n *\n * 颜色默认按顺序取图表色板(itemColor 可以按行给);值可以是 number、bigint、Decimal,提示框与数据表按原值显示。\n * 图例、单位、加载与空状态、给读屏的数据表(类目、数值、占比)同其他图表(见 ChartFrame)。\n */\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport { css } from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport type { VNodeChild } from 'vue';\nimport { useLang } from '../config/context';\nimport { useFormat, type NumericFormat } from '../config/lang/format';\nimport { Skeleton } from '../feedback/Skeleton';\nimport { toNumber, type NumericValue } from '../utils/data/numeric';\nimport type { PartProps } from '../utils/props';\nimport { t } from '../config/theme/tokens';\nimport { svgNumber } from '../utils/data/number';\nimport { ChartFrame, chartTooltip, useChartValueText, type ChartFrameParts } from './frame';\nimport { ChartTooltipBox, chartSvgStyles, pointerIn, usePlotArea, type ChartTip } from './plot';\nimport { arcPath } from './shape';\nimport { resolveColor, seriesValue, type ChartColor, type NumericKey } from './series';\n\n/** 未声明的属性落到外层 div(role=\"figure\")上,aria-label 是图表的名称 */\nexport interface DonutChartProps<T extends object>\n extends PartProps<'div', 'role' | 'aria-busy'>, ChartFrameParts {\n /** 数据:每一行一个扇区 */\n data: readonly T[];\n /** 名称字段:图例、提示框、数据表里显示,也是隐藏一项时记的 key */\n index: keyof T & string;\n /** 数值字段 */\n value: NumericKey<T>;\n /** 每一项的颜色;不写时按顺序取图表色板 */\n itemColor?: (row: T, index: number) => ChartColor | undefined;\n /** 数值的格式(可缩放,如以万元为单位):中间的合计、提示框、数据表共用 */\n valueFormat?: NumericFormat;\n /** 单位:显示在左上方,数值后面带上 */\n unit?: string;\n /**\n * 绘图区的高度;数字按 px\n * @default 240\n */\n height?: number | string;\n /**\n * 环的粗细(px);0 是饼图\n * @default 28\n */\n arcWidth?: number;\n /** 中间的大字;默认是显示中各项的合计,false 时不写 */\n centerLabel?: string | false;\n /** 中间大字下面的小字;默认是“合计” */\n centerSubLabel?: string;\n /**\n * 图例\n * @default true\n */\n legend?: boolean;\n /**\n * 悬停时的提示框\n * @default true\n */\n tooltip?: boolean;\n /**\n * 加载中:绘图区显示一个圆形的骨架,带 aria-busy\n * @default false\n */\n loading?: boolean;\n /** 隐藏的项(存名称):点图例时写回,父级可以写 _hiddenItems={names} */\n _hiddenItems?: string[];\n /** 没有数据时显示的内容,默认是“暂无数据” */\n empty?: VNodeChild;\n /**\n * 点了一个扇区(下钻):参数是这一行与它在 data 里的位置;写了它时指针是手形。\n * 点图是鼠标的捷径:同样的去处在页面上还要有链接或按钮\n */\n onItemClick?: (row: T, index: number) => void;\n}\n\n/** DonutChart 的样式:skeleton、扇区、中间的文字 */\nexport const donutChartStyles = css.create({\n // 进场:整个环从略小淡入放大\n ring: {\n transformBox: 'fill-box',\n transformOrigin: 'center',\n opacity: { default: 1, '@starting-style': 0 },\n scale: { default: '1', '@starting-style': '0.9' },\n transition: `opacity ${t.durations.lg} ${t.easings.standard}, scale ${t.durations.lg} ${t.easings.standard}`,\n },\n background: { fill: '_fill' },\n segment: { stroke: '_surface', strokeWidth: 1 },\n centerValue: { fill: '_text', fontSize: '_xl', fontWeight: '_semibold' },\n centerLabel: { fill: '_textMuted', fontSize: '_xs' },\n skeleton: {\n display: 'block',\n height: '100%',\n aspectRatio: '1',\n marginInline: 'auto',\n borderRadius: '50%',\n },\n});\n\n/** 扇区的数据:一行加上它的名字与颜色 */\ninterface Slice<T> {\n row: T;\n name: string;\n color: string;\n value: NumericValue | undefined;\n}\n\n/** 环形图 */\nexport const DonutChart = _component(\n <T extends object>({\n data,\n index,\n value,\n itemColor,\n valueFormat,\n unit,\n height = 240,\n arcWidth = 28,\n centerLabel,\n centerSubLabel,\n legend = true,\n tooltip = true,\n loading = false,\n onItemClick,\n _hiddenItems: hiddenItems = _bindable<string[]>([]),\n empty,\n ...rest\n }: DonutChartProps<T>) => {\n const lang = useLang();\n const format = useFormat();\n\n // 颜色按原来的顺序定:隐藏一项时其余扇区不换色\n const slices = _derived.by((): Slice<T>[] => {\n return data.map((row, position) => ({\n row,\n name: String(row[index] ?? ''),\n color: resolveColor(itemColor?.(row, position), position),\n value: seriesValue(row, value),\n }));\n });\n const visible = _derived(slices.filter((slice) => !hiddenItems.includes(slice.name)));\n const total = _derived(\n visible.reduce(\n (sum, slice) => sum + (slice.value === undefined ? 0 : toNumber(slice.value)),\n 0,\n ),\n );\n\n const valueText = useChartValueText(() => ({ valueFormat, unit }));\n const shareText = (amount: NumericValue | undefined) =>\n amount === undefined || total === 0\n ? lang.emptyValue\n : format.number(toNumber(amount) / total, { style: 'percent', maximumFractionDigits: 1 });\n\n function toggle(name: string) {\n hiddenItems = hiddenItems.includes(name)\n ? hiddenItems.filter((item) => item !== name)\n : [...hiddenItems, name];\n }\n\n const area = usePlotArea();\n let hovered = _state.raw<{ index: number; x: number; y: number } | null>(null);\n\n /** 扇区的位置:从 12 点钟方向顺时针,按数值分角度;扇区之间留一点缝 */\n const arcs = _derived.by(() => {\n const pad = arcWidth > 0 && visible.length > 1 ? 0.012 : 0;\n let angle = 0;\n return visible.map((slice) => {\n const amount = slice.value === undefined ? 0 : Math.max(toNumber(slice.value), 0);\n const sweep = total > 0 ? (amount / total) * Math.PI * 2 : 0;\n const start = angle;\n angle += sweep;\n return { slice, start: start + pad / 2, end: Math.max(start + pad / 2, angle - pad / 2) };\n });\n });\n\n function onPointermove(event: PointerEvent) {\n const target = (event.target as Element).closest?.('[data-index]');\n if (!target) {\n hovered = null;\n return;\n }\n const { x, y } = pointerIn(event, event.currentTarget as Element);\n hovered = { index: Number(target.getAttribute('data-index')), x, y };\n }\n\n function onClick(event: MouseEvent) {\n const target = (event.target as Element).closest?.('[data-index]');\n const slice = target && arcs[Number(target.getAttribute('data-index'))]?.slice;\n if (slice) onItemClick?.(slice.row, data.indexOf(slice.row));\n }\n\n function renderPlot() {\n const { width, height } = area;\n // 留出悬停时扇区往外长的 6px\n const outer = Math.max(0, Math.min(width, height) / 2 - 8);\n const inner = arcWidth > 0 ? Math.max(0, outer - arcWidth) : 0;\n const center =\n centerLabel === false || inner < 24\n ? undefined\n : (centerLabel ?? format.number(total, valueFormat));\n return (\n <svg\n width={width}\n height={height}\n {...css.attrs(chartSvgStyles.svg)}\n onPointermove={onPointermove}\n onPointerleave={() => (hovered = null)}\n onClick={onClick}\n >\n <g transform={`translate(${svgNumber(width / 2)},${svgNumber(height / 2)})`}>\n <g {...css.attrs(donutChartStyles.ring)}>\n {inner > 0 && (\n <path\n d={arcPath({ inner, outer, start: 0, end: Math.PI * 2 })}\n {...css.attrs(donutChartStyles.background)}\n />\n )}\n {arcs.map((arc, index) => (\n <path\n key={arc.slice.name}\n data-part=\"segment\"\n data-index={index}\n d={arcPath({\n inner,\n outer: hovered?.index === index ? outer + 6 : outer,\n start: arc.start,\n end: arc.end,\n corner: arcWidth > 0 ? 3 : 0,\n })}\n {...css.attrs(\n donutChartStyles.segment,\n chartSvgStyles.fill(arc.slice.color),\n chartSvgStyles.dimmable,\n hovered !== null && hovered.index !== index && chartSvgStyles.dimmed,\n )}\n />\n ))}\n </g>\n {center !== undefined && (\n <g data-part=\"center-label\" {...css.attrs(chartSvgStyles.noPointer)}>\n <text y={-2} text-anchor=\"middle\" {...css.attrs(donutChartStyles.centerValue)}>\n {center}\n </text>\n <text y={18} text-anchor=\"middle\" {...css.attrs(donutChartStyles.centerLabel)}>\n {centerSubLabel ?? lang.chartTotal}\n </text>\n </g>\n )}\n </g>\n </svg>\n );\n }\n\n const tip = (): ChartTip | null => {\n const current = hovered;\n const slice = current && arcs[current.index]?.slice;\n if (!tooltip || !current || !slice) return null;\n return {\n x: current.x,\n y: current.y,\n content: chartTooltip(slice.name, [\n { label: lang.chartValue, color: slice.color, value: valueText(slice.value) },\n { label: lang.chartShare, color: 'transparent', value: shareText(slice.value) },\n ]),\n };\n };\n\n return () => (\n <ChartFrame\n {...rest}\n unit={unit}\n height={height}\n legend={\n legend\n ? slices.map((slice) => ({ key: slice.name, label: slice.name, color: slice.color }))\n : []\n }\n hiddenKeys={hiddenItems}\n onLegendToggle={toggle}\n loading={loading}\n skeleton={<Skeleton sx={donutChartStyles.skeleton} />}\n clickable={onItemClick != null}\n isEmpty={data.length === 0}\n empty={empty}\n table={{\n columns: [\n lang.chartCategory,\n unit ? `${lang.chartValue}(${unit})` : lang.chartValue,\n lang.chartShare,\n ],\n rows: visible.map((slice) => ({\n header: slice.name,\n cells: [\n slice.value === undefined ? lang.emptyValue : format.number(slice.value, valueFormat),\n shareText(slice.value),\n ],\n })),\n }}\n plotRef={area.ref}\n plot={\n area.ready\n ? [renderPlot(), <ChartTooltipBox key=\"tooltip\" tip={tip()} width={area.width} />]\n : null\n }\n />\n );\n },\n);\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"FunnelChart.js","names":["_vtp_readonlyProp","_vtp_restProps","_vue_computed","_createVNode","_mergeProps","_createTextVNode"],"sources":["../../src/charts/FunnelChart.tsx"],"sourcesContent":["/**\n * 漏斗图:一串前后相接的环节,看每一步留下多少、在哪一步流失最多(访问 → 注册 → 下单、线索转化)。\n *\n * <FunnelChart aria-label=\"渠道转化\" stages={[{ label: '提交建议', value: 120 }, { label: '立项', value: 46 }]} unit=\"项\" />\n *\n * 每个环节一条居中的横条,长短按数值(最大的环节占满);环节之间写比上一环节的转化率,末尾写总转化。\n * 横条默认同一个颜色(chart1),环节可以单独给 color。Unovis 没有漏斗图:这里用主题 tokens 直接画,\n * 外层结构(单位、加载与空状态、给读屏的数据表)同其他图表(见 ChartFrame)。\n */\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport { css } from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport type { VNodeChild } from 'vue';\nimport { useLang } from '../config/context';\nimport { useFormat, type NumericFormat } from '../config/lang/format';\nimport { Skeleton } from '../feedback/Skeleton';\nimport { toNumber, type NumericValue } from '../utils/data/numeric';\nimport type { PartProps } from '../utils/props';\nimport { ChartFrame, type ChartFrameParts } from './frame';\nimport { resolveColor, type ChartColor } from './series';\nimport { t } from '../config/theme/tokens';\n\n/** 漏斗的一个环节 */\nexport interface FunnelStage {\n /** 名称 */\n label: string;\n /** 数值:number、bigint 或 Decimal */\n value: NumericValue;\n /** 颜色;不写时用图表的 color */\n color?: ChartColor;\n}\n\n/** 未声明的属性落到外层 div(role=\"figure\")上,aria-label 是图表的名称 */\nexport interface FunnelChartProps extends PartProps<'div', 'role' | 'aria-busy'>, ChartFrameParts {\n /** 各环节:从上到下 */\n stages: readonly FunnelStage[];\n /** 数值的格式(可缩放):横条旁的数值与数据表共用 */\n valueFormat?: NumericFormat;\n /** 单位:显示在左上方,数值后面带上 */\n unit?: string;\n /**\n * 横条的颜色\n * @default 'chart1'\n */\n color?: ChartColor;\n /**\n * 环节之间写转化率,末尾写总转化\n * @default true\n */\n rates?: boolean;\n /**\n * 加载中:显示几条骨架,带 aria-busy\n * @default false\n */\n loading?: boolean;\n /** 没有数据时显示的内容,默认是“暂无数据” */\n empty?: VNodeChild;\n /**\n * 点了一个环节(下钻):参数是这个环节与它的位置;写了它时指针是手形。\n * 点图是鼠标的捷径:同样的去处在页面上还要有链接或按钮\n */\n onItemClick?: (stage: FunnelStage, index: number) => void;\n}\n\n/** FunnelChart 的样式:list、row、label、track、bar、value、rate… */\nexport const funnelChartStyles = css.create({\n // 名称 | 居中的横条 | 数值:三列由整个列表定宽,各行用 subgrid 对齐\n list: {\n display: 'grid',\n gridTemplateColumns: 'max-content minmax(0, 1fr) max-content',\n columnGap: '_md',\n },\n row: {\n display: 'grid',\n gridColumn: '1 / -1',\n gridTemplateColumns: 'subgrid',\n alignItems: 'center',\n paddingBlock: '2px',\n paddingInline: '_xs',\n borderRadius: '_sm',\n },\n rowClickable: { backgroundColor: { default: 'transparent', ':hover': '_fill' } },\n label: { color: '_text', textAlign: 'end', whiteSpace: 'nowrap' },\n track: { display: 'flex', justifyContent: 'center' },\n bar: {\n height: '1.75rem',\n minWidth: '2px',\n borderRadius: '_sm',\n transition: `width ${t.durations.md} ${t.easings.standard}`,\n },\n value: { fontVariantNumeric: 'tabular-nums', fontWeight: '_medium', whiteSpace: 'nowrap' },\n // 环节之间:转化率写在中间那一列,小号的次要文字\n rate: {\n display: 'grid',\n gridColumn: '1 / -1',\n gridTemplateColumns: 'subgrid',\n paddingInline: '_xs',\n color: '_textMuted',\n fontSize: '_xs',\n fontVariantNumeric: 'tabular-nums',\n lineHeight: '_tight',\n },\n rateText: { gridColumn: '2', textAlign: 'center' },\n overall: { gridColumn: '2 / 4', textAlign: 'center', paddingBlockStart: '_xs' },\n skeleton: { display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '_sm' },\n skeletonBar: { height: '1.75rem', borderRadius: '_sm' },\n});\n\n// 横条的宽度与颜色随数据变:单独一个 create(见 COMPONENTS.md §4)\n/** FunnelChart 的样式:barSize、skeletonWidth */\nexport const funnelChartDynamicStyles = css.create({\n barSize: (width: string, color: string) => ({ width, backgroundColor: color }),\n skeletonWidth: (width: string) => ({ width }),\n});\n\n/** 骨架横条的宽度:从宽到窄 */\nconst SKELETON_WIDTHS = ['90%', '68%', '46%', '28%'];\n\n/** 漏斗图 */\nexport const FunnelChart = _component(\n ({\n stages,\n valueFormat,\n unit,\n color = 'chart1',\n rates = true,\n loading = false,\n empty,\n onItemClick,\n ...rest\n }: FunnelChartProps) => {\n const lang = useLang();\n const format = useFormat();\n const numbers = _derived(stages.map((stage) => Math.max(toNumber(stage.value), 0)));\n const max = _derived(Math.max(0, ...numbers));\n const percent = (value: number) =>\n format.number(value, { style: 'percent', maximumFractionDigits: 1 });\n /** 第 index 个环节比 base 个环节的转化率;base 为 0 时没有 */\n const rate = (index: number, base: number) =>\n numbers[base] ? percent(numbers[index]! / numbers[base]!) : lang.emptyValue;\n const valueText = (stage: FunnelStage) =>\n format.number(stage.value, unit ? { ...valueFormat, suffix: unit } : valueFormat);\n\n return () => (\n <ChartFrame\n {...rest}\n unit={unit}\n height=\"auto\"\n legend={[]}\n hiddenKeys={[]}\n onLegendToggle={() => {}}\n loading={loading}\n skeleton={\n <div aria-hidden=\"true\" {...css.attrs(funnelChartStyles.skeleton)}>\n {SKELETON_WIDTHS.map((width) => (\n <Skeleton\n key={width}\n sx={[funnelChartStyles.skeletonBar, funnelChartDynamicStyles.skeletonWidth(width)]}\n />\n ))}\n </div>\n }\n isEmpty={stages.length === 0}\n empty={empty}\n table={{\n columns: [\n lang.chartStage,\n unit ? `${lang.chartValue}(${unit})` : lang.chartValue,\n lang.chartStepRate,\n lang.chartOverallRate,\n ],\n rows: stages.map((stage, index) => ({\n header: stage.label,\n cells: [\n format.number(stage.value, valueFormat),\n index === 0 ? lang.emptyValue : rate(index, index - 1),\n index === 0 ? lang.emptyValue : rate(index, 0),\n ],\n })),\n }}\n plot={\n <div {...css.attrs(funnelChartStyles.list)}>\n {stages.map((stage, index) => [\n index > 0 && rates && (\n <div key={`rate-${index}`} {...css.attrs(funnelChartStyles.rate)}>\n <span {...css.attrs(funnelChartStyles.rateText)}>↓ {rate(index, index - 1)}</span>\n </div>\n ),\n <div\n key={`stage-${index}`}\n {...css.attrs(funnelChartStyles.row, onItemClick && funnelChartStyles.rowClickable)}\n onClick={onItemClick && (() => onItemClick(stage, index))}\n >\n <span {...css.attrs(funnelChartStyles.label)}>{stage.label}</span>\n <span {...css.attrs(funnelChartStyles.track)}>\n <span\n {...css.attrs(\n funnelChartStyles.bar,\n funnelChartDynamicStyles.barSize(\n max ? `${(numbers[index]! / max) * 100}%` : '0%',\n resolveColor(stage.color ?? color, index),\n ),\n )}\n />\n </span>\n <span {...css.attrs(funnelChartStyles.value)}>{valueText(stage)}</span>\n </div>,\n ])}\n {rates && stages.length > 1 && (\n <div {...css.attrs(funnelChartStyles.rate)}>\n <span {...css.attrs(funnelChartStyles.overall)}>\n {lang.chartOverallRate} {rate(stages.length - 1, 0)}\n </span>\n </div>\n )}\n </div>\n }\n clickable={onItemClick != null}\n />\n );\n },\n);\n"],"mappings":"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"}
1
+ {"version":3,"file":"FunnelChart.js","names":["_vtp_readonlyProp","_vtp_restProps","_vue_computed","_createVNode","_mergeProps","_createTextVNode"],"sources":["../../src/charts/FunnelChart.tsx"],"sourcesContent":["/**\n * 漏斗图:一串前后相接的环节,看每一步留下多少、在哪一步流失最多(访问 → 注册 → 下单、线索转化)。\n *\n * <FunnelChart aria-label=\"渠道转化\" stages={[{ label: '提交建议', value: 120 }, { label: '立项', value: 46 }]} unit=\"项\" />\n *\n * 每个环节一条居中的横条,长短按数值(最大的环节占满);环节之间写比上一环节的转化率,末尾写总转化。\n * 横条默认同一个颜色(chart1),环节可以单独给 color。横条是 HTML(不是 SVG),长短、颜色直接用主题 tokens;\n * 外层结构(单位、加载与空状态、给读屏的数据表)同其他图表(见 ChartFrame)。\n */\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport { css } from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport type { VNodeChild } from 'vue';\nimport { useLang } from '../config/context';\nimport { useFormat, type NumericFormat } from '../config/lang/format';\nimport { Skeleton } from '../feedback/Skeleton';\nimport { toNumber, type NumericValue } from '../utils/data/numeric';\nimport type { PartProps } from '../utils/props';\nimport { ChartFrame, type ChartFrameParts } from './frame';\nimport { resolveColor, type ChartColor } from './series';\nimport { t } from '../config/theme/tokens';\n\n/** 漏斗的一个环节 */\nexport interface FunnelStage {\n /** 名称 */\n label: string;\n /** 数值:number、bigint 或 Decimal */\n value: NumericValue;\n /** 颜色;不写时用图表的 color */\n color?: ChartColor;\n}\n\n/** 未声明的属性落到外层 div(role=\"figure\")上,aria-label 是图表的名称 */\nexport interface FunnelChartProps extends PartProps<'div', 'role' | 'aria-busy'>, ChartFrameParts {\n /** 各环节:从上到下 */\n stages: readonly FunnelStage[];\n /** 数值的格式(可缩放):横条旁的数值与数据表共用 */\n valueFormat?: NumericFormat;\n /** 单位:显示在左上方,数值后面带上 */\n unit?: string;\n /**\n * 横条的颜色\n * @default 'chart1'\n */\n color?: ChartColor;\n /**\n * 环节之间写转化率,末尾写总转化\n * @default true\n */\n rates?: boolean;\n /**\n * 加载中:显示几条骨架,带 aria-busy\n * @default false\n */\n loading?: boolean;\n /** 没有数据时显示的内容,默认是“暂无数据” */\n empty?: VNodeChild;\n /**\n * 点了一个环节(下钻):参数是这个环节与它的位置;写了它时指针是手形。\n * 点图是鼠标的捷径:同样的去处在页面上还要有链接或按钮\n */\n onItemClick?: (stage: FunnelStage, index: number) => void;\n}\n\n/** FunnelChart 的样式:list、row、label、track、bar、value、rate… */\nexport const funnelChartStyles = css.create({\n // 名称 | 居中的横条 | 数值:三列由整个列表定宽,各行用 subgrid 对齐\n list: {\n display: 'grid',\n gridTemplateColumns: 'max-content minmax(0, 1fr) max-content',\n columnGap: '_md',\n },\n row: {\n display: 'grid',\n gridColumn: '1 / -1',\n gridTemplateColumns: 'subgrid',\n alignItems: 'center',\n paddingBlock: '2px',\n paddingInline: '_xs',\n borderRadius: '_sm',\n },\n rowClickable: { backgroundColor: { default: 'transparent', ':hover': '_fill' } },\n label: { color: '_text', textAlign: 'end', whiteSpace: 'nowrap' },\n track: { display: 'flex', justifyContent: 'center' },\n bar: {\n height: '1.75rem',\n minWidth: '2px',\n borderRadius: '_sm',\n transition: `width ${t.durations.md} ${t.easings.standard}`,\n },\n value: { fontVariantNumeric: 'tabular-nums', fontWeight: '_medium', whiteSpace: 'nowrap' },\n // 环节之间:转化率写在中间那一列,小号的次要文字\n rate: {\n display: 'grid',\n gridColumn: '1 / -1',\n gridTemplateColumns: 'subgrid',\n paddingInline: '_xs',\n color: '_textMuted',\n fontSize: '_xs',\n fontVariantNumeric: 'tabular-nums',\n lineHeight: '_tight',\n },\n rateText: { gridColumn: '2', textAlign: 'center' },\n overall: { gridColumn: '2 / 4', textAlign: 'center', paddingBlockStart: '_xs' },\n skeleton: { display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '_sm' },\n skeletonBar: { height: '1.75rem', borderRadius: '_sm' },\n});\n\n// 横条的宽度与颜色随数据变:单独一个 create(见 COMPONENTS.md §4)\n/** FunnelChart 的样式:barSize、skeletonWidth */\nexport const funnelChartDynamicStyles = css.create({\n barSize: (width: string, color: string) => ({ width, backgroundColor: color }),\n skeletonWidth: (width: string) => ({ width }),\n});\n\n/** 骨架横条的宽度:从宽到窄 */\nconst SKELETON_WIDTHS = ['90%', '68%', '46%', '28%'];\n\n/** 漏斗图 */\nexport const FunnelChart = _component(\n ({\n stages,\n valueFormat,\n unit,\n color = 'chart1',\n rates = true,\n loading = false,\n empty,\n onItemClick,\n ...rest\n }: FunnelChartProps) => {\n const lang = useLang();\n const format = useFormat();\n const numbers = _derived(stages.map((stage) => Math.max(toNumber(stage.value), 0)));\n const max = _derived(Math.max(0, ...numbers));\n const percent = (value: number) =>\n format.number(value, { style: 'percent', maximumFractionDigits: 1 });\n /** 第 index 个环节比 base 个环节的转化率;base 为 0 时没有 */\n const rate = (index: number, base: number) =>\n numbers[base] ? percent(numbers[index]! / numbers[base]!) : lang.emptyValue;\n const valueText = (stage: FunnelStage) =>\n format.number(stage.value, unit ? { ...valueFormat, suffix: unit } : valueFormat);\n\n return () => (\n <ChartFrame\n {...rest}\n unit={unit}\n height=\"auto\"\n legend={[]}\n hiddenKeys={[]}\n onLegendToggle={() => {}}\n loading={loading}\n skeleton={\n <div aria-hidden=\"true\" {...css.attrs(funnelChartStyles.skeleton)}>\n {SKELETON_WIDTHS.map((width) => (\n <Skeleton\n key={width}\n sx={[funnelChartStyles.skeletonBar, funnelChartDynamicStyles.skeletonWidth(width)]}\n />\n ))}\n </div>\n }\n isEmpty={stages.length === 0}\n empty={empty}\n table={{\n columns: [\n lang.chartStage,\n unit ? `${lang.chartValue}(${unit})` : lang.chartValue,\n lang.chartStepRate,\n lang.chartOverallRate,\n ],\n rows: stages.map((stage, index) => ({\n header: stage.label,\n cells: [\n format.number(stage.value, valueFormat),\n index === 0 ? lang.emptyValue : rate(index, index - 1),\n index === 0 ? lang.emptyValue : rate(index, 0),\n ],\n })),\n }}\n plot={\n <div {...css.attrs(funnelChartStyles.list)}>\n {stages.map((stage, index) => [\n index > 0 && rates && (\n <div key={`rate-${index}`} {...css.attrs(funnelChartStyles.rate)}>\n <span {...css.attrs(funnelChartStyles.rateText)}>↓ {rate(index, index - 1)}</span>\n </div>\n ),\n <div\n key={`stage-${index}`}\n {...css.attrs(funnelChartStyles.row, onItemClick && funnelChartStyles.rowClickable)}\n onClick={onItemClick && (() => onItemClick(stage, index))}\n >\n <span {...css.attrs(funnelChartStyles.label)}>{stage.label}</span>\n <span {...css.attrs(funnelChartStyles.track)}>\n <span\n {...css.attrs(\n funnelChartStyles.bar,\n funnelChartDynamicStyles.barSize(\n max ? `${(numbers[index]! / max) * 100}%` : '0%',\n resolveColor(stage.color ?? color, index),\n ),\n )}\n />\n </span>\n <span {...css.attrs(funnelChartStyles.value)}>{valueText(stage)}</span>\n </div>,\n ])}\n {rates && stages.length > 1 && (\n <div {...css.attrs(funnelChartStyles.rate)}>\n <span {...css.attrs(funnelChartStyles.overall)}>\n {lang.chartOverallRate} {rate(stages.length - 1, 0)}\n </span>\n </div>\n )}\n </div>\n }\n clickable={onItemClick != null}\n />\n );\n },\n);\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { css } from '@zerodep-dev/vue-tsx-pro-tsx/styles';
2
- import type { VNodeChild } from 'vue';
2
+ import { type VNodeChild } from 'vue';
3
3
  import { type NumericFormat } from '../config/lang/format';
4
4
  import { type NumericValue } from '../utils/data/numeric';
5
5
  import type { PartProps } from '../utils/props';
@@ -21,15 +21,13 @@ export interface GraphChartLayouts {
21
21
  /** 力导向(默认):相连的靠近、不相连的推开,适合看关系疏密 */
22
22
  force: unknown;
23
23
  /** 分层:沿 direction 一层层排开,适合有先后、上下游的关系 */
24
- dagre: unknown;
24
+ layered: unknown;
25
25
  /** 排成一圈 */
26
26
  circular: unknown;
27
27
  /** 同心圆:连得多的在中间 */
28
28
  concentric: unknown;
29
29
  /** 按 group 分列,组内按连接排 */
30
30
  parallel: unknown;
31
- /** 分层,分组框更规整(elkjs,约 1.6 MB,用到时才加载) */
32
- elk: unknown;
33
31
  }
34
32
  /** 一个节点 */
35
33
  export interface GraphChartNode {
@@ -78,7 +76,7 @@ export interface GraphChartProps extends PartProps<'div', 'role' | 'aria-busy'>,
78
76
  nodes: readonly GraphChartNode[];
79
77
  /** 连线 */
80
78
  links: readonly GraphChartLink[];
81
- /** 分组:同组的节点框在一起(dagre、elk、parallel 布局),颜色相同 */
79
+ /** 分组:同组的节点框在一起(layered、parallel 布局),颜色相同 */
82
80
  groups?: readonly GraphChartGroup[];
83
81
  /**
84
82
  * 布局
@@ -86,11 +84,11 @@ export interface GraphChartProps extends PartProps<'div', 'role' | 'aria-busy'>,
86
84
  */
87
85
  layout?: keyof GraphChartLayouts;
88
86
  /**
89
- * 分层布局(dagre、elk)的方向:LR 从左到右,TB 从上到下
87
+ * 分层、分列布局的方向:LR 从左到右,TB 从上到下
90
88
  * @default 'LR'
91
89
  */
92
90
  direction?: 'LR' | 'TB';
93
- /** 连线带箭头;默认分层布局(dagre、elk)带,其余不带 */
91
+ /** 连线带箭头;默认分层布局带,其余不带 */
94
92
  arrows?: boolean;
95
93
  /** 连线数值的格式:提示框、数据表共用 */
96
94
  valueFormat?: NumericFormat;
@@ -102,7 +100,7 @@ export interface GraphChartProps extends PartProps<'div', 'role' | 'aria-busy'>,
102
100
  */
103
101
  height?: number | string;
104
102
  /**
105
- * 能缩放、拖动画布
103
+ * 能拖动画布、按住 Ctrl 滚轮缩放
106
104
  * @default true
107
105
  */
108
106
  zoom?: boolean;
@@ -126,10 +124,24 @@ export interface GraphChartProps extends PartProps<'div', 'role' | 'aria-busy'>,
126
124
  */
127
125
  onItemClick?: (node: GraphChartNode, index: number) => void;
128
126
  }
129
- /** GraphChart 的样式:root(--vis-graph-* 主题变量)、skeleton */
127
+ /** GraphChart 的样式 */
130
128
  export declare const graphChartStyles: Readonly<{
131
- root: css.CompiledStyles;
129
+ background: css.CompiledStyles;
130
+ clickable: css.CompiledStyles;
131
+ link: css.CompiledStyles;
132
+ linkDashed: css.CompiledStyles;
133
+ linkLabel: css.CompiledStyles;
134
+ linkLabelBox: css.CompiledStyles;
135
+ node: css.CompiledStyles;
136
+ nodeLabel: css.CompiledStyles;
137
+ nodeSelected: css.CompiledStyles;
138
+ nodeShape: css.CompiledStyles;
139
+ nodeSublabel: css.CompiledStyles;
140
+ panel: css.CompiledStyles;
141
+ panelLabel: css.CompiledStyles;
132
142
  skeleton: css.CompiledStyles;
143
+ viewport: css.CompiledStyles;
144
+ wheelHint: css.CompiledStyles;
133
145
  }>;
134
146
  /** 网络图 */
135
147
  export declare const GraphChart: import("@zerodep-dev/vue-tsx-pro-tsx").TsxComponent<GraphChartProps>;