Skip to content

Commit fbc03a3

Browse files
committed
fix issue 3954 - implement hoverinfo and hovertemplate for funnel percentages
1 parent 3e3b22d commit fbc03a3

File tree

8 files changed

+138
-16
lines changed

8 files changed

+138
-16
lines changed

src/components/fx/helpers.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,9 @@ exports.makeEventData = function makeEventData(pt, trace, cd) {
149149
if(pt.xa) out.xaxis = pt.xa;
150150
if(pt.ya) out.yaxis = pt.ya;
151151
if(pt.zLabelVal !== undefined) out.z = pt.zLabelVal;
152+
if(pt.percentInitial !== undefined) out.z = pt.percentInitial;
153+
if(pt.percentPrevious !== undefined) out.z = pt.percentPrevious;
154+
if(pt.percentTotal !== undefined) out.z = pt.percentTotal;
152155
}
153156

154157
exports.appendArrayPointValue(out, trace, pointNumber);

src/components/fx/hover.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,9 @@ function _hover(gd, evt, subplot, noHoverEvent) {
432432
xLabelVal: undefined,
433433
yLabelVal: undefined,
434434
zLabelVal: undefined,
435+
percentInitial: undefined,
436+
percentPrevious: undefined,
437+
percentTotal: undefined,
435438
text: undefined
436439
};
437440

@@ -908,6 +911,16 @@ function createHoverText(hoverData, opts, gd) {
908911
text += (text ? '<br>' : '') + d.text;
909912
}
910913

914+
if(d.percentInitial !== undefined) {
915+
text += (text ? '<br>' : '') + d.percentInitial + ' of initial';
916+
}
917+
if(d.percentPrevious !== undefined) {
918+
text += (text ? '<br>' : '') + d.percentPrevious + ' of previous';
919+
}
920+
if(d.percentTotal !== undefined) {
921+
text += (text ? '<br>' : '') + d.percentTotal + ' of total';
922+
}
923+
911924
// used by other modules (initially just ternary) that
912925
// manage their own hoverinfo independent of cleanPoint
913926
// the rest of this will still apply, so such modules
@@ -1383,6 +1396,9 @@ function cleanPoint(d, hovermode) {
13831396
if(infomode.indexOf('z') === -1) d.zLabel = undefined;
13841397
if(infomode.indexOf('text') === -1) d.text = undefined;
13851398
if(infomode.indexOf('name') === -1) d.name = undefined;
1399+
if(infomode.indexOf('percentInitial') === -1) d.percentInitial = undefined;
1400+
if(infomode.indexOf('percentPrevious') === -1) d.percentPrevious = undefined;
1401+
if(infomode.indexOf('percentTotal') === -1) d.percentTotal = undefined;
13861402
}
13871403

13881404
return d;

src/traces/funnel/attributes.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
var barAttrs = require('../bar/attributes');
1212
var lineAttrs = require('../scatter/attributes').line;
13+
var plotAttrs = require('../../plots/attributes');
1314
var extendFlat = require('../../lib/extend').extendFlat;
1415
var Color = require('../../components/color');
1516

@@ -23,6 +24,9 @@ module.exports = {
2324

2425
hovertext: barAttrs.hovertext,
2526
hovertemplate: barAttrs.hovertemplate,
27+
hoverinfo: extendFlat({}, plotAttrs.hoverinfo, {
28+
flags: ['percentInitial', 'percentPrevious', 'percentTotal'].concat(plotAttrs.hoverinfo.flags)
29+
}),
2630

2731
textinfo: {
2832
valType: 'flaglist',

src/traces/funnel/event_data.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/**
2+
* Copyright 2012-2019, Plotly, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
'use strict';
10+
11+
module.exports = function eventData(out, pt /* , trace, cd, pointNumber */) {
12+
// standard cartesian event data
13+
out.x = 'xVal' in pt ? pt.xVal : pt.x;
14+
out.y = 'yVal' in pt ? pt.yVal : pt.y;
15+
16+
// for funnel
17+
if('percentInitial' in pt) out.percentInitial = pt.percentInitial;
18+
if('percentPrevious' in pt) out.percentPrevious = pt.percentPrevious;
19+
if('percentTotal' in pt) out.percentTotal = pt.percentTotal;
20+
21+
if(pt.xa) out.xaxis = pt.xa;
22+
if(pt.ya) out.yaxis = pt.ya;
23+
24+
return out;
25+
};

src/traces/funnel/hover.js

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,18 @@ module.exports = function hoverPoints(pointData, xval, yval, hovermode) {
2525
var di = cd[index];
2626

2727
var sizeLetter = isHorizontal ? 'x' : 'y';
28-
2928
point[sizeLetter + 'LabelVal'] = di.s;
3029

31-
// display ratio to initial value
32-
point.extraText = [
33-
formatPercent(di.begR, 1) + ' of initial',
34-
formatPercent(di.difR, 1) + ' of previous',
35-
formatPercent(di.sumR, 1) + ' of total'
36-
].join('<br>');
37-
// TODO: Should we use pieHelpers.formatPieValue instead ?
30+
var hoverinfo = trace.hoverinfo;
31+
if(hoverinfo !== 'none' && hoverinfo !== 'skip') {
32+
var parts = (hoverinfo || '').split('+');
33+
var isAll = (hoverinfo === 'all') || (hoverinfo === undefined);
34+
var hasFlag = function(flag) { return isAll || parts.indexOf(flag) !== -1; };
35+
36+
if(hasFlag('percentInitial')) point.percentInitial = formatPercent(di.begR, 1);
37+
if(hasFlag('percentPrevious')) point.percentPrevious = formatPercent(di.difR, 1);
38+
if(hasFlag('percentTotal')) point.percentTotal = formatPercent(di.sumR, 1);
39+
}
3840

3941
point.color = getTraceColor(trace, di);
4042

src/traces/funnel/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ module.exports = {
1919
plot: require('./plot'),
2020
style: require('./style').style,
2121
hoverPoints: require('./hover'),
22+
eventData: require('./event_data'),
23+
2224
selectPoints: require('../bar/select'),
2325

2426
moduleType: 'trace',

test/jasmine/assets/custom_assertions.js

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ function count(selector) {
9898
* - vOrder {array of number}
9999
* - hOrder {array of number}
100100
* - isRotated {boolean}
101+
* - isEmpty {boolean}
101102
* @param {string} msg
102103
*/
103104
exports.assertHoverLabelContent = function(expectation, msg) {
@@ -192,11 +193,15 @@ exports.assertHoverLabelContent = function(expectation, msg) {
192193
}
193194
});
194195
} else {
195-
if(expectation.nums) {
196-
fail(ptMsg + ': expecting *nums* labels, did not find any.');
197-
}
198-
if(expectation.name) {
199-
fail(ptMsg + ': expecting *nums* labels, did not find any.');
196+
if(expectation.isEmpty) {
197+
return true;
198+
} else {
199+
if(expectation.nums) {
200+
fail(ptMsg + ': expecting *nums* labels, did not find any.');
201+
}
202+
if(expectation.name) {
203+
fail(ptMsg + ': expecting *nums* labels, did not find any.');
204+
}
200205
}
201206
}
202207

test/jasmine/tests/funnel_test.js

Lines changed: 68 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1326,14 +1326,76 @@ describe('funnel hover', function() {
13261326
.then(done);
13271327
});
13281328

1329+
it('should turn off percentages with hoveinfo none or skip', function(done) {
1330+
gd = createGraphDiv();
1331+
1332+
var mock = Lib.extendDeep({}, require('@mocks/text_chart_arrays'));
1333+
mock.data.forEach(function(t, i) {
1334+
t.type = 'funnel';
1335+
t.orientation = 'v';
1336+
if(i === 0) {
1337+
t.hoverinfo = 'none';
1338+
} else {
1339+
t.hoverinfo = 'skip';
1340+
}
1341+
});
1342+
1343+
function _hover() {
1344+
var evt = { xpx: 125, ypx: 150 };
1345+
Fx.hover('graph', evt, 'xy');
1346+
}
1347+
1348+
Plotly.plot(gd, mock)
1349+
.then(_hover)
1350+
.then(function() {
1351+
assertHoverLabelContent({
1352+
isEmpty: true
1353+
});
1354+
})
1355+
.catch(failTest)
1356+
.then(done);
1357+
});
1358+
1359+
it('should turn on percentages with hoveinfo all', function(done) {
1360+
gd = createGraphDiv();
1361+
1362+
var mock = Lib.extendDeep({}, require('@mocks/text_chart_arrays'));
1363+
mock.data.forEach(function(t) {
1364+
t.type = 'funnel';
1365+
t.orientation = 'v';
1366+
t.hoverinfo = 'all';
1367+
});
1368+
1369+
function _hover() {
1370+
var evt = { xpx: 125, ypx: 150 };
1371+
Fx.hover('graph', evt, 'xy');
1372+
}
1373+
1374+
Plotly.plot(gd, mock)
1375+
.then(_hover)
1376+
.then(function() {
1377+
assertHoverLabelContent({
1378+
nums: [
1379+
'1\nHover text A\n100% of initial\n100% of previous\n33.3% of total',
1380+
'2\nHover text G\n100% of initial\n100% of previous\n33.3% of total',
1381+
'1.5\na (hover)\n100% of initial\n100% of previous\n33.3% of total'
1382+
],
1383+
name: ['Lines, Marke...', 'Lines and Text', 'missing text'],
1384+
axis: '0'
1385+
});
1386+
})
1387+
.catch(failTest)
1388+
.then(done);
1389+
});
1390+
13291391
it('should use hovertemplate if specified', function(done) {
13301392
gd = createGraphDiv();
13311393

13321394
var mock = Lib.extendDeep({}, require('@mocks/text_chart_arrays'));
13331395
mock.data.forEach(function(t) {
13341396
t.type = 'funnel';
13351397
t.orientation = 'v';
1336-
t.hovertemplate = '%{y}<extra></extra>';
1398+
t.hovertemplate = 'Value: %{y}<br>Total percentage: %{percentTotal}<br>Initial percentage: %{percentInitial}<br>Previous percentage: %{percentPrevious}<extra></extra>';
13371399
});
13381400

13391401
function _hover() {
@@ -1345,11 +1407,14 @@ describe('funnel hover', function() {
13451407
.then(_hover)
13461408
.then(function() {
13471409
assertHoverLabelContent({
1348-
nums: ['1', '2', '1.5'],
1410+
nums: [
1411+
'Value: 1\nTotal percentage: 33.3%\nInitial percentage: 100%\nPrevious percentage: 100%',
1412+
'Value: 2\nTotal percentage: 33.3%\nInitial percentage: 100%\nPrevious percentage: 100%',
1413+
'Value: 1.5\nTotal percentage: 33.3%\nInitial percentage: 100%\nPrevious percentage: 100%'
1414+
],
13491415
name: ['', '', ''],
13501416
axis: '0'
13511417
});
1352-
// return Plotly.restyle(gd, 'text', ['APPLE', 'BANANA', 'ORANGE']);
13531418
})
13541419
.catch(failTest)
13551420
.then(done);

0 commit comments

Comments
 (0)