Skip to content

Commit 568a376

Browse files
authored
Merge pull request #3366 from plotly/finance-hover-fixups
Finance trace hover fixups
2 parents 364cdec + bba9f3c commit 568a376

File tree

8 files changed

+112
-14
lines changed

8 files changed

+112
-14
lines changed

src/components/fx/hover.js

+8-6
Original file line numberDiff line numberDiff line change
@@ -329,12 +329,14 @@ function _hover(gd, evt, subplot, noHoverEvent) {
329329
// user specified an array of points to highlight
330330
hovermode = 'array';
331331
for(itemnum = 0; itemnum < evt.length; itemnum++) {
332-
cd = gd.calcdata[evt[itemnum].curveNumber||0];
333-
trace = cd[0].trace;
334-
if(cd[0].trace.hoverinfo !== 'skip') {
335-
searchData.push(cd);
336-
if(trace.orientation === 'h') {
337-
hasOneHorizontalTrace = true;
332+
cd = gd.calcdata[evt[itemnum].curveNumber || 0];
333+
if(cd) {
334+
trace = cd[0].trace;
335+
if(cd[0].trace.hoverinfo !== 'skip') {
336+
searchData.push(cd);
337+
if(trace.orientation === 'h') {
338+
hasOneHorizontalTrace = true;
339+
}
338340
}
339341
}
340342
}

src/traces/box/plot.js

+2
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,8 @@ function plotBoxAndWhiskers(sel, axes, trace, t) {
100100
paths.exit().remove();
101101

102102
paths.each(function(d) {
103+
if(d.empty) return 'M0,0Z';
104+
103105
var pos = d.pos;
104106
var posc = posAxis.c2p(pos + bPos, true) + bPosPxOffset;
105107
var pos0 = posAxis.c2p(pos + bPos - bdPos0, true) + bPosPxOffset;

src/traces/box/style.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,15 @@ function style(gd, cd) {
3232

3333
if(trace.type === 'candlestick') {
3434
allBoxes.each(function(boxData) {
35+
if(boxData.empty) return;
36+
3537
var thisBox = d3.select(this);
3638
var container = trace[boxData.dir]; // dir = 'increasing' or 'decreasing'
3739
styleBox(thisBox, container.line.width, container.line.color, container.fillcolor);
3840
// TODO: custom selection style for candlesticks
3941
thisBox.style('opacity', trace.selectedpoints && !boxData.selected ? 0.3 : 1);
4042
});
41-
}
42-
else {
43+
} else {
4344
styleBox(allBoxes, lineWidth, trace.line.color, trace.fillcolor);
4445
el.selectAll('path.mean')
4546
.style({

src/traces/ohlc/calc.js

+2
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ function calcCommon(gd, trace, x, ya, ptFunc) {
8989
if(hasTextArray) pt.tx = trace.text[i];
9090

9191
cd.push(pt);
92+
} else {
93+
cd.push({empty: true});
9294
}
9395
}
9496

src/traces/ohlc/hover.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,9 @@ function getClosestPoint(pointData, xval, yval, hovermode) {
6161
}
6262

6363
function dy(di) {
64-
return Fx.inbox(di[minAttr] - yval, di[maxAttr] - yval, hoverPseudoDistance);
64+
var min = di[minAttr];
65+
var max = di[maxAttr];
66+
return min === max || Fx.inbox(min - yval, max - yval, hoverPseudoDistance);
6567
}
6668

6769
function dxy(di) { return (dx(di) + dy(di)) / 2; }

src/traces/ohlc/plot.js

+2
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ module.exports = function plot(gd, plotinfo, cdOHLC, ohlcLayer) {
3737
paths.exit().remove();
3838

3939
paths.attr('d', function(d) {
40+
if(d.empty) return 'M0,0Z';
41+
4042
var x = xa.c2p(d.pos, true);
4143
var xo = xa.c2p(d.pos - tickLen, true);
4244
var xc = xa.c2p(d.pos + tickLen, true);

src/traces/ohlc/style.js

+2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ module.exports = function style(gd, cd) {
2323
var trace = d[0].trace;
2424

2525
d3.select(this).selectAll('path').each(function(di) {
26+
if(di.empty) return;
27+
2628
var dirLine = trace[di.dir].line;
2729
d3.select(this)
2830
.style('fill', 'none')

test/jasmine/tests/finance_test.js

+90-5
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ var d3 = require('d3');
66
var createGraphDiv = require('../assets/create_graph_div');
77
var destroyGraphDiv = require('../assets/destroy_graph_div');
88
var supplyAllDefaults = require('../assets/supply_defaults');
9+
var hover = require('../assets/hover');
10+
var assertHoverLabelContent = require('../assets/custom_assertions').assertHoverLabelContent;
911
var failTest = require('../assets/fail_test');
1012

1113
var mock0 = {
@@ -426,15 +428,22 @@ describe('finance charts calc', function() {
426428
addJunk(trace1);
427429

428430
var out = _calcRaw([trace0, trace1]);
429-
var indices = [0, 1, 2, 3, 4, 5, 6, 7];
431+
var indices = [0, 1, 2, 3, 4, 5, 6, 7, undefined, undefined, undefined, undefined];
430432
var i = 'increasing';
431433
var d = 'decreasing';
432-
var directions = [i, d, d, i, d, i, d, i];
434+
var directions = [i, d, d, i, d, i, d, i, undefined, undefined, undefined, undefined];
435+
var empties = [
436+
undefined, undefined, undefined, undefined,
437+
undefined, undefined, undefined, undefined,
438+
true, true, true, true
439+
];
433440

434441
expect(mapGet(out[0], 'pos')).toEqual(indices);
435442
expect(mapGet(out[0], 'dir')).toEqual(directions);
436443
expect(mapGet(out[1], 'pos')).toEqual(indices);
437444
expect(mapGet(out[1], 'dir')).toEqual(directions);
445+
expect(mapGet(out[0], 'empty')).toEqual(empties);
446+
expect(mapGet(out[1], 'empty')).toEqual(empties);
438447
});
439448

440449
it('should work with *filter* transforms', function() {
@@ -1104,8 +1113,8 @@ describe('finance trace hover:', function() {
11041113
margin: {t: 0, b: 0, l: 0, r: 0, pad: 0}
11051114
}, specs.layout || {});
11061115

1107-
var xval = 'xval' in specs ? specs.xvals : 0;
1108-
var yval = 'yval' in specs ? specs.yvals : 1;
1116+
var xval = 'xval' in specs ? specs.xval : 0;
1117+
var yval = 'yval' in specs ? specs.yval : 1;
11091118
var hovermode = layout.hovermode || 'x';
11101119

11111120
return Plotly.plot(gd, data, layout).then(function() {
@@ -1127,7 +1136,6 @@ describe('finance trace hover:', function() {
11271136
var actual = results[0];
11281137
var exp = specs.exp;
11291138

1130-
11311139
for(var k in exp) {
11321140
var msg = '- key ' + k;
11331141
expect(actual[k]).toBe(exp[k], msg);
@@ -1178,6 +1186,21 @@ describe('finance trace hover:', function() {
11781186
exp: {
11791187
extraText: 'A'
11801188
}
1189+
}, {
1190+
type: type,
1191+
desc: 'when high === low in *closest* mode',
1192+
traces: [{
1193+
high: [6, null, 7, 8],
1194+
close: [4, null, 7, 8],
1195+
low: [5, null, 7, 8],
1196+
open: [3, null, 7, 8]
1197+
}],
1198+
layout: {hovermode: 'closest'},
1199+
xval: 2,
1200+
yval: 6.9,
1201+
exp: {
1202+
extraText: 'open: 7<br>high: 7<br>low: 7<br>close: 7 ▲'
1203+
}
11811204
}]
11821205
.forEach(function(specs) {
11831206
it('should generate correct hover labels ' + type + ' - ' + specs.desc, function(done) {
@@ -1186,3 +1209,65 @@ describe('finance trace hover:', function() {
11861209
});
11871210
});
11881211
});
1212+
1213+
describe('finance trace hover via Fx.hover():', function() {
1214+
var gd;
1215+
1216+
beforeEach(function() { gd = createGraphDiv(); });
1217+
1218+
afterEach(destroyGraphDiv);
1219+
1220+
['candlestick', 'ohlc'].forEach(function(type) {
1221+
it('should pick correct ' + type + ' item', function(done) {
1222+
var x = ['hover ok!', 'time2', 'hover off by 1', 'time4'];
1223+
1224+
Plotly.newPlot(gd, [{
1225+
x: x,
1226+
high: [6, null, 7, 8],
1227+
close: [4, null, 7, 8],
1228+
low: [5, null, 7, 8],
1229+
open: [3, null, 7, 8],
1230+
type: type
1231+
}, {
1232+
x: x,
1233+
y: [1, null, 2, 3],
1234+
type: 'bar'
1235+
}], {
1236+
xaxis: { rangeslider: {visible: false} },
1237+
width: 500,
1238+
height: 500
1239+
})
1240+
.then(function() {
1241+
gd.on('plotly_hover', function(d) {
1242+
Plotly.Fx.hover(gd, [
1243+
{curveNumber: 0, pointNumber: d.points[0].pointNumber},
1244+
{curveNumber: 1, pointNumber: d.points[0].pointNumber}
1245+
]);
1246+
});
1247+
})
1248+
.then(function() { hover(281, 252); })
1249+
.then(function() {
1250+
assertHoverLabelContent({
1251+
nums: [
1252+
'hover off by 1\nopen: 7\nhigh: 7\nlow: 7\nclose: 7 ▲',
1253+
'(hover off by 1, 2)'
1254+
],
1255+
name: ['trace 0', 'trace 1']
1256+
}, 'hover over 3rd items (aka 2nd visible items)');
1257+
})
1258+
.then(function() {
1259+
Lib.clearThrottle();
1260+
return Plotly.react(gd, [gd.data[0]], gd.layout);
1261+
})
1262+
.then(function() { hover(281, 252); })
1263+
.then(function() {
1264+
assertHoverLabelContent({
1265+
nums: 'hover off by 1\nopen: 7\nhigh: 7\nlow: 7\nclose: 7 ▲',
1266+
name: ''
1267+
}, 'after removing 2nd trace');
1268+
})
1269+
.catch(failTest)
1270+
.then(done);
1271+
});
1272+
});
1273+
});

0 commit comments

Comments
 (0)