Skip to content

Commit 8ce56d7

Browse files
authored
Merge pull request #3284 from plotly/3269-sankey-hovertemplate
hovertemplate support for sankey
2 parents df066d5 + 3204274 commit 8ce56d7

File tree

5 files changed

+88
-5
lines changed

5 files changed

+88
-5
lines changed

src/components/fx/hovertemplate_attributes.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ module.exports = function(opts, extra) {
1919
for(var i = 0; i < keys.length; i++) {
2020
quotedKeys[i] = '`' + keys[i] + '`';
2121
}
22-
descPart = descPart + 'Finally, this trace also supports ';
22+
descPart = descPart + 'Finally, the template string has access to ';
2323
if(keys.length === 1) {
2424
descPart = 'variable ' + quotedKeys[0];
2525
} else {

src/traces/sankey/attributes.js

+9
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ var plotAttrs = require('../../plots/attributes');
1313
var colorAttrs = require('../../components/color/attributes');
1414
var fxAttrs = require('../../components/fx/attributes');
1515
var domainAttrs = require('../../plots/domain').attributes;
16+
var hovertemplateAttrs = require('../../components/fx/hovertemplate_attributes');
1617

1718
var extendFlat = require('../../lib/extend').extendFlat;
1819
var overrideAll = require('../../plot_api/edit_types').overrideAll;
@@ -146,6 +147,10 @@ var attrs = module.exports = overrideAll({
146147
].join(' ')
147148
},
148149
hoverlabel: fxAttrs.hoverlabel, // needs editType override,
150+
hovertemplate: hovertemplateAttrs({}, {
151+
description: 'Variables `sourceLinks` and `targetLinks` are arrays of link objects.',
152+
keys: ['value', 'label']
153+
}),
149154
description: 'The nodes of the Sankey plot.'
150155
},
151156

@@ -216,6 +221,10 @@ var attrs = module.exports = overrideAll({
216221
].join(' ')
217222
},
218223
hoverlabel: fxAttrs.hoverlabel, // needs editType override,
224+
hovertemplate: hovertemplateAttrs({}, {
225+
description: 'Variables `source` and `target` are node objects.',
226+
keys: ['value', 'label']
227+
}),
219228
description: 'The links of the Sankey plot.'
220229
}
221230
}, 'calc', 'nested');

src/traces/sankey/defaults.js

+2
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
3535
coerceNode('line.width');
3636
coerceNode('hoverinfo', traceIn.hoverinfo);
3737
handleHoverLabelDefaults(nodeIn, nodeOut, coerceNode, hoverlabelDefault);
38+
coerceNode('hovertemplate');
3839

3940
var colors = layout.colorway;
4041

@@ -57,6 +58,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
5758
coerceLink('line.width');
5859
coerceLink('hoverinfo', traceIn.hoverinfo);
5960
handleHoverLabelDefaults(linkIn, linkOut, coerceLink, hoverlabelDefault);
61+
coerceLink('hovertemplate');
6062

6163
var defaultLinkColor = tinycolor(layout.paper_bgcolor).getLuminance() < 0.333 ?
6264
'rgba(255, 255, 255, 0.6)' :

src/traces/sankey/plot.js

+21-3
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ module.exports = function plot(gd, calcData) {
133133
if(gd._fullLayout.hovermode === false) return;
134134
d3.select(element).call(linkHoveredStyle.bind(0, d, sankey, true));
135135
if(d.link.trace.link.hoverinfo !== 'skip') {
136+
d.link.fullData = d.link.trace;
136137
gd.emit('plotly_hover', {
137138
event: d3.event,
138139
points: [d.link]
@@ -155,10 +156,13 @@ module.exports = function plot(gd, calcData) {
155156
var hoverCenterX = boundingBox.left + boundingBox.width / 2;
156157
var hoverCenterY = boundingBox.top + boundingBox.height / 2;
157158

159+
var hovertemplateLabels = {valueLabel: d3.format(d.valueFormat)(d.link.value) + d.valueSuffix};
160+
d.link.fullData = d.link.trace;
161+
158162
var tooltip = Fx.loneHover({
159163
x: hoverCenterX - rootBBox.left,
160164
y: hoverCenterY - rootBBox.top,
161-
name: d3.format(d.valueFormat)(d.link.value) + d.valueSuffix,
165+
name: hovertemplateLabels.valueLabel,
162166
text: [
163167
d.link.label || '',
164168
sourceLabel + d.link.source.label,
@@ -169,7 +173,11 @@ module.exports = function plot(gd, calcData) {
169173
fontFamily: castHoverOption(obj, 'font.family'),
170174
fontSize: castHoverOption(obj, 'font.size'),
171175
fontColor: castHoverOption(obj, 'font.color'),
172-
idealAlign: d3.event.x < hoverCenterX ? 'right' : 'left'
176+
idealAlign: d3.event.x < hoverCenterX ? 'right' : 'left',
177+
178+
hovertemplate: obj.hovertemplate,
179+
hovertemplateLabels: hovertemplateLabels,
180+
eventData: [d.link]
173181
}, {
174182
container: fullLayout._hoverlayer.node(),
175183
outerContainer: fullLayout._paper.node(),
@@ -184,6 +192,7 @@ module.exports = function plot(gd, calcData) {
184192
if(gd._fullLayout.hovermode === false) return;
185193
d3.select(element).call(linkNonHoveredStyle.bind(0, d, sankey, true));
186194
if(d.link.trace.link.hoverinfo !== 'skip') {
195+
d.link.fullData = d.link.trace;
187196
gd.emit('plotly_unhover', {
188197
event: d3.event,
189198
points: [d.link]
@@ -205,6 +214,7 @@ module.exports = function plot(gd, calcData) {
205214
if(gd._fullLayout.hovermode === false) return;
206215
d3.select(element).call(nodeHoveredStyle, d, sankey);
207216
if(d.node.trace.node.hoverinfo !== 'skip') {
217+
d.node.fullData = d.node.trace;
208218
gd.emit('plotly_hover', {
209219
event: d3.event,
210220
points: [d.node]
@@ -224,6 +234,9 @@ module.exports = function plot(gd, calcData) {
224234
var hoverCenterX1 = boundingBox.right + 2 - rootBBox.left;
225235
var hoverCenterY = boundingBox.top + boundingBox.height / 4 - rootBBox.top;
226236

237+
var hovertemplateLabels = {valueLabel: d3.format(d.valueFormat)(d.node.value) + d.valueSuffix};
238+
d.node.fullData = d.node.trace;
239+
227240
var tooltip = Fx.loneHover({
228241
x0: hoverCenterX0,
229242
x1: hoverCenterX1,
@@ -239,7 +252,11 @@ module.exports = function plot(gd, calcData) {
239252
fontFamily: castHoverOption(obj, 'font.family'),
240253
fontSize: castHoverOption(obj, 'font.size'),
241254
fontColor: castHoverOption(obj, 'font.color'),
242-
idealAlign: 'left'
255+
idealAlign: 'left',
256+
257+
hovertemplate: obj.hovertemplate,
258+
hovertemplateLabels: hovertemplateLabels,
259+
eventData: [d.node]
243260
}, {
244261
container: fullLayout._hoverlayer.node(),
245262
outerContainer: fullLayout._paper.node(),
@@ -254,6 +271,7 @@ module.exports = function plot(gd, calcData) {
254271
if(gd._fullLayout.hovermode === false) return;
255272
d3.select(element).call(nodeNonHoveredStyle, d, sankey);
256273
if(d.node.trace.node.hoverinfo !== 'skip') {
274+
d.node.fullData = d.node.trace;
257275
gd.emit('plotly_unhover', {
258276
event: d3.event,
259277
points: [d.node]

test/jasmine/tests/sankey_test.js

+55-1
Original file line numberDiff line numberDiff line change
@@ -497,6 +497,53 @@ describe('sankey tests', function() {
497497
.then(done);
498498
});
499499

500+
it('should show the correct hover labels when hovertemplate is specified', function(done) {
501+
var gd = createGraphDiv();
502+
var mockCopy = Lib.extendDeep({}, mock);
503+
504+
Plotly.plot(gd, mockCopy).then(function() {
505+
_hover(404, 302);
506+
507+
assertLabel(
508+
['Solid', 'incoming flow count: 4', 'outgoing flow count: 3', '447TWh'],
509+
['rgb(148, 103, 189)', 'rgb(255, 255, 255)', 13, 'Arial', 'rgb(255, 255, 255)']
510+
);
511+
})
512+
.then(function() {
513+
_hover(450, 300);
514+
515+
assertLabel(
516+
['source: Solid', 'target: Industry', '46TWh'],
517+
['rgb(0, 0, 96)', 'rgb(255, 255, 255)', 13, 'Arial', 'rgb(255, 255, 255)']
518+
);
519+
})
520+
// Test (node|link).hovertemplate
521+
.then(function() {
522+
return Plotly.restyle(gd, {
523+
'node.hovertemplate': 'hovertemplate<br>%{value}<br>%{value:0.2f}<extra>%{fullData.name}</extra>',
524+
'link.hovertemplate': 'hovertemplate<br>source: %{source.label}<br>target: %{target.label}<br>size: %{value:0.0f}TWh<extra>%{fullData.name}</extra>'
525+
});
526+
})
527+
.then(function() {
528+
_hover(404, 302);
529+
530+
assertLabel(
531+
[ 'hovertemplate', '447TWh', '447.48', 'trace 0'],
532+
['rgb(148, 103, 189)', 'rgb(255, 255, 255)', 13, 'Arial', 'rgb(255, 255, 255)']
533+
);
534+
})
535+
.then(function() {
536+
_hover(450, 300);
537+
538+
assertLabel(
539+
['hovertemplate', 'source: Solid', 'target: Industry', 'size: 46TWh', 'trace 0'],
540+
['rgb(0, 0, 96)', 'rgb(255, 255, 255)', 13, 'Arial', 'rgb(255, 255, 255)']
541+
);
542+
})
543+
.catch(failTest)
544+
.then(done);
545+
});
546+
500547
it('should show the correct hover labels with the style provided in template', function(done) {
501548
var gd = createGraphDiv();
502549
var mockCopy = Lib.extendDeep({}, mock);
@@ -744,8 +791,12 @@ describe('sankey tests', function() {
744791
_assert(d, {
745792
curveNumber: 0,
746793
pointNumber: 4,
747-
label: 'Solid'
794+
label: 'Solid',
795+
value: 447.48
748796
});
797+
var pt = d.points[0];
798+
expect(pt.sourceLinks.length).toBe(3);
799+
expect(pt.targetLinks.length).toBe(4);
749800
})
750801
.then(function() { return _hover('link'); })
751802
.then(function(d) {
@@ -754,6 +805,9 @@ describe('sankey tests', function() {
754805
pointNumber: 61,
755806
value: 46.477
756807
});
808+
var pt = d.points[0];
809+
expect(pt.hasOwnProperty('source')).toBeTruthy();
810+
expect(pt.hasOwnProperty('target')).toBeTruthy();
757811
})
758812
.then(function() { return _unhover('node'); })
759813
.then(function(d) {

0 commit comments

Comments
 (0)