diff --git a/src/components/modebar/manage.js b/src/components/modebar/manage.js index d455fcf8856..255a30e001f 100644 --- a/src/components/modebar/manage.js +++ b/src/components/modebar/manage.js @@ -185,7 +185,6 @@ function areAllAxesFixed(fullLayout) { } // look for traces that support selection -// to be updated as we add more selectPoints handlers function isSelectable(fullData) { var selectable = false; @@ -194,7 +193,7 @@ function isSelectable(fullData) { var trace = fullData[i]; - if(!trace._module || !trace._module.selectPoints) continue; + if(!trace._module || !trace._module.selectable) continue; if(Registry.traceIs(trace, 'scatter-like')) { if(scatterSubTypes.hasMarkers(trace) || scatterSubTypes.hasText(trace)) { @@ -205,7 +204,7 @@ function isSelectable(fullData) { selectable = true; } } - // assume that in general if the trace module has selectPoints, + // assume that in general if the trace module has getPointsIn and toggleSelected, // then it's selectable. Scatter is an exception to this because it must // have markers or text, not just be a scatter type. else selectable = true; diff --git a/src/lib/index.js b/src/lib/index.js index 9df929ce3e4..3921d30cc48 100644 --- a/src/lib/index.js +++ b/src/lib/index.js @@ -72,6 +72,9 @@ lib.variance = statsModule.variance; lib.stdev = statsModule.stdev; lib.interp = statsModule.interp; +var setOpsModule = require('./set_operations'); +lib.difference = setOpsModule.difference; + var matrixModule = require('./matrix'); lib.init2dArray = matrixModule.init2dArray; lib.transposeRagged = matrixModule.transposeRagged; diff --git a/src/lib/set_operations.js b/src/lib/set_operations.js new file mode 100644 index 00000000000..131353527e7 --- /dev/null +++ b/src/lib/set_operations.js @@ -0,0 +1,43 @@ +/** +* Copyright 2012-2018, 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'; + + +/** + * Computes the set difference of two arrays. + * + * @param {array} a + * @param {array} b + * @returns out all elements of a that are not in b. + * If a is not an array, an empty array is returned. + * If b is not an array, a is returned. + */ +function difference(a, b) { + if(!Array.isArray(a)) return []; + if(!Array.isArray(b)) return a; + + var hash = {}; + var out = []; + var i; + + for(i = 0; i < b.length; i++) { + hash[b[i]] = 1; + } + + for(i = 0; i < a.length; i++) { + var ai = a[i]; + if(!hash[ai]) out.push(ai); + } + + return out; +} + +module.exports = { + difference: difference +}; diff --git a/src/plots/cartesian/dragbox.js b/src/plots/cartesian/dragbox.js index 8d66a4cb4a5..27fcc569643 100644 --- a/src/plots/cartesian/dragbox.js +++ b/src/plots/cartesian/dragbox.js @@ -30,6 +30,8 @@ var doTicksSingle = require('./axes').doTicksSingle; var getFromId = require('./axis_ids').getFromId; var prepSelect = require('./select').prepSelect; var clearSelect = require('./select').clearSelect; +var selectOnClick = require('./select').selectOnClick; + var scaleZoom = require('./scale_zoom'); var constants = require('./constants'); @@ -158,7 +160,7 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { // to pan (or to zoom if it already is pan) on shift if(e.shiftKey) { if(dragModeNow === 'pan') dragModeNow = 'zoom'; - else if(!isSelectOrLasso(dragModeNow)) dragModeNow = 'pan'; + else if(!isBoxOrLassoSelect(dragModeNow)) dragModeNow = 'pan'; } else if(e.ctrlKey) { dragModeNow = 'pan'; @@ -171,10 +173,11 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { if(dragModeNow === 'lasso') dragOptions.minDrag = 1; else dragOptions.minDrag = undefined; - if(isSelectOrLasso(dragModeNow)) { + if(isBoxOrLassoSelect(dragModeNow)) { dragOptions.xaxes = xaxes; dragOptions.yaxes = yaxes; // this attaches moveFn, clickFn, doneFn on dragOptions + // TODO Maybe rename the function to prepSelectOnDrag prepSelect(e, startX, startY, dragOptions, dragModeNow); } else { dragOptions.clickFn = clickFn; @@ -212,8 +215,10 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { if(numClicks === 2 && !singleEnd) doubleClick(); if(isMainDrag) { - Fx.click(gd, evt, plotinfo.id); + handleClickInMainDrag(gd, numClicks, evt, xaxes, yaxes, plotinfo.id); } + // Allow manual editing of range bounds through an input field + // TODO consider extracting that to a method for clarity else if(numClicks === 1 && singleEnd) { var ax = ns ? ya0 : xa0, end = (ns === 's' || ew === 'w') ? 0 : 1, @@ -620,6 +625,12 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { redrawObjs(gd._fullLayout.images || [], Registry.getComponentMethod('images', 'draw'), true); } + function handleClickInMainDrag(gd, numClicks, evt, xaxes, yaxes, subplot) { + // TODO differentiate based on `clickmode` attr here as soon as it is available + selectOnClick(gd, numClicks, evt, xaxes, yaxes); + Fx.click(gd, evt, subplot); + } + function doubleClick() { if(gd._transitioningWithDuration) return; @@ -1013,7 +1024,7 @@ function showDoubleClickNotifier(gd) { } } -function isSelectOrLasso(dragmode) { +function isBoxOrLassoSelect(dragmode) { return dragmode === 'lasso' || dragmode === 'select'; } diff --git a/src/plots/cartesian/select.js b/src/plots/cartesian/select.js index 498dfff60d2..6d147cfe921 100644 --- a/src/plots/cartesian/select.js +++ b/src/plots/cartesian/select.js @@ -15,6 +15,7 @@ var Registry = require('../../registry'); var Color = require('../../components/color'); var Fx = require('../../components/fx'); +var difference = require('../../lib/set_operations').difference; var polygon = require('../../lib/polygon'); var throttle = require('../../lib/throttle'); var makeEventData = require('../../components/fx/helpers').makeEventData; @@ -26,7 +27,6 @@ var MINSELECT = constants.MINSELECT; var filteredPolygon = polygon.filter; var polygonTester = polygon.tester; -var multipolygonTester = polygon.multitester; function getAxId(ax) { return ax._id; } @@ -45,13 +45,12 @@ function prepSelect(e, startX, startY, dragOptions, mode) { var path0 = 'M' + x0 + ',' + y0; var pw = dragOptions.xaxes[0]._length; var ph = dragOptions.yaxes[0]._length; - var xAxisIds = dragOptions.xaxes.map(getAxId); - var yAxisIds = dragOptions.yaxes.map(getAxId); var allAxes = dragOptions.xaxes.concat(dragOptions.yaxes); var subtract = e.altKey; - var filterPoly, testPoly, mergedPolygons, currentPolygon; - var i, cd, trace, searchInfo, eventData; + var filterPoly, mergedPolygons, currentPolygon; + var pointsInPolygon = []; + var i, searchInfo, eventData; var selectingOnSameSubplot = ( fullLayout._lastSelectedSubplot && @@ -71,7 +70,7 @@ function prepSelect(e, startX, startY, dragOptions, mode) { (!e.shiftKey && !e.altKey) || ((e.shiftKey || e.altKey) && !plotinfo.selection) ) { - // create new polygons, if shift mode or selecting across different subplots + // create new polygons, if not shift mode or selecting across different subplots plotinfo.selection = {}; plotinfo.selection.polygons = dragOptions.polygons = []; plotinfo.selection.mergedPolygons = dragOptions.mergedPolygons = []; @@ -106,52 +105,10 @@ function prepSelect(e, startX, startY, dragOptions, mode) { .attr('d', 'M0,0Z'); - // find the traces to search for selection points - var searchTraces = []; var throttleID = fullLayout._uid + constants.SELECTID; - var selection = []; - - for(i = 0; i < gd.calcdata.length; i++) { - cd = gd.calcdata[i]; - trace = cd[0].trace; - - if(trace.visible !== true || !trace._module || !trace._module.selectPoints) continue; - - if(dragOptions.subplot) { - if( - trace.subplot === dragOptions.subplot || - trace.geo === dragOptions.subplot - ) { - searchTraces.push({ - _module: trace._module, - cd: cd, - xaxis: dragOptions.xaxes[0], - yaxis: dragOptions.yaxes[0] - }); - } - } else if( - trace.type === 'splom' && - // FIXME: make sure we don't have more than single axis for splom - trace._xaxes[xAxisIds[0]] && trace._yaxes[yAxisIds[0]] - ) { - searchTraces.push({ - _module: trace._module, - cd: cd, - xaxis: dragOptions.xaxes[0], - yaxis: dragOptions.yaxes[0] - }); - } else { - if(xAxisIds.indexOf(trace.xaxis) === -1) continue; - if(yAxisIds.indexOf(trace.yaxis) === -1) continue; - searchTraces.push({ - _module: trace._module, - cd: cd, - xaxis: getFromId(gd, trace.xaxis), - yaxis: getFromId(gd, trace.yaxis) - }); - } - } + // find the traces to search for selection points + var searchTraces = determineSearchTraces(gd, dragOptions.xaxes, dragOptions.yaxes, dragOptions.subplot); function axValue(ax) { var index = (ax._id.charAt(0) === 'y') ? 1 : 0; @@ -256,11 +213,9 @@ function prepSelect(e, startX, startY, dragOptions, mode) { if(dragOptions.polygons && dragOptions.polygons.length) { mergedPolygons = mergePolygons(dragOptions.mergedPolygons, currentPolygon, subtract); currentPolygon.subtract = subtract; - testPoly = multipolygonTester(dragOptions.polygons.concat([currentPolygon])); } else { mergedPolygons = [currentPolygon]; - testPoly = polygonTester(currentPolygon); } // draw selection @@ -276,14 +231,27 @@ function prepSelect(e, startX, startY, dragOptions, mode) { throttleID, constants.SELECTDELAY, function() { - selection = []; + var selection = [], + retainSelection = shouldRetainSelection(e), + module, + searchInfo, + i; - var thisSelection, traceSelections = [], traceSelection; + var thisSelection, traceSelection; for(i = 0; i < searchTraces.length; i++) { searchInfo = searchTraces[i]; + module = searchInfo._module; + + if(!retainSelection) module.toggleSelected(searchInfo, false); - traceSelection = searchInfo._module.selectPoints(searchInfo, testPoly); - traceSelections.push(traceSelection); + var currentPolygonTester = polygonTester(currentPolygon); + var pointsInCurrentPolygon = module.getPointsIn(searchInfo, currentPolygonTester); + module.toggleSelected(searchInfo, !subtract, pointsInCurrentPolygon); + + var pointsNoLongerSelected = difference(pointsInPolygon[i], pointsInCurrentPolygon); + + traceSelection = module.toggleSelected(searchInfo, false, pointsNoLongerSelected); + pointsInPolygon[i] = pointsInCurrentPolygon; thisSelection = fillSelectionItem(traceSelection, searchInfo); @@ -308,18 +276,24 @@ function prepSelect(e, startX, startY, dragOptions, mode) { throttle.done(throttleID).then(function() { throttle.clear(throttleID); + if(numClicks === 2) { - // clear selection on doubleclick - outlines.remove(); for(i = 0; i < searchTraces.length; i++) { searchInfo = searchTraces[i]; - searchInfo._module.selectPoints(searchInfo, false); + searchInfo._module.toggleSelected(searchInfo, false); } + // clear visual boundaries of selection area if displayed + outlines.remove(); + updateSelectedState(gd, searchTraces); + gd.emit('plotly_deselect', null); } else { + // TODO What to do with the code below because we now have behavior for a single click + selectOnClick(gd, numClicks, evt, dragOptions.xaxes, dragOptions.yaxes, outlines, dragOptions.subplot); + // TODO: remove in v2 - this was probably never intended to work as it does, // but in case anyone depends on it we don't want to break it now. gd.emit('plotly_selected', undefined); @@ -349,11 +323,233 @@ function prepSelect(e, startX, startY, dragOptions, mode) { }; } +// Missing features +// ---------------- +// TODO handle clearing selection when no point is clicked (based on hoverData) +// TODO remove console.log statements +function selectOnClick(gd, numClicks, evt, xAxes, yAxes, outlines, subplot) { + var hoverData = gd._hoverdata; + var retainSelection = shouldRetainSelection(evt); + var searchTraces; + var searchInfo; + var trace; + var clearEntireSelection; + var clickedPts; + var clickedPt; + var shouldSelect; + var traceSelection; + var allSelectionItems; + var eventData; + var i; + var j; + + if(numClicks === 1 && isHoverDataSet(hoverData)) { + allSelectionItems = []; + + searchTraces = determineSearchTraces(gd, xAxes, yAxes, subplot); + clearEntireSelection = entireSelectionToBeCleared(searchTraces, hoverData); + + for(i = 0; i < searchTraces.length; i++) { + searchInfo = searchTraces[i]; + trace = searchInfo.cd[0].trace; + + // Clear old selection if needed + if(!retainSelection || clearEntireSelection) { + searchInfo._module.toggleSelected(searchInfo, false); + if(outlines) outlines.remove(); + + if(clearEntireSelection) continue; + } + + // Determine clicked points, + // call selection modification functions of the trace's module + // and collect the resulting set of selected points + clickedPts = clickedPtsFor(searchInfo, hoverData); + if(clickedPts.length > 0) { + for(j = 0; j < clickedPts.length; j++) { + clickedPt = clickedPts[j]; + var ptSelected = isPointSelected(trace, clickedPt); + shouldSelect = !ptSelected || (ptSelected && !clearEntireSelection && !retainSelection); + traceSelection = searchInfo._module.toggleSelected(searchInfo, shouldSelect, [clickedPt.pointNumber]); + } + } else { + // If current trace has no pts clicked, we at least call toggleSelected + // with an empty array to obtain currently selected points for this trace. + traceSelection = searchInfo._module.toggleSelected(searchInfo, true, []); + } + + // Merge this trace's selection with the other ones + // to prepare the grand selection state update + allSelectionItems = allSelectionItems.concat(fillSelectionItem(traceSelection, searchInfo)); + } + + // Grand selection state update needs to be done once for the entire plot + // console.log('allSelItems ' + allSelectionItems.map(asi => asi.pointNumber)); + if(clearEntireSelection) { + updateSelectedState(gd, searchTraces); + } else { + eventData = {points: allSelectionItems}; + updateSelectedState(gd, searchTraces, eventData); + } + } + + function isHoverDataSet(hoverData) { + return hoverData && + Array.isArray(hoverData) && + hoverData[0].hoverOnBox !== true; + } + + /** + * Function to determine the clicked points for the given searchInfo (trace) + * based on the passed hoverData. + * + * Function assumes the following about hoverData: + * - when hoverData has more than one element (e.g. box trace), + * if a point is hovered upon, the clicked point is the first + * element in the array. It is assumed that fx/hover.js and satellite + * modules are doing that correctly. + * - at the moment only one point at a time is considered to be selected + * upon one click. + * + * Function also encapsulates special knowledge about the slight + * inconsistencies in what hoverData can look like for different + * trace types. As hoverData will become more homogeneous, this + * logic will become cleaner. + * + * See https://github.com/plotly/plotly.js/issues/1852 for the + * respective discussion. + */ + function clickedPtsFor(searchInfo, hoverData) { + var clickedPts = []; + var hoverDatum; + + if(hoverData.length > 0) { + hoverDatum = hoverData[0]; + if(hoverDatum.fullData._expandedIndex === searchInfo.cd[0].trace._expandedIndex) { + // Special case for box (and violin) + if(hoverDatum.hoverOnBox === true) return clickedPts; + + // TODO hoverDatum not having a pointNumber but a binNumber seems to be an oddity of histogram only + // Not deleting .pointNumber in histogram/event_data.js would simplify code here and in addition + // would not break the hover event structure + // documented at https://plot.ly/javascript/hover-events/ + if(hoverDatum.pointNumber !== undefined) { + clickedPts.push({ + pointNumber: hoverDatum.pointNumber + }); + } else if(hoverDatum.binNumber !== undefined) { + clickedPts.push({ + pointNumber: hoverDatum.binNumber, + pointNumbers: hoverDatum.pointNumbers + }); + } + } + } + + return clickedPts; + } +} + +function determineSearchTraces(gd, xAxes, yAxes, subplot) { + var searchTraces = []; + var xAxisIds = xAxes.map(getAxId); + var yAxisIds = yAxes.map(getAxId); + var cd; + var trace; + var i; + + for(i = 0; i < gd.calcdata.length; i++) { + cd = gd.calcdata[i]; + trace = cd[0].trace; + + if(trace.visible !== true || !trace._module || !trace._module.selectable) continue; + + if(subplot && (trace.subplot === subplot || trace.geo === subplot)) { + searchTraces.push(createSearchInfo(trace._module, cd, xAxes[0], yAxes[0])); + } else if( + trace.type === 'splom' && + // FIXME: make sure we don't have more than single axis for splom + trace._xaxes[xAxisIds[0]] && trace._yaxes[yAxisIds[0]] + ) { + searchTraces.push(createSearchInfo(trace._module, cd, xAxes[0], yAxes[0])); + } else { + if(xAxisIds.indexOf(trace.xaxis) === -1) continue; + if(yAxisIds.indexOf(trace.yaxis) === -1) continue; + + searchTraces.push(createSearchInfo(trace._module, cd, + getFromId(gd, trace.xaxis), getFromId(gd, trace.yaxis))); + } + } + + return searchTraces; +} + +function createSearchInfo(module, calcData, xaxis, yaxis) { + return { + _module: module, + cd: calcData, + xaxis: xaxis, + yaxis: yaxis + }; +} + +function shouldRetainSelection(evt) { + return evt.shiftKey; +} + +// TODO Clean up +function entireSelectionToBeCleared(searchTraces, hoverData) { + var somePointsSelected = false; + for(var i = 0; i < searchTraces.length; i++) { + var searchInfo = searchTraces[i]; + var trace = searchInfo.cd[0].trace; + if(trace.selectedpoints && trace.selectedpoints.length > 0) { + somePointsSelected = true; + var selectedPtsCopy = trace.selectedpoints.slice(); + + for(var j = 0; j < hoverData.length; j++) { + var hoverDatum = hoverData[j]; + if(hoverDatum.fullData._expandedIndex === trace._expandedIndex) { + selectedPtsCopy = difference(selectedPtsCopy, hoverDatum.pointNumbers || [hoverDatum.pointNumber]); + } + } + + if(selectedPtsCopy.length > 0) { + return false; + } + } + } + return somePointsSelected ? true : false; +} + +function isPointSelected(trace, point) { + if(!trace.selectedpoints && !Array.isArray(trace.selectedpoints)) return false; + if(point.pointNumbers) { + for(var i = 0; i < point.pointNumbers.length; i++) { + if(trace.selectedpoints.indexOf(point.pointNumbers[i]) < 0) return false; + } + return true; + } + return trace.selectedpoints.indexOf(point.pointNumber) > -1; +} + +/** + * Updates the selection state properties of the passed traces + * and initiates proper selection styling. + * + * If no eventData is passed, the selection state is cleared + * for the traces passed. + * + * @param gd + * @param searchTraces + * @param eventData + */ function updateSelectedState(gd, searchTraces, eventData) { var i, j, searchInfo, trace; if(eventData) { - var pts = eventData.points || []; + // var pts = eventData.points || []; TODO remove eventually + var pts = eventData.points; for(i = 0; i < searchTraces.length; i++) { trace = searchTraces[i].cd[0].trace; @@ -471,5 +667,6 @@ function clearSelect(zoomlayer) { module.exports = { prepSelect: prepSelect, - clearSelect: clearSelect + clearSelect: clearSelect, + selectOnClick: selectOnClick }; diff --git a/src/plots/mapbox/mapbox.js b/src/plots/mapbox/mapbox.js index c5332d05fd8..44fdbc250a7 100644 --- a/src/plots/mapbox/mapbox.js +++ b/src/plots/mapbox/mapbox.js @@ -15,6 +15,7 @@ var Fx = require('../../components/fx'); var Lib = require('../../lib'); var dragElement = require('../../components/dragelement'); var prepSelect = require('../cartesian/select').prepSelect; +var selectOnClick = require('../cartesian/select').selectOnClick; var constants = require('./constants'); var layoutAttributes = require('./layout_attributes'); var createMapboxLayer = require('./layers'); @@ -176,15 +177,6 @@ proto.createMap = function(calcData, fullLayout, resolve, reject) { Fx.hover(gd, evt, self.id); }); - map.on('click', function(evt) { - // TODO: this does not support right-click. If we want to support it, we - // would likely need to change mapbox to use dragElement instead of straight - // mapbox event binding. Or perhaps better, make a simple wrapper with the - // right mousedown, mousemove, and mouseup handlers just for a left/right click - // pie would use this too. - Fx.click(gd, evt.originalEvent); - }); - function unhover() { Fx.loneUnhover(fullLayout._toppaper); } @@ -221,11 +213,17 @@ proto.createMap = function(calcData, fullLayout, resolve, reject) { gd.emit('plotly_relayout', evtData); } - // define clear select on map creation, to keep one ref per map, + // define event handlers on map creation, to keep one ref per map, // so that map.on / map.off in updateFx works as expected self.clearSelect = function() { gd._fullLayout._zoomlayer.selectAll('.select-outline').remove(); }; + + self.onClickInPan = function(evt) { + // TODO Add condition evaluating clickmode attr when it has been introduced + selectOnClick(gd, 1, evt.originalEvent, [self.xaxis], [self.yaxis], undefined, self.id); + Fx.click(gd, evt.originalEvent); + }; }; proto.updateMap = function(calcData, fullLayout, resolve, reject) { @@ -390,6 +388,7 @@ proto.updateFx = function(fullLayout) { element: self.div, gd: gd, plotinfo: { + id: self.id, xaxis: self.xaxis, yaxis: self.yaxis, fillRangeItems: fillRangeItems @@ -404,10 +403,19 @@ proto.updateFx = function(fullLayout) { }; dragElement.init(dragOptions); + + map.off('click', self.onClickInPan); } else { map.dragPan.enable(); map.off('zoomstart', self.clearSelect); self.div.onmousedown = null; + + // TODO: this does not support right-click. If we want to support it, we + // would likely need to change mapbox to use dragElement instead of straight + // mapbox event binding. Or perhaps better, make a simple wrapper with the + // right mousedown, mousemove, and mouseup handlers just for a left/right click + // pie would use this too. + map.on('click', self.onClickInPan); } }; diff --git a/src/plots/plots.js b/src/plots/plots.js index 861493eea07..ec26d4dd143 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -1168,7 +1168,7 @@ plots.supplyTraceDefaults = function(traceIn, traceOut, colorIndex, layout, trac traceOut.visible = !!traceOut.visible; } - if(_module && _module.selectPoints) { + if(_module && _module.selectable) { coerce('selectedpoints'); } diff --git a/src/registry.js b/src/registry.js index 953e58b285e..3235ca7387a 100644 --- a/src/registry.js +++ b/src/registry.js @@ -241,6 +241,8 @@ function registerTraceModule(_module) { exports.allCategories[categoriesIn[i]] = true; } + _module.selectable = _module.getPointsIn && _module.toggleSelected; + exports.modules[thisType] = { _module: _module, categories: categoryObj diff --git a/src/traces/bar/index.js b/src/traces/bar/index.js index 91836d265c6..ef83e390fdc 100644 --- a/src/traces/bar/index.js +++ b/src/traces/bar/index.js @@ -23,7 +23,8 @@ Bar.plot = require('./plot'); Bar.style = require('./style').style; Bar.styleOnSelect = require('./style').styleOnSelect; Bar.hoverPoints = require('./hover'); -Bar.selectPoints = require('./select'); +Bar.getPointsIn = require('./select').getPointsIn; +Bar.toggleSelected = require('./select').toggleSelected; Bar.moduleType = 'trace'; Bar.name = 'bar'; diff --git a/src/traces/bar/select.js b/src/traces/bar/select.js index 04ede09356c..13bcd525ab5 100644 --- a/src/traces/bar/select.js +++ b/src/traces/bar/select.js @@ -8,34 +8,65 @@ 'use strict'; -module.exports = function selectPoints(searchInfo, polygon) { - var cd = searchInfo.cd; - var xa = searchInfo.xaxis; - var ya = searchInfo.yaxis; +// TODO DRY +function _togglePointSelectedState(searchInfo, pointIds, selected) { var selection = []; - var i; - if(polygon === false) { - // clear selection - for(i = 0; i < cd.length; i++) { - cd[i].selected = 0; - } - } else { - for(i = 0; i < cd.length; i++) { - var di = cd[i]; - - if(polygon.contains(di.ct)) { - selection.push({ - pointNumber: i, - x: xa.c2d(di.x), - y: ya.c2d(di.y) - }); - di.selected = 1; - } else { - di.selected = 0; - } + var calcData = searchInfo.cd, + xAxis = searchInfo.xaxis, + yAxis = searchInfo.yaxis; + + // TODO use foreach?! + // Mutate state + for(var j = 0; j < pointIds.length; j++) { + var pointId = pointIds[j]; + calcData[pointId].selected = selected ? 1 : 0; + } + + // Compute selection array from internal state + for(var i = 0; i < calcData.length; i++) { + if(calcData[i].selected === 1) { + selection.push(_newSelectionItem( + i, + xAxis.c2d(calcData[i].x), + yAxis.c2d(calcData[i].y))); } } return selection; +} + +// TODO DRY +function _newSelectionItem(pointNumber, xInData, yInData) { + return { + pointNumber: pointNumber, + x: xInData, + y: yInData + }; +} + +exports.getPointsIn = function(searchInfo, polygon) { + var pointsIn = []; + + var calcData = searchInfo.cd, + i; + + for(i = 0; i < calcData.length; i++) { + if(polygon.contains(calcData[i].ct)) { + pointsIn.push(i); + } + } + + return pointsIn; +}; + +exports.toggleSelected = function(searchInfo, selected, pointIds) { + if(!Array.isArray(pointIds)) { + // TODO Use arrayRange maybe + pointIds = []; + for(var i = 0; i < searchInfo.cd.length; i++) { + pointIds.push(i); + } + } + return _togglePointSelectedState(searchInfo, pointIds, selected); }; diff --git a/src/traces/box/event_data.js b/src/traces/box/event_data.js new file mode 100644 index 00000000000..bdc1cc88817 --- /dev/null +++ b/src/traces/box/event_data.js @@ -0,0 +1,26 @@ +/** +* Copyright 2012-2018, 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) { + if(pt.hoverOnBox) out.hoverOnBox = pt.hoverOnBox; + + // TODO Clean up + 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; + + return out; +}; diff --git a/src/traces/box/hover.js b/src/traces/box/hover.js index 79e24509360..b4729279e1c 100644 --- a/src/traces/box/hover.js +++ b/src/traces/box/hover.js @@ -169,6 +169,10 @@ function hoverOnBoxes(pointData, xval, yval, hovermode) { pointData2[vLetter + 'LabelVal'] = val; pointData2[vLetter + 'Label'] = (t.labels ? t.labels[attr] + ' ' : '') + Axes.hoverLabelText(vAxis, val); + // Note: introduced to be able to distinguish a + // clicked point from a box during click-to-select + pointData2.hoverOnBox = true; + if(attr === 'mean' && ('sd' in di) && trace.boxmean === 'sd') { pointData2[vLetter + 'err'] = di.sd; } diff --git a/src/traces/box/index.js b/src/traces/box/index.js index 3ad049e1701..d0186722e01 100644 --- a/src/traces/box/index.js +++ b/src/traces/box/index.js @@ -20,7 +20,9 @@ Box.plot = require('./plot').plot; Box.style = require('./style').style; Box.styleOnSelect = require('./style').styleOnSelect; Box.hoverPoints = require('./hover').hoverPoints; -Box.selectPoints = require('./select'); +Box.eventData = require('./event_data'); +Box.getPointsIn = require('./select').getPointsIn; +Box.toggleSelected = require('./select').toggleSelected; Box.moduleType = 'trace'; Box.name = 'box'; diff --git a/src/traces/box/select.js b/src/traces/box/select.js index 9ec9ed03e3f..a753d5ef460 100644 --- a/src/traces/box/select.js +++ b/src/traces/box/select.js @@ -8,37 +8,57 @@ 'use strict'; -module.exports = function selectPoints(searchInfo, polygon) { +exports.getPointsIn = function(searchInfo, polygon) { + var pointsIn = []; var cd = searchInfo.cd; var xa = searchInfo.xaxis; var ya = searchInfo.yaxis; - var selection = []; - var i, j; + var i; + var j; + var pt; + var x; + var y; + + for(i = 0; i < cd.length; i++) { + for(j = 0; j < (cd[i].pts || []).length; j++) { + pt = cd[i].pts[j]; + x = xa.c2p(pt.x); + y = ya.c2p(pt.y); - if(polygon === false) { - for(i = 0; i < cd.length; i++) { - for(j = 0; j < (cd[i].pts || []).length; j++) { - // clear selection - cd[i].pts[j].selected = 0; + if(polygon.contains([x, y])) { + pointsIn.push(pt.i); } } - } else { - for(i = 0; i < cd.length; i++) { - for(j = 0; j < (cd[i].pts || []).length; j++) { - var pt = cd[i].pts[j]; - var x = xa.c2p(pt.x); - var y = ya.c2p(pt.y); - - if(polygon.contains([x, y])) { - selection.push({ - pointNumber: pt.i, - x: xa.c2d(pt.x), - y: ya.c2d(pt.y) - }); - pt.selected = 1; - } else { - pt.selected = 0; - } + } + + return pointsIn; +}; + +exports.toggleSelected = function(searchInfo, selected, pointIds) { + var selection = []; + var modifyAll = !Array.isArray(pointIds); + var cd = searchInfo.cd; + var xa = searchInfo.xaxis; + var ya = searchInfo.yaxis; + var pt; + var i; + var j; + + for(i = 0; i < cd.length; i++) { + for(j = 0; j < (cd[i].pts || []).length; j++) { + pt = cd[i].pts[j]; + + if(modifyAll) pt.selected = selected ? 1 : 0; + else if(pointIds.indexOf(pt.i) > -1) { + pt.selected = selected ? 1 : 0; + } + + if(pt.selected) { + selection.push({ + pointNumber: pt.i, + x: xa.c2d(pt.x), + y: ya.c2d(pt.y) + }); } } } diff --git a/src/traces/histogram/index.js b/src/traces/histogram/index.js index 50abc6f24b7..f6b923df6cc 100644 --- a/src/traces/histogram/index.js +++ b/src/traces/histogram/index.js @@ -37,7 +37,8 @@ Histogram.style = require('../bar/style').style; Histogram.styleOnSelect = require('../bar/style').styleOnSelect; Histogram.colorbar = require('../scatter/marker_colorbar'); Histogram.hoverPoints = require('./hover'); -Histogram.selectPoints = require('../bar/select'); +Histogram.getPointsIn = require('../bar/select').getPointsIn; +Histogram.toggleSelected = require('../bar/select').toggleSelected; Histogram.eventData = require('./event_data'); Histogram.moduleType = 'trace'; diff --git a/src/traces/scatter/index.js b/src/traces/scatter/index.js index 020bc06a511..4d365304745 100644 --- a/src/traces/scatter/index.js +++ b/src/traces/scatter/index.js @@ -27,7 +27,8 @@ Scatter.colorbar = require('./marker_colorbar'); Scatter.style = require('./style').style; Scatter.styleOnSelect = require('./style').styleOnSelect; Scatter.hoverPoints = require('./hover'); -Scatter.selectPoints = require('./select'); +Scatter.getPointsIn = require('./select').getPointsIn; +Scatter.toggleSelected = require('./select').toggleSelected; Scatter.animatable = true; Scatter.moduleType = 'trace'; diff --git a/src/traces/scatter/select.js b/src/traces/scatter/select.js index 5d10050b494..e0c82a3a624 100644 --- a/src/traces/scatter/select.js +++ b/src/traces/scatter/select.js @@ -11,43 +11,84 @@ var subtypes = require('./subtypes'); -module.exports = function selectPoints(searchInfo, polygon) { - var cd = searchInfo.cd, - xa = searchInfo.xaxis, - ya = searchInfo.yaxis, - selection = [], - trace = cd[0].trace, +function _togglePointSelectedState(searchInfo, pointIds, selected) { + var selection = []; + + var calcData = searchInfo.cd, + xAxis = searchInfo.xaxis, + yAxis = searchInfo.yaxis; + + // TODO use foreach?! + // Mutate state + for(var j = 0; j < pointIds.length; j++) { + var pointId = pointIds[j]; + calcData[pointId].selected = selected ? 1 : 0; + } + + // Compute selection array from internal state + for(var i = 0; i < calcData.length; i++) { + if(calcData[i].selected === 1) { + selection.push(_newSelectionItem( + i, + xAxis.c2d(calcData[i].x), + yAxis.c2d(calcData[i].y))); + } + } + + return selection; +} + +// TODO May be needed in other trace types as well, so may centralize somewhere +function _newSelectionItem(pointNumber, xInData, yInData) { + return { + pointNumber: pointNumber, + x: xInData, + y: yInData + }; +} + +exports.getPointsIn = function(searchInfo, polygon) { + var pointsIn = []; + + var calcData = searchInfo.cd, + trace = calcData[0].trace, + xAxis = searchInfo.xaxis, + yAxis = searchInfo.yaxis, i, - di, - x, - y; + x, y; - var hasOnlyLines = (!subtypes.hasMarkers(trace) && !subtypes.hasText(trace)); + var hasOnlyLines = !subtypes.hasMarkers(trace) && !subtypes.hasText(trace); if(hasOnlyLines) return []; - if(polygon === false) { // clear selection - for(i = 0; i < cd.length; i++) { - cd[i].selected = 0; + for(i = 0; i < calcData.length; i++) { + x = xAxis.c2p(calcData[i].x); + y = yAxis.c2p(calcData[i].y); + + if(polygon.contains([x, y])) { + pointsIn.push(i); } } - else { - for(i = 0; i < cd.length; i++) { - di = cd[i]; - x = xa.c2p(di.x); - y = ya.c2p(di.y); - - if(polygon.contains([x, y])) { - selection.push({ - pointNumber: i, - x: xa.c2d(di.x), - y: ya.c2d(di.y) - }); - di.selected = 1; - } else { - di.selected = 0; - } + + return pointsIn; +}; + +/** + * Update the selected flag of the given points. Omitting which points + * to modify will update all points of the passed trace. + * + * @param {object} searchInfo - info about trace to modify + * @param {boolean} selected - are these points to be selected (true) or deselected (false) + * @param {integer[]} pointIds - the points to modify - omit to modify all points + * in the trace. i.e. clearSelection is toggleSelection(searchInfo, false). + * + * @return {object[]} an array of all points selected after modification + */ +exports.toggleSelected = function(searchInfo, selected, pointIds) { + if(!Array.isArray(pointIds)) { + pointIds = []; + for(var i = 0; i < searchInfo.cd.length; i++) { + pointIds.push(i); } } - - return selection; + return _togglePointSelectedState(searchInfo, pointIds, selected); }; diff --git a/src/traces/scattercarpet/index.js b/src/traces/scattercarpet/index.js index eba9c0f0b66..e41a5969a59 100644 --- a/src/traces/scattercarpet/index.js +++ b/src/traces/scattercarpet/index.js @@ -18,7 +18,8 @@ ScatterCarpet.plot = require('./plot'); ScatterCarpet.style = require('../scatter/style').style; ScatterCarpet.styleOnSelect = require('../scatter/style').styleOnSelect; ScatterCarpet.hoverPoints = require('./hover'); -ScatterCarpet.selectPoints = require('../scatter/select'); +ScatterCarpet.getPointsIn = require('../scatter/select').getPointsIn; +ScatterCarpet.toggleSelected = require('../scatter/select').toggleSelected; ScatterCarpet.eventData = require('./event_data'); ScatterCarpet.moduleType = 'trace'; diff --git a/src/traces/scattergeo/index.js b/src/traces/scattergeo/index.js index 1f2976735c5..c30fc125605 100644 --- a/src/traces/scattergeo/index.js +++ b/src/traces/scattergeo/index.js @@ -20,7 +20,8 @@ ScatterGeo.style = require('./style'); ScatterGeo.styleOnSelect = require('../scatter/style').styleOnSelect; ScatterGeo.hoverPoints = require('./hover'); ScatterGeo.eventData = require('./event_data'); -ScatterGeo.selectPoints = require('./select'); +ScatterGeo.getPointsIn = require('./select').getPointsIn; +ScatterGeo.toggleSelected = require('./select').toggleSelected; ScatterGeo.moduleType = 'trace'; ScatterGeo.name = 'scattergeo'; diff --git a/src/traces/scattergeo/select.js b/src/traces/scattergeo/select.js index 4c11e6c3196..fee2ec62ae8 100644 --- a/src/traces/scattergeo/select.js +++ b/src/traces/scattergeo/select.js @@ -10,44 +10,70 @@ var subtypes = require('../scatter/subtypes'); var BADNUM = require('../../constants/numerical').BADNUM; +var arrayRange = require('array-range'); -module.exports = function selectPoints(searchInfo, polygon) { +exports.getPointsIn = function(searchInfo, polygon) { + var pointsIn = []; var cd = searchInfo.cd; + var trace = cd[0].trace; + var hasOnlyLines = (!subtypes.hasMarkers(trace) && !subtypes.hasText(trace)); var xa = searchInfo.xaxis; var ya = searchInfo.yaxis; - var selection = []; - var trace = cd[0].trace; - - var di, lonlat, x, y, i; + var di; + var lonlat; + var x; + var y; + var i; - var hasOnlyLines = (!subtypes.hasMarkers(trace) && !subtypes.hasText(trace)); if(hasOnlyLines) return []; - if(polygon === false) { - for(i = 0; i < cd.length; i++) { - cd[i].selected = 0; + for(i = 0; i < cd.length; i++) { + di = cd[i]; + lonlat = di.lonlat; + + // some projection types can't handle BADNUMs + if(lonlat[0] === BADNUM) continue; + + x = xa.c2p(lonlat); + y = ya.c2p(lonlat); + + if(polygon.contains([x, y])) { + pointsIn.push(i); } - } else { - for(i = 0; i < cd.length; i++) { - di = cd[i]; - lonlat = di.lonlat; + } - // some projection types can't handle BADNUMs - if(lonlat[0] === BADNUM) continue; - - x = xa.c2p(lonlat); - y = ya.c2p(lonlat); - - if(polygon.contains([x, y])) { - selection.push({ - pointNumber: i, - lon: lonlat[0], - lat: lonlat[1] - }); - di.selected = 1; - } else { - di.selected = 0; - } + return pointsIn; +}; + +exports.toggleSelected = function(searchInfo, selected, pointIds) { + var selection = []; + var cd = searchInfo.cd; + var modifyAll = !Array.isArray(pointIds); + var di; + var pointId; + var lonlat; + var i; + + if(modifyAll) { + pointIds = arrayRange(cd.length); + } + + // Mutate state + for(i = 0; i < pointIds.length; i++) { + pointId = pointIds[i]; + cd[pointId].selected = selected ? 1 : 0; + } + + // Compute selection array from internal state + for(i = 0; i < cd.length; i++) { + di = cd[i]; + if(di.selected === 1) { + lonlat = di.lonlat; + selection.push({ + pointNumber: i, + lon: lonlat[0], + lat: lonlat[1] + }); } } diff --git a/src/traces/scattergl/index.js b/src/traces/scattergl/index.js index e4cf251a984..c237b0ea5f6 100644 --- a/src/traces/scattergl/index.js +++ b/src/traces/scattergl/index.js @@ -522,7 +522,9 @@ function plot(gd, subplot, cdata) { scene.selectBatch = null; scene.unselectBatch = null; var dragmode = fullLayout.dragmode; - var selectMode = dragmode === 'lasso' || dragmode === 'select'; + // TODO get that from layout as soon as clickmode attribute has been added + var clickmode = 'click'; + var selectMode = (dragmode === 'lasso' || dragmode === 'select' || clickmode === 'select'); for(i = 0; i < cdata.length; i++) { var cd0 = cdata[i][0]; @@ -571,7 +573,6 @@ function plot(gd, subplot, cdata) { } } - if(selectMode) { // create select2d if(!scene.select2d) { @@ -814,49 +815,91 @@ function calcHover(pointData, x, y, trace) { return pointData; } +function getPointsIn(searchInfo, polygon) { + var pointsIn = []; -function selectPoints(searchInfo, polygon) { - var cd = searchInfo.cd; - var selection = []; - var trace = cd[0].trace; - var stash = cd[0].t; - var x = stash.x; - var y = stash.y; + var calcData = searchInfo.cd; + var trace = calcData[0].trace; + var stash = calcData[0].t; var scene = stash._scene; + var i; - if(!scene) return selection; + if(!scene) return []; var hasText = subTypes.hasText(trace); var hasMarkers = subTypes.hasMarkers(trace); var hasOnlyLines = !hasMarkers && !hasText; - if(trace.visible !== true || hasOnlyLines) return selection; + if(trace.visible !== true || hasOnlyLines) return []; - // degenerate polygon does not enable selection - // filter out points by visible scatter ones - var els = null; - var unels = null; - // FIXME: clearing selection does not work here - var i; if(polygon !== false && !polygon.degenerate) { - els = [], unels = []; for(i = 0; i < stash.count; i++) { if(polygon.contains([stash.xpx[i], stash.ypx[i]])) { - els.push(i); - selection.push({ - pointNumber: i, - x: x[i], - y: y[i] - }); - } - else { - unels.push(i); + pointsIn.push(i); } } + } + + return pointsIn; +} + +function toggleSelected(searchInfo, selected, pointIds) { + var clearSelection = selected === false && !Array.isArray(pointIds); + var selection = []; + var calcData = searchInfo.cd; + var stash = calcData[0].t; + var scene = stash._scene; + var trace = calcData[0].trace; + var hasText = subTypes.hasText(trace); + var hasMarkers = subTypes.hasMarkers(trace); + var oldEls; + var oldUnels; + var newEls; + var newUnels; + var i; + + if(!Array.isArray(pointIds)) { + pointIds = arrayRange(stash.count); + } + + // Mutate state + coerceSelectBatches(scene, stash, hasMarkers); + oldEls = scene.selectBatch[stash.index]; + oldUnels = scene.unselectBatch[stash.index]; + + if(selected) { + newEls = oldEls.concat(Lib.difference(pointIds, oldEls)); + newUnels = Lib.difference(oldUnels, pointIds); } else { - unels = arrayRange(stash.count); + newEls = Lib.difference(oldEls, pointIds); + newUnels = oldUnels.concat(Lib.difference(pointIds, oldUnels)); + } + + scene.selectBatch[stash.index] = clearSelection ? null : newEls; + scene.unselectBatch[stash.index] = clearSelection ? arrayRange(stash.count) : newUnels; + + // Compute and return selection array from internal state + for(i = 0; i < newEls.length; i++) { + selection.push({ + pointNumber: newEls[i], + x: stash.x[newEls[i]], + y: stash.y[newEls[i]] + }); + } + + if(hasText) { + styleTextSelection(calcData); } - // make sure selectBatch is created + // TODO remove eventually + // console.log('els : ' + JSON.stringify(scene.selectBatch[stash.index])); + // console.log('unels: ' + JSON.stringify(scene.unselectBatch[stash.index])); + // console.log('selection: ' + JSON.stringify(selection)); + return selection; +} + +function coerceSelectBatches(scene, stash, hasMarkers) { + var i; + if(!scene.selectBatch) { scene.selectBatch = []; scene.unselectBatch = []; @@ -868,21 +911,14 @@ function selectPoints(searchInfo, polygon) { scene.selectBatch[i] = []; scene.unselectBatch[i] = []; } + + scene.unselectBatch[stash.index] = arrayRange(stash.count); + // we should turn scatter2d into unselected once we have any points selected if(hasMarkers) { scene.scatter2d.update(scene.markerUnselectedOptions); } } - - scene.selectBatch[stash.index] = els; - scene.unselectBatch[stash.index] = unels; - - // update text options - if(hasText) { - styleTextSelection(cd); - } - - return selection; } function style(gd, cds) { @@ -905,8 +941,8 @@ function styleTextSelection(cd) { var stash = cd0.t; var scene = stash._scene; var index = stash.index; - var els = scene.selectBatch[index]; - var unels = scene.unselectBatch[index]; + var els = scene.selectBatch ? scene.selectBatch[index] : null; + var unels = scene.unselectBatch ? scene.unselectBatch[index] : null; var baseOpts = scene.textOptions[index]; var selOpts = scene.textSelectedOptions[index] || {}; var unselOpts = scene.textUnselectedOptions[index] || {}; @@ -950,7 +986,8 @@ module.exports = { plot: plot, hoverPoints: hoverPoints, style: style, - selectPoints: selectPoints, + getPointsIn: getPointsIn, + toggleSelected: toggleSelected, sceneOptions: sceneOptions, sceneUpdate: sceneUpdate, diff --git a/src/traces/scattermapbox/index.js b/src/traces/scattermapbox/index.js index ea58c0936b8..55f402b1fec 100644 --- a/src/traces/scattermapbox/index.js +++ b/src/traces/scattermapbox/index.js @@ -18,7 +18,8 @@ ScatterMapbox.calc = require('../scattergeo/calc'); ScatterMapbox.plot = require('./plot'); ScatterMapbox.hoverPoints = require('./hover'); ScatterMapbox.eventData = require('./event_data'); -ScatterMapbox.selectPoints = require('./select'); +ScatterMapbox.getPointsIn = require('./select').getPointsIn; +ScatterMapbox.toggleSelected = require('./select').toggleSelected; ScatterMapbox.style = function(_, cd) { if(cd) { diff --git a/src/traces/scattermapbox/select.js b/src/traces/scattermapbox/select.js index ade2ea5ceeb..fb842c2f0f5 100644 --- a/src/traces/scattermapbox/select.js +++ b/src/traces/scattermapbox/select.js @@ -11,43 +11,66 @@ var Lib = require('../../lib'); var subtypes = require('../scatter/subtypes'); var BADNUM = require('../../constants/numerical').BADNUM; +var arrayRange = require('array-range'); -module.exports = function selectPoints(searchInfo, polygon) { +exports.getPointsIn = function(searchInfo, polygon) { + var pointsIn = []; var cd = searchInfo.cd; var xa = searchInfo.xaxis; var ya = searchInfo.yaxis; - var selection = []; var trace = cd[0].trace; var i; if(!subtypes.hasMarkers(trace)) return []; - if(polygon === false) { - for(i = 0; i < cd.length; i++) { - cd[i].selected = 0; - } - } else { - for(i = 0; i < cd.length; i++) { - var di = cd[i]; - var lonlat = di.lonlat; - - if(lonlat[0] !== BADNUM) { - var lonlat2 = [Lib.wrap180(lonlat[0]), lonlat[1]]; - var xy = [xa.c2p(lonlat2), ya.c2p(lonlat2)]; - - if(polygon.contains(xy)) { - selection.push({ - pointNumber: i, - lon: lonlat[0], - lat: lonlat[1] - }); - di.selected = 1; - } else { - di.selected = 0; - } + for(i = 0; i < cd.length; i++) { + var di = cd[i]; + var lonlat = di.lonlat; + + if(lonlat[0] !== BADNUM) { + var lonlat2 = [Lib.wrap180(lonlat[0]), lonlat[1]]; + var xy = [xa.c2p(lonlat2), ya.c2p(lonlat2)]; + + if(polygon.contains(xy)) { + pointsIn.push(i); } } } + return pointsIn; +}; + +exports.toggleSelected = function(searchInfo, selected, pointIds) { + var selection = []; + var cd = searchInfo.cd; + var modifyAll = !Array.isArray(pointIds); + var di; + var pointId; + var lonlat; + var i; + + if(modifyAll) { + pointIds = arrayRange(cd.length); + } + + // Mutate state + for(i = 0; i < pointIds.length; i++) { + pointId = pointIds[i]; + cd[pointId].selected = selected ? 1 : 0; + } + + // Compute selection array from internal state + for(i = 0; i < cd.length; i++) { + di = cd[i]; + if(di.selected === 1) { + lonlat = di.lonlat; + selection.push({ + pointNumber: i, + lon: lonlat[0], + lat: lonlat[1] + }); + } + } + return selection; }; diff --git a/src/traces/violin/index.js b/src/traces/violin/index.js index 4885709ceea..8c7bb9a0c75 100644 --- a/src/traces/violin/index.js +++ b/src/traces/violin/index.js @@ -19,7 +19,8 @@ module.exports = { style: require('./style'), styleOnSelect: require('../scatter/style').styleOnSelect, hoverPoints: require('./hover'), - selectPoints: require('../box/select'), + getPointsIn: require('../box/select').getPointsIn, + toggleSelected: require('../box/select').toggleSelected, moduleType: 'trace', name: 'violin', diff --git a/test/jasmine/tests/modebar_test.js b/test/jasmine/tests/modebar_test.js index d0e447d21a4..4f2ca3a66c4 100644 --- a/test/jasmine/tests/modebar_test.js +++ b/test/jasmine/tests/modebar_test.js @@ -356,7 +356,7 @@ describe('ModeBar', function() { type: 'scatter', visible: true, mode: 'markers', - _module: {selectPoints: true} + _module: {selectable: true} }]; manageModeBar(gd); @@ -380,7 +380,7 @@ describe('ModeBar', function() { type: 'box', visible: true, boxpoints: 'all', - _module: {selectPoints: true} + _module: {selectable: true} }]; manageModeBar(gd); @@ -452,7 +452,7 @@ describe('ModeBar', function() { type: 'scattergeo', visible: true, mode: 'markers', - _module: {selectPoints: true} + _module: {selectable: true} }]; manageModeBar(gd); @@ -492,7 +492,7 @@ describe('ModeBar', function() { type: 'scatter', visible: true, mode: 'markers', - _module: {selectPoints: true} + _module: {selectable: true} }]; manageModeBar(gd); @@ -584,7 +584,7 @@ describe('ModeBar', function() { type: 'scatter', visible: true, mode: 'markers', - _module: {selectPoints: true} + _module: {selectable: true} }]; manageModeBar(gd); @@ -606,7 +606,7 @@ describe('ModeBar', function() { type: 'scatter', visible: true, mode: 'markers', - _module: {selectPoints: true} + _module: {selectable: true} }]; gd._fullLayout.xaxis = {fixedrange: false}; gd._fullLayout._basePlotModules = [{ name: 'cartesian' }, { name: 'pie' }]; @@ -662,7 +662,7 @@ describe('ModeBar', function() { type: 'scatterternary', visible: true, mode: 'markers', - _module: {selectPoints: true} + _module: {selectable: true} }]; gd._fullLayout._basePlotModules = [{ name: 'ternary' }]; diff --git a/test/jasmine/tests/select_test.js b/test/jasmine/tests/select_test.js index a801c36ddf8..98d433510a2 100644 --- a/test/jasmine/tests/select_test.js +++ b/test/jasmine/tests/select_test.js @@ -259,7 +259,7 @@ describe('@flaky Test select box and lasso in general:', function() { }) .then(function() { // sub selection - drag([[219, 143], [219, 183]], {altKey: true}); + drag([[219, 143], [219, 183]], {shiftKey: true, altKey: true}); }).then(function() { assertEventData(selectingData.points, [{ curveNumber: 0, @@ -635,6 +635,7 @@ describe('@flaky Test select box and lasso in general:', function() { fig.layout.xaxis.range = [2, 8]; fig.layout.yaxis.autorange = false; fig.layout.yaxis.range = [0, 3]; + fig.layout.hovermode = 'closest'; function _assert(msg, exp) { expect(gd.layout.xaxis.range)