Skip to content

Commit d25b7eb

Browse files
committed
add hovertemplate and line.hovertemplate to parcats traces
1 parent eac9b3b commit d25b7eb

File tree

5 files changed

+181
-29
lines changed

5 files changed

+181
-29
lines changed

src/components/fx/hovertemplate_attributes.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ module.exports = function(opts, extra) {
3131
valType: 'string',
3232
role: 'info',
3333
dflt: '',
34-
arrayOk: true,
3534
editType: opts.editType || 'none',
3635
description: [
3736
'Template string used for rendering the information that appear on hover box.',
@@ -46,5 +45,9 @@ module.exports = function(opts, extra) {
4645
].join(' ')
4746
};
4847

48+
if(opts.arrayOk !== false) {
49+
hovertemplate.arrayOk = true;
50+
}
51+
4952
return hovertemplate;
5053
};

src/traces/parcats/attributes.js

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ var extendFlat = require('../../lib/extend').extendFlat;
1212
var plotAttrs = require('../../plots/attributes');
1313
var fontAttrs = require('../../plots/font_attributes');
1414
var colorAttributes = require('../../components/colorscale/attributes');
15+
var hovertemplateAttrs = require('../../components/fx/hovertemplate_attributes');
1516
var domainAttrs = require('../../plots/domain').attributes;
1617
var scatterAttrs = require('../scatter/attributes');
1718
var scatterLineAttrs = scatterAttrs.line;
@@ -34,16 +35,26 @@ var line = extendFlat({
3435
'If `linear`, paths are composed of straight lines.',
3536
'If `hspline`, paths are composed of horizontal curved splines'
3637
].join(' ')
37-
}
38+
},
39+
40+
hovertemplate: hovertemplateAttrs({
41+
editType: 'plot',
42+
arrayOk: false
43+
}, {
44+
keys: ['count', 'probability'],
45+
description: [
46+
'This value here applies when hovering over lines.'
47+
].join(' ')
48+
})
3849
});
3950

4051
module.exports = {
4152
domain: domainAttrs({name: 'parcats', trace: true, editType: 'calc'}),
53+
4254
hoverinfo: extendFlat({}, plotAttrs.hoverinfo, {
4355
flags: ['count', 'probability'],
4456
editType: 'plot',
4557
arrayOk: false
46-
// plotAttrs.hoverinfo description is appropriate
4758
}),
4859
hoveron: {
4960
valType: 'enumerated',
@@ -58,6 +69,21 @@ module.exports = {
5869
'If `dimension`, hover interactions take place across all categories per dimension.'
5970
].join(' ')
6071
},
72+
hovertemplate: hovertemplateAttrs({
73+
editType: 'plot',
74+
arrayOk: false
75+
}, {
76+
keys: [
77+
'count', 'probability', 'category',
78+
'categorycount', 'colorcount', 'bandcolorcount'
79+
],
80+
description: [
81+
'This value here applies when hovering over dimensions.',
82+
'Note tath `*categorycount`, *colorcount* and *bandcolorcount*',
83+
'are only available when `hoveron` contains the *color* flag'
84+
].join(' ')
85+
}),
86+
6187
arrangement: {
6288
valType: 'enumerated',
6389
values: ['perpendicular', 'freeform', 'fixed'],

src/traces/parcats/defaults.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,9 @@ var attributes = require('./attributes');
1818
var mergeLength = require('../parcoords/merge_length');
1919

2020
function handleLineDefaults(traceIn, traceOut, defaultColor, layout, coerce) {
21-
2221
coerce('line.shape');
22+
coerce('line.hovertemplate');
23+
2324
var lineColor = coerce('line.color', layout.colorway[0]);
2425
if(hasColorscale(traceIn, 'line') && Lib.isArrayOrTypedArray(lineColor)) {
2526
if(lineColor.length) {
@@ -96,6 +97,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
9697
mergeLength(traceOut, dimensions, 'values', len);
9798

9899
coerce('hoveron');
100+
coerce('hovertemplate');
99101
coerce('arrangement');
100102
coerce('bundlecolors');
101103
coerce('sortpaths');

src/traces/parcats/parcats.js

Lines changed: 74 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,7 @@ function mouseoverPath(d) {
413413

414414
// Label
415415
var gd = d.parcatsViewModel.graphDiv;
416+
var trace = d.parcatsViewModel.trace;
416417
var fullLayout = gd._fullLayout;
417418
var rootBBox = fullLayout._paperdiv.node().getBoundingClientRect();
418419
var graphDivBBox = d.parcatsViewModel.graphDiv.getBoundingClientRect();
@@ -438,19 +439,27 @@ function mouseoverPath(d) {
438439

439440
var textColor = tinycolor.mostReadable(d.model.color, ['black', 'white']);
440441

442+
var count = d.model.count;
443+
var prob = count / d.parcatsViewModel.model.count;
444+
var labels = {
445+
countLabel: count,
446+
probabilityLabel: prob.toFixed(3)
447+
};
448+
441449
// Build hover text
442450
var hovertextParts = [];
443451
if(d.parcatsViewModel.hoverinfoItems.indexOf('count') !== -1) {
444-
hovertextParts.push(['Count:', d.model.count].join(' '));
452+
hovertextParts.push(['Count:', labels.countLabel].join(' '));
445453
}
446454
if(d.parcatsViewModel.hoverinfoItems.indexOf('probability') !== -1) {
447-
hovertextParts.push(['P:', (d.model.count / d.parcatsViewModel.model.count).toFixed(3)].join(' '));
455+
hovertextParts.push(['P:', labels.probabilityLabel].join(' '));
448456
}
449457

450458
var hovertext = hovertextParts.join('<br>');
451459
var mouseX = d3.mouse(gd)[0];
452460

453461
Fx.loneHover({
462+
trace: trace,
454463
x: hoverCenterX - rootBBox.left + graphDivBBox.left,
455464
y: hoverCenterY - rootBBox.top + graphDivBBox.top,
456465
text: hovertext,
@@ -459,7 +468,15 @@ function mouseoverPath(d) {
459468
fontFamily: 'Monaco, "Courier New", monospace',
460469
fontSize: 10,
461470
fontColor: textColor,
462-
idealAlign: mouseX < hoverCenterX ? 'right' : 'left'
471+
idealAlign: mouseX < hoverCenterX ? 'right' : 'left',
472+
hovertemplate: (trace.line || {}).hovertemplate,
473+
hovertemplateLabels: labels,
474+
eventData: [{
475+
data: trace._input,
476+
fullData: trace,
477+
count: count,
478+
probability: prob
479+
}]
463480
}, {
464481
container: fullLayout._hoverlayer.node(),
465482
outerContainer: fullLayout._paper.node(),
@@ -715,6 +732,7 @@ function createHoverLabelForCategoryHovermode(rootBBox, bandElement) {
715732
var catViewModel = rectSelection.datum();
716733
var parcatsViewModel = catViewModel.parcatsViewModel;
717734
var dimensionModel = parcatsViewModel.model.dimensions[catViewModel.model.dimensionInd];
735+
var trace = parcatsViewModel.trace;
718736

719737
// Positions
720738
var hoverCenterY = rectBoundingBox.top + rectBoundingBox.height / 2;
@@ -732,19 +750,27 @@ function createHoverLabelForCategoryHovermode(rootBBox, bandElement) {
732750
hoverLabelIdealAlign = 'right';
733751
}
734752

753+
var count = catViewModel.model.count;
754+
var catLabel = catViewModel.model.categoryLabel;
755+
var prob = count / catViewModel.parcatsViewModel.model.count;
756+
var labels = {
757+
countLabel: count,
758+
categoryLabel: catLabel,
759+
probabilityLabel: prob.toFixed(3)
760+
};
761+
735762
// Hover label text
736763
var hoverinfoParts = [];
737764
if(catViewModel.parcatsViewModel.hoverinfoItems.indexOf('count') !== -1) {
738-
hoverinfoParts.push(['Count:', catViewModel.model.count].join(' '));
765+
hoverinfoParts.push(['Count:', labels.countLabel].join(' '));
739766
}
740767
if(catViewModel.parcatsViewModel.hoverinfoItems.indexOf('probability') !== -1) {
741-
hoverinfoParts.push([
742-
'P(' + catViewModel.model.categoryLabel + '):',
743-
(catViewModel.model.count / catViewModel.parcatsViewModel.model.count).toFixed(3)].join(' '));
768+
hoverinfoParts.push(['P(' + labels.categoryLabel + '):', labels.probabilityLabel].join(' '));
744769
}
745770

746771
var hovertext = hoverinfoParts.join('<br>');
747772
return {
773+
trace: trace,
748774
x: hoverCenterX - rootBBox.left,
749775
y: hoverCenterY - rootBBox.top,
750776
text: hovertext,
@@ -753,7 +779,16 @@ function createHoverLabelForCategoryHovermode(rootBBox, bandElement) {
753779
fontFamily: 'Monaco, "Courier New", monospace',
754780
fontSize: 12,
755781
fontColor: 'black',
756-
idealAlign: hoverLabelIdealAlign
782+
idealAlign: hoverLabelIdealAlign,
783+
hovertemplate: trace.hovertemplate,
784+
hovertemplateLabels: labels,
785+
eventData: [{
786+
data: trace._input,
787+
fullData: trace,
788+
count: count,
789+
category: catLabel,
790+
probability: prob
791+
}]
757792
};
758793
}
759794

@@ -800,6 +835,7 @@ function createHoverLabelForColorHovermode(rootBBox, bandElement) {
800835
var catViewModel = bandViewModel.categoryViewModel;
801836
var parcatsViewModel = catViewModel.parcatsViewModel;
802837
var dimensionModel = parcatsViewModel.model.dimensions[catViewModel.model.dimensionInd];
838+
var trace = parcatsViewModel.trace;
803839

804840
// positions
805841
var hoverCenterY = bandBoundingBox.y + bandBoundingBox.height / 2;
@@ -840,26 +876,25 @@ function createHoverLabelForColorHovermode(rootBBox, bandElement) {
840876
}
841877
});
842878

879+
var pColorAndCat = bandColorCount / totalCount;
880+
var pCatGivenColor = bandColorCount / colorCount;
881+
var pColorGivenCat = bandColorCount / catCount;
882+
883+
var labels = {
884+
countLabel: totalCount,
885+
categoryLabel: catLabel,
886+
probabilityLabel: pColorAndCat.toFixed(3)
887+
};
888+
843889
// Hover label text
844890
var hoverinfoParts = [];
845891
if(catViewModel.parcatsViewModel.hoverinfoItems.indexOf('count') !== -1) {
846-
hoverinfoParts.push(['Count:', bandColorCount].join(' '));
892+
hoverinfoParts.push(['Count:', labels.countLabel].join(' '));
847893
}
848894
if(catViewModel.parcatsViewModel.hoverinfoItems.indexOf('probability') !== -1) {
849-
var pColorAndCatLable = 'P(color ∩ ' + catLabel + '): ';
850-
var pColorAndCatValue = (bandColorCount / totalCount).toFixed(3);
851-
var pColorAndCatRow = pColorAndCatLable + pColorAndCatValue;
852-
hoverinfoParts.push(pColorAndCatRow);
853-
854-
var pCatGivenColorLabel = 'P(' + catLabel + ' | color): ';
855-
var pCatGivenColorValue = (bandColorCount / colorCount).toFixed(3);
856-
var pCatGivenColorRow = pCatGivenColorLabel + pCatGivenColorValue;
857-
hoverinfoParts.push(pCatGivenColorRow);
858-
859-
var pColorGivenCatLabel = 'P(color | ' + catLabel + '): ';
860-
var pColorGivenCatValue = (bandColorCount / catCount).toFixed(3);
861-
var pColorGivenCatRow = pColorGivenCatLabel + pColorGivenCatValue;
862-
hoverinfoParts.push(pColorGivenCatRow);
895+
hoverinfoParts.push('P(color ∩ ' + catLabel + '): ' + labels.probabilityLabel);
896+
hoverinfoParts.push('P(' + catLabel + ' | color): ' + pCatGivenColor.toFixed(3));
897+
hoverinfoParts.push('P(color | ' + catLabel + '): ' + pColorGivenCat.toFixed(3));
863898
}
864899

865900
var hovertext = hoverinfoParts.join('<br>');
@@ -868,6 +903,7 @@ function createHoverLabelForColorHovermode(rootBBox, bandElement) {
868903
var textColor = tinycolor.mostReadable(bandViewModel.color, ['black', 'white']);
869904

870905
return {
906+
trace: trace,
871907
x: hoverCenterX - rootBBox.left,
872908
y: hoverCenterY - rootBBox.top,
873909
// name: 'NAME',
@@ -877,7 +913,19 @@ function createHoverLabelForColorHovermode(rootBBox, bandElement) {
877913
fontFamily: 'Monaco, "Courier New", monospace',
878914
fontColor: textColor,
879915
fontSize: 10,
880-
idealAlign: hoverLabelIdealAlign
916+
idealAlign: hoverLabelIdealAlign,
917+
hovertemplate: trace.hovertemplate,
918+
hovertemplateLabels: labels,
919+
eventData: [{
920+
data: trace._input,
921+
fullData: trace,
922+
category: catLabel,
923+
count: totalCount,
924+
probability: pColorAndCat,
925+
categorycount: catCount,
926+
colorcount: colorCount,
927+
bandcolorcount: bandColorCount
928+
}]
881929
};
882930
}
883931

@@ -1475,12 +1523,13 @@ function createParcatsViewModel(graphDiv, layout, wrappedParcatsModel) {
14751523
if(trace.hoverinfo === 'all') {
14761524
hoverinfoItems = ['count', 'probability'];
14771525
} else {
1478-
hoverinfoItems = trace.hoverinfo.split('+');
1526+
hoverinfoItems = (trace.hoverinfo || '').split('+');
14791527
}
14801528

14811529
// Construct parcatsViewModel
14821530
// --------------------------
14831531
var parcatsViewModel = {
1532+
trace: trace,
14841533
key: trace.uid,
14851534
model: parcatsModel,
14861535
x: traceX,

test/jasmine/tests/parcats_test.js

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
var Plotly = require('@lib/index');
22
var Lib = require('@src/lib');
3+
34
var d3 = require('d3');
45
var createGraphDiv = require('../assets/create_graph_div');
56
var destroyGraphDiv = require('../assets/destroy_graph_div');
@@ -8,6 +9,9 @@ var mouseEvent = require('../assets/mouse_event');
89
var click = require('../assets/click');
910
var delay = require('../assets/delay');
1011

12+
var customAssertions = require('../assets/custom_assertions');
13+
var assertHoverLabelContent = customAssertions.assertHoverLabelContent;
14+
1115
var CALLBACK_DELAY = 500;
1216

1317
// Testing constants
@@ -1726,3 +1730,71 @@ describe('Hover events with hoveron color', function() {
17261730
.then(done);
17271731
});
17281732
});
1733+
1734+
describe('Parcats hover:', function() {
1735+
var gd;
1736+
1737+
afterEach(destroyGraphDiv);
1738+
1739+
function run(s, done) {
1740+
gd = createGraphDiv();
1741+
1742+
var fig = Lib.extendDeep({},
1743+
s.mock || require('@mocks/parcats_basic.json')
1744+
);
1745+
if(s.patch) fig = s.patch(fig);
1746+
1747+
return Plotly.plot(gd, fig).then(function() {
1748+
mouseEvent('mousemove', s.pos[0], s.pos[1]);
1749+
mouseEvent('mouseover', s.pos[0], s.pos[1]);
1750+
1751+
setTimeout(function() {
1752+
assertHoverLabelContent(s);
1753+
done();
1754+
}, CALLBACK_DELAY);
1755+
1756+
})
1757+
.catch(failTest);
1758+
}
1759+
1760+
var dimPos = [320, 310];
1761+
var linePos = [272, 415];
1762+
1763+
var specs = [{
1764+
desc: 'basic - on dimension',
1765+
pos: dimPos,
1766+
nums: 'Count: 9',
1767+
name: ''
1768+
}, {
1769+
desc: 'basic - on line',
1770+
pos: linePos,
1771+
nums: 'Count: 1',
1772+
name: ''
1773+
}, {
1774+
desc: 'with hovetemplate - on dimension',
1775+
pos: dimPos,
1776+
patch: function(fig) {
1777+
fig.data[0].hovertemplate = 'probz=%{probability:.1f}<extra>LOOK</extra>';
1778+
return fig;
1779+
},
1780+
nums: 'probz=1.0',
1781+
name: 'LOOK'
1782+
}, {
1783+
desc: 'with hovertemplate - on line',
1784+
pos: linePos,
1785+
patch: function(fig) {
1786+
fig.data[0].line = {
1787+
hovertemplate: 'P=%{probability}<extra>with count=%{count}</extra>'
1788+
};
1789+
return fig;
1790+
},
1791+
nums: 'P=0.111',
1792+
name: 'with count=1'
1793+
}];
1794+
1795+
specs.forEach(function(s) {
1796+
it('should generate correct hover labels ' + s.desc, function(done) {
1797+
run(s, done);
1798+
});
1799+
});
1800+
});

0 commit comments

Comments
 (0)