diff --git a/src/components/fx/hovertemplate_attributes.js b/src/components/fx/hovertemplate_attributes.js index 6c002d6404c..7d0359cb0e1 100644 --- a/src/components/fx/hovertemplate_attributes.js +++ b/src/components/fx/hovertemplate_attributes.js @@ -19,7 +19,7 @@ module.exports = function(opts, extra) { for(var i = 0; i < keys.length; i++) { quotedKeys[i] = '`' + keys[i] + '`'; } - descPart = descPart + 'Finally, this trace also supports '; + descPart = descPart + 'Finally, the template string has access to '; if(keys.length === 1) { descPart = 'variable ' + quotedKeys[0]; } else { diff --git a/src/traces/sankey/attributes.js b/src/traces/sankey/attributes.js index ee4d1cd5530..df4af7ba137 100644 --- a/src/traces/sankey/attributes.js +++ b/src/traces/sankey/attributes.js @@ -13,6 +13,7 @@ var plotAttrs = require('../../plots/attributes'); var colorAttrs = require('../../components/color/attributes'); var fxAttrs = require('../../components/fx/attributes'); var domainAttrs = require('../../plots/domain').attributes; +var hovertemplateAttrs = require('../../components/fx/hovertemplate_attributes'); var extendFlat = require('../../lib/extend').extendFlat; var overrideAll = require('../../plot_api/edit_types').overrideAll; @@ -146,6 +147,10 @@ var attrs = module.exports = overrideAll({ ].join(' ') }, hoverlabel: fxAttrs.hoverlabel, // needs editType override, + hovertemplate: hovertemplateAttrs({}, { + description: 'Variables `sourceLinks` and `targetLinks` are arrays of link objects.', + keys: ['value', 'label'] + }), description: 'The nodes of the Sankey plot.' }, @@ -216,6 +221,10 @@ var attrs = module.exports = overrideAll({ ].join(' ') }, hoverlabel: fxAttrs.hoverlabel, // needs editType override, + hovertemplate: hovertemplateAttrs({}, { + description: 'Variables `source` and `target` are node objects.', + keys: ['value', 'label'] + }), description: 'The links of the Sankey plot.' } }, 'calc', 'nested'); diff --git a/src/traces/sankey/defaults.js b/src/traces/sankey/defaults.js index 51dc790f846..25a89c91dc2 100644 --- a/src/traces/sankey/defaults.js +++ b/src/traces/sankey/defaults.js @@ -35,6 +35,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerceNode('line.width'); coerceNode('hoverinfo', traceIn.hoverinfo); handleHoverLabelDefaults(nodeIn, nodeOut, coerceNode, hoverlabelDefault); + coerceNode('hovertemplate'); var colors = layout.colorway; @@ -57,6 +58,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerceLink('line.width'); coerceLink('hoverinfo', traceIn.hoverinfo); handleHoverLabelDefaults(linkIn, linkOut, coerceLink, hoverlabelDefault); + coerceLink('hovertemplate'); var defaultLinkColor = tinycolor(layout.paper_bgcolor).getLuminance() < 0.333 ? 'rgba(255, 255, 255, 0.6)' : diff --git a/src/traces/sankey/plot.js b/src/traces/sankey/plot.js index cb6c7300c74..6a89c2306cd 100644 --- a/src/traces/sankey/plot.js +++ b/src/traces/sankey/plot.js @@ -133,6 +133,7 @@ module.exports = function plot(gd, calcData) { if(gd._fullLayout.hovermode === false) return; d3.select(element).call(linkHoveredStyle.bind(0, d, sankey, true)); if(d.link.trace.link.hoverinfo !== 'skip') { + d.link.fullData = d.link.trace; gd.emit('plotly_hover', { event: d3.event, points: [d.link] @@ -155,10 +156,13 @@ module.exports = function plot(gd, calcData) { var hoverCenterX = boundingBox.left + boundingBox.width / 2; var hoverCenterY = boundingBox.top + boundingBox.height / 2; + var hovertemplateLabels = {valueLabel: d3.format(d.valueFormat)(d.link.value) + d.valueSuffix}; + d.link.fullData = d.link.trace; + var tooltip = Fx.loneHover({ x: hoverCenterX - rootBBox.left, y: hoverCenterY - rootBBox.top, - name: d3.format(d.valueFormat)(d.link.value) + d.valueSuffix, + name: hovertemplateLabels.valueLabel, text: [ d.link.label || '', sourceLabel + d.link.source.label, @@ -169,7 +173,11 @@ module.exports = function plot(gd, calcData) { fontFamily: castHoverOption(obj, 'font.family'), fontSize: castHoverOption(obj, 'font.size'), fontColor: castHoverOption(obj, 'font.color'), - idealAlign: d3.event.x < hoverCenterX ? 'right' : 'left' + idealAlign: d3.event.x < hoverCenterX ? 'right' : 'left', + + hovertemplate: obj.hovertemplate, + hovertemplateLabels: hovertemplateLabels, + eventData: [d.link] }, { container: fullLayout._hoverlayer.node(), outerContainer: fullLayout._paper.node(), @@ -184,6 +192,7 @@ module.exports = function plot(gd, calcData) { if(gd._fullLayout.hovermode === false) return; d3.select(element).call(linkNonHoveredStyle.bind(0, d, sankey, true)); if(d.link.trace.link.hoverinfo !== 'skip') { + d.link.fullData = d.link.trace; gd.emit('plotly_unhover', { event: d3.event, points: [d.link] @@ -205,6 +214,7 @@ module.exports = function plot(gd, calcData) { if(gd._fullLayout.hovermode === false) return; d3.select(element).call(nodeHoveredStyle, d, sankey); if(d.node.trace.node.hoverinfo !== 'skip') { + d.node.fullData = d.node.trace; gd.emit('plotly_hover', { event: d3.event, points: [d.node] @@ -224,6 +234,9 @@ module.exports = function plot(gd, calcData) { var hoverCenterX1 = boundingBox.right + 2 - rootBBox.left; var hoverCenterY = boundingBox.top + boundingBox.height / 4 - rootBBox.top; + var hovertemplateLabels = {valueLabel: d3.format(d.valueFormat)(d.node.value) + d.valueSuffix}; + d.node.fullData = d.node.trace; + var tooltip = Fx.loneHover({ x0: hoverCenterX0, x1: hoverCenterX1, @@ -239,7 +252,11 @@ module.exports = function plot(gd, calcData) { fontFamily: castHoverOption(obj, 'font.family'), fontSize: castHoverOption(obj, 'font.size'), fontColor: castHoverOption(obj, 'font.color'), - idealAlign: 'left' + idealAlign: 'left', + + hovertemplate: obj.hovertemplate, + hovertemplateLabels: hovertemplateLabels, + eventData: [d.node] }, { container: fullLayout._hoverlayer.node(), outerContainer: fullLayout._paper.node(), @@ -254,6 +271,7 @@ module.exports = function plot(gd, calcData) { if(gd._fullLayout.hovermode === false) return; d3.select(element).call(nodeNonHoveredStyle, d, sankey); if(d.node.trace.node.hoverinfo !== 'skip') { + d.node.fullData = d.node.trace; gd.emit('plotly_unhover', { event: d3.event, points: [d.node] diff --git a/test/jasmine/tests/sankey_test.js b/test/jasmine/tests/sankey_test.js index dfb1855f275..4f881126619 100644 --- a/test/jasmine/tests/sankey_test.js +++ b/test/jasmine/tests/sankey_test.js @@ -497,6 +497,53 @@ describe('sankey tests', function() { .then(done); }); + it('should show the correct hover labels when hovertemplate is specified', function(done) { + var gd = createGraphDiv(); + var mockCopy = Lib.extendDeep({}, mock); + + Plotly.plot(gd, mockCopy).then(function() { + _hover(404, 302); + + assertLabel( + ['Solid', 'incoming flow count: 4', 'outgoing flow count: 3', '447TWh'], + ['rgb(148, 103, 189)', 'rgb(255, 255, 255)', 13, 'Arial', 'rgb(255, 255, 255)'] + ); + }) + .then(function() { + _hover(450, 300); + + assertLabel( + ['source: Solid', 'target: Industry', '46TWh'], + ['rgb(0, 0, 96)', 'rgb(255, 255, 255)', 13, 'Arial', 'rgb(255, 255, 255)'] + ); + }) + // Test (node|link).hovertemplate + .then(function() { + return Plotly.restyle(gd, { + 'node.hovertemplate': 'hovertemplate
%{value}
%{value:0.2f}%{fullData.name}', + 'link.hovertemplate': 'hovertemplate
source: %{source.label}
target: %{target.label}
size: %{value:0.0f}TWh%{fullData.name}' + }); + }) + .then(function() { + _hover(404, 302); + + assertLabel( + [ 'hovertemplate', '447TWh', '447.48', 'trace 0'], + ['rgb(148, 103, 189)', 'rgb(255, 255, 255)', 13, 'Arial', 'rgb(255, 255, 255)'] + ); + }) + .then(function() { + _hover(450, 300); + + assertLabel( + ['hovertemplate', 'source: Solid', 'target: Industry', 'size: 46TWh', 'trace 0'], + ['rgb(0, 0, 96)', 'rgb(255, 255, 255)', 13, 'Arial', 'rgb(255, 255, 255)'] + ); + }) + .catch(failTest) + .then(done); + }); + it('should show the correct hover labels with the style provided in template', function(done) { var gd = createGraphDiv(); var mockCopy = Lib.extendDeep({}, mock); @@ -744,8 +791,12 @@ describe('sankey tests', function() { _assert(d, { curveNumber: 0, pointNumber: 4, - label: 'Solid' + label: 'Solid', + value: 447.48 }); + var pt = d.points[0]; + expect(pt.sourceLinks.length).toBe(3); + expect(pt.targetLinks.length).toBe(4); }) .then(function() { return _hover('link'); }) .then(function(d) { @@ -754,6 +805,9 @@ describe('sankey tests', function() { pointNumber: 61, value: 46.477 }); + var pt = d.points[0]; + expect(pt.hasOwnProperty('source')).toBeTruthy(); + expect(pt.hasOwnProperty('target')).toBeTruthy(); }) .then(function() { return _unhover('node'); }) .then(function(d) {