@progress/kendo-charts 2.3.0-dev.202402161236 → 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.
@@ -3,24 +3,40 @@ import { deepExtend } from '../common';
3
3
  var max = function (array, mapFn) { return Math.max.apply(null, array.map(mapFn)); };
4
4
  var min = function (array, mapFn) { return Math.min.apply(null, array.map(mapFn)); };
5
5
  var sum = function (array, mapFn) { return array.map(mapFn).reduce(function (acc, curr) { return (acc + curr); }, 0); };
6
- var sortAsc = function (a, b) { return a.y0 - b.y0; };
6
+ var sortAsc = function (a, b) { return (a.y0 === b.y0 ? a.index - b.index : a.y0 + a.y1 - b.y0 - b.y1); };
7
7
  var sortSource = function (a, b) { return sortAsc(a.source, b.source); };
8
8
  var sortTarget = function (a, b) { return sortAsc(a.target, b.target); };
9
9
  var value = function (node) { return node.value; };
10
10
 
11
11
  function sortLinks(nodes) {
12
12
  nodes.forEach(function (node) {
13
- var sourceLinks = node.sourceLinks;
14
- var targetLinks = node.targetLinks;
15
- sourceLinks.sort(sortTarget);
16
- targetLinks.sort(sortSource);
13
+ node.targetLinks.forEach(function (link) {
14
+ link.source.sourceLinks.sort(sortTarget);
15
+ });
16
+ node.sourceLinks.forEach(function (link) {
17
+ link.target.targetLinks.sort(sortSource);
18
+ });
17
19
  });
18
20
  }
19
21
 
22
+ var calcLayer = function (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
+
20
34
  var Sankey = function Sankey(options) {
21
- var offset = options.nodesOptions.offset || {};
35
+ var ref = options.nodesOptions;
36
+ var offset = ref.offset; if ( offset === void 0 ) offset = {};
37
+ var align = ref.align;
22
38
  this.data = {
23
- nodes: options.nodes.map(function (node) { return deepExtend({}, { offset: offset }, node); }),
39
+ nodes: options.nodes.map(function (node) { return deepExtend({}, { offset: offset, align: align }, node); }),
24
40
  links: options.links.map(function (link) { return deepExtend({}, link); })
25
41
  };
26
42
 
@@ -30,6 +46,10 @@ var Sankey = function Sankey(options) {
30
46
  this.offsetY = options.offsetY || 0;
31
47
  this.nodeWidth = options.nodesOptions.width;
32
48
  this.nodePadding = options.nodesOptions.padding;
49
+ this.reverse = options.reverse;
50
+ this.targetColumnIndex = options.targetColumnIndex;
51
+ this.loops = options.loops;
52
+ this.autoLayout = options.autoLayout;
33
53
  };
34
54
 
35
55
  Sankey.prototype.calculate = function calculate () {
@@ -39,19 +59,21 @@ Sankey.prototype.calculate = function calculate () {
39
59
  this.connectLinksToNodes(nodes, links);
40
60
  this.calculateNodeValues(nodes);
41
61
  this.calculateNodeDepths(nodes);
62
+ this.calculateNodeHeights(nodes);
42
63
 
43
64
  var columns = this.calculateNodeColumns(nodes);
44
65
  this.calculateNodeBreadths(columns);
45
66
  this.applyNodesOffset(nodes);
46
67
  this.calculateLinkBreadths(nodes);
47
68
 
48
- return this.data;
69
+ return Object.assign({}, this.data, {columns: columns});
49
70
  };
50
71
 
51
72
  Sankey.prototype.connectLinksToNodes = function connectLinksToNodes (nodes, links) {
52
73
  var nodesMap = new Map();
53
74
 
54
- nodes.forEach(function (node) {
75
+ nodes.forEach(function (node, i) {
76
+ node.index = i;
55
77
  node.sourceLinks = [];
56
78
  node.targetLinks = [];
57
79
  node.id = node.id !== undefined ? node.id : node.label.text;
@@ -95,6 +117,24 @@ Sankey.prototype.calculateNodeDepths = function calculateNodeDepths (nodes) {
95
117
  }
96
118
  };
97
119
 
120
+ Sankey.prototype.calculateNodeHeights = function calculateNodeHeights (nodes) {
121
+ var current = new Set(nodes);
122
+ var next = new Set;
123
+ var x = 0;
124
+ var eachNode = function (node) {
125
+ node.height = x;
126
+ node.targetLinks.forEach(function (link) {
127
+ next.add(link.source);
128
+ });
129
+ };
130
+ while (current.size) {
131
+ current.forEach(eachNode);
132
+ x++;
133
+ current = next;
134
+ next = new Set;
135
+ }
136
+ };
137
+
98
138
  Sankey.prototype.calculateNodeColumns = function calculateNodeColumns (nodes) {
99
139
  var this$1 = this;
100
140
 
@@ -103,11 +143,12 @@ Sankey.prototype.calculateNodeColumns = function calculateNodeColumns (nodes) {
103
143
  var columns = new Array(maxDepth + 1);
104
144
  for (var i = 0; i < nodes.length; i++) {
105
145
  var node = nodes[i];
106
- var depth = Math.max(0, Math.min(maxDepth, node.sourceLinks.length ? node.depth : maxDepth));
107
- node.x0 = this$1.offsetX + depth * columnWidth;
146
+ var layer = Math.max(0, Math.min(maxDepth, calcLayer(node, maxDepth)));
147
+ node.x0 = this$1.offsetX + layer * columnWidth;
108
148
  node.x1 = node.x0 + this$1.nodeWidth;
109
- columns[depth] = columns[depth] || [];
110
- columns[depth].push(node);
149
+ node.layer = layer;
150
+ columns[layer] = columns[layer] || [];
151
+ columns[layer].push(node);
111
152
  }
112
153
 
113
154
  return columns;
@@ -118,31 +159,39 @@ Sankey.prototype.calculateNodeBreadths = function calculateNodeBreadths (columns
118
159
 
119
160
  var kSize = min(columns, function (c) { return (this$1.height - this$1.offsetY - (c.length - 1) * this$1.nodePadding) / sum(c, value); });
120
161
 
121
- for (var c = 0; c < columns.length; c++) {
122
- var nodes = columns[c];
162
+ columns.forEach(function (nodes) {
123
163
  var y = this$1.offsetY;
124
- for (var i = 0; i < nodes.length; i++) {
125
- var node = nodes[i];
164
+ nodes.forEach(function (node) {
126
165
  node.y0 = y;
127
166
  node.y1 = y + node.value * kSize;
128
167
  y = node.y1 + this$1.nodePadding;
129
- for (var l = 0; l < node.sourceLinks.length; l++) {
130
- var link = node.sourceLinks[l];
168
+ node.sourceLinks.forEach(function (link) {
131
169
  link.width = link.value * kSize;
132
- }
133
- }
134
-
170
+ });
171
+ });
135
172
  y = (this$1.height - y + this$1.nodePadding) / (nodes.length + 1);
136
- for (var i$1 = 0; i$1 < nodes.length; ++i$1) {
137
- var node$1 = nodes[i$1];
138
- node$1.y0 += y * (i$1 + 1);
139
- node$1.y1 += y * (i$1 + 1);
173
+ nodes.forEach(function (node, i) {
174
+ node.y0 += y * (i + 1);
175
+ node.y1 += y * (i + 1);
176
+ });
177
+ });
178
+
179
+ if (this.autoLayout !== false) {
180
+ var loops = this.loops !== undefined ? this.loops : columns.length - 1;
181
+ var targetColumnIndex = this.targetColumnIndex || 1;
182
+
183
+ for (var i = 0; i < loops; i++) {
184
+ if (!this$1.reverse) {
185
+ this$1.uncurlLinksToLeft(columns, targetColumnIndex);
186
+ this$1.uncurlLinksToRight(columns, targetColumnIndex);
187
+ } else {
188
+ this$1.uncurlLinksToRight(columns, targetColumnIndex);
189
+ this$1.uncurlLinksToLeft(columns, targetColumnIndex);
190
+ }
140
191
  }
141
192
  }
142
193
 
143
- for (var c$1 = 0; c$1 < columns.length; c$1++) {
144
- sortLinks(columns[c$1]);
145
- }
194
+ columns.forEach(sortLinks);
146
195
  };
147
196
 
148
197
  Sankey.prototype.applyNodesOffset = function applyNodesOffset (nodes) {
@@ -163,14 +212,188 @@ Sankey.prototype.calculateLinkBreadths = function calculateLinkBreadths (nodes)
163
212
  var y = node.y0;
164
213
  var y1 = y;
165
214
  sourceLinks.forEach(function (link) {
215
+ link.x0 = link.source.x1;
166
216
  link.y0 = y + link.width / 2;
167
217
  y += link.width;
168
218
  });
169
219
  targetLinks.forEach(function (link) {
220
+ link.x1 = link.target.x0;
170
221
  link.y1 = y1 + link.width / 2;
171
222
  y1 += link.width;
172
223
  });
173
224
  });
174
225
  };
175
226
 
227
+ Sankey.prototype.uncurlLinksToRight = function uncurlLinksToRight (columns, targetColumnIndex) {
228
+ var this$1 = this;
229
+
230
+ var n = columns.length;
231
+ for (var i = targetColumnIndex; i < n; i++) {
232
+ var column = columns[i];
233
+ column.forEach(function (target) {
234
+ var y = 0;
235
+ var sum = 0;
236
+ target.targetLinks.forEach(function (link) {
237
+ var kValue = link.value * (target.layer - link.source.layer);
238
+ y += this$1.targetTopPos(link.source, target) * kValue;
239
+ sum += kValue;
240
+ });
241
+
242
+ var dy = y === 0 ? 0 : (y / sum - target.y0);
243
+ target.y0 += dy;
244
+ target.y1 += dy;
245
+ sortLinks([target]);
246
+ });
247
+ column.sort(sortAsc);
248
+ this$1.arrangeNodesVertically(column);
249
+ }
250
+ };
251
+
252
+ Sankey.prototype.uncurlLinksToLeft = function uncurlLinksToLeft (columns, targetColumnIndex) {
253
+ var this$1 = this;
254
+
255
+ var l = columns.length;
256
+ var startIndex = l - 1 - targetColumnIndex;
257
+ for (var i = startIndex; i >= 0; i--) {
258
+ var column = columns[i];
259
+ var loop = function ( j ) {
260
+ var source = column[j];
261
+ var y = 0;
262
+ var sum = 0;
263
+ source.sourceLinks.forEach(function (link) {
264
+ var kValue = link.value * (link.target.layer - source.layer);
265
+ y += this$1.sourceTopPos(source, link.target) * kValue;
266
+ sum += kValue;
267
+ });
268
+ var dy = y === 0 ? 0 : (y / sum - source.y0);
269
+ source.y0 += dy;
270
+ source.y1 += dy;
271
+ sortLinks([source]);
272
+ };
273
+
274
+ for (var j = 0; j < column.length; j++) loop( j );
275
+
276
+ column.sort(sortAsc);
277
+ this$1.arrangeNodesVertically(column);
278
+ }
279
+ };
280
+
281
+ Sankey.prototype.arrangeNodesVertically = function arrangeNodesVertically (nodes) {
282
+ var startIndex = 0;
283
+ var endIndex = nodes.length - 1;
284
+
285
+ this.arrangeUp(nodes, this.height, endIndex);
286
+ this.arrangeDown(nodes, this.offsetY, startIndex);
287
+ };
288
+
289
+ Sankey.prototype.arrangeDown = function arrangeDown (nodes, yPos, index) {
290
+ var this$1 = this;
291
+
292
+ var currentY = yPos;
293
+
294
+ for (var i = index; i < nodes.length; i++) {
295
+ var node = nodes[i];
296
+ var dy = Math.max(0, currentY - node.y0);
297
+ node.y0 += dy;
298
+ node.y1 += dy;
299
+ currentY = node.y1 + this$1.nodePadding;
300
+ }
301
+ };
302
+
303
+ Sankey.prototype.arrangeUp = function arrangeUp (nodes, yPos, index) {
304
+ var this$1 = this;
305
+
306
+ var currentY = yPos;
307
+ for (var i = index; i >= 0; --i) {
308
+ var node = nodes[i];
309
+ var dy = Math.max(0, node.y1 - currentY);
310
+ node.y0 -= dy;
311
+ node.y1 -= dy;
312
+ currentY = node.y0 - this$1.nodePadding;
313
+ }
314
+ };
315
+
316
+ Sankey.prototype.sourceTopPos = function sourceTopPos (source, target) {
317
+ var this$1 = this;
318
+
319
+ var y = target.y0 - ((target.targetLinks.length - 1) * this.nodePadding) / 2;
320
+ for (var i = 0; i < target.targetLinks.length; i++) {
321
+ var link = target.targetLinks[i];
322
+ if (link.source === source) {
323
+ break;
324
+ }
325
+ y += link.width + this$1.nodePadding;
326
+ }
327
+ for (var i$1 = 0; i$1 < source.sourceLinks.length; i$1++) {
328
+ var link$1 = source.sourceLinks[i$1];
329
+ if (link$1.target === target) {
330
+ break;
331
+ }
332
+ y -= link$1.width;
333
+ }
334
+ return y;
335
+ };
336
+
337
+ Sankey.prototype.targetTopPos = function targetTopPos (source, target) {
338
+ var this$1 = this;
339
+
340
+ var y = source.y0 - ((source.sourceLinks.length - 1) * this.nodePadding) / 2;
341
+ for (var i = 0; i < source.sourceLinks.length; i++) {
342
+ var link = source.sourceLinks[i];
343
+ if (link.target === target) {
344
+ break;
345
+ }
346
+ y += link.width + this$1.nodePadding;
347
+ }
348
+ for (var i$1 = 0; i$1 < target.targetLinks.length; i$1++) {
349
+ var link$1 = target.targetLinks[i$1];
350
+ if (link$1.source === source) {
351
+ break;
352
+ }
353
+ y -= link$1.width;
354
+ }
355
+ return y;
356
+ };
357
+
176
358
  export var calculateSankey = function (options) { return new Sankey(options).calculate(); };
359
+
360
+ export var crossesValue = function (links) {
361
+ var value = 0;
362
+ var linksLength = links.length;
363
+
364
+ for (var i = 0; i < linksLength; i++) {
365
+ var link = links[i];
366
+
367
+ for (var lNext = i + 1; lNext < linksLength; lNext++) {
368
+ var nextLink = links[lNext];
369
+
370
+ if (intersect(link, nextLink)) {
371
+ value += Math.min(link.value, nextLink.value);
372
+ }
373
+ }
374
+ }
375
+
376
+ return value;
377
+ };
378
+
379
+ function rotationDirection(p1x, p1y, p2x, p2y, p3x, p3y) {
380
+ var expression1 = (p3y - p1y) * (p2x - p1x);
381
+ var expression2 = (p2y - p1y) * (p3x - p1x);
382
+
383
+ if (expression1 > expression2) {
384
+ return 1;
385
+ } else if (expression1 === expression2) {
386
+ return 0;
387
+ }
388
+
389
+ return -1;
390
+ }
391
+
392
+ function intersect(link1, link2) {
393
+ var f1 = rotationDirection(link1.x0, link1.y0, link1.x1, link1.y1, link2.x1, link2.y1);
394
+ var f2 = rotationDirection(link1.x0, link1.y0, link1.x1, link1.y1, link2.x0, link2.y0);
395
+ var f3 = rotationDirection(link1.x0, link1.y0, link2.x0, link2.y0, link2.x1, link2.y1);
396
+ var f4 = rotationDirection(link1.x1, link1.y1, link2.x0, link2.y0, link2.x1, link2.y1);
397
+
398
+ return f1 !== f2 && f3 !== f4;
399
+ }
@@ -14,14 +14,14 @@ export var Link = (function (SankeyElement) {
14
14
 
15
15
  Link.prototype.getElement = function getElement () {
16
16
  var link = this.options.link;
17
- var source = link.source;
18
- var target = link.target;
17
+ var x0 = link.x0;
18
+ var x1 = link.x1;
19
19
  var y0 = link.y0;
20
20
  var y1 = link.y1;
21
- var xC = (source.x0 + target.x1) / 2;
21
+ var xC = (x0 + x1) / 2;
22
22
 
23
23
  return new drawing.Path(this.visualOptions())
24
- .moveTo(source.x1, y0).curveTo([xC, y0], [xC, y1], [target.x0, y1]);
24
+ .moveTo(x0, y0).curveTo([xC, y0], [xC, y1], [x1, y1]);
25
25
  };
26
26
 
27
27
  Link.prototype.visualOptions = function visualOptions () {
@@ -1,6 +1,6 @@
1
- import { drawing } from '@progress/kendo-drawing';
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';
@@ -10,6 +10,7 @@ import Box from '../core/box';
10
10
  import rectToBox from '../core/utils/rect-to-box';
11
11
  import { Observable } from '../common/observable';
12
12
  import { Legend } from './legend';
13
+ import { defined } from '../drawing-utils';
13
14
 
14
15
  var LINK = 'link';
15
16
  var NODE = 'node';
@@ -67,6 +68,7 @@ export var Sankey = (function (Observable) {
67
68
  }
68
69
  this$1.size = { width: width, height: height };
69
70
  this$1.surface.setSize(this$1.size);
71
+ this$1.resize = true;
70
72
  this$1._redraw();
71
73
  });
72
74
  });
@@ -257,17 +259,17 @@ export var Sankey = (function (Observable) {
257
259
  return legendVisual.chartElement.box;
258
260
  };
259
261
 
260
- Sankey.prototype.calculateSankey = function calculateSankey$1 (options) {
261
- var ref = this.options;
262
- var title = ref.title;
263
- var legend = ref.legend;
264
- var data = ref.data;
265
- var ref$1 = this.options;
266
- var nodes = ref$1.nodes;
267
- var labels = ref$1.labels;
268
- var nodesColors = ref$1.nodesColors;
269
-
270
- var sankeyBox = new Box(0, 0, options.width, options.height);
262
+ Sankey.prototype.calculateSankey = function calculateSankey$1 (calcOptions, sankeyOptions) {
263
+ var title = sankeyOptions.title;
264
+ var legend = sankeyOptions.legend;
265
+ var data = sankeyOptions.data;
266
+ var nodes = sankeyOptions.nodes;
267
+ var labels = sankeyOptions.labels;
268
+ var nodesColors = sankeyOptions.nodesColors;
269
+ var disableAutoLayout = sankeyOptions.disableAutoLayout;
270
+ var autoLayout = !disableAutoLayout;
271
+
272
+ var sankeyBox = new Box(0, 0, calcOptions.width, calcOptions.height);
271
273
  var titleBox = this.titleBox(title, sankeyBox);
272
274
 
273
275
  var legendArea = sankeyBox.clone();
@@ -276,34 +278,35 @@ export var Sankey = (function (Observable) {
276
278
  var titleHeight = titleBox.height();
277
279
  if (title.position === TOP) {
278
280
  sankeyBox.unpad({ top: titleHeight });
279
- legendArea = new Box(0, titleHeight, options.width, options.height);
281
+ legendArea = new Box(0, titleHeight, calcOptions.width, calcOptions.height);
280
282
  } else {
281
283
  sankeyBox.shrink(0, titleHeight);
282
- legendArea = new Box(0, 0, options.width, options.height - titleHeight);
284
+ legendArea = new Box(0, 0, calcOptions.width, calcOptions.height - titleHeight);
283
285
  }
284
286
  }
285
287
 
286
288
  var legendBox = this.legendBox(legend, data.nodes, legendArea);
289
+ var legendPosition = (legend && legend.position) || Legend.prototype.options.position;
287
290
 
288
291
  if (legendBox) {
289
- if (legend.position === LEFT) {
292
+ if (legendPosition === LEFT) {
290
293
  sankeyBox.unpad({ left: legendBox.width() });
291
294
  }
292
295
 
293
- if (legend.position === RIGHT) {
296
+ if (legendPosition === RIGHT) {
294
297
  sankeyBox.shrink(legendBox.width(), 0);
295
298
  }
296
299
 
297
- if (legend.position === TOP) {
300
+ if (legendPosition === TOP) {
298
301
  sankeyBox.unpad({ top: legendBox.height() });
299
302
  }
300
303
 
301
- if (legend.position === BOTTOM) {
304
+ if (legendPosition === BOTTOM) {
302
305
  sankeyBox.shrink(0, legendBox.height());
303
306
  }
304
307
  }
305
308
 
306
- var calculatedNodes = calculateSankey(Object.assign({}, options, {offsetX: sankeyBox.x1, offsetY: sankeyBox.y1, width: sankeyBox.x2, height: sankeyBox.y2})).nodes;
309
+ var calculatedNodes = calculateSankey(Object.assign({}, calcOptions, {offsetX: 0, offsetY: 0, width: sankeyBox.width(), height: sankeyBox.height()})).nodes;
307
310
  var box = new Box();
308
311
 
309
312
  calculatedNodes.forEach(function (nodeEl, i) {
@@ -311,47 +314,102 @@ export var Sankey = (function (Observable) {
311
314
  var nodeInstance = new Node(nodeOps);
312
315
  box.wrap(rectToBox(nodeInstance.exportVisual().rawBBox()));
313
316
 
314
- var labelInstance = new Label(deepExtend({ node: nodeEl, totalWidth: options.width }, labels));
317
+ var labelInstance = new Label(resolveLabelOptions(nodeEl, labels, sankeyBox.width()));
315
318
  var labelVisual = labelInstance.exportVisual();
316
319
  if (labelVisual) {
317
320
  box.wrap(rectToBox(labelVisual.rawBBox()));
318
321
  }
319
322
  });
320
323
 
321
- var offsetX = (box.x1 < 0 ? -box.x1 : 0) + sankeyBox.x1;
322
- var offsetY = (box.y1 < 0 ? -box.y1 : 0) + sankeyBox.y1;
324
+ var offsetX = sankeyBox.x1;
325
+ var offsetY = sankeyBox.y1;
323
326
 
324
- var width = box.width() > sankeyBox.x2 ? offsetX + sankeyBox.x2 - (box.width() - sankeyBox.x2) : sankeyBox.x2;
325
- var height = box.height() > sankeyBox.y2 ? offsetY + sankeyBox.y2 - (box.height() - sankeyBox.y2) : sankeyBox.y2;
327
+ var width = sankeyBox.width() + offsetX;
328
+ var height = sankeyBox.height() + offsetY;
329
+
330
+ width -= box.x2 > sankeyBox.width() ? box.x2 - sankeyBox.width() : 0;
331
+ height -= box.y2 > sankeyBox.height() ? box.y2 - sankeyBox.height() : 0;
332
+
333
+ offsetX += box.x1 < 0 ? -box.x1 : 0;
334
+ offsetY += box.y1 < 0 ? -box.y1 : 0;
335
+
336
+ if (autoLayout === false) {
337
+ return {
338
+ sankey: calculateSankey(Object.assign({}, calcOptions, {offsetX: offsetX, offsetY: offsetY, width: width, height: height, autoLayout: false})),
339
+ legendBox: legendBox,
340
+ titleBox: titleBox
341
+ };
342
+ }
343
+
344
+ if (this.resize && autoLayout && this.permutation) {
345
+ this.resize = false;
346
+ return {
347
+ sankey: calculateSankey(Object.assign({}, calcOptions, {offsetX: offsetX, offsetY: offsetY, width: width, height: height}, this.permutation)),
348
+ legendBox: legendBox,
349
+ titleBox: titleBox
350
+ };
351
+ }
352
+
353
+ var startColumn = 1;
354
+ var loops = 2;
355
+ var columnsLength = calculateSankey(Object.assign({}, calcOptions, {offsetX: offsetX, offsetY: offsetY, width: width, height: height, autoLayout: false})).columns.length;
356
+ var results = [];
357
+
358
+ var permutation = function (targetColumnIndex, reverse) {
359
+ var currPerm = calculateSankey(Object.assign({}, calcOptions, {offsetX: offsetX, offsetY: offsetY, width: width, height: height, loops: loops, targetColumnIndex: targetColumnIndex, reverse: reverse}));
360
+ var crosses = crossesValue(currPerm.links);
361
+ results.push({
362
+ crosses: crosses,
363
+ reverse: reverse,
364
+ targetColumnIndex: targetColumnIndex
365
+ });
366
+ return crosses === 0;
367
+ };
368
+
369
+ for (var index = startColumn; index <= columnsLength - 1; index++) {
370
+ if (permutation(index, false) || permutation(index, true)) {
371
+ break;
372
+ }
373
+ }
374
+
375
+ var minCrosses = Math.min.apply(null, results.map(function (r) { return r.crosses; }));
376
+ var bestResult = results.find(function (r) { return r.crosses === minCrosses; });
377
+ this.permutation = { targetColumnIndex: bestResult.targetColumnIndex, reverse: bestResult.reverse };
378
+ var result = calculateSankey(Object.assign({}, calcOptions, {offsetX: offsetX, offsetY: offsetY, width: width, height: height}, this.permutation));
326
379
 
327
380
  return {
328
- sankey: calculateSankey(Object.assign({}, options, {offsetX: offsetX, offsetY: offsetY, width: width, height: height})),
381
+ sankey: result,
329
382
  legendBox: legendBox,
330
383
  titleBox: titleBox
331
384
  };
332
385
  };
333
386
 
334
- Sankey.prototype._render = function _render () {
335
- var ref = this.options;
336
- var data = ref.data;
337
- var labelOptions = ref.labels;
338
- var nodesOptions = ref.nodes;
339
- var linkOptions = ref.links;
340
- var nodesColors = ref.nodesColors;
341
- var title = ref.title;
342
- var legend = ref.legend;
343
- var ref$1 = this.size;
344
- var width = ref$1.width;
345
- var height = ref$1.height;
387
+ Sankey.prototype._render = function _render (options, context) {
388
+ var sankeyOptions = options || this.options;
389
+ var sankeyContext = context || this;
390
+
391
+ var data = sankeyOptions.data;
392
+ var labelOptions = sankeyOptions.labels;
393
+ var nodesOptions = sankeyOptions.nodes;
394
+ var linkOptions = sankeyOptions.links;
395
+ var nodesColors = sankeyOptions.nodesColors;
396
+ var title = sankeyOptions.title;
397
+ var legend = sankeyOptions.legend;
398
+ var ref = sankeyContext.size;
399
+ var width = ref.width;
400
+ var height = ref.height;
401
+
346
402
  var calcOptions = Object.assign({}, data, {width: width, height: height, nodesOptions: nodesOptions, title: title, legend: legend});
347
- var ref$2 = this.calculateSankey(calcOptions);
348
- var sankey = ref$2.sankey;
349
- var titleBox = ref$2.titleBox;
350
- var legendBox = ref$2.legendBox;
403
+ var ref$1 = this.calculateSankey(calcOptions, sankeyOptions);
404
+ var sankey = ref$1.sankey;
405
+ var titleBox = ref$1.titleBox;
406
+ var legendBox = ref$1.legendBox;
351
407
  var nodes = sankey.nodes;
352
408
  var links = sankey.links;
353
409
 
354
- var visual = new drawing.Group();
410
+ var visual = new drawing.Group({
411
+ clip: drawing.Path.fromRect(new geometry.Rect([0, 0], [width, height]))
412
+ });
355
413
 
356
414
  if (titleBox) {
357
415
  var titleElement = new Title(Object.assign({}, title, {drawingRect: titleBox}));
@@ -366,6 +424,7 @@ export var Sankey = (function (Observable) {
366
424
  }
367
425
 
368
426
  var visualNodes = new Map();
427
+ sankeyContext.nodesVisuals = visualNodes;
369
428
 
370
429
  nodes.forEach(function (node, i) {
371
430
  var nodeOps = resolveNodeOptions(node, nodesOptions, nodesColors, i);
@@ -388,6 +447,7 @@ export var Sankey = (function (Observable) {
388
447
  var sortedLinks = links.slice().sort(function (a, b) { return b.value - a.value; });
389
448
 
390
449
  var linksVisuals = [];
450
+ sankeyContext.linksVisuals = linksVisuals;
391
451
 
392
452
  sortedLinks.forEach(function (link) {
393
453
  var source = link.source;
@@ -412,11 +472,9 @@ export var Sankey = (function (Observable) {
412
472
  visual.append(linkVisual);
413
473
  });
414
474
 
415
- this.linksVisuals = linksVisuals;
416
- this.nodesVisuals = visualNodes;
417
-
475
+ var diagramWidth = nodes.reduce(function (acc, node) { return Math.max(acc, node.x1); }, 0);
418
476
  nodes.forEach(function (node) {
419
- var textOps = resolveLabelOptions(node, labelOptions, width);
477
+ var textOps = resolveLabelOptions(node, labelOptions, diagramWidth);
420
478
  var labelInstance = new Label(textOps);
421
479
  var labelVisual = labelInstance.exportVisual();
422
480
 
@@ -428,8 +486,18 @@ export var Sankey = (function (Observable) {
428
486
  return visual;
429
487
  };
430
488
 
431
- Sankey.prototype.exportVisual = function exportVisual () {
432
- return this._render();
489
+ Sankey.prototype.exportVisual = function exportVisual (exportOptions) {
490
+ var options = (exportOptions && exportOptions.options) ?
491
+ deepExtend({}, this.options, exportOptions.options) : this.options;
492
+
493
+ var context = {
494
+ size: {
495
+ width: defined(exportOptions && exportOptions.width) ? exportOptions.width : this.size.width,
496
+ height: defined(exportOptions && exportOptions.height) ? exportOptions.height : this.size.height
497
+ }
498
+ };
499
+
500
+ return this._render(options, context);
433
501
  };
434
502
 
435
503
  Sankey.prototype._setOptions = function _setOptions (options) {
@@ -440,6 +508,9 @@ export var Sankey = (function (Observable) {
440
508
  }(Observable));
441
509
 
442
510
  setDefaultOptions(Sankey, {
511
+ title: {
512
+ position: TOP, // 'top', 'bottom'
513
+ },
443
514
  labels: {
444
515
  visible: true,
445
516
  margin: {
@@ -458,6 +529,7 @@ setDefaultOptions(Sankey, {
458
529
  width: 24,
459
530
  padding: 16,
460
531
  opacity: 1,
532
+ align: 'stretch', // 'left', 'right', 'stretch'
461
533
  offset: { left: 0, top: 0 }
462
534
  },
463
535
  links: {
@@ -37,7 +37,6 @@ export var Title = (function (SankeyElement) {
37
37
  }(SankeyElement));
38
38
 
39
39
  setDefaultOptions(Title, {
40
- position: TOP, // 'top', 'bottom'
41
40
  align: CENTER, // 'left', 'right', 'center'
42
41
  opacity: 1,
43
42
  border: {