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", + "e = 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", + "e = 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] };