From ec82529c17763c9964e34209f97f627864cf7bb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Tue, 2 Apr 2019 15:59:03 -0400 Subject: [PATCH 1/7] some linting in plot_api.js --- src/plot_api/plot_api.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index b62620c0a7e..eb888b37a6c 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -135,7 +135,9 @@ exports.plot = function(gd, data, layout, config) { gd.empty = false; } - if(!gd.layout || graphWasEmpty) gd.layout = helpers.cleanLayout(layout); + if(!gd.layout || graphWasEmpty) { + gd.layout = helpers.cleanLayout(layout); + } // if the user is trying to drag the axes, allow new data and layout // to come in but don't allow a replot. @@ -195,7 +197,7 @@ exports.plot = function(gd, data, layout, config) { if(gd._context.responsive) { if(!gd._responsiveChartHandler) { // Keep a reference to the resize handler to purge it down the road - gd._responsiveChartHandler = function() {Plots.resize(gd);}; + gd._responsiveChartHandler = function() { Plots.resize(gd); }; // Listen to window resize window.addEventListener('resize', gd._responsiveChartHandler); @@ -242,10 +244,10 @@ exports.plot = function(gd, data, layout, config) { return 'gl-canvas gl-canvas-' + d.key.replace('Layer', ''); }) .style({ - 'position': 'absolute', - 'top': 0, - 'left': 0, - 'overflow': 'visible', + position: 'absolute', + top: 0, + left: 0, + overflow: 'visible', 'pointer-events': 'none' }); } From 869a42f61e87db30a7c8701bd7eac3a2531a6b78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Tue, 2 Apr 2019 16:02:25 -0400 Subject: [PATCH 2/7] pass gd to clearSelect ... instead of `zoomlayer`, making it easier to reuse. --- src/plot_api/plot_api.js | 7 +------ src/plots/cartesian/dragbox.js | 2 +- src/plots/cartesian/select.js | 22 +++++++++++++--------- src/plots/polar/polar.js | 6 +++--- src/plots/ternary/ternary.js | 4 ++-- 5 files changed, 20 insertions(+), 21 deletions(-) diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index eb888b37a6c..da3470d1870 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -1994,13 +1994,8 @@ function addAxRangeSequence(seq, rangesAltered) { return Axes.draw(gd, 'redraw'); }; - var _clearSelect = function(gd) { - var zoomlayer = gd._fullLayout._zoomlayer; - if(zoomlayer) clearSelect(zoomlayer); - }; - seq.push( - _clearSelect, + clearSelect, subroutines.doAutoRangeAndConstraints, drawAxes, subroutines.drawData, diff --git a/src/plots/cartesian/dragbox.js b/src/plots/cartesian/dragbox.js index 6096ba389d9..b525ced75c3 100644 --- a/src/plots/cartesian/dragbox.js +++ b/src/plots/cartesian/dragbox.js @@ -222,7 +222,7 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { // clear selection polygon cache (if any) dragOptions.plotinfo.selection = false; // clear selection outlines - clearSelect(zoomlayer); + clearSelect(gd); } function clickFn(numClicks, evt) { diff --git a/src/plots/cartesian/select.js b/src/plots/cartesian/select.js index a051adbe9ad..073f5308f60 100644 --- a/src/plots/cartesian/select.js +++ b/src/plots/cartesian/select.js @@ -465,14 +465,14 @@ function multiTester(list) { function coerceSelectionsCache(evt, gd, dragOptions) { var fullLayout = gd._fullLayout; - var zoomLayer = fullLayout._zoomlayer; var plotinfo = dragOptions.plotinfo; var selectingOnSameSubplot = ( - fullLayout._lastSelectedSubplot && - fullLayout._lastSelectedSubplot === plotinfo.id + fullLayout._lastSelectedSubplot && + fullLayout._lastSelectedSubplot === plotinfo.id ); var hasModifierKey = evt.shiftKey || evt.altKey; + if(selectingOnSameSubplot && hasModifierKey && (plotinfo.selection && plotinfo.selection.selectionDefs) && !dragOptions.selectionDefs) { // take over selection definitions from prev mode, if any @@ -484,7 +484,7 @@ function coerceSelectionsCache(evt, gd, dragOptions) { // clear selection outline when selecting a different subplot if(!selectingOnSameSubplot) { - clearSelect(zoomLayer); + clearSelect(gd); fullLayout._lastSelectedSubplot = plotinfo.id; } } @@ -774,11 +774,15 @@ function fillSelectionItem(selection, searchInfo) { return selection; } -function clearSelect(zoomlayer) { - // until we get around to persistent selections, remove the outline - // here. The selection itself will be removed when the plot redraws - // at the end. - zoomlayer.selectAll('.select-outline').remove(); +// until we get around to persistent selections, remove the outline +// here. The selection itself will be removed when the plot redraws +// at the end. +function clearSelect(gd) { + var fullLayout = gd._fullLayout || {}; + var zoomlayer = fullLayout._zoomlayer; + if(zoomlayer) { + zoomlayer.selectAll('.select-outline').remove(); + } } module.exports = { diff --git a/src/plots/polar/polar.js b/src/plots/polar/polar.js index f37772d2086..a6f33e2da6c 100644 --- a/src/plots/polar/polar.js +++ b/src/plots/polar/polar.js @@ -795,7 +795,7 @@ proto.updateMainDrag = function(fullLayout) { zb = dragBox.makeZoombox(zoomlayer, lum, cx, cy, path0); zb.attr('fill-rule', 'evenodd'); corners = dragBox.makeCorners(zoomlayer, cx, cy); - clearSelect(zoomlayer); + clearSelect(gd); } // N.B. this sets scoped 'r0' and 'r1' @@ -1115,7 +1115,7 @@ proto.updateRadialDrag = function(fullLayout, polarLayout, rngIndex) { dragOpts.moveFn = moveFn; dragOpts.doneFn = doneFn; - clearSelect(fullLayout._zoomlayer); + clearSelect(gd); }; dragOpts.clampFn = function(dx, dy) { @@ -1263,7 +1263,7 @@ proto.updateAngularDrag = function(fullLayout) { dragOpts.moveFn = moveFn; dragOpts.doneFn = doneFn; - clearSelect(fullLayout._zoomlayer); + clearSelect(gd); }; // I don't what we should do in this case, skip we now diff --git a/src/plots/ternary/ternary.js b/src/plots/ternary/ternary.js index f9ca0688efb..43101b86354 100644 --- a/src/plots/ternary/ternary.js +++ b/src/plots/ternary/ternary.js @@ -526,7 +526,7 @@ proto.initInteractions = function() { dragOptions.clickFn = clickZoomPan; dragOptions.doneFn = dragDone; panPrep(); - clearSelect(zoomContainer); + clearSelect(gd); } else if(dragModeNow === 'select' || dragModeNow === 'lasso') { prepSelect(e, startX, startY, dragOptions, dragModeNow); @@ -598,7 +598,7 @@ proto.initInteractions = function() { }) .attr('d', 'M0,0Z'); - clearSelect(zoomContainer); + clearSelect(gd); } function getAFrac(x, y) { return 1 - (y / _this.h); } From d2bd9518b226b7a1b6a8bd8d64fd8cfb7cad39ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Tue, 2 Apr 2019 16:06:09 -0400 Subject: [PATCH 3/7] first cut Plots.redrag - setup in a similar way to Plots.rehover - stash drag dx/dy values on gd._dragdata, call drag handler `moveFn` after redraw - :hocho: gd._replotPending logic ! - :hocho: gd._dragging Promise.reject ! --- src/components/dragelement/index.js | 21 ++++++++++---------- src/plot_api/plot_api.js | 21 +++++--------------- src/plots/cartesian/dragbox.js | 16 +++++++++++++-- src/plots/plots.js | 8 +++++++- test/jasmine/tests/plot_promise_test.js | 26 ------------------------- test/jasmine/tests/plots_test.js | 6 +++--- 6 files changed, 40 insertions(+), 58 deletions(-) diff --git a/src/components/dragelement/index.js b/src/components/dragelement/index.js index faab912d9a7..43e97b237ef 100644 --- a/src/components/dragelement/index.js +++ b/src/components/dragelement/index.js @@ -13,7 +13,6 @@ var mouseOffset = require('mouse-event-offset'); var hasHover = require('has-hover'); var supportsPassive = require('has-passive-events'); -var Registry = require('../../registry'); var Lib = require('../../lib'); var constants = require('../../plots/cartesian/constants'); @@ -191,12 +190,21 @@ dragElement.init = function init(options) { dragElement.unhover(gd); } - if(gd._dragged && options.moveFn && !rightClick) options.moveFn(dx, dy); + if(gd._dragged && options.moveFn && !rightClick) { + gd._dragdata = { + element: element, + dx: dx, + dy: dy + }; + options.moveFn(dx, dy); + } return; } function onDone(e) { + delete gd._dragdata; + if(options.dragmode !== false) { e.preventDefault(); document.removeEventListener('mousemove', onMove); @@ -258,10 +266,8 @@ dragElement.init = function init(options) { } } - finishDrag(gd); - + gd._dragging = false; gd._dragged = false; - return; } }; @@ -286,11 +292,6 @@ function coverSlip() { dragElement.coverSlip = coverSlip; -function finishDrag(gd) { - gd._dragging = false; - if(gd._replotPending) Registry.call('plot', gd); -} - function pointerOffset(e) { return mouseOffset( e.changedTouches ? e.changedTouches[0] : e, diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index da3470d1870..449fdb259c8 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -139,18 +139,6 @@ exports.plot = function(gd, data, layout, config) { gd.layout = helpers.cleanLayout(layout); } - // if the user is trying to drag the axes, allow new data and layout - // to come in but don't allow a replot. - if(gd._dragging && !gd._transitioning) { - // signal to drag handler that after everything else is done - // we need to replot, because something has changed - gd._replotPending = true; - return Promise.reject(); - } else { - // we're going ahead with a replot now - gd._replotPending = false; - } - Plots.supplyDefaults(gd); var fullLayout = gd._fullLayout; @@ -386,6 +374,7 @@ exports.plot = function(gd, data, layout, config) { initInteractions, Plots.addLinks, Plots.rehover, + Plots.redrag, // TODO: doAutoMargin is only needed here for axis automargin, which // happens outside of marginPushers where all the other automargins are // calculated. Would be much better to separate margin calculations from @@ -1404,7 +1393,7 @@ function restyle(gd, astr, val, _traces) { seq.push(emitAfterPlot); } - seq.push(Plots.rehover); + seq.push(Plots.rehover, Plots.redrag); Queue.add(gd, restyle, [gd, specs.undoit, specs.traces], @@ -1913,7 +1902,7 @@ function relayout(gd, astr, val) { seq.push(emitAfterPlot); } - seq.push(Plots.rehover); + seq.push(Plots.rehover, Plots.redrag); Queue.add(gd, relayout, [gd, specs.undoit], @@ -2446,7 +2435,7 @@ function update(gd, traceUpdate, layoutUpdate, _traces) { seq.push(emitAfterPlot); } - seq.push(Plots.rehover); + seq.push(Plots.rehover, Plots.redrag); Queue.add(gd, update, [gd, restyleSpecs.undoit, relayoutSpecs.undoit, restyleSpecs.traces], @@ -2849,7 +2838,7 @@ exports.react = function(gd, data, layout, config) { seq.push(emitAfterPlot); } - seq.push(Plots.rehover); + seq.push(Plots.rehover, Plots.redrag); plotDone = Lib.syncOrAsync(seq, gd); if(!plotDone || !plotDone.then) plotDone = Promise.resolve(gd); diff --git a/src/plots/cartesian/dragbox.js b/src/plots/cartesian/dragbox.js index b525ced75c3..60aa7e856d9 100644 --- a/src/plots/cartesian/dragbox.js +++ b/src/plots/cartesian/dragbox.js @@ -6,7 +6,6 @@ * LICENSE file in the root directory of this source tree. */ - 'use strict'; var d3 = require('d3'); @@ -38,7 +37,6 @@ var constants = require('./constants'); var MINDRAG = constants.MINDRAG; var MINZOOM = constants.MINZOOM; - // flag for showing "doubleclick to zoom out" only at the beginning var SHOWZOOMOUTTIP = true; @@ -216,6 +214,20 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { } } } + + gd._fullLayout._redrag = function() { + var dragDataNow = gd._dragdata; + + if(dragDataNow && dragDataNow.element === dragger) { + var dragModeNow = gd._fullLayout.dragmode; + + if(!isSelectOrLasso(dragModeNow)) { + recomputeAxisLists(); + updateSubplots([0, 0, pw, ph]); + dragOptions.moveFn(dragDataNow.dx, dragDataNow.dy); + } + } + }; }; function clearAndResetSelect() { diff --git a/src/plots/plots.js b/src/plots/plots.js index 466bd81af14..2b3243e7b7b 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -1679,10 +1679,10 @@ plots.purge = function(gd) { // themselves, but may not if there was an error delete gd._dragging; delete gd._dragged; + delete gd._dragdata; delete gd._hoverdata; delete gd._snapshotInProgress; delete gd._editing; - delete gd._replotPending; delete gd._mouseDownTime; delete gd._legendMouseDownTime; @@ -2907,6 +2907,12 @@ plots.rehover = function(gd) { } }; +plots.redrag = function(gd) { + if(gd._fullLayout._redrag) { + gd._fullLayout._redrag(); + } +}; + plots.generalUpdatePerTraceModule = function(gd, subplot, subplotCalcData, subplotLayout) { var traceHashOld = subplot.traceHash; var traceHash = {}; diff --git a/test/jasmine/tests/plot_promise_test.js b/test/jasmine/tests/plot_promise_test.js index e493a1049df..53856770ba3 100644 --- a/test/jasmine/tests/plot_promise_test.js +++ b/test/jasmine/tests/plot_promise_test.js @@ -62,32 +62,6 @@ describe('Plotly.___ methods', function() { }); }); - describe('Plotly.plot promise', function() { - var gd; - var promise; - var promiseRejected = false; - - beforeEach(function(done) { - var data = [{ x: [1, 2, 3], y: [4, 5, 6] }]; - - gd = createGraphDiv(); - - gd._dragging = true; - - promise = Plotly.plot(gd, data, {}); - - promise.then(null, function() { - promiseRejected = true; - done(); - }); - }); - - - it('should reject the promise when graph is being dragged', function() { - expect(promiseRejected).toBe(true); - }); - }); - describe('Plotly.redraw promise', function() { var promise; var promiseGd; diff --git a/test/jasmine/tests/plots_test.js b/test/jasmine/tests/plots_test.js index 0b4b37ca925..95c74b6c214 100644 --- a/test/jasmine/tests/plots_test.js +++ b/test/jasmine/tests/plots_test.js @@ -405,10 +405,10 @@ describe('Test Plots', function() { // because _dragging and _dragged were not cleared by purge. gd._dragging = true; gd._dragged = true; + gd._dragdata = true; gd._hoverdata = true; gd._snapshotInProgress = true; gd._editing = true; - gd._replotPending = true; gd._mouseDownTime = true; gd._legendMouseDownTime = true; }); @@ -427,8 +427,8 @@ describe('Test Plots', function() { 'empty', 'fid', 'undoqueue', 'undonum', 'autoplay', 'changed', '_promises', '_redrawTimer', 'firstscatter', '_transitionData', '_transitioning', '_hmpixcount', '_hmlumcount', - '_dragging', '_dragged', '_hoverdata', '_snapshotInProgress', '_editing', - '_replotPending', '_mouseDownTime', '_legendMouseDownTime' + '_dragging', '_dragged', '_dragdata', '_hoverdata', '_snapshotInProgress', '_editing', + '_mouseDownTime', '_legendMouseDownTime' ]; Plots.purge(gd); From 52fe1d13c4df5cc37fc09f151fe30277a27947d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Tue, 2 Apr 2019 16:08:28 -0400 Subject: [PATCH 4/7] a fixes for redraw calls in `plotly_selecting` handlers --- src/plots/cartesian/dragbox.js | 2 ++ src/plots/cartesian/select.js | 3 ++- src/plots/plots.js | 15 ++++++++++----- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/plots/cartesian/dragbox.js b/src/plots/cartesian/dragbox.js index 60aa7e856d9..a63a5811fb5 100644 --- a/src/plots/cartesian/dragbox.js +++ b/src/plots/cartesian/dragbox.js @@ -226,6 +226,8 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { updateSubplots([0, 0, pw, ph]); dragOptions.moveFn(dragDataNow.dx, dragDataNow.dy); } + + // TODO should we try to "re-select" under select/lasso modes? } }; }; diff --git a/src/plots/cartesian/select.js b/src/plots/cartesian/select.js index 073f5308f60..60653648ad9 100644 --- a/src/plots/cartesian/select.js +++ b/src/plots/cartesian/select.js @@ -20,6 +20,7 @@ var throttle = require('../../lib/throttle'); var makeEventData = require('../../components/fx/helpers').makeEventData; var getFromId = require('./axis_ids').getFromId; var clearGlCanvases = require('../../lib/clear_gl_canvases'); + var redrawReglTraces = require('../../plot_api/subroutines').redrawReglTraces; var constants = require('./constants'); @@ -669,7 +670,7 @@ function updateSelectedState(gd, searchTraces, eventData) { // before anything else, update preGUI if necessary for(i = 0; i < searchTraces.length; i++) { var fullInputTrace = searchTraces[i].cd[0].trace._fullInput; - var tracePreGUI = gd._fullLayout._tracePreGUI[fullInputTrace.uid]; + var tracePreGUI = gd._fullLayout._tracePreGUI[fullInputTrace.uid] || {}; if(tracePreGUI.selectedpoints === undefined) { tracePreGUI.selectedpoints = fullInputTrace._input.selectedpoints || null; } diff --git a/src/plots/plots.js b/src/plots/plots.js index 2b3243e7b7b..626699d4b5d 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -19,7 +19,7 @@ var Lib = require('../lib'); var Color = require('../components/color'); var BADNUM = require('../constants/numerical').BADNUM; -var axisIDs = require('../plots/cartesian/axis_ids'); +var axisIDs = require('./cartesian/axis_ids'); var animationAttrs = require('./animation_attributes'); var frameAttrs = require('./frame_attributes'); @@ -476,6 +476,15 @@ plots.supplyDefaults = function(gd, opts) { // clean subplots and other artifacts from previous plot calls plots.cleanPlot(newFullData, newFullLayout, oldFullData, oldFullLayout); + // clear selection outline until we implement persistent selection, + // don't clear them though when drag handlers (e.g. listening to + // `plotly_selecting`) update the graph. + // we should try to come up with a better solution when implementing + // https://github.com/plotly/plotly.js/issues/1851 + if(oldFullLayout._zoomlayer && !gd._dragging) { + oldFullLayout._zoomlayer.selectAll('.select-outline').remove(); + } + // relink functions and _ attributes to promote consistency between plots relinkPrivateKeys(newFullLayout, oldFullLayout); @@ -779,10 +788,6 @@ plots.cleanPlot = function(newFullData, newFullLayout, oldFullData, oldFullLayou oldFullLayout._infolayer.select('.cb' + oldUid).remove(); } } - - if(oldFullLayout._zoomlayer) { - oldFullLayout._zoomlayer.selectAll('.select-outline').remove(); - } }; plots.linkSubplots = function(newFullData, newFullLayout, oldFullData, oldFullLayout) { From 8c04a943462aa37bb90f4ccd651af6e681f0fe4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Wed, 3 Apr 2019 11:07:01 -0400 Subject: [PATCH 5/7] rename x0/y0 -> xStart/yStart to avoid conflict for outer scope --- src/plots/cartesian/dragbox.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/plots/cartesian/dragbox.js b/src/plots/cartesian/dragbox.js index a63a5811fb5..a6106836d46 100644 --- a/src/plots/cartesian/dragbox.js +++ b/src/plots/cartesian/dragbox.js @@ -601,8 +601,8 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { else if(yActive === 's') dy = dz(yaxes, 0, -dy); else if(!yActive) dy = 0; - var x0 = (xActive === 'w') ? dx : 0; - var y0 = (yActive === 'n') ? dy : 0; + var xStart = (xActive === 'w') ? dx : 0; + var yStart = (yActive === 'n') ? dy : 0; if(links.isSubplotConstrained) { var i; @@ -614,7 +614,7 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { scaleZoom(xaxes[i], 1 - dy / ph); } dx = dy * pw / ph; - x0 = dx / 2; + xStart = dx / 2; } if(!yActive && xActive.length === 1) { for(i = 0; i < yaxes.length; i++) { @@ -622,13 +622,13 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { scaleZoom(yaxes[i], 1 - dx / pw); } dy = dx * ph / pw; - y0 = dy / 2; + yStart = dy / 2; } } updateMatchedAxRange('x'); updateMatchedAxRange('y'); - updateSubplots([x0, y0, pw - dx, ph - dy]); + updateSubplots([xStart, yStart, pw - dx, ph - dy]); ticksAndAnnotations(); } From 8fea15620773d35f58c3ee6d6db00898e75d514f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Wed, 3 Apr 2019 15:12:28 -0400 Subject: [PATCH 6/7] add _redrag tests - 1 extendTraces during drag case - 1 plotly_relayout callback - 1 plotly_selecting callback --- src/plots/cartesian/dragbox.js | 1 + test/jasmine/tests/cartesian_interact_test.js | 377 ++++++++++++++++++ 2 files changed, 378 insertions(+) diff --git a/src/plots/cartesian/dragbox.js b/src/plots/cartesian/dragbox.js index a6106836d46..25be88e3862 100644 --- a/src/plots/cartesian/dragbox.js +++ b/src/plots/cartesian/dragbox.js @@ -228,6 +228,7 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { } // TODO should we try to "re-select" under select/lasso modes? + // probably best to wait for https://github.com/plotly/plotly.js/issues/1851 } }; }; diff --git a/test/jasmine/tests/cartesian_interact_test.js b/test/jasmine/tests/cartesian_interact_test.js index a025a253c34..8a220b999cc 100644 --- a/test/jasmine/tests/cartesian_interact_test.js +++ b/test/jasmine/tests/cartesian_interact_test.js @@ -1397,6 +1397,383 @@ describe('axis zoom/pan and main plot zoom', function() { .then(done); }); }); + + describe('redrag behavior', function() { + function _assertZoombox(msg, exp) { + var gd3 = d3.select(gd); + var zb = gd3.select('g.zoomlayer').select('.zoombox-corners'); + + if(zb.size()) { + expect(zb.attr('d')).toBe(exp.zoombox, msg + '| zoombox path'); + } else { + expect(false).toBe(exp.zoombox, msg + '| no zoombox'); + } + } + + function _assertClipRect(msg, exp) { + var gd3 = d3.select(gd); + var uid = gd._fullLayout._uid; + var clipRect = gd3.select('#clip' + uid + 'xyplot > rect'); + var xy = Drawing.getTranslate(clipRect); + expect(xy.x).toBeCloseTo(exp.clipTranslate[0], 2, msg + '| clip rect translate.x'); + expect(xy.y).toBeCloseTo(exp.clipTranslate[1], 2, msg + '| clip rect translate.y'); + } + + it('should handle extendTraces redraws during drag interactions', function(done) { + var step = 500; + var interval; + var xrngPrev; + + function _assert(msg, exp) { + return function() { + var fullLayout = gd._fullLayout; + + expect(fullLayout.xaxis.range).toBeCloseToArray(exp.xrng === 'previous' ? + xrngPrev : + exp.xrng, 2, msg + '|xaxis range'); + expect(d3.select(gd).selectAll('.point').size()).toBe(exp.nodeCnt, msg + '|pt cnt'); + expect(Boolean(gd._dragdata)).toBe(exp.hasDragData, msg + '|has gd._dragdata?'); + _assertZoombox(msg, exp); + _assertClipRect(msg, exp); + + xrngPrev = fullLayout.xaxis.range.slice(); + }; + } + + Plotly.plot(gd, [{y: [1, 2, 1]}], {dragmode: 'zoom'}) + .then(_assert('base', { + nodeCnt: 3, + xrng: [-0.128, 2.128], + hasDragData: false, + zoombox: false, + clipTranslate: [0, 0] + })) + .then(function() { + interval = setInterval(function() { + Plotly.extendTraces(gd, { y: [[Math.random()]] }, [0]); + }, step); + }) + .then(delay(1.5 * step)) + .then(_assert('after 1st extendTraces trace call', { + nodeCnt: 4, + xrng: [-0.1927, 3.1927], + hasDragData: false, + zoombox: false, + clipTranslate: [0, 0] + })) + .then(function() { + var drag = makeDragFns('xy', 'nsew', 30, 0); + return drag.start() + .then(_assert('just after start of zoombox', { + nodeCnt: 4, + xrng: [-0.1927, 3.1927], + hasDragData: true, + zoombox: 'M269.5,114.5h-3v41h3ZM300.5,114.5h3v41h-3Z', + clipTranslate: [0, 0] + })) + .then(delay(step)) + .then(_assert('during zoombox drag', { + nodeCnt: 5, + xrng: [-0.257, 4.257], + hasDragData: true, + zoombox: 'M269.5,114.5h-3v41h3ZM300.5,114.5h3v41h-3Z', + clipTranslate: [0, 0] + })) + .then(drag.end); + }) + .then(_assert('just after zoombox drag', { + nodeCnt: 5, + xrng: [2, 2.2507], + hasDragData: false, + zoombox: false, + clipTranslate: [0, 0] + })) + .then(delay(step)) + .then(function() { + return Plotly.relayout(gd, { + dragmode: 'pan', + 'xaxis.autorange': true, + 'yaxis.autorange': true + }); + }) + .then(delay(step)) + .then(_assert('after extendTraces two more steps / back to autorange:true', { + nodeCnt: 7, + xrng: [-0.385, 6.385], + hasDragData: false, + zoombox: false, + clipTranslate: [0, 0] + })) + .then(function() { + var drag = makeDragFns('xy', 'nsew', 60, 0); + return drag.start() + .then(_assert('just after pan start', { + nodeCnt: 7, + xrng: [-1.137, 5.633], + hasDragData: true, + zoombox: false, + clipTranslate: [-60, 0] + })) + .then(delay(step)) + .then(_assert('during pan mousedown', { + nodeCnt: 8, + xrng: [-1.327, 6.572], + hasDragData: true, + zoombox: false, + clipTranslate: [-60, 0] + })) + .then(drag.end); + }) + .then(_assert('just after pan end', { + nodeCnt: 8, + // N.B same xrng as just before on dragend + xrng: 'previous', + hasDragData: false, + zoombox: false, + clipTranslate: [0, 0] + })) + .then(delay(step)) + .then(_assert('last extendTraces call', { + nodeCnt: 9, + // N.B. same range as previous assert + // as now that xaxis range is set + xrng: 'previous', + hasDragData: false, + zoombox: false, + clipTranslate: [0, 0] + })) + .catch(failTest) + .then(function() { clearInterval(interval); }) + .then(done); + }); + + it('should handle plotly_relayout callback during drag interactions', function(done) { + var step = 500; + var relayoutTracker = []; + var restyleTracker = []; + var zCnt = 0; + var xrngPrev; + var yrngPrev; + + function z() { + return [[1, 2, 3], [2, (zCnt++) * 5, 1], [3, 2, 1]]; + } + + function _assert(msg, exp) { + return function() { + var trace = gd._fullData[0]; + var fullLayout = gd._fullLayout; + + expect(fullLayout.xaxis.range).toBeCloseToArray(exp.xrng === 'previous' ? + xrngPrev : + exp.xrng, 2, msg + '|xaxis range'); + expect(fullLayout.yaxis.range).toBeCloseToArray(exp.yrng === 'previous' ? + yrngPrev : + exp.yrng, 2, msg + '|yaxis range'); + + expect(trace.zmax).toBe(exp.zmax, msg + '|zmax'); + expect(Boolean(gd._dragdata)).toBe(exp.hasDragData, msg + '|has gd._dragdata?'); + expect(relayoutTracker.length).toBe(exp.relayoutCnt, msg + '|relayout cnt'); + expect(restyleTracker.length).toBe(exp.restyleCnt, msg + '|restyle cnt'); + _assertZoombox(msg, exp); + _assertClipRect(msg, exp); + + xrngPrev = fullLayout.xaxis.range.slice(); + yrngPrev = fullLayout.yaxis.range.slice(); + }; + } + + Plotly.plot(gd, [{ type: 'heatmap', z: z() }], {dragmode: 'pan'}) + .then(function() { + // inspired by https://github.com/plotly/plotly.js/issues/2687 + gd.on('plotly_relayout', function(d) { + relayoutTracker.unshift(d); + setTimeout(function() { + Plotly.restyle(gd, 'z', [z()]); + }, step); + }); + gd.on('plotly_restyle', function(d) { + restyleTracker.unshift(d); + }); + }) + .then(_assert('base', { + zmax: 3, + xrng: [-0.5, 2.5], + yrng: [-0.5, 2.5], + relayoutCnt: 0, + restyleCnt: 0, + hasDragData: false, + zoombox: false, + clipTranslate: [0, 0] + })) + .then(doDrag('xy', 'nsew', 30, 30)) + .then(_assert('after drag / before update #1', { + zmax: 3, + xrng: [-0.6707, 2.329], + yrng: [-0.1666, 2.833], + relayoutCnt: 1, + restyleCnt: 0, + hasDragData: false, + zoombox: false, + clipTranslate: [0, 0] + })) + .then(delay(step + 10)) + .then(_assert('after update #1', { + zmax: 5, + xrng: [-0.6707, 2.329], + yrng: [-0.1666, 2.833], + relayoutCnt: 1, + restyleCnt: 1, + hasDragData: false, + zoombox: false, + clipTranslate: [0, 0] + })) + .then(doDrag('xy', 'nsew', 30, 30)) + .then(delay(step / 2)) + .then(function() { + var drag = makeDragFns('xy', 'nsew', 30, 30); + return drag.start() + .then(_assert('just after pan start', { + zmax: 5, + xrng: [-1.005, 1.994], + yrng: [0.5, 3.5], + relayoutCnt: 2, + restyleCnt: 1, + hasDragData: true, + zoombox: false, + clipTranslate: [-30, -30] + })) + .then(delay(step)) + .then(_assert('after update #2 / during pan mousedown', { + zmax: 10, + xrng: 'previous', + yrng: 'previous', + relayoutCnt: 2, + restyleCnt: 2, + hasDragData: true, + zoombox: false, + clipTranslate: [-30, -30] + })) + .then(drag.end); + }) + .then(_assert('after pan end', { + zmax: 10, + xrng: 'previous', + yrng: 'previous', + relayoutCnt: 3, + restyleCnt: 2, + hasDragData: false, + zoombox: false, + clipTranslate: [0, 0] + })) + .then(delay(step)) + .then(_assert('after update #3', { + zmax: 15, + xrng: 'previous', + yrng: 'previous', + relayoutCnt: 3, + restyleCnt: 3, + hasDragData: false, + zoombox: false, + clipTranslate: [0, 0] + })) + .catch(failTest) + .then(done); + }); + + it('should handle react calls in plotly_selecting callback', function(done) { + var selectingTracker = []; + var selectedTracker = []; + + function _assert(msg, exp) { + return function() { + var gd3 = d3.select(gd); + + expect(gd3.selectAll('.point').size()).toBe(exp.nodeCnt, msg + '|pt cnt'); + expect(Boolean(gd._dragdata)).toBe(exp.hasDragData, msg + '|has gd._dragdata?'); + expect(selectingTracker.length).toBe(exp.selectingCnt, msg + '| selecting cnt'); + expect(selectedTracker.length).toBe(exp.selectedCnt, msg + '| selected cnt'); + + var outline = d3.select('.zoomlayer > .select-outline'); + if(outline.size()) { + expect(outline.attr('d')).toBe(exp.selectOutline, msg + '| selection outline path'); + } else { + expect(false).toBe(exp.selectOutline, msg + '| no selection outline'); + } + }; + } + + var trace0 = {mode: 'markers', x: [1, 2, 3], y: [1, 2, 1], marker: {opacity: 0.5}}; + var trace1 = {mode: 'markers', x: [], y: [], marker: {size: 20}}; + + var layout = { + dragmode: 'select', + showlegend: false, + width: 400, + height: 400, + margin: {l: 0, r: 0, t: 0, b: 0} + }; + + Plotly.plot(gd, [trace0], layout) + .then(function() { + // inspired by https://github.com/plotly/plotly.js-crossfilter.js + gd.on('plotly_selecting', function(d) { + selectingTracker.unshift(d); + + if(d && d.points) { + trace1.x = d.points.map(function(p) { return trace0.x[p.pointNumber]; }); + trace1.y = d.points.map(function(p) { return trace0.y[p.pointNumber]; }); + } else { + trace1.x = []; + trace1.y = []; + } + + Plotly.react(gd, [trace0, trace1], layout); + }); + + gd.on('plotly_selected', function(d) { + selectedTracker.unshift(d); + Plotly.react(gd, [trace0], layout); + }); + }) + .then(_assert('base', { + nodeCnt: 3, + hasDragData: false, + selectingCnt: 0, + selectedCnt: 0, + selectOutline: false + })) + .then(function() { + var drag = makeDragFns('xy', 'nsew', 200, 200, 20, 20); + return drag.start() + .then(_assert('just after pan start', { + nodeCnt: 4, + hasDragData: true, + selectingCnt: 1, + selectedCnt: 0, + selectOutline: 'M20,20L20,220L220,220L220,20L20,20Z' + })) + .then(delay(100)) + .then(_assert('while holding on mouse', { + nodeCnt: 4, + hasDragData: true, + selectingCnt: 1, + selectedCnt: 0, + selectOutline: 'M20,20L20,220L220,220L220,20L20,20Z' + })) + .then(drag.end); + }) + .then(_assert('after drag', { + nodeCnt: 3, + hasDragData: false, + selectingCnt: 1, + selectedCnt: 1, + selectOutline: false + })) + .catch(failTest) + .then(done); + }); + }); }); describe('Event data:', function() { From 51b74b56d41aa0f0f2b4eaaf128485f269ab008d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Fri, 5 Apr 2019 11:13:00 -0400 Subject: [PATCH 7/7] lint for mojtaba --- src/components/dragelement/index.js | 22 +++++++--------------- src/plots/ternary/ternary.js | 6 +++--- 2 files changed, 10 insertions(+), 18 deletions(-) diff --git a/src/components/dragelement/index.js b/src/components/dragelement/index.js index 43e97b237ef..f34e2103453 100644 --- a/src/components/dragelement/index.js +++ b/src/components/dragelement/index.js @@ -6,15 +6,13 @@ * LICENSE file in the root directory of this source tree. */ - 'use strict'; var mouseOffset = require('mouse-event-offset'); var hasHover = require('has-hover'); var supportsPassive = require('has-passive-events'); -var Lib = require('../../lib'); - +var removeElement = require('../../lib').removeElement; var constants = require('../../plots/cartesian/constants'); var interactConstants = require('../../constants/interactions'); @@ -27,7 +25,6 @@ var unhover = require('./unhover'); dragElement.unhover = unhover.wrapped; dragElement.unhoverRaw = unhover.raw; - /** * Abstracts click & drag interactions * @@ -105,8 +102,7 @@ dragElement.init = function init(options) { if(!supportsPassive) { element.ontouchstart = onStart; - } - else { + } else { if(element._ontouchstart) { element.removeEventListener('touchstart', element._ontouchstart); } @@ -144,8 +140,7 @@ dragElement.init = function init(options) { if(newMouseDownTime - gd._mouseDownTime < DBLCLICKDELAY) { // in a click train numClicks += 1; - } - else { + } else { // new click train numClicks = 1; gd._mouseDownTime = newMouseDownTime; @@ -156,8 +151,7 @@ dragElement.init = function init(options) { if(hasHover && !rightClick) { dragCover = coverSlip(); dragCover.style.cursor = window.getComputedStyle(element).cursor; - } - else if(!hasHover) { + } else if(!hasHover) { // document acts as a dragcover for mobile, bc we can't create dragcover dynamically dragCover = document; cursor = window.getComputedStyle(document.documentElement).cursor; @@ -215,9 +209,8 @@ dragElement.init = function init(options) { document.removeEventListener('touchend', onDone); if(hasHover) { - Lib.removeElement(dragCover); - } - else if(cursor) { + removeElement(dragCover); + } else if(cursor) { dragCover.documentElement.style.cursor = cursor; cursor = null; } @@ -236,8 +229,7 @@ dragElement.init = function init(options) { if(gd._dragged) { if(options.doneFn) options.doneFn(); - } - else { + } else { if(options.clickFn) options.clickFn(numClicks, initialEvent); // If we haven't dragged, this should be a click. But because of the diff --git a/src/plots/ternary/ternary.js b/src/plots/ternary/ternary.js index 43101b86354..f0511d7dda5 100644 --- a/src/plots/ternary/ternary.js +++ b/src/plots/ternary/ternary.js @@ -493,7 +493,7 @@ proto.initInteractions = function() { var _this = this; var dragger = _this.layers.plotbg.select('path').node(); var gd = _this.graphDiv; - var zoomContainer = gd._fullLayout._zoomlayer; + var zoomLayer = gd._fullLayout._zoomlayer; // use plotbg for the main interactions var dragOptions = { @@ -578,7 +578,7 @@ proto.initInteractions = function() { path0 = 'M0,' + _this.h + 'L' + (_this.w / 2) + ', 0L' + _this.w + ',' + _this.h + 'Z'; dimmed = false; - zb = zoomContainer.append('path') + zb = zoomLayer.append('path') .attr('class', 'zoombox') .attr('transform', 'translate(' + _this.x0 + ', ' + _this.y0 + ')') .style({ @@ -587,7 +587,7 @@ proto.initInteractions = function() { }) .attr('d', path0); - corners = zoomContainer.append('path') + corners = zoomLayer.append('path') .attr('class', 'zoombox-corners') .attr('transform', 'translate(' + _this.x0 + ', ' + _this.y0 + ')') .style({