- 
          
- 
                Notifications
    You must be signed in to change notification settings 
- Fork 1.9k
Box points hover & select #2094
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
cfc8725
              53c446b
              581f3fa
              e59f283
              5c58049
              aef61ae
              d352fa5
              6a878b0
              0027b84
              a1ea1e8
              6bfcbe0
              9ca410a
              7e44c9c
              f228a5e
              b4d8044
              9e69900
              8901528
              b66628e
              a3ec75a
              File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
|  | @@ -203,5 +203,16 @@ module.exports = { | |
| }, | ||
| editType: 'plot' | ||
| }, | ||
| fillcolor: scatterAttrs.fillcolor | ||
| fillcolor: scatterAttrs.fillcolor, | ||
| hoveron: { | ||
| valType: 'flaglist', | ||
| flags: ['boxes', 'points'], | ||
| dflt: 'boxes', | ||
| role: 'info', | ||
| editType: 'style', | ||
| description: [ | ||
| 'Do the hover effects highlight individual boxes ', | ||
| 'or jitter points or both?' | ||
|          | ||
| ].join(' ') | ||
| } | ||
| }; | ||
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
|  | @@ -14,94 +14,167 @@ var Fx = require('../../components/fx'); | |
| var Color = require('../../components/color'); | ||
|  | ||
| module.exports = function hoverPoints(pointData, xval, yval, hovermode) { | ||
| // closest mode: handicap box plots a little relative to others | ||
| var cd = pointData.cd, | ||
| trace = cd[0].trace, | ||
| t = cd[0].t, | ||
| xa = pointData.xa, | ||
| ya = pointData.ya, | ||
| closeData = [], | ||
| dx, dy, distfn, boxDelta, | ||
| posLetter, posAxis, | ||
| val, valLetter, valAxis; | ||
|  | ||
| // adjust inbox w.r.t. to calculate box size | ||
| boxDelta = (hovermode === 'closest') ? 2.5 * t.bdPos : t.bdPos; | ||
|  | ||
| if(trace.orientation === 'h') { | ||
| dx = function(di) { | ||
| return Fx.inbox(di.min - xval, di.max - xval); | ||
| }; | ||
| dy = function(di) { | ||
| var pos = di.pos + t.bPos - yval; | ||
| return Fx.inbox(pos - boxDelta, pos + boxDelta); | ||
| }; | ||
| posLetter = 'y'; | ||
| posAxis = ya; | ||
| valLetter = 'x'; | ||
| valAxis = xa; | ||
| } else { | ||
| dx = function(di) { | ||
| var pos = di.pos + t.bPos - xval; | ||
| return Fx.inbox(pos - boxDelta, pos + boxDelta); | ||
| }; | ||
| dy = function(di) { | ||
| return Fx.inbox(di.min - yval, di.max - yval); | ||
| }; | ||
| posLetter = 'x'; | ||
| posAxis = xa; | ||
| valLetter = 'y'; | ||
| valAxis = ya; | ||
| } | ||
| var cd = pointData.cd; | ||
| var xa = pointData.xa; | ||
| var ya = pointData.ya; | ||
|  | ||
| var trace = cd[0].trace; | ||
| var hoveron = trace.hoveron; | ||
| var marker = trace.marker || {}; | ||
|  | ||
| // output hover points array | ||
| var closeData = []; | ||
| // x/y/effective distance functions | ||
| var dx, dy, distfn; | ||
| // orientation-specific fields | ||
| var posLetter, valLetter, posAxis, valAxis; | ||
| // calcdata item | ||
| var di; | ||
| // hover point item extended from pointData | ||
| var pointData2; | ||
| // loop indices | ||
| var i, j; | ||
|  | ||
| if(hoveron.indexOf('boxes') !== -1) { | ||
| var t = cd[0].t; | ||
|  | ||
| // closest mode: handicap box plots a little relative to others | ||
| // adjust inbox w.r.t. to calculate box size | ||
| var boxDelta = (hovermode === 'closest') ? 2.5 * t.bdPos : t.bdPos; | ||
|  | ||
| if(trace.orientation === 'h') { | ||
| dx = function(di) { | ||
| return Fx.inbox(di.min - xval, di.max - xval); | ||
| }; | ||
| dy = function(di) { | ||
| var pos = di.pos + t.bPos - yval; | ||
| return Fx.inbox(pos - boxDelta, pos + boxDelta); | ||
| }; | ||
| posLetter = 'y'; | ||
| posAxis = ya; | ||
| valLetter = 'x'; | ||
| valAxis = xa; | ||
| } else { | ||
| dx = function(di) { | ||
| var pos = di.pos + t.bPos - xval; | ||
| return Fx.inbox(pos - boxDelta, pos + boxDelta); | ||
| }; | ||
| dy = function(di) { | ||
| return Fx.inbox(di.min - yval, di.max - yval); | ||
| }; | ||
| posLetter = 'x'; | ||
| posAxis = xa; | ||
| valLetter = 'y'; | ||
| valAxis = ya; | ||
| } | ||
|  | ||
| distfn = Fx.getDistanceFunction(hovermode, dx, dy); | ||
| Fx.getClosest(cd, distfn, pointData); | ||
|  | ||
| distfn = Fx.getDistanceFunction(hovermode, dx, dy); | ||
| Fx.getClosest(cd, distfn, pointData); | ||
| // skip the rest (for this trace) if we didn't find a close point | ||
| // and create the item(s) in closedata for this point | ||
| if(pointData.index !== false) { | ||
| di = cd[pointData.index]; | ||
|  | ||
| // skip the rest (for this trace) if we didn't find a close point | ||
| if(pointData.index === false) return; | ||
| var lc = trace.line.color; | ||
| var mc = marker.color; | ||
|  | ||
| // create the item(s) in closedata for this point | ||
| if(Color.opacity(lc) && trace.line.width) pointData.color = lc; | ||
| else if(Color.opacity(mc) && trace.boxpoints) pointData.color = mc; | ||
| else pointData.color = trace.fillcolor; | ||
|  | ||
| // the closest data point | ||
| var di = cd[pointData.index], | ||
| lc = trace.line.color, | ||
| mc = (trace.marker || {}).color; | ||
| if(Color.opacity(lc) && trace.line.width) pointData.color = lc; | ||
| else if(Color.opacity(mc) && trace.boxpoints) pointData.color = mc; | ||
| else pointData.color = trace.fillcolor; | ||
| pointData[posLetter + '0'] = posAxis.c2p(di.pos + t.bPos - t.bdPos, true); | ||
| pointData[posLetter + '1'] = posAxis.c2p(di.pos + t.bPos + t.bdPos, true); | ||
|  | ||
| pointData[posLetter + '0'] = posAxis.c2p(di.pos + t.bPos - t.bdPos, true); | ||
| pointData[posLetter + '1'] = posAxis.c2p(di.pos + t.bPos + t.bdPos, true); | ||
| Axes.tickText(posAxis, posAxis.c2l(di.pos), 'hover').text; | ||
| pointData[posLetter + 'LabelVal'] = di.pos; | ||
|  | ||
| Axes.tickText(posAxis, posAxis.c2l(di.pos), 'hover').text; | ||
| pointData[posLetter + 'LabelVal'] = di.pos; | ||
| // box plots: each "point" gets many labels | ||
| var usedVals = {}; | ||
| var attrs = ['med', 'min', 'q1', 'q3', 'max']; | ||
|  | ||
| // box plots: each "point" gets many labels | ||
| var usedVals = {}, | ||
| attrs = ['med', 'min', 'q1', 'q3', 'max'], | ||
| attr, | ||
| pointData2; | ||
| if(trace.boxmean) attrs.push('mean'); | ||
| if(trace.boxpoints) [].push.apply(attrs, ['lf', 'uf']); | ||
| if(trace.boxmean) attrs.push('mean'); | ||
| if(trace.boxpoints) [].push.apply(attrs, ['lf', 'uf']); | ||
|  | ||
| for(var i = 0; i < attrs.length; i++) { | ||
| attr = attrs[i]; | ||
| for(i = 0; i < attrs.length; i++) { | ||
| var attr = attrs[i]; | ||
|  | ||
| if(!(attr in di) || (di[attr] in usedVals)) continue; | ||
| usedVals[di[attr]] = true; | ||
| if(!(attr in di) || (di[attr] in usedVals)) continue; | ||
| usedVals[di[attr]] = true; | ||
|  | ||
| // copy out to a new object for each value to label | ||
| val = valAxis.c2p(di[attr], true); | ||
| pointData2 = Lib.extendFlat({}, pointData); | ||
| pointData2[valLetter + '0'] = pointData2[valLetter + '1'] = val; | ||
| pointData2[valLetter + 'LabelVal'] = di[attr]; | ||
| pointData2.attr = attr; | ||
| // copy out to a new object for each value to label | ||
| var val = valAxis.c2p(di[attr], true); | ||
| pointData2 = Lib.extendFlat({}, pointData); | ||
| pointData2[valLetter + '0'] = pointData2[valLetter + '1'] = val; | ||
| pointData2[valLetter + 'LabelVal'] = di[attr]; | ||
| pointData2.attr = attr; | ||
|  | ||
| if(attr === 'mean' && ('sd' in di) && trace.boxmean === 'sd') { | ||
| pointData2[valLetter + 'err'] = di.sd; | ||
| if(attr === 'mean' && ('sd' in di) && trace.boxmean === 'sd') { | ||
| pointData2[valLetter + 'err'] = di.sd; | ||
| } | ||
| // only keep name on the first item (median) | ||
| pointData.name = ''; | ||
|  | ||
| closeData.push(pointData2); | ||
| } | ||
| } | ||
| pointData.name = ''; // only keep name on the first item (median) | ||
| closeData.push(pointData2); | ||
| } | ||
|  | ||
| if(hoveron.indexOf('points') !== -1) { | ||
| var xPx = xa.c2p(xval); | ||
| var yPx = ya.c2p(yval); | ||
|  | ||
| // do not take jitter into consideration in compare hover modes | ||
|          | ||
| var kx, ky; | ||
| if(hovermode === 'closest') { | ||
| kx = 'x'; | ||
| ky = 'y'; | ||
| } else { | ||
| kx = 'xh'; | ||
| ky = 'yh'; | ||
| } | ||
|  | ||
| dx = function(di) { | ||
| var rad = Math.max(3, di.mrc || 0); | ||
| return Math.max(Math.abs(xa.c2p(di[kx]) - xPx) - rad, 1 - 3 / rad); | ||
| }; | ||
| dy = function(di) { | ||
| var rad = Math.max(3, di.mrc || 0); | ||
| return Math.max(Math.abs(ya.c2p(di[ky]) - yPx) - rad, 1 - 3 / rad); | ||
| }; | ||
| distfn = Fx.getDistanceFunction(hovermode, dx, dy); | ||
|  | ||
| for(i = 0; i < cd.length; i++) { | ||
| di = cd[i]; | ||
|  | ||
| for(j = 0; j < (di.pts || []).length; j++) { | ||
| var pt = di.pts[j]; | ||
|  | ||
| var newDistance = distfn(pt); | ||
| if(newDistance <= pointData.distance) { | ||
| pointData.distance = newDistance; | ||
|  | ||
| var xc = xa.c2p(pt.x, true); | ||
| var yc = ya.c2p(pt.y, true); | ||
| var rad = pt.mrc || 1; | ||
|  | ||
| pointData2 = Lib.extendFlat({}, pointData, { | ||
| // corresponds to index in x/y input data array | ||
| index: pt.i, | ||
| color: marker.color, | ||
| x0: xc - rad, | ||
| x1: xc + rad, | ||
| xLabelVal: pt.x, | ||
| y0: yc - rad, | ||
| y1: yc + rad, | ||
| yLabelVal: pt.y | ||
| }); | ||
|  | ||
| closeData.push(pointData2); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|  | ||
| return closeData; | ||
|          | ||
| }; | ||





There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Debatable choice here. Strictly speaking we need
dflt: 'boxes'to keep backward compat, but perhaps someone can argue otherwise.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dflt: 'boxes'👍There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree here, but perhaps to make this feature accessible to existing graphs, the show closest data on hover mode bar button could restyle
hoveronto'points+boxes'as well aslayout.hovermodeon click for traces withboxpoints: 'all'?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh, hmm... that's a good point actually. Can I change my answer to
dflt: 'boxes+points'? I don't think turning on a new hover feature really amounts to a breaking change, I suppose in principle it can lead to events that a user application doesn't know how to handle but that seems pretty minor IMHO.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dflt: boxes+points👍 One could even argue that not being able to hover on the box points is a 🐞 .Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done in 6bfcbe0