@zerodep-dev/vue-tsx-pro-ui 0.3.3 → 0.4.1
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/charts/AreaChart.d.ts +1 -1
- package/dist/charts/AreaChart.js +6 -48
- package/dist/charts/AreaChart.js.map +1 -1
- package/dist/charts/BarChart.js +6 -49
- package/dist/charts/BarChart.js.map +1 -1
- package/dist/charts/ChordChart.d.ts +7 -2
- package/dist/charts/ChordChart.js +151 -60
- package/dist/charts/ChordChart.js.map +1 -1
- package/dist/charts/ComboChart.d.ts +2 -1
- package/dist/charts/ComboChart.js +17 -93
- package/dist/charts/ComboChart.js.map +1 -1
- package/dist/charts/DonutChart.d.ts +6 -1
- package/dist/charts/DonutChart.js +134 -51
- package/dist/charts/DonutChart.js.map +1 -1
- package/dist/charts/FunnelChart.js.map +1 -1
- package/dist/charts/GraphChart.d.ts +22 -10
- package/dist/charts/GraphChart.js +415 -175
- package/dist/charts/GraphChart.js.map +1 -1
- package/dist/charts/LineChart.d.ts +1 -29
- package/dist/charts/LineChart.js +6 -54
- package/dist/charts/LineChart.js.map +1 -1
- package/dist/charts/MapChart.d.ts +21 -48
- package/dist/charts/MapChart.js +426 -353
- package/dist/charts/MapChart.js.map +1 -1
- package/dist/charts/RadarChart.js.map +1 -1
- package/dist/charts/SankeyChart.d.ts +7 -2
- package/dist/charts/SankeyChart.js +164 -76
- package/dist/charts/SankeyChart.js.map +1 -1
- package/dist/charts/ScatterChart.d.ts +7 -1
- package/dist/charts/ScatterChart.js +45 -30
- package/dist/charts/ScatterChart.js.map +1 -1
- package/dist/charts/TimelineChart.d.ts +8 -1
- package/dist/charts/TimelineChart.js +141 -89
- package/dist/charts/TimelineChart.js.map +1 -1
- package/dist/charts/TreemapChart.d.ts +6 -0
- package/dist/charts/TreemapChart.js +174 -58
- package/dist/charts/TreemapChart.js.map +1 -1
- package/dist/charts/WaterfallChart.d.ts +6 -7
- package/dist/charts/WaterfallChart.js +46 -51
- package/dist/charts/WaterfallChart.js.map +1 -1
- package/dist/charts/XYChart.d.ts +44 -35
- package/dist/charts/XYChart.js +493 -281
- package/dist/charts/XYChart.js.map +1 -1
- package/dist/charts/axis-ticks.d.ts +1 -1
- package/dist/charts/axis-ticks.js +4 -5
- package/dist/charts/axis-ticks.js.map +1 -1
- package/dist/charts/flow.d.ts +4 -11
- package/dist/charts/flow.js +4 -4
- package/dist/charts/flow.js.map +1 -1
- package/dist/charts/frame.d.ts +6 -30
- package/dist/charts/frame.js +6 -99
- package/dist/charts/frame.js.map +1 -1
- package/dist/charts/geo/projection.d.ts +45 -0
- package/dist/charts/geo/projection.js +201 -0
- package/dist/charts/geo/projection.js.map +1 -0
- package/dist/charts/geo/topojson.d.ts +50 -0
- package/dist/charts/geo/topojson.js +119 -0
- package/dist/charts/geo/topojson.js.map +1 -0
- package/dist/charts/index.d.ts +21 -9
- package/dist/charts/index.js +19 -8
- package/dist/charts/label-layout.js.map +1 -1
- package/dist/charts/layout/chord.d.ts +44 -0
- package/dist/charts/layout/chord.js +45 -0
- package/dist/charts/layout/chord.js.map +1 -0
- package/dist/charts/layout/graph.d.ts +49 -0
- package/dist/charts/layout/graph.js +268 -0
- package/dist/charts/layout/graph.js.map +1 -0
- package/dist/charts/layout/sankey.d.ts +79 -0
- package/dist/charts/layout/sankey.js +200 -0
- package/dist/charts/layout/sankey.js.map +1 -0
- package/dist/charts/layout/treemap.d.ts +46 -0
- package/dist/charts/layout/treemap.js +152 -0
- package/dist/charts/layout/treemap.js.map +1 -0
- package/dist/charts/marks.d.ts +79 -0
- package/dist/charts/marks.js +231 -0
- package/dist/charts/marks.js.map +1 -0
- package/dist/charts/plot.d.ts +107 -0
- package/dist/charts/plot.js +231 -0
- package/dist/charts/plot.js.map +1 -0
- package/dist/charts/scale.d.ts +29 -0
- package/dist/charts/scale.js +244 -0
- package/dist/charts/scale.js.map +1 -0
- package/dist/charts/shape.d.ts +46 -0
- package/dist/charts/shape.js +131 -0
- package/dist/charts/shape.js.map +1 -0
- package/dist/charts/stack.d.ts +17 -0
- package/dist/charts/stack.js +35 -0
- package/dist/charts/stack.js.map +1 -0
- package/dist/charts/text-size.d.ts +12 -0
- package/dist/charts/text-size.js +32 -0
- package/dist/charts/text-size.js.map +1 -0
- package/dist/config/createUi.d.ts +21 -2
- package/dist/config/createUi.js +18 -1
- package/dist/config/createUi.js.map +1 -1
- package/dist/config/theme/preference.js +2 -1
- package/dist/config/theme/preference.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/maps/china-provinces.js +35680 -0
- package/dist/maps/china-provinces.js.map +1 -0
- package/dist/maps/china.d.ts +13 -0
- package/dist/maps/china.js +8 -0
- package/dist/maps/china.js.map +1 -0
- package/dist/maps/index.d.ts +9 -0
- package/dist/maps/index.js +3 -0
- package/dist/maps/world-countries.js +22273 -0
- package/dist/maps/world-countries.js.map +1 -0
- package/dist/maps/world.d.ts +13 -0
- package/dist/maps/world.js +8 -0
- package/dist/maps/world.js.map +1 -0
- package/dist/overlay/detached.js.map +1 -1
- package/dist/utils/request/use-request.d.ts +25 -1
- package/dist/utils/request/use-request.js +35 -3
- package/dist/utils/request/use-request.js.map +1 -1
- package/package.json +7 -8
- package/dist/charts/single.d.ts +0 -41
- package/dist/charts/single.js +0 -66
- package/dist/charts/single.js.map +0 -1
|
@@ -1,36 +1,58 @@
|
|
|
1
|
+
import { svgNumber } from "../utils/data/number.js";
|
|
1
2
|
import { toNumber } from "../utils/data/numeric.js";
|
|
3
|
+
import { colors, 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";
|
|
5
8
|
import { ChartFrame, chartTooltip } from "./frame.js";
|
|
6
9
|
import { resolveColor, seriesValue } from "./series.js";
|
|
7
|
-
import {
|
|
10
|
+
import { estimateTextWidth, truncateText } from "./text-size.js";
|
|
11
|
+
import { groupTree, treemapLayout } from "./layout/treemap.js";
|
|
8
12
|
import { computed, createVNode, shallowRef } from "vue";
|
|
9
13
|
import { _component, bindable, mergeJsxProps, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
10
14
|
import { css } from "@zerodep-dev/vue-tsx-pro-tsx/styles";
|
|
11
|
-
import { Treemap } from "@unovis/ts";
|
|
12
15
|
//#region src/charts/TreemapChart.tsx
|
|
13
|
-
/**
|
|
14
|
-
* 矩形树图:一个总量按层级分块(销售额按区域 → 供应商、费用按科目),块的面积是数值,一眼看出谁占得多。
|
|
15
|
-
*
|
|
16
|
-
* <TreemapChart
|
|
17
|
-
* aria-label="采购额构成"
|
|
18
|
-
* data={savings}
|
|
19
|
-
* groups={['region', 'supplier']} // 从外到内的层级
|
|
20
|
-
* value="amount"
|
|
21
|
-
* unit="万元"
|
|
22
|
-
* />
|
|
23
|
-
*
|
|
24
|
-
* 数据是扁平的明细,按 groups 的字段逐级分组(Unovis 的 Treemap);同一个顶层分组同一种颜色(按色板),
|
|
25
|
-
* 里面的块按亮度区分;点图例隐藏一个顶层分组,占比按剩下的算。悬停看路径、数值与占比;读屏的数据表列出每一块。
|
|
26
|
-
*/
|
|
27
16
|
/** 未声明的属性落到外层 div(role="figure")上;aria-label 是图表的名称 */
|
|
28
17
|
/** TreemapChart 的样式 */
|
|
29
|
-
var treemapChartStyles = css.create({
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
18
|
+
var treemapChartStyles = css.create({
|
|
19
|
+
skeleton: {
|
|
20
|
+
display: "block",
|
|
21
|
+
width: "100%",
|
|
22
|
+
height: "100%"
|
|
23
|
+
},
|
|
24
|
+
group: { stroke: "none" },
|
|
25
|
+
tile: {
|
|
26
|
+
stroke: "_surface",
|
|
27
|
+
strokeWidth: 0,
|
|
28
|
+
transition: `stroke-width ${t.durations.sm} ${t.easings.standard}`
|
|
29
|
+
},
|
|
30
|
+
tileActive: {
|
|
31
|
+
stroke: "_text",
|
|
32
|
+
strokeWidth: 1.5
|
|
33
|
+
},
|
|
34
|
+
name: {
|
|
35
|
+
fill: "_text",
|
|
36
|
+
fontSize: "_xs",
|
|
37
|
+
fontWeight: "_medium"
|
|
38
|
+
},
|
|
39
|
+
value: {
|
|
40
|
+
fill: "_text",
|
|
41
|
+
fontSize: "_xs",
|
|
42
|
+
opacity: .75
|
|
43
|
+
},
|
|
44
|
+
groupName: {
|
|
45
|
+
fill: "_textMuted",
|
|
46
|
+
fontSize: "_xs",
|
|
47
|
+
fontWeight: "_semibold"
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
/** 块与块之间、分组四周的空(px);写分组名时分组顶边留的空 */
|
|
51
|
+
var PADDING = 3;
|
|
52
|
+
var GROUP_TITLE = 20;
|
|
53
|
+
/** 块里的文字离边的距离 */
|
|
54
|
+
var INSET = 6;
|
|
55
|
+
/** 悬停的块:叶子的位置、提示框的位置 */
|
|
34
56
|
/** 矩形树图 */
|
|
35
57
|
var TreemapChart = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
36
58
|
const _cache = [];
|
|
@@ -48,6 +70,8 @@ var TreemapChart = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
48
70
|
const rest = restProps(_props, _ctx, true);
|
|
49
71
|
const lang = useLang();
|
|
50
72
|
const format = useFormat();
|
|
73
|
+
const area = usePlotArea();
|
|
74
|
+
let hovered = shallowRef(null);
|
|
51
75
|
const numberOf = (row) => {
|
|
52
76
|
const amount = seriesValue(row, value.value);
|
|
53
77
|
return amount === void 0 ? 0 : Math.max(toNumber(amount), 0);
|
|
@@ -65,46 +89,133 @@ var TreemapChart = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
65
89
|
maximumFractionDigits: 1
|
|
66
90
|
});
|
|
67
91
|
/** 一个节点的路径:从顶层到它 */
|
|
68
|
-
const pathOf = (node) =>
|
|
92
|
+
const pathOf = (node) => {
|
|
93
|
+
const keys = [];
|
|
94
|
+
for (let item = node; item?.parent; item = item.parent) keys.unshift(item.key);
|
|
95
|
+
return keys.join(" / ");
|
|
96
|
+
};
|
|
97
|
+
/** 顶层分组 */
|
|
98
|
+
const topOfNode = (node) => {
|
|
99
|
+
let item = node;
|
|
100
|
+
while (item.parent?.parent) item = item.parent;
|
|
101
|
+
return item;
|
|
102
|
+
};
|
|
69
103
|
function toggle(key) {
|
|
70
104
|
hiddenGroups.value = hiddenGroups.value.includes(key) ? hiddenGroups.value.filter((item) => item !== key) : [...hiddenGroups.value, key];
|
|
71
105
|
}
|
|
72
|
-
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
+
/** 布局:分组与叶子(叶子按出现的顺序编号,悬停按它找) */
|
|
107
|
+
const layout = computed(() => {
|
|
108
|
+
const root = groupTree(visible.value, groups.value.map((key) => (row) => String(row[key] ?? "")), numberOf);
|
|
109
|
+
treemapLayout(root, {
|
|
110
|
+
width: area.width,
|
|
111
|
+
height: area.height,
|
|
112
|
+
padding: PADDING,
|
|
113
|
+
paddingTop: groupLabels.value ? GROUP_TITLE : PADDING
|
|
114
|
+
});
|
|
115
|
+
const branches = [];
|
|
116
|
+
const leaves = [];
|
|
117
|
+
const walk = (node) => {
|
|
118
|
+
for (const child of node.children ?? []) {
|
|
119
|
+
(child.children ? branches : leaves).push(child);
|
|
120
|
+
walk(child);
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
walk(root);
|
|
124
|
+
return {
|
|
125
|
+
branches,
|
|
126
|
+
leaves
|
|
127
|
+
};
|
|
128
|
+
});
|
|
129
|
+
/** 叶子的颜色:顶层分组的颜色,同一组里越靠后(数值越小)越浅 */
|
|
130
|
+
const leafColor = (node) => {
|
|
131
|
+
const base = topColors.value.get(topOfNode(node).key) ?? resolveColor(void 0, 0);
|
|
132
|
+
const siblings = node.parent?.children ?? [node];
|
|
133
|
+
const rank = siblings.indexOf(node) / Math.max(siblings.length, 1);
|
|
134
|
+
return `color-mix(in srgb, ${base} ${Math.round(62 - rank * 30)}%, ${colors.surface})`;
|
|
135
|
+
};
|
|
136
|
+
function onPointermove(event) {
|
|
137
|
+
const target = event.target.closest?.("[data-leaf]");
|
|
138
|
+
if (!target) {
|
|
139
|
+
hovered.value = null;
|
|
140
|
+
return;
|
|
106
141
|
}
|
|
107
|
-
|
|
142
|
+
const { x, y } = pointerIn(event, event.currentTarget);
|
|
143
|
+
hovered.value = {
|
|
144
|
+
index: Number(target.getAttribute("data-leaf")),
|
|
145
|
+
x,
|
|
146
|
+
y
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
function renderPlot() {
|
|
150
|
+
const { branches, leaves } = layout.value;
|
|
151
|
+
return createVNode("svg", mergeJsxProps({
|
|
152
|
+
"width": area.width,
|
|
153
|
+
"height": area.height
|
|
154
|
+
}, css.attrs(chartSvgStyles.svg), {
|
|
155
|
+
"onPointermove": onPointermove,
|
|
156
|
+
"onPointerleave": _cache[0] || (_cache[0] = () => hovered.value = null)
|
|
157
|
+
}), [createVNode("g", css.attrs(chartSvgStyles.fadeIn), [branches.map((node) => {
|
|
158
|
+
const base = topColors.value.get(topOfNode(node).key) ?? resolveColor(void 0, 0);
|
|
159
|
+
const width = node.x1 - node.x0;
|
|
160
|
+
return createVNode("g", { "key": `group:${pathOf(node)}` }, [createVNode("rect", mergeJsxProps({
|
|
161
|
+
"x": svgNumber(node.x0),
|
|
162
|
+
"y": svgNumber(node.y0),
|
|
163
|
+
"width": svgNumber(width),
|
|
164
|
+
"height": svgNumber(node.y1 - node.y0),
|
|
165
|
+
"rx": 4
|
|
166
|
+
}, css.attrs(treemapChartStyles.group, chartSvgStyles.fill(`color-mix(in srgb, ${base} 14%, transparent)`))), null), groupLabels.value && width > 40 && createVNode("text", mergeJsxProps({
|
|
167
|
+
"x": svgNumber(node.x0 + INSET),
|
|
168
|
+
"y": svgNumber(node.y0 + GROUP_TITLE / 2 + 1),
|
|
169
|
+
"dominant-baseline": "central"
|
|
170
|
+
}, css.attrs(treemapChartStyles.groupName, chartSvgStyles.noPointer)), [truncateText(node.key, width - 12)])]);
|
|
171
|
+
}), leaves.map((node, index) => {
|
|
172
|
+
const width = node.x1 - node.x0;
|
|
173
|
+
const height = node.y1 - node.y0;
|
|
174
|
+
const name = truncateText(node.key, width - 12);
|
|
175
|
+
const showName = height >= 22 && width >= Math.min(estimateTextWidth(node.key), 36) + 12;
|
|
176
|
+
const amount = valueText(node.value);
|
|
177
|
+
const showValue = showName && height >= 40 && estimateTextWidth(amount) <= width - 12;
|
|
178
|
+
return createVNode("g", { "key": `leaf:${pathOf(node)}:${index}` }, [
|
|
179
|
+
createVNode("rect", mergeJsxProps({
|
|
180
|
+
"data-part": "tile",
|
|
181
|
+
"data-leaf": index,
|
|
182
|
+
"x": svgNumber(node.x0),
|
|
183
|
+
"y": svgNumber(node.y0),
|
|
184
|
+
"width": svgNumber(width),
|
|
185
|
+
"height": svgNumber(height),
|
|
186
|
+
"rx": 3
|
|
187
|
+
}, css.attrs(treemapChartStyles.tile, chartSvgStyles.fill(leafColor(node)), hovered.value?.index === index && treemapChartStyles.tileActive)), null),
|
|
188
|
+
showName && createVNode("text", mergeJsxProps({
|
|
189
|
+
"x": svgNumber(node.x0 + INSET),
|
|
190
|
+
"y": svgNumber(node.y0 + INSET + 7),
|
|
191
|
+
"dominant-baseline": "central"
|
|
192
|
+
}, css.attrs(treemapChartStyles.name, chartSvgStyles.noPointer)), [name]),
|
|
193
|
+
showValue && createVNode("text", mergeJsxProps({
|
|
194
|
+
"x": svgNumber(node.x0 + INSET),
|
|
195
|
+
"y": svgNumber(node.y0 + INSET + 24),
|
|
196
|
+
"dominant-baseline": "central"
|
|
197
|
+
}, css.attrs(treemapChartStyles.value, chartSvgStyles.noPointer)), [amount])
|
|
198
|
+
]);
|
|
199
|
+
})])]);
|
|
200
|
+
}
|
|
201
|
+
const tip = () => {
|
|
202
|
+
const current = hovered.value;
|
|
203
|
+
const node = current && layout.value.leaves[current.index];
|
|
204
|
+
if (!tooltip.value || !current || !node) return null;
|
|
205
|
+
return {
|
|
206
|
+
x: current.x,
|
|
207
|
+
y: current.y,
|
|
208
|
+
content: chartTooltip(pathOf(node), [{
|
|
209
|
+
label: lang.chartValue,
|
|
210
|
+
color: topColors.value.get(topOfNode(node).key) ?? "transparent",
|
|
211
|
+
value: valueText(node.value)
|
|
212
|
+
}, {
|
|
213
|
+
label: lang.chartShare,
|
|
214
|
+
color: "transparent",
|
|
215
|
+
value: shareText(node.value)
|
|
216
|
+
}])
|
|
217
|
+
};
|
|
218
|
+
};
|
|
108
219
|
return () => createVNode(ChartFrame, mergeJsxProps(rest, {
|
|
109
220
|
"unit": unit.value,
|
|
110
221
|
"height": height.value,
|
|
@@ -130,7 +241,12 @@ var TreemapChart = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
130
241
|
cells: [valueText(seriesValue(row, value.value) ?? 0), shareText(numberOf(row))]
|
|
131
242
|
}))
|
|
132
243
|
},
|
|
133
|
-
"plotRef":
|
|
244
|
+
"plotRef": area.ref,
|
|
245
|
+
"plot": area.ready && visible.value.length > 0 ? [renderPlot(), createVNode(ChartTooltipBox, {
|
|
246
|
+
"key": "tooltip",
|
|
247
|
+
"tip": tip(),
|
|
248
|
+
"width": area.width
|
|
249
|
+
}, null)] : null
|
|
134
250
|
}), null);
|
|
135
251
|
}, {
|
|
136
252
|
name: "TreemapChart",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TreemapChart.js","names":["_vtp_readonlyProp","_vtp_bindable","_vtp_restProps","_vue_computed","_vue_shallowRef","_createVNode","_mergeProps"],"sources":["../../src/charts/TreemapChart.tsx"],"sourcesContent":["/**\n * 矩形树图:一个总量按层级分块(销售额按区域 → 供应商、费用按科目),块的面积是数值,一眼看出谁占得多。\n *\n * <TreemapChart\n * aria-label=\"采购额构成\"\n * data={savings}\n * groups={['region', 'supplier']} // 从外到内的层级\n * value=\"amount\"\n * unit=\"万元\"\n * />\n *\n * 数据是扁平的明细,按 groups 的字段逐级分组(Unovis 的 Treemap);同一个顶层分组同一种颜色(按色板),\n * 里面的块按亮度区分;点图例隐藏一个顶层分组,占比按剩下的算。悬停看路径、数值与占比;读屏的数据表列出每一块。\n */\nimport { Treemap, type TreemapNode } 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 { chartTooltip, ChartFrame, type ChartFrameParts } from './frame';\nimport { resolveColor, seriesValue, type NumericKey } from './series';\nimport { useSingleChart, type SingleChart } from './single';\n\n/** 未声明的属性落到外层 div(role=\"figure\")上;aria-label 是图表的名称 */\nexport interface TreemapChartProps<T extends object>\n extends PartProps<'div', 'role' | 'aria-busy'>, ChartFrameParts {\n /** 明细 */\n data: readonly T[];\n /** 分组的字段,从外到内(区域、供应商) */\n groups: readonly (keyof T & string)[];\n /** 面积的数值字段 */\n value: NumericKey<T>;\n /** 数值的格式 */\n valueFormat?: NumericFormat;\n /** 单位:显示在左上方,提示框与数据表的数值后面带上 */\n unit?: string;\n /**\n * 绘图区的高度;数字按 px\n * @default 300\n */\n height?: number | string;\n /** 外层分组也写名字(块够大时) */\n groupLabels?: boolean;\n /** 隐藏的顶层分组:点图例时写回,父级可以写 _hiddenGroups={keys} */\n _hiddenGroups?: string[];\n /** 加载中:绘图区显示骨架 */\n loading?: boolean;\n /** 没有数据时显示的内容 */\n empty?: VNodeChild;\n /** 悬停时的提示框,默认 true */\n tooltip?: boolean;\n}\n\n/** TreemapChart 的样式 */\nexport const treemapChartStyles = css.create({\n skeleton: { display: 'block', width: '100%', height: '100%' },\n});\n\n/** 矩形树图 */\nexport const TreemapChart = _component(\n <T extends object>({\n data,\n groups,\n value,\n valueFormat,\n unit,\n height = 300,\n groupLabels = false,\n _hiddenGroups: hiddenGroups = _bindable<string[]>([]),\n loading = false,\n empty,\n tooltip = true,\n ...rest\n }: TreemapChartProps<T>) => {\n const lang = useLang();\n const format = useFormat();\n const numberOf = (row: T) => {\n const amount = seriesValue(row, value);\n return amount === undefined ? 0 : Math.max(toNumber(amount), 0);\n };\n const topOf = (row: T) => String(row[groups[0]!] ?? '');\n const visible = _derived(data.filter((row) => !hiddenGroups.includes(topOf(row))));\n const total = _derived(visible.reduce((sum, row) => sum + numberOf(row), 0));\n // 顶层分组的颜色:按第一次出现的顺序取色板\n const topColors = _derived(\n new Map(\n [...new Set(data.map(topOf))].map((key, position) => [\n key,\n resolveColor(undefined, position),\n ]),\n ),\n );\n const valueText = (amount: NumericValue) =>\n format.number(amount, unit ? { ...valueFormat, suffix: unit } : valueFormat);\n const shareText = (amount: number) =>\n total === 0\n ? lang.emptyValue\n : format.number(amount / total, { style: 'percent', maximumFractionDigits: 1 });\n /** 一个节点的路径:从顶层到它 */\n const pathOf = (node: TreemapNode<T>) =>\n node\n .ancestors()\n .reverse()\n .slice(1)\n .map((item) => item.data.key ?? '')\n .join(' / ');\n\n function toggle(key: string) {\n hiddenGroups = hiddenGroups.includes(key)\n ? hiddenGroups.filter((item) => item !== key)\n : [...hiddenGroups, key];\n }\n\n let plot = _state.raw<HTMLElement | null>(null);\n const chart: SingleChart = useSingleChart<T[]>(() => ({\n element: plot,\n ready: !loading && visible.length > 0,\n tooltip,\n build: (instance) => {\n const treemap = instance('treemap', () => new Treemap<T>({ layers: [] }));\n treemap.setConfig({\n value: numberOf,\n layers: groups.map((key) => (row: T) => String(row[key] ?? '')),\n // 外层分组是浅色的底,叶子是实色:同一组里的块靠底色的缝隙分开\n tileColor: (node) => {\n const base =\n topColors.get(node.topLevelParent?.data.key ?? '') ?? resolveColor(undefined, 0);\n return node.children ? `color-mix(in srgb, ${base} 18%, transparent)` : base;\n },\n tileLabel: (node) => node.data.key ?? '',\n labelInternalNodes: groupLabels,\n enableTileLabelFontSizeVariation: false,\n tilePadding: 3,\n // 分组的名字写在顶边留出的地方\n tilePaddingTop: groupLabels ? 20 : undefined,\n tileBorderRadius: 4,\n });\n return {\n component: treemap,\n data: [...visible],\n triggers: {\n [Treemap.selectors.tile]: (node: TreemapNode<T>) =>\n chart.tooltip(\n chartTooltip(pathOf(node), [\n {\n label: lang.chartValue,\n color: topColors.get(node.topLevelParent?.data.key ?? '') ?? 'transparent',\n value: valueText(node.value),\n },\n { label: lang.chartShare, color: 'transparent', value: shareText(node.value) },\n ]),\n ),\n },\n };\n },\n }));\n\n return () => (\n <ChartFrame\n {...rest}\n unit={unit}\n height={height}\n legend={[...topColors].map(([key, color]) => ({ key, label: key, color }))}\n hiddenKeys={hiddenGroups}\n onLegendToggle={toggle}\n loading={loading}\n skeleton={<Skeleton sx={treemapChartStyles.skeleton} />}\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((row) => ({\n header: groups.map((key) => String(row[key] ?? '')).join(' / '),\n cells: [valueText(seriesValue(row, value) ?? 0), shareText(numberOf(row))],\n })),\n }}\n plotRef={(element) => (plot = element)}\n />\n );\n },\n);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"TreemapChart.js","names":["_vtp_readonlyProp","_vtp_bindable","_vtp_restProps","_vue_shallowRef","_vue_computed","_createVNode","_mergeProps"],"sources":["../../src/charts/TreemapChart.tsx"],"sourcesContent":["/**\n * 矩形树图:一个总量按层级分块(销售额按区域 → 供应商、费用按科目),块的面积是数值,一眼看出谁占得多。\n *\n * <TreemapChart\n * aria-label=\"采购额构成\"\n * data={savings}\n * groups={['region', 'supplier']} // 从外到内的层级\n * value=\"amount\"\n * unit=\"万元\"\n * />\n *\n * 数据是扁平的明细,按 groups 的字段逐级分组(布局见 groupTree、treemapLayout,squarified 算法);\n * 同一个顶层分组同一种颜色(按色板),里面的块按深浅区分;点图例隐藏一个顶层分组,占比按剩下的算。\n * 块够大时写名字与数值;悬停看路径、数值与占比;读屏的数据表列出每一块。\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 { colors, t } from '../config/theme/tokens';\nimport { svgNumber } from '../utils/data/number';\nimport { chartTooltip, ChartFrame, type ChartFrameParts } from './frame';\nimport { groupTree, treemapLayout, type TreemapNode } from './layout/treemap';\nimport { ChartTooltipBox, chartSvgStyles, pointerIn, usePlotArea, type ChartTip } from './plot';\nimport { resolveColor, seriesValue, type NumericKey } from './series';\nimport { estimateTextWidth, truncateText } from './text-size';\n\n/** 未声明的属性落到外层 div(role=\"figure\")上;aria-label 是图表的名称 */\nexport interface TreemapChartProps<T extends object>\n extends PartProps<'div', 'role' | 'aria-busy'>, ChartFrameParts {\n /** 明细 */\n data: readonly T[];\n /** 分组的字段,从外到内(区域、供应商) */\n groups: readonly (keyof T & string)[];\n /** 面积的数值字段 */\n value: NumericKey<T>;\n /** 数值的格式 */\n valueFormat?: NumericFormat;\n /** 单位:显示在左上方,提示框与数据表的数值后面带上 */\n unit?: string;\n /**\n * 绘图区的高度;数字按 px\n * @default 300\n */\n height?: number | string;\n /** 外层分组也写名字(块够大时) */\n groupLabels?: boolean;\n /** 隐藏的顶层分组:点图例时写回,父级可以写 _hiddenGroups={keys} */\n _hiddenGroups?: string[];\n /** 加载中:绘图区显示骨架 */\n loading?: boolean;\n /** 没有数据时显示的内容 */\n empty?: VNodeChild;\n /** 悬停时的提示框,默认 true */\n tooltip?: boolean;\n}\n\n/** TreemapChart 的样式 */\nexport const treemapChartStyles = css.create({\n skeleton: { display: 'block', width: '100%', height: '100%' },\n group: { stroke: 'none' },\n tile: {\n stroke: '_surface',\n strokeWidth: 0,\n transition: `stroke-width ${t.durations.sm} ${t.easings.standard}`,\n },\n // 悬停的块描一圈正文色\n tileActive: { stroke: '_text', strokeWidth: 1.5 },\n name: { fill: '_text', fontSize: '_xs', fontWeight: '_medium' },\n value: { fill: '_text', fontSize: '_xs', opacity: 0.75 },\n groupName: { fill: '_textMuted', fontSize: '_xs', fontWeight: '_semibold' },\n});\n\n/** 块与块之间、分组四周的空(px);写分组名时分组顶边留的空 */\nconst PADDING = 3;\nconst GROUP_TITLE = 20;\n/** 块里的文字离边的距离 */\nconst INSET = 6;\n\n/** 悬停的块:叶子的位置、提示框的位置 */\ninterface TreemapHover {\n index: number;\n x: number;\n y: number;\n}\n\n/** 矩形树图 */\nexport const TreemapChart = _component(\n <T extends object>({\n data,\n groups,\n value,\n valueFormat,\n unit,\n height = 300,\n groupLabels = false,\n _hiddenGroups: hiddenGroups = _bindable<string[]>([]),\n loading = false,\n empty,\n tooltip = true,\n ...rest\n }: TreemapChartProps<T>) => {\n const lang = useLang();\n const format = useFormat();\n const area = usePlotArea();\n let hovered = _state.raw<TreemapHover | null>(null);\n const numberOf = (row: T) => {\n const amount = seriesValue(row, value);\n return amount === undefined ? 0 : Math.max(toNumber(amount), 0);\n };\n const topOf = (row: T) => String(row[groups[0]!] ?? '');\n const visible = _derived(data.filter((row) => !hiddenGroups.includes(topOf(row))));\n const total = _derived(visible.reduce((sum, row) => sum + numberOf(row), 0));\n // 顶层分组的颜色:按第一次出现的顺序取色板\n const topColors = _derived(\n new Map(\n [...new Set(data.map(topOf))].map((key, position) => [\n key,\n resolveColor(undefined, position),\n ]),\n ),\n );\n const valueText = (amount: NumericValue) =>\n format.number(amount, unit ? { ...valueFormat, suffix: unit } : valueFormat);\n const shareText = (amount: number) =>\n total === 0\n ? lang.emptyValue\n : format.number(amount / total, { style: 'percent', maximumFractionDigits: 1 });\n /** 一个节点的路径:从顶层到它 */\n const pathOf = (node: TreemapNode<T>) => {\n const keys: string[] = [];\n for (let item: TreemapNode<T> | undefined = node; item?.parent; item = item.parent)\n keys.unshift(item.key);\n return keys.join(' / ');\n };\n /** 顶层分组 */\n const topOfNode = (node: TreemapNode<T>) => {\n let item = node;\n while (item.parent?.parent) item = item.parent;\n return item;\n };\n\n function toggle(key: string) {\n hiddenGroups = hiddenGroups.includes(key)\n ? hiddenGroups.filter((item) => item !== key)\n : [...hiddenGroups, key];\n }\n\n /** 布局:分组与叶子(叶子按出现的顺序编号,悬停按它找) */\n const layout = _derived.by(() => {\n const root = groupTree(\n visible,\n groups.map((key) => (row: T) => String(row[key] ?? '')),\n numberOf,\n );\n treemapLayout(root, {\n width: area.width,\n height: area.height,\n padding: PADDING,\n paddingTop: groupLabels ? GROUP_TITLE : PADDING,\n });\n const branches: TreemapNode<T>[] = [];\n const leaves: TreemapNode<T>[] = [];\n const walk = (node: TreemapNode<T>) => {\n for (const child of node.children ?? []) {\n (child.children ? branches : leaves).push(child);\n walk(child);\n }\n };\n walk(root);\n return { branches, leaves };\n });\n\n /** 叶子的颜色:顶层分组的颜色,同一组里越靠后(数值越小)越浅 */\n const leafColor = (node: TreemapNode<T>) => {\n const base = topColors.get(topOfNode(node).key) ?? resolveColor(undefined, 0);\n const siblings = node.parent?.children ?? [node];\n const rank = siblings.indexOf(node) / Math.max(siblings.length, 1);\n return `color-mix(in srgb, ${base} ${Math.round(62 - rank * 30)}%, ${colors.surface})`;\n };\n\n function onPointermove(event: PointerEvent) {\n const target = (event.target as Element).closest?.('[data-leaf]');\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-leaf')), x, y };\n }\n\n function renderPlot() {\n const { branches, leaves } = layout;\n return (\n <svg\n width={area.width}\n height={area.height}\n {...css.attrs(chartSvgStyles.svg)}\n onPointermove={onPointermove}\n onPointerleave={() => (hovered = null)}\n >\n <g {...css.attrs(chartSvgStyles.fadeIn)}>\n {branches.map((node) => {\n const base = topColors.get(topOfNode(node).key) ?? resolveColor(undefined, 0);\n const width = node.x1 - node.x0;\n return (\n <g key={`group:${pathOf(node)}`}>\n <rect\n x={svgNumber(node.x0)}\n y={svgNumber(node.y0)}\n width={svgNumber(width)}\n height={svgNumber(node.y1 - node.y0)}\n rx={4}\n {...css.attrs(\n treemapChartStyles.group,\n chartSvgStyles.fill(`color-mix(in srgb, ${base} 14%, transparent)`),\n )}\n />\n {groupLabels && width > 40 && (\n <text\n x={svgNumber(node.x0 + INSET)}\n y={svgNumber(node.y0 + GROUP_TITLE / 2 + 1)}\n dominant-baseline=\"central\"\n {...css.attrs(treemapChartStyles.groupName, chartSvgStyles.noPointer)}\n >\n {truncateText(node.key, width - INSET * 2)}\n </text>\n )}\n </g>\n );\n })}\n {leaves.map((node, index) => {\n const width = node.x1 - node.x0;\n const height = node.y1 - node.y0;\n const name = truncateText(node.key, width - INSET * 2);\n const showName =\n height >= 22 && width >= Math.min(estimateTextWidth(node.key), 36) + INSET * 2;\n const amount = valueText(node.value);\n const showValue =\n showName && height >= 40 && estimateTextWidth(amount) <= width - INSET * 2;\n return (\n <g key={`leaf:${pathOf(node)}:${index}`}>\n <rect\n data-part=\"tile\"\n data-leaf={index}\n x={svgNumber(node.x0)}\n y={svgNumber(node.y0)}\n width={svgNumber(width)}\n height={svgNumber(height)}\n rx={3}\n {...css.attrs(\n treemapChartStyles.tile,\n chartSvgStyles.fill(leafColor(node)),\n hovered?.index === index && treemapChartStyles.tileActive,\n )}\n />\n {showName && (\n <text\n x={svgNumber(node.x0 + INSET)}\n y={svgNumber(node.y0 + INSET + 7)}\n dominant-baseline=\"central\"\n {...css.attrs(treemapChartStyles.name, chartSvgStyles.noPointer)}\n >\n {name}\n </text>\n )}\n {showValue && (\n <text\n x={svgNumber(node.x0 + INSET)}\n y={svgNumber(node.y0 + INSET + 24)}\n dominant-baseline=\"central\"\n {...css.attrs(treemapChartStyles.value, chartSvgStyles.noPointer)}\n >\n {amount}\n </text>\n )}\n </g>\n );\n })}\n </g>\n </svg>\n );\n }\n\n const tip = (): ChartTip | null => {\n const current = hovered;\n const node = current && layout.leaves[current.index];\n if (!tooltip || !current || !node) return null;\n return {\n x: current.x,\n y: current.y,\n content: chartTooltip(pathOf(node), [\n {\n label: lang.chartValue,\n color: topColors.get(topOfNode(node).key) ?? 'transparent',\n value: valueText(node.value),\n },\n { label: lang.chartShare, color: 'transparent', value: shareText(node.value) },\n ]),\n };\n };\n\n return () => (\n <ChartFrame\n {...rest}\n unit={unit}\n height={height}\n legend={[...topColors].map(([key, color]) => ({ key, label: key, color }))}\n hiddenKeys={hiddenGroups}\n onLegendToggle={toggle}\n loading={loading}\n skeleton={<Skeleton sx={treemapChartStyles.skeleton} />}\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((row) => ({\n header: groups.map((key) => String(row[key] ?? '')).join(' / '),\n cells: [valueText(seriesValue(row, value) ?? 0), shareText(numberOf(row))],\n })),\n }}\n plotRef={area.ref}\n plot={\n area.ready && visible.length > 0\n ? [renderPlot(), <ChartTooltipBox key=\"tooltip\" tip={tip()} width={area.width} />]\n : null\n }\n />\n );\n },\n);\n"],"mappings":"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"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { css } from '@zerodep-dev/vue-tsx-pro-tsx/styles';
|
|
1
2
|
import { type NumericValue } from '../utils/data/numeric';
|
|
2
3
|
import { type ChartColor } from './series';
|
|
3
4
|
import { type XYChartProps } from './XYChart';
|
|
@@ -10,7 +11,7 @@ export interface WaterfallStep {
|
|
|
10
11
|
/** 合计柱:从 0 画到当时的累计 */
|
|
11
12
|
total?: boolean;
|
|
12
13
|
}
|
|
13
|
-
/** 画图用的一行(waterfallRows 算出):柱子从 low 画到 high
|
|
14
|
+
/** 画图用的一行(waterfallRows 算出):柱子从 low 画到 high */
|
|
14
15
|
export interface WaterfallRow {
|
|
15
16
|
/** 名称 */
|
|
16
17
|
label: string;
|
|
@@ -20,12 +21,6 @@ export interface WaterfallRow {
|
|
|
20
21
|
change: NumericValue | undefined;
|
|
21
22
|
/** 这一步之后的累计 */
|
|
22
23
|
cumulative: number;
|
|
23
|
-
/** 透明的垫底:柱子悬在上一步的累计处 */
|
|
24
|
-
base: number;
|
|
25
|
-
/** 看得见的一段 */
|
|
26
|
-
bar: number;
|
|
27
|
-
/** 跨过 0 时负的那一段 */
|
|
28
|
-
below: number;
|
|
29
24
|
/** 柱子的下沿 */
|
|
30
25
|
low: number;
|
|
31
26
|
/** 柱子的上沿 */
|
|
@@ -55,6 +50,10 @@ export interface WaterfallChartProps extends Omit<XYChartProps<WaterfallRow>, 'd
|
|
|
55
50
|
/** 点了一步(下钻):参数是这一步与它的位置;写了它时指针是手形 */
|
|
56
51
|
onItemClick?: (step: WaterfallStep, index: number) => void;
|
|
57
52
|
}
|
|
53
|
+
/** WaterfallChart 的样式 */
|
|
54
|
+
export declare const waterfallChartStyles: Readonly<{
|
|
55
|
+
connector: css.CompiledStyles;
|
|
56
|
+
}>;
|
|
58
57
|
/** 各步算成画图用的行:累计、每根柱子的上下沿 */
|
|
59
58
|
export declare function waterfallRows(steps: readonly WaterfallStep[]): WaterfallRow[];
|
|
60
59
|
/** 瀑布图 */
|
|
@@ -1,36 +1,25 @@
|
|
|
1
1
|
import { toNumber } from "../utils/data/numeric.js";
|
|
2
2
|
import { useLang } from "../config/context.js";
|
|
3
3
|
import { useFormat } from "../config/lang/format.js";
|
|
4
|
+
import { chartSvgStyles } from "./plot.js";
|
|
5
|
+
import { chartBar, chartMarkStyles } from "./marks.js";
|
|
4
6
|
import { useChartValueText } from "./frame.js";
|
|
5
7
|
import { resolveColor } from "./series.js";
|
|
6
8
|
import { XYChart } from "./XYChart.js";
|
|
7
9
|
import { computed, createVNode } from "vue";
|
|
8
10
|
import { _component, mergeJsxProps, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
9
|
-
import {
|
|
11
|
+
import { css } from "@zerodep-dev/vue-tsx-pro-tsx/styles";
|
|
10
12
|
//#region src/charts/WaterfallChart.tsx
|
|
11
|
-
/**
|
|
12
|
-
* 瀑布图:从起点出发,一步步加减到终点,看总数是怎么变过来的(测算两版之间各参数的影响、预算到实际的差异拆解)。
|
|
13
|
-
*
|
|
14
|
-
* <WaterfallChart
|
|
15
|
-
* aria-label="V1 到 V2 的变化"
|
|
16
|
-
* steps={[
|
|
17
|
-
* { label: 'V1 测算', value: 120, total: true },
|
|
18
|
-
* { label: '材料单价', value: -18 },
|
|
19
|
-
* { label: '工时', value: 6.5 },
|
|
20
|
-
* { label: 'V2 测算', total: true },
|
|
21
|
-
* ]}
|
|
22
|
-
* unit="万元"
|
|
23
|
-
* />
|
|
24
|
-
*
|
|
25
|
-
* 每一步是 { label, value }:value 是这一步的变动(正数往上、负数往下),可以是 Decimal、bigint;
|
|
26
|
-
* total: true 的是合计柱,从 0 画到当时的累计(写了 value 时以它为准,作为新的起点,如“V1 测算”)。
|
|
27
|
-
* 往上的步用 success 色、往下的用 danger 色、合计用 chart1(increaseColor、decreaseColor、totalColor 可改:
|
|
28
|
-
* 成本类的图里减少是好事时对调)。提示框与读屏的数据表列出变动与累计。
|
|
29
|
-
* 坐标轴、目标线、下钻、加载与空状态同 BarChart(见 XYChart)。
|
|
30
|
-
*/
|
|
31
13
|
/** 瀑布图的一步 */
|
|
32
|
-
/** 画图用的一行(waterfallRows 算出):柱子从 low 画到 high
|
|
14
|
+
/** 画图用的一行(waterfallRows 算出):柱子从 low 画到 high */
|
|
33
15
|
/** 未声明的属性落到外层 div(role="figure")上;aria-label 是图表的名称 */
|
|
16
|
+
/** WaterfallChart 的样式 */
|
|
17
|
+
var waterfallChartStyles = css.create({ connector: {
|
|
18
|
+
stroke: "_borderStrong",
|
|
19
|
+
strokeWidth: 1,
|
|
20
|
+
strokeDasharray: "2 2",
|
|
21
|
+
pointerEvents: "none"
|
|
22
|
+
} });
|
|
34
23
|
/** 各步算成画图用的行:累计、每根柱子的上下沿 */
|
|
35
24
|
function waterfallRows(steps) {
|
|
36
25
|
let running = 0;
|
|
@@ -39,17 +28,11 @@ function waterfallRows(steps) {
|
|
|
39
28
|
if (step.total) running = step.value === void 0 ? running : toNumber(step.value);
|
|
40
29
|
else running += step.value === void 0 ? 0 : toNumber(step.value);
|
|
41
30
|
const [low, high] = before <= running ? [before, running] : [running, before];
|
|
42
|
-
const crosses = low < 0 && high > 0;
|
|
43
|
-
const base = crosses ? 0 : high <= 0 ? high : low;
|
|
44
|
-
const bar = crosses ? high : high <= 0 ? low - high : high - low;
|
|
45
31
|
return {
|
|
46
32
|
label: step.label,
|
|
47
33
|
kind: step.total ? "total" : running >= before ? "increase" : "decrease",
|
|
48
34
|
change: step.value,
|
|
49
35
|
cumulative: running,
|
|
50
|
-
base,
|
|
51
|
-
bar,
|
|
52
|
-
below: crosses ? low : 0,
|
|
53
36
|
low,
|
|
54
37
|
high
|
|
55
38
|
};
|
|
@@ -82,28 +65,40 @@ var WaterfallChart = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
82
65
|
const cumulativeText = (row) => format.number(row.cumulative, rest.valueFormat);
|
|
83
66
|
const kind = computed(() => ({
|
|
84
67
|
skeleton: "bars",
|
|
85
|
-
|
|
68
|
+
band: true,
|
|
86
69
|
domainValues: (list) => list.flatMap((row) => [row.low, row.high]),
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
(
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
70
|
+
render: (context) => {
|
|
71
|
+
const width = Math.min(context.step * .7, 56);
|
|
72
|
+
return createVNode("g", { "data-part": "bars" }, [context.rows.map((row, index) => {
|
|
73
|
+
const center = context.category(row, index);
|
|
74
|
+
const next = context.rows[index + 1];
|
|
75
|
+
const level = context.value(row.cumulative);
|
|
76
|
+
const connector = next && (context.horizontal ? {
|
|
77
|
+
x1: level,
|
|
78
|
+
x2: level,
|
|
79
|
+
y1: center + width / 2,
|
|
80
|
+
y2: context.category(next, index + 1) - width / 2
|
|
81
|
+
} : {
|
|
82
|
+
x1: center + width / 2,
|
|
83
|
+
x2: context.category(next, index + 1) - width / 2,
|
|
84
|
+
y1: level,
|
|
85
|
+
y2: level
|
|
86
|
+
});
|
|
87
|
+
const { d, grow } = chartBar(context, {
|
|
88
|
+
start: center - width / 2,
|
|
89
|
+
width,
|
|
90
|
+
low: row.low,
|
|
91
|
+
high: row.high,
|
|
92
|
+
radius: radius.value,
|
|
93
|
+
round: true,
|
|
94
|
+
down: row.kind === "decrease" || row.kind === "total" && row.cumulative < 0
|
|
95
|
+
});
|
|
96
|
+
return createVNode("g", { "key": index }, [connector && createVNode("line", mergeJsxProps(connector, css.attrs(waterfallChartStyles.connector)), null), createVNode("path", mergeJsxProps({
|
|
97
|
+
"data-part": "bar",
|
|
98
|
+
"data-index": index,
|
|
99
|
+
"d": d
|
|
100
|
+
}, css.attrs(chartMarkStyles.bar, grow, chartSvgStyles.fill(colorOf(row)))), null)]);
|
|
101
|
+
})]);
|
|
107
102
|
},
|
|
108
103
|
tooltipItems: (row) => [{
|
|
109
104
|
label: row.kind === "total" ? lang.chartTotal : lang.chartChange,
|
|
@@ -142,7 +137,7 @@ var WaterfallChart = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
142
137
|
"data": rows.value,
|
|
143
138
|
"index": "label",
|
|
144
139
|
"series": [{
|
|
145
|
-
key: "
|
|
140
|
+
key: "cumulative",
|
|
146
141
|
label: lang.chartChange
|
|
147
142
|
}],
|
|
148
143
|
"legend": false,
|
|
@@ -162,6 +157,6 @@ var WaterfallChart = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
162
157
|
]
|
|
163
158
|
});
|
|
164
159
|
//#endregion
|
|
165
|
-
export { WaterfallChart, waterfallRows };
|
|
160
|
+
export { WaterfallChart, waterfallChartStyles, waterfallRows };
|
|
166
161
|
|
|
167
162
|
//# sourceMappingURL=WaterfallChart.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WaterfallChart.js","names":["_vtp_readonlyProp","_vtp_restProps","_vue_computed","_createVNode","_mergeProps"],"sources":["../../src/charts/WaterfallChart.tsx"],"sourcesContent":["/**\n * 瀑布图:从起点出发,一步步加减到终点,看总数是怎么变过来的(测算两版之间各参数的影响、预算到实际的差异拆解)。\n *\n * <WaterfallChart\n * aria-label=\"V1 到 V2 的变化\"\n * steps={[\n * { label: 'V1 测算', value: 120, total: true },\n * { label: '材料单价', value: -18 },\n * { label: '工时', value: 6.5 },\n * { label: 'V2 测算', total: true },\n * ]}\n * unit=\"万元\"\n * />\n *\n * 每一步是 { label, value }:value 是这一步的变动(正数往上、负数往下),可以是 Decimal、bigint;\n * total: true 的是合计柱,从 0 画到当时的累计(写了 value 时以它为准,作为新的起点,如“V1 测算”)。\n * 往上的步用 success 色、往下的用 danger 色、合计用 chart1(increaseColor、decreaseColor、totalColor 可改:\n * 成本类的图里减少是好事时对调)。提示框与读屏的数据表列出变动与累计。\n * 坐标轴、目标线、下钻、加载与空状态同 BarChart(见 XYChart)。\n */\nimport { StackedBar } from '@unovis/ts';\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport { useLang } from '../config/context';\nimport { useFormat } from '../config/lang/format';\nimport { toNumber, type NumericValue } from '../utils/data/numeric';\nimport { useChartValueText } from './frame';\nimport { resolveColor, type ChartColor } from './series';\nimport { XYChart, type XYChartKind, type XYChartProps } from './XYChart';\n\n/** 瀑布图的一步 */\nexport interface WaterfallStep {\n /** 名称:类目轴的刻度 */\n label: string;\n /** 这一步的变动;合计柱写了时是新的累计(作为之后的起点) */\n value?: NumericValue;\n /** 合计柱:从 0 画到当时的累计 */\n total?: boolean;\n}\n\n/** 画图用的一行(waterfallRows 算出):柱子从 low 画到 high;跨过 0 的分成正负两段(Unovis 的堆叠正负分开叠) */\nexport interface WaterfallRow {\n /** 名称 */\n label: string;\n /** 这一步的种类:决定颜色 */\n kind: 'increase' | 'decrease' | 'total';\n /** 这一步的变动(原值,提示框与数据表按它格式化) */\n change: NumericValue | undefined;\n /** 这一步之后的累计 */\n cumulative: number;\n /** 透明的垫底:柱子悬在上一步的累计处 */\n base: number;\n /** 看得见的一段 */\n bar: number;\n /** 跨过 0 时负的那一段 */\n below: number;\n /** 柱子的下沿 */\n low: number;\n /** 柱子的上沿 */\n high: number;\n}\n\n/** 未声明的属性落到外层 div(role=\"figure\")上;aria-label 是图表的名称 */\nexport interface WaterfallChartProps extends Omit<\n XYChartProps<WaterfallRow>,\n 'data' | 'index' | 'series' | 'indexFormat' | 'legend' | 'hiddenSeries' | 'onItemClick'\n> {\n /** 各步:按顺序从左到右 */\n steps: readonly WaterfallStep[];\n /** 往上走的颜色,默认 success */\n increaseColor?: ChartColor;\n /** 往下走的颜色,默认 danger */\n decreaseColor?: ChartColor;\n /** 合计柱的颜色,默认 chart1 */\n totalColor?: ChartColor;\n /**\n * 柱子末端的圆角(px)\n * @default 2\n */\n radius?: number;\n /**\n * 数值轴从 0 起;false 时从接近最低的累计处起:合计很大、各步变动很小时变动也看得清\n * (合计柱被截短,读数看轴上的刻度)。写了 valueDomain 时以它为准\n * @default true\n */\n fromZero?: boolean;\n /** 点了一步(下钻):参数是这一步与它的位置;写了它时指针是手形 */\n onItemClick?: (step: WaterfallStep, index: number) => void;\n}\n\n/** 各步算成画图用的行:累计、每根柱子的上下沿 */\nexport function waterfallRows(steps: readonly WaterfallStep[]): WaterfallRow[] {\n let running = 0;\n return steps.map((step) => {\n const before = step.total ? 0 : running;\n if (step.total) running = step.value === undefined ? running : toNumber(step.value);\n else running += step.value === undefined ? 0 : toNumber(step.value);\n const [low, high] = before <= running ? [before, running] : [running, before];\n // 不跨 0:透明的一段垫到较近 0 的一端,看得见的一段补到另一端;跨 0:正负各一段,不垫底\n const crosses = low < 0 && high > 0;\n const base = crosses ? 0 : high <= 0 ? high : low;\n const bar = crosses ? high : high <= 0 ? low - high : high - low;\n return {\n label: step.label,\n kind: step.total ? 'total' : running >= before ? 'increase' : 'decrease',\n change: step.value,\n cumulative: running,\n base,\n bar,\n below: crosses ? low : 0,\n low,\n high,\n };\n });\n}\n\n/** 瀑布图 */\nexport const WaterfallChart = _component(\n ({\n steps,\n increaseColor,\n decreaseColor,\n totalColor,\n radius = 2,\n fromZero = true,\n onItemClick,\n ...rest\n }: WaterfallChartProps) => {\n const lang = useLang();\n const format = useFormat();\n const valueText = useChartValueText(() => ({ valueFormat: rest.valueFormat, unit: rest.unit }));\n const rows = _derived(waterfallRows(steps));\n const colorOf = (row: WaterfallRow) =>\n resolveColor(\n row.kind === 'total'\n ? (totalColor ?? 'chart1')\n : row.kind === 'increase'\n ? (increaseColor ?? 'success')\n : (decreaseColor ?? 'danger'),\n 0,\n );\n /** 变动:带正负号;suffix 时数值后面带单位(提示框) */\n const changeText = (row: WaterfallRow, suffix = false) =>\n row.change === undefined\n ? lang.emptyValue\n : format.number(row.change, {\n ...rest.valueFormat,\n signDisplay: 'exceptZero',\n ...(suffix && rest.unit ? { suffix: rest.unit } : {}),\n });\n const cumulativeText = (row: WaterfallRow) => format.number(row.cumulative, rest.valueFormat);\n\n const kind = _derived<XYChartKind<WaterfallRow>>({\n skeleton: 'bars',\n stacked: true,\n domainValues: (list) => list.flatMap((row) => [row.low, row.high]),\n itemSelector: StackedBar.selectors.bar,\n components: ({ x, instance, events }) => {\n const bars = instance('waterfall', () => new StackedBar<WaterfallRow>({ x, y: [] }));\n bars.setConfig({\n x,\n y: [(row) => row.base, (row) => row.bar, (row) => row.below],\n // 垫底的一段透明:看起来柱子悬在上一步的累计处\n color: (row: WaterfallRow, position: number) =>\n position === 0 ? 'transparent' : colorOf(row),\n roundedCorners: radius,\n barPadding: 0.3,\n barMaxWidth: 56,\n events,\n });\n return [bars];\n },\n tooltipItems: (row) => [\n {\n label: row.kind === 'total' ? lang.chartTotal : lang.chartChange,\n color: colorOf(row),\n value: row.kind === 'total' ? valueText(row.cumulative) : changeText(row, true),\n },\n ...(row.kind === 'total'\n ? []\n : [\n {\n label: lang.chartCumulative,\n color: 'transparent',\n value: valueText(row.cumulative),\n },\n ]),\n ],\n table: (list) => ({\n columns: [\n lang.chartCategory,\n rest.unit ? `${lang.chartChange}(${rest.unit})` : lang.chartChange,\n rest.unit ? `${lang.chartCumulative}(${rest.unit})` : lang.chartCumulative,\n ],\n rows: list.map((row) => ({\n header: row.label,\n cells: [row.kind === 'total' ? lang.emptyValue : changeText(row), cumulativeText(row)],\n })),\n }),\n });\n\n /**\n * 不从 0 起时轴的下沿:最低的累计再往下留出变动幅度的一半(没有变动时留出它的一成);\n * 有负的累计时仍按数据自动取,不截\n */\n const zoomedDomain = (): [number | undefined, undefined] => {\n const values = rows.flatMap((row) =>\n row.kind === 'total' ? [row.high] : [row.low, row.high],\n );\n const low = Math.min(...values);\n if (low <= 0) return [undefined, undefined];\n const span = Math.max(...values) - low || low * 0.1;\n return [Math.max(0, low - span * 0.5), undefined];\n };\n\n return () => (\n <XYChart<WaterfallRow>\n {...rest}\n valueDomain={\n rest.valueDomain ?? (fromZero || rows.length === 0 ? undefined : zoomedDomain())\n }\n data={rows}\n index=\"label\"\n // 系列只用来给十字线定位与算范围;画法、提示框、数据表由 kind 决定\n series={[{ key: 'bar', label: lang.chartChange }]}\n legend={false}\n kind={kind}\n onItemClick={onItemClick && ((_row, position) => onItemClick(steps[position]!, position))}\n />\n );\n },\n);\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0FA,SAAgB,cAAc,OAAiD;CAC7E,IAAI,UAAU;CACd,OAAO,MAAM,KAAK,SAAS;EACzB,MAAM,SAAS,KAAK,QAAQ,IAAI;EAChC,IAAI,KAAK,OAAO,UAAU,KAAK,UAAU,KAAA,IAAY,UAAU,SAAS,KAAK,KAAK;OAC7E,WAAW,KAAK,UAAU,KAAA,IAAY,IAAI,SAAS,KAAK,KAAK;EAClE,MAAM,CAAC,KAAK,QAAQ,UAAU,UAAU,CAAC,QAAQ,OAAO,IAAI,CAAC,SAAS,MAAM;EAE5E,MAAM,UAAU,MAAM,KAAK,OAAO;EAClC,MAAM,OAAO,UAAU,IAAI,QAAQ,IAAI,OAAO;EAC9C,MAAM,MAAM,UAAU,OAAO,QAAQ,IAAI,MAAM,OAAO,OAAO;EAC7D,OAAO;GACL,OAAO,KAAK;GACZ,MAAM,KAAK,QAAQ,UAAU,WAAW,SAAS,aAAa;GAC9D,QAAQ,KAAK;GACb,YAAY;GACZ;GACA;GACA,OAAO,UAAU,MAAM;GACvB;GACA;EACF;CACF,CAAC;AACH;;AAGA,IAAa,iBAAiB,0BAC5B,QAAA,SAS2B;CAAA,MAAA,QAAAA,aAAA,QAAA,OAAA;CAAA,MAAA,gBAAAA,aAAA,QAAA,eAAA;CAAA,MAAA,gBAAAA,aAAA,QAAA,eAAA;CAAA,MAAA,aAAAA,aAAA,QAAA,YAAA;CAAA,MAAA,SAAAA,aAAA,QAAA,UAJhB,CAAC;CAAA,MAAA,WAAAA,aAAA,QAAA,YACC,IAAI;CAAA,MAAA,cAAAA,aAAA,QAAA,aAAA;CAAA,MAEZ,OAAIC,UAAA,QAAA,MAAA,IAAA;CAEP,MAAM,OAAO,QAAQ;CACrB,MAAM,SAAS,UAAU;CACzB,MAAM,YAAY,yBAAyB;EAAE,aAAa,KAAK;EAAa,MAAM,KAAK;CAAK,EAAE;CAC9F,MAAM,OAAIC,eAAY,cAAa,MAAA,KAAM,CAAC;CAC1C,MAAM,WAAW,QACf,aACE,IAAI,SAAS,UACR,WAAA,SAAc,WACf,IAAI,SAAS,aACV,cAAA,SAAiB,YACjB,cAAA,SAAiB,UACxB,CACF;;CAEF,MAAM,cAAc,KAAmB,SAAS,UAC9C,IAAI,WAAW,KAAA,IACX,KAAK,aACL,OAAO,OAAO,IAAI,QAAQ;EACxB,GAAG,KAAK;EACR,aAAa;EACb,GAAI,UAAU,KAAK,OAAO,EAAE,QAAQ,KAAK,KAAK,IAAI,CAAC;CACrD,CAAC;CACP,MAAM,kBAAkB,QAAsB,OAAO,OAAO,IAAI,YAAY,KAAK,WAAW;CAE5F,MAAM,OAAIA,gBAAuC;EAC/C,UAAU;EACV,SAAS;EACT,eAAe,SAAS,KAAK,SAAS,QAAQ,CAAC,IAAI,KAAK,IAAI,IAAI,CAAC;EACjE,cAAc,WAAW,UAAU;EACnC,aAAa,EAAE,GAAG,UAAU,aAAa;GACvC,MAAM,OAAO,SAAS,mBAAmB,IAAI,WAAyB;IAAE;IAAG,GAAG,CAAA;GAAG,CAAC,CAAC;GACnF,KAAK,UAAU;IACb;IACA,GAAG;MAAE,QAAQ,IAAI;MAAO,QAAQ,IAAI;MAAM,QAAQ,IAAI;IAAK;IAE3D,QAAQ,KAAmB,aACzB,aAAa,IAAI,gBAAgB,QAAQ,GAAG;IAC9C,gBAAc,OAAA;IACd,YAAY;IACZ,aAAa;IACb;GACF,CAAC;GACD,OAAO,CAAC,IAAI;EACd;EACA,eAAe,QAAQ,CACrB;GACE,OAAO,IAAI,SAAS,UAAU,KAAK,aAAa,KAAK;GACrD,OAAO,QAAQ,GAAG;GAClB,OAAO,IAAI,SAAS,UAAU,UAAU,IAAI,UAAU,IAAI,WAAW,KAAK,IAAI;EAChF,GACA,GAAI,IAAI,SAAS,UACb,CAAA,IACA,CACE;GACE,OAAO,KAAK;GACZ,OAAO;GACP,OAAO,UAAU,IAAI,UAAU;EACjC,CAAC,CACD;EAER,QAAQ,UAAU;GAChB,SAAS;IACP,KAAK;IACL,KAAK,OAAO,GAAG,KAAK,YAAW,GAAI,KAAK,KAAI,KAAM,KAAK;IACvD,KAAK,OAAO,GAAG,KAAK,gBAAe,GAAI,KAAK,KAAI,KAAM,KAAK;GAAe;GAE5E,MAAM,KAAK,KAAK,SAAS;IACvB,QAAQ,IAAI;IACZ,OAAO,CAAC,IAAI,SAAS,UAAU,KAAK,aAAa,WAAW,GAAG,GAAG,eAAe,GAAG,CAAC;GACvF,EAAE;EACJ;CACF,EAAC;;;;;CAMD,MAAM,qBAAsD;EAC1D,MAAM,SAAS,KAAA,MAAK,SAAS,QAC3B,IAAI,SAAS,UAAU,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,KAAK,IAAI,IAAI,CACxD;EACA,MAAM,MAAM,KAAK,IAAI,GAAG,MAAM;EAC9B,IAAI,OAAO,GAAG,OAAO,CAAC,KAAA,GAAW,KAAA,CAAS;EAC1C,MAAM,OAAO,KAAK,IAAI,GAAG,MAAM,IAAI,OAAO,MAAM;EAChD,OAAO,CAAC,KAAK,IAAI,GAAG,MAAM,OAAO,EAAG,GAAG,KAAA,CAAS;CAClD;CAEA,aAAOC,YAAA,SAAAC,cAEC,MAAI;EAAA,eAEN,KAAK,gBAAgB,SAAA,SAAY,KAAA,MAAK,WAAW,IAAI,KAAA,IAAY,aAAa;EAAE,QAAA,KAAA;EAAA,SAAA;EAAA,UAK1E,CAAC;GAAE,KAAK;GAAO,OAAO,KAAK;EAAY,CAAC;EAAC,UACzC;EAAK,QAAA,KAAA;EAAA,eAEA,YAAA,WAAiB,MAAM,aAAa,YAAA,MAAY,MAAA,MAAM,WAAY,QAAQ;CAAE,CAAA,GAAA,IAAA;AAG/F,GAAC;CAAA,MAAA;CAAA,OAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;EAAA;CAAA;AAAA,CACH"}
|
|
1
|
+
{"version":3,"file":"WaterfallChart.js","names":["_vtp_readonlyProp","_vtp_restProps","_vue_computed","_createVNode","_mergeProps"],"sources":["../../src/charts/WaterfallChart.tsx"],"sourcesContent":["/**\n * 瀑布图:从起点出发,一步步加减到终点,看总数是怎么变过来的(测算两版之间各参数的影响、预算到实际的差异拆解)。\n *\n * <WaterfallChart\n * aria-label=\"V1 到 V2 的变化\"\n * steps={[\n * { label: 'V1 测算', value: 120, total: true },\n * { label: '材料单价', value: -18 },\n * { label: '工时', value: 6.5 },\n * { label: 'V2 测算', total: true },\n * ]}\n * unit=\"万元\"\n * />\n *\n * 每一步是 { label, value }:value 是这一步的变动(正数往上、负数往下),可以是 Decimal、bigint;\n * total: true 的是合计柱,从 0 画到当时的累计(写了 value 时以它为准,作为新的起点,如“V1 测算”)。\n * 往上的步用 success 色、往下的用 danger 色、合计用 chart1(increaseColor、decreaseColor、totalColor 可改:\n * 成本类的图里减少是好事时对调)。提示框与读屏的数据表列出变动与累计。\n * 坐标轴、目标线、下钻、加载与空状态同 BarChart(见 XYChart)。\n */\nimport { _component } from '@zerodep-dev/vue-tsx-pro-tsx';\nimport { css } from '@zerodep-dev/vue-tsx-pro-tsx/styles';\nimport { useLang } from '../config/context';\nimport { useFormat } from '../config/lang/format';\nimport { toNumber, type NumericValue } from '../utils/data/numeric';\nimport { useChartValueText } from './frame';\nimport { chartBar, chartMarkStyles } from './marks';\nimport { chartSvgStyles } from './plot';\nimport { resolveColor, type ChartColor } from './series';\nimport { XYChart, type XYChartKind, type XYChartProps } from './XYChart';\n\n/** 瀑布图的一步 */\nexport interface WaterfallStep {\n /** 名称:类目轴的刻度 */\n label: string;\n /** 这一步的变动;合计柱写了时是新的累计(作为之后的起点) */\n value?: NumericValue;\n /** 合计柱:从 0 画到当时的累计 */\n total?: boolean;\n}\n\n/** 画图用的一行(waterfallRows 算出):柱子从 low 画到 high */\nexport interface WaterfallRow {\n /** 名称 */\n label: string;\n /** 这一步的种类:决定颜色 */\n kind: 'increase' | 'decrease' | 'total';\n /** 这一步的变动(原值,提示框与数据表按它格式化) */\n change: NumericValue | undefined;\n /** 这一步之后的累计 */\n cumulative: number;\n /** 柱子的下沿 */\n low: number;\n /** 柱子的上沿 */\n high: number;\n}\n\n/** 未声明的属性落到外层 div(role=\"figure\")上;aria-label 是图表的名称 */\nexport interface WaterfallChartProps extends Omit<\n XYChartProps<WaterfallRow>,\n 'data' | 'index' | 'series' | 'indexFormat' | 'legend' | 'hiddenSeries' | 'onItemClick'\n> {\n /** 各步:按顺序从左到右 */\n steps: readonly WaterfallStep[];\n /** 往上走的颜色,默认 success */\n increaseColor?: ChartColor;\n /** 往下走的颜色,默认 danger */\n decreaseColor?: ChartColor;\n /** 合计柱的颜色,默认 chart1 */\n totalColor?: ChartColor;\n /**\n * 柱子末端的圆角(px)\n * @default 2\n */\n radius?: number;\n /**\n * 数值轴从 0 起;false 时从接近最低的累计处起:合计很大、各步变动很小时变动也看得清\n * (合计柱被截短,读数看轴上的刻度)。写了 valueDomain 时以它为准\n * @default true\n */\n fromZero?: boolean;\n /** 点了一步(下钻):参数是这一步与它的位置;写了它时指针是手形 */\n onItemClick?: (step: WaterfallStep, index: number) => void;\n}\n\n/** WaterfallChart 的样式 */\nexport const waterfallChartStyles = css.create({\n connector: {\n stroke: '_borderStrong',\n strokeWidth: 1,\n strokeDasharray: '2 2',\n pointerEvents: 'none',\n },\n});\n\n/** 各步算成画图用的行:累计、每根柱子的上下沿 */\nexport function waterfallRows(steps: readonly WaterfallStep[]): WaterfallRow[] {\n let running = 0;\n return steps.map((step) => {\n const before = step.total ? 0 : running;\n if (step.total) running = step.value === undefined ? running : toNumber(step.value);\n else running += step.value === undefined ? 0 : toNumber(step.value);\n const [low, high] = before <= running ? [before, running] : [running, before];\n return {\n label: step.label,\n kind: step.total ? 'total' : running >= before ? 'increase' : 'decrease',\n change: step.value,\n cumulative: running,\n low,\n high,\n };\n });\n}\n\n/** 瀑布图 */\nexport const WaterfallChart = _component(\n ({\n steps,\n increaseColor,\n decreaseColor,\n totalColor,\n radius = 2,\n fromZero = true,\n onItemClick,\n ...rest\n }: WaterfallChartProps) => {\n const lang = useLang();\n const format = useFormat();\n const valueText = useChartValueText(() => ({ valueFormat: rest.valueFormat, unit: rest.unit }));\n const rows = _derived(waterfallRows(steps));\n const colorOf = (row: WaterfallRow) =>\n resolveColor(\n row.kind === 'total'\n ? (totalColor ?? 'chart1')\n : row.kind === 'increase'\n ? (increaseColor ?? 'success')\n : (decreaseColor ?? 'danger'),\n 0,\n );\n /** 变动:带正负号;suffix 时数值后面带单位(提示框) */\n const changeText = (row: WaterfallRow, suffix = false) =>\n row.change === undefined\n ? lang.emptyValue\n : format.number(row.change, {\n ...rest.valueFormat,\n signDisplay: 'exceptZero',\n ...(suffix && rest.unit ? { suffix: rest.unit } : {}),\n });\n const cumulativeText = (row: WaterfallRow) => format.number(row.cumulative, rest.valueFormat);\n\n const kind = _derived<XYChartKind<WaterfallRow>>({\n skeleton: 'bars',\n band: true,\n domainValues: (list) => list.flatMap((row) => [row.low, row.high]),\n render: (context) => {\n const width = Math.min(context.step * 0.7, 56);\n return (\n <g data-part=\"bars\">\n {context.rows.map((row, index) => {\n const center = context.category(row, index);\n const next = context.rows[index + 1];\n // 连接线:这一步的累计接到下一根柱子\n const level = context.value(row.cumulative);\n const connector =\n next &&\n (context.horizontal\n ? {\n x1: level,\n x2: level,\n y1: center + width / 2,\n y2: context.category(next, index + 1) - width / 2,\n }\n : {\n x1: center + width / 2,\n x2: context.category(next, index + 1) - width / 2,\n y1: level,\n y2: level,\n });\n const { d, grow } = chartBar(context, {\n start: center - width / 2,\n width,\n low: row.low,\n high: row.high,\n radius,\n round: true,\n down: row.kind === 'decrease' || (row.kind === 'total' && row.cumulative < 0),\n });\n return (\n <g key={index}>\n {connector && (\n <line {...connector} {...css.attrs(waterfallChartStyles.connector)} />\n )}\n <path\n data-part=\"bar\"\n data-index={index}\n d={d}\n {...css.attrs(chartMarkStyles.bar, grow, chartSvgStyles.fill(colorOf(row)))}\n />\n </g>\n );\n })}\n </g>\n );\n },\n tooltipItems: (row) => [\n {\n label: row.kind === 'total' ? lang.chartTotal : lang.chartChange,\n color: colorOf(row),\n value: row.kind === 'total' ? valueText(row.cumulative) : changeText(row, true),\n },\n ...(row.kind === 'total'\n ? []\n : [\n {\n label: lang.chartCumulative,\n color: 'transparent',\n value: valueText(row.cumulative),\n },\n ]),\n ],\n table: (list) => ({\n columns: [\n lang.chartCategory,\n rest.unit ? `${lang.chartChange}(${rest.unit})` : lang.chartChange,\n rest.unit ? `${lang.chartCumulative}(${rest.unit})` : lang.chartCumulative,\n ],\n rows: list.map((row) => ({\n header: row.label,\n cells: [row.kind === 'total' ? lang.emptyValue : changeText(row), cumulativeText(row)],\n })),\n }),\n });\n\n /**\n * 不从 0 起时轴的下沿:最低的累计再往下留出变动幅度的一半(没有变动时留出它的一成);\n * 有负的累计时仍按数据自动取,不截\n */\n const zoomedDomain = (): [number | undefined, undefined] => {\n const values = rows.flatMap((row) =>\n row.kind === 'total' ? [row.high] : [row.low, row.high],\n );\n const low = Math.min(...values);\n if (low <= 0) return [undefined, undefined];\n const span = Math.max(...values) - low || low * 0.1;\n return [Math.max(0, low - span * 0.5), undefined];\n };\n\n return () => (\n <XYChart<WaterfallRow>\n {...rest}\n valueDomain={\n rest.valueDomain ?? (fromZero || rows.length === 0 ? undefined : zoomedDomain())\n }\n data={rows}\n index=\"label\"\n // 系列只用来给十字线定位与算范围;画法、提示框、数据表由 kind 决定\n series={[{ key: 'cumulative', label: lang.chartChange }]}\n legend={false}\n kind={kind}\n onItemClick={onItemClick && ((_row, position) => onItemClick(steps[position]!, position))}\n />\n );\n },\n);\n"],"mappings":"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"}
|