diff --git a/src/plot_api/subroutines.js b/src/plot_api/subroutines.js
index 587e014dc60..64b82fa349e 100644
--- a/src/plot_api/subroutines.js
+++ b/src/plot_api/subroutines.js
@@ -295,7 +295,6 @@ exports.doModeBar = function(gd) {
var subplotIds, i;
ModeBar.manage(gd);
- Plotly.Fx.supplyLayoutDefaults(gd.layout, gd._fullLayout, gd._fullData);
Plotly.Fx.init(gd);
subplotIds = Plots.getSubplotIds(fullLayout, 'gl3d');
@@ -304,11 +303,8 @@ exports.doModeBar = function(gd) {
scene.updateFx(fullLayout.dragmode, fullLayout.hovermode);
}
- subplotIds = Plots.getSubplotIds(fullLayout, 'gl2d');
- for(i = 0; i < subplotIds.length; i++) {
- var scene2d = fullLayout._plots[subplotIds[i]]._scene2d;
- scene2d.updateFx(fullLayout);
- }
+ // no need to do this for gl2d subplots,
+ // Plots.linkSubplots takes care of it all.
subplotIds = Plots.getSubplotIds(fullLayout, 'geo');
for(i = 0; i < subplotIds.length; i++) {
diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js
index 64dc21e4b71..dae22f8f009 100644
--- a/src/plots/cartesian/axes.js
+++ b/src/plots/cartesian/axes.js
@@ -59,7 +59,7 @@ axes.getFromTrace = axisIds.getFromTrace;
*/
axes.coerceRef = function(containerIn, containerOut, gd, attr, dflt, extraOption) {
var axLetter = attr.charAt(attr.length - 1),
- axlist = gd._fullLayout._has('gl2d') ? [] : axes.listIds(gd, axLetter),
+ axlist = axes.listIds(gd, axLetter),
refAttr = attr + 'ref',
attrDef = {};
diff --git a/src/plots/cartesian/graph_interact.js b/src/plots/cartesian/graph_interact.js
index ea2f8c908ec..9a7cff983bf 100644
--- a/src/plots/cartesian/graph_interact.js
+++ b/src/plots/cartesian/graph_interact.js
@@ -34,9 +34,6 @@ var fx = module.exports = {};
// copy on Fx for backward compatible
fx.unhover = dragElement.unhover;
-fx.layoutAttributes = {
-};
-
fx.supplyLayoutDefaults = function(layoutIn, layoutOut, fullData) {
function coerce(attr, dflt) {
diff --git a/src/plots/gl2d/camera.js b/src/plots/gl2d/camera.js
index 6da2699ad2b..405795b6b57 100644
--- a/src/plots/gl2d/camera.js
+++ b/src/plots/gl2d/camera.js
@@ -91,6 +91,7 @@ function createCamera(scene) {
updateRange(1, result.boxStart[1], result.boxEnd[1]);
unSetAutoRange();
result.boxEnabled = false;
+ scene.relayoutCallback();
}
break;
@@ -110,9 +111,15 @@ function createCamera(scene) {
scene.setRanges(dataBox);
+ result.panning = true;
result.lastInputTime = Date.now();
unSetAutoRange();
scene.cameraChanged();
+ scene.handleAnnotations();
+ }
+ else if(result.panning) {
+ result.panning = false;
+ scene.relayoutCallback();
}
break;
}
@@ -152,6 +159,8 @@ function createCamera(scene) {
result.lastInputTime = Date.now();
unSetAutoRange();
scene.cameraChanged();
+ scene.handleAnnotations();
+ scene.relayoutCallback();
break;
}
diff --git a/src/plots/gl2d/scene2d.js b/src/plots/gl2d/scene2d.js
index e5b006900cb..d86303c29f9 100644
--- a/src/plots/gl2d/scene2d.js
+++ b/src/plots/gl2d/scene2d.js
@@ -9,6 +9,7 @@
'use strict';
+var Registry = require('../../registry');
var Axes = require('../../plots/cartesian/axes');
var Fx = require('../../plots/cartesian/graph_interact');
@@ -33,9 +34,8 @@ function Scene2D(options, fullLayout) {
this.id = options.id;
this.staticPlot = !!options.staticPlot;
- this.fullLayout = fullLayout;
this.fullData = null;
- this.updateAxes(fullLayout);
+ this.updateRefs(fullLayout);
this.makeFramework();
@@ -237,6 +237,11 @@ proto.updateSize = function(canvas) {
};
proto.computeTickMarks = function() {
+ this.xaxis.setScale();
+ this.yaxis.setScale();
+
+ // override _length from backward compatibility
+ // even though setScale 'should' give the correct result
this.xaxis._length =
this.glplot.viewBox[2] - this.glplot.viewBox[0];
this.yaxis._length =
@@ -272,41 +277,40 @@ function compareTicks(a, b) {
return false;
}
-proto.updateAxes = function(options) {
+proto.updateRefs = function(newFullLayout) {
+ this.fullLayout = newFullLayout;
+
var spmatch = Axes.subplotMatch,
xaxisName = 'xaxis' + this.id.match(spmatch)[1],
yaxisName = 'yaxis' + this.id.match(spmatch)[2];
- this.xaxis = options[xaxisName];
- this.yaxis = options[yaxisName];
-};
-
-proto.updateFx = function(options) {
- var fullLayout = this.fullLayout;
-
- fullLayout.dragmode = options.dragmode;
- fullLayout.hovermode = options.hovermode;
+ this.xaxis = this.fullLayout[xaxisName];
+ this.yaxis = this.fullLayout[yaxisName];
};
-var relayoutCallback = function(scene) {
-
- var xrange = scene.xaxis.range,
- yrange = scene.yaxis.range;
-
- // Update the layout on the DIV
- scene.graphDiv.layout.xaxis.autorange = scene.xaxis.autorange;
- scene.graphDiv.layout.xaxis.range = xrange.slice(0);
- scene.graphDiv.layout.yaxis.autorange = scene.yaxis.autorange;
- scene.graphDiv.layout.yaxis.range = yrange.slice(0);
-
- // Make a meaningful value to be passed on to the possible 'plotly_relayout' subscriber(s)
- var update = { // scene.camera has no many useful projection or scale information
- lastInputTime: scene.camera.lastInputTime // helps determine which one is the latest input (if async)
+proto.relayoutCallback = function() {
+ var graphDiv = this.graphDiv,
+ xaxis = this.xaxis,
+ yaxis = this.yaxis,
+ layout = graphDiv.layout;
+
+ // update user layout
+ layout.xaxis.autorange = xaxis.autorange;
+ layout.xaxis.range = xaxis.range.slice(0);
+ layout.yaxis.autorange = yaxis.autorange;
+ layout.yaxis.range = yaxis.range.slice(0);
+
+ // make a meaningful value to be passed on to the possible 'plotly_relayout' subscriber(s)
+ // scene.camera has no many useful projection or scale information
+ // helps determine which one is the latest input (if async)
+ var update = {
+ lastInputTime: this.camera.lastInputTime
};
- update[scene.xaxis._name] = xrange.slice();
- update[scene.yaxis._name] = yrange.slice();
- scene.graphDiv.emit('plotly_relayout', update);
+ update[xaxis._name] = xaxis.range.slice(0);
+ update[yaxis._name] = yaxis.range.slice(0);
+
+ graphDiv.emit('plotly_relayout', update);
};
proto.cameraChanged = function() {
@@ -321,7 +325,20 @@ proto.cameraChanged = function() {
this.glplotOptions.ticks = nextTicks;
this.glplotOptions.dataBox = camera.dataBox;
this.glplot.update(this.glplotOptions);
- relayoutCallback(this);
+ this.handleAnnotations();
+ }
+};
+
+proto.handleAnnotations = function() {
+ var gd = this.graphDiv,
+ annotations = this.fullLayout.annotations;
+
+ for(var i = 0; i < annotations.length; i++) {
+ var ann = annotations[i];
+
+ if(ann.xref === this.xaxis._id && ann.yref === this.yaxis._id) {
+ Registry.getComponentMethod('annotations', 'drawOne')(gd, i);
+ }
}
};
@@ -350,8 +367,7 @@ proto.destroy = function() {
proto.plot = function(fullData, calcData, fullLayout) {
var glplot = this.glplot;
- this.fullLayout = fullLayout;
- this.updateAxes(fullLayout);
+ this.updateRefs(fullLayout);
this.updateTraces(fullData, calcData);
var width = fullLayout.width,
@@ -406,6 +422,7 @@ proto.plot = function(fullData, calcData, fullLayout) {
ax._length = options.viewBox[i + 2] - options.viewBox[i];
Axes.doAutoRange(ax);
+ ax.setScale();
}
options.ticks = this.computeTickMarks();
diff --git a/src/plots/plots.js b/src/plots/plots.js
index 3fd99071263..3253aba48f7 100644
--- a/src/plots/plots.js
+++ b/src/plots/plots.js
@@ -655,6 +655,10 @@ plots.linkSubplots = function(newFullData, newFullLayout, oldFullData, oldFullLa
if(oldSubplot) {
plotinfo = newSubplots[id] = oldSubplot;
+
+ if(plotinfo._scene2d) {
+ plotinfo._scene2d.updateRefs(newFullLayout);
+ }
}
else {
plotinfo = newSubplots[id] = {};
diff --git a/test/image/baselines/gl2d_annotations.png b/test/image/baselines/gl2d_annotations.png
new file mode 100644
index 00000000000..ab0306aa89d
Binary files /dev/null and b/test/image/baselines/gl2d_annotations.png differ
diff --git a/test/image/mocks/gl2d_annotations.json b/test/image/mocks/gl2d_annotations.json
new file mode 100644
index 00000000000..3e8a1c4e0d2
--- /dev/null
+++ b/test/image/mocks/gl2d_annotations.json
@@ -0,0 +1,52 @@
+{
+ "data":[{
+ "type": "scattergl",
+ "x":[1,1,1,1,1,2,2,2,2,2,3,3,3,3,3,4,4,4,4,4,5,5,5,5,5],
+ "y":[1,2,3,4,5,1,2,3,4,5,1,2,3,4,5,1,2,3,4,5,1,2,3,4,5],
+ "mode":"markers"
+ }],
+ "layout": {
+ "yaxis":{"autorange":false,"range":[1,5],"showgrid":false,"zeroline":false,"showticklabels":false},
+ "xaxis":{"autorange":false,"range":[1,5],"showgrid":false,"zeroline":false,"showticklabels":false},
+ "height":500,
+ "width":800,
+ "margin": {"l":100,"r":100,"top":80,"bottom":80,"pad":0},
+ "showlegend":false,
+ "annotations":[
+ {"text":"left top","showarrow":false,"xref":"paper","yref":"paper","xanchor":"left","yanchor":"top","x":0,"y":1},
+ {"text":"center middle","showarrow":false,"xref":"paper","yref":"paper","xanchor":"center","yanchor":"middle","x":0.25,"y":1},
+ {"text":"right bottom","showarrow":false,"xref":"paper","yref":"paper","xanchor":"right","yanchor":"bottom","x":0.5,"y":1},
+ {"text":"move with page","xref":"paper","yref":"paper","x":0.75,"y":1},
+ {"text":"opacity","opacity":0.5,"x":5,"y":5},
+ {"text":"not-visible", "visible": false},
+ {"text":"left
justified","showarrow":false,"align":"left","x":1,"y":4},
+ {"text":"center
justified","showarrow":false,"x":2,"y":4},
+ {"text":"right
justified","showarrow":false,"align":"right","x":3,"y":4},
+ {"text":"no arrow
page auto TR","showarrow":false,"xref":"paper","yref":"paper","x":0.75,"y":0.75},
+ {"text":"no arrow
page auto ML","showarrow":false,"xref":"paper","yref":"paper","x":0.25,"y":0.5},
+ {"text":"no arrow
page auto BC","showarrow":false,"xref":"paper","yref":"paper","x":0.5,"y":0.25},
+ {"text":"default"},
+ {"text":"no arrow","x":5,"y":4,"showarrow":false},
+ {"text":"border","showarrow":false,"bordercolor":"rgb(148, 103, 189)","x":4,"y":3},
+ {"text":"border width","showarrow":false,"bordercolor":"rgb(0, 0, 255)","borderwidth":3,"x":5,"y":3},
+ {"text":"background","showarrow":false,"bgcolor":"rgb(255, 127, 14)","x":4,"y":2},
+ {"text":"padding","showarrow":false,"bordercolor":"rgb(0, 0, 0)","borderpad":3,"x":5,"y":2},
+ {"text":"angle
Bottom R","showarrow":false,"textangle":40,"x":1,"y":1,"xanchor":"right","yanchor":"bottom"},
+ {"text":"angle
Middle C","showarrow":false,"textangle":-70,"x":2,"y":1,"xanchor":"center","yanchor":"middle"},
+ {"text":"angle
Top L","showarrow":false,"textangle":160,"x":3,"y":1,"xanchor":"left","yanchor":"top"},
+ {
+ "text":"arrowhead styling","arrowcolor":"rgb(214, 39, 40)","arrowwidth":4.1,"arrowhead":7,
+ "ax":-34,"ay":37,"arrowsize":2,"x":4,"y":1
+ },
+ {
+ "text":"All together
withSTYLE",
+ "opacity":0.6,"arrowwidth":5,"arrowhead":3,"ax":-77,"ay":-5,
+ "bordercolor":"rgb(255, 0, 0)","borderwidth":4,"bgcolor":"rgba(255,255,0,0.5)",
+ "font":{"color":"rgb(0, 0, 255)","size":20},
+ "arrowcolor":"rgb(166, 28, 0)","borderpad":3,"textangle":50,"x":5,"y":1
+ },
+ {"text":"","showarrow":true,"borderwidth":1.2,"arrowhead":2,"axref":"x","ayref":"y","x":5,"y":3,"ax":4,"ay":5},
+ {"text":"","showarrow":true,"borderwidth":1.2,"arrowhead":2,"axref":"x","ayref":"y","x":6,"y":2,"ax":3,"ay":3}
+ ]
+ }
+}
diff --git a/test/jasmine/tests/gl_plot_interact_test.js b/test/jasmine/tests/gl_plot_interact_test.js
index 83abcf0c303..21db7afabc7 100644
--- a/test/jasmine/tests/gl_plot_interact_test.js
+++ b/test/jasmine/tests/gl_plot_interact_test.js
@@ -236,6 +236,13 @@ describe('Test gl plot interactions', function() {
it('should respond to drag interactions', function(done) {
+ function mouseTo(p0, p1) {
+ mouseEvent('mousemove', p0[0], p0[1]);
+ mouseEvent('mousedown', p0[0], p0[1], { buttons: 1 });
+ mouseEvent('mousemove', p1[0], p1[1], { buttons: 1 });
+ mouseEvent('mouseup', p1[0], p1[1]);
+ }
+
jasmine.addMatchers(customMatchers);
var precision = 5;
@@ -263,14 +270,10 @@ describe('Test gl plot interactions', function() {
expect(gd.layout.yaxis.range).toBeCloseToArray(originalY, precision);
setTimeout(function() {
-
- mouseEvent('mousemove', 200, 200);
-
relayoutCallback.calls.reset();
// Drag scene along the X axis
-
- mouseEvent('mousemove', 220, 200, {buttons: 1});
+ mouseTo([200, 200], [220, 200]);
expect(gd.layout.xaxis.autorange).toBe(false);
expect(gd.layout.yaxis.autorange).toBe(false);
@@ -279,36 +282,31 @@ describe('Test gl plot interactions', function() {
expect(gd.layout.yaxis.range).toBeCloseToArray(originalY, precision);
// Drag scene back along the X axis
-
- mouseEvent('mousemove', 200, 200, {buttons: 1});
+ mouseTo([220, 200], [200, 200]);
expect(gd.layout.xaxis.range).toBeCloseToArray(originalX, precision);
expect(gd.layout.yaxis.range).toBeCloseToArray(originalY, precision);
// Drag scene along the Y axis
-
- mouseEvent('mousemove', 200, 150, {buttons: 1});
+ mouseTo([200, 200], [200, 150]);
expect(gd.layout.xaxis.range).toBeCloseToArray(originalX, precision);
expect(gd.layout.yaxis.range).toBeCloseToArray(newY, precision);
// Drag scene back along the Y axis
-
- mouseEvent('mousemove', 200, 200, {buttons: 1});
+ mouseTo([200, 150], [200, 200]);
expect(gd.layout.xaxis.range).toBeCloseToArray(originalX, precision);
expect(gd.layout.yaxis.range).toBeCloseToArray(originalY, precision);
// Drag scene along both the X and Y axis
-
- mouseEvent('mousemove', 220, 150, {buttons: 1});
+ mouseTo([200, 200], [220, 150]);
expect(gd.layout.xaxis.range).toBeCloseToArray(newX, precision);
expect(gd.layout.yaxis.range).toBeCloseToArray(newY, precision);
// Drag scene back along the X and Y axis
-
- mouseEvent('mousemove', 200, 200, {buttons: 1});
+ mouseTo([220, 150], [200, 200]);
expect(gd.layout.xaxis.range).toBeCloseToArray(originalX, precision);
expect(gd.layout.yaxis.range).toBeCloseToArray(originalY, precision);
@@ -743,3 +741,68 @@ describe('Test gl plot side effects', function() {
}).then(done);
});
});
+
+describe('gl2d interaction', function() {
+ var gd;
+
+ beforeAll(function() {
+ jasmine.addMatchers(customMatchers);
+ });
+
+ beforeEach(function() {
+ gd = createGraphDiv();
+ });
+
+ afterEach(function() {
+ Plotly.purge(gd);
+ destroyGraphDiv();
+ });
+
+ it('data-referenced annotations should update on drag', function(done) {
+
+ function drag(start, end) {
+ var opts = { buttons: 1 };
+
+ mouseEvent('mousemove', start[0], start[1], opts);
+ mouseEvent('mousedown', start[0], start[1], opts);
+ mouseEvent('mousemove', end[0], end[1], opts);
+ mouseEvent('mouseup', end[0], end[1], opts);
+ }
+
+ function assertAnnotation(xy) {
+ var ann = d3.select('g.annotation-text-g');
+ var x = +ann.attr('x');
+ var y = +ann.attr('y');
+
+ expect([x, y]).toBeCloseToArray(xy);
+ }
+
+ Plotly.plot(gd, [{
+ type: 'scattergl',
+ x: [1, 2, 3],
+ y: [2, 1, 2]
+ }], {
+ annotations: [{
+ x: 2,
+ y: 1,
+ text: 'text'
+ }],
+ dragmode: 'pan'
+ })
+ .then(function() {
+ assertAnnotation([340, 334]);
+
+ drag([250, 200], [150, 300]);
+ assertAnnotation([410, 264]);
+
+ return Plotly.relayout(gd, {
+ 'xaxis.range': [1.5, 2.5],
+ 'yaxis.range': [1, 1.5]
+ });
+ })
+ .then(function() {
+ assertAnnotation([340, 340]);
+ })
+ .then(done);
+ });
+});