Skip to content
Merged
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
85 changes: 82 additions & 3 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
Loading