Skip to content

Commit 43e8d30

Browse files
committed
hoveron=fills initial commit
1 parent 41b1fc4 commit 43e8d30

File tree

4 files changed

+133
-48
lines changed

4 files changed

+133
-48
lines changed

src/traces/scatter/attributes.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,17 @@ module.exports = {
9292
'then the default is *lines+markers*. Otherwise, *lines*.'
9393
].join(' ')
9494
},
95+
hoveron: {
96+
valType: 'enumerated',
97+
values: ['points', 'fills'],
98+
role: 'info',
99+
description: [
100+
'Do the hover effects highlight individual points (markers or',
101+
'line points) or do they highlight filled regions?',
102+
'If the fill is *toself* or *tonext* and there are no markers',
103+
'or text, then the default is *fills*, otherwise it is *points*.'
104+
].join(' ')
105+
},
95106
line: {
96107
color: {
97108
valType: 'color',

src/traces/scatter/defaults.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,11 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
5353
handleTextDefaults(traceIn, traceOut, layout, coerce);
5454
}
5555

56+
var dfltHoverOn = '';
57+
5658
if(subTypes.hasMarkers(traceOut) || subTypes.hasText(traceOut)) {
5759
coerce('marker.maxdisplayed');
60+
dfltHoverOn = 'points';
5861
}
5962

6063
coerce('fill');
@@ -63,6 +66,11 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
6366
if(!subTypes.hasLines(traceOut)) handleLineShapeDefaults(traceIn, traceOut, coerce);
6467
}
6568

69+
if(!dfltHoverOn && (traceOut.fill === 'tonext' || traceOut.fill === 'toself')) {
70+
dfltHoverOn = 'fills';
71+
}
72+
coerce('hoveron', dfltHoverOn || 'points');
73+
6674
errorBarsSupplyDefaults(traceIn, traceOut, defaultColor, {axis: 'y'});
6775
errorBarsSupplyDefaults(traceIn, traceOut, defaultColor, {axis: 'x', inherit: 'y'});
6876
};

src/traces/scatter/hover.js

Lines changed: 101 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -9,61 +9,115 @@
99

1010
'use strict';
1111

12+
var Lib = require('../../lib');
1213
var Fx = require('../../plots/cartesian/graph_interact');
1314
var ErrorBars = require('../../components/errorbars');
1415
var getTraceColor = require('./get_trace_color');
16+
var Color = require('../../components/color');
1517

1618

1719
module.exports = function hoverPoints(pointData, xval, yval, hovermode) {
1820
var cd = pointData.cd,
1921
trace = cd[0].trace,
2022
xa = pointData.xa,
2123
ya = pointData.ya,
22-
dx = function(di) {
23-
// scatter points: d.mrc is the calculated marker radius
24-
// adjust the distance so if you're inside the marker it
25-
// always will show up regardless of point size, but
26-
// prioritize smaller points
27-
var rad = Math.max(3, di.mrc || 0);
28-
return Math.max(Math.abs(xa.c2p(di.x) - xa.c2p(xval)) - rad, 1 - 3 / rad);
29-
},
30-
dy = function(di) {
31-
var rad = Math.max(3, di.mrc || 0);
32-
return Math.max(Math.abs(ya.c2p(di.y) - ya.c2p(yval)) - rad, 1 - 3 / rad);
33-
},
34-
dxy = function(di) {
35-
var rad = Math.max(3, di.mrc || 0),
36-
dx = Math.abs(xa.c2p(di.x) - xa.c2p(xval)),
37-
dy = Math.abs(ya.c2p(di.y) - ya.c2p(yval));
38-
return Math.max(Math.sqrt(dx * dx + dy * dy) - rad, 1 - 3 / rad);
39-
},
40-
distfn = Fx.getDistanceFunction(hovermode, dx, dy, dxy);
41-
42-
Fx.getClosest(cd, distfn, pointData);
43-
44-
// skip the rest (for this trace) if we didn't find a close point
45-
if(pointData.index === false) return;
46-
47-
// the closest data point
48-
var di = cd[pointData.index],
49-
xc = xa.c2p(di.x, true),
50-
yc = ya.c2p(di.y, true),
51-
rad = di.mrc || 1;
52-
53-
pointData.color = getTraceColor(trace, di);
54-
55-
pointData.x0 = xc - rad;
56-
pointData.x1 = xc + rad;
57-
pointData.xLabelVal = di.x;
58-
59-
pointData.y0 = yc - rad;
60-
pointData.y1 = yc + rad;
61-
pointData.yLabelVal = di.y;
62-
63-
if(di.tx) pointData.text = di.tx;
64-
else if(trace.text) pointData.text = trace.text;
65-
66-
ErrorBars.hoverInfo(di, trace, pointData);
67-
68-
return [pointData];
24+
xpx = xa.c2p(xval),
25+
ypx = ya.c2p(yval),
26+
pt = [xpx, ypx];
27+
28+
// even if hoveron is 'fills', only use it if we have polygons too
29+
if(trace.hoveron === 'fills' && trace._polygons) {
30+
var polygons = trace._polygons,
31+
inside = false,
32+
x0 = Infinity,
33+
x1 = -Infinity,
34+
y0 = Infinity,
35+
y1 = -Infinity;
36+
37+
for(var i = 0; i < polygons.length; i++) {
38+
var polygon = polygons[i];
39+
// TODO: this is not going to work right for curved edges, it will
40+
// act as though they're straight. That's probably going to need
41+
// the elements themselves to capture the events. Worth it?
42+
if(polygon.contains(pt)) {
43+
inside = !inside;
44+
// TODO: need better than just the overall bounding box
45+
x0 = Math.min(x0, polygon.xmin);
46+
x1 = Math.max(x1, polygon.xmax);
47+
y0 = Math.min(y0, polygon.ymin);
48+
y1 = Math.max(y1, polygon.ymax);
49+
}
50+
}
51+
52+
if(inside) {
53+
// get only fill or line color for the hover color
54+
var color = Color.defaultLine;
55+
if(Color.opacity(trace.fillcolor)) color = trace.fillcolor;
56+
else if(Color.opacity((trace.line || {}).color)) {
57+
color = trace.line.color;
58+
}
59+
60+
Lib.extendFlat(pointData, {
61+
// never let a 2D override 1D type as closest point
62+
distance: Fx.MAXDIST + 10,
63+
x0: x0,
64+
x1: x1,
65+
y0: y0,
66+
y1: y1,
67+
color: color
68+
});
69+
70+
delete pointData.index;
71+
return [pointData];
72+
}
73+
}
74+
else {
75+
var dx = function(di) {
76+
// scatter points: d.mrc is the calculated marker radius
77+
// adjust the distance so if you're inside the marker it
78+
// always will show up regardless of point size, but
79+
// prioritize smaller points
80+
var rad = Math.max(3, di.mrc || 0);
81+
return Math.max(Math.abs(xa.c2p(di.x) - xa.c2p(xval)) - rad, 1 - 3 / rad);
82+
},
83+
dy = function(di) {
84+
var rad = Math.max(3, di.mrc || 0);
85+
return Math.max(Math.abs(ya.c2p(di.y) - ya.c2p(yval)) - rad, 1 - 3 / rad);
86+
},
87+
dxy = function(di) {
88+
var rad = Math.max(3, di.mrc || 0),
89+
dx = Math.abs(xa.c2p(di.x) - xa.c2p(xval)),
90+
dy = Math.abs(ya.c2p(di.y) - ya.c2p(yval));
91+
return Math.max(Math.sqrt(dx * dx + dy * dy) - rad, 1 - 3 / rad);
92+
},
93+
distfn = Fx.getDistanceFunction(hovermode, dx, dy, dxy);
94+
95+
Fx.getClosest(cd, distfn, pointData);
96+
97+
// skip the rest (for this trace) if we didn't find a close point
98+
if(pointData.index === false) return;
99+
100+
// the closest data point
101+
var di = cd[pointData.index],
102+
xc = xa.c2p(di.x, true),
103+
yc = ya.c2p(di.y, true),
104+
rad = di.mrc || 1;
105+
106+
pointData.color = getTraceColor(trace, di);
107+
108+
pointData.x0 = xc - rad;
109+
pointData.x1 = xc + rad;
110+
pointData.xLabelVal = di.x;
111+
112+
pointData.y0 = yc - rad;
113+
pointData.y1 = yc + rad;
114+
pointData.yLabelVal = di.y;
115+
116+
if(di.tx) pointData.text = di.tx;
117+
else if(trace.text) pointData.text = trace.text;
118+
119+
ErrorBars.hoverInfo(di, trace, pointData);
120+
121+
return [pointData];
122+
}
69123
};

src/traces/scatter/plot.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ var Lib = require('../../lib');
1515
var Drawing = require('../../components/drawing');
1616
var ErrorBars = require('../../components/errorbars');
1717

18+
var polygonTester = require('../../lib/polygon').tester;
19+
1820
var subTypes = require('./subtypes');
1921
var arraysToCalcdata = require('./arrays_to_calcdata');
2022
var linePoints = require('./line_points');
@@ -125,12 +127,22 @@ module.exports = function plot(gd, plotinfo, cdscatter) {
125127
linear: line.shape === 'linear'
126128
});
127129

130+
// since we already have the pixel segments here, use them to make
131+
// polygons for hover on fill
132+
// TODO: can we skip this if hoveron!=fills? That would mean we
133+
// need to redraw when you change hoveron...
134+
trace._polygons = new Array(segments.length);
135+
var i;
136+
for(i = 0; i < segments.length; i++) {
137+
trace._polygons[i] = polygonTester(segments[i]);
138+
}
139+
128140
if(segments.length) {
129141
var pt0 = segments[0][0],
130142
lastSegment = segments[segments.length - 1],
131143
pt1 = lastSegment[lastSegment.length - 1];
132144

133-
for(var i = 0; i < segments.length; i++) {
145+
for(i = 0; i < segments.length; i++) {
134146
var pts = segments[i];
135147
thispath = pathfn(pts);
136148
thisrevpath = revpathfn(pts);

0 commit comments

Comments
 (0)