Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions draftlogs/7725_fix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Fix sankey nodes being clipped at the bottom edge when using user-positioned nodes (`node.x`/`node.y`) near `y=1.0` or with `arrangement="snap"` collision resolution [[#7725](https://github.com/plotly/plotly.js/pull/7725)]
19 changes: 17 additions & 2 deletions src/traces/sankey/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,17 @@ function sankeyModel(layout, d, traceIndex) {
}
y = node.y1 + nodePad;
}
// If the last node extends past the bottom, shift the whole column up
if(n > 0) {
var lastNode = nodes[n - 1];
if(lastNode.y1 > height) {
dy = lastNode.y1 - height;
for(i = 0; i < n; ++i) {
nodes[i].y0 -= dy;
nodes[i].y1 -= dy;
}
}
}
});
}

Expand Down Expand Up @@ -251,8 +262,12 @@ function sankeyModel(layout, d, traceIndex) {
graph.nodes[i].x1 = pos[0] + nodeThickness / 2;

var nodeHeight = graph.nodes[i].y1 - graph.nodes[i].y0;
graph.nodes[i].y0 = pos[1] - nodeHeight / 2;
graph.nodes[i].y1 = pos[1] + nodeHeight / 2;
var yCenter = pos[1];
// Clamp so node doesn't extend past bottom or top
yCenter = Math.max(yCenter, nodeHeight / 2);
yCenter = Math.min(yCenter, height - nodeHeight / 2);
graph.nodes[i].y0 = yCenter - nodeHeight / 2;
graph.nodes[i].y1 = yCenter + nodeHeight / 2;
}
}
if(trace.arrangement === 'snap') {
Expand Down
23 changes: 23 additions & 0 deletions test/image/mocks/sankey_x_y_bottom_clipping.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"data": [
{
"type": "sankey",
"arrangement": "snap",
"node": {
"label": ["A", "B", "C", "D"],
"x": [0.1, 0.1, 0.5, 0.9],
"y": [0.5, 0.95, 0.95, 0.95]
},
"link": {
"source": [0, 0, 1, 2],
"target": [1, 2, 3, 3],
"value": [5, 3, 5, 3]
}
}
],
"layout": {
"title": { "text": "Sankey with bottom-edge nodes" },
"width": 600,
"height": 400
}
}
27 changes: 27 additions & 0 deletions test/jasmine/tests/sankey_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -578,6 +578,33 @@ describe('sankey tests', function() {
.then(done, done.fail);
});

it('prevents nodes from being clipped at the bottom edge', function(done) {
var mockBottom = require('../../image/mocks/sankey_x_y_bottom_clipping.json');
var mockCopy = Lib.extendDeep({}, mockBottom);

Plotly.newPlot(gd, mockCopy)
.then(function() {
var nodeRects = document.querySelectorAll('.sankey-node .node-rect');
var sankeyLayer = document.querySelector('.sankey');
var sankeyRect = sankeyLayer.getBoundingClientRect();

for(var i = 0; i < nodeRects.length; i++) {
var rect = nodeRects[i].getBoundingClientRect();
// Every node's bottom edge must be within the sankey area
expect(rect.bottom).not.toBeGreaterThan(
sankeyRect.bottom + 1, // 1px tolerance
'node ' + i + ' extends past the bottom edge'
);
// Every node's top edge must be within the sankey area
expect(rect.top).not.toBeLessThan(
sankeyRect.top - 1, // 1px tolerance
'node ' + i + ' extends past the top edge'
);
}
})
.then(done, done.fail);
});

it('resets each subplot to its initial view (ie. x, y groups) via modebar button', function(done) {
var mockCopy = Lib.extendDeep({}, require('../../image/mocks/sankey_subplots_circular'));

Expand Down