diff --git a/src/components/modebar/buttons.js b/src/components/modebar/buttons.js index 776a75df610..2fa852e70de 100644 --- a/src/components/modebar/buttons.js +++ b/src/components/modebar/buttons.js @@ -574,3 +574,29 @@ function setSpikelineVisibility(gd) { return aobj; } + +modeBarButtons.resetViewMapbox = { + name: 'resetViewMapbox', + title: 'Reset view', + attr: 'reset', + icon: Icons.home, + click: function(gd) { + var fullLayout = gd._fullLayout; + var subplotIds = Plots.getSubplotIds(fullLayout, 'mapbox'); + var aObj = {}; + + for(var i = 0; i < subplotIds.length; i++) { + var id = subplotIds[i]; + var subplotObj = fullLayout[id]._subplot; + var viewInitial = subplotObj.viewInitial; + var viewKeys = Object.keys(viewInitial); + + for(var j = 0; j < viewKeys.length; j++) { + var key = viewKeys[j]; + aObj[id + '.' + key] = viewInitial[key]; + } + } + + Plotly.relayout(gd, aObj); + } +}; diff --git a/src/components/modebar/manage.js b/src/components/modebar/manage.js index 5cb86a9eea2..7bc6c3b8b34 100644 --- a/src/components/modebar/manage.js +++ b/src/components/modebar/manage.js @@ -148,6 +148,9 @@ function getButtonGroups(gd, buttonsToRemove, buttonsToAdd) { else if(hasPie) { addGroup(['hoverClosestPie']); } + else if(hasMapbox) { + addGroup(['resetViewMapbox', 'toggleHover']); + } return appendButtonsToGroups(groups, buttonsToAdd); } diff --git a/src/plots/mapbox/index.js b/src/plots/mapbox/index.js index 25d5ef7dafc..8eb77e1f520 100644 --- a/src/plots/mapbox/index.js +++ b/src/plots/mapbox/index.js @@ -11,6 +11,7 @@ var mapboxgl = require('mapbox-gl'); +var Lib = require('../../lib'); var Plots = require('../plots'); var xmlnsNamespaces = require('../../constants/xmlns_namespaces'); @@ -75,6 +76,15 @@ exports.plot = function plotMapbox(gd) { fullLayout[id]._subplot = mapbox; } + if(!mapbox.viewInitial) { + mapbox.viewInitial = { + center: Lib.extendFlat({}, opts.center), + zoom: opts.zoom, + bearing: opts.bearing, + pitch: opts.pitch + }; + } + mapbox.plot(subplotCalcData, fullLayout, gd._promises); } }; diff --git a/src/plots/mapbox/mapbox.js b/src/plots/mapbox/mapbox.js index e2c923421b6..272bd9afe80 100644 --- a/src/plots/mapbox/mapbox.js +++ b/src/plots/mapbox/mapbox.js @@ -19,7 +19,6 @@ var constants = require('./constants'); var layoutAttributes = require('./layout_attributes'); var createMapboxLayer = require('./layers'); - function Mapbox(opts) { this.id = opts.id; this.gd = opts.gd; @@ -111,6 +110,7 @@ proto.createMap = function(calcData, fullLayout, resolve, reject) { interactive: !self.isStatic, preserveDrawingBuffer: self.isStatic, + doubleClickZoom: false, boxZoom: false }); @@ -186,6 +186,24 @@ proto.createMap = function(calcData, fullLayout, resolve, reject) { map.on('dragstart', unhover); map.on('zoomstart', unhover); + + map.on('dblclick', function() { + var viewInitial = self.viewInitial; + + map.setCenter(convertCenter(viewInitial.center)); + map.setZoom(viewInitial.zoom); + map.setBearing(viewInitial.bearing); + map.setPitch(viewInitial.pitch); + + var viewNow = self.getView(); + + opts._input.center = opts.center = viewNow.center; + opts._input.zoom = opts.zoom = viewNow.zoom; + opts._input.bearing = opts.bearing = viewNow.bearing; + opts._input.pitch = opts.pitch = viewNow.pitch; + + gd.emit('plotly_doubleclick', null); + }); }; proto.updateMap = function(calcData, fullLayout, resolve, reject) { @@ -488,8 +506,8 @@ proto.project = function(v) { proto.getView = function() { var map = this.map; - var mapCenter = map.getCenter(), - center = { lon: mapCenter.lng, lat: mapCenter.lat }; + var mapCenter = map.getCenter(); + var center = { lon: mapCenter.lng, lat: mapCenter.lat }; return { center: center, diff --git a/test/jasmine/tests/mapbox_test.js b/test/jasmine/tests/mapbox_test.js index 050605b2494..3f469d56c95 100644 --- a/test/jasmine/tests/mapbox_test.js +++ b/test/jasmine/tests/mapbox_test.js @@ -23,7 +23,6 @@ Plotly.setPlotConfig({ mapboxAccessToken: MAPBOX_ACCESS_TOKEN }); - describe('mapbox defaults', function() { 'use strict'; @@ -788,31 +787,19 @@ describe('@noCI, mapbox plots', function() { .then(done); }, LONG_TIMEOUT_INTERVAL); - it('should respond drag / scroll interactions', function(done) { - var relayoutCnt = 0, - updateData; + it('should respond drag / scroll / double-click interactions', function(done) { + var relayoutCnt = 0; + var doubleClickCnt = 0; + var updateData; gd.on('plotly_relayout', function(eventData) { relayoutCnt++; updateData = eventData; }); - function _drag(p0, p1, cb) { - var promise = _mouseEvent('mousemove', p0, noop).then(function() { - return _mouseEvent('mousedown', p0, noop); - }).then(function() { - return _mouseEvent('mousemove', p1, noop); - }).then(function() { - // repeat mousemove to simulate long dragging motion - return _mouseEvent('mousemove', p1, noop); - }).then(function() { - return _mouseEvent('mouseup', p1, noop); - }).then(function() { - return _mouseEvent('mouseup', p1, noop); - }).then(cb); - - return promise; - } + gd.on('plotly_doubleclick', function() { + doubleClickCnt++; + }); function assertLayout(center, zoom, opts) { var mapInfo = getMapInfo(gd), @@ -838,8 +825,13 @@ describe('@noCI, mapbox plots', function() { _drag(pointPos, p1, function() { expect(relayoutCnt).toEqual(1); - assertLayout([-19.651, 13.751], 1.234, { withUpdateData: true }); + assertLayout([-19.651, 13.751], 1.234, {withUpdateData: true}); + return _doubleClick(p1); + }) + .then(function() { + expect(doubleClickCnt).toBe(1, 'double click cnt'); + assertLayout([-4.710, 19.475], 1.234); }) .catch(failTest) .then(done); @@ -855,16 +847,6 @@ describe('@noCI, mapbox plots', function() { ptData = eventData.points[0]; }); - function _click(pos, cb) { - var promise = _mouseEvent('mousemove', pos, noop).then(function() { - return _mouseEvent('mousedown', pos, noop); - }).then(function() { - return _mouseEvent('click', pos, cb); - }); - - return promise; - } - _click(blankPos, function() { expect(ptData).toBe(undefined, 'not firing on blank points'); }) @@ -991,6 +973,38 @@ describe('@noCI, mapbox plots', function() { }, MOUSE_DELAY); }); } + + function _click(pos, cb) { + var promise = _mouseEvent('mousemove', pos, noop).then(function() { + return _mouseEvent('mousedown', pos, noop); + }).then(function() { + return _mouseEvent('click', pos, cb); + }); + + return promise; + } + + function _doubleClick(pos) { + return _mouseEvent('dblclick', pos, noop); + } + + function _drag(p0, p1, cb) { + var promise = _mouseEvent('mousemove', p0, noop).then(function() { + return _mouseEvent('mousedown', p0, noop); + }).then(function() { + return _mouseEvent('mousemove', p1, noop); + }).then(function() { + // repeat mousemove to simulate long dragging motion + return _mouseEvent('mousemove', p1, noop); + }).then(function() { + return _mouseEvent('mouseup', p1, noop); + }).then(function() { + return _mouseEvent('mouseup', p1, noop); + }).then(cb); + + return promise; + } + }); describe('@noCI, mapbox toImage', function() { diff --git a/test/jasmine/tests/modebar_test.js b/test/jasmine/tests/modebar_test.js index 5133e5fc975..888b0b9e075 100644 --- a/test/jasmine/tests/modebar_test.js +++ b/test/jasmine/tests/modebar_test.js @@ -270,6 +270,44 @@ describe('ModeBar', function() { checkButtons(modeBar, buttons, 1); }); + it('creates mode bar (mapbox version)', function() { + var buttons = getButtons([ + ['toImage', 'sendDataToCloud'], + ['pan2d'], + ['resetViewMapbox', 'toggleHover'] + ]); + + var gd = getMockGraphInfo(); + gd._fullLayout._basePlotModules = [{ name: 'mapbox' }]; + + manageModeBar(gd); + var modeBar = gd._fullLayout._modeBar; + + checkButtons(modeBar, buttons, 1); + }); + + it('creates mode bar (mapbox + selected version)', function() { + var buttons = getButtons([ + ['toImage', 'sendDataToCloud'], + ['pan2d', 'select2d', 'lasso2d'], + ['resetViewMapbox', 'toggleHover'] + ]); + + var gd = getMockGraphInfo(); + gd._fullLayout._basePlotModules = [{ name: 'mapbox' }]; + gd._fullData = [{ + type: 'scatter', + visible: true, + mode: 'markers', + _module: {selectPoints: true} + }]; + + manageModeBar(gd); + var modeBar = gd._fullLayout._modeBar; + + checkButtons(modeBar, buttons, 1); + }); + it('creates mode bar (gl2d version)', function() { var buttons = getButtons([ ['toImage', 'sendDataToCloud'], @@ -882,5 +920,50 @@ describe('ModeBar', function() { }); + describe('@noCI mapbox handlers', function() { + it('button *resetViewMapbox* should reset the mapbox view attribute to their default', function(done) { + var gd = createGraphDiv(); + + function _assert(centerLon, centerLat, zoom) { + var mapboxLayout = gd._fullLayout.mapbox; + + expect(mapboxLayout.center.lon).toBe(centerLon, 'center.lon'); + expect(mapboxLayout.center.lat).toBe(centerLat, 'center.lat'); + expect(mapboxLayout.zoom).toBe(zoom, 'zoom'); + } + + Plotly.plot(gd, [{ + type: 'scattermapbox', + lon: [10, 20, 30], + lat: [10, 20, 30] + }], { + mapbox: { + center: {lon: 10, lat: 10}, + zoom: 8 + } + }, { + mapboxAccessToken: require('@build/credentials.json').MAPBOX_ACCESS_TOKEN + }) + .then(function() { + _assert(10, 10, 8); + + return Plotly.relayout(gd, { + 'mapbox.zoom': 10, + 'mapbox.center.lon': 30 + }); + }) + .then(function() { + _assert(30, 10, 10); + + var button = selectButton(gd._fullLayout._modeBar, 'resetViewMapbox'); + + button.isActive(false); + button.click(false); + _assert(10, 10, 8); + button.isActive(false); + }) + .then(done); + }); + }); }); });