@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,83 +1,115 @@
|
|
|
1
|
+
import { svgNumber } from "../utils/data/number.js";
|
|
1
2
|
import { toNumber } from "../utils/data/numeric.js";
|
|
2
|
-
import { colors,
|
|
3
|
+
import { colors, t } from "../config/theme/tokens.js";
|
|
3
4
|
import { useLang } from "../config/context.js";
|
|
4
5
|
import { useFormat } from "../config/lang/format.js";
|
|
5
6
|
import { Skeleton } from "../feedback/Skeleton.js";
|
|
7
|
+
import { usePanZoom } from "../utils/dom/pan-zoom.js";
|
|
8
|
+
import { ChartTooltipBox, chartSvgStyles, pointerIn, usePlotArea } from "./plot.js";
|
|
6
9
|
import { ChartFrame, chartTooltip, useChartValueText } from "./frame.js";
|
|
7
10
|
import { resolveColor } from "./series.js";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
+
import { estimateTextWidth, truncateText } from "./text-size.js";
|
|
12
|
+
import { graphLayout } from "./layout/graph.js";
|
|
13
|
+
import { computed, createVNode, ref, shallowRef } from "vue";
|
|
14
|
+
import { _component, bindable, effect, mergeJsxProps, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
|
|
11
15
|
import { css } from "@zerodep-dev/vue-tsx-pro-tsx/styles";
|
|
12
|
-
import { Graph, GraphLayoutType, GraphLinkArrowStyle, GraphLinkStyle, GraphNodeSelectionHighlightMode, GraphNodeShape } from "@unovis/ts";
|
|
13
16
|
//#region src/charts/GraphChart.tsx
|
|
14
|
-
/**
|
|
15
|
-
* 网络图:节点与连线,看谁和谁有关系、关系有多强(案例与触发事件的匹配、团队与供应商的协作、依赖关系)。
|
|
16
|
-
*
|
|
17
|
-
* <GraphChart aria-label="协作关系" nodes={nodes} links={links} layout="dagre" groups={teams} />
|
|
18
|
-
*
|
|
19
|
-
* 节点是 { id, label?, sublabel?, group?, color?, shape?, size? },连线是 { source, target, value?, label?, dashed? }。
|
|
20
|
-
* 布局交给 Unovis 的 Graph:force(默认,力导向)、dagre(分层,direction 定方向)、circular、concentric、parallel
|
|
21
|
-
* (按 group 分列)、elk(分层且支持分组框,较大,用到时才加载)。布局异步算好再画,第一帧是空的。
|
|
22
|
-
* 写了 groups 时同组的节点框在一起(dagre、elk、parallel 布局;力导向、环形布局里组只决定颜色,框会互相盖住);
|
|
23
|
-
* 节点的颜色默认按组取色板,没有组时都是 chart1。
|
|
24
|
-
* 连线写了 value 时按大小加粗(1–6px),悬停看“起点 → 终点”与数值。点节点选中它(selected 双向绑定),
|
|
25
|
-
* 与它不相连的节点、连线变淡;点空白处取消。能缩放、拖动画布(zoom={false} 关掉),双击回到全图。
|
|
26
|
-
* 给读屏的数据表每条连线一行;单位、加载与空状态同其他图表(见 ChartFrame)。
|
|
27
|
-
*/
|
|
28
17
|
/** 节点的形状 */
|
|
29
18
|
/** 布局 */
|
|
30
19
|
/** 一个节点 */
|
|
31
20
|
/** 一条连线 */
|
|
32
21
|
/** 一组节点 */
|
|
33
22
|
/** 未声明的属性落到外层 div(role="figure")上,aria-label 是图表的名称 */
|
|
34
|
-
/** GraphChart
|
|
23
|
+
/** GraphChart 的样式 */
|
|
35
24
|
var graphChartStyles = css.create({
|
|
36
|
-
root: {
|
|
37
|
-
"--vis-graph-node-stroke-color": colors.surface,
|
|
38
|
-
"--vis-graph-node-label-text-color": colors.text,
|
|
39
|
-
"--vis-graph-node-label-font-size": fontSizes.xs,
|
|
40
|
-
"--vis-graph-node-label-background": colors.surface,
|
|
41
|
-
"--vis-graph-node-sublabel-text-color": colors.textMuted,
|
|
42
|
-
"--vis-graph-node-sublabel-font-size": fontSizes.xs,
|
|
43
|
-
"--vis-graph-node-selection-color": colors.accent,
|
|
44
|
-
"--vis-graph-node-greyout-color": colors.surfaceMuted,
|
|
45
|
-
"--vis-graph-node-greyout-opacity": opacities.disabled,
|
|
46
|
-
"--vis-graph-link-stroke-color": colors.borderStrong,
|
|
47
|
-
"--vis-graph-link-greyout-opacity": "0.25",
|
|
48
|
-
"--vis-graph-link-label-background": colors.fill,
|
|
49
|
-
"--vis-graph-link-label-text-color-dark": colors.text,
|
|
50
|
-
"--vis-graph-link-label-font-size": fontSizes.xs,
|
|
51
|
-
"--vis-graph-panel-fill-color": colors.surfaceMuted,
|
|
52
|
-
"--vis-graph-panel-border-color": colors.borderMuted,
|
|
53
|
-
"--vis-graph-panel-label-color": colors.textMuted,
|
|
54
|
-
"--vis-graph-panel-label-background": colors.surface,
|
|
55
|
-
"--vis-graph-panel-label-font-size": fontSizes.xs,
|
|
56
|
-
"--vis-graph-panel-label-font-weight": "500"
|
|
57
|
-
},
|
|
58
25
|
skeleton: {
|
|
59
26
|
display: "block",
|
|
60
27
|
width: "100%",
|
|
61
28
|
height: "100%"
|
|
29
|
+
},
|
|
30
|
+
viewport: {
|
|
31
|
+
position: "absolute",
|
|
32
|
+
inset: 0
|
|
33
|
+
},
|
|
34
|
+
background: { fill: "transparent" },
|
|
35
|
+
panel: {
|
|
36
|
+
fill: "_surfaceMuted",
|
|
37
|
+
strokeWidth: 1
|
|
38
|
+
},
|
|
39
|
+
panelLabel: {
|
|
40
|
+
fill: "_textMuted",
|
|
41
|
+
fontSize: "_xs",
|
|
42
|
+
fontWeight: "_medium"
|
|
43
|
+
},
|
|
44
|
+
link: {
|
|
45
|
+
fill: "none",
|
|
46
|
+
transition: `opacity ${t.durations.sm} ${t.easings.standard}`
|
|
47
|
+
},
|
|
48
|
+
linkDashed: { strokeDasharray: "5 4" },
|
|
49
|
+
linkLabelBox: { fill: "_fill" },
|
|
50
|
+
linkLabel: {
|
|
51
|
+
fill: "_text",
|
|
52
|
+
fontSize: "_xs"
|
|
53
|
+
},
|
|
54
|
+
node: { transition: `opacity ${t.durations.sm} ${t.easings.standard}` },
|
|
55
|
+
nodeShape: {
|
|
56
|
+
stroke: "_surface",
|
|
57
|
+
strokeWidth: 2
|
|
58
|
+
},
|
|
59
|
+
nodeSelected: {
|
|
60
|
+
stroke: "_accent",
|
|
61
|
+
strokeWidth: 3
|
|
62
|
+
},
|
|
63
|
+
nodeLabel: {
|
|
64
|
+
fill: "_text",
|
|
65
|
+
fontSize: "_xs",
|
|
66
|
+
fontWeight: "_medium"
|
|
67
|
+
},
|
|
68
|
+
nodeSublabel: {
|
|
69
|
+
fill: "_textMuted",
|
|
70
|
+
fontSize: "_xs"
|
|
71
|
+
},
|
|
72
|
+
clickable: { cursor: "pointer" },
|
|
73
|
+
wheelHint: {
|
|
74
|
+
position: "absolute",
|
|
75
|
+
top: "_sm",
|
|
76
|
+
insetInlineEnd: "_sm",
|
|
77
|
+
paddingBlock: "_xs",
|
|
78
|
+
paddingInline: "_sm",
|
|
79
|
+
borderRadius: "_md",
|
|
80
|
+
backgroundColor: "_surfaceRaised",
|
|
81
|
+
boxShadow: "_sm",
|
|
82
|
+
color: "_textMuted",
|
|
83
|
+
fontSize: "_xs",
|
|
84
|
+
pointerEvents: "none"
|
|
62
85
|
}
|
|
63
86
|
});
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
var
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
87
|
+
/** 节点的默认大小、名字的行高、名字最多几个字宽(px) */
|
|
88
|
+
var NODE_SIZE = 28;
|
|
89
|
+
var LINE = 16;
|
|
90
|
+
var LABEL_MAX = 120;
|
|
91
|
+
/** 分组框四周、顶上(写组名)留的空 */
|
|
92
|
+
var PANEL_PAD = 16;
|
|
93
|
+
var PANEL_TITLE = 22;
|
|
94
|
+
/** 箭头的长与宽 */
|
|
95
|
+
var ARROW = 8;
|
|
96
|
+
/** 节点的形状:中心在原点、外接圆直径 size 的 SVG 路径 */
|
|
97
|
+
function shapePath(shape, size) {
|
|
98
|
+
const r = size / 2;
|
|
99
|
+
const polygon = (count, rotate, radius) => `${Array.from({ length: count }, (_, i) => {
|
|
100
|
+
const angle = rotate + i / count * Math.PI * 2;
|
|
101
|
+
return `${i ? "L" : "M"}${svgNumber(radius * Math.cos(angle))},${svgNumber(radius * Math.sin(angle))}`;
|
|
102
|
+
}).join("")}Z`;
|
|
103
|
+
if (shape === "square") {
|
|
104
|
+
const half = r * .86;
|
|
105
|
+
return `M${-half},${-half}H${half}V${half}H${-half}Z`;
|
|
106
|
+
}
|
|
107
|
+
if (shape === "hexagon") return polygon(6, 0, r);
|
|
108
|
+
if (shape === "triangle") return polygon(3, -Math.PI / 2, r * 1.1);
|
|
109
|
+
return `M${-r},0A${r},${r},0,1,1,${r},0A${r},${r},0,1,1,${-r},0Z`;
|
|
110
|
+
}
|
|
111
|
+
/** 画图用的节点:带上名称、颜色与位置 */
|
|
112
|
+
/** 悬停的节点或连线 */
|
|
81
113
|
/** 网络图 */
|
|
82
114
|
var GraphChart = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
83
115
|
const _cache = [];
|
|
@@ -96,7 +128,6 @@ var GraphChart = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
96
128
|
const loading = readonlyProp(_props, "loading", false);
|
|
97
129
|
const empty = readonlyProp(_props, "empty");
|
|
98
130
|
const onItemClick = readonlyProp(_props, "onItemClick");
|
|
99
|
-
const sx = readonlyProp(_props, "sx");
|
|
100
131
|
const rest = restProps(_props, _ctx, true);
|
|
101
132
|
const lang = useLang();
|
|
102
133
|
const format = useFormat();
|
|
@@ -104,20 +135,33 @@ var GraphChart = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
104
135
|
valueFormat: valueFormat.value,
|
|
105
136
|
unit: unit.value
|
|
106
137
|
}));
|
|
138
|
+
const area = usePlotArea();
|
|
139
|
+
const viewport = shallowRef(null);
|
|
140
|
+
let hovered = shallowRef(null);
|
|
141
|
+
let wheelHint = ref(false);
|
|
142
|
+
let hintTimer;
|
|
143
|
+
effect(() => () => clearTimeout(hintTimer));
|
|
144
|
+
const view = usePanZoom({
|
|
145
|
+
viewport: () => viewport.value,
|
|
146
|
+
min: .3,
|
|
147
|
+
max: 4,
|
|
148
|
+
disabled: () => !zoom.value,
|
|
149
|
+
filter: (event) => {
|
|
150
|
+
if (event instanceof WheelEvent && !event.ctrlKey && !event.metaKey) {
|
|
151
|
+
wheelHint.value = true;
|
|
152
|
+
clearTimeout(hintTimer);
|
|
153
|
+
hintTimer = setTimeout(() => wheelHint.value = false, 1500);
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
return !(event.type === "dblclick" && event.target.closest?.("[data-node]"));
|
|
157
|
+
}
|
|
158
|
+
});
|
|
107
159
|
const groupColors = computed(() => new Map((groups.value ?? []).map((group, position) => [group.id, resolveColor(group.color, position)])));
|
|
108
|
-
const
|
|
109
|
-
...node,
|
|
110
|
-
label: node.label ?? node.id,
|
|
111
|
-
fill: node.color ? resolveColor(node.color, 0) : (node.group && groupColors.value.get(node.group)) ?? resolveColor("chart1", 0),
|
|
112
|
-
index
|
|
113
|
-
})));
|
|
114
|
-
const byId = computed(() => new Map(resolved.value.map((node) => [node.id, node])));
|
|
160
|
+
const byId = computed(() => new Map(nodes.value.map((node) => [node.id, node])));
|
|
115
161
|
const labelOf = (id) => byId.value.get(id)?.label ?? id;
|
|
116
|
-
const shownLinks = computed(() => links.value.filter((link) => byId.value.has(link.source) && byId.value.has(link.target))
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
})));
|
|
120
|
-
const maxAmount = computed(() => Math.max(0, ...shownLinks.value.map((link) => link.amount ?? 0)));
|
|
162
|
+
const shownLinks = computed(() => links.value.filter((link) => byId.value.has(link.source) && byId.value.has(link.target)));
|
|
163
|
+
const amounts = computed(() => shownLinks.value.map((link) => link.value === void 0 ? void 0 : toNumber(link.value)));
|
|
164
|
+
const maxAmount = computed(() => Math.max(0, ...amounts.value.map((amount) => amount ?? 0)));
|
|
121
165
|
const degree = computed(() => {
|
|
122
166
|
const counts = /* @__PURE__ */ new Map();
|
|
123
167
|
for (const link of shownLinks.value) {
|
|
@@ -126,112 +170,308 @@ var GraphChart = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
126
170
|
}
|
|
127
171
|
return counts;
|
|
128
172
|
});
|
|
129
|
-
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
const
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
173
|
+
const layered = () => layout.value === "layered";
|
|
174
|
+
const framed = () => layered() || layout.value === "parallel";
|
|
175
|
+
/** 布局:节点的位置(未缩放,单位 px) */
|
|
176
|
+
const positions = computed(() => {
|
|
177
|
+
const inputs = nodes.value.map((node) => {
|
|
178
|
+
const size = node.size ?? NODE_SIZE;
|
|
179
|
+
const label = node.label ?? node.id;
|
|
180
|
+
const textWidth = Math.min(LABEL_MAX, Math.max(estimateTextWidth(label), estimateTextWidth(node.sublabel ?? "")));
|
|
181
|
+
return {
|
|
182
|
+
id: node.id,
|
|
183
|
+
group: node.group,
|
|
184
|
+
width: Math.max(size, textWidth) + 8,
|
|
185
|
+
height: size + 6 + LINE * (node.sublabel ? 2 : 1)
|
|
186
|
+
};
|
|
187
|
+
});
|
|
188
|
+
return graphLayout(inputs, shownLinks.value, {
|
|
189
|
+
layout: layout.value,
|
|
190
|
+
direction: direction.value,
|
|
191
|
+
groups: groups.value?.map((group) => group.id)
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
/** 放进绘图区:位置按绘图区缩放、居中(节点与文字不缩放),最多放大到 1.2 倍 */
|
|
195
|
+
const placed = computed(() => {
|
|
196
|
+
const items = nodes.value.map((node, index) => {
|
|
197
|
+
const position = positions.value.get(node.id) ?? {
|
|
198
|
+
x: 0,
|
|
199
|
+
y: 0
|
|
200
|
+
};
|
|
201
|
+
return {
|
|
202
|
+
...node,
|
|
203
|
+
label: node.label ?? node.id,
|
|
204
|
+
fill: node.color ? resolveColor(node.color, 0) : (node.group && groupColors.value.get(node.group)) ?? resolveColor("chart1", 0),
|
|
205
|
+
index,
|
|
206
|
+
size: node.size ?? NODE_SIZE,
|
|
207
|
+
x: position.x,
|
|
208
|
+
y: position.y
|
|
209
|
+
};
|
|
210
|
+
});
|
|
211
|
+
if (!items.length) return items;
|
|
212
|
+
const xs = items.map((item) => item.x);
|
|
213
|
+
const ys = items.map((item) => item.y);
|
|
214
|
+
const [minX, maxX, minY, maxY] = [
|
|
215
|
+
Math.min(...xs),
|
|
216
|
+
Math.max(...xs),
|
|
217
|
+
Math.min(...ys),
|
|
218
|
+
Math.max(...ys)
|
|
219
|
+
];
|
|
220
|
+
const frame = framed() && groups.value?.length ? PANEL_PAD : 0;
|
|
221
|
+
const room = {
|
|
222
|
+
x: LABEL_MAX / 2 + frame + 8,
|
|
223
|
+
top: NODE_SIZE / 2 + frame + (frame ? PANEL_TITLE : 0) + 8,
|
|
224
|
+
bottom: 46 + frame + 8
|
|
225
|
+
};
|
|
226
|
+
const width = area.width - room.x * 2;
|
|
227
|
+
const height = area.height - room.top - room.bottom;
|
|
228
|
+
const scale = Math.min(1.2, maxX > minX ? width / (maxX - minX) : 1.2, maxY > minY ? height / (maxY - minY) : 1.2);
|
|
229
|
+
const offsetX = room.x + (width - (maxX - minX) * scale) / 2 - minX * scale;
|
|
230
|
+
const offsetY = room.top + (height - (maxY - minY) * scale) / 2 - minY * scale;
|
|
231
|
+
return items.map((item) => ({
|
|
232
|
+
...item,
|
|
233
|
+
x: item.x * scale + offsetX,
|
|
234
|
+
y: item.y * scale + offsetY
|
|
235
|
+
}));
|
|
236
|
+
});
|
|
237
|
+
const placedById = computed(() => new Map(placed.value.map((node) => [node.id, node])));
|
|
238
|
+
/** 选中节点时它与相连的节点、连线保持原样,其余变淡 */
|
|
239
|
+
const connected = computed(() => {
|
|
240
|
+
if (selected.value === null) return null;
|
|
241
|
+
const ids = /* @__PURE__ */ new Set([selected.value]);
|
|
242
|
+
for (const link of shownLinks.value) {
|
|
243
|
+
if (link.source === selected.value) ids.add(link.target);
|
|
244
|
+
if (link.target === selected.value) ids.add(link.source);
|
|
245
|
+
}
|
|
246
|
+
return ids;
|
|
247
|
+
});
|
|
248
|
+
/** 一条连线的路径、终点处的方向(画箭头)与中点(写标签) */
|
|
249
|
+
function linkGeometry(source, target, arrow) {
|
|
250
|
+
const tail = arrow ? ARROW : 0;
|
|
251
|
+
if (layered()) {
|
|
252
|
+
const horizontal = direction.value === "LR";
|
|
253
|
+
const [sx, sy] = horizontal ? [source.x + source.size / 2, source.y] : [source.x, source.y + source.size / 2];
|
|
254
|
+
const [tx, ty] = horizontal ? [target.x - target.size / 2 - tail, target.y] : [target.x, target.y - target.size / 2 - tail];
|
|
199
255
|
return {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
},
|
|
205
|
-
triggers: {
|
|
206
|
-
[Graph.selectors.node]: (node) => chart.tooltip(chartTooltip(node.label, [...node.sublabel ? [{
|
|
207
|
-
label: node.sublabel,
|
|
208
|
-
color: "transparent",
|
|
209
|
-
value: ""
|
|
210
|
-
}] : [], {
|
|
211
|
-
label: lang.chartDegree(degree.value.get(node.id) ?? 0),
|
|
212
|
-
color: node.fill,
|
|
213
|
-
value: ""
|
|
214
|
-
}])),
|
|
215
|
-
[Graph.selectors.link]: (link) => chart.tooltip(chartTooltip(lang.chartFlowLabel(link.source.label, link.target.label), [...link.value === void 0 ? [] : [{
|
|
216
|
-
label: lang.chartValue,
|
|
217
|
-
color: link.source.fill,
|
|
218
|
-
value: valueText(link.value)
|
|
219
|
-
}], ...link.label ? [{
|
|
220
|
-
label: link.label,
|
|
221
|
-
color: "transparent",
|
|
222
|
-
value: ""
|
|
223
|
-
}] : []]))
|
|
224
|
-
}
|
|
256
|
+
d: horizontal ? `M${svgNumber(sx)},${svgNumber(sy)}C${svgNumber((sx + tx) / 2)},${svgNumber(sy)},${svgNumber((sx + tx) / 2)},${svgNumber(ty)},${svgNumber(tx)},${svgNumber(ty)}` : `M${svgNumber(sx)},${svgNumber(sy)}C${svgNumber(sx)},${svgNumber((sy + ty) / 2)},${svgNumber(tx)},${svgNumber((sy + ty) / 2)},${svgNumber(tx)},${svgNumber(ty)}`,
|
|
257
|
+
end: [tx, ty],
|
|
258
|
+
angle: horizontal ? 0 : Math.PI / 2,
|
|
259
|
+
middle: [(sx + tx) / 2, (sy + ty) / 2]
|
|
225
260
|
};
|
|
226
261
|
}
|
|
227
|
-
|
|
262
|
+
const dx = target.x - source.x;
|
|
263
|
+
const dy = target.y - source.y;
|
|
264
|
+
const length = Math.hypot(dx, dy) || 1;
|
|
265
|
+
const control = [(source.x + target.x) / 2 - dy / length * length * .12, (source.y + target.y) / 2 + dx / length * length * .12];
|
|
266
|
+
const toward = (from, to, distance) => {
|
|
267
|
+
const ux = to[0] - from[0];
|
|
268
|
+
const uy = to[1] - from[1];
|
|
269
|
+
const norm = Math.hypot(ux, uy) || 1;
|
|
270
|
+
return [from[0] + ux / norm * distance, from[1] + uy / norm * distance];
|
|
271
|
+
};
|
|
272
|
+
const start = toward([source.x, source.y], control, source.size / 2);
|
|
273
|
+
const end = toward([target.x, target.y], control, target.size / 2 + tail);
|
|
274
|
+
return {
|
|
275
|
+
d: `M${svgNumber(start[0])},${svgNumber(start[1])}Q${svgNumber(control[0])},${svgNumber(control[1])},${svgNumber(end[0])},${svgNumber(end[1])}`,
|
|
276
|
+
end,
|
|
277
|
+
angle: Math.atan2(target.y - control[1], target.x - control[0]),
|
|
278
|
+
middle: [(start[0] + 2 * control[0] + end[0]) / 4, (start[1] + 2 * control[1] + end[1]) / 4]
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
function onPointermove(event) {
|
|
282
|
+
const target = event.target.closest?.("[data-node],[data-link]");
|
|
283
|
+
if (!target) {
|
|
284
|
+
hovered.value = null;
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
const { x, y } = pointerIn(event, viewport.value ?? event.currentTarget);
|
|
288
|
+
const node = target.getAttribute("data-node");
|
|
289
|
+
hovered.value = node === null ? {
|
|
290
|
+
kind: "link",
|
|
291
|
+
index: Number(target.getAttribute("data-link")),
|
|
292
|
+
x,
|
|
293
|
+
y
|
|
294
|
+
} : {
|
|
295
|
+
kind: "node",
|
|
296
|
+
index: Number(node),
|
|
297
|
+
x,
|
|
298
|
+
y
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
function onClick(event) {
|
|
302
|
+
const target = event.target.closest?.("[data-node]");
|
|
303
|
+
if (!target) {
|
|
304
|
+
selected.value = null;
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
const node = placed.value[Number(target.getAttribute("data-node"))];
|
|
308
|
+
if (!node) return;
|
|
309
|
+
selected.value = node.id;
|
|
310
|
+
onItemClick.value?.(nodes.value[node.index], node.index);
|
|
311
|
+
}
|
|
312
|
+
function renderPanels() {
|
|
313
|
+
if (!framed()) return null;
|
|
314
|
+
return (groups.value ?? []).map((group) => {
|
|
315
|
+
const members = placed.value.filter((node) => node.group === group.id);
|
|
316
|
+
if (!members.length) return null;
|
|
317
|
+
const half = (node) => Math.max(node.size / 2, Math.min(LABEL_MAX, estimateTextWidth(node.label)) / 2);
|
|
318
|
+
const x0 = Math.min(...members.map((node) => node.x - half(node))) - PANEL_PAD;
|
|
319
|
+
const x1 = Math.max(...members.map((node) => node.x + half(node))) + PANEL_PAD;
|
|
320
|
+
const y0 = Math.min(...members.map((node) => node.y - node.size / 2)) - PANEL_PAD - PANEL_TITLE;
|
|
321
|
+
const y1 = Math.max(...members.map((node) => node.y + node.size / 2 + LINE * (node.sublabel ? 2 : 1) + 4)) + PANEL_PAD;
|
|
322
|
+
return createVNode("g", {
|
|
323
|
+
"key": group.id,
|
|
324
|
+
"data-part": "panel"
|
|
325
|
+
}, [createVNode("rect", mergeJsxProps({
|
|
326
|
+
"x": svgNumber(x0),
|
|
327
|
+
"y": svgNumber(y0),
|
|
328
|
+
"width": svgNumber(x1 - x0),
|
|
329
|
+
"height": svgNumber(y1 - y0),
|
|
330
|
+
"rx": 8
|
|
331
|
+
}, css.attrs(graphChartStyles.panel, chartSvgStyles.stroke(groupColors.value.get(group.id) ?? colors.borderMuted))), null), createVNode("text", mergeJsxProps({
|
|
332
|
+
"x": svgNumber(x0 + 10),
|
|
333
|
+
"y": svgNumber(y0 + PANEL_TITLE / 2 + 2),
|
|
334
|
+
"dominant-baseline": "central"
|
|
335
|
+
}, css.attrs(graphChartStyles.panelLabel)), [truncateText(group.label, x1 - x0 - 20)])]);
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
function renderLinks() {
|
|
339
|
+
const withArrows = arrows.value ?? layered();
|
|
340
|
+
return shownLinks.value.map((link, index) => {
|
|
341
|
+
const source = placedById.value.get(link.source);
|
|
342
|
+
const target = placedById.value.get(link.target);
|
|
343
|
+
if (source === target) return null;
|
|
344
|
+
const amount = amounts.value[index];
|
|
345
|
+
const width = amount === void 0 || maxAmount.value === 0 ? 1.5 : 1 + 5 * amount / maxAmount.value;
|
|
346
|
+
const color = link.color ? resolveColor(link.color, 0) : colors.borderStrong;
|
|
347
|
+
const { d, end, angle, middle } = linkGeometry(source, target, withArrows);
|
|
348
|
+
const faded = connected.value !== null && link.source !== selected.value && link.target !== selected.value;
|
|
349
|
+
const arrowSize = ARROW + width;
|
|
350
|
+
const arrowPath = () => {
|
|
351
|
+
const [ex, ey] = end;
|
|
352
|
+
const tipX = ex + Math.cos(angle) * arrowSize;
|
|
353
|
+
const tipY = ey + Math.sin(angle) * arrowSize;
|
|
354
|
+
const side = arrowSize * .55;
|
|
355
|
+
const nx = -Math.sin(angle) * side;
|
|
356
|
+
const ny = Math.cos(angle) * side;
|
|
357
|
+
return `M${svgNumber(tipX)},${svgNumber(tipY)}L${svgNumber(ex + nx)},${svgNumber(ey + ny)}L${svgNumber(ex - nx)},${svgNumber(ey - ny)}Z`;
|
|
358
|
+
};
|
|
359
|
+
return createVNode("g", mergeJsxProps({ "key": `${link.source}->${link.target}:${index}` }, css.attrs(graphChartStyles.node, faded && chartSvgStyles.dimmed)), [
|
|
360
|
+
createVNode("path", mergeJsxProps({
|
|
361
|
+
"data-part": "link",
|
|
362
|
+
"data-link": index,
|
|
363
|
+
"d": d,
|
|
364
|
+
"stroke-width": svgNumber(width)
|
|
365
|
+
}, css.attrs(graphChartStyles.link, chartSvgStyles.stroke(color), link.dashed && graphChartStyles.linkDashed)), null),
|
|
366
|
+
withArrows && createVNode("path", mergeJsxProps({ "d": arrowPath() }, css.attrs(chartSvgStyles.fill(color))), null),
|
|
367
|
+
link.label && createVNode("g", mergeJsxProps({ "transform": `translate(${svgNumber(middle[0])},${svgNumber(middle[1])})` }, css.attrs(chartSvgStyles.noPointer)), [createVNode("rect", mergeJsxProps({
|
|
368
|
+
"x": -estimateTextWidth(link.label) / 2 - 4,
|
|
369
|
+
"y": -9,
|
|
370
|
+
"width": estimateTextWidth(link.label) + 8,
|
|
371
|
+
"height": 18,
|
|
372
|
+
"rx": 4
|
|
373
|
+
}, css.attrs(graphChartStyles.linkLabelBox)), null), createVNode("text", mergeJsxProps({
|
|
374
|
+
"text-anchor": "middle",
|
|
375
|
+
"dominant-baseline": "central"
|
|
376
|
+
}, css.attrs(graphChartStyles.linkLabel)), [link.label])])
|
|
377
|
+
]);
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
function renderNodes() {
|
|
381
|
+
return placed.value.map((node, index) => {
|
|
382
|
+
const faded = connected.value !== null && !connected.value.has(node.id);
|
|
383
|
+
const shape = node.shape ?? "circle";
|
|
384
|
+
return createVNode("g", mergeJsxProps({
|
|
385
|
+
"key": node.id,
|
|
386
|
+
"data-part": "node",
|
|
387
|
+
"data-node": index,
|
|
388
|
+
"transform": `translate(${svgNumber(node.x)},${svgNumber(node.y)})`
|
|
389
|
+
}, css.attrs(graphChartStyles.node, onItemClick.value && graphChartStyles.clickable, faded && chartSvgStyles.dimmed)), [
|
|
390
|
+
createVNode("path", mergeJsxProps({ "d": shapePath(shape, node.size) }, css.attrs(graphChartStyles.nodeShape, chartSvgStyles.fill(node.fill), selected.value === node.id && graphChartStyles.nodeSelected)), null),
|
|
391
|
+
createVNode("text", mergeJsxProps({
|
|
392
|
+
"y": node.size / 2 + 4,
|
|
393
|
+
"text-anchor": "middle",
|
|
394
|
+
"dominant-baseline": "hanging"
|
|
395
|
+
}, css.attrs(graphChartStyles.nodeLabel, chartSvgStyles.halo)), [truncateText(node.label, LABEL_MAX)]),
|
|
396
|
+
node.sublabel && createVNode("text", mergeJsxProps({
|
|
397
|
+
"y": node.size / 2 + 4 + LINE,
|
|
398
|
+
"text-anchor": "middle",
|
|
399
|
+
"dominant-baseline": "hanging"
|
|
400
|
+
}, css.attrs(graphChartStyles.nodeSublabel, chartSvgStyles.halo)), [truncateText(node.sublabel, LABEL_MAX)])
|
|
401
|
+
]);
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
const tip = () => {
|
|
405
|
+
const current = hovered.value;
|
|
406
|
+
if (!tooltip.value || !current) return null;
|
|
407
|
+
if (current.kind === "node") {
|
|
408
|
+
const node = placed.value[current.index];
|
|
409
|
+
if (!node) return null;
|
|
410
|
+
return {
|
|
411
|
+
x: current.x,
|
|
412
|
+
y: current.y,
|
|
413
|
+
content: chartTooltip(node.label, [...node.sublabel ? [{
|
|
414
|
+
label: node.sublabel,
|
|
415
|
+
color: "transparent",
|
|
416
|
+
value: ""
|
|
417
|
+
}] : [], {
|
|
418
|
+
label: lang.chartDegree(degree.value.get(node.id) ?? 0),
|
|
419
|
+
color: node.fill,
|
|
420
|
+
value: ""
|
|
421
|
+
}])
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
const link = shownLinks.value[current.index];
|
|
425
|
+
if (!link) return null;
|
|
426
|
+
const source = placedById.value.get(link.source);
|
|
427
|
+
return {
|
|
428
|
+
x: current.x,
|
|
429
|
+
y: current.y,
|
|
430
|
+
content: chartTooltip(lang.chartFlowLabel(source.label, placedById.value.get(link.target).label), [...link.value === void 0 ? [] : [{
|
|
431
|
+
label: lang.chartValue,
|
|
432
|
+
color: source.fill,
|
|
433
|
+
value: valueText(link.value)
|
|
434
|
+
}], ...link.label ? [{
|
|
435
|
+
label: link.label,
|
|
436
|
+
color: "transparent",
|
|
437
|
+
value: ""
|
|
438
|
+
}] : []])
|
|
439
|
+
};
|
|
440
|
+
};
|
|
441
|
+
function renderPlot() {
|
|
442
|
+
return createVNode("div", mergeJsxProps(css.attrs(graphChartStyles.viewport), zoom.value ? view.viewportProps : {}, {
|
|
443
|
+
"ref": _cache[0] || (_cache[0] = (element) => viewport.value = element),
|
|
444
|
+
"onPointermove": _cache[1] || (_cache[1] = (event) => {
|
|
445
|
+
if (zoom.value) view.viewportProps.onPointermove(event);
|
|
446
|
+
onPointermove(event);
|
|
447
|
+
}),
|
|
448
|
+
"onPointerleave": _cache[2] || (_cache[2] = () => hovered.value = null)
|
|
449
|
+
}), [
|
|
450
|
+
createVNode("svg", mergeJsxProps({
|
|
451
|
+
"width": area.width,
|
|
452
|
+
"height": area.height
|
|
453
|
+
}, css.attrs(chartSvgStyles.svg), { "onClick": onClick }), [createVNode("rect", mergeJsxProps({
|
|
454
|
+
"data-part": "background",
|
|
455
|
+
"width": area.width,
|
|
456
|
+
"height": area.height
|
|
457
|
+
}, css.attrs(graphChartStyles.background)), null), createVNode("g", { "transform": `translate(${svgNumber(view.x)} ${svgNumber(view.y)}) scale(${svgNumber(view.scale)})` }, [createVNode("g", css.attrs(chartSvgStyles.fadeIn), [
|
|
458
|
+
renderPanels(),
|
|
459
|
+
renderLinks(),
|
|
460
|
+
renderNodes()
|
|
461
|
+
])])]),
|
|
462
|
+
createVNode(ChartTooltipBox, {
|
|
463
|
+
"tip": tip(),
|
|
464
|
+
"width": area.width
|
|
465
|
+
}, null),
|
|
466
|
+
wheelHint.value && createVNode("span", css.attrs(graphChartStyles.wheelHint), [lang.chartZoomWheelHint])
|
|
467
|
+
]);
|
|
468
|
+
}
|
|
228
469
|
return () => createVNode(ChartFrame, mergeJsxProps(rest, {
|
|
229
|
-
"sx": [graphChartStyles.root, sx.value],
|
|
230
470
|
"unit": unit.value,
|
|
231
471
|
"height": height.value,
|
|
232
|
-
"legend": _cache[
|
|
233
|
-
"hiddenKeys": _cache[
|
|
234
|
-
"onLegendToggle": _cache[
|
|
472
|
+
"legend": _cache[3] || (_cache[3] = []),
|
|
473
|
+
"hiddenKeys": _cache[4] || (_cache[4] = []),
|
|
474
|
+
"onLegendToggle": _cache[5] || (_cache[5] = () => {}),
|
|
235
475
|
"loading": loading.value,
|
|
236
476
|
"skeleton": createVNode(Skeleton, { "sx": graphChartStyles.skeleton }, null),
|
|
237
477
|
"isEmpty": nodes.value.length === 0,
|
|
@@ -244,7 +484,8 @@ var GraphChart = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
244
484
|
cells: [[link.value === void 0 ? "" : format.number(link.value, valueFormat.value), link.label ?? ""].filter(Boolean).join(" · ") || lang.emptyValue]
|
|
245
485
|
}))
|
|
246
486
|
},
|
|
247
|
-
"plotRef":
|
|
487
|
+
"plotRef": area.ref,
|
|
488
|
+
"plot": area.ready ? renderPlot() : null
|
|
248
489
|
}), null);
|
|
249
490
|
}, {
|
|
250
491
|
name: "GraphChart",
|
|
@@ -264,8 +505,7 @@ var GraphChart = /*#__PURE__*/ _component((_props, _ctx) => {
|
|
|
264
505
|
"onUpdate:_selected",
|
|
265
506
|
"loading",
|
|
266
507
|
"empty",
|
|
267
|
-
"onItemClick"
|
|
268
|
-
"sx"
|
|
508
|
+
"onItemClick"
|
|
269
509
|
]
|
|
270
510
|
});
|
|
271
511
|
//#endregion
|