Skip to content

Commit b0892ce

Browse files
authored
Merge pull request #1731 from plotly/sankey-rotation-squashed
Sankey rotation between horizontal and vertical
2 parents 0904db3 + 22d2c20 commit b0892ce

File tree

3 files changed

+68
-42
lines changed

3 files changed

+68
-42
lines changed

src/traces/sankey/constants.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,5 @@ module.exports = {
1616
forceIterations: 5,
1717
forceTicksPerFrame: 10,
1818
duration: 500,
19-
ease: 'linear'
19+
ease: 'cubic-in-out'
2020
};

src/traces/sankey/render.js

+67-41
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,6 @@ function sankeyModel(layout, d, i) {
115115
node = sankeyNodes[n];
116116
node.width = width;
117117
node.height = height;
118-
if(node.parallel) node.x = (horizontal ? width : height) * node.parallel;
119-
if(node.perpendicular) node.y = (horizontal ? height : width) * node.perpendicular;
120118
}
121119

122120
switchToForceFormat(nodes);
@@ -180,9 +178,7 @@ function nodeModel(uniqueKeys, d, n) {
180178
zoneThicknessPad = c.nodePadAcross,
181179
zoneLengthPad = d.nodePad / 2,
182180
visibleThickness = n.dx + 0.5,
183-
visibleLength = n.dy - 0.5,
184-
zoneThickness = visibleThickness + 2 * zoneThicknessPad,
185-
zoneLength = visibleLength + 2 * zoneLengthPad;
181+
visibleLength = n.dy - 0.5;
186182

187183
var basicKey = n.label;
188184
var foundKey = uniqueKeys[basicKey];
@@ -198,15 +194,15 @@ function nodeModel(uniqueKeys, d, n) {
198194
nodeLineWidth: d.nodeLineWidth,
199195
textFont: d.textFont,
200196
size: d.horizontal ? d.height : d.width,
201-
visibleWidth: Math.ceil(d.horizontal ? visibleThickness : visibleLength),
202-
visibleHeight: Math.ceil(d.horizontal ? visibleLength : visibleThickness),
203-
zoneX: d.horizontal ? -zoneThicknessPad : -zoneLengthPad,
204-
zoneY: d.horizontal ? -zoneLengthPad : -zoneThicknessPad,
205-
zoneWidth: d.horizontal ? zoneThickness : zoneLength,
206-
zoneHeight: d.horizontal ? zoneLength : zoneThickness,
197+
visibleWidth: Math.ceil(visibleThickness),
198+
visibleHeight: Math.ceil(visibleLength),
199+
zoneX: -zoneThicknessPad,
200+
zoneY: -zoneLengthPad,
201+
zoneWidth: visibleThickness + 2 * zoneThicknessPad,
202+
zoneHeight: visibleLength + 2 * zoneLengthPad,
207203
labelY: d.horizontal ? n.dy / 2 + 1 : n.dx / 2 + 1,
208204
left: n.originalLayer === 1,
209-
sizeAcross: d.horizontal ? d.width : d.height,
205+
sizeAcross: d.width,
210206
forceLayouts: d.forceLayouts,
211207
horizontal: d.horizontal,
212208
darkBackground: tc.getBrightness() <= 128,
@@ -230,9 +226,7 @@ function crispLinesOnEnd(sankeyNode) {
230226
function updateNodePositions(sankeyNode) {
231227
sankeyNode
232228
.attr('transform', function(d) {
233-
return d.horizontal ?
234-
'translate(' + (d.node.x - 0.5) + ', ' + (d.node.y - d.node.dy / 2 + 0.5) + ')' :
235-
'translate(' + (d.node.y - d.node.dy / 2 - 0.5) + ', ' + (d.node.x + 0.5) + ')';
229+
return 'translate(' + (d.node.x - 0.5) + ', ' + (d.node.y - d.node.dy / 2 + 0.5) + ')';
236230
});
237231
}
238232

@@ -259,20 +253,28 @@ function sizeNode(rect) {
259253
.attr('height', function(d) {return d.visibleHeight;});
260254
}
261255

262-
function salientEnough(d) {
263-
return d.link.dy > 1 || d.linkLineWidth > 0;
256+
function salientEnough(d) {return d.link.dy > 1 || d.linkLineWidth > 0;}
257+
258+
function sankeyTransform(d) {
259+
var offset = 'translate(' + d.translateX + ',' + d.translateY + ')';
260+
return offset + (d.horizontal ? 'matrix(1 0 0 1 0 0)' : 'matrix(0 1 1 0 0 0)');
264261
}
265262

266-
function linksTransform(d) {
267-
return d.horizontal ? 'matrix(1 0 0 1 0 0)' : 'matrix(0 1 1 0 0 0)';
263+
function nodeCentering(d) {
264+
return 'translate(' + (d.horizontal ? 0 : d.labelY) + ' ' + (d.horizontal ? d.labelY : 0) + ')';
268265
}
269266

270267
function textGuidePath(d) {
271268
return d3.svg.line()([
272-
[d.horizontal ? (d.left ? -d.sizeAcross : d.visibleWidth + c.nodeTextOffsetHorizontal) : c.nodeTextOffsetHorizontal, d.labelY],
273-
[d.horizontal ? (d.left ? - c.nodeTextOffsetHorizontal : d.sizeAcross) : d.visibleWidth - c.nodeTextOffsetHorizontal, d.labelY]
269+
[d.horizontal ? (d.left ? -d.sizeAcross : d.visibleWidth + c.nodeTextOffsetHorizontal) : c.nodeTextOffsetHorizontal, 0],
270+
[d.horizontal ? (d.left ? - c.nodeTextOffsetHorizontal : d.sizeAcross) : d.visibleHeight - c.nodeTextOffsetHorizontal, 0]
274271
]);}
275272

273+
function sankeyInverseTransform(d) {return d.horizontal ? 'matrix(1 0 0 1 0 0)' : 'matrix(0 1 1 0 0 0)';}
274+
function textFlip(d) {return d.horizontal ? 'scale(1 1)' : 'scale(-1 1)';}
275+
function nodeTextColor(d) {return d.darkBackground && !d.horizontal ? 'rgb(255,255,255)' : 'rgb(0,0,0)';}
276+
function nodeTextOffset(d) {return d.horizontal && d.left ? '100%' : '0%';}
277+
276278
// event handling
277279

278280
function attachPointerEvents(selection, sankey, eventSet) {
@@ -311,7 +313,7 @@ function attachDragHandler(sankeyNode, sankeyLink, callbacks) {
311313

312314
var dragBehavior = d3.behavior.drag()
313315

314-
.origin(function(d) {return d.horizontal ? d.node : {x: d.node.y, y: d.node.x};})
316+
.origin(function(d) {return d.node;})
315317

316318
.on('dragstart', function(d) {
317319
if(d.arrangement === 'fixed') return;
@@ -335,8 +337,8 @@ function attachDragHandler(sankeyNode, sankeyLink, callbacks) {
335337

336338
.on('drag', function(d) {
337339
if(d.arrangement === 'fixed') return;
338-
var x = d.horizontal ? d3.event.x : d3.event.y;
339-
var y = d.horizontal ? d3.event.y : d3.event.x;
340+
var x = d3.event.x;
341+
var y = d3.event.y;
340342
if(d.arrangement === 'snap') {
341343
d.node.x = x;
342344
d.node.y = y;
@@ -432,23 +434,20 @@ module.exports = function(svg, styledData, layout, callbacks) {
432434
.style('left', 0)
433435
.style('shape-rendering', 'geometricPrecision')
434436
.style('pointer-events', 'auto')
435-
.style('box-sizing', 'content-box');
437+
.style('box-sizing', 'content-box')
438+
.attr('transform', sankeyTransform);
436439

437-
sankey
438-
.attr('transform', function(d) {return 'translate(' + d.translateX + ',' + d.translateY + ')';});
440+
sankey.transition()
441+
.ease(c.ease).duration(c.duration)
442+
.attr('transform', sankeyTransform);
439443

440444
var sankeyLinks = sankey.selectAll('.sankeyLinks')
441445
.data(repeat, keyFun);
442446

443447
sankeyLinks.enter()
444448
.append('g')
445449
.classed('sankeyLinks', true)
446-
.style('fill', 'none')
447-
.attr('transform', linksTransform);
448-
449-
sankeyLinks.transition()
450-
.ease(c.ease).duration(c.duration)
451-
.attr('transform', linksTransform);
450+
.style('fill', 'none');
452451

453452
var sankeyLink = sankeyLinks.selectAll('.sankeyLink')
454453
.data(function(d) {
@@ -562,26 +561,42 @@ module.exports = function(svg, styledData, layout, callbacks) {
562561
.attr('width', function(d) {return d.zoneWidth;})
563562
.attr('height', function(d) {return d.zoneHeight;});
564563

565-
var nodeLabelGuide = sankeyNode.selectAll('.nodeLabelGuide')
564+
var nodeCentered = sankeyNode.selectAll('.nodeCentered')
565+
.data(repeat);
566+
567+
nodeCentered.enter()
568+
.append('g')
569+
.classed('nodeCentered', true)
570+
.attr('transform', nodeCentering);
571+
572+
nodeCentered
573+
.transition()
574+
.ease(c.ease).duration(c.duration)
575+
.attr('transform', nodeCentering);
576+
577+
var nodeLabelGuide = nodeCentered.selectAll('.nodeLabelGuide')
566578
.data(repeat);
567579

568580
nodeLabelGuide.enter()
569581
.append('path')
570582
.classed('nodeLabelGuide', true)
571583
.attr('id', function(d) {return d.uniqueNodeLabelPathId;})
572-
.attr('d', textGuidePath);
584+
.attr('d', textGuidePath)
585+
.attr('transform', sankeyInverseTransform);
573586

574587
nodeLabelGuide
575588
.transition()
576589
.ease(c.ease).duration(c.duration)
577-
.attr('d', textGuidePath);
590+
.attr('d', textGuidePath)
591+
.attr('transform', sankeyInverseTransform);
578592

579-
var nodeLabel = sankeyNode.selectAll('.nodeLabel')
593+
var nodeLabel = nodeCentered.selectAll('.nodeLabel')
580594
.data(repeat);
581595

582596
nodeLabel.enter()
583597
.append('text')
584598
.classed('nodeLabel', true)
599+
.attr('transform', textFlip)
585600
.style('user-select', 'none')
586601
.style('cursor', 'default')
587602
.style('fill', 'black');
@@ -592,18 +607,29 @@ module.exports = function(svg, styledData, layout, callbacks) {
592607
})
593608
.each(function(d) {Drawing.font(nodeLabel, d.textFont);});
594609

610+
nodeLabel
611+
.transition()
612+
.ease(c.ease).duration(c.duration)
613+
.attr('transform', textFlip);
614+
595615
var nodeLabelTextPath = nodeLabel.selectAll('.nodeLabelTextPath')
596616
.data(repeat);
597617

598618
nodeLabelTextPath.enter()
599619
.append('textPath')
600620
.classed('nodeLabelTextPath', true)
601621
.attr('alignment-baseline', 'middle')
602-
.attr('xlink:href', function(d) {return '#' + d.uniqueNodeLabelPathId;});
622+
.attr('xlink:href', function(d) {return '#' + d.uniqueNodeLabelPathId;})
623+
.attr('startOffset', nodeTextOffset)
624+
.style('fill', nodeTextColor);
603625

604626
nodeLabelTextPath
605627
.text(function(d) {return d.horizontal || d.node.dy > 5 ? d.node.label : '';})
606-
.attr('startOffset', function(d) {return d.horizontal && d.left ? '100%' : '0%';})
607-
.style('text-anchor', function(d) {return d.horizontal && d.left ? 'end' : 'start';})
608-
.style('fill', function(d) {return d.darkBackground && !d.horizontal ? 'white' : 'black';});
628+
.style('text-anchor', function(d) {return d.horizontal && d.left ? 'end' : 'start';});
629+
630+
nodeLabelTextPath
631+
.transition()
632+
.ease(c.ease).duration(c.duration)
633+
.attr('startOffset', nodeTextOffset)
634+
.style('fill', nodeTextColor);
609635
};
3.3 KB
Loading

0 commit comments

Comments
 (0)