|
9 | 9 |
|
10 | 10 | 'use strict';
|
11 | 11 |
|
| 12 | +var Lib = require('../../lib'); |
12 | 13 | var Fx = require('../../plots/cartesian/graph_interact');
|
| 14 | +var constants = require('../../plots/cartesian/constants'); |
13 | 15 | var ErrorBars = require('../../components/errorbars');
|
14 | 16 | var getTraceColor = require('./get_trace_color');
|
| 17 | +var Color = require('../../components/color'); |
15 | 18 |
|
16 | 19 |
|
17 | 20 | module.exports = function hoverPoints(pointData, xval, yval, hovermode) {
|
18 | 21 | var cd = pointData.cd,
|
19 | 22 | trace = cd[0].trace,
|
20 | 23 | xa = pointData.xa,
|
21 | 24 | 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]; |
| 25 | + xpx = xa.c2p(xval), |
| 26 | + ypx = ya.c2p(yval), |
| 27 | + pt = [xpx, ypx]; |
| 28 | + |
| 29 | + // look for points to hover on first, then take fills only if we |
| 30 | + // didn't find a point |
| 31 | + if(trace.hoveron.indexOf('points') !== -1) { |
| 32 | + var dx = function(di) { |
| 33 | + // scatter points: d.mrc is the calculated marker radius |
| 34 | + // adjust the distance so if you're inside the marker it |
| 35 | + // always will show up regardless of point size, but |
| 36 | + // prioritize smaller points |
| 37 | + var rad = Math.max(3, di.mrc || 0); |
| 38 | + return Math.max(Math.abs(xa.c2p(di.x) - xpx) - rad, 1 - 3 / rad); |
| 39 | + }, |
| 40 | + dy = function(di) { |
| 41 | + var rad = Math.max(3, di.mrc || 0); |
| 42 | + return Math.max(Math.abs(ya.c2p(di.y) - ypx) - rad, 1 - 3 / rad); |
| 43 | + }, |
| 44 | + dxy = function(di) { |
| 45 | + var rad = Math.max(3, di.mrc || 0), |
| 46 | + dx = xa.c2p(di.x) - xpx, |
| 47 | + dy = ya.c2p(di.y) - ypx; |
| 48 | + return Math.max(Math.sqrt(dx * dx + dy * dy) - rad, 1 - 3 / rad); |
| 49 | + }, |
| 50 | + distfn = Fx.getDistanceFunction(hovermode, dx, dy, dxy); |
| 51 | + |
| 52 | + Fx.getClosest(cd, distfn, pointData); |
| 53 | + |
| 54 | + // skip the rest (for this trace) if we didn't find a close point |
| 55 | + if(pointData.index !== false) { |
| 56 | + |
| 57 | + // the closest data point |
| 58 | + var di = cd[pointData.index], |
| 59 | + xc = xa.c2p(di.x, true), |
| 60 | + yc = ya.c2p(di.y, true), |
| 61 | + rad = di.mrc || 1; |
| 62 | + |
| 63 | + Lib.extendFlat(pointData, { |
| 64 | + color: getTraceColor(trace, di), |
| 65 | + |
| 66 | + x0: xc - rad, |
| 67 | + x1: xc + rad, |
| 68 | + xLabelVal: di.x, |
| 69 | + |
| 70 | + y0: yc - rad, |
| 71 | + y1: yc + rad, |
| 72 | + yLabelVal: di.y |
| 73 | + }); |
| 74 | + |
| 75 | + if(di.tx) pointData.text = di.tx; |
| 76 | + else if(trace.text) pointData.text = trace.text; |
| 77 | + |
| 78 | + ErrorBars.hoverInfo(di, trace, pointData); |
| 79 | + |
| 80 | + return [pointData]; |
| 81 | + } |
| 82 | + } |
| 83 | + |
| 84 | + // even if hoveron is 'fills', only use it if we have polygons too |
| 85 | + if(trace.hoveron.indexOf('fills') !== -1 && trace._polygons) { |
| 86 | + var polygons = trace._polygons, |
| 87 | + polygonsIn = [], |
| 88 | + inside = false, |
| 89 | + xmin = Infinity, |
| 90 | + xmax = -Infinity, |
| 91 | + ymin = Infinity, |
| 92 | + ymax = -Infinity, |
| 93 | + i, j, polygon, pts, xCross, x0, x1, y0, y1; |
| 94 | + |
| 95 | + for(i = 0; i < polygons.length; i++) { |
| 96 | + polygon = polygons[i]; |
| 97 | + // TODO: this is not going to work right for curved edges, it will |
| 98 | + // act as though they're straight. That's probably going to need |
| 99 | + // the elements themselves to capture the events. Worth it? |
| 100 | + if(polygon.contains(pt)) { |
| 101 | + inside = !inside; |
| 102 | + // TODO: need better than just the overall bounding box |
| 103 | + polygonsIn.push(polygon); |
| 104 | + ymin = Math.min(ymin, polygon.ymin); |
| 105 | + ymax = Math.max(ymax, polygon.ymax); |
| 106 | + } |
| 107 | + } |
| 108 | + |
| 109 | + if(inside) { |
| 110 | + // find the overall left-most and right-most points of the |
| 111 | + // polygon(s) we're inside at their combined vertical midpoint. |
| 112 | + // This is where we will draw the hover label. |
| 113 | + // Note that this might not be the vertical midpoint of the |
| 114 | + // whole trace, if it's disjoint. |
| 115 | + var yAvg = (ymin + ymax) / 2; |
| 116 | + for(i = 0; i < polygonsIn.length; i++) { |
| 117 | + pts = polygonsIn[i].pts; |
| 118 | + for(j = 1; j < pts.length; j++) { |
| 119 | + y0 = pts[j - 1][1]; |
| 120 | + y1 = pts[j][1]; |
| 121 | + if((y0 > yAvg) !== (y1 >= yAvg)) { |
| 122 | + x0 = pts[j - 1][0]; |
| 123 | + x1 = pts[j][0]; |
| 124 | + xCross = x0 + (x1 - x0) * (yAvg - y0) / (y1 - y0); |
| 125 | + xmin = Math.min(xmin, xCross); |
| 126 | + xmax = Math.max(xmax, xCross); |
| 127 | + } |
| 128 | + } |
| 129 | + } |
| 130 | + |
| 131 | + // get only fill or line color for the hover color |
| 132 | + var color = Color.defaultLine; |
| 133 | + if(Color.opacity(trace.fillcolor)) color = trace.fillcolor; |
| 134 | + else if(Color.opacity((trace.line || {}).color)) { |
| 135 | + color = trace.line.color; |
| 136 | + } |
| 137 | + |
| 138 | + Lib.extendFlat(pointData, { |
| 139 | + // never let a 2D override 1D type as closest point |
| 140 | + distance: constants.MAXDIST + 10, |
| 141 | + x0: xmin, |
| 142 | + x1: xmax, |
| 143 | + y0: yAvg, |
| 144 | + y1: yAvg, |
| 145 | + color: color |
| 146 | + }); |
| 147 | + |
| 148 | + delete pointData.index; |
| 149 | + |
| 150 | + if(trace.text && !Array.isArray(trace.text)) { |
| 151 | + pointData.text = String(trace.text); |
| 152 | + } |
| 153 | + else pointData.text = trace.name; |
| 154 | + |
| 155 | + return [pointData]; |
| 156 | + } |
| 157 | + } |
69 | 158 | };
|
0 commit comments