@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.
- 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/display/VirtualList.d.ts +1 -2
- package/dist/display/VirtualList.js +11 -21
- package/dist/display/VirtualList.js.map +1 -1
- package/dist/input/Combobox.d.ts +1 -1
- package/dist/input/Combobox.js.map +1 -1
- package/dist/input/ListPopover.d.ts +1 -1
- package/dist/input/ListPopover.js.map +1 -1
- package/dist/input/Listbox.d.ts +1 -2
- package/dist/input/Listbox.js +7 -19
- package/dist/input/Listbox.js.map +1 -1
- package/dist/input/Select.d.ts +1 -1
- package/dist/input/Select.js.map +1 -1
- 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/CommandPalette.d.ts +1 -1
- package/dist/overlay/CommandPalette.js.map +1 -1
- package/dist/overlay/detached.js +14 -1
- package/dist/overlay/detached.js.map +1 -1
- package/dist/patterns/QueryTable.js +0 -2
- package/dist/patterns/QueryTable.js.map +1 -1
- package/dist/table/DataTable.js +1 -1
- package/dist/table/DataTable.js.map +1 -1
- package/dist/table/DataTableView.d.ts +0 -3
- package/dist/table/DataTableView.js +32 -26
- package/dist/table/DataTableView.js.map +1 -1
- package/dist/table/table-styles.d.ts +1 -1
- package/dist/table/table-styles.js +0 -13
- package/dist/table/table-styles.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
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
//#region src/charts/layout/graph.ts
|
|
2
|
+
/** 节点的邻居:按 id 查 */
|
|
3
|
+
function neighbors(nodes, links) {
|
|
4
|
+
const map = new Map(nodes.map((node) => [node.id, []]));
|
|
5
|
+
for (const link of links) {
|
|
6
|
+
if (link.source === link.target) continue;
|
|
7
|
+
map.get(link.source)?.push(link.target);
|
|
8
|
+
map.get(link.target)?.push(link.source);
|
|
9
|
+
}
|
|
10
|
+
return map;
|
|
11
|
+
}
|
|
12
|
+
/** 按组排先后:组在 groups 里的位置,没有组的排最后;同组按原来的顺序 */
|
|
13
|
+
function groupRank(groups) {
|
|
14
|
+
return (node) => {
|
|
15
|
+
const index = node.group === void 0 ? -1 : (groups ?? []).indexOf(node.group);
|
|
16
|
+
return index < 0 ? groups?.length ?? 0 : index;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/** 力导向(见文件开头) */
|
|
20
|
+
function force(nodes, links, distance, charge) {
|
|
21
|
+
const index = new Map(nodes.map((node, i) => [node.id, i]));
|
|
22
|
+
const points = nodes.map((_, i) => {
|
|
23
|
+
const radius = 10 * Math.sqrt(.5 + i);
|
|
24
|
+
const angle = i * Math.PI * (3 - Math.sqrt(5));
|
|
25
|
+
return {
|
|
26
|
+
x: radius * Math.cos(angle),
|
|
27
|
+
y: radius * Math.sin(angle),
|
|
28
|
+
vx: 0,
|
|
29
|
+
vy: 0
|
|
30
|
+
};
|
|
31
|
+
});
|
|
32
|
+
const edges = links.flatMap((link) => {
|
|
33
|
+
const s = index.get(link.source);
|
|
34
|
+
const t = index.get(link.target);
|
|
35
|
+
return s === void 0 || t === void 0 || s === t ? [] : [[s, t]];
|
|
36
|
+
});
|
|
37
|
+
const count = points.map(() => 0);
|
|
38
|
+
for (const [s, t] of edges) {
|
|
39
|
+
count[s] += 1;
|
|
40
|
+
count[t] += 1;
|
|
41
|
+
}
|
|
42
|
+
const radii = nodes.map((node) => Math.hypot(node.width, node.height) / 2);
|
|
43
|
+
const jiggle = (i) => (i + 1) * 1e-6;
|
|
44
|
+
const decay = 1 - .001 ** (1 / 300);
|
|
45
|
+
let alpha = 1;
|
|
46
|
+
for (let tick = 0; tick < 300; tick += 1) {
|
|
47
|
+
alpha += (0 - alpha) * decay;
|
|
48
|
+
for (const [s, t] of edges) {
|
|
49
|
+
const a = points[s];
|
|
50
|
+
const b = points[t];
|
|
51
|
+
let dx = b.x + b.vx - a.x - a.vx || jiggle(s);
|
|
52
|
+
let dy = b.y + b.vy - a.y - a.vy || jiggle(t);
|
|
53
|
+
const length = Math.hypot(dx, dy);
|
|
54
|
+
const strength = 1 / Math.min(count[s], count[t]);
|
|
55
|
+
const k = (length - distance) / length * alpha * strength;
|
|
56
|
+
const bias = count[s] / (count[s] + count[t]);
|
|
57
|
+
dx *= k;
|
|
58
|
+
dy *= k;
|
|
59
|
+
b.vx -= dx * bias;
|
|
60
|
+
b.vy -= dy * bias;
|
|
61
|
+
a.vx += dx * (1 - bias);
|
|
62
|
+
a.vy += dy * (1 - bias);
|
|
63
|
+
}
|
|
64
|
+
for (let i = 0; i < points.length; i += 1) for (let j = 0; j < points.length; j += 1) {
|
|
65
|
+
if (i === j) continue;
|
|
66
|
+
const dx = points[j].x - points[i].x || jiggle(i);
|
|
67
|
+
const dy = points[j].y - points[i].y || jiggle(j);
|
|
68
|
+
const squared = Math.max(dx * dx + dy * dy, 1);
|
|
69
|
+
const w = charge * alpha / squared;
|
|
70
|
+
points[i].vx += dx * w;
|
|
71
|
+
points[i].vy += dy * w;
|
|
72
|
+
}
|
|
73
|
+
for (const point of points) {
|
|
74
|
+
point.vx *= .6;
|
|
75
|
+
point.vy *= .6;
|
|
76
|
+
point.x += point.vx;
|
|
77
|
+
point.y += point.vy;
|
|
78
|
+
}
|
|
79
|
+
for (let i = 0; i < points.length; i += 1) for (let j = i + 1; j < points.length; j += 1) {
|
|
80
|
+
const a = points[i];
|
|
81
|
+
const b = points[j];
|
|
82
|
+
const dx = b.x - a.x || jiggle(i);
|
|
83
|
+
const dy = b.y - a.y || jiggle(j);
|
|
84
|
+
const length = Math.hypot(dx, dy);
|
|
85
|
+
const room = (radii[i] + radii[j]) * .6;
|
|
86
|
+
if (length >= room) continue;
|
|
87
|
+
const push = (room - length) / length * .5;
|
|
88
|
+
a.x -= dx * push;
|
|
89
|
+
a.y -= dy * push;
|
|
90
|
+
b.x += dx * push;
|
|
91
|
+
b.y += dy * push;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return new Map(nodes.map((node, i) => [node.id, {
|
|
95
|
+
x: points[i].x,
|
|
96
|
+
y: points[i].y
|
|
97
|
+
}]));
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* 分层:每层里节点按 order 的顺序排,层与层之间 rankGap、层内按各自的外框留空;
|
|
101
|
+
* 几轮向邻居的平均位置靠拢(保持顺序、不重叠),连线更直
|
|
102
|
+
*/
|
|
103
|
+
function placeLayers(layers, around, vertical, rankGap) {
|
|
104
|
+
const breadth = (node) => (vertical ? node.width : node.height) + 16;
|
|
105
|
+
const along = /* @__PURE__ */ new Map();
|
|
106
|
+
for (const layer of layers) {
|
|
107
|
+
let position = -layer.reduce((sum, node) => sum + breadth(node), 0) / 2;
|
|
108
|
+
for (const node of layer) {
|
|
109
|
+
along.set(node.id, position + breadth(node) / 2);
|
|
110
|
+
position += breadth(node);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/** 一层节点挪到邻居的平均位置,再从中间往两头推开、不重叠 */
|
|
114
|
+
const relax = (layer) => {
|
|
115
|
+
const wanted = layer.map((node) => {
|
|
116
|
+
const others = (around.get(node.id) ?? []).filter((id) => along.has(id));
|
|
117
|
+
return others.length ? others.reduce((sum, id) => sum + along.get(id), 0) / others.length : along.get(node.id);
|
|
118
|
+
});
|
|
119
|
+
layer.forEach((node, i) => along.set(node.id, wanted[i]));
|
|
120
|
+
const middle = layer.length >> 1;
|
|
121
|
+
for (let i = middle + 1; i < layer.length; i += 1) {
|
|
122
|
+
const min = along.get(layer[i - 1].id) + (breadth(layer[i - 1]) + breadth(layer[i])) / 2;
|
|
123
|
+
along.set(layer[i].id, Math.max(along.get(layer[i].id), min));
|
|
124
|
+
}
|
|
125
|
+
for (let i = middle - 1; i >= 0; i -= 1) {
|
|
126
|
+
const max = along.get(layer[i + 1].id) - (breadth(layer[i + 1]) + breadth(layer[i])) / 2;
|
|
127
|
+
along.set(layer[i].id, Math.min(along.get(layer[i].id), max));
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
for (let round = 0; round < 4; round += 1) {
|
|
131
|
+
for (const layer of layers.slice(1)) relax(layer);
|
|
132
|
+
for (const layer of layers.slice(0, -1).reverse()) relax(layer);
|
|
133
|
+
}
|
|
134
|
+
const positions = /* @__PURE__ */ new Map();
|
|
135
|
+
layers.forEach((layer, rank) => {
|
|
136
|
+
for (const node of layer) {
|
|
137
|
+
const across = rank * rankGap;
|
|
138
|
+
const value = along.get(node.id);
|
|
139
|
+
positions.set(node.id, vertical ? {
|
|
140
|
+
x: value,
|
|
141
|
+
y: across
|
|
142
|
+
} : {
|
|
143
|
+
x: across,
|
|
144
|
+
y: value
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
return positions;
|
|
149
|
+
}
|
|
150
|
+
/** 重心法排每层的顺序:几轮上下来回,按相邻层里邻居的平均位置排;有组时组优先 */
|
|
151
|
+
function orderLayers(layers, around, rankOf) {
|
|
152
|
+
const position = /* @__PURE__ */ new Map();
|
|
153
|
+
const record = () => layers.forEach((layer) => layer.forEach((node, i) => position.set(node.id, i)));
|
|
154
|
+
record();
|
|
155
|
+
const sweep = (layer, neighborLayer) => {
|
|
156
|
+
const inNeighbor = new Set(neighborLayer.map((node) => node.id));
|
|
157
|
+
const center = (node) => {
|
|
158
|
+
const others = (around.get(node.id) ?? []).filter((id) => inNeighbor.has(id));
|
|
159
|
+
return others.length ? others.reduce((sum, id) => sum + position.get(id), 0) / others.length : position.get(node.id);
|
|
160
|
+
};
|
|
161
|
+
const keys = new Map(layer.map((node) => [node.id, center(node)]));
|
|
162
|
+
layer.sort((a, b) => rankOf(a) - rankOf(b) || keys.get(a.id) - keys.get(b.id));
|
|
163
|
+
layer.forEach((node, i) => position.set(node.id, i));
|
|
164
|
+
};
|
|
165
|
+
for (let round = 0; round < 6; round += 1) {
|
|
166
|
+
for (let i = 1; i < layers.length; i += 1) sweep(layers[i], layers[i - 1]);
|
|
167
|
+
for (let i = layers.length - 2; i >= 0; i -= 1) sweep(layers[i], layers[i + 1]);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/** 分层:去环后按最长路径分层(见文件开头) */
|
|
171
|
+
function layered(nodes, links, vertical, groups) {
|
|
172
|
+
const ids = new Set(nodes.map((node) => node.id));
|
|
173
|
+
const outgoing = new Map(nodes.map((node) => [node.id, []]));
|
|
174
|
+
const state = /* @__PURE__ */ new Map();
|
|
175
|
+
const visit = (id) => {
|
|
176
|
+
state.set(id, "open");
|
|
177
|
+
for (const link of links) {
|
|
178
|
+
if (link.source !== id || !ids.has(link.target) || link.target === id) continue;
|
|
179
|
+
const seen = state.get(link.target);
|
|
180
|
+
if (seen === "open") continue;
|
|
181
|
+
outgoing.get(id).push(link.target);
|
|
182
|
+
if (!seen) visit(link.target);
|
|
183
|
+
}
|
|
184
|
+
state.set(id, "done");
|
|
185
|
+
};
|
|
186
|
+
for (const node of nodes) if (!state.has(node.id)) visit(node.id);
|
|
187
|
+
const indegree = new Map(nodes.map((node) => [node.id, 0]));
|
|
188
|
+
for (const targets of outgoing.values()) for (const target of new Set(targets)) indegree.set(target, indegree.get(target) + 1);
|
|
189
|
+
const rank = new Map(nodes.map((node) => [node.id, 0]));
|
|
190
|
+
const queue = nodes.filter((node) => indegree.get(node.id) === 0).map((node) => node.id);
|
|
191
|
+
while (queue.length) {
|
|
192
|
+
const id = queue.shift();
|
|
193
|
+
for (const target of new Set(outgoing.get(id))) {
|
|
194
|
+
rank.set(target, Math.max(rank.get(target), rank.get(id) + 1));
|
|
195
|
+
indegree.set(target, indegree.get(target) - 1);
|
|
196
|
+
if (indegree.get(target) === 0) queue.push(target);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
const byRank = groupRank(groups);
|
|
200
|
+
const layers = [];
|
|
201
|
+
for (const node of [...nodes].sort((a, b) => byRank(a) - byRank(b))) (layers[rank.get(node.id)] ??= []).push(node);
|
|
202
|
+
const filled = layers.filter(Boolean);
|
|
203
|
+
const around = neighbors(nodes, links);
|
|
204
|
+
orderLayers(filled, around, byRank);
|
|
205
|
+
return placeLayers(filled, around, vertical, Math.max(...nodes.map((node) => vertical ? node.height : node.width)) + (groups?.length ? 72 : 48));
|
|
206
|
+
}
|
|
207
|
+
/** 环形:按组、按原来的顺序排成一圈,圈的大小放得下所有外框 */
|
|
208
|
+
function circular(nodes, groups) {
|
|
209
|
+
const byRank = groupRank(groups);
|
|
210
|
+
const sorted = [...nodes].sort((a, b) => byRank(a) - byRank(b));
|
|
211
|
+
const perimeter = sorted.reduce((sum, node) => sum + Math.max(node.width, node.height) + 12, 0);
|
|
212
|
+
const radius = Math.max(perimeter / (Math.PI * 2), 60);
|
|
213
|
+
return new Map(sorted.map((node, i) => {
|
|
214
|
+
const angle = i / sorted.length * Math.PI * 2 - Math.PI / 2;
|
|
215
|
+
return [node.id, {
|
|
216
|
+
x: radius * Math.cos(angle),
|
|
217
|
+
y: radius * Math.sin(angle)
|
|
218
|
+
}];
|
|
219
|
+
}));
|
|
220
|
+
}
|
|
221
|
+
/** 同心圆:连接数最多的在圆心,往外每圈放得下多少放多少 */
|
|
222
|
+
function concentric(nodes, links) {
|
|
223
|
+
const around = neighbors(nodes, links);
|
|
224
|
+
const sorted = [...nodes].sort((a, b) => (around.get(b.id)?.length ?? 0) - (around.get(a.id)?.length ?? 0));
|
|
225
|
+
const footprint = Math.max(...nodes.map((node) => Math.max(node.width, node.height))) + 12;
|
|
226
|
+
const positions = /* @__PURE__ */ new Map();
|
|
227
|
+
let ring = 0;
|
|
228
|
+
let placed = 0;
|
|
229
|
+
while (placed < sorted.length) {
|
|
230
|
+
const radius = ring * footprint;
|
|
231
|
+
const capacity = ring === 0 ? 1 : Math.max(1, Math.floor(Math.PI * 2 * radius / footprint));
|
|
232
|
+
const members = sorted.slice(placed, placed + capacity);
|
|
233
|
+
members.forEach((node, i) => {
|
|
234
|
+
const angle = i / members.length * Math.PI * 2 - Math.PI / 2;
|
|
235
|
+
positions.set(node.id, {
|
|
236
|
+
x: radius * Math.cos(angle),
|
|
237
|
+
y: radius * Math.sin(angle)
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
placed += members.length;
|
|
241
|
+
ring += 1;
|
|
242
|
+
}
|
|
243
|
+
return positions;
|
|
244
|
+
}
|
|
245
|
+
/** 分列:每组一列(没有组的单独一列),列里按重心法排 */
|
|
246
|
+
function parallel(nodes, links, vertical, groups) {
|
|
247
|
+
const byRank = groupRank(groups);
|
|
248
|
+
const columns = [];
|
|
249
|
+
for (const node of nodes) (columns[byRank(node)] ??= []).push(node);
|
|
250
|
+
const filled = columns.filter(Boolean);
|
|
251
|
+
const around = neighbors(nodes, links);
|
|
252
|
+
orderLayers(filled, around, () => 0);
|
|
253
|
+
return placeLayers(filled, around, vertical, Math.max(...nodes.map((node) => vertical ? node.height : node.width)) + 96);
|
|
254
|
+
}
|
|
255
|
+
/** 网络图的布局(见文件开头) */
|
|
256
|
+
function graphLayout(nodes, links, { layout, direction = "LR", groups, linkDistance = 90, charge = -600 }) {
|
|
257
|
+
if (nodes.length === 0) return /* @__PURE__ */ new Map();
|
|
258
|
+
const vertical = direction === "TB";
|
|
259
|
+
if (layout === "layered") return layered(nodes, links, vertical, groups);
|
|
260
|
+
if (layout === "circular") return circular(nodes, groups);
|
|
261
|
+
if (layout === "concentric") return concentric(nodes, links);
|
|
262
|
+
if (layout === "parallel") return parallel(nodes, links, vertical, groups);
|
|
263
|
+
return force(nodes, links, linkDistance, charge);
|
|
264
|
+
}
|
|
265
|
+
//#endregion
|
|
266
|
+
export { graphLayout };
|
|
267
|
+
|
|
268
|
+
//# sourceMappingURL=graph.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph.js","names":[],"sources":["../../../src/charts/layout/graph.ts"],"sourcesContent":["/**\n * 网络图的布局:给每个节点算出坐标(单位是 px,原点任意,画的时候再整体居中、按绘图区缩放)。\n *\n * - force:力导向(d3-force 的做法:连线像弹簧、节点互相排斥、往中心拉、不重叠),从固定的初始位置迭代 300 步,结果是确定的;\n * - layered:分层(Sugiyama 的简化版:去环、按最长路径分层、重心法几轮减少交叉、向邻居的平均位置靠拢),direction 定方向;\n * - circular:排成一圈;concentric:连得多的在中间,一圈圈往外;parallel:按组分列,列里按连接排。\n * 有组时 layered、circular、parallel 让同组的节点挨在一起(组的先后按 groups)。\n *\n * const positions = graphLayout(nodes, links, { layout: 'layered', direction: 'LR', groups: ['a', 'b'] });\n * positions.get('n1') // { x, y }\n */\n\n/** 布局 */\nexport type GraphLayoutName = 'force' | 'layered' | 'circular' | 'concentric' | 'parallel';\n\n/** 布局的输入:一个节点与它占的地方(节点加名字的外框) */\nexport interface GraphLayoutInput {\n /** id */\n id: string;\n /** 所在的组 */\n group?: string;\n /** 占的宽 */\n width: number;\n /** 占的高 */\n height: number;\n}\n\n/** graphLayout 的选项 */\nexport interface GraphLayoutOptions {\n /** 布局 */\n layout: GraphLayoutName;\n /** 分层、分列的方向:LR 从左到右,TB 从上到下 */\n direction?: 'LR' | 'TB';\n /** 组的先后 */\n groups?: readonly string[];\n /** 力导向的连线长度,默认 90 */\n linkDistance?: number;\n /** 力导向的排斥强度(负数),默认 -600 */\n charge?: number;\n}\n\n/** 一个点 */\ninterface Position {\n x: number;\n y: number;\n}\n\ntype Links = readonly { source: string; target: string }[];\n\n/** 节点的邻居:按 id 查 */\nfunction neighbors(nodes: readonly GraphLayoutInput[], links: Links): Map<string, string[]> {\n const map = new Map(nodes.map((node) => [node.id, [] as string[]]));\n for (const link of links) {\n if (link.source === link.target) continue;\n map.get(link.source)?.push(link.target);\n map.get(link.target)?.push(link.source);\n }\n return map;\n}\n\n/** 按组排先后:组在 groups 里的位置,没有组的排最后;同组按原来的顺序 */\nfunction groupRank(groups: readonly string[] | undefined) {\n return (node: GraphLayoutInput) => {\n const index = node.group === undefined ? -1 : (groups ?? []).indexOf(node.group);\n return index < 0 ? (groups?.length ?? 0) : index;\n };\n}\n\n/** 力导向(见文件开头) */\nfunction force(\n nodes: readonly GraphLayoutInput[],\n links: Links,\n distance: number,\n charge: number,\n): Map<string, Position> {\n const index = new Map(nodes.map((node, i) => [node.id, i]));\n // 初始位置:向日葵式的螺旋(同 d3-force),节点均匀铺开,结果每次相同\n const points = nodes.map((_, i) => {\n const radius = 10 * Math.sqrt(0.5 + i);\n const angle = i * Math.PI * (3 - Math.sqrt(5));\n return { x: radius * Math.cos(angle), y: radius * Math.sin(angle), vx: 0, vy: 0 };\n });\n const edges = links.flatMap((link) => {\n const s = index.get(link.source);\n const t = index.get(link.target);\n return s === undefined || t === undefined || s === t ? [] : [[s, t] as const];\n });\n const count = points.map(() => 0);\n for (const [s, t] of edges) {\n count[s]! += 1;\n count[t]! += 1;\n }\n const radii = nodes.map((node) => Math.hypot(node.width, node.height) / 2);\n // 两点重合时给一点确定的偏移,免得除以 0\n const jiggle = (i: number) => (i + 1) * 1e-6;\n const decay = 1 - 0.001 ** (1 / 300);\n let alpha = 1;\n for (let tick = 0; tick < 300; tick += 1) {\n alpha += (0 - alpha) * decay;\n for (const [s, t] of edges) {\n const a = points[s]!;\n const b = points[t]!;\n let dx = b.x + b.vx - a.x - a.vx || jiggle(s);\n let dy = b.y + b.vy - a.y - a.vy || jiggle(t);\n const length = Math.hypot(dx, dy);\n const strength = 1 / Math.min(count[s]!, count[t]!);\n const k = ((length - distance) / length) * alpha * strength;\n const bias = count[s]! / (count[s]! + count[t]!);\n dx *= k;\n dy *= k;\n b.vx -= dx * bias;\n b.vy -= dy * bias;\n a.vx += dx * (1 - bias);\n a.vy += dy * (1 - bias);\n }\n for (let i = 0; i < points.length; i += 1)\n for (let j = 0; j < points.length; j += 1) {\n if (i === j) continue;\n const dx = points[j]!.x - points[i]!.x || jiggle(i);\n const dy = points[j]!.y - points[i]!.y || jiggle(j);\n const squared = Math.max(dx * dx + dy * dy, 1);\n const w = (charge * alpha) / squared;\n points[i]!.vx += dx * w;\n points[i]!.vy += dy * w;\n }\n for (const point of points) {\n point.vx *= 0.6;\n point.vy *= 0.6;\n point.x += point.vx;\n point.y += point.vy;\n }\n // 不重叠:两两推开到外框的半径之和\n for (let i = 0; i < points.length; i += 1)\n for (let j = i + 1; j < points.length; j += 1) {\n const a = points[i]!;\n const b = points[j]!;\n const dx = b.x - a.x || jiggle(i);\n const dy = b.y - a.y || jiggle(j);\n const length = Math.hypot(dx, dy);\n const room = (radii[i]! + radii[j]!) * 0.6;\n if (length >= room) continue;\n const push = ((room - length) / length) * 0.5;\n a.x -= dx * push;\n a.y -= dy * push;\n b.x += dx * push;\n b.y += dy * push;\n }\n }\n return new Map(nodes.map((node, i) => [node.id, { x: points[i]!.x, y: points[i]!.y }]));\n}\n\n/**\n * 分层:每层里节点按 order 的顺序排,层与层之间 rankGap、层内按各自的外框留空;\n * 几轮向邻居的平均位置靠拢(保持顺序、不重叠),连线更直\n */\nfunction placeLayers(\n layers: readonly GraphLayoutInput[][],\n around: Map<string, string[]>,\n vertical: boolean,\n rankGap: number,\n): Map<string, Position> {\n const breadth = (node: GraphLayoutInput) => (vertical ? node.width : node.height) + 16;\n const along = new Map<string, number>();\n // 每层先居中排开\n for (const layer of layers) {\n const total = layer.reduce((sum, node) => sum + breadth(node), 0);\n let position = -total / 2;\n for (const node of layer) {\n along.set(node.id, position + breadth(node) / 2);\n position += breadth(node);\n }\n }\n /** 一层节点挪到邻居的平均位置,再从中间往两头推开、不重叠 */\n const relax = (layer: readonly GraphLayoutInput[]) => {\n const wanted = layer.map((node) => {\n const others = (around.get(node.id) ?? []).filter((id) => along.has(id));\n return others.length\n ? others.reduce((sum, id) => sum + along.get(id)!, 0) / others.length\n : along.get(node.id)!;\n });\n layer.forEach((node, i) => along.set(node.id, wanted[i]!));\n const middle = layer.length >> 1;\n for (let i = middle + 1; i < layer.length; i += 1) {\n const min = along.get(layer[i - 1]!.id)! + (breadth(layer[i - 1]!) + breadth(layer[i]!)) / 2;\n along.set(layer[i]!.id, Math.max(along.get(layer[i]!.id)!, min));\n }\n for (let i = middle - 1; i >= 0; i -= 1) {\n const max = along.get(layer[i + 1]!.id)! - (breadth(layer[i + 1]!) + breadth(layer[i]!)) / 2;\n along.set(layer[i]!.id, Math.min(along.get(layer[i]!.id)!, max));\n }\n };\n for (let round = 0; round < 4; round += 1) {\n for (const layer of layers.slice(1)) relax(layer);\n for (const layer of layers.slice(0, -1).reverse()) relax(layer);\n }\n const positions = new Map<string, Position>();\n layers.forEach((layer, rank) => {\n for (const node of layer) {\n const across = rank * rankGap;\n const value = along.get(node.id)!;\n positions.set(node.id, vertical ? { x: value, y: across } : { x: across, y: value });\n }\n });\n return positions;\n}\n\n/** 重心法排每层的顺序:几轮上下来回,按相邻层里邻居的平均位置排;有组时组优先 */\nfunction orderLayers(\n layers: GraphLayoutInput[][],\n around: Map<string, string[]>,\n rankOf: (node: GraphLayoutInput) => number,\n) {\n const position = new Map<string, number>();\n const record = () =>\n layers.forEach((layer) => layer.forEach((node, i) => position.set(node.id, i)));\n record();\n const sweep = (layer: GraphLayoutInput[], neighborLayer: GraphLayoutInput[]) => {\n const inNeighbor = new Set(neighborLayer.map((node) => node.id));\n const center = (node: GraphLayoutInput) => {\n const others = (around.get(node.id) ?? []).filter((id) => inNeighbor.has(id));\n return others.length\n ? others.reduce((sum, id) => sum + position.get(id)!, 0) / others.length\n : position.get(node.id)!;\n };\n const keys = new Map(layer.map((node) => [node.id, center(node)]));\n layer.sort((a, b) => rankOf(a) - rankOf(b) || keys.get(a.id)! - keys.get(b.id)!);\n layer.forEach((node, i) => position.set(node.id, i));\n };\n for (let round = 0; round < 6; round += 1) {\n for (let i = 1; i < layers.length; i += 1) sweep(layers[i]!, layers[i - 1]!);\n for (let i = layers.length - 2; i >= 0; i -= 1) sweep(layers[i]!, layers[i + 1]!);\n }\n}\n\n/** 分层:去环后按最长路径分层(见文件开头) */\nfunction layered(\n nodes: readonly GraphLayoutInput[],\n links: Links,\n vertical: boolean,\n groups: readonly string[] | undefined,\n): Map<string, Position> {\n const ids = new Set(nodes.map((node) => node.id));\n const outgoing = new Map(nodes.map((node) => [node.id, [] as string[]]));\n // 去环:深度优先,指回栈里节点的边不算(等于把它反过来)\n const state = new Map<string, 'open' | 'done'>();\n const visit = (id: string) => {\n state.set(id, 'open');\n for (const link of links) {\n if (link.source !== id || !ids.has(link.target) || link.target === id) continue;\n const seen = state.get(link.target);\n if (seen === 'open') continue;\n outgoing.get(id)!.push(link.target);\n if (!seen) visit(link.target);\n }\n state.set(id, 'done');\n };\n for (const node of nodes) if (!state.has(node.id)) visit(node.id);\n // 最长路径:按拓扑序,节点的层 = 前驱的层 + 1 的最大值\n const indegree = new Map(nodes.map((node) => [node.id, 0]));\n for (const targets of outgoing.values())\n for (const target of new Set(targets)) indegree.set(target, indegree.get(target)! + 1);\n const rank = new Map(nodes.map((node) => [node.id, 0]));\n const queue = nodes.filter((node) => indegree.get(node.id) === 0).map((node) => node.id);\n while (queue.length) {\n const id = queue.shift()!;\n for (const target of new Set(outgoing.get(id))) {\n rank.set(target, Math.max(rank.get(target)!, rank.get(id)! + 1));\n indegree.set(target, indegree.get(target)! - 1);\n if (indegree.get(target) === 0) queue.push(target);\n }\n }\n const byRank = groupRank(groups);\n const layers: GraphLayoutInput[][] = [];\n for (const node of [...nodes].sort((a, b) => byRank(a) - byRank(b)))\n (layers[rank.get(node.id)!] ??= []).push(node);\n const filled = layers.filter(Boolean);\n const around = neighbors(nodes, links);\n orderLayers(filled, around, byRank);\n // 层间距:横向时要放得下名字(外框的宽),纵向时放得下节点加名字的高\n const extent = Math.max(...nodes.map((node) => (vertical ? node.height : node.width)));\n return placeLayers(filled, around, vertical, extent + (groups?.length ? 72 : 48));\n}\n\n/** 环形:按组、按原来的顺序排成一圈,圈的大小放得下所有外框 */\nfunction circular(nodes: readonly GraphLayoutInput[], groups: readonly string[] | undefined) {\n const byRank = groupRank(groups);\n const sorted = [...nodes].sort((a, b) => byRank(a) - byRank(b));\n const perimeter = sorted.reduce((sum, node) => sum + Math.max(node.width, node.height) + 12, 0);\n const radius = Math.max(perimeter / (Math.PI * 2), 60);\n return new Map(\n sorted.map((node, i) => {\n const angle = (i / sorted.length) * Math.PI * 2 - Math.PI / 2;\n return [node.id, { x: radius * Math.cos(angle), y: radius * Math.sin(angle) }];\n }),\n );\n}\n\n/** 同心圆:连接数最多的在圆心,往外每圈放得下多少放多少 */\nfunction concentric(nodes: readonly GraphLayoutInput[], links: Links) {\n const around = neighbors(nodes, links);\n const sorted = [...nodes].sort(\n (a, b) => (around.get(b.id)?.length ?? 0) - (around.get(a.id)?.length ?? 0),\n );\n const footprint = Math.max(...nodes.map((node) => Math.max(node.width, node.height))) + 12;\n const positions = new Map<string, Position>();\n let ring = 0;\n let placed = 0;\n while (placed < sorted.length) {\n const radius = ring * footprint;\n const capacity = ring === 0 ? 1 : Math.max(1, Math.floor((Math.PI * 2 * radius) / footprint));\n const members = sorted.slice(placed, placed + capacity);\n members.forEach((node, i) => {\n const angle = (i / members.length) * Math.PI * 2 - Math.PI / 2;\n positions.set(node.id, { x: radius * Math.cos(angle), y: radius * Math.sin(angle) });\n });\n placed += members.length;\n ring += 1;\n }\n return positions;\n}\n\n/** 分列:每组一列(没有组的单独一列),列里按重心法排 */\nfunction parallel(\n nodes: readonly GraphLayoutInput[],\n links: Links,\n vertical: boolean,\n groups: readonly string[] | undefined,\n) {\n const byRank = groupRank(groups);\n const columns: GraphLayoutInput[][] = [];\n for (const node of nodes) (columns[byRank(node)] ??= []).push(node);\n const filled = columns.filter(Boolean);\n const around = neighbors(nodes, links);\n orderLayers(filled, around, () => 0);\n const extent = Math.max(...nodes.map((node) => (vertical ? node.height : node.width)));\n return placeLayers(filled, around, vertical, extent + 96);\n}\n\n/** 网络图的布局(见文件开头) */\nexport function graphLayout(\n nodes: readonly GraphLayoutInput[],\n links: Links,\n { layout, direction = 'LR', groups, linkDistance = 90, charge = -600 }: GraphLayoutOptions,\n): Map<string, Position> {\n if (nodes.length === 0) return new Map();\n const vertical = direction === 'TB';\n if (layout === 'layered') return layered(nodes, links, vertical, groups);\n if (layout === 'circular') return circular(nodes, groups);\n if (layout === 'concentric') return concentric(nodes, links);\n if (layout === 'parallel') return parallel(nodes, links, vertical, groups);\n return force(nodes, links, linkDistance, charge);\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 桑基图的布局:节点按离起点的远近分列,列里的节点按流量定高,再几轮松弛让带子尽量平直、互不重叠
|
|
3
|
+
* (d3-sankey 的算法,Mike Bostock,ISC;只留本库用到的部分)。
|
|
4
|
+
*
|
|
5
|
+
* const { nodes, links } = sankeyLayout(['a', 'b', 'c'], [{ source: 'a', target: 'b', value: 3 }, …],
|
|
6
|
+
* { width: 600, height: 320, nodeWidth: 12, nodePadding: 16, align: 'justify' });
|
|
7
|
+
* nodes[0].x0、y0、x1、y1;links[0].y0(起点处带子中线)、y1(终点处)、width(粗细)
|
|
8
|
+
*
|
|
9
|
+
* 流向不能成环:有环时环上的节点停在发现环的那一列。
|
|
10
|
+
*/
|
|
11
|
+
/** 节点的排法:两端对齐、靠左、靠右、居中(同 d3-sankey 的 sankeyJustify 等) */
|
|
12
|
+
export type SankeyAlign = 'justify' | 'left' | 'right' | 'center';
|
|
13
|
+
/** 布局的输入:一条流向 */
|
|
14
|
+
export interface SankeyInputLink {
|
|
15
|
+
/** 起点节点的 id */
|
|
16
|
+
source: string;
|
|
17
|
+
/** 终点节点的 id */
|
|
18
|
+
target: string;
|
|
19
|
+
/** 流量,大于 0 */
|
|
20
|
+
value: number;
|
|
21
|
+
}
|
|
22
|
+
/** 布局后的节点 */
|
|
23
|
+
export interface SankeyLayoutNode {
|
|
24
|
+
/** 节点的 id */
|
|
25
|
+
id: string;
|
|
26
|
+
/** 流量:流入、流出里较大的那个 */
|
|
27
|
+
value: number;
|
|
28
|
+
/** 所在的列,从 0 起 */
|
|
29
|
+
layer: number;
|
|
30
|
+
/** 左 */
|
|
31
|
+
x0: number;
|
|
32
|
+
/** 右 */
|
|
33
|
+
x1: number;
|
|
34
|
+
/** 上 */
|
|
35
|
+
y0: number;
|
|
36
|
+
/** 下 */
|
|
37
|
+
y1: number;
|
|
38
|
+
/** 从这个节点流出的 */
|
|
39
|
+
sourceLinks: SankeyLayoutLink[];
|
|
40
|
+
/** 流入这个节点的 */
|
|
41
|
+
targetLinks: SankeyLayoutLink[];
|
|
42
|
+
}
|
|
43
|
+
/** 布局后的流向 */
|
|
44
|
+
export interface SankeyLayoutLink {
|
|
45
|
+
/** 在输入里的位置 */
|
|
46
|
+
index: number;
|
|
47
|
+
/** 起点 */
|
|
48
|
+
source: SankeyLayoutNode;
|
|
49
|
+
/** 终点 */
|
|
50
|
+
target: SankeyLayoutNode;
|
|
51
|
+
/** 流量 */
|
|
52
|
+
value: number;
|
|
53
|
+
/** 带子的粗细 */
|
|
54
|
+
width: number;
|
|
55
|
+
/** 起点处带子中线的纵坐标 */
|
|
56
|
+
y0: number;
|
|
57
|
+
/** 终点处带子中线的纵坐标 */
|
|
58
|
+
y1: number;
|
|
59
|
+
}
|
|
60
|
+
/** sankeyLayout 的选项 */
|
|
61
|
+
export interface SankeyLayoutOptions {
|
|
62
|
+
/** 布局区域的宽 */
|
|
63
|
+
width: number;
|
|
64
|
+
/** 布局区域的高 */
|
|
65
|
+
height: number;
|
|
66
|
+
/** 节点的宽度 */
|
|
67
|
+
nodeWidth: number;
|
|
68
|
+
/** 同一列里节点的间距 */
|
|
69
|
+
nodePadding: number;
|
|
70
|
+
/** 节点的排法,默认 justify */
|
|
71
|
+
align?: SankeyAlign;
|
|
72
|
+
/** 松弛的轮数,默认 6 */
|
|
73
|
+
iterations?: number;
|
|
74
|
+
}
|
|
75
|
+
/** 桑基图的布局(见文件开头) */
|
|
76
|
+
export declare function sankeyLayout(ids: readonly string[], input: readonly SankeyInputLink[], { width, height, nodeWidth, nodePadding, align, iterations }: SankeyLayoutOptions): {
|
|
77
|
+
nodes: SankeyLayoutNode[];
|
|
78
|
+
links: SankeyLayoutLink[];
|
|
79
|
+
};
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
//#region src/charts/layout/sankey.ts
|
|
2
|
+
/** 节点该排在第几列(共 columns 列) */
|
|
3
|
+
function columnOf(node, columns, align) {
|
|
4
|
+
if (align === "left") return node.depth;
|
|
5
|
+
if (align === "right") return columns - 1 - node.height;
|
|
6
|
+
if (align === "center") {
|
|
7
|
+
if (node.targetLinks.length) return node.depth;
|
|
8
|
+
return node.sourceLinks.length ? Math.min(...node.sourceLinks.map((link) => link.target.depth)) - 1 : 0;
|
|
9
|
+
}
|
|
10
|
+
return node.sourceLinks.length ? node.depth : columns - 1;
|
|
11
|
+
}
|
|
12
|
+
/** 从起点(或终点)一层层往外数:depth 是离起点的步数,height 是离终点的步数 */
|
|
13
|
+
function assignDepths(nodes, forward) {
|
|
14
|
+
let current = new Set(nodes);
|
|
15
|
+
for (let step = 0; current.size && step <= nodes.length; step += 1) {
|
|
16
|
+
const next = /* @__PURE__ */ new Set();
|
|
17
|
+
for (const node of current) {
|
|
18
|
+
if (forward) node.depth = step;
|
|
19
|
+
else node.height = step;
|
|
20
|
+
for (const link of forward ? node.sourceLinks : node.targetLinks) next.add(forward ? link.target : link.source);
|
|
21
|
+
}
|
|
22
|
+
current = next;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
var byBreadth = (a, b) => a.y0 - b.y0;
|
|
26
|
+
var bySourceBreadth = (a, b) => byBreadth(a.source, b.source) || a.index - b.index;
|
|
27
|
+
var byTargetBreadth = (a, b) => byBreadth(a.target, b.target) || a.index - b.index;
|
|
28
|
+
/** 桑基图的布局(见文件开头) */
|
|
29
|
+
function sankeyLayout(ids, input, { width, height, nodeWidth, nodePadding, align = "justify", iterations = 6 }) {
|
|
30
|
+
const nodes = ids.map((id) => ({
|
|
31
|
+
id,
|
|
32
|
+
value: 0,
|
|
33
|
+
layer: 0,
|
|
34
|
+
depth: 0,
|
|
35
|
+
height: 0,
|
|
36
|
+
x0: 0,
|
|
37
|
+
x1: 0,
|
|
38
|
+
y0: 0,
|
|
39
|
+
y1: 0,
|
|
40
|
+
sourceLinks: [],
|
|
41
|
+
targetLinks: []
|
|
42
|
+
}));
|
|
43
|
+
const byId = new Map(nodes.map((node) => [node.id, node]));
|
|
44
|
+
const links = input.flatMap((link, index) => {
|
|
45
|
+
const source = byId.get(link.source);
|
|
46
|
+
const target = byId.get(link.target);
|
|
47
|
+
if (!source || !target || !(link.value > 0)) return [];
|
|
48
|
+
const laid = {
|
|
49
|
+
index,
|
|
50
|
+
source,
|
|
51
|
+
target,
|
|
52
|
+
value: link.value,
|
|
53
|
+
width: 0,
|
|
54
|
+
y0: 0,
|
|
55
|
+
y1: 0
|
|
56
|
+
};
|
|
57
|
+
source.sourceLinks.push(laid);
|
|
58
|
+
target.targetLinks.push(laid);
|
|
59
|
+
return [laid];
|
|
60
|
+
});
|
|
61
|
+
const total = (list) => list.reduce((sum, link) => sum + link.value, 0);
|
|
62
|
+
for (const node of nodes) node.value = Math.max(total(node.sourceLinks), total(node.targetLinks));
|
|
63
|
+
assignDepths(nodes, true);
|
|
64
|
+
assignDepths(nodes, false);
|
|
65
|
+
const columnCount = Math.max(0, ...nodes.map((node) => node.depth)) + 1;
|
|
66
|
+
const kx = columnCount > 1 ? (width - nodeWidth) / (columnCount - 1) : 0;
|
|
67
|
+
const columns = Array.from({ length: columnCount }, () => []);
|
|
68
|
+
for (const node of nodes) {
|
|
69
|
+
const layer = Math.max(0, Math.min(columnCount - 1, Math.floor(columnOf(node, columnCount, align))));
|
|
70
|
+
node.layer = layer;
|
|
71
|
+
node.x0 = layer * kx;
|
|
72
|
+
node.x1 = node.x0 + nodeWidth;
|
|
73
|
+
columns[layer].push(node);
|
|
74
|
+
}
|
|
75
|
+
const longest = Math.max(1, ...columns.map((column) => column.length));
|
|
76
|
+
const py = longest > 1 ? Math.min(nodePadding, height / (longest - 1)) : nodePadding;
|
|
77
|
+
const reorderLinks = (node) => {
|
|
78
|
+
node.sourceLinks.sort(byTargetBreadth);
|
|
79
|
+
node.targetLinks.sort(bySourceBreadth);
|
|
80
|
+
};
|
|
81
|
+
const reorderNodeLinks = (node) => {
|
|
82
|
+
for (const link of node.targetLinks) link.source.sourceLinks.sort(byTargetBreadth);
|
|
83
|
+
for (const link of node.sourceLinks) link.target.targetLinks.sort(bySourceBreadth);
|
|
84
|
+
};
|
|
85
|
+
const ky = Math.min(...columns.filter((column) => column.length).map((column) => (height - (column.length - 1) * py) / (column.reduce((sum, node) => sum + node.value, 0) || 1)));
|
|
86
|
+
for (const column of columns) {
|
|
87
|
+
let y = 0;
|
|
88
|
+
for (const node of column) {
|
|
89
|
+
node.y0 = y;
|
|
90
|
+
node.y1 = y + node.value * ky;
|
|
91
|
+
y = node.y1 + py;
|
|
92
|
+
for (const link of node.sourceLinks) link.width = link.value * ky;
|
|
93
|
+
}
|
|
94
|
+
const spread = (height - y + py) / (column.length + 1);
|
|
95
|
+
column.forEach((node, i) => {
|
|
96
|
+
node.y0 += spread * (i + 1);
|
|
97
|
+
node.y1 += spread * (i + 1);
|
|
98
|
+
});
|
|
99
|
+
column.forEach(reorderLinks);
|
|
100
|
+
}
|
|
101
|
+
/** 让 source → target 的带子平直时 target 的 y0(同 d3-sankey 的 targetTop) */
|
|
102
|
+
const targetTop = (source, target) => {
|
|
103
|
+
let y = source.y0 - (source.sourceLinks.length - 1) * py / 2;
|
|
104
|
+
for (const link of source.sourceLinks) {
|
|
105
|
+
if (link.target === target) break;
|
|
106
|
+
y += link.width + py;
|
|
107
|
+
}
|
|
108
|
+
for (const link of target.targetLinks) {
|
|
109
|
+
if (link.source === source) break;
|
|
110
|
+
y -= link.width;
|
|
111
|
+
}
|
|
112
|
+
return y;
|
|
113
|
+
};
|
|
114
|
+
/** 让 source → target 的带子平直时 source 的 y0 */
|
|
115
|
+
const sourceTop = (source, target) => {
|
|
116
|
+
let y = target.y0 - (target.targetLinks.length - 1) * py / 2;
|
|
117
|
+
for (const link of target.targetLinks) {
|
|
118
|
+
if (link.source === source) break;
|
|
119
|
+
y += link.width + py;
|
|
120
|
+
}
|
|
121
|
+
for (const link of source.sourceLinks) {
|
|
122
|
+
if (link.target === target) break;
|
|
123
|
+
y -= link.width;
|
|
124
|
+
}
|
|
125
|
+
return y;
|
|
126
|
+
};
|
|
127
|
+
const shift = (node, dy) => {
|
|
128
|
+
node.y0 += dy;
|
|
129
|
+
node.y1 += dy;
|
|
130
|
+
};
|
|
131
|
+
/** 列里的节点互不重叠、不出上下边:从中间的节点往两头推,再从两头往回推 */
|
|
132
|
+
const resolveCollisions = (column, alpha) => {
|
|
133
|
+
const pushDown = (from, start) => {
|
|
134
|
+
let y = start;
|
|
135
|
+
for (let i = from; i < column.length; i += 1) {
|
|
136
|
+
const dy = (y - column[i].y0) * alpha;
|
|
137
|
+
if (dy > 1e-6) shift(column[i], dy);
|
|
138
|
+
y = column[i].y1 + py;
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
const pushUp = (from, start) => {
|
|
142
|
+
let y = start;
|
|
143
|
+
for (let i = from; i >= 0; i -= 1) {
|
|
144
|
+
const dy = (column[i].y1 - y) * alpha;
|
|
145
|
+
if (dy > 1e-6) shift(column[i], -dy);
|
|
146
|
+
y = column[i].y0 - py;
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
const middle = column.length >> 1;
|
|
150
|
+
const subject = column[middle];
|
|
151
|
+
if (!subject) return;
|
|
152
|
+
pushUp(middle - 1, subject.y0 - py);
|
|
153
|
+
pushDown(middle + 1, subject.y1 + py);
|
|
154
|
+
pushUp(column.length - 1, height);
|
|
155
|
+
pushDown(0, 0);
|
|
156
|
+
};
|
|
157
|
+
/** 一列节点朝着与它相连的另一侧节点挪(按流量加权),再消除重叠 */
|
|
158
|
+
const relax = (column, alpha, beta, fromLeft) => {
|
|
159
|
+
for (const node of column) {
|
|
160
|
+
let y = 0;
|
|
161
|
+
let weight = 0;
|
|
162
|
+
for (const link of fromLeft ? node.targetLinks : node.sourceLinks) {
|
|
163
|
+
const v = link.value * (link.target.layer - link.source.layer);
|
|
164
|
+
y += (fromLeft ? targetTop(link.source, node) : sourceTop(node, link.target)) * v;
|
|
165
|
+
weight += v;
|
|
166
|
+
}
|
|
167
|
+
if (!(weight > 0)) continue;
|
|
168
|
+
shift(node, (y / weight - node.y0) * alpha);
|
|
169
|
+
reorderNodeLinks(node);
|
|
170
|
+
}
|
|
171
|
+
column.sort(byBreadth);
|
|
172
|
+
resolveCollisions(column, beta);
|
|
173
|
+
};
|
|
174
|
+
for (let i = 0; i < iterations; i += 1) {
|
|
175
|
+
const alpha = .99 ** i;
|
|
176
|
+
const beta = Math.max(1 - alpha, (i + 1) / iterations);
|
|
177
|
+
for (let c = columns.length - 2; c >= 0; c -= 1) relax(columns[c], alpha, beta, false);
|
|
178
|
+
for (let c = 1; c < columns.length; c += 1) relax(columns[c], alpha, beta, true);
|
|
179
|
+
}
|
|
180
|
+
for (const node of nodes) {
|
|
181
|
+
let y0 = node.y0;
|
|
182
|
+
let y1 = node.y0;
|
|
183
|
+
for (const link of node.sourceLinks) {
|
|
184
|
+
link.y0 = y0 + link.width / 2;
|
|
185
|
+
y0 += link.width;
|
|
186
|
+
}
|
|
187
|
+
for (const link of node.targetLinks) {
|
|
188
|
+
link.y1 = y1 + link.width / 2;
|
|
189
|
+
y1 += link.width;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return {
|
|
193
|
+
nodes,
|
|
194
|
+
links
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
//#endregion
|
|
198
|
+
export { sankeyLayout };
|
|
199
|
+
|
|
200
|
+
//# sourceMappingURL=sankey.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sankey.js","names":[],"sources":["../../../src/charts/layout/sankey.ts"],"sourcesContent":["/**\n * 桑基图的布局:节点按离起点的远近分列,列里的节点按流量定高,再几轮松弛让带子尽量平直、互不重叠\n * (d3-sankey 的算法,Mike Bostock,ISC;只留本库用到的部分)。\n *\n * const { nodes, links } = sankeyLayout(['a', 'b', 'c'], [{ source: 'a', target: 'b', value: 3 }, …],\n * { width: 600, height: 320, nodeWidth: 12, nodePadding: 16, align: 'justify' });\n * nodes[0].x0、y0、x1、y1;links[0].y0(起点处带子中线)、y1(终点处)、width(粗细)\n *\n * 流向不能成环:有环时环上的节点停在发现环的那一列。\n */\n\n/** 节点的排法:两端对齐、靠左、靠右、居中(同 d3-sankey 的 sankeyJustify 等) */\nexport type SankeyAlign = 'justify' | 'left' | 'right' | 'center';\n\n/** 布局的输入:一条流向 */\nexport interface SankeyInputLink {\n /** 起点节点的 id */\n source: string;\n /** 终点节点的 id */\n target: string;\n /** 流量,大于 0 */\n value: number;\n}\n\n/** 布局后的节点 */\nexport interface SankeyLayoutNode {\n /** 节点的 id */\n id: string;\n /** 流量:流入、流出里较大的那个 */\n value: number;\n /** 所在的列,从 0 起 */\n layer: number;\n /** 左 */\n x0: number;\n /** 右 */\n x1: number;\n /** 上 */\n y0: number;\n /** 下 */\n y1: number;\n /** 从这个节点流出的 */\n sourceLinks: SankeyLayoutLink[];\n /** 流入这个节点的 */\n targetLinks: SankeyLayoutLink[];\n}\n\n/** 布局后的流向 */\nexport interface SankeyLayoutLink {\n /** 在输入里的位置 */\n index: number;\n /** 起点 */\n source: SankeyLayoutNode;\n /** 终点 */\n target: SankeyLayoutNode;\n /** 流量 */\n value: number;\n /** 带子的粗细 */\n width: number;\n /** 起点处带子中线的纵坐标 */\n y0: number;\n /** 终点处带子中线的纵坐标 */\n y1: number;\n}\n\n/** sankeyLayout 的选项 */\nexport interface SankeyLayoutOptions {\n /** 布局区域的宽 */\n width: number;\n /** 布局区域的高 */\n height: number;\n /** 节点的宽度 */\n nodeWidth: number;\n /** 同一列里节点的间距 */\n nodePadding: number;\n /** 节点的排法,默认 justify */\n align?: SankeyAlign;\n /** 松弛的轮数,默认 6 */\n iterations?: number;\n}\n\ntype Node = SankeyLayoutNode & { depth: number; height: number };\n\n/** 节点该排在第几列(共 columns 列) */\nfunction columnOf(node: Node, columns: number, align: SankeyAlign): number {\n if (align === 'left') return node.depth;\n if (align === 'right') return columns - 1 - node.height;\n if (align === 'center') {\n if (node.targetLinks.length) return node.depth;\n return node.sourceLinks.length\n ? Math.min(...node.sourceLinks.map((link) => (link.target as Node).depth)) - 1\n : 0;\n }\n return node.sourceLinks.length ? node.depth : columns - 1;\n}\n\n/** 从起点(或终点)一层层往外数:depth 是离起点的步数,height 是离终点的步数 */\nfunction assignDepths(nodes: readonly Node[], forward: boolean) {\n let current = new Set(nodes);\n for (let step = 0; current.size && step <= nodes.length; step += 1) {\n const next = new Set<Node>();\n for (const node of current) {\n if (forward) node.depth = step;\n else node.height = step;\n for (const link of forward ? node.sourceLinks : node.targetLinks)\n next.add((forward ? link.target : link.source) as Node);\n }\n current = next;\n }\n}\n\nconst byBreadth = (a: SankeyLayoutNode, b: SankeyLayoutNode) => a.y0 - b.y0;\nconst bySourceBreadth = (a: SankeyLayoutLink, b: SankeyLayoutLink) =>\n byBreadth(a.source, b.source) || a.index - b.index;\nconst byTargetBreadth = (a: SankeyLayoutLink, b: SankeyLayoutLink) =>\n byBreadth(a.target, b.target) || a.index - b.index;\n\n/** 桑基图的布局(见文件开头) */\nexport function sankeyLayout(\n ids: readonly string[],\n input: readonly SankeyInputLink[],\n { width, height, nodeWidth, nodePadding, align = 'justify', iterations = 6 }: SankeyLayoutOptions,\n): { nodes: SankeyLayoutNode[]; links: SankeyLayoutLink[] } {\n const nodes: Node[] = ids.map((id) => ({\n id,\n value: 0,\n layer: 0,\n depth: 0,\n height: 0,\n x0: 0,\n x1: 0,\n y0: 0,\n y1: 0,\n sourceLinks: [],\n targetLinks: [],\n }));\n const byId = new Map(nodes.map((node) => [node.id, node]));\n const links: SankeyLayoutLink[] = input.flatMap((link, index) => {\n const source = byId.get(link.source);\n const target = byId.get(link.target);\n if (!source || !target || !(link.value > 0)) return [];\n const laid = { index, source, target, value: link.value, width: 0, y0: 0, y1: 0 };\n source.sourceLinks.push(laid);\n target.targetLinks.push(laid);\n return [laid];\n });\n const total = (list: readonly SankeyLayoutLink[]) =>\n list.reduce((sum, link) => sum + link.value, 0);\n for (const node of nodes) node.value = Math.max(total(node.sourceLinks), total(node.targetLinks));\n assignDepths(nodes, true);\n assignDepths(nodes, false);\n\n // 分列:横坐标按列均分\n const columnCount = Math.max(0, ...nodes.map((node) => node.depth)) + 1;\n const kx = columnCount > 1 ? (width - nodeWidth) / (columnCount - 1) : 0;\n const columns: Node[][] = Array.from({ length: columnCount }, () => []);\n for (const node of nodes) {\n const layer = Math.max(\n 0,\n Math.min(columnCount - 1, Math.floor(columnOf(node, columnCount, align))),\n );\n node.layer = layer;\n node.x0 = layer * kx;\n node.x1 = node.x0 + nodeWidth;\n columns[layer]!.push(node);\n }\n\n const longest = Math.max(1, ...columns.map((column) => column.length));\n const py = longest > 1 ? Math.min(nodePadding, height / (longest - 1)) : nodePadding;\n\n const reorderLinks = (node: SankeyLayoutNode) => {\n node.sourceLinks.sort(byTargetBreadth);\n node.targetLinks.sort(bySourceBreadth);\n };\n // 节点挪动之后,与它相连的节点上的带子顺序跟着变\n const reorderNodeLinks = (node: SankeyLayoutNode) => {\n for (const link of node.targetLinks) link.source.sourceLinks.sort(byTargetBreadth);\n for (const link of node.sourceLinks) link.target.targetLinks.sort(bySourceBreadth);\n };\n\n // 初始:按流量定高(各列取能放下的最小比例),列里从上往下排,余下的空均分到间隔里\n const ky = Math.min(\n ...columns\n .filter((column) => column.length)\n .map(\n (column) =>\n (height - (column.length - 1) * py) /\n (column.reduce((sum, node) => sum + node.value, 0) || 1),\n ),\n );\n for (const column of columns) {\n let y = 0;\n for (const node of column) {\n node.y0 = y;\n node.y1 = y + node.value * ky;\n y = node.y1 + py;\n for (const link of node.sourceLinks) link.width = link.value * ky;\n }\n const spread = (height - y + py) / (column.length + 1);\n column.forEach((node, i) => {\n node.y0 += spread * (i + 1);\n node.y1 += spread * (i + 1);\n });\n column.forEach(reorderLinks);\n }\n\n /** 让 source → target 的带子平直时 target 的 y0(同 d3-sankey 的 targetTop) */\n const targetTop = (source: SankeyLayoutNode, target: SankeyLayoutNode) => {\n let y = source.y0 - ((source.sourceLinks.length - 1) * py) / 2;\n for (const link of source.sourceLinks) {\n if (link.target === target) break;\n y += link.width + py;\n }\n for (const link of target.targetLinks) {\n if (link.source === source) break;\n y -= link.width;\n }\n return y;\n };\n /** 让 source → target 的带子平直时 source 的 y0 */\n const sourceTop = (source: SankeyLayoutNode, target: SankeyLayoutNode) => {\n let y = target.y0 - ((target.targetLinks.length - 1) * py) / 2;\n for (const link of target.targetLinks) {\n if (link.source === source) break;\n y += link.width + py;\n }\n for (const link of source.sourceLinks) {\n if (link.target === target) break;\n y -= link.width;\n }\n return y;\n };\n\n const shift = (node: SankeyLayoutNode, dy: number) => {\n node.y0 += dy;\n node.y1 += dy;\n };\n /** 列里的节点互不重叠、不出上下边:从中间的节点往两头推,再从两头往回推 */\n const resolveCollisions = (column: SankeyLayoutNode[], alpha: number) => {\n const pushDown = (from: number, start: number) => {\n let y = start;\n for (let i = from; i < column.length; i += 1) {\n const dy = (y - column[i]!.y0) * alpha;\n if (dy > 1e-6) shift(column[i]!, dy);\n y = column[i]!.y1 + py;\n }\n };\n const pushUp = (from: number, start: number) => {\n let y = start;\n for (let i = from; i >= 0; i -= 1) {\n const dy = (column[i]!.y1 - y) * alpha;\n if (dy > 1e-6) shift(column[i]!, -dy);\n y = column[i]!.y0 - py;\n }\n };\n const middle = column.length >> 1;\n const subject = column[middle];\n if (!subject) return;\n pushUp(middle - 1, subject.y0 - py);\n pushDown(middle + 1, subject.y1 + py);\n pushUp(column.length - 1, height);\n pushDown(0, 0);\n };\n\n /** 一列节点朝着与它相连的另一侧节点挪(按流量加权),再消除重叠 */\n const relax = (column: Node[], alpha: number, beta: number, fromLeft: boolean) => {\n for (const node of column) {\n let y = 0;\n let weight = 0;\n for (const link of fromLeft ? node.targetLinks : node.sourceLinks) {\n const v = link.value * (link.target.layer - link.source.layer);\n y += (fromLeft ? targetTop(link.source, node) : sourceTop(node, link.target)) * v;\n weight += v;\n }\n if (!(weight > 0)) continue;\n shift(node, (y / weight - node.y0) * alpha);\n reorderNodeLinks(node);\n }\n column.sort(byBreadth);\n resolveCollisions(column, beta);\n };\n for (let i = 0; i < iterations; i += 1) {\n const alpha = 0.99 ** i;\n const beta = Math.max(1 - alpha, (i + 1) / iterations);\n for (let c = columns.length - 2; c >= 0; c -= 1) relax(columns[c]!, alpha, beta, false);\n for (let c = 1; c < columns.length; c += 1) relax(columns[c]!, alpha, beta, true);\n }\n\n // 带子在两端节点上的位置:按另一端的上下顺序依次排开\n for (const node of nodes) {\n let y0 = node.y0;\n let y1 = node.y0;\n for (const link of node.sourceLinks) {\n link.y0 = y0 + link.width / 2;\n y0 += link.width;\n }\n for (const link of node.targetLinks) {\n link.y1 = y1 + link.width / 2;\n y1 += link.width;\n }\n }\n return { nodes, links };\n}\n"],"mappings":"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"}
|