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); + }); +});