From 54cc67bf4dcc33da28d44cf2fae68643f4bf0961 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Etienne=20T=C3=A9treault-Pinard?= Date: Wed, 15 Nov 2017 14:50:45 -0500 Subject: [PATCH 01/11] make hover AND select use same event data pipeline --- src/components/fx/helpers.js | 46 +++++++++++++++++++++++++++++++++++ src/components/fx/hover.js | 21 +--------------- src/plots/cartesian/select.js | 10 +++----- 3 files changed, 50 insertions(+), 27 deletions(-) diff --git a/src/components/fx/helpers.js b/src/components/fx/helpers.js index fd005f52824..bc33eb1bef5 100644 --- a/src/components/fx/helpers.js +++ b/src/components/fx/helpers.js @@ -85,6 +85,52 @@ exports.quadrature = function quadrature(dx, dy) { }; }; +/** Fill event data point object for hover and selection. + * Invokes _module.eventData if present. + * + * N.B. note that point 'index' corresponds to input data array index + * whereas 'number' is its post-transform version. + * + * If the hovered/selected pt corresponds to an multiple input points + * (e.g. for histogram and transformed traces), 'pointNumbers` and 'pointIndices' + * are include in the event data. + * + * @param {object} pt + * @param {object} trace + * @param {object} cd + * @return {object} + */ +exports.makeEventData = function makeEventData(pt, trace, cd) { + // hover uses 'index', select uses 'pointNumber' + var pointNumber = 'index' in pt ? pt.index : pt.pointNumber; + + var out = { + data: trace._input, + fullData: trace, + curveNumber: trace.index, + pointNumber: pointNumber + }; + + + if(trace._module.eventData) { + out = trace._module.eventData(out, pt, trace, cd, pointNumber); + } else { + if('xVal' in pt) out.x = pt.xVal; + else if('x' in pt) out.x = pt.x; + + if('yVal' in pt) out.y = pt.yVal; + else if('y' in pt) out.y = pt.y; + + if(pt.xa) out.xaxis = pt.xa; + if(pt.ya) out.yaxis = pt.ya; + if(pt.zLabelVal !== undefined) out.z = pt.zLabelVal; + } + + exports.appendArrayPointValue(out, trace, pointNumber); + + return out; +}; + /** Appends values inside array attributes corresponding to given point number * * @param {object} pointData : point data object (gets mutated here) diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index 37d80fbc126..3df5fd605ae 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -417,26 +417,7 @@ function _hover(gd, evt, subplot, noHoverEvent) { // other people and send it to the event for(itemnum = 0; itemnum < hoverData.length; itemnum++) { var pt = hoverData[itemnum]; - - var out = { - data: pt.trace._input, - fullData: pt.trace, - curveNumber: pt.trace.index, - pointNumber: pt.index - }; - - if(pt.trace._module.eventData) out = pt.trace._module.eventData(out, pt); - else { - out.x = pt.xVal; - out.y = pt.yVal; - out.xaxis = pt.xa; - out.yaxis = pt.ya; - - if(pt.zLabelVal !== undefined) out.z = pt.zLabelVal; - } - - helpers.appendArrayPointValue(out, pt.trace, pt.index); - newhoverdata.push(out); + newhoverdata.push(helpers.makeEventData(pt, pt.trace, pt.cd)); } gd._hoverdata = newhoverdata; diff --git a/src/plots/cartesian/select.js b/src/plots/cartesian/select.js index bae4c0315f6..b789f40f23e 100644 --- a/src/plots/cartesian/select.js +++ b/src/plots/cartesian/select.js @@ -13,7 +13,7 @@ var polybool = require('polybooljs'); var polygon = require('../../lib/polygon'); var throttle = require('../../lib/throttle'); var color = require('../../components/color'); -var appendArrayPointValue = require('../../components/fx/helpers').appendArrayPointValue; +var makeEventData = require('../../components/fx/helpers').makeEventData; var axes = require('./axes'); var constants = require('./constants'); @@ -355,15 +355,11 @@ function mergePolygons(list, poly, subtract) { function fillSelectionItem(selection, searchInfo) { if(Array.isArray(selection)) { + var cd = searchInfo.cd; var trace = searchInfo.cd[0].trace; for(var i = 0; i < selection.length; i++) { - var sel = selection[i]; - - sel.curveNumber = trace.index; - sel.data = trace._input; - sel.fullData = trace; - appendArrayPointValue(sel, trace, sel.pointNumber); + selection[i] = makeEventData(selection[i], trace, cd); } } From 638d03db45d0f00bff2f0666b782185d0e861095 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Etienne=20T=C3=A9treault-Pinard?= Date: Wed, 15 Nov 2017 14:52:54 -0500 Subject: [PATCH 02/11] adapt Histogram.eventData so that it works with selection pts --- src/traces/histogram/event_data.js | 24 +++++++++++++----------- src/traces/histogram/hover.js | 1 - src/traces/histogram2d/hover.js | 1 - 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/traces/histogram/event_data.js b/src/traces/histogram/event_data.js index 7c39678087d..7a963336aee 100644 --- a/src/traces/histogram/event_data.js +++ b/src/traces/histogram/event_data.js @@ -6,21 +6,23 @@ * LICENSE file in the root directory of this source tree. */ - 'use strict'; - -module.exports = function eventData(out, pt) { +module.exports = function eventData(out, pt, trace, cd, pointNumber) { // standard cartesian event data - out.x = pt.xVal; - out.y = pt.yVal; - out.xaxis = pt.xa; - out.yaxis = pt.ya; + out.x = 'xVal' in pt ? pt.xVal : pt.x; + out.y = 'yVal' in pt ? pt.yVal : pt.y; + + if(pt.xa) out.xaxis = pt.xa; + if(pt.ya) out.yaxis = pt.ya; + + // specific to histogram - CDFs do not have pts (yet?) + if(!(trace.cumulative || {}).enabled) { + var pts = Array.isArray(pointNumber) ? + cd[0].pts[pointNumber[0]][pointNumber[1]] : + cd[pointNumber].pts; - // specific to histogram - // CDFs do not have pts (yet?) - if(pt.pts) { - out.pointNumbers = pt.pts; + out.pointNumbers = pts; out.binNumber = out.pointNumber; delete out.pointNumber; } diff --git a/src/traces/histogram/hover.js b/src/traces/histogram/hover.js index 5bfe0317840..6fe0b2222b3 100644 --- a/src/traces/histogram/hover.js +++ b/src/traces/histogram/hover.js @@ -25,7 +25,6 @@ module.exports = function hoverPoints(pointData, xval, yval, hovermode) { var posLetter = trace.orientation === 'h' ? 'y' : 'x'; pointData[posLetter + 'Label'] = hoverLabelText(pointData[posLetter + 'a'], di.p0, di.p1); - pointData.pts = di.pts; } return pts; diff --git a/src/traces/histogram2d/hover.js b/src/traces/histogram2d/hover.js index ccce7d3d712..73bad554923 100644 --- a/src/traces/histogram2d/hover.js +++ b/src/traces/histogram2d/hover.js @@ -27,7 +27,6 @@ module.exports = function hoverPoints(pointData, xval, yval, hovermode, hoverLay pointData.xLabel = hoverLabelText(pointData.xa, xRange[0], xRange[1]); pointData.yLabel = hoverLabelText(pointData.ya, yRange[0], yRange[1]); - pointData.pts = cd0.pts[ny][nx]; return pts; }; From b30fab46a7fb8812d464c2da324167e0cf94ba18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Etienne=20T=C3=A9treault-Pinard?= Date: Wed, 15 Nov 2017 14:54:33 -0500 Subject: [PATCH 03/11] add Scattercarpet.eventData method - :hocho: its select method and reuse Scatter.select instead which is much DRYer. --- src/traces/scattercarpet/event_data.js | 18 +++++++++++++++ src/traces/scattercarpet/index.js | 3 ++- src/traces/scattercarpet/select.js | 32 -------------------------- 3 files changed, 20 insertions(+), 33 deletions(-) create mode 100644 src/traces/scattercarpet/event_data.js delete mode 100644 src/traces/scattercarpet/select.js diff --git a/src/traces/scattercarpet/event_data.js b/src/traces/scattercarpet/event_data.js new file mode 100644 index 00000000000..829dcdaef20 --- /dev/null +++ b/src/traces/scattercarpet/event_data.js @@ -0,0 +1,18 @@ +/** +* Copyright 2012-2017, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +module.exports = function eventData(out, pt, trace, cd, pointNumber) { + var cdi = cd[pointNumber]; + + out.a = cdi.a; + out.b = cdi.b; + + return out; +}; diff --git a/src/traces/scattercarpet/index.js b/src/traces/scattercarpet/index.js index a5d84296fd1..c7633f1f4f5 100644 --- a/src/traces/scattercarpet/index.js +++ b/src/traces/scattercarpet/index.js @@ -17,7 +17,8 @@ ScatterCarpet.calc = require('./calc'); ScatterCarpet.plot = require('./plot'); ScatterCarpet.style = require('./style'); ScatterCarpet.hoverPoints = require('./hover'); -ScatterCarpet.selectPoints = require('./select'); +ScatterCarpet.selectPoints = require('../scatter/select'); +ScatterCarpet.eventData = require('./event_data'); ScatterCarpet.moduleType = 'trace'; ScatterCarpet.name = 'scattercarpet'; diff --git a/src/traces/scattercarpet/select.js b/src/traces/scattercarpet/select.js deleted file mode 100644 index ff40c61a271..00000000000 --- a/src/traces/scattercarpet/select.js +++ /dev/null @@ -1,32 +0,0 @@ -/** -* Copyright 2012-2017, Plotly, Inc. -* All rights reserved. -* -* This source code is licensed under the MIT license found in the -* LICENSE file in the root directory of this source tree. -*/ - - -'use strict'; - -var scatterSelect = require('../scatter/select'); - - -module.exports = function selectPoints(searchInfo, polygon) { - var selection = scatterSelect(searchInfo, polygon); - if(!selection) return; - - var cd = searchInfo.cd, - pt, cdi, i; - - for(i = 0; i < selection.length; i++) { - pt = selection[i]; - cdi = cd[pt.pointNumber]; - pt.a = cdi.a; - pt.b = cdi.b; - delete pt.x; - delete pt.y; - } - - return selection; -}; From 949b311f81b46caf1fad77139e13ab1d9369a461 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Etienne=20T=C3=A9treault-Pinard?= Date: Wed, 15 Nov 2017 14:56:25 -0500 Subject: [PATCH 04/11] add ScatterTernary.eventData method - :hocho: is select method, reuse Scatter.select instead - using same event-data pipeline for hover and select revealed an inconsistency: hover data includes the raw input coords whereas selection data includes the scaled coords (as in calcdata). --- src/traces/scatterternary/event_data.js | 37 +++++++++++++++++++++++++ src/traces/scatterternary/index.js | 3 +- src/traces/scatterternary/select.js | 33 ---------------------- 3 files changed, 39 insertions(+), 34 deletions(-) create mode 100644 src/traces/scatterternary/event_data.js delete mode 100644 src/traces/scatterternary/select.js diff --git a/src/traces/scatterternary/event_data.js b/src/traces/scatterternary/event_data.js new file mode 100644 index 00000000000..5ded7380d59 --- /dev/null +++ b/src/traces/scatterternary/event_data.js @@ -0,0 +1,37 @@ +/** +* Copyright 2012-2017, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +module.exports = function eventData(out, pt, trace, cd, pointNumber) { + if(pt.xa) out.xaxis = pt.xa; + if(pt.ya) out.yaxis = pt.ya; + + if(cd[pointNumber]) { + var cdi = cd[pointNumber]; + + // N.B. These are the scale coordinates !!! + // + // On master, hover events get the non-scaled coordinates + // whereas selection events get the scaled version. + // Note also that the hover labels show the scaled version. + // + // What about the 'raw' input coordinates? + // Should we include them in parallel here or replace a/b/c with them? + out.a = cdi.a; + out.b = cdi.b; + out.c = cdi.c; + } else { + // for fill-hover only + out.a = pt.a; + out.b = pt.b; + out.c = pt.c; + } + + return out; +}; diff --git a/src/traces/scatterternary/index.js b/src/traces/scatterternary/index.js index abbdc7f06b1..781cf0c10fe 100644 --- a/src/traces/scatterternary/index.js +++ b/src/traces/scatterternary/index.js @@ -17,7 +17,8 @@ ScatterTernary.calc = require('./calc'); ScatterTernary.plot = require('./plot'); ScatterTernary.style = require('./style'); ScatterTernary.hoverPoints = require('./hover'); -ScatterTernary.selectPoints = require('./select'); +ScatterTernary.selectPoints = require('../scatter/select'); +ScatterTernary.eventData = require('./event_data'); ScatterTernary.moduleType = 'trace'; ScatterTernary.name = 'scatterternary'; diff --git a/src/traces/scatterternary/select.js b/src/traces/scatterternary/select.js deleted file mode 100644 index 5682b0e1669..00000000000 --- a/src/traces/scatterternary/select.js +++ /dev/null @@ -1,33 +0,0 @@ -/** -* Copyright 2012-2017, Plotly, Inc. -* All rights reserved. -* -* This source code is licensed under the MIT license found in the -* LICENSE file in the root directory of this source tree. -*/ - - -'use strict'; - -var scatterSelect = require('../scatter/select'); - - -module.exports = function selectPoints(searchInfo, polygon) { - var selection = scatterSelect(searchInfo, polygon); - if(!selection) return; - - var cd = searchInfo.cd, - pt, cdi, i; - - for(i = 0; i < selection.length; i++) { - pt = selection[i]; - cdi = cd[pt.pointNumber]; - pt.a = cdi.a; - pt.b = cdi.b; - pt.c = cdi.c; - delete pt.x; - delete pt.y; - } - - return selection; -}; From 315e6327bbd512b20cec61e651f6bb945c1521d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Etienne=20T=C3=A9treault-Pinard?= Date: Wed, 15 Nov 2017 15:04:00 -0500 Subject: [PATCH 05/11] add Lib.tagSelected made to make selectedpoints work w/ transforms - tagSelected make heavy use of the transforms _indexToPoints map object to determine how input index (called pointIndex) are mapped to calcdata index (called pointNumber). - use pointIndex (not pointNumber) while filling up selectedpoints array to match input indices. - Use tagSelected in scatter, box, and histogram calcSelection methods taking care of all trace types that support selections. --- src/lib/index.js | 51 ++++++++++++++++++++++++++++ src/plots/cartesian/select.js | 25 +++++++------- src/plots/plots.js | 16 ++++++++- src/traces/box/calc.js | 13 +++---- src/traces/histogram/calc.js | 18 ++++++++-- src/traces/scatter/calc_selection.js | 18 +++------- 6 files changed, 106 insertions(+), 35 deletions(-) diff --git a/src/lib/index.js b/src/lib/index.js index b34099227d6..3a34443d06d 100644 --- a/src/lib/index.js +++ b/src/lib/index.js @@ -465,6 +465,57 @@ lib.extractOption = function(calcPt, trace, calcKey, traceKey) { if(!Array.isArray(traceVal)) return traceVal; }; +/** Tag selected calcdata items + * + * N.B. note that point 'index' corresponds to input data array index + * whereas 'number' is its post-transform version. + * + * @param {array} calcTrace + * @param {object} trace + * - selectedpoints {array} + * - _indexToPoints {object} + * @param {ptNumber2cdIndex} ptNumber2cdIndex (optional) + * optional map object for trace types that do not have 1-to-1 point number to + * calcdata item index correspondence (e.g. histogram) + */ +lib.tagSelected = function(calcTrace, trace, ptNumber2cdIndex) { + var selectedpoints = trace.selectedpoints; + var indexToPoints = trace._indexToPoints; + var ptIndex2ptNumber; + + // make pt index-to-number map object, which takes care of transformed traces + if(indexToPoints) { + ptIndex2ptNumber = {}; + for(var k in indexToPoints) { + var pts = indexToPoints[k]; + for(var j = 0; j < pts.length; j++) { + ptIndex2ptNumber[pts[j]] = k; + } + } + } + + function isPtIndexValid(v) { + return lib.validate(v, {valType: 'integer', min: 0}); + } + + function isCdIndexValid(v) { + return v !== undefined && v < calcTrace.length; + } + + for(var i = 0; i < selectedpoints.length; i++) { + var ptIndex = selectedpoints[i]; + + if(isPtIndexValid(ptIndex)) { + var ptNumber = ptIndex2ptNumber ? ptIndex2ptNumber[ptIndex] : ptIndex; + var cdIndex = ptNumber2cdIndex ? ptNumber2cdIndex[ptNumber] : ptNumber; + + if(isCdIndexValid(cdIndex)) { + calcTrace[cdIndex].selected = 1; + } + } + } +}; + /** Returns target as set by 'target' transform attribute * * @param {object} trace : full trace object diff --git a/src/plots/cartesian/select.js b/src/plots/cartesian/select.js index b789f40f23e..353ba900735 100644 --- a/src/plots/cartesian/select.js +++ b/src/plots/cartesian/select.js @@ -240,9 +240,7 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) { traceSelection = searchInfo.selectPoints(searchInfo, testPoly); traceSelections.push(traceSelection); - var thisSelection = fillSelectionItem( - traceSelection, searchInfo - ); + var thisSelection = fillSelectionItem(traceSelection, searchInfo); if(selection.length) { for(var j = 0; j < thisSelection.length; j++) { selection.push(thisSelection[j]); @@ -294,30 +292,31 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) { }; function updateSelectedState(gd, searchTraces, eventData) { - var i, searchInfo; + var i, searchInfo, trace; if(eventData) { var pts = eventData.points || []; for(i = 0; i < searchTraces.length; i++) { - searchInfo = searchTraces[i]; - searchInfo.cd[0].trace.selectedpoints = []; - searchInfo.cd[0].trace._input.selectedpoints = []; + trace = searchTraces[i].cd[0].trace; + trace.selectedpoints = []; + trace._input.selectedpoints = []; } for(i = 0; i < pts.length; i++) { var pt = pts[i]; - var ptNumber = pt.pointNumber; + var data = pt.data; + var fullData = pt.fullData; - pt.data.selectedpoints.push(ptNumber); - pt.fullData.selectedpoints.push(ptNumber); + data.selectedpoints.push(pt.pointIndex); + fullData.selectedpoints.push(pt.pointIndex); } } else { for(i = 0; i < searchTraces.length; i++) { - searchInfo = searchTraces[i]; - delete searchInfo.cd[0].trace.selectedpoints; - delete searchInfo.cd[0].trace._input.selectedpoints; + trace = searchTraces[i].cd[0].trace; + delete trace.selectedpoints; + delete trace._input.selectedpoints; } } diff --git a/src/plots/plots.js b/src/plots/plots.js index a666aca97d7..de243c8a1c8 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -2241,7 +2241,21 @@ plots.doCalcdata = function(gd, traces) { if(trace.visible === true) { _module = trace._module; - if(_module && _module.calc) cd = _module.calc(gd, trace); + + // keep ref of index-to-points map object of the *last* enabled transform, + // this index-to-points map object is required to determine the calcdata indices + // that correspond to input indices (e.g. from 'selectedpoints') + var transforms = trace.transforms || []; + for(j = transforms.length - 1; j >= 0; j--) { + if(transforms[j].enabled) { + trace._indexToPoints = transforms[j]._indexToPoints; + break; + } + } + + if(_module && _module.calc) { + cd = _module.calc(gd, trace); + } } // Make sure there is a first point. diff --git a/src/traces/box/calc.js b/src/traces/box/calc.js index 2ded0b06d31..b1ad8c72d84 100644 --- a/src/traces/box/calc.js +++ b/src/traces/box/calc.js @@ -114,6 +114,7 @@ module.exports = function calc(gd, trace) { } } + calcSelection(cd, trace); Axes.expand(valAxis, val, {padded: true}); if(cd.length > 0) { @@ -193,13 +194,13 @@ function arraysToCalcdata(pt, trace, i) { pt[trace2calc[k]] = trace[k][i]; } } +} - var selectedpoints = trace.selectedpoints; - - // TODO this is slow - if(Array.isArray(selectedpoints)) { - if(selectedpoints.indexOf(pt.i) !== -1) { - pt.selected = 1; +function calcSelection(cd, trace) { + if(Array.isArray(trace.selectedpoints)) { + for(var i = 0; i < cd.length; i++) { + var pts = cd[i].pts || []; + Lib.tagSelected(pts, trace); } } } diff --git a/src/traces/histogram/calc.js b/src/traces/histogram/calc.js index 95a80db06b9..9722b0bf00d 100644 --- a/src/traces/histogram/calc.js +++ b/src/traces/histogram/calc.js @@ -15,7 +15,6 @@ var Lib = require('../../lib'); var Axes = require('../../plots/cartesian/axes'); var arraysToCalcdata = require('../bar/arrays_to_calcdata'); -var calcSelection = require('../scatter/calc_selection'); var binFunctions = require('./bin_functions'); var normFunctions = require('./norm_functions'); var doAvg = require('./average'); @@ -23,7 +22,6 @@ var cleanBins = require('./clean_bins'); var oneMonth = require('../../constants/numerical').ONEAVGMONTH; var getBinSpanLabelRound = require('./bin_label_vals'); - module.exports = function calc(gd, trace) { // ignore as much processing as possible (and including in autorange) if bar is not visible if(trace.visible !== true) return; @@ -512,3 +510,19 @@ function cdf(size, direction, currentBin) { } } } + +function calcSelection(cd, trace) { + if(Array.isArray(trace.selectedpoints)) { + var ptNumber2cdIndex = {}; + + // make histogram-specific pt-number-to-cd-index map object + for(var i = 0; i < cd.length; i++) { + var pts = cd[i].pts || []; + for(var j = 0; j < pts.length; j++) { + ptNumber2cdIndex[pts[j]] = i; + } + } + + Lib.tagSelected(cd, trace, ptNumber2cdIndex); + } +} diff --git a/src/traces/scatter/calc_selection.js b/src/traces/scatter/calc_selection.js index ef821498b3b..72279ee31eb 100644 --- a/src/traces/scatter/calc_selection.js +++ b/src/traces/scatter/calc_selection.js @@ -8,20 +8,12 @@ 'use strict'; -var isNumeric = require('fast-isnumeric'); +var Lib = require('../../lib'); -module.exports = function calcSelection(cd, trace) { - var selectedpoints = trace.selectedpoints; - - // TODO ids vs points?? +// TODO ids vs points?? - if(Array.isArray(selectedpoints)) { - for(var i = 0; i < selectedpoints.length; i++) { - var ptNumber = selectedpoints[i]; - - if(isNumeric(ptNumber)) { - cd[+ptNumber].selected = 1; - } - } +module.exports = function calcSelection(cd, trace) { + if(Array.isArray(trace.selectedpoints)) { + Lib.tagSelected(cd, trace); } }; From 9b79b3faa758f69edc36f1f2b12342bd9932fc6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Etienne=20T=C3=A9treault-Pinard?= Date: Wed, 15 Nov 2017 15:06:05 -0500 Subject: [PATCH 06/11] add support for selection of aggregations - similar to pointNumber and pointNumbers, make the distinction between 1-to-1 input to selection/hovered pt and many-to-1 maps like for histogram traces and aggregation transforms. --- src/components/fx/helpers.js | 11 +++++++++++ src/plots/cartesian/select.js | 9 +++++++-- src/traces/histogram/event_data.js | 13 +++++++++++++ 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/components/fx/helpers.js b/src/components/fx/helpers.js index bc33eb1bef5..4474c709613 100644 --- a/src/components/fx/helpers.js +++ b/src/components/fx/helpers.js @@ -111,6 +111,17 @@ exports.makeEventData = function makeEventData(pt, trace, cd) { pointNumber: pointNumber }; + if(trace._indexToPoints) { + var pointIndices = trace._indexToPoints[pointNumber]; + + if(pointIndices.length === 1) { + out.pointIndex = pointIndices[0]; + } else { + out.pointIndices = pointIndices; + } + } else { + out.pointIndex = pointNumber; + } if(trace._module.eventData) { out = trace._module.eventData(out, pt, trace, cd, pointNumber); diff --git a/src/plots/cartesian/select.js b/src/plots/cartesian/select.js index 353ba900735..be7226e5590 100644 --- a/src/plots/cartesian/select.js +++ b/src/plots/cartesian/select.js @@ -308,8 +308,13 @@ function updateSelectedState(gd, searchTraces, eventData) { var data = pt.data; var fullData = pt.fullData; - data.selectedpoints.push(pt.pointIndex); - fullData.selectedpoints.push(pt.pointIndex); + if(pt.pointIndices) { + data.selectedpoints = data.selectedpoints.concat(pt.pointIndices); + fullData.selectedpoints = fullData.selectedpoints.concat(pt.pointIndices); + } else { + data.selectedpoints.push(pt.pointIndex); + fullData.selectedpoints.push(pt.pointIndex); + } } } else { diff --git a/src/traces/histogram/event_data.js b/src/traces/histogram/event_data.js index 7a963336aee..c863695ac63 100644 --- a/src/traces/histogram/event_data.js +++ b/src/traces/histogram/event_data.js @@ -25,6 +25,19 @@ module.exports = function eventData(out, pt, trace, cd, pointNumber) { out.pointNumbers = pts; out.binNumber = out.pointNumber; delete out.pointNumber; + delete out.pointIndex; + + var pointIndices; + if(trace._indexToPoints) { + pointIndices = []; + for(var i = 0; i < pts.length; i++) { + pointIndices = pointIndices.concat(trace._indexToPoints[pts[i]]); + } + } else { + pointIndices = pts; + } + + out.pointIndices = pointIndices; } return out; From 92102788e6dc0f95b10424205032798429f65296 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Etienne=20T=C3=A9treault-Pinard?= Date: Wed, 15 Nov 2017 15:06:16 -0500 Subject: [PATCH 07/11] update point-selection baseline --- test/image/baselines/point-selection.png | Bin 28115 -> 28216 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/test/image/baselines/point-selection.png b/test/image/baselines/point-selection.png index f161239eaae9d3dad05e8bd0268e3baf20381e51..396cf75cad1016e97425ee42043ae866d1d7ae31 100644 GIT binary patch literal 28216 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�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 Date: Wed, 15 Nov 2017 15:06:36 -0500 Subject: [PATCH 08/11] update event-data jasmine tests (mostly adding pointIndex) --- test/jasmine/tests/click_test.js | 10 ++++----- test/jasmine/tests/geo_test.js | 24 +++++++++++--------- test/jasmine/tests/mapbox_test.js | 6 ++--- test/jasmine/tests/scattermapbox_test.js | 8 +++---- test/jasmine/tests/select_test.js | 13 +++++++++++ test/jasmine/tests/ternary_test.js | 28 +++++++++--------------- 6 files changed, 49 insertions(+), 40 deletions(-) diff --git a/test/jasmine/tests/click_test.js b/test/jasmine/tests/click_test.js index deb54b228f4..0841b5ce895 100644 --- a/test/jasmine/tests/click_test.js +++ b/test/jasmine/tests/click_test.js @@ -86,7 +86,7 @@ describe('Test click interactions:', function() { var pt = futureData.points[0]; expect(Object.keys(pt)).toEqual([ - 'data', 'fullData', 'curveNumber', 'pointNumber', + 'data', 'fullData', 'curveNumber', 'pointNumber', 'pointIndex', 'x', 'y', 'xaxis', 'yaxis' ]); expect(pt.curveNumber).toEqual(0); @@ -128,7 +128,7 @@ describe('Test click interactions:', function() { var pt = futureData.points[0]; expect(Object.keys(pt)).toEqual([ - 'data', 'fullData', 'curveNumber', 'pointNumber', + 'data', 'fullData', 'curveNumber', 'pointNumber', 'pointIndex', 'x', 'y', 'xaxis', 'yaxis' ]); expect(pt.curveNumber).toEqual(0); @@ -208,7 +208,7 @@ describe('Test click interactions:', function() { var pt = futureData.points[0]; expect(Object.keys(pt)).toEqual([ - 'data', 'fullData', 'curveNumber', 'pointNumber', + 'data', 'fullData', 'curveNumber', 'pointNumber', 'pointIndex', 'x', 'y', 'xaxis', 'yaxis' ]); expect(pt.curveNumber).toEqual(0); @@ -239,7 +239,7 @@ describe('Test click interactions:', function() { var pt = futureData.points[0]; expect(Object.keys(pt)).toEqual([ - 'data', 'fullData', 'curveNumber', 'pointNumber', + 'data', 'fullData', 'curveNumber', 'pointNumber', 'pointIndex', 'x', 'y', 'xaxis', 'yaxis' ]); expect(pt.curveNumber).toEqual(0); @@ -274,7 +274,7 @@ describe('Test click interactions:', function() { var pt = futureData.points[0]; expect(Object.keys(pt)).toEqual([ - 'data', 'fullData', 'curveNumber', 'pointNumber', + 'data', 'fullData', 'curveNumber', 'pointNumber', 'pointIndex', 'x', 'y', 'xaxis', 'yaxis' ]); expect(pt.curveNumber).toEqual(0); diff --git a/test/jasmine/tests/geo_test.js b/test/jasmine/tests/geo_test.js index bca4661146e..51f8b1e901a 100644 --- a/test/jasmine/tests/geo_test.js +++ b/test/jasmine/tests/geo_test.js @@ -569,7 +569,7 @@ describe('Test geo interactions', function() { it('should contain the correct fields', function() { expect(Object.keys(ptData)).toEqual([ - 'data', 'fullData', 'curveNumber', 'pointNumber', + 'data', 'fullData', 'curveNumber', 'pointNumber', 'pointIndex', 'lon', 'lat', 'location', 'marker.size' ]); expect(cnt).toEqual(1); @@ -632,7 +632,7 @@ describe('Test geo interactions', function() { it('should contain the correct fields', function() { expect(Object.keys(ptData)).toEqual([ - 'data', 'fullData', 'curveNumber', 'pointNumber', + 'data', 'fullData', 'curveNumber', 'pointNumber', 'pointIndex', 'lon', 'lat', 'location', 'marker.size' ]); }); @@ -664,7 +664,7 @@ describe('Test geo interactions', function() { it('should contain the correct fields', function() { expect(Object.keys(ptData)).toEqual([ - 'data', 'fullData', 'curveNumber', 'pointNumber', + 'data', 'fullData', 'curveNumber', 'pointNumber', 'pointIndex', 'lon', 'lat', 'location', 'marker.size' ]); }); @@ -693,7 +693,7 @@ describe('Test geo interactions', function() { it('should contain the correct fields', function() { expect(Object.keys(ptData)).toEqual([ - 'data', 'fullData', 'curveNumber', 'pointNumber', + 'data', 'fullData', 'curveNumber', 'pointNumber', 'pointIndex', 'location', 'z' ]); }); @@ -721,7 +721,7 @@ describe('Test geo interactions', function() { it('should contain the correct fields', function() { expect(Object.keys(ptData)).toEqual([ - 'data', 'fullData', 'curveNumber', 'pointNumber', + 'data', 'fullData', 'curveNumber', 'pointNumber', 'pointIndex', 'location', 'z' ]); }); @@ -753,7 +753,7 @@ describe('Test geo interactions', function() { it('should contain the correct fields', function() { expect(Object.keys(ptData)).toEqual([ - 'data', 'fullData', 'curveNumber', 'pointNumber', + 'data', 'fullData', 'curveNumber', 'pointNumber', 'pointIndex', 'location', 'z' ]); }); @@ -1303,7 +1303,8 @@ describe('Test event property of interactions on a geo plot:', function() { evt = futureData.event; expect(Object.keys(pt)).toEqual([ - 'data', 'fullData', 'curveNumber', 'pointNumber', 'lon', 'lat', + 'data', 'fullData', 'curveNumber', 'pointNumber', 'pointIndex', + 'lon', 'lat', 'location', 'text', 'marker.size' ]); @@ -1351,7 +1352,8 @@ describe('Test event property of interactions on a geo plot:', function() { evt = futureData.event; expect(Object.keys(pt)).toEqual([ - 'data', 'fullData', 'curveNumber', 'pointNumber', 'lon', 'lat', + 'data', 'fullData', 'curveNumber', 'pointNumber', 'pointIndex', + 'lon', 'lat', 'location', 'text', 'marker.size' ]); @@ -1392,7 +1394,8 @@ describe('Test event property of interactions on a geo plot:', function() { evt = futureData.event; expect(Object.keys(pt)).toEqual([ - 'data', 'fullData', 'curveNumber', 'pointNumber', 'lon', 'lat', + 'data', 'fullData', 'curveNumber', 'pointNumber', 'pointIndex', + 'lon', 'lat', 'location', 'text', 'marker.size' ]); @@ -1428,7 +1431,8 @@ describe('Test event property of interactions on a geo plot:', function() { evt = futureData.event; expect(Object.keys(pt)).toEqual([ - 'data', 'fullData', 'curveNumber', 'pointNumber', 'lon', 'lat', + 'data', 'fullData', 'curveNumber', 'pointNumber', 'pointIndex', + 'lon', 'lat', 'location', 'text', 'marker.size' ]); diff --git a/test/jasmine/tests/mapbox_test.js b/test/jasmine/tests/mapbox_test.js index ae7ec7a9cf3..68590653f1f 100644 --- a/test/jasmine/tests/mapbox_test.js +++ b/test/jasmine/tests/mapbox_test.js @@ -768,7 +768,7 @@ describe('@noCI, mapbox plots', function() { return _mouseEvent('mousemove', pointPos, function() { expect(hoverData).not.toBe(undefined, 'firing on data points'); expect(Object.keys(hoverData)).toEqual([ - 'data', 'fullData', 'curveNumber', 'pointNumber', 'lon', 'lat' + 'data', 'fullData', 'curveNumber', 'pointNumber', 'pointIndex', 'lon', 'lat' ], 'returning the correct event data keys'); expect(hoverData.curveNumber).toEqual(0, 'returning the correct curve number'); expect(hoverData.pointNumber).toEqual(0, 'returning the correct point number'); @@ -778,7 +778,7 @@ describe('@noCI, mapbox plots', function() { return _mouseEvent('mousemove', blankPos, function() { expect(unhoverData).not.toBe(undefined, 'firing on data points'); expect(Object.keys(unhoverData)).toEqual([ - 'data', 'fullData', 'curveNumber', 'pointNumber', 'lon', 'lat' + 'data', 'fullData', 'curveNumber', 'pointNumber', 'pointIndex', 'lon', 'lat' ], 'returning the correct event data keys'); expect(unhoverData.curveNumber).toEqual(0, 'returning the correct curve number'); expect(unhoverData.pointNumber).toEqual(0, 'returning the correct point number'); @@ -859,7 +859,7 @@ describe('@noCI, mapbox plots', function() { return _click(pointPos, function() { expect(ptData).not.toBe(undefined, 'firing on data points'); expect(Object.keys(ptData)).toEqual([ - 'data', 'fullData', 'curveNumber', 'pointNumber', 'lon', 'lat' + 'data', 'fullData', 'curveNumber', 'pointNumber', 'pointIndex', 'lon', 'lat' ], 'returning the correct event data keys'); expect(ptData.curveNumber).toEqual(0, 'returning the correct curve number'); expect(ptData.pointNumber).toEqual(0, 'returning the correct point number'); diff --git a/test/jasmine/tests/scattermapbox_test.js b/test/jasmine/tests/scattermapbox_test.js index f4efc19421b..729b4cd6abc 100644 --- a/test/jasmine/tests/scattermapbox_test.js +++ b/test/jasmine/tests/scattermapbox_test.js @@ -697,7 +697,7 @@ describe('@noCI Test plotly events on a scattermapbox plot:', function() { evt = futureData.event; expect(Object.keys(pt)).toEqual([ - 'data', 'fullData', 'curveNumber', 'pointNumber', 'lon', 'lat' + 'data', 'fullData', 'curveNumber', 'pointNumber', 'pointIndex', 'lon', 'lat' ]); expect(pt.curveNumber).toEqual(0, 'points[0].curveNumber'); @@ -741,7 +741,7 @@ describe('@noCI Test plotly events on a scattermapbox plot:', function() { evt = futureData.event; expect(Object.keys(pt)).toEqual([ - 'data', 'fullData', 'curveNumber', 'pointNumber', 'lon', 'lat' + 'data', 'fullData', 'curveNumber', 'pointNumber', 'pointIndex', 'lon', 'lat' ]); expect(pt.curveNumber).toEqual(0, 'points[0].curveNumber'); @@ -778,7 +778,7 @@ describe('@noCI Test plotly events on a scattermapbox plot:', function() { evt = futureData.event; expect(Object.keys(pt)).toEqual([ - 'data', 'fullData', 'curveNumber', 'pointNumber', 'lon', 'lat' + 'data', 'fullData', 'curveNumber', 'pointNumber', 'pointIndex', 'lon', 'lat' ]); expect(pt.curveNumber).toEqual(0, 'points[0].curveNumber'); @@ -810,7 +810,7 @@ describe('@noCI Test plotly events on a scattermapbox plot:', function() { evt = futureData.event; expect(Object.keys(pt)).toEqual([ - 'data', 'fullData', 'curveNumber', 'pointNumber', 'lon', 'lat' + 'data', 'fullData', 'curveNumber', 'pointNumber', 'pointIndex', 'lon', 'lat' ]); expect(pt.curveNumber).toEqual(0, 'points[0].curveNumber'); diff --git a/test/jasmine/tests/select_test.js b/test/jasmine/tests/select_test.js index 196d99a6d74..31f26d2b408 100644 --- a/test/jasmine/tests/select_test.js +++ b/test/jasmine/tests/select_test.js @@ -162,6 +162,7 @@ describe('Test select box and lasso in general:', function() { assertEventData(selectedData.points, [{ curveNumber: 0, pointNumber: 0, + pointIndex: 0, x: 0.002, y: 16.25, id: 'id-0.002', @@ -169,6 +170,7 @@ describe('Test select box and lasso in general:', function() { }, { curveNumber: 0, pointNumber: 1, + pointIndex: 1, x: 0.004, y: 12.5, id: 'id-0.004', @@ -199,6 +201,7 @@ describe('Test select box and lasso in general:', function() { assertEventData(selectingData.points, [{ curveNumber: 0, pointNumber: 0, + pointIndex: 0, x: 0.002, y: 16.25, id: 'id-0.002', @@ -206,6 +209,7 @@ describe('Test select box and lasso in general:', function() { }, { curveNumber: 0, pointNumber: 1, + pointIndex: 1, x: 0.004, y: 12.5, id: 'id-0.004', @@ -225,6 +229,7 @@ describe('Test select box and lasso in general:', function() { assertEventData(selectingData.points, [{ curveNumber: 0, pointNumber: 0, + pointIndex: 0, x: 0.002, y: 16.25, id: 'id-0.002', @@ -232,6 +237,7 @@ describe('Test select box and lasso in general:', function() { }, { curveNumber: 0, pointNumber: 1, + pointIndex: 1, x: 0.004, y: 12.5, id: 'id-0.004', @@ -239,6 +245,7 @@ describe('Test select box and lasso in general:', function() { }, { curveNumber: 0, pointNumber: 4, + pointIndex: 4, x: 0.013, y: 6.875, id: 'id-0.013', @@ -252,6 +259,7 @@ describe('Test select box and lasso in general:', function() { assertEventData(selectingData.points, [{ curveNumber: 0, pointNumber: 0, + pointIndex: 0, x: 0.002, y: 16.25, id: 'id-0.002', @@ -259,6 +267,7 @@ describe('Test select box and lasso in general:', function() { }, { curveNumber: 0, pointNumber: 1, + pointIndex: 1, x: 0.004, y: 12.5, id: 'id-0.004', @@ -299,6 +308,7 @@ describe('Test select box and lasso in general:', function() { assertEventData(selectingData.points, [{ curveNumber: 0, pointNumber: 10, + pointIndex: 10, x: 0.099, y: 2.75 }], 'with the correct selecting points (1)'); @@ -307,6 +317,7 @@ describe('Test select box and lasso in general:', function() { assertEventData(selectedData.points, [{ curveNumber: 0, pointNumber: 10, + pointIndex: 10, x: 0.099, y: 2.75, }], 'with the correct selected points (2)'); @@ -374,6 +385,7 @@ describe('Test select box and lasso in general:', function() { assertEventData(selectingData.points, [{ curveNumber: 0, pointNumber: 10, + pointIndex: 10, x: 0.099, y: 2.75 }], 'with the correct selecting points (1)'); @@ -382,6 +394,7 @@ describe('Test select box and lasso in general:', function() { assertEventData(selectedData.points, [{ curveNumber: 0, pointNumber: 10, + pointIndex: 10, x: 0.099, y: 2.75, }], 'with the correct selected points (2)'); diff --git a/test/jasmine/tests/ternary_test.js b/test/jasmine/tests/ternary_test.js index 45a4ee75c86..780994cce37 100644 --- a/test/jasmine/tests/ternary_test.js +++ b/test/jasmine/tests/ternary_test.js @@ -184,8 +184,8 @@ describe('ternary plots', function() { mouseEvent('mousemove', pointPos[0], pointPos[1]); expect(hoverData).not.toBe(undefined, 'firing on data points'); expect(Object.keys(hoverData)).toEqual([ - 'data', 'fullData', 'curveNumber', 'pointNumber', - 'x', 'y', 'xaxis', 'yaxis', 'a', 'b', 'c' + 'data', 'fullData', 'curveNumber', 'pointNumber', 'pointIndex', + 'xaxis', 'yaxis', 'a', 'b', 'c' ], 'returning the correct event data keys'); expect(hoverData.curveNumber).toEqual(0, 'returning the correct curve number'); expect(hoverData.pointNumber).toEqual(0, 'returning the correct point number'); @@ -193,8 +193,8 @@ describe('ternary plots', function() { mouseEvent('mouseout', pointPos[0], pointPos[1]); expect(unhoverData).not.toBe(undefined, 'firing on data points'); expect(Object.keys(unhoverData)).toEqual([ - 'data', 'fullData', 'curveNumber', 'pointNumber', - 'x', 'y', 'xaxis', 'yaxis', 'a', 'b', 'c' + 'data', 'fullData', 'curveNumber', 'pointNumber', 'pointIndex', + 'xaxis', 'yaxis', 'a', 'b', 'c' ], 'returning the correct event data keys'); expect(unhoverData.curveNumber).toEqual(0, 'returning the correct curve number'); expect(unhoverData.pointNumber).toEqual(0, 'returning the correct point number'); @@ -216,8 +216,8 @@ describe('ternary plots', function() { click(pointPos[0], pointPos[1]); expect(ptData).not.toBe(undefined, 'firing on data points'); expect(Object.keys(ptData)).toEqual([ - 'data', 'fullData', 'curveNumber', 'pointNumber', - 'x', 'y', 'xaxis', 'yaxis', 'a', 'b', 'c' + 'data', 'fullData', 'curveNumber', 'pointNumber', 'pointIndex', + 'xaxis', 'yaxis', 'a', 'b', 'c' ], 'returning the correct event data keys'); expect(ptData.curveNumber).toEqual(0, 'returning the correct curve number'); expect(ptData.pointNumber).toEqual(0, 'returning the correct point number'); @@ -491,7 +491,7 @@ describe('Test event property of interactions on a ternary plot:', function() { evt = futureData.event; expect(Object.keys(pt)).toEqual([ - 'data', 'fullData', 'curveNumber', 'pointNumber', 'x', 'y', + 'data', 'fullData', 'curveNumber', 'pointNumber', 'pointIndex', 'xaxis', 'yaxis', 'a', 'b', 'c' ]); @@ -499,8 +499,6 @@ describe('Test event property of interactions on a ternary plot:', function() { expect(typeof pt.data).toEqual(typeof {}, 'points[0].data'); expect(typeof pt.fullData).toEqual(typeof {}, 'points[0].fullData'); expect(pt.pointNumber).toEqual(0, 'points[0].pointNumber'); - expect(pt.x).toEqual(undefined, 'points[0].x'); - expect(pt.y).toEqual(undefined, 'points[0].y'); expect(typeof pt.xaxis).toEqual(typeof {}, 'points[0].xaxis'); expect(typeof pt.yaxis).toEqual(typeof {}, 'points[0].yaxis'); expect(pt.a).toEqual(2, 'points[0].a'); @@ -541,7 +539,7 @@ describe('Test event property of interactions on a ternary plot:', function() { evt = futureData.event; expect(Object.keys(pt)).toEqual([ - 'data', 'fullData', 'curveNumber', 'pointNumber', 'x', 'y', + 'data', 'fullData', 'curveNumber', 'pointNumber', 'pointIndex', 'xaxis', 'yaxis', 'a', 'b', 'c' ]); @@ -549,8 +547,6 @@ describe('Test event property of interactions on a ternary plot:', function() { expect(typeof pt.data).toEqual(typeof {}, 'points[0].data'); expect(typeof pt.fullData).toEqual(typeof {}, 'points[0].fullData'); expect(pt.pointNumber).toEqual(0, 'points[0].pointNumber'); - expect(pt.x).toEqual(undefined, 'points[0].x'); - expect(pt.y).toEqual(undefined, 'points[0].y'); expect(typeof pt.xaxis).toEqual(typeof {}, 'points[0].xaxis'); expect(typeof pt.yaxis).toEqual(typeof {}, 'points[0].yaxis'); expect(pt.a).toEqual(2, 'points[0].a'); @@ -588,7 +584,7 @@ describe('Test event property of interactions on a ternary plot:', function() { yvals0 = futureData.yvals[0]; expect(Object.keys(pt)).toEqual([ - 'data', 'fullData', 'curveNumber', 'pointNumber', 'x', 'y', + 'data', 'fullData', 'curveNumber', 'pointNumber', 'pointIndex', 'xaxis', 'yaxis', 'a', 'b', 'c' ]); @@ -596,8 +592,6 @@ describe('Test event property of interactions on a ternary plot:', function() { expect(typeof pt.data).toEqual(typeof {}, 'points[0].data'); expect(typeof pt.fullData).toEqual(typeof {}, 'points[0].fullData'); expect(pt.pointNumber).toEqual(0, 'points[0].pointNumber'); - expect(pt.x).toEqual(undefined, 'points[0].x'); - expect(pt.y).toEqual(undefined, 'points[0].y'); expect(typeof pt.xaxis).toEqual(typeof {}, 'points[0].xaxis'); expect(typeof pt.yaxis).toEqual(typeof {}, 'points[0].yaxis'); expect(pt.a).toEqual(2, 'points[0].a'); @@ -634,7 +628,7 @@ describe('Test event property of interactions on a ternary plot:', function() { evt = futureData.event; expect(Object.keys(pt)).toEqual([ - 'data', 'fullData', 'curveNumber', 'pointNumber', 'x', 'y', + 'data', 'fullData', 'curveNumber', 'pointNumber', 'pointIndex', 'xaxis', 'yaxis', 'a', 'b', 'c' ]); @@ -642,8 +636,6 @@ describe('Test event property of interactions on a ternary plot:', function() { expect(typeof pt.data).toEqual(typeof {}, 'points[0].data'); expect(typeof pt.fullData).toEqual(typeof {}, 'points[0].fullData'); expect(pt.pointNumber).toEqual(0, 'points[0].pointNumber'); - expect(pt.x).toEqual(undefined, 'points[0].x'); - expect(pt.y).toEqual(undefined, 'points[0].y'); expect(typeof pt.xaxis).toEqual(typeof {}, 'points[0].xaxis'); expect(typeof pt.yaxis).toEqual(typeof {}, 'points[0].yaxis'); expect(pt.a).toEqual(2, 'points[0].a'); From 451778b2adf7e4584bb5d55406712da9ef85eb7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Etienne=20T=C3=A9treault-Pinard?= Date: Thu, 16 Nov 2017 13:19:37 -0500 Subject: [PATCH 09/11] speed up is-pt-index-valid check --- src/lib/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/index.js b/src/lib/index.js index 3a34443d06d..55ce226f1fb 100644 --- a/src/lib/index.js +++ b/src/lib/index.js @@ -495,7 +495,7 @@ lib.tagSelected = function(calcTrace, trace, ptNumber2cdIndex) { } function isPtIndexValid(v) { - return lib.validate(v, {valType: 'integer', min: 0}); + return isNumeric(v) && v >= 0 && v % 1 === 0; } function isCdIndexValid(v) { From c8d715b98e26e88c2e6fdbfa5b39f3fbb353fcba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Etienne=20T=C3=A9treault-Pinard?= Date: Thu, 16 Nov 2017 13:20:30 -0500 Subject: [PATCH 10/11] emit 'normalized' a/b/c coords in scatterternary event data - fixup ternary hover event data test accordingly. --- src/traces/scatterternary/event_data.js | 9 +-------- test/jasmine/tests/ternary_test.js | 24 ++++++++++++------------ 2 files changed, 13 insertions(+), 20 deletions(-) diff --git a/src/traces/scatterternary/event_data.js b/src/traces/scatterternary/event_data.js index 5ded7380d59..ffec801c0cb 100644 --- a/src/traces/scatterternary/event_data.js +++ b/src/traces/scatterternary/event_data.js @@ -15,14 +15,7 @@ module.exports = function eventData(out, pt, trace, cd, pointNumber) { if(cd[pointNumber]) { var cdi = cd[pointNumber]; - // N.B. These are the scale coordinates !!! - // - // On master, hover events get the non-scaled coordinates - // whereas selection events get the scaled version. - // Note also that the hover labels show the scaled version. - // - // What about the 'raw' input coordinates? - // Should we include them in parallel here or replace a/b/c with them? + // N.B. These are the normalized coordinates. out.a = cdi.a; out.b = cdi.b; out.c = cdi.c; diff --git a/test/jasmine/tests/ternary_test.js b/test/jasmine/tests/ternary_test.js index 780994cce37..5715e48499a 100644 --- a/test/jasmine/tests/ternary_test.js +++ b/test/jasmine/tests/ternary_test.js @@ -501,9 +501,9 @@ describe('Test event property of interactions on a ternary plot:', function() { expect(pt.pointNumber).toEqual(0, 'points[0].pointNumber'); expect(typeof pt.xaxis).toEqual(typeof {}, 'points[0].xaxis'); expect(typeof pt.yaxis).toEqual(typeof {}, 'points[0].yaxis'); - expect(pt.a).toEqual(2, 'points[0].a'); - expect(pt.b).toEqual(1, 'points[0].b'); - expect(pt.c).toEqual(1, 'points[0].c'); + expect(pt.a).toEqual(0.5, 'points[0].a'); + expect(pt.b).toEqual(0.25, 'points[0].b'); + expect(pt.c).toEqual(0.25, 'points[0].c'); expect(evt.clientX).toEqual(pointPos[0], 'event.clientX'); expect(evt.clientY).toEqual(pointPos[1], 'event.clientY'); @@ -549,9 +549,9 @@ describe('Test event property of interactions on a ternary plot:', function() { expect(pt.pointNumber).toEqual(0, 'points[0].pointNumber'); expect(typeof pt.xaxis).toEqual(typeof {}, 'points[0].xaxis'); expect(typeof pt.yaxis).toEqual(typeof {}, 'points[0].yaxis'); - expect(pt.a).toEqual(2, 'points[0].a'); - expect(pt.b).toEqual(1, 'points[0].b'); - expect(pt.c).toEqual(1, 'points[0].c'); + expect(pt.a).toEqual(0.5, 'points[0].a'); + expect(pt.b).toEqual(0.25, 'points[0].b'); + expect(pt.c).toEqual(0.25, 'points[0].c'); expect(evt.clientX).toEqual(pointPos[0], 'event.clientX'); expect(evt.clientY).toEqual(pointPos[1], 'event.clientY'); @@ -594,9 +594,9 @@ describe('Test event property of interactions on a ternary plot:', function() { expect(pt.pointNumber).toEqual(0, 'points[0].pointNumber'); expect(typeof pt.xaxis).toEqual(typeof {}, 'points[0].xaxis'); expect(typeof pt.yaxis).toEqual(typeof {}, 'points[0].yaxis'); - expect(pt.a).toEqual(2, 'points[0].a'); - expect(pt.b).toEqual(1, 'points[0].b'); - expect(pt.c).toEqual(1, 'points[0].c'); + expect(pt.a).toEqual(0.5, 'points[0].a'); + expect(pt.b).toEqual(0.25, 'points[0].b'); + expect(pt.c).toEqual(0.25, 'points[0].c'); expect(xaxes0).toEqual(pt.xaxis, 'xaxes[0]'); expect(xvals0).toEqual(-0.0016654247744483342, 'xaxes[0]'); @@ -638,9 +638,9 @@ describe('Test event property of interactions on a ternary plot:', function() { expect(pt.pointNumber).toEqual(0, 'points[0].pointNumber'); expect(typeof pt.xaxis).toEqual(typeof {}, 'points[0].xaxis'); expect(typeof pt.yaxis).toEqual(typeof {}, 'points[0].yaxis'); - expect(pt.a).toEqual(2, 'points[0].a'); - expect(pt.b).toEqual(1, 'points[0].b'); - expect(pt.c).toEqual(1, 'points[0].c'); + expect(pt.a).toEqual(0.5, 'points[0].a'); + expect(pt.b).toEqual(0.25, 'points[0].b'); + expect(pt.c).toEqual(0.25, 'points[0].c'); expect(evt.clientX).toEqual(pointPos[0], 'event.clientX'); expect(evt.clientY).toEqual(pointPos[1], 'event.clientY'); From 7543dc6a146d0438caa521036fa38cb220031b58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Etienne=20T=C3=A9treault-Pinard?= Date: Thu, 16 Nov 2017 13:39:10 -0500 Subject: [PATCH 11/11] move ptNumber2cdIndex computation to main binning loop --- src/traces/histogram/calc.js | 26 ++++++++------------------ test/jasmine/tests/select_test.js | 8 +++++--- 2 files changed, 13 insertions(+), 21 deletions(-) diff --git a/src/traces/histogram/calc.js b/src/traces/histogram/calc.js index 9722b0bf00d..c53587148da 100644 --- a/src/traces/histogram/calc.js +++ b/src/traces/histogram/calc.js @@ -112,11 +112,13 @@ module.exports = function calc(gd, trace) { }; } + // bin the data + // and make histogram-specific pt-number-to-cd-index map object var nMax = size.length; var uniqueValsPerBin = true; var leftGap = Infinity; var rightGap = Infinity; - // bin the data + var ptNumber2cdIndex = {}; for(i = 0; i < pos0.length; i++) { var posi = pos0[i]; n = Lib.findBin(posi, bins); @@ -126,6 +128,7 @@ module.exports = function calc(gd, trace) { uniqueValsPerBin = false; } inputPoints[n].push(i); + ptNumber2cdIndex[i] = n; leftGap = Math.min(leftGap, posi - binEdges[n]); rightGap = Math.min(rightGap, binEdges[n + 1] - posi); @@ -195,7 +198,10 @@ module.exports = function calc(gd, trace) { } arraysToCalcdata(cd, trace); - calcSelection(cd, trace); + + if(Array.isArray(trace.selectedpoints)) { + Lib.tagSelected(cd, trace, ptNumber2cdIndex); + } return cd; }; @@ -510,19 +516,3 @@ function cdf(size, direction, currentBin) { } } } - -function calcSelection(cd, trace) { - if(Array.isArray(trace.selectedpoints)) { - var ptNumber2cdIndex = {}; - - // make histogram-specific pt-number-to-cd-index map object - for(var i = 0; i < cd.length; i++) { - var pts = cd[i].pts || []; - for(var j = 0; j < pts.length; j++) { - ptNumber2cdIndex[pts[j]] = i; - } - } - - Lib.tagSelected(cd, trace, ptNumber2cdIndex); - } -} diff --git a/test/jasmine/tests/select_test.js b/test/jasmine/tests/select_test.js index 31f26d2b408..25b027410f8 100644 --- a/test/jasmine/tests/select_test.js +++ b/test/jasmine/tests/select_test.js @@ -554,6 +554,8 @@ describe('Test select box and lasso per trace:', function() { if(typeof e[j] === 'number') { expect(p[k]).toBeCloseTo(e[j], 1, msgFull); + } else if(Array.isArray(e[j])) { + expect(p[k]).toBeCloseToArray(e[j], 1, msgFull); } else { expect(p[k]).toBe(e[j], msgFull); } @@ -990,7 +992,7 @@ describe('Test select box and lasso per trace:', function() { }); it('should work for histogram traces', function(done) { - var assertPoints = makeAssertPoints(['curveNumber', 'x', 'y']); + var assertPoints = makeAssertPoints(['curveNumber', 'x', 'y', 'pointIndices']); var assertRanges = makeAssertRanges(); var assertLassoPoints = makeAssertLassoPoints(); @@ -1006,7 +1008,7 @@ describe('Test select box and lasso per trace:', function() { [[200, 200], [400, 200], [400, 350], [200, 350], [200, 200]], function() { assertPoints([ - [0, 1.8, 2], [1, 2.2, 1], [1, 3.2, 1] + [0, 1.8, 2, [3, 4]], [1, 2.2, 1, [1]], [1, 3.2, 1, [2]] ]); assertLassoPoints([ [1.66, 3.59, 3.59, 1.66, 1.66], @@ -1024,7 +1026,7 @@ describe('Test select box and lasso per trace:', function() { [[200, 200], [400, 350]], function() { assertPoints([ - [0, 1.8, 2], [1, 2.2, 1], [1, 3.2, 1] + [0, 1.8, 2, [3, 4]], [1, 2.2, 1, [1]], [1, 3.2, 1, [2]] ]); assertRanges([[1.66, 3.59], [0.69, 2.17]]); },