From f8203a361ffb94be13ad5ca2fc82b3a06813e37c Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Tue, 21 May 2019 13:01:25 -0400 Subject: [PATCH 01/10] plotly_relayouting for cartesian plots --- src/plots/cartesian/dragbox.js | 30 ++++--- test/jasmine/tests/cartesian_interact_test.js | 90 ++++++++++++++++++- 2 files changed, 107 insertions(+), 13 deletions(-) diff --git a/src/plots/cartesian/dragbox.js b/src/plots/cartesian/dragbox.js index 937a3ebc034..0960a543d60 100644 --- a/src/plots/cartesian/dragbox.js +++ b/src/plots/cartesian/dragbox.js @@ -85,7 +85,7 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { // graph-wide optimization flags var hasScatterGl, hasSplom, hasSVG; // collected changes to be made to the plot by relayout at the end - var updates; + var updates = {}; function recomputeAxisLists() { xa0 = plotinfo.xaxis; @@ -409,18 +409,12 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { gd._dragged = zoomDragged; updateZoombox(zb, corners, box, path0, dimmed, lum); + computeZoomUpdates(); + gd.emit('plotly_relayouting', updates); dimmed = true; } - function zoomDone() { - updates = {}; - - // more strict than dragged, which allows you to come back to where you started - // and still count as dragged - if(Math.min(box.h, box.w) < MINDRAG * 2) { - return removeZoombox(gd); - } - + function computeZoomUpdates() { // TODO: edit linked axes in zoomAxRanges and in dragTail if(zoomMode === 'xy' || zoomMode === 'x') { zoomAxRanges(xaxes, box.l / pw, box.r / pw, updates, links.xaxes); @@ -430,6 +424,18 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { zoomAxRanges(yaxes, (ph - box.b) / ph, (ph - box.t) / ph, updates, links.yaxes); updateMatchedAxRange('y', updates); } + } + + function zoomDone() { + updates = {}; + + // more strict than dragged, which allows you to come back to where you started + // and still count as dragged + if(Math.min(box.h, box.w) < MINDRAG * 2) { + return removeZoombox(gd); + } + + computeZoomUpdates(); removeZoombox(gd); dragTail(); @@ -515,6 +521,8 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { updateSubplots(scrollViewBox); ticksAndAnnotations(); + gd.emit('plotly_relayouting', updates); + // then replot after a delay to make sure // no more scrolling is coming redrawTimer = setTimeout(function() { @@ -552,6 +560,7 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { } updateSubplots([xActive ? -dx : 0, yActive ? -dy : 0, pw, ph]); ticksAndAnnotations(); + gd.emit('plotly_relayouting', updates); return; } @@ -626,6 +635,7 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) { updateMatchedAxRange('y'); updateSubplots([xStart, yStart, pw - dx, ph - dy]); ticksAndAnnotations(); + gd.emit('plotly_relayouting', updates); } function updateMatchedAxRange(axLetter, out) { diff --git a/test/jasmine/tests/cartesian_interact_test.js b/test/jasmine/tests/cartesian_interact_test.js index 8a220b999cc..212bba188c4 100644 --- a/test/jasmine/tests/cartesian_interact_test.js +++ b/test/jasmine/tests/cartesian_interact_test.js @@ -188,6 +188,48 @@ describe('main plot pan', function() { .then(done); }); + it('should emit plotly_relayouting events during pan interactions', function(done) { + var mock = require('@mocks/10.json'); + + function _drag(x0, y0, x1, y1, n) { + mouseEvent('mousedown', x0, y0); + var dx = (x1 - x0) / n; + var dy = (y1 - y0) / n; + for(var i = 0; i <= n; i++) { + mouseEvent('mousemove', x0 + dx * i, y0 + dy * i); + } + mouseEvent('mouseup', x1, y1); + } + + var nsteps = 10; var events = []; var relayoutCallback; + Plotly.plot(gd, mock.data, mock.layout).then(function() { + // Switch to pan mode + modeBar = gd._fullLayout._modeBar; + var buttonPan = selectButton(modeBar, 'pan2d'); + buttonPan.click(); + expect(buttonPan.isActive()).toBe(true); // switched on dragmode + }) + .then(function() { + relayoutCallback = jasmine.createSpy('relayoutCallback'); + gd.on('plotly_relayout', relayoutCallback); + gd.on('plotly_relayouting', function(e) { + events.push(e); + }); + _drag(100, 150, 220, 250, nsteps); + }) + .then(function() { + expect(events.length).toEqual(nsteps); + var first = events.splice(0, 1)[0]; + var last = events.splice(-1, 1)[0]; + expect(first['xaxis.range[1]'] - first['xaxis.range[0]']).toBeCloseTo(6, 0); + expect(last['xaxis.range[1]'] - last['xaxis.range[0]']).toBeCloseTo(6, 0); + + expect(first['xaxis.range[1]'] - last['xaxis.range[1]']).toBeCloseTo(1, 0); + }) + .catch(failTest) + .then(done); + }); + it('should show/hide `cliponaxis: false` pts according to range', function(done) { function _assert(markerDisplay, textDisplay, barTextDisplay) { var gd3 = d3.select(gd); @@ -289,10 +331,10 @@ describe('axis zoom/pan and main plot zoom', function() { return document.querySelector('.' + directions + 'drag[data-subplot="' + subplot + '"]'); } - function doDrag(subplot, directions, dx, dy) { + function doDrag(subplot, directions, dx, dy, nsteps) { return function() { var dragger = getDragger(subplot, directions); - return drag(dragger, dx, dy); + return drag(dragger, dx, dy, undefined, undefined, undefined, nsteps); }; } @@ -311,7 +353,11 @@ describe('axis zoom/pan and main plot zoom', function() { var dy = opts.dy || 0; var dragger = getDragger(subplot, directions); var coords = getNodeCoords(dragger, edge); - mouseEvent('scroll', coords.x + dx, coords.y + dy, {deltaY: deltaY, element: dragger}); + var nsteps = opts.nsteps || 1; + + for(var i = 1; i <= nsteps; i++) { + mouseEvent('scroll', coords.x + dx, coords.y + dy, {deltaY: deltaY / nsteps * i, element: dragger}); + } return delay(constants.REDRAWDELAY + 10)(); }; } @@ -629,6 +675,44 @@ describe('axis zoom/pan and main plot zoom', function() { .then(done); }); + it('should emit plotly_relayouting events when drawing zoom selection', function(done) { + var nsteps = 10; var events = []; var relayoutCallback; + Plotly.plot(gd, [{ y: [1, 2, 1] }]) + .then(function() { + relayoutCallback = jasmine.createSpy('relayoutCallback'); + gd.on('plotly_relayout', relayoutCallback); + gd.on('plotly_relayouting', function(e) { + events.push(e); + }); + }) + .then(doDrag('xy', 'nsew', 100, 100, nsteps)) + .then(function() { + expect(events.length).toEqual(nsteps); + expect(relayoutCallback).toHaveBeenCalledTimes(1); + }) + .catch(failTest) + .then(done); + }); + + it('should emit plotly_relayouting events when zooming via mouse wheel', function(done) { + var nsteps = 10; var events = []; var relayoutCallback; + Plotly.plot(gd, [{ y: [1, 2, 1] }], {}, {scrollZoom: true}) + .then(function() { + relayoutCallback = jasmine.createSpy('relayoutCallback'); + gd.on('plotly_relayout', relayoutCallback); + gd.on('plotly_relayouting', function(e) { + events.push(e); + }); + }) + .then(doScroll('xy', 'nsew', 100, {edge: 'se', nsteps: nsteps})) + .then(function() { + expect(events.length).toEqual(nsteps); + expect(relayoutCallback).toHaveBeenCalledTimes(1); + }) + .catch(failTest) + .then(done); + }); + it('handles xy, x-only and y-only zoombox updates', function(done) { function _assert(msg, xrng, yrng) { expect(gd.layout.xaxis.range).toBeCloseToArray(xrng, 2, 'xrng - ' + msg); From 4b0a0fde9f69670cd6e217d288fc67b60ee7b174 Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Tue, 21 May 2019 19:34:45 -0400 Subject: [PATCH 02/10] plotly_relayouting for geo plots --- src/plots/geo/zoom.js | 2 ++ test/jasmine/tests/geo_test.js | 27 +++++++++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/src/plots/geo/zoom.js b/src/plots/geo/zoom.js index ee632a22af9..e7b0e74efe7 100644 --- a/src/plots/geo/zoom.js +++ b/src/plots/geo/zoom.js @@ -86,6 +86,7 @@ function zoomScoped(geo, projection) { .scale(d3.event.scale) .translate(d3.event.translate); geo.render(); + geo.graphDiv.emit('plotly_relayouting', {'projection.scale': projection.scale() / geo.fitScale}); } function syncCb(set) { @@ -164,6 +165,7 @@ function zoomNonClipped(geo, projection) { didZoom = true; geo.render(); + geo.graphDiv.emit('plotly_relayouting', {'projection.scale': projection.scale() / geo.fitScale}); } function handleZoomend() { diff --git a/test/jasmine/tests/geo_test.js b/test/jasmine/tests/geo_test.js index fdad67c09cf..5a9410153c0 100644 --- a/test/jasmine/tests/geo_test.js +++ b/test/jasmine/tests/geo_test.js @@ -2066,4 +2066,31 @@ describe('Test geo zoom/pan/drag interactions:', function() { .catch(failTest) .then(done); }); + + describe('plotly_relayouting', function() { + ['pan', 'zoom'].forEach(function(dragmode) { + it('should emit plotly_relayouting events on', function(done) { + var events = []; var path = [[300, 300], [350, 300], [350, 400]]; var relayoutCallback; + var fig = Lib.extendDeep({}, require('@mocks/geo_choropleth-usa')); + fig.layout.dragmode = dragmode; + + gd = createGraphDiv(); + Plotly.plot(gd, fig) + .then(function() { + relayoutCallback = jasmine.createSpy('relayoutCallback'); + gd.on('plotly_relayout', relayoutCallback); + gd.on('plotly_relayouting', function(e) { + events.push(e); + }); + return drag(path); + }) + .then(function() { + expect(events.length).toEqual(path.length - 1); + expect(relayoutCallback).toHaveBeenCalledTimes(1); + }) + .catch(failTest) + .then(done); + }); + }); + }); }); From 4520a21f3c9297b846e9d74ae8b97b36357ea95d Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Tue, 21 May 2019 21:41:11 -0400 Subject: [PATCH 03/10] plotly_relayouting for ternary plots --- src/plots/ternary/ternary.js | 4 ++++ test/jasmine/tests/ternary_test.js | 27 +++++++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/src/plots/ternary/ternary.js b/src/plots/ternary/ternary.js index 59d0245ce93..71e9ffe92d4 100644 --- a/src/plots/ternary/ternary.js +++ b/src/plots/ternary/ternary.js @@ -644,6 +644,8 @@ proto.initInteractions = function() { .duration(200); dimmed = true; } + + gd.emit('plotly_relayouting', makeUpdate(mins)); } function zoomDone() { @@ -720,6 +722,8 @@ proto.initInteractions = function() { .select('.scatterlayer').selectAll('.trace') .call(Drawing.hideOutsideRangePoints, _this); } + + gd.emit('plotly_relayouting', makeUpdate(mins)); } function dragDone() { diff --git a/test/jasmine/tests/ternary_test.js b/test/jasmine/tests/ternary_test.js index f6475dbab1a..565e3fa583a 100644 --- a/test/jasmine/tests/ternary_test.js +++ b/test/jasmine/tests/ternary_test.js @@ -514,6 +514,33 @@ describe('ternary plots', function() { .then(done); }); + describe('plotly_relayouting', function() { + ['pan', 'zoom'].forEach(function(dragmode) { + it('should emit plotly_relayouting events on ' + dragmode, function(done) { + var events = []; var path = [[350, 250], [375, 250], [375, 225]]; var relayoutCallback; + var fig = Lib.extendDeep({}, require('@mocks/ternary_simple')); + fig.layout.dragmode = dragmode; + + var gd = createGraphDiv(); + Plotly.plot(gd, fig) + .then(function() { + relayoutCallback = jasmine.createSpy('relayoutCallback'); + gd.on('plotly_relayout', relayoutCallback); + gd.on('plotly_relayouting', function(e) { + events.push(e); + }); + return drag(path); + }) + .then(function() { + expect(events.length).toEqual(path.length - 1); + // expect(relayoutCallback).toHaveBeenCalledTimes(1); + }) + .catch(failTest) + .then(done); + }); + }); + }); + function countTernarySubplot() { return d3.selectAll('.ternary').size(); } From 68529816f037e623faed786da9a1aa8cd959d11f Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Wed, 22 May 2019 01:11:56 -0400 Subject: [PATCH 04/10] plotly_relayouting for polar plots --- src/plots/polar/polar.js | 27 +++++++++++--- test/jasmine/tests/polar_test.js | 64 ++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 5 deletions(-) diff --git a/src/plots/polar/polar.js b/src/plots/polar/polar.js index a6f33e2da6c..50d61870250 100644 --- a/src/plots/polar/polar.js +++ b/src/plots/polar/polar.js @@ -836,6 +836,10 @@ proto.updateMainDrag = function(fullLayout) { corners.attr('d', cpath); dragBox.transitionZoombox(zb, corners, dimmed, lum); dimmed = true; + + var updateObj = {}; + computeZoomUpdates(updateObj); + gd.emit('plotly_relayouting', updateObj); } function zoomMove(dx, dy) { @@ -889,16 +893,22 @@ proto.updateMainDrag = function(fullLayout) { dragBox.removeZoombox(gd); if(r0 === null || r1 === null) return; + var updateObj = {}; + computeZoomUpdates(updateObj); dragBox.showDoubleClickNotifier(gd); + Registry.call('_guiRelayout', gd, updateObj); + } + + function computeZoomUpdates(update) { var rl = radialAxis._rl; var m = (rl[1] - rl[0]) / (1 - innerRadius / radius) / radius; var newRng = [ rl[0] + (r0 - innerRadius) * m, rl[0] + (r1 - innerRadius) * m ]; - Registry.call('_guiRelayout', gd, _this.id + '.radialaxis.range', newRng); + update[_this.id + '.radialaxis.range'] = newRng; } function zoomClick(numClicks, evt) { @@ -1236,18 +1246,25 @@ proto.updateAngularDrag = function(fullLayout) { clearGlCanvases(gd); redrawReglTraces(gd); } - } - function doneFn() { - scatterTextPoints.select('text').attr('transform', null); + var update = {}; + computeRotationUpdates(update); + gd.emit('plotly_relayouting', update); + } - var updateObj = {}; + function computeRotationUpdates(updateObj) { updateObj[_this.id + '.angularaxis.rotation'] = rot1; if(_this.vangles) { updateObj[_this.id + '.radialaxis.angle'] = rrot1; } + } + function doneFn() { + scatterTextPoints.select('text').attr('transform', null); + + var updateObj = {}; + computeRotationUpdates(updateObj); Registry.call('_guiRelayout', gd, updateObj); } diff --git a/test/jasmine/tests/polar_test.js b/test/jasmine/tests/polar_test.js index 1896ad949dd..e911060c3aa 100644 --- a/test/jasmine/tests/polar_test.js +++ b/test/jasmine/tests/polar_test.js @@ -1247,6 +1247,70 @@ describe('Test polar interactions:', function() { }); }); }); + + describe('plotly_relayouting', function() { + afterEach(destroyGraphDiv); + + ['zoom'].forEach(function(dragmode) { + function _drag(p0, dp, nsteps) { + var node = d3.select('.polar > .draglayer > .maindrag').node(); + return drag(node, dp[0], dp[1], null, p0[0], p0[1], nsteps); + } + + it('should emit plotly_relayouting events on ' + dragmode, function(done) { + var events = []; var path = [[150, 250], [175, 250]]; var relayoutCallback; + var fig = Lib.extendDeep({}, require('@mocks/polar_scatter.json')); + fig.layout.dragmode = dragmode; + + var gd = createGraphDiv(); + Plotly.plot(gd, fig) + .then(function() { + relayoutCallback = jasmine.createSpy('relayoutCallback'); + gd.on('plotly_relayout', relayoutCallback); + gd.on('plotly_relayouting', function(e) { + events.push(e); + }); + return _drag(path[0], path[1]); + }) + .then(function() { + expect(events.length).toEqual(path.length - 1); + expect(events[0]['polar.radialaxis.range']).toBeCloseToArray([6, 11], 0.1); + expect(relayoutCallback).toHaveBeenCalledTimes(1); + }) + .catch(failTest) + .then(done); + }); + }); + it('should emit plotly_relayouting events on angular drag', function(done) { + var events = []; var relayoutCallback; + var fig = Lib.extendDeep({}, require('@mocks/polar_scatter.json')); + + function _drag(p0, dp, nsteps) { + var node = d3.select('.polar > .draglayer > .angulardrag').node(); + return drag(node, dp[0], dp[1], null, p0[0], p0[1], nsteps); + } + + var dragPos0 = [360, 180]; + + var gd = createGraphDiv(); + Plotly.plot(gd, fig) + .then(function() { + relayoutCallback = jasmine.createSpy('relayoutCallback'); + gd.on('plotly_relayout', relayoutCallback); + gd.on('plotly_relayouting', function(e) { + events.push(e); + }); + return _drag(dragPos0, [0, -110], 10); + }) + .then(function() { + expect(events.length).toEqual(10); + expect(events.splice(-1, 1)[0]['polar.angularaxis.rotation']).toBeCloseTo(29, 0); + expect(relayoutCallback).toHaveBeenCalledTimes(1); + }) + .catch(failTest) + .then(done); + }); + }); }); describe('Test polar *gridshape linear* interactions', function() { From e268cda016f1a3d7f27de74212d27703f08b26a6 Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Wed, 22 May 2019 11:09:48 -0400 Subject: [PATCH 05/10] plotly_relayouting for mapbox plots --- src/plots/mapbox/mapbox.js | 8 ++++++++ test/jasmine/tests/mapbox_test.js | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/src/plots/mapbox/mapbox.js b/src/plots/mapbox/mapbox.js index 12494985486..ce2791af0b2 100644 --- a/src/plots/mapbox/mapbox.js +++ b/src/plots/mapbox/mapbox.js @@ -182,6 +182,14 @@ proto.createMap = function(calcData, fullLayout, resolve, reject) { map.on('dragstart', unhover); map.on('zoomstart', unhover); + function emitUpdate() { + var viewNow = self.getView(); + gd.emit('plotly_relayouting', self.getViewEdits(viewNow)); + } + + map.on('drag', emitUpdate); + map.on('zoom', emitUpdate); + map.on('dblclick', function() { var optsNow = gd._fullLayout[self.id]; Registry.call('_storeDirectGUIEdit', gd.layout, gd._fullLayout._preGUI, self.getViewEdits(optsNow)); diff --git a/test/jasmine/tests/mapbox_test.js b/test/jasmine/tests/mapbox_test.js index b97d0d0ff27..8087bd41d58 100644 --- a/test/jasmine/tests/mapbox_test.js +++ b/test/jasmine/tests/mapbox_test.js @@ -887,6 +887,7 @@ describe('@noCI, mapbox plots', function() { it('should respond drag / scroll / double-click interactions', function(done) { var relayoutCnt = 0; var doubleClickCnt = 0; + var relayoutingCnt = 0; var evtData; gd.on('plotly_relayout', function(d) { @@ -894,6 +895,10 @@ describe('@noCI, mapbox plots', function() { evtData = d; }); + gd.on('plotly_relayouting', function() { + relayoutingCnt++; + }); + gd.on('plotly_doubleclick', function() { doubleClickCnt++; }); @@ -930,6 +935,7 @@ describe('@noCI, mapbox plots', function() { _drag(pointPos, p1, function() { expect(relayoutCnt).toBe(1, 'relayout cnt'); + expect(relayoutingCnt).toBe(2, 'relayouting cnt'); expect(doubleClickCnt).toBe(0, 'double click cnt'); _assert([-19.651, 13.751], 1.234); @@ -937,6 +943,7 @@ describe('@noCI, mapbox plots', function() { }) .then(function() { expect(relayoutCnt).toBe(2, 'relayout cnt'); + expect(relayoutingCnt).toBe(2, 'relayouting cnt'); expect(doubleClickCnt).toBe(1, 'double click cnt'); _assert([-4.710, 19.475], 1.234); @@ -944,6 +951,7 @@ describe('@noCI, mapbox plots', function() { }) .then(function() { expect(relayoutCnt).toBe(3, 'relayout cnt'); + expect(relayoutingCnt).toBeCloseTo(10, -1, 'relayouting cnt'); expect(doubleClickCnt).toBe(1, 'double click cnt'); expect(getMapInfo(gd).zoom).toBeGreaterThan(1.234); }) From 7a66dafc5e2b38a907b7757849240f432d6e2372 Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Wed, 22 May 2019 12:24:14 -0400 Subject: [PATCH 06/10] plotly_relayouting for gl3d plots --- src/plots/gl3d/scene.js | 9 ++++ test/jasmine/tests/gl3d_plot_interact_test.js | 48 +++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/src/plots/gl3d/scene.js b/src/plots/gl3d/scene.js index 57a317e72c8..0b5018bead2 100644 --- a/src/plots/gl3d/scene.js +++ b/src/plots/gl3d/scene.js @@ -272,6 +272,15 @@ function initializeGLPlot(scene, pixelRatio, canvas, gl) { } }, passiveSupported ? {passive: false} : false); + scene.glplot.canvas.addEventListener('mousemove', function() { + if(scene.fullSceneLayout.dragmode === false) return; + if(scene.camera.mouseListener.buttons === 0) return; + + var update = {}; + update[scene.id + '.camera'] = getLayoutCamera(scene.camera); + scene.graphDiv.emit('plotly_relayouting', update); + }); + if(!scene.staticMode) { scene.glplot.canvas.addEventListener('webglcontextlost', function(event) { if(gd && gd.emit) { diff --git a/test/jasmine/tests/gl3d_plot_interact_test.js b/test/jasmine/tests/gl3d_plot_interact_test.js index 821406f09bf..6eb9a67157e 100644 --- a/test/jasmine/tests/gl3d_plot_interact_test.js +++ b/test/jasmine/tests/gl3d_plot_interact_test.js @@ -929,6 +929,54 @@ describe('Test gl3d drag and wheel interactions', function() { .catch(failTest) .then(done); }); + + it('@gl should fire plotly_relayouting events', function(done) { + var sceneLayout, sceneTarget, relayoutCallback, relayoutingCallback; + + var mock = { + data: [ + { type: 'scatter3d', x: [1, 2, 3], y: [2, 3, 1], z: [3, 1, 2] } + ], + layout: { + scene: { camera: { projection: {type: 'orthographic'}, eye: { x: 0.1, y: 0.1, z: 1 }}}, + width: 400, height: 400 + } + }; + + var nsteps = 10; + function _drag(target, start, end, n) { + return new Promise(function(resolve) { + mouseEvent('mousedown', start[0], start[1], {element: target, buttons: 1}); + var dx = (end[0] - start[0]) / n; + var dy = (end[1] - start[1]) / n; + for(var i = 1; i <= n; i++) { + mouseEvent('mousemove', start[0] + dx * n, start[1] + dy * n, {element: target, buttons: 1}); + } + mouseEvent('mouseup', end[0], end[1], {element: target, buttons: 1}); + setTimeout(resolve, 0); + }); + } + + Plotly.plot(gd, mock) + .then(function() { + relayoutCallback = jasmine.createSpy('relayoutCallback'); + gd.on('plotly_relayout', relayoutCallback); + + relayoutingCallback = jasmine.createSpy('relayoutingCallback'); + gd.on('plotly_relayouting', relayoutingCallback); + + sceneLayout = gd._fullLayout.scene; + sceneTarget = gd.querySelector('.svg-container .gl-container #scene canvas'); + + return _drag(sceneTarget, [200, 200], [100, 100], nsteps); + }) + .then(function() { + expect(relayoutCallback).toHaveBeenCalledTimes(1); + expect(relayoutingCallback).toHaveBeenCalledTimes(nsteps); + }) + .catch(failTest) + .then(done); + }); }); describe('Test gl3d relayout calls', function() { From 2545746698588ac3f3edaa91bec2a1ac98ac33b3 Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Wed, 22 May 2019 16:28:28 -0400 Subject: [PATCH 07/10] improve coverage of plotly_relayouting for geo plots --- src/plots/geo/zoom.js | 26 +++++++++++++-- test/jasmine/tests/geo_test.js | 59 +++++++++++++++++++++------------- 2 files changed, 61 insertions(+), 24 deletions(-) diff --git a/src/plots/geo/zoom.js b/src/plots/geo/zoom.js index e7b0e74efe7..66ef7d15fd0 100644 --- a/src/plots/geo/zoom.js +++ b/src/plots/geo/zoom.js @@ -86,7 +86,13 @@ function zoomScoped(geo, projection) { .scale(d3.event.scale) .translate(d3.event.translate); geo.render(); - geo.graphDiv.emit('plotly_relayouting', {'projection.scale': projection.scale() / geo.fitScale}); + + var center = projection.invert(geo.midPt); + geo.graphDiv.emit('plotly_relayouting', { + 'geo.projection.scale': projection.scale() / geo.fitScale, + 'geo.center.lon': center[0], + 'geo.center.lat': center[1] + }); } function syncCb(set) { @@ -165,7 +171,16 @@ function zoomNonClipped(geo, projection) { didZoom = true; geo.render(); - geo.graphDiv.emit('plotly_relayouting', {'projection.scale': projection.scale() / geo.fitScale}); + + var rotate = projection.rotate(); + var center = projection.invert(geo.midPt); + geo.graphDiv.emit('plotly_relayouting', { + 'geo.projection.scale': projection.scale() / geo.fitScale, + 'geo.center.lon': center[0], + 'geo.center.lat': center[1], + 'geo.projection.rotation.lon': -rotate[0] + + }); } function handleZoomend() { @@ -263,6 +278,13 @@ function zoomClipped(geo, projection) { }) .on('zoom.redraw', function() { geo.render(); + + var _rotate = projection.rotate(); + geo.graphDiv.emit('plotly_relayouting', { + 'geo.projection.scale': projection.scale() / geo.fitScale, + 'geo.projection.rotation.lon': -_rotate[0], + 'geo.projection.rotation.lat': -_rotate[1] + }); }); function zoomstarted(dispatch) { diff --git a/test/jasmine/tests/geo_test.js b/test/jasmine/tests/geo_test.js index 5a9410153c0..7c790900edd 100644 --- a/test/jasmine/tests/geo_test.js +++ b/test/jasmine/tests/geo_test.js @@ -2068,28 +2068,43 @@ describe('Test geo zoom/pan/drag interactions:', function() { }); describe('plotly_relayouting', function() { - ['pan', 'zoom'].forEach(function(dragmode) { - it('should emit plotly_relayouting events on', function(done) { - var events = []; var path = [[300, 300], [350, 300], [350, 400]]; var relayoutCallback; - var fig = Lib.extendDeep({}, require('@mocks/geo_choropleth-usa')); - fig.layout.dragmode = dragmode; - - gd = createGraphDiv(); - Plotly.plot(gd, fig) - .then(function() { - relayoutCallback = jasmine.createSpy('relayoutCallback'); - gd.on('plotly_relayout', relayoutCallback); - gd.on('plotly_relayouting', function(e) { - events.push(e); - }); - return drag(path); - }) - .then(function() { - expect(events.length).toEqual(path.length - 1); - expect(relayoutCallback).toHaveBeenCalledTimes(1); - }) - .catch(failTest) - .then(done); + var mocks = { + 'non-clipped': require('@mocks/geo_winkel-tripel'), + 'clipped': require('@mocks/geo_orthographic'), + 'scoped': require('@mocks/geo_europe-bubbles') + }; + ['non-clipped', 'clipped', 'scoped'].forEach(function(zoomHandler) { + ['pan'].forEach(function(dragmode) { + it('should emit events on ' + dragmode + ' for ' + zoomHandler, function(done) { + var events = []; var path = [[300, 300], [350, 300], [350, 400]]; + var relayoutCnt = 0; var relayoutEvent; + var fig = Lib.extendDeep({}, mocks[zoomHandler]); + fig.layout.dragmode = dragmode; + fig.layout.width = 700; + fig.layout.height = 500; + + gd = createGraphDiv(); + Plotly.plot(gd, fig) + .then(function() { + gd.on('plotly_relayout', function(e) { + relayoutCnt++; + relayoutEvent = e; + }); + gd.on('plotly_relayouting', function(e) { + events.push(e); + }); + return drag(path); + }) + .then(function() { + expect(events.length).toEqual(path.length - 1); + expect(relayoutCnt).toEqual(1); + Object.keys(relayoutEvent).sort().forEach(function(key) { + expect(Object.keys(events[0])).toContain(key); + }); + }) + .catch(failTest) + .then(done); + }); }); }); }); From 082f1d230f3fa404f2a63cb36d5f170020f5eb46 Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Wed, 22 May 2019 16:55:41 -0400 Subject: [PATCH 08/10] improve tests of plotly_relayouting for gl3d plots --- test/jasmine/tests/gl3d_plot_interact_test.js | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/test/jasmine/tests/gl3d_plot_interact_test.js b/test/jasmine/tests/gl3d_plot_interact_test.js index 6eb9a67157e..19c846d796c 100644 --- a/test/jasmine/tests/gl3d_plot_interact_test.js +++ b/test/jasmine/tests/gl3d_plot_interact_test.js @@ -931,7 +931,9 @@ describe('Test gl3d drag and wheel interactions', function() { }); it('@gl should fire plotly_relayouting events', function(done) { - var sceneLayout, sceneTarget, relayoutCallback, relayoutingCallback; + var sceneTarget, relayoutEvent; + var relayoutCnt = 0; + var events = []; var mock = { data: [ @@ -959,20 +961,24 @@ describe('Test gl3d drag and wheel interactions', function() { Plotly.plot(gd, mock) .then(function() { - relayoutCallback = jasmine.createSpy('relayoutCallback'); - gd.on('plotly_relayout', relayoutCallback); - - relayoutingCallback = jasmine.createSpy('relayoutingCallback'); - gd.on('plotly_relayouting', relayoutingCallback); + gd.on('plotly_relayout', function(e) { + relayoutCnt++; + relayoutEvent = e; + }); + gd.on('plotly_relayouting', function(e) { + events.push(e); + }); - sceneLayout = gd._fullLayout.scene; sceneTarget = gd.querySelector('.svg-container .gl-container #scene canvas'); return _drag(sceneTarget, [200, 200], [100, 100], nsteps); }) .then(function() { - expect(relayoutCallback).toHaveBeenCalledTimes(1); - expect(relayoutingCallback).toHaveBeenCalledTimes(nsteps); + expect(events.length).toEqual(nsteps); + expect(relayoutCnt).toEqual(1); + Object.keys(relayoutEvent).sort().forEach(function(key) { + expect(Object.keys(events[0])).toContain(key); + }); }) .catch(failTest) .then(done); From 12658db141231bf1e8bc2606f23342c07dcdda40 Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Wed, 22 May 2019 17:36:54 -0400 Subject: [PATCH 09/10] improve coverage of plotly_relayouting for polar plots --- src/plots/polar/polar.js | 12 ++ test/jasmine/tests/cartesian_interact_test.js | 11 +- test/jasmine/tests/polar_test.js | 111 +++++++++++++----- 3 files changed, 95 insertions(+), 39 deletions(-) diff --git a/src/plots/polar/polar.js b/src/plots/polar/polar.js index 50d61870250..1c450123545 100644 --- a/src/plots/polar/polar.js +++ b/src/plots/polar/polar.js @@ -1047,6 +1047,18 @@ proto.updateRadialDrag = function(fullLayout, polarLayout, rngIndex) { moveFn2 = comp < 0.5 ? rotateMove : rerangeMove; } } + + var update = {}; + computeRadialAxisUpdates(update); + gd.emit('plotly_relayouting', update); + } + + function computeRadialAxisUpdates(update) { + if(angle1 !== null) { + update[_this.id + '.radialaxis.angle'] = angle1; + } else if(rprime !== null) { + update[_this.id + '.radialaxis.range[' + rngIndex + ']'] = rprime; + } } function doneFn() { diff --git a/test/jasmine/tests/cartesian_interact_test.js b/test/jasmine/tests/cartesian_interact_test.js index 212bba188c4..63d8da5be7d 100644 --- a/test/jasmine/tests/cartesian_interact_test.js +++ b/test/jasmine/tests/cartesian_interact_test.js @@ -189,7 +189,8 @@ describe('main plot pan', function() { }); it('should emit plotly_relayouting events during pan interactions', function(done) { - var mock = require('@mocks/10.json'); + var mock = Lib.extendDeep({}, require('@mocks/10.json')); + mock.layout.dragmode = 'pan'; function _drag(x0, y0, x1, y1, n) { mouseEvent('mousedown', x0, y0); @@ -202,13 +203,7 @@ describe('main plot pan', function() { } var nsteps = 10; var events = []; var relayoutCallback; - Plotly.plot(gd, mock.data, mock.layout).then(function() { - // Switch to pan mode - modeBar = gd._fullLayout._modeBar; - var buttonPan = selectButton(modeBar, 'pan2d'); - buttonPan.click(); - expect(buttonPan.isActive()).toBe(true); // switched on dragmode - }) + Plotly.plot(gd, mock.data, mock.layout) .then(function() { relayoutCallback = jasmine.createSpy('relayoutCallback'); gd.on('plotly_relayout', relayoutCallback); diff --git a/test/jasmine/tests/polar_test.js b/test/jasmine/tests/polar_test.js index e911060c3aa..131eb0414ce 100644 --- a/test/jasmine/tests/polar_test.js +++ b/test/jasmine/tests/polar_test.js @@ -1251,38 +1251,83 @@ describe('Test polar interactions:', function() { describe('plotly_relayouting', function() { afterEach(destroyGraphDiv); - ['zoom'].forEach(function(dragmode) { + it('should emit events on radial drag area', function(done) { + var events = []; var path = [[375, 200], [-100, 0]]; var nsteps = 10; + var relayoutEvents = []; + var fig = Lib.extendDeep({}, require('@mocks/polar_scatter.json')); + // to avoid dragging on hover labels + fig.layout.hovermode = false; + + // adjust margins so that middle of plot area is at 300x300 + // with its middle at [200,200] + fig.layout.width = 400; + fig.layout.height = 400; + fig.layout.margin = {l: 50, t: 50, b: 50, r: 50}; + function _drag(p0, dp, nsteps) { - var node = d3.select('.polar > .draglayer > .maindrag').node(); + var node = d3.select('.polar > .draglayer > .radialdrag').node(); return drag(node, dp[0], dp[1], null, p0[0], p0[1], nsteps); } - it('should emit plotly_relayouting events on ' + dragmode, function(done) { - var events = []; var path = [[150, 250], [175, 250]]; var relayoutCallback; - var fig = Lib.extendDeep({}, require('@mocks/polar_scatter.json')); - fig.layout.dragmode = dragmode; + var gd = createGraphDiv(); + Plotly.plot(gd, fig) + .then(function() { + gd.on('plotly_relayout', function(e) { + relayoutEvents.push(e); + }); + gd.on('plotly_relayouting', function(e) { + events.push(e); + }); + return _drag(path[0], path[1], nsteps); + }) + .then(function() { + var len = events.length; + expect(len).toEqual(nsteps); + expect(events[len - 1]['polar.radialaxis.range[1]']).toBeCloseTo(16, -1); + expect(relayoutEvents.length).toEqual(1); + Object.keys(relayoutEvents[0]).sort().forEach(function(key) { + expect(Object.keys(events[len - 1])).toContain(key); + }); + }) + .catch(failTest) + .then(done); + }); - var gd = createGraphDiv(); - Plotly.plot(gd, fig) - .then(function() { - relayoutCallback = jasmine.createSpy('relayoutCallback'); - gd.on('plotly_relayout', relayoutCallback); - gd.on('plotly_relayouting', function(e) { - events.push(e); - }); - return _drag(path[0], path[1]); - }) - .then(function() { - expect(events.length).toEqual(path.length - 1); - expect(events[0]['polar.radialaxis.range']).toBeCloseToArray([6, 11], 0.1); - expect(relayoutCallback).toHaveBeenCalledTimes(1); - }) - .catch(failTest) - .then(done); - }); + it('should emit events on inner radial drag area', function(done) { + var events = []; var path = [[150, 250], [175, 250]]; + var relayoutEvents = []; + var fig = Lib.extendDeep({}, require('@mocks/polar_scatter.json')); + + function _drag(p0, dp, nsteps) { + var node = d3.select('.polar > .draglayer > .maindrag').node(); + return drag(node, dp[0], dp[1], null, p0[0], p0[1], nsteps); + } + + var gd = createGraphDiv(); + Plotly.plot(gd, fig) + .then(function() { + gd.on('plotly_relayout', function(e) { + relayoutEvents.push(e); + }); + gd.on('plotly_relayouting', function(e) { + events.push(e); + }); + return _drag(path[0], path[1]); + }) + .then(function() { + expect(events.length).toEqual(path.length - 1); + expect(events[0]['polar.radialaxis.range']).toBeCloseToArray([6, 11], 0.1); + expect(relayoutEvents.length).toEqual(1); + Object.keys(relayoutEvents[0]).sort().forEach(function(key) { + expect(Object.keys(events[0])).toContain(key); + }); + }) + .catch(failTest) + .then(done); }); - it('should emit plotly_relayouting events on angular drag', function(done) { - var events = []; var relayoutCallback; + + it('should emit events on angular drag area', function(done) { + var events = []; var relayoutEvents = []; var nsteps = 10; var fig = Lib.extendDeep({}, require('@mocks/polar_scatter.json')); function _drag(p0, dp, nsteps) { @@ -1295,17 +1340,21 @@ describe('Test polar interactions:', function() { var gd = createGraphDiv(); Plotly.plot(gd, fig) .then(function() { - relayoutCallback = jasmine.createSpy('relayoutCallback'); - gd.on('plotly_relayout', relayoutCallback); + gd.on('plotly_relayout', function(e) { + relayoutEvents.push(e); + }); gd.on('plotly_relayouting', function(e) { events.push(e); }); - return _drag(dragPos0, [0, -110], 10); + return _drag(dragPos0, [0, -110], nsteps); }) .then(function() { - expect(events.length).toEqual(10); + expect(events.length).toEqual(nsteps); expect(events.splice(-1, 1)[0]['polar.angularaxis.rotation']).toBeCloseTo(29, 0); - expect(relayoutCallback).toHaveBeenCalledTimes(1); + expect(relayoutEvents.length).toEqual(1); + Object.keys(relayoutEvents[0]).sort().forEach(function(key) { + expect(Object.keys(events[0])).toContain(key); + }); }) .catch(failTest) .then(done); From a76a52acdda5181fa82b1ac2f2e4033f340e5c2d Mon Sep 17 00:00:00 2001 From: Antoine Roy-Gobeil Date: Thu, 23 May 2019 12:42:25 -0400 Subject: [PATCH 10/10] cartesian_interact: update assert values due to timing change --- test/jasmine/tests/cartesian_interact_test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/jasmine/tests/cartesian_interact_test.js b/test/jasmine/tests/cartesian_interact_test.js index 63d8da5be7d..20d1394452c 100644 --- a/test/jasmine/tests/cartesian_interact_test.js +++ b/test/jasmine/tests/cartesian_interact_test.js @@ -1545,7 +1545,7 @@ describe('axis zoom/pan and main plot zoom', function() { return drag.start() .then(_assert('just after start of zoombox', { nodeCnt: 4, - xrng: [-0.1927, 3.1927], + xrng: [1.5, 1.6880], hasDragData: true, zoombox: 'M269.5,114.5h-3v41h3ZM300.5,114.5h3v41h-3Z', clipTranslate: [0, 0] @@ -1553,7 +1553,7 @@ describe('axis zoom/pan and main plot zoom', function() { .then(delay(step)) .then(_assert('during zoombox drag', { nodeCnt: 5, - xrng: [-0.257, 4.257], + xrng: [2, 2.2507], hasDragData: true, zoombox: 'M269.5,114.5h-3v41h3ZM300.5,114.5h3v41h-3Z', clipTranslate: [0, 0]