Skip to content

Commit b49e13f

Browse files
committed
add hovertemplate to gl3d traces
- i.e. scatter3d, surface, mesh3d, cone, streamtube and isosurface!
1 parent 02e51d8 commit b49e13f

19 files changed

+134
-46
lines changed

src/components/fx/hovertemplate_attributes.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ module.exports = function(opts, extra) {
3232
role: 'info',
3333
dflt: '',
3434
arrayOk: true,
35-
editType: 'none',
35+
editType: opts.editType || 'none',
3636
description: [
3737
'Template string used for rendering the information that appear on hover box.',
3838
'Note that this will override `hoverinfo`.',

src/plots/gl3d/scene.js

Lines changed: 58 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -71,18 +71,21 @@ function render(scene) {
7171
var pdata = project(scene.glplot.cameraParams, selection.dataCoordinate);
7272
trace = lastPicked.data;
7373
var ptNumber = selection.index;
74-
var hoverinfo = Fx.castHoverinfo(trace, scene.fullLayout, ptNumber);
75-
var hoverinfoParts = hoverinfo.split('+');
76-
var isHoverinfoAll = hoverinfo === 'all';
7774

78-
var xVal = formatter('xaxis', selection.traceCoordinate[0]);
79-
var yVal = formatter('yaxis', selection.traceCoordinate[1]);
80-
var zVal = formatter('zaxis', selection.traceCoordinate[2]);
75+
var labels = {
76+
xLabel: formatter('xaxis', selection.traceCoordinate[0]),
77+
yLabel: formatter('yaxis', selection.traceCoordinate[1]),
78+
zLabel: formatter('zaxis', selection.traceCoordinate[2])
79+
};
80+
81+
var hoverinfo = Fx.castHoverinfo(trace, scene.fullLayout, ptNumber);
82+
var hoverinfoParts = (hoverinfo || '').split('+');
83+
var isHoverinfoAll = hoverinfo && hoverinfo === 'all';
8184

82-
if(!isHoverinfoAll) {
83-
if(hoverinfoParts.indexOf('x') === -1) xVal = undefined;
84-
if(hoverinfoParts.indexOf('y') === -1) yVal = undefined;
85-
if(hoverinfoParts.indexOf('z') === -1) zVal = undefined;
85+
if(!trace.hovertemplate && !isHoverinfoAll) {
86+
if(hoverinfoParts.indexOf('x') === -1) labels.xLabel = undefined;
87+
if(hoverinfoParts.indexOf('y') === -1) labels.yLabel = undefined;
88+
if(hoverinfoParts.indexOf('z') === -1) labels.zLabel = undefined;
8689
if(hoverinfoParts.indexOf('text') === -1) selection.textLabel = undefined;
8790
if(hoverinfoParts.indexOf('name') === -1) lastPicked.name = undefined;
8891
}
@@ -91,27 +94,38 @@ function render(scene) {
9194
var vectorTx = [];
9295

9396
if(trace.type === 'cone' || trace.type === 'streamtube') {
97+
labels.uLabel = formatter('xaxis', selection.traceCoordinate[3]);
9498
if(isHoverinfoAll || hoverinfoParts.indexOf('u') !== -1) {
95-
vectorTx.push('u: ' + formatter('xaxis', selection.traceCoordinate[3]));
99+
vectorTx.push('u: ' + labels.uLabel);
96100
}
101+
102+
labels.vLabel = formatter('yaxis', selection.traceCoordinate[4]);
97103
if(isHoverinfoAll || hoverinfoParts.indexOf('v') !== -1) {
98-
vectorTx.push('v: ' + formatter('yaxis', selection.traceCoordinate[4]));
104+
vectorTx.push('v: ' + labels.vLabel);
99105
}
106+
107+
labels.wLabel = formatter('zaxis', selection.traceCoordinate[5]);
100108
if(isHoverinfoAll || hoverinfoParts.indexOf('w') !== -1) {
101-
vectorTx.push('w: ' + formatter('zaxis', selection.traceCoordinate[5]));
109+
vectorTx.push('w: ' + labels.wLabel);
102110
}
111+
112+
labels.normLabel = selection.traceCoordinate[6].toPrecision(3);
103113
if(isHoverinfoAll || hoverinfoParts.indexOf('norm') !== -1) {
104-
vectorTx.push('norm: ' + selection.traceCoordinate[6].toPrecision(3));
114+
vectorTx.push('norm: ' + labels.normLabel);
105115
}
106-
if(trace.type === 'streamtube' && (isHoverinfoAll || hoverinfoParts.indexOf('divergence') !== -1)) {
107-
vectorTx.push('divergence: ' + selection.traceCoordinate[7].toPrecision(3));
116+
if(trace.type === 'streamtube') {
117+
labels.divergenceLabel = selection.traceCoordinate[7].toPrecision(3);
118+
if(isHoverinfoAll || hoverinfoParts.indexOf('divergence') !== -1) {
119+
vectorTx.push('divergence: ' + labels.divergenceLabel);
120+
}
108121
}
109122
if(selection.textLabel) {
110123
vectorTx.push(selection.textLabel);
111124
}
112125
tx = vectorTx.join('<br>');
113126
} else if(trace.type === 'isosurface') {
114-
vectorTx.push('value: ' + Axes.tickText(scene.mockAxis, scene.mockAxis.d2l(selection.traceCoordinate[3]), 'hover').text);
127+
labels.valueLabel = Axes.tickText(scene.mockAxis, scene.mockAxis.d2l(selection.traceCoordinate[3]), 'hover').text;
128+
vectorTx.push('value: ' + labels.valueLabel);
115129
if(selection.textLabel) {
116130
vectorTx.push(selection.textLabel);
117131
}
@@ -120,27 +134,6 @@ function render(scene) {
120134
tx = selection.textLabel;
121135
}
122136

123-
if(scene.fullSceneLayout.hovermode) {
124-
Fx.loneHover({
125-
x: (0.5 + 0.5 * pdata[0] / pdata[3]) * width,
126-
y: (0.5 - 0.5 * pdata[1] / pdata[3]) * height,
127-
xLabel: xVal,
128-
yLabel: yVal,
129-
zLabel: zVal,
130-
text: tx,
131-
name: lastPicked.name,
132-
color: Fx.castHoverOption(trace, ptNumber, 'bgcolor') || lastPicked.color,
133-
borderColor: Fx.castHoverOption(trace, ptNumber, 'bordercolor'),
134-
fontFamily: Fx.castHoverOption(trace, ptNumber, 'font.family'),
135-
fontSize: Fx.castHoverOption(trace, ptNumber, 'font.size'),
136-
fontColor: Fx.castHoverOption(trace, ptNumber, 'font.color')
137-
}, {
138-
container: svgContainer,
139-
gd: scene.graphDiv
140-
});
141-
}
142-
143-
// TODO not sure if streamtube x/y/z should be emitted as x/y/z
144137
var pointData = {
145138
x: selection.traceCoordinate[0],
146139
y: selection.traceCoordinate[1],
@@ -151,18 +144,41 @@ function render(scene) {
151144
pointNumber: ptNumber
152145
};
153146

147+
Fx.appendArrayPointValue(pointData, trace, ptNumber);
148+
154149
if(trace._module.eventData) {
155150
pointData = trace._module.eventData(pointData, selection, trace, {}, ptNumber);
156151
}
157152

158-
Fx.appendArrayPointValue(pointData, trace, ptNumber);
159-
160153
var eventData = {points: [pointData]};
161154

155+
if(scene.fullSceneLayout.hovermode) {
156+
Fx.loneHover({
157+
trace: trace,
158+
x: (0.5 + 0.5 * pdata[0] / pdata[3]) * width,
159+
y: (0.5 - 0.5 * pdata[1] / pdata[3]) * height,
160+
xLabel: labels.xLabel,
161+
yLabel: labels.yLabel,
162+
zLabel: labels.zLabel,
163+
text: tx,
164+
name: lastPicked.name,
165+
color: Fx.castHoverOption(trace, ptNumber, 'bgcolor') || lastPicked.color,
166+
borderColor: Fx.castHoverOption(trace, ptNumber, 'bordercolor'),
167+
fontFamily: Fx.castHoverOption(trace, ptNumber, 'font.family'),
168+
fontSize: Fx.castHoverOption(trace, ptNumber, 'font.size'),
169+
fontColor: Fx.castHoverOption(trace, ptNumber, 'font.color'),
170+
hovertemplate: Lib.castOption(trace, ptNumber, 'hovertemplate'),
171+
hovertemplateLabels: Lib.extendFlat({}, pointData, labels),
172+
eventData: [pointData]
173+
}, {
174+
container: svgContainer,
175+
gd: scene.graphDiv
176+
});
177+
}
178+
162179
if(selection.buttons && selection.distance < 5) {
163180
scene.graphDiv.emit('plotly_click', eventData);
164-
}
165-
else {
181+
} else {
166182
scene.graphDiv.emit('plotly_hover', eventData);
167183
}
168184

src/traces/cone/attributes.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
var colorscaleAttrs = require('../../components/colorscale/attributes');
1212
var colorbarAttrs = require('../../components/colorbar/attributes');
13+
var hovertemplateAttrs = require('../../components/fx/hovertemplate_attributes');
1314
var mesh3dAttrs = require('../mesh3d/attributes');
1415
var baseAttrs = require('../../plots/attributes');
1516

@@ -157,7 +158,8 @@ var attrs = {
157158
'If trace `hoverinfo` contains a *text* flag and *hovertext* is not set,',
158159
'these elements will be seen in the hover labels.'
159160
].join(' ')
160-
}
161+
},
162+
hovertemplate: hovertemplateAttrs({editType: 'calc'}, {keys: ['norm']})
161163
};
162164

163165
extendFlat(attrs, colorscaleAttrs('', {

src/traces/cone/defaults.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
5252
colorscaleDefaults(traceIn, traceOut, layout, coerce, {prefix: '', cLetter: 'c'});
5353

5454
coerce('text');
55+
coerce('hovertemplate');
5556

5657
// disable 1D transforms (for now)
5758
traceOut._length = null;

src/traces/cone/index.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ module.exports = {
2222
},
2323
calc: require('./calc'),
2424
plot: require('./convert'),
25+
eventData: function(out, pt) {
26+
out.norm = pt.traceCoordinate[6];
27+
return out;
28+
},
2529

2630
meta: {
2731
description: [

src/traces/isosurface/attributes.js

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

1111
var colorscaleAttrs = require('../../components/colorscale/attributes');
1212
var colorbarAttrs = require('../../components/colorbar/attributes');
13+
var hovertemplateAttrs = require('../../components/fx/hovertemplate_attributes');
1314
var surfaceAtts = require('../surface/attributes');
1415
var meshAttrs = require('../mesh3d/attributes');
1516
var baseAttrs = require('../../plots/attributes');
@@ -226,6 +227,7 @@ var attrs = module.exports = overrideAll(extendFlat({
226227
'these elements will be seen in the hover labels.'
227228
].join(' ')
228229
},
230+
hovertemplate: hovertemplateAttrs()
229231
},
230232

231233
colorscaleAttrs('', {

src/traces/isosurface/defaults.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
* LICENSE file in the root directory of this source tree.
77
*/
88

9-
109
'use strict';
1110

1211
var Registry = require('../../registry');
@@ -85,6 +84,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
8584
// Coerce remaining properties
8685
[
8786
'text',
87+
'hovertemplate',
8888
'lighting.ambient',
8989
'lighting.diffuse',
9090
'lighting.specular',

src/traces/mesh3d/attributes.js

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

1111
var colorscaleAttrs = require('../../components/colorscale/attributes');
1212
var colorbarAttrs = require('../../components/colorbar/attributes');
13+
var hovertemplateAttrs = require('../../components/fx/hovertemplate_attributes');
1314
var surfaceAtts = require('../surface/attributes');
1415
var baseAttrs = require('../../plots/attributes');
1516

@@ -89,6 +90,7 @@ module.exports = extendFlat({
8990
'these elements will be seen in the hover labels.'
9091
].join(' ')
9192
},
93+
hovertemplate: hovertemplateAttrs({editType: 'calc'}),
9294

9395
delaunayaxis: {
9496
valType: 'enumerated',

src/traces/mesh3d/defaults.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
8888
}
8989

9090
coerce('text');
91+
coerce('hovertemplate');
9192

9293
// disable 1D transforms
9394
// x/y/z should match lengths, and i/j/k should match as well, but

src/traces/scatter3d/attributes.js

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

1111
var scatterAttrs = require('../scatter/attributes');
1212
var colorAttributes = require('../../components/colorscale/attributes');
13+
var hovertemplateAttrs = require('../../components/fx/hovertemplate_attributes');
1314
var baseAttrs = require('../../plots/attributes');
1415
var DASHES = require('../../constants/gl3d_dashes');
1516

@@ -94,6 +95,7 @@ var attrs = module.exports = overrideAll({
9495
'To be seen, trace `hoverinfo` must contain a *text* flag.'
9596
].join(' ')
9697
}),
98+
hovertemplate: hovertemplateAttrs(),
9799

98100
mode: extendFlat({}, scatterAttrs.mode, // shouldn't this be on-par with 2D?
99101
{dflt: 'lines+markers'}),

src/traces/scatter3d/defaults.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
3333

3434
coerce('text');
3535
coerce('hovertext');
36+
coerce('hovertemplate');
3637
coerce('mode');
3738

3839
if(subTypes.hasLines(traceOut)) {

src/traces/streamtube/attributes.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
var colorscaleAttrs = require('../../components/colorscale/attributes');
1212
var colorbarAttrs = require('../../components/colorbar/attributes');
13+
var hovertemplateAttrs = require('../../components/fx/hovertemplate_attributes');
1314
var mesh3dAttrs = require('../mesh3d/attributes');
1415
var baseAttrs = require('../../plots/attributes');
1516

@@ -130,7 +131,14 @@ var attrs = {
130131
'this text element will be seen in all hover labels.',
131132
'Note that streamtube traces do not support array `text` values.'
132133
].join(' ')
133-
}
134+
},
135+
hovertemplate: hovertemplateAttrs({editType: 'calc'}, {
136+
keys: [
137+
'tubex', 'tubey', 'tubez',
138+
'tubeu', 'tubev', 'tubew',
139+
'norm', 'divergence'
140+
]
141+
})
134142
};
135143

136144
extendFlat(attrs, colorscaleAttrs('', {

src/traces/streamtube/defaults.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
5353
colorscaleDefaults(traceIn, traceOut, layout, coerce, {prefix: '', cLetter: 'c'});
5454

5555
coerce('text');
56+
coerce('hovertemplate');
5657

5758
// disable 1D transforms (for now)
5859
// x/y/z and u/v/w have matching lengths,

src/traces/surface/attributes.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
var Color = require('../../components/color');
1212
var colorscaleAttrs = require('../../components/colorscale/attributes');
1313
var colorbarAttrs = require('../../components/colorbar/attributes');
14+
var hovertemplateAttrs = require('../../components/fx/hovertemplate_attributes');
1415
var baseAttrs = require('../../plots/attributes');
1516

1617
var extendFlat = require('../../lib/extend').extendFlat;
@@ -123,6 +124,7 @@ var attrs = module.exports = overrideAll(extendFlat({
123124
'these elements will be seen in the hover labels.'
124125
].join(' ')
125126
},
127+
hovertemplate: hovertemplateAttrs(),
126128

127129
surfacecolor: {
128130
valType: 'data_array',

src/traces/surface/defaults.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
4242
handleCalendarDefaults(traceIn, traceOut, ['x', 'y', 'z'], layout);
4343

4444
coerce('text');
45+
coerce('hovertemplate');
4546

4647
// Coerce remaining properties
4748
[

test/jasmine/tests/cone_test.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,16 @@ describe('Test cone interactions', function() {
297297
'norm: 3.00'
298298
].join('\n')
299299
});
300+
301+
return Plotly.restyle(gd, 'hovertemplate', 'NORM : %{norm}<br>at %{x},%{y},%{z}<extra>LOOKOUT</extra>');
302+
})
303+
.then(delay(20))
304+
.then(_hover)
305+
.then(function() {
306+
assertHoverLabelContent({
307+
name: 'LOOKOUT',
308+
nums: 'NORM : 3.00\nat 2,2,2'
309+
});
300310
})
301311
.catch(failTest)
302312
.then(done);

test/jasmine/tests/gl3d_plot_interact_test.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,11 @@ describe('Test gl3d plots', function() {
242242
})
243243
.then(function() {
244244
assertHoverText(null, null, '100k');
245+
246+
return Plotly.restyle(gd, 'hovertemplate', 'THIS Y -- %{y}<extra></extra>');
247+
})
248+
.then(function() {
249+
assertHoverText(null, null, null, 'THIS Y -- c');
245250
})
246251
.catch(failTest)
247252
.then(done);
@@ -339,6 +344,11 @@ describe('Test gl3d plots', function() {
339344
})
340345
.then(function() {
341346
assertHoverText(null, null, null, 'yo!');
347+
348+
return Plotly.restyle(gd, 'hovertemplate', '!!! %{z} !!!<extra></extra>');
349+
})
350+
.then(function() {
351+
assertHoverText(null, null, null, '!!! 43 !!!');
342352
})
343353
.then(done);
344354
});
@@ -418,6 +428,12 @@ describe('Test gl3d plots', function() {
418428
.then(function() {
419429
assertHoverText(null, null, null, 'yo!');
420430
})
431+
.then(function() {
432+
return Plotly.restyle(gd, 'hovertemplate', '%{x}-%{y}-%{z}<extra></extra>');
433+
})
434+
.then(function() {
435+
assertHoverText(null, null, null, '3-4-5');
436+
})
421437
.catch(failTest)
422438
.then(done);
423439
});

0 commit comments

Comments
 (0)