@progress/kendo-charts 2.3.0-dev.202402161315 → 2.3.0-dev.202403071434
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/cdn/js/kendo-charts.js +1 -1
- package/dist/cdn/main.js +1 -1
- package/dist/es/sankey/calculation.js +252 -29
- package/dist/es/sankey/link.js +4 -4
- package/dist/es/sankey/sankey.js +72 -13
- package/dist/es/sankey/title.js +0 -1
- package/dist/es2015/sankey/calculation.js +235 -27
- package/dist/es2015/sankey/link.js +3 -3
- package/dist/es2015/sankey/sankey.js +72 -14
- package/dist/es2015/sankey/title.js +0 -1
- package/dist/npm/main.js +328 -47
- package/dist/npm/sankey.d.ts +8 -0
- package/dist/systemjs/kendo-charts.js +1 -1
- package/package.json +1 -1
|
@@ -3,24 +3,39 @@ import { deepExtend } from '../common';
|
|
|
3
3
|
const max = (array, mapFn) => Math.max.apply(null, array.map(mapFn));
|
|
4
4
|
const min = (array, mapFn) => Math.min.apply(null, array.map(mapFn));
|
|
5
5
|
const sum = (array, mapFn) => array.map(mapFn).reduce((acc, curr) => (acc + curr), 0);
|
|
6
|
-
const sortAsc = (a, b) => a.y0 - b.y0;
|
|
6
|
+
const sortAsc = (a, b) => (a.y0 === b.y0 ? a.index - b.index : a.y0 + a.y1 - b.y0 - b.y1);
|
|
7
7
|
const sortSource = (a, b) => sortAsc(a.source, b.source);
|
|
8
8
|
const sortTarget = (a, b) => sortAsc(a.target, b.target);
|
|
9
9
|
const value = (node) => node.value;
|
|
10
10
|
|
|
11
11
|
function sortLinks(nodes) {
|
|
12
|
-
nodes.forEach(
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
nodes.forEach(node => {
|
|
13
|
+
node.targetLinks.forEach(link => {
|
|
14
|
+
link.source.sourceLinks.sort(sortTarget);
|
|
15
|
+
});
|
|
16
|
+
node.sourceLinks.forEach(link => {
|
|
17
|
+
link.target.targetLinks.sort(sortSource);
|
|
18
|
+
});
|
|
16
19
|
});
|
|
17
20
|
}
|
|
18
21
|
|
|
22
|
+
const calcLayer = (node, maxDepth) => {
|
|
23
|
+
if (node.align === 'left') {
|
|
24
|
+
return node.depth;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (node.align === 'right') {
|
|
28
|
+
return maxDepth - node.height;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return node.sourceLinks.length ? node.depth : maxDepth;
|
|
32
|
+
};
|
|
33
|
+
|
|
19
34
|
class Sankey {
|
|
20
35
|
constructor(options) {
|
|
21
|
-
const offset = options.nodesOptions
|
|
36
|
+
const { offset = {}, align } = options.nodesOptions;
|
|
22
37
|
this.data = {
|
|
23
|
-
nodes: options.nodes.map((node) => deepExtend({}, { offset }, node)),
|
|
38
|
+
nodes: options.nodes.map((node) => deepExtend({}, { offset, align }, node)),
|
|
24
39
|
links: options.links.map((link) => deepExtend({}, link))
|
|
25
40
|
};
|
|
26
41
|
|
|
@@ -30,6 +45,10 @@ class Sankey {
|
|
|
30
45
|
this.offsetY = options.offsetY || 0;
|
|
31
46
|
this.nodeWidth = options.nodesOptions.width;
|
|
32
47
|
this.nodePadding = options.nodesOptions.padding;
|
|
48
|
+
this.reverse = options.reverse;
|
|
49
|
+
this.targetColumnIndex = options.targetColumnIndex;
|
|
50
|
+
this.loops = options.loops;
|
|
51
|
+
this.autoLayout = options.autoLayout;
|
|
33
52
|
}
|
|
34
53
|
|
|
35
54
|
calculate() {
|
|
@@ -37,19 +56,21 @@ class Sankey {
|
|
|
37
56
|
this.connectLinksToNodes(nodes, links);
|
|
38
57
|
this.calculateNodeValues(nodes);
|
|
39
58
|
this.calculateNodeDepths(nodes);
|
|
59
|
+
this.calculateNodeHeights(nodes);
|
|
40
60
|
|
|
41
61
|
const columns = this.calculateNodeColumns(nodes);
|
|
42
62
|
this.calculateNodeBreadths(columns);
|
|
43
63
|
this.applyNodesOffset(nodes);
|
|
44
64
|
this.calculateLinkBreadths(nodes);
|
|
45
65
|
|
|
46
|
-
return this.data;
|
|
66
|
+
return Object.assign({}, this.data, {columns});
|
|
47
67
|
}
|
|
48
68
|
|
|
49
69
|
connectLinksToNodes(nodes, links) {
|
|
50
70
|
const nodesMap = new Map();
|
|
51
71
|
|
|
52
|
-
nodes.forEach((node) => {
|
|
72
|
+
nodes.forEach((node, i) => {
|
|
73
|
+
node.index = i;
|
|
53
74
|
node.sourceLinks = [];
|
|
54
75
|
node.targetLinks = [];
|
|
55
76
|
node.id = node.id !== undefined ? node.id : node.label.text;
|
|
@@ -93,17 +114,36 @@ class Sankey {
|
|
|
93
114
|
}
|
|
94
115
|
}
|
|
95
116
|
|
|
117
|
+
calculateNodeHeights(nodes) {
|
|
118
|
+
let current = new Set(nodes);
|
|
119
|
+
let next = new Set;
|
|
120
|
+
let x = 0;
|
|
121
|
+
const eachNode = (node) => {
|
|
122
|
+
node.height = x;
|
|
123
|
+
node.targetLinks.forEach((link) => {
|
|
124
|
+
next.add(link.source);
|
|
125
|
+
});
|
|
126
|
+
};
|
|
127
|
+
while (current.size) {
|
|
128
|
+
current.forEach(eachNode);
|
|
129
|
+
x++;
|
|
130
|
+
current = next;
|
|
131
|
+
next = new Set;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
96
135
|
calculateNodeColumns(nodes) {
|
|
97
136
|
const maxDepth = max(nodes, (d) => d.depth);
|
|
98
137
|
const columnWidth = (this.width - this.offsetX - this.nodeWidth) / maxDepth;
|
|
99
138
|
const columns = new Array(maxDepth + 1);
|
|
100
139
|
for (let i = 0; i < nodes.length; i++) {
|
|
101
140
|
const node = nodes[i];
|
|
102
|
-
const
|
|
103
|
-
node.x0 = this.offsetX +
|
|
141
|
+
const layer = Math.max(0, Math.min(maxDepth, calcLayer(node, maxDepth)));
|
|
142
|
+
node.x0 = this.offsetX + layer * columnWidth;
|
|
104
143
|
node.x1 = node.x0 + this.nodeWidth;
|
|
105
|
-
|
|
106
|
-
columns[
|
|
144
|
+
node.layer = layer;
|
|
145
|
+
columns[layer] = columns[layer] || [];
|
|
146
|
+
columns[layer].push(node);
|
|
107
147
|
}
|
|
108
148
|
|
|
109
149
|
return columns;
|
|
@@ -112,31 +152,39 @@ class Sankey {
|
|
|
112
152
|
calculateNodeBreadths(columns) {
|
|
113
153
|
const kSize = min(columns, (c) => (this.height - this.offsetY - (c.length - 1) * this.nodePadding) / sum(c, value));
|
|
114
154
|
|
|
115
|
-
|
|
116
|
-
const nodes = columns[c];
|
|
155
|
+
columns.forEach(nodes => {
|
|
117
156
|
let y = this.offsetY;
|
|
118
|
-
|
|
119
|
-
const node = nodes[i];
|
|
157
|
+
nodes.forEach((node) => {
|
|
120
158
|
node.y0 = y;
|
|
121
159
|
node.y1 = y + node.value * kSize;
|
|
122
160
|
y = node.y1 + this.nodePadding;
|
|
123
|
-
|
|
124
|
-
const link = node.sourceLinks[l];
|
|
161
|
+
node.sourceLinks.forEach((link) => {
|
|
125
162
|
link.width = link.value * kSize;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
163
|
+
});
|
|
164
|
+
});
|
|
129
165
|
y = (this.height - y + this.nodePadding) / (nodes.length + 1);
|
|
130
|
-
|
|
131
|
-
const node = nodes[i];
|
|
166
|
+
nodes.forEach((node, i) => {
|
|
132
167
|
node.y0 += y * (i + 1);
|
|
133
168
|
node.y1 += y * (i + 1);
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
if (this.autoLayout !== false) {
|
|
173
|
+
const loops = this.loops !== undefined ? this.loops : columns.length - 1;
|
|
174
|
+
const targetColumnIndex = this.targetColumnIndex || 1;
|
|
175
|
+
|
|
176
|
+
for (let i = 0; i < loops; i++) {
|
|
177
|
+
if (!this.reverse) {
|
|
178
|
+
this.uncurlLinksToLeft(columns, targetColumnIndex);
|
|
179
|
+
this.uncurlLinksToRight(columns, targetColumnIndex);
|
|
180
|
+
} else {
|
|
181
|
+
this.uncurlLinksToRight(columns, targetColumnIndex);
|
|
182
|
+
this.uncurlLinksToLeft(columns, targetColumnIndex);
|
|
183
|
+
}
|
|
134
184
|
}
|
|
135
185
|
}
|
|
136
186
|
|
|
137
|
-
|
|
138
|
-
sortLinks(columns[c]);
|
|
139
|
-
}
|
|
187
|
+
columns.forEach(sortLinks);
|
|
140
188
|
}
|
|
141
189
|
|
|
142
190
|
applyNodesOffset(nodes) {
|
|
@@ -156,15 +204,175 @@ class Sankey {
|
|
|
156
204
|
let y = node.y0;
|
|
157
205
|
let y1 = y;
|
|
158
206
|
sourceLinks.forEach((link) => {
|
|
207
|
+
link.x0 = link.source.x1;
|
|
159
208
|
link.y0 = y + link.width / 2;
|
|
160
209
|
y += link.width;
|
|
161
210
|
});
|
|
162
211
|
targetLinks.forEach((link) => {
|
|
212
|
+
link.x1 = link.target.x0;
|
|
163
213
|
link.y1 = y1 + link.width / 2;
|
|
164
214
|
y1 += link.width;
|
|
165
215
|
});
|
|
166
216
|
});
|
|
167
217
|
}
|
|
218
|
+
|
|
219
|
+
uncurlLinksToRight(columns, targetColumnIndex) {
|
|
220
|
+
const n = columns.length;
|
|
221
|
+
for (let i = targetColumnIndex; i < n; i++) {
|
|
222
|
+
const column = columns[i];
|
|
223
|
+
column.forEach((target) => {
|
|
224
|
+
let y = 0;
|
|
225
|
+
let sum = 0;
|
|
226
|
+
target.targetLinks.forEach((link) => {
|
|
227
|
+
let kValue = link.value * (target.layer - link.source.layer);
|
|
228
|
+
y += this.targetTopPos(link.source, target) * kValue;
|
|
229
|
+
sum += kValue;
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
let dy = y === 0 ? 0 : (y / sum - target.y0);
|
|
233
|
+
target.y0 += dy;
|
|
234
|
+
target.y1 += dy;
|
|
235
|
+
sortLinks([target]);
|
|
236
|
+
});
|
|
237
|
+
column.sort(sortAsc);
|
|
238
|
+
this.arrangeNodesVertically(column);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
uncurlLinksToLeft(columns, targetColumnIndex) {
|
|
243
|
+
const l = columns.length;
|
|
244
|
+
const startIndex = l - 1 - targetColumnIndex;
|
|
245
|
+
for (let i = startIndex; i >= 0; i--) {
|
|
246
|
+
const column = columns[i];
|
|
247
|
+
for (let j = 0; j < column.length; j++) {
|
|
248
|
+
const source = column[j];
|
|
249
|
+
let y = 0;
|
|
250
|
+
let sum = 0;
|
|
251
|
+
source.sourceLinks.forEach((link) => {
|
|
252
|
+
let kValue = link.value * (link.target.layer - source.layer);
|
|
253
|
+
y += this.sourceTopPos(source, link.target) * kValue;
|
|
254
|
+
sum += kValue;
|
|
255
|
+
});
|
|
256
|
+
let dy = y === 0 ? 0 : (y / sum - source.y0);
|
|
257
|
+
source.y0 += dy;
|
|
258
|
+
source.y1 += dy;
|
|
259
|
+
sortLinks([source]);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
column.sort(sortAsc);
|
|
263
|
+
this.arrangeNodesVertically(column);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
arrangeNodesVertically(nodes) {
|
|
268
|
+
const startIndex = 0;
|
|
269
|
+
const endIndex = nodes.length - 1;
|
|
270
|
+
|
|
271
|
+
this.arrangeUp(nodes, this.height, endIndex);
|
|
272
|
+
this.arrangeDown(nodes, this.offsetY, startIndex);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
arrangeDown(nodes, yPos, index) {
|
|
276
|
+
let currentY = yPos;
|
|
277
|
+
|
|
278
|
+
for (let i = index; i < nodes.length; i++) {
|
|
279
|
+
const node = nodes[i];
|
|
280
|
+
const dy = Math.max(0, currentY - node.y0);
|
|
281
|
+
node.y0 += dy;
|
|
282
|
+
node.y1 += dy;
|
|
283
|
+
currentY = node.y1 + this.nodePadding;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
arrangeUp(nodes, yPos, index) {
|
|
288
|
+
let currentY = yPos;
|
|
289
|
+
for (let i = index; i >= 0; --i) {
|
|
290
|
+
const node = nodes[i];
|
|
291
|
+
const dy = Math.max(0, node.y1 - currentY);
|
|
292
|
+
node.y0 -= dy;
|
|
293
|
+
node.y1 -= dy;
|
|
294
|
+
currentY = node.y0 - this.nodePadding;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
sourceTopPos(source, target) {
|
|
299
|
+
let y = target.y0 - ((target.targetLinks.length - 1) * this.nodePadding) / 2;
|
|
300
|
+
for (let i = 0; i < target.targetLinks.length; i++) {
|
|
301
|
+
const link = target.targetLinks[i];
|
|
302
|
+
if (link.source === source) {
|
|
303
|
+
break;
|
|
304
|
+
}
|
|
305
|
+
y += link.width + this.nodePadding;
|
|
306
|
+
}
|
|
307
|
+
for (let i = 0; i < source.sourceLinks.length; i++) {
|
|
308
|
+
const link = source.sourceLinks[i];
|
|
309
|
+
if (link.target === target) {
|
|
310
|
+
break;
|
|
311
|
+
}
|
|
312
|
+
y -= link.width;
|
|
313
|
+
}
|
|
314
|
+
return y;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
targetTopPos(source, target) {
|
|
318
|
+
let y = source.y0 - ((source.sourceLinks.length - 1) * this.nodePadding) / 2;
|
|
319
|
+
for (let i = 0; i < source.sourceLinks.length; i++) {
|
|
320
|
+
const link = source.sourceLinks[i];
|
|
321
|
+
if (link.target === target) {
|
|
322
|
+
break;
|
|
323
|
+
}
|
|
324
|
+
y += link.width + this.nodePadding;
|
|
325
|
+
}
|
|
326
|
+
for (let i = 0; i < target.targetLinks.length; i++) {
|
|
327
|
+
const link = target.targetLinks[i];
|
|
328
|
+
if (link.source === source) {
|
|
329
|
+
break;
|
|
330
|
+
}
|
|
331
|
+
y -= link.width;
|
|
332
|
+
}
|
|
333
|
+
return y;
|
|
334
|
+
}
|
|
168
335
|
}
|
|
169
336
|
|
|
170
337
|
export const calculateSankey = (options) => new Sankey(options).calculate();
|
|
338
|
+
|
|
339
|
+
export const crossesValue = (links) => {
|
|
340
|
+
let value = 0;
|
|
341
|
+
const linksLength = links.length;
|
|
342
|
+
|
|
343
|
+
for (let i = 0; i < linksLength; i++) {
|
|
344
|
+
const link = links[i];
|
|
345
|
+
|
|
346
|
+
for (let lNext = i + 1; lNext < linksLength; lNext++) {
|
|
347
|
+
const nextLink = links[lNext];
|
|
348
|
+
|
|
349
|
+
if (intersect(link, nextLink)) {
|
|
350
|
+
value += Math.min(link.value, nextLink.value);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return value;
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
function rotationDirection(p1x, p1y, p2x, p2y, p3x, p3y) {
|
|
359
|
+
const expression1 = (p3y - p1y) * (p2x - p1x);
|
|
360
|
+
const expression2 = (p2y - p1y) * (p3x - p1x);
|
|
361
|
+
|
|
362
|
+
if (expression1 > expression2) {
|
|
363
|
+
return 1;
|
|
364
|
+
} else if (expression1 === expression2) {
|
|
365
|
+
return 0;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
return -1;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function intersect(link1, link2) {
|
|
372
|
+
const f1 = rotationDirection(link1.x0, link1.y0, link1.x1, link1.y1, link2.x1, link2.y1);
|
|
373
|
+
const f2 = rotationDirection(link1.x0, link1.y0, link1.x1, link1.y1, link2.x0, link2.y0);
|
|
374
|
+
const f3 = rotationDirection(link1.x0, link1.y0, link2.x0, link2.y0, link2.x1, link2.y1);
|
|
375
|
+
const f4 = rotationDirection(link1.x1, link1.y1, link2.x0, link2.y0, link2.x1, link2.y1);
|
|
376
|
+
|
|
377
|
+
return f1 !== f2 && f3 !== f4;
|
|
378
|
+
}
|
|
@@ -6,11 +6,11 @@ import { defined } from '../drawing-utils';
|
|
|
6
6
|
export class Link extends SankeyElement {
|
|
7
7
|
getElement() {
|
|
8
8
|
const link = this.options.link;
|
|
9
|
-
const {
|
|
10
|
-
const xC = (
|
|
9
|
+
const { x0, x1, y0, y1 } = link;
|
|
10
|
+
const xC = (x0 + x1) / 2;
|
|
11
11
|
|
|
12
12
|
return new drawing.Path(this.visualOptions())
|
|
13
|
-
.moveTo(
|
|
13
|
+
.moveTo(x0, y0).curveTo([xC, y0], [xC, y1], [x1, y1]);
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
visualOptions() {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { geometry, drawing } from '@progress/kendo-drawing';
|
|
2
2
|
import { deepExtend, addClass, setDefaultOptions } from '../common';
|
|
3
|
-
import { calculateSankey } from './calculation';
|
|
3
|
+
import { calculateSankey, crossesValue } from './calculation';
|
|
4
4
|
import { Node, resolveNodeOptions } from './node';
|
|
5
5
|
import { Link, resolveLinkOptions } from './link';
|
|
6
6
|
import { Label, resolveLabelOptions } from './label';
|
|
@@ -60,6 +60,7 @@ export class Sankey extends Observable {
|
|
|
60
60
|
}
|
|
61
61
|
this.size = { width, height };
|
|
62
62
|
this.surface.setSize(this.size);
|
|
63
|
+
this.resize = true;
|
|
63
64
|
this._redraw();
|
|
64
65
|
});
|
|
65
66
|
});
|
|
@@ -244,7 +245,8 @@ export class Sankey extends Observable {
|
|
|
244
245
|
}
|
|
245
246
|
|
|
246
247
|
calculateSankey(calcOptions, sankeyOptions) {
|
|
247
|
-
const { title, legend, data, nodes, labels, nodesColors } = sankeyOptions;
|
|
248
|
+
const { title, legend, data, nodes, labels, nodesColors, disableAutoLayout } = sankeyOptions;
|
|
249
|
+
const autoLayout = !disableAutoLayout;
|
|
248
250
|
|
|
249
251
|
const sankeyBox = new Box(0, 0, calcOptions.width, calcOptions.height);
|
|
250
252
|
const titleBox = this.titleBox(title, sankeyBox);
|
|
@@ -263,26 +265,27 @@ export class Sankey extends Observable {
|
|
|
263
265
|
}
|
|
264
266
|
|
|
265
267
|
const legendBox = this.legendBox(legend, data.nodes, legendArea);
|
|
268
|
+
const legendPosition = (legend && legend.position) || Legend.prototype.options.position;
|
|
266
269
|
|
|
267
270
|
if (legendBox) {
|
|
268
|
-
if (
|
|
271
|
+
if (legendPosition === LEFT) {
|
|
269
272
|
sankeyBox.unpad({ left: legendBox.width() });
|
|
270
273
|
}
|
|
271
274
|
|
|
272
|
-
if (
|
|
275
|
+
if (legendPosition === RIGHT) {
|
|
273
276
|
sankeyBox.shrink(legendBox.width(), 0);
|
|
274
277
|
}
|
|
275
278
|
|
|
276
|
-
if (
|
|
279
|
+
if (legendPosition === TOP) {
|
|
277
280
|
sankeyBox.unpad({ top: legendBox.height() });
|
|
278
281
|
}
|
|
279
282
|
|
|
280
|
-
if (
|
|
283
|
+
if (legendPosition === BOTTOM) {
|
|
281
284
|
sankeyBox.shrink(0, legendBox.height());
|
|
282
285
|
}
|
|
283
286
|
}
|
|
284
287
|
|
|
285
|
-
const calculatedNodes = calculateSankey(Object.assign({}, calcOptions, {offsetX:
|
|
288
|
+
const calculatedNodes = calculateSankey(Object.assign({}, calcOptions, {offsetX: 0, offsetY: 0, width: sankeyBox.width(), height: sankeyBox.height()})).nodes;
|
|
286
289
|
const box = new Box();
|
|
287
290
|
|
|
288
291
|
calculatedNodes.forEach((nodeEl, i) => {
|
|
@@ -290,21 +293,71 @@ export class Sankey extends Observable {
|
|
|
290
293
|
const nodeInstance = new Node(nodeOps);
|
|
291
294
|
box.wrap(rectToBox(nodeInstance.exportVisual().rawBBox()));
|
|
292
295
|
|
|
293
|
-
const labelInstance = new Label(
|
|
296
|
+
const labelInstance = new Label(resolveLabelOptions(nodeEl, labels, sankeyBox.width()));
|
|
294
297
|
const labelVisual = labelInstance.exportVisual();
|
|
295
298
|
if (labelVisual) {
|
|
296
299
|
box.wrap(rectToBox(labelVisual.rawBBox()));
|
|
297
300
|
}
|
|
298
301
|
});
|
|
299
302
|
|
|
300
|
-
let offsetX =
|
|
301
|
-
let offsetY =
|
|
303
|
+
let offsetX = sankeyBox.x1;
|
|
304
|
+
let offsetY = sankeyBox.y1;
|
|
302
305
|
|
|
303
|
-
let width =
|
|
304
|
-
let height =
|
|
306
|
+
let width = sankeyBox.width() + offsetX;
|
|
307
|
+
let height = sankeyBox.height() + offsetY;
|
|
308
|
+
|
|
309
|
+
width -= box.x2 > sankeyBox.width() ? box.x2 - sankeyBox.width() : 0;
|
|
310
|
+
height -= box.y2 > sankeyBox.height() ? box.y2 - sankeyBox.height() : 0;
|
|
311
|
+
|
|
312
|
+
offsetX += box.x1 < 0 ? -box.x1 : 0;
|
|
313
|
+
offsetY += box.y1 < 0 ? -box.y1 : 0;
|
|
314
|
+
|
|
315
|
+
if (autoLayout === false) {
|
|
316
|
+
return {
|
|
317
|
+
sankey: calculateSankey(Object.assign({}, calcOptions, {offsetX, offsetY, width, height, autoLayout: false})),
|
|
318
|
+
legendBox,
|
|
319
|
+
titleBox
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (this.resize && autoLayout && this.permutation) {
|
|
324
|
+
this.resize = false;
|
|
325
|
+
return {
|
|
326
|
+
sankey: calculateSankey(Object.assign({}, calcOptions, {offsetX, offsetY, width, height}, this.permutation)),
|
|
327
|
+
legendBox,
|
|
328
|
+
titleBox
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const startColumn = 1;
|
|
333
|
+
const loops = 2;
|
|
334
|
+
const columnsLength = calculateSankey(Object.assign({}, calcOptions, {offsetX, offsetY, width, height, autoLayout: false})).columns.length;
|
|
335
|
+
const results = [];
|
|
336
|
+
|
|
337
|
+
const permutation = (targetColumnIndex, reverse) => {
|
|
338
|
+
let currPerm = calculateSankey(Object.assign({}, calcOptions, {offsetX, offsetY, width, height, loops: loops, targetColumnIndex, reverse}));
|
|
339
|
+
let crosses = crossesValue(currPerm.links);
|
|
340
|
+
results.push({
|
|
341
|
+
crosses: crosses,
|
|
342
|
+
reverse: reverse,
|
|
343
|
+
targetColumnIndex: targetColumnIndex
|
|
344
|
+
});
|
|
345
|
+
return crosses === 0;
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
for (let index = startColumn; index <= columnsLength - 1; index++) {
|
|
349
|
+
if (permutation(index, false) || permutation(index, true)) {
|
|
350
|
+
break;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const minCrosses = Math.min.apply(null, results.map(r => r.crosses));
|
|
355
|
+
const bestResult = results.find(r => r.crosses === minCrosses);
|
|
356
|
+
this.permutation = { targetColumnIndex: bestResult.targetColumnIndex, reverse: bestResult.reverse };
|
|
357
|
+
const result = calculateSankey(Object.assign({}, calcOptions, {offsetX, offsetY, width, height}, this.permutation));
|
|
305
358
|
|
|
306
359
|
return {
|
|
307
|
-
sankey:
|
|
360
|
+
sankey: result,
|
|
308
361
|
legendBox,
|
|
309
362
|
titleBox
|
|
310
363
|
};
|
|
@@ -385,8 +438,9 @@ export class Sankey extends Observable {
|
|
|
385
438
|
visual.append(linkVisual);
|
|
386
439
|
});
|
|
387
440
|
|
|
441
|
+
const diagramWidth = nodes.reduce((acc, node) => Math.max(acc, node.x1), 0);
|
|
388
442
|
nodes.forEach((node) => {
|
|
389
|
-
const textOps = resolveLabelOptions(node, labelOptions,
|
|
443
|
+
const textOps = resolveLabelOptions(node, labelOptions, diagramWidth);
|
|
390
444
|
const labelInstance = new Label(textOps);
|
|
391
445
|
const labelVisual = labelInstance.exportVisual();
|
|
392
446
|
|
|
@@ -418,6 +472,9 @@ export class Sankey extends Observable {
|
|
|
418
472
|
}
|
|
419
473
|
|
|
420
474
|
setDefaultOptions(Sankey, {
|
|
475
|
+
title: {
|
|
476
|
+
position: TOP, // 'top', 'bottom'
|
|
477
|
+
},
|
|
421
478
|
labels: {
|
|
422
479
|
visible: true,
|
|
423
480
|
margin: {
|
|
@@ -436,6 +493,7 @@ setDefaultOptions(Sankey, {
|
|
|
436
493
|
width: 24,
|
|
437
494
|
padding: 16,
|
|
438
495
|
opacity: 1,
|
|
496
|
+
align: 'stretch', // 'left', 'right', 'stretch'
|
|
439
497
|
offset: { left: 0, top: 0 }
|
|
440
498
|
},
|
|
441
499
|
links: {
|