Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

plotly_relayouting event: live updates during panning/zooming #3888

Merged
merged 11 commits into from
May 23, 2019
30 changes: 20 additions & 10 deletions src/plots/cartesian/dragbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -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();
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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) {
Expand Down
24 changes: 24 additions & 0 deletions src/plots/geo/zoom.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,13 @@ function zoomScoped(geo, projection) {
.scale(d3.event.scale)
.translate(d3.event.translate);
geo.render();

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) {
Expand Down Expand Up @@ -164,6 +171,16 @@ function zoomNonClipped(geo, projection) {

didZoom = true;
geo.render();

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() {
Expand Down Expand Up @@ -261,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) {
Expand Down
9 changes: 9 additions & 0 deletions src/plots/gl3d/scene.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
8 changes: 8 additions & 0 deletions src/plots/mapbox/mapbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
39 changes: 34 additions & 5 deletions src/plots/polar/polar.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -1037,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() {
Expand Down Expand Up @@ -1236,18 +1258,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);
}

Expand Down
4 changes: 4 additions & 0 deletions src/plots/ternary/ternary.js
Original file line number Diff line number Diff line change
Expand Up @@ -644,6 +644,8 @@ proto.initInteractions = function() {
.duration(200);
dimmed = true;
}

gd.emit('plotly_relayouting', makeUpdate(mins));
}

function zoomDone() {
Expand Down Expand Up @@ -720,6 +722,8 @@ proto.initInteractions = function() {
.select('.scatterlayer').selectAll('.trace')
.call(Drawing.hideOutsideRangePoints, _this);
}

gd.emit('plotly_relayouting', makeUpdate(mins));
}

function dragDone() {
Expand Down
89 changes: 84 additions & 5 deletions test/jasmine/tests/cartesian_interact_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,43 @@ describe('main plot pan', function() {
.then(done);
});

it('should emit plotly_relayouting events during pan interactions', function(done) {
var mock = Lib.extendDeep({}, require('@mocks/10.json'));
mock.layout.dragmode = 'pan';

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() {
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);
Expand Down Expand Up @@ -289,10 +326,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);
};
}

Expand All @@ -311,7 +348,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)();
};
}
Expand Down Expand Up @@ -629,6 +670,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);
Expand Down Expand Up @@ -1466,15 +1545,15 @@ 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]
}))
.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]
Expand Down
Loading