diff --git a/src/lib/svg_text_utils.js b/src/lib/svg_text_utils.js
index 4df1953d3ff..55a4123cb70 100644
--- a/src/lib/svg_text_utils.js
+++ b/src/lib/svg_text_utils.js
@@ -755,10 +755,12 @@ function alignHTMLWith(_base, container, options) {
};
}
-exports.makeTextShadow = function(offset, color) {
- var x = offset + 'px ';
- var y = offset + 'px ';
- var b = '1px ';
+var onePx = '1px ';
+
+exports.makeTextShadow = function(color) {
+ var x = onePx;
+ var y = onePx;
+ var b = onePx;
return x + y + b + color + ', ' +
'-' + x + '-' + y + b + color + ', ' +
x + '-' + y + b + color + ', ' +
diff --git a/src/traces/parcats/parcats.js b/src/traces/parcats/parcats.js
index 77bd227c29f..15f84b8bcb3 100644
--- a/src/traces/parcats/parcats.js
+++ b/src/traces/parcats/parcats.js
@@ -238,7 +238,7 @@ function performPlot(parcatsModels, graphDiv, layout, svg) {
})
.attr('alignment-baseline', 'middle')
- .style('text-shadow', svgTextUtils.makeTextShadow(1, paperColor))
+ .style('text-shadow', svgTextUtils.makeTextShadow(paperColor))
.style('fill', 'rgb(0, 0, 0)')
.attr('x',
function(d) {
diff --git a/src/traces/parcoords/parcoords.js b/src/traces/parcoords/parcoords.js
index 28cc48c89c8..af65f2e0131 100644
--- a/src/traces/parcoords/parcoords.js
+++ b/src/traces/parcoords/parcoords.js
@@ -649,7 +649,7 @@ module.exports = function parcoords(gd, cdModule, layout, callbacks) {
.attr('stroke-width', '1px');
axis.selectAll('text')
- .style('text-shadow', svgTextUtils.makeTextShadow(1, paperColor))
+ .style('text-shadow', svgTextUtils.makeTextShadow(paperColor))
.style('cursor', 'default');
var axisHeading = axisOverlays.selectAll('.' + c.cn.axisHeading)
diff --git a/src/traces/sankey/constants.js b/src/traces/sankey/constants.js
index 3145c55261d..c7eda8ca65b 100644
--- a/src/traces/sankey/constants.js
+++ b/src/traces/sankey/constants.js
@@ -16,10 +16,6 @@ module.exports = {
sankeyNodeSet: 'sankey-node-set',
sankeyNode: 'sankey-node',
nodeRect: 'node-rect',
- nodeCapture: 'node-capture',
- nodeCentered: 'node-entered',
- nodeLabelGuide: 'node-label-guide',
- nodeLabel: 'node-label',
- nodeLabelTextPath: 'node-label-text-path'
+ nodeLabel: 'node-label'
}
};
diff --git a/src/traces/sankey/render.js b/src/traces/sankey/render.js
index fb289ae1d60..ced03a514f5 100644
--- a/src/traces/sankey/render.js
+++ b/src/traces/sankey/render.js
@@ -1,24 +1,31 @@
'use strict';
-var c = require('./constants');
+var d3Force = require('d3-force');
+var interpolateNumber = require('d3-interpolate').interpolateNumber;
var d3 = require('@plotly/d3');
+var d3Sankey = require('@plotly/d3-sankey');
+var d3SankeyCircular = require('@plotly/d3-sankey-circular');
+
+var c = require('./constants');
var tinycolor = require('tinycolor2');
var Color = require('../../components/color');
var Drawing = require('../../components/drawing');
-var d3Sankey = require('@plotly/d3-sankey');
-var d3SankeyCircular = require('@plotly/d3-sankey-circular');
-var d3Force = require('d3-force');
var Lib = require('../../lib');
var strTranslate = Lib.strTranslate;
+var strRotate = Lib.strRotate;
var gup = require('../../lib/gup');
var keyFun = gup.keyFun;
var repeat = gup.repeat;
var unwrap = gup.unwrap;
-var interpolateNumber = require('d3-interpolate').interpolateNumber;
var svgTextUtils = require('../../lib/svg_text_utils');
var Registry = require('../../registry');
+var alignmentConstants = require('../../constants/alignment');
+var CAP_SHIFT = alignmentConstants.CAP_SHIFT;
+var LINE_SPACING = alignmentConstants.LINE_SPACING;
+var TEXTPAD = 3;
+
// view models
function sankeyModel(layout, d, traceIndex) {
@@ -547,22 +554,6 @@ function sankeyTransform(d) {
return offset + (d.horizontal ? 'matrix(1 0 0 1 0 0)' : 'matrix(0 1 1 0 0 0)');
}
-function nodeCentering(d) {
- return strTranslate(d.horizontal ? 0 : d.labelY, d.horizontal ? d.labelY : 0);
-}
-
-function textGuidePath(d) {
- return d3.svg.line()([
- [d.horizontal ? (d.left ? -d.sizeAcross : d.visibleWidth + c.nodeTextOffsetHorizontal) : c.nodeTextOffsetHorizontal, 0],
- [d.horizontal ? (d.left ? - c.nodeTextOffsetHorizontal : d.sizeAcross) : d.visibleHeight - c.nodeTextOffsetHorizontal, 0]
- ]);
-}
-
-function sankeyInverseTransform(d) {return d.horizontal ? 'matrix(1 0 0 1 0 0)' : 'matrix(0 1 1 0 0 0)';}
-function textFlip(d) {return d.horizontal ? 'scale(1 1)' : 'scale(-1 1)';}
-function nodeTextColor(d) {return d.darkBackground && !d.horizontal ? 'rgb(255,255,255)' : 'rgb(0,0,0)';}
-function nodeTextOffset(d) {return d.horizontal && d.left ? '100%' : '0%';}
-
// event handling
function attachPointerEvents(selection, sankey, eventSet) {
@@ -970,88 +961,55 @@ module.exports = function(gd, svg, calcData, layout, callbacks) {
.ease(c.ease).duration(c.duration)
.call(sizeNode);
- var nodeCapture = sankeyNode.selectAll('.' + c.cn.nodeCapture)
- .data(repeat);
-
- nodeCapture.enter()
- .append('rect')
- .classed(c.cn.nodeCapture, true)
- .style('fill-opacity', 0);
-
- nodeCapture
- .attr('x', function(d) {return d.zoneX;})
- .attr('y', function(d) {return d.zoneY;})
- .attr('width', function(d) {return d.zoneWidth;})
- .attr('height', function(d) {return d.zoneHeight;});
-
- var nodeCentered = sankeyNode.selectAll('.' + c.cn.nodeCentered)
- .data(repeat);
-
- nodeCentered.enter()
- .append('g')
- .classed(c.cn.nodeCentered, true)
- .attr('transform', nodeCentering);
-
- nodeCentered
- .transition()
- .ease(c.ease).duration(c.duration)
- .attr('transform', nodeCentering);
-
- var nodeLabelGuide = nodeCentered.selectAll('.' + c.cn.nodeLabelGuide)
- .data(repeat);
-
- nodeLabelGuide.enter()
- .append('path')
- .classed(c.cn.nodeLabelGuide, true)
- .attr('id', function(d) {return d.uniqueNodeLabelPathId;})
- .attr('d', textGuidePath)
- .attr('transform', sankeyInverseTransform);
-
- nodeLabelGuide
- .transition()
- .ease(c.ease).duration(c.duration)
- .attr('d', textGuidePath)
- .attr('transform', sankeyInverseTransform);
-
- var nodeLabel = nodeCentered.selectAll('.' + c.cn.nodeLabel)
+ var nodeLabel = sankeyNode.selectAll('.' + c.cn.nodeLabel)
.data(repeat);
nodeLabel.enter()
.append('text')
.classed(c.cn.nodeLabel, true)
- .attr('transform', textFlip)
- .style('cursor', 'default')
- .style('fill', 'black');
+ .style('cursor', 'default');
nodeLabel
- .style('text-shadow', function(d) {
- return d.horizontal ? svgTextUtils.makeTextShadow(1, '#fff') : 'none';
+ .attr('data-notex', 1) // prohibit tex interpretation until we can handle tex and regular text together
+ .text(function(d) { return d.node.label; })
+ .each(function(d) {
+ var e = d3.select(this);
+ Drawing.font(e, d.textFont);
+ svgTextUtils.convertToTspans(e, gd);
})
- .each(function(d) {Drawing.font(nodeLabel, d.textFont);});
-
- nodeLabel
- .transition()
- .ease(c.ease).duration(c.duration)
- .attr('transform', textFlip);
-
- var nodeLabelTextPath = nodeLabel.selectAll('.' + c.cn.nodeLabelTextPath)
- .data(repeat);
+ .style('text-shadow', svgTextUtils.makeTextShadow(gd._fullLayout.paper_bgcolor))
+ .attr('text-anchor', function(d) {
+ return (d.horizontal && d.left) ? 'end' : 'start';
+ })
+ .attr('transform', function(d) {
+ var e = d3.select(this);
+ // how much to shift a multi-line label to center it vertically.
+ var nLines = svgTextUtils.lineCount(e);
+ var blockHeight = d.textFont.size * (
+ (nLines - 1) * LINE_SPACING - CAP_SHIFT
+ );
+
+ var posX = d.nodeLineWidth / 2 + TEXTPAD;
+ var posY = ((d.horizontal ? d.visibleHeight : d.visibleWidth) - blockHeight) / 2;
+ if(d.horizontal) {
+ if(d.left) {
+ posX = -posX;
+ } else {
+ posX += d.visibleWidth;
+ }
+ }
- nodeLabelTextPath.enter()
- .append('textPath')
- .classed(c.cn.nodeLabelTextPath, true)
- .attr('alignment-baseline', 'middle')
- .attr('xlink:href', function(d) {return '#' + d.uniqueNodeLabelPathId;})
- .attr('startOffset', nodeTextOffset)
- .style('fill', nodeTextColor);
+ var flipText = d.horizontal ? '' : (
+ 'scale(-1,1)' + strRotate(90)
+ );
- nodeLabelTextPath
- .text(function(d) {return d.horizontal || d.node.dy > 5 ? d.node.label : '';})
- .attr('text-anchor', function(d) {return d.horizontal && d.left ? 'end' : 'start';});
+ return strTranslate(
+ d.horizontal ? posX : posY,
+ d.horizontal ? posY : posX
+ ) + flipText;
+ });
- nodeLabelTextPath
+ nodeLabel
.transition()
- .ease(c.ease).duration(c.duration)
- .attr('startOffset', nodeTextOffset)
- .style('fill', nodeTextColor);
+ .ease(c.ease).duration(c.duration);
};
diff --git a/test/image/baselines/sankey_circular.png b/test/image/baselines/sankey_circular.png
index f2ba831b652..a85f6c8fe98 100644
Binary files a/test/image/baselines/sankey_circular.png and b/test/image/baselines/sankey_circular.png differ
diff --git a/test/image/baselines/sankey_circular_large.png b/test/image/baselines/sankey_circular_large.png
index 600ff74b614..97844d0ae23 100644
Binary files a/test/image/baselines/sankey_circular_large.png and b/test/image/baselines/sankey_circular_large.png differ
diff --git a/test/image/baselines/sankey_circular_process.png b/test/image/baselines/sankey_circular_process.png
index 3be8d36284a..ab285e48f77 100644
Binary files a/test/image/baselines/sankey_circular_process.png and b/test/image/baselines/sankey_circular_process.png differ
diff --git a/test/image/baselines/sankey_circular_simple.png b/test/image/baselines/sankey_circular_simple.png
index 8c6a99f2e26..295945716d5 100644
Binary files a/test/image/baselines/sankey_circular_simple.png and b/test/image/baselines/sankey_circular_simple.png differ
diff --git a/test/image/baselines/sankey_circular_simple2.png b/test/image/baselines/sankey_circular_simple2.png
index 61e8c1571e3..7fd506cf2b5 100644
Binary files a/test/image/baselines/sankey_circular_simple2.png and b/test/image/baselines/sankey_circular_simple2.png differ
diff --git a/test/image/baselines/sankey_energy.png b/test/image/baselines/sankey_energy.png
index 0e059234f0b..36641bd909c 100644
Binary files a/test/image/baselines/sankey_energy.png and b/test/image/baselines/sankey_energy.png differ
diff --git a/test/image/baselines/sankey_energy_dark.png b/test/image/baselines/sankey_energy_dark.png
index b2feea2325f..3a9b1f4d922 100644
Binary files a/test/image/baselines/sankey_energy_dark.png and b/test/image/baselines/sankey_energy_dark.png differ
diff --git a/test/image/baselines/sankey_groups.png b/test/image/baselines/sankey_groups.png
index ee05b7d512e..41ea4caeec5 100644
Binary files a/test/image/baselines/sankey_groups.png and b/test/image/baselines/sankey_groups.png differ
diff --git a/test/image/baselines/sankey_large_padding.png b/test/image/baselines/sankey_large_padding.png
index b27b24c12eb..e7e70a7b614 100644
Binary files a/test/image/baselines/sankey_large_padding.png and b/test/image/baselines/sankey_large_padding.png differ
diff --git a/test/image/baselines/sankey_link_concentration.png b/test/image/baselines/sankey_link_concentration.png
index 9b512b8fac7..79d5b7e5a98 100644
Binary files a/test/image/baselines/sankey_link_concentration.png and b/test/image/baselines/sankey_link_concentration.png differ
diff --git a/test/image/baselines/sankey_messy.png b/test/image/baselines/sankey_messy.png
index e42539a8a7d..d06586a3833 100644
Binary files a/test/image/baselines/sankey_messy.png and b/test/image/baselines/sankey_messy.png differ
diff --git a/test/image/baselines/sankey_subplots.png b/test/image/baselines/sankey_subplots.png
index 5034eade547..fa0bb6f7f52 100644
Binary files a/test/image/baselines/sankey_subplots.png and b/test/image/baselines/sankey_subplots.png differ
diff --git a/test/image/baselines/sankey_subplots_circular.png b/test/image/baselines/sankey_subplots_circular.png
index 9d744f00ca4..1ee0a9ec92d 100644
Binary files a/test/image/baselines/sankey_subplots_circular.png and b/test/image/baselines/sankey_subplots_circular.png differ
diff --git a/test/image/baselines/sankey_x_y.png b/test/image/baselines/sankey_x_y.png
index f769d35ab57..2b2b9fef300 100644
Binary files a/test/image/baselines/sankey_x_y.png and b/test/image/baselines/sankey_x_y.png differ
diff --git a/test/image/mocks/sankey_energy_dark.json b/test/image/mocks/sankey_energy_dark.json
index 6552a355a34..e7848260609 100644
--- a/test/image/mocks/sankey_energy_dark.json
+++ b/test/image/mocks/sankey_energy_dark.json
@@ -366,6 +366,7 @@
"height": 1000,
"paper_bgcolor": "rgba(0,0,0,1)",
"font": {
+ "color": "white",
"size": 10
},
"updatemenus": [
diff --git a/test/image/mocks/sankey_subplots.json b/test/image/mocks/sankey_subplots.json
index 17cc7202c20..42700358634 100644
--- a/test/image/mocks/sankey_subplots.json
+++ b/test/image/mocks/sankey_subplots.json
@@ -1,40 +1,57 @@
{
"data": [{
"domain": {
- "x": [0, 0.45]
+ "y": [0, 0.45]
},
"type": "sankey",
"orientation": "h",
"node": {
"line": {
+ "width": 4,
"color": "black"
},
- "label": ["el1", "el2", "el3"]
+ "label": [
+ "Rien ne se perd,
rien ne se crée,
tout se transforme.",
+ "H2O",
+ "eiπ = cos i + i sin π"
+ ]
},
"link": {
"source": [0, 2],
"target": [1, 1],
"value": [120, 50],
"label": ["stram1", "stream2"]
+ },
+ "textfont": {
+ "color": "darkblue"
}
},
{
"domain": {
- "x": [0.55, 1]
+ "y": [0.55, 1]
},
"type": "sankey",
- "orientation": "h",
+ "orientation": "v",
"node": {
+ "thickness": 50,
"line": {
+ "width": 4,
"color": "black"
},
- "label": ["el4", "el5", "el6"]
+ "label": [
+ "Rien ne se perd,
rien ne se crée,
tout se transforme.",
+ "H2O",
+ "eiπ = cos i + i sin π"
+ ]
},
"link": {
"source": [0, 2],
"target": [1, 1],
"value": [120, 50],
- "label": ["stram4", "stream5"]
+ "label": ["stram1", "stream2"]
+ },
+ "textfont": {
+ "color": "darkblue"
}
},
{
@@ -44,7 +61,8 @@
],
"layout": {
"title": {"text": "Multiple Sankey plots"},
- "autosize": true,
+ "width": 600,
+ "height": 600,
"showlegend": false
}
}
diff --git a/test/jasmine/tests/sankey_test.js b/test/jasmine/tests/sankey_test.js
index 2f83014fb6a..e8a25e3465a 100644
--- a/test/jasmine/tests/sankey_test.js
+++ b/test/jasmine/tests/sankey_test.js
@@ -669,7 +669,7 @@ describe('sankey tests', function() {
Lib.clearThrottle();
}
- var node = [404, 302];
+ var node = [410, 300];
var link = [450, 300];
it('should show the correct hover labels', function(done) {
@@ -677,7 +677,7 @@ describe('sankey tests', function() {
var mockCopy = Lib.extendDeep({}, mock);
Plotly.newPlot(gd, mockCopy).then(function() {
- _hover(404, 302);
+ _hover(410, 300);
assertLabel(
['Solid', 'incoming flow count: 4', 'outgoing flow count: 3', '447TWh'],
@@ -697,7 +697,7 @@ describe('sankey tests', function() {
return Plotly.relayout(gd, 'hoverlabel.font.family', 'Roboto');
})
.then(function() {
- _hover(404, 302);
+ _hover(410, 300);
assertLabel(
['Solid', 'incoming flow count: 4', 'outgoing flow count: 3', '447TWh'],
@@ -722,7 +722,7 @@ describe('sankey tests', function() {
});
})
.then(function() {
- _hover(404, 302);
+ _hover(410, 300);
assertLabel(
['Solid', 'incoming flow count: 4', 'outgoing flow count: 3', '447TWh'],
@@ -753,7 +753,7 @@ describe('sankey tests', function() {
});
})
.then(function() {
- _hover(404, 302);
+ _hover(410, 300);
assertLabel(
['Solid', 'incoming flow count: 4', 'outgoing flow count: 3', '447TWh'],
@@ -815,7 +815,7 @@ describe('sankey tests', function() {
mockCopy.data[0].link.customdata[61] = ['linkCustomdata0', 'linkCustomdata1'];
Plotly.newPlot(gd, mockCopy).then(function() {
- _hover(404, 302);
+ _hover(410, 300);
assertLabel(
['Solid', 'incoming flow count: 4', 'outgoing flow count: 3', '447TWh'],
@@ -838,7 +838,7 @@ describe('sankey tests', function() {
});
})
.then(function() {
- _hover(404, 302);
+ _hover(410, 300);
assertLabel(
[ 'hovertemplate', '447TWh', '447.48', 'nodeCustomdata0/nodeCustomdata1', 'trace 0'],
@@ -890,7 +890,7 @@ describe('sankey tests', function() {
Plotly.newPlot(gd, mockCopy)
.then(function() {
- _hover(404, 302);
+ _hover(410, 300);
assertLabel(
['Solid', 'incoming flow count: 4', 'outgoing flow count: 3', '447TWh'],
@@ -1054,7 +1054,7 @@ describe('sankey tests', function() {
var mockCopy = Lib.extendDeep({}, mock);
Plotly.newPlot(gd, mockCopy)
- .then(function() { _hover(404, 302); })
+ .then(function() { _hover(410, 300); })
.then(function() {
assertHoverLabelContent({
nums: 'Solid\nincoming flow count: 4\noutgoing flow count: 3',
@@ -1064,7 +1064,7 @@ describe('sankey tests', function() {
.then(function() {
return Plotly.restyle(gd, 'hoverlabel.namelength', 3);
})
- .then(function() { _hover(404, 302); })
+ .then(function() { _hover(410, 300); })
.then(function() {
assertHoverLabelContent({
nums: 'Solid\nincoming flow count: 4\noutgoing flow count: 3',
@@ -1086,7 +1086,7 @@ describe('sankey tests', function() {
function _makeWrapper(eventType, mouseFn) {
var posByElementType = {
- node: [404, 302],
+ node: [410, 300],
link: [450, 300]
};