diff --git a/package-lock.json b/package-lock.json index a88cd6771aa..b3b66a118b3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -78,9 +78,9 @@ "integrity": "sha1-zlblOfg1UrWNENZy6k1vya3HsjQ=" }, "@mapbox/mapbox-gl-supported": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@mapbox/mapbox-gl-supported/-/mapbox-gl-supported-1.4.0.tgz", - "integrity": "sha512-ZD0Io4XK+/vU/4zpANjOtdWfVszAgnaMPsGR6LKsWh4kLIEv9qoobTVmJPPuwuM+ZI2b3BlZ6DYw1XHVmv6YTA==" + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@mapbox/mapbox-gl-supported/-/mapbox-gl-supported-1.4.1.tgz", + "integrity": "sha512-yyKza9S6z3ELKuf6w5n6VNUB0Osu6Z93RXPfMHLIlNWohu3KqxewLOq4lMXseYJ92GwkRAxd207Pr/Z98cwmvw==" }, "@mapbox/point-geometry": { "version": "0.1.0", @@ -3126,6 +3126,11 @@ } } }, + "earcut": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.1.5.tgz", + "integrity": "sha512-QFWC7ywTVLtvRAJTVp8ugsuuGQ5mVqNmJ1cRYeLrSHgP3nycr2RHTJob9OtM0v8ujuoKN0NY1a93J/omeTL1PA==" + }, "ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", @@ -7077,9 +7082,9 @@ } }, "mapbox-gl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/mapbox-gl/-/mapbox-gl-1.1.0.tgz", - "integrity": "sha512-ODwesguQJM7FobmSlv/qGJkmrzUTlIRe92dEBy587RV2k2QGsQDQUCk6/KE+lzVJuyk7WQappNkzhgagaxY5Eg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/mapbox-gl/-/mapbox-gl-1.1.1.tgz", + "integrity": "sha512-i57kASg8J/U/lJzBePyqTP2ImKUcx8FkHyCjb3ssWYaBBXHUeZ4STGXXfU9u1AQU9170PjDIJLubUUB1vLLSBQ==", "requires": { "@mapbox/geojson-rewind": "^0.4.0", "@mapbox/geojson-types": "^1.0.2", @@ -7107,11 +7112,6 @@ "vt-pbf": "^3.1.1" }, "dependencies": { - "earcut": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.1.5.tgz", - "integrity": "sha512-QFWC7ywTVLtvRAJTVp8ugsuuGQ5mVqNmJ1cRYeLrSHgP3nycr2RHTJob9OtM0v8ujuoKN0NY1a93J/omeTL1PA==" - }, "minimist": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", diff --git a/package.json b/package.json index ec787360bcb..945ae710e3e 100644 --- a/package.json +++ b/package.json @@ -91,7 +91,7 @@ "glslify": "^7.0.0", "has-hover": "^1.0.1", "has-passive-events": "^1.0.0", - "mapbox-gl": "1.1.0", + "mapbox-gl": "^1.1.1", "matrix-camera-controller": "^2.1.3", "mouse-change": "^1.4.0", "mouse-event-offset": "^3.0.2", diff --git a/src/plots/mapbox/constants.js b/src/plots/mapbox/constants.js index 858856223cd..37f0dc8f3c8 100644 --- a/src/plots/mapbox/constants.js +++ b/src/plots/mapbox/constants.js @@ -8,149 +8,152 @@ 'use strict'; -var requiredVersion = '1.1.0'; - -module.exports = { - requiredVersion: requiredVersion, - - styleUrlPrefix: 'mapbox://styles/mapbox/', - styleUrlSuffix: 'v9', - - styleValuesMapbox: ['basic', 'streets', 'outdoors', 'light', 'dark', 'satellite', 'satellite-streets'], - styleValueOSM: 'open-street-map', - styleValueDflt: 'basic', - - styles: { - 'open-street-map': { - id: 'osm', - version: 8, - sources: { - 'plotly-osm-tiles': { - type: 'raster', - attribution: '© OpenStreetMap', - tiles: [ - 'https://a.tile.openstreetmap.org/{z}/{x}/{y}.png', - 'https://b.tile.openstreetmap.org/{z}/{x}/{y}.png' - ], - tileSize: 256 - } - }, - layers: [{ - id: 'plotly-osm-tiles', +var requiredVersion = '1.1.1'; + +var stylesNonMapbox = { + 'open-street-map': { + id: 'osm', + version: 8, + sources: { + 'plotly-osm-tiles': { type: 'raster', - source: 'plotly-osm-tiles', - minzoom: 0, - maxzoom: 22 - }] + attribution: '© OpenStreetMap', + tiles: [ + 'https://a.tile.openstreetmap.org/{z}/{x}/{y}.png', + 'https://b.tile.openstreetmap.org/{z}/{x}/{y}.png' + ], + tileSize: 256 + } }, - 'white-bg': { + layers: [{ + id: 'plotly-osm-tiles', + type: 'raster', + source: 'plotly-osm-tiles', + minzoom: 0, + maxzoom: 22 + }] + }, + 'white-bg': { + id: 'white-bg', + version: 8, + sources: {}, + layers: [{ id: 'white-bg', - version: 8, - sources: {}, - layers: [{ - id: 'white-bg', - type: 'background', - paint: {'background-color': '#FFFFFF'}, - minzoom: 0, - maxzoom: 22 - }] - }, - 'carto-positron': { - id: 'carto-positron', - version: 8, - sources: { - 'plotly-carto-positron': { - type: 'raster', - attribution: '© CARTO', - tiles: ['https://cartodb-basemaps-c.global.ssl.fastly.net/light_all/{z}/{x}/{y}.png'], - tileSize: 256 - } - }, - layers: [{ - id: 'plotly-carto-positron', + type: 'background', + paint: {'background-color': '#FFFFFF'}, + minzoom: 0, + maxzoom: 22 + }] + }, + 'carto-positron': { + id: 'carto-positron', + version: 8, + sources: { + 'plotly-carto-positron': { type: 'raster', - source: 'plotly-carto-positron', - minzoom: 0, - maxzoom: 22 - }] + attribution: '© CARTO', + tiles: ['https://cartodb-basemaps-c.global.ssl.fastly.net/light_all/{z}/{x}/{y}.png'], + tileSize: 256 + } }, - 'carto-darkmatter': { - id: 'carto-darkmatter', - version: 8, - sources: { - 'plotly-carto-darkmatter': { - type: 'raster', - attribution: '© CARTO', - tiles: ['https://cartodb-basemaps-c.global.ssl.fastly.net/dark_all/{z}/{x}/{y}.png'], - tileSize: 256 - } - }, - layers: [{ - id: 'plotly-carto-darkmatter', + layers: [{ + id: 'plotly-carto-positron', + type: 'raster', + source: 'plotly-carto-positron', + minzoom: 0, + maxzoom: 22 + }] + }, + 'carto-darkmatter': { + id: 'carto-darkmatter', + version: 8, + sources: { + 'plotly-carto-darkmatter': { type: 'raster', - source: 'plotly-carto-darkmatter', - minzoom: 0, - maxzoom: 22 - }] + attribution: '© CARTO', + tiles: ['https://cartodb-basemaps-c.global.ssl.fastly.net/dark_all/{z}/{x}/{y}.png'], + tileSize: 256 + } }, - 'stamen-terrain': { - id: 'stamen-terrain', - version: 8, - sources: { - 'plotly-stamen-terrain': { - type: 'raster', - attribution: 'Map tiles by Stamen Design, under CC BY 3.0 | Data by OpenStreetMap, under ODbL.', - tiles: ['https://stamen-tiles.a.ssl.fastly.net/terrain/{z}/{x}/{y}.png'], - tileSize: 256 - } - }, - layers: [{ - id: 'plotly-stamen-terrain', + layers: [{ + id: 'plotly-carto-darkmatter', + type: 'raster', + source: 'plotly-carto-darkmatter', + minzoom: 0, + maxzoom: 22 + }] + }, + 'stamen-terrain': { + id: 'stamen-terrain', + version: 8, + sources: { + 'plotly-stamen-terrain': { type: 'raster', - source: 'plotly-stamen-terrain', - minzoom: 0, - maxzoom: 22 - }] + attribution: 'Map tiles by Stamen Design, under CC BY 3.0 | Data by OpenStreetMap, under ODbL.', + tiles: ['https://stamen-tiles.a.ssl.fastly.net/terrain/{z}/{x}/{y}.png'], + tileSize: 256 + } }, - 'stamen-toner': { - id: 'stamen-toner', - version: 8, - sources: { - 'plotly-stamen-toner': { - type: 'raster', - attribution: 'Map tiles by Stamen Design, under CC BY 3.0 | Data by OpenStreetMap, under ODbL.', - tiles: ['https://stamen-tiles.a.ssl.fastly.net/toner/{z}/{x}/{y}.png'], - tileSize: 256 - } - }, - layers: [{ - id: 'plotly-stamen-toner', + layers: [{ + id: 'plotly-stamen-terrain', + type: 'raster', + source: 'plotly-stamen-terrain', + minzoom: 0, + maxzoom: 22 + }] + }, + 'stamen-toner': { + id: 'stamen-toner', + version: 8, + sources: { + 'plotly-stamen-toner': { type: 'raster', - source: 'plotly-stamen-toner', - minzoom: 0, - maxzoom: 22 - }] + attribution: 'Map tiles by Stamen Design, under CC BY 3.0 | Data by OpenStreetMap, under ODbL.', + tiles: ['https://stamen-tiles.a.ssl.fastly.net/toner/{z}/{x}/{y}.png'], + tileSize: 256 + } }, - 'stamen-watercolor': { - id: 'stamen-watercolor', - version: 8, - sources: { - 'plotly-stamen-watercolor': { - type: 'raster', - attribution: 'Map tiles by Stamen Design, under CC BY 3.0 | Data by OpenStreetMap, under CC BY SA.', - tiles: ['https://stamen-tiles.a.ssl.fastly.net/watercolor/{z}/{x}/{y}.png'], - tileSize: 256 - } - }, - layers: [{ - id: 'plotly-stamen-watercolor', - type: 'raster', - source: 'plotly-stamen-watercolor', - minzoom: 0, - maxzoom: 22 - }] - } + layers: [{ + id: 'plotly-stamen-toner', + type: 'raster', + source: 'plotly-stamen-toner', + minzoom: 0, + maxzoom: 22 + }] }, + 'stamen-watercolor': { + id: 'stamen-watercolor', + version: 8, + sources: { + 'plotly-stamen-watercolor': { + type: 'raster', + attribution: 'Map tiles by Stamen Design, under CC BY 3.0 | Data by OpenStreetMap, under CC BY SA.', + tiles: ['https://stamen-tiles.a.ssl.fastly.net/watercolor/{z}/{x}/{y}.png'], + tileSize: 256 + } + }, + layers: [{ + id: 'plotly-stamen-watercolor', + type: 'raster', + source: 'plotly-stamen-watercolor', + minzoom: 0, + maxzoom: 22 + }] + } +}; + +var styleValuesNonMapbox = Object.keys(stylesNonMapbox); + +module.exports = { + requiredVersion: requiredVersion, + + styleUrlPrefix: 'mapbox://styles/mapbox/', + styleUrlSuffix: 'v9', + + styleValuesMapbox: ['basic', 'streets', 'outdoors', 'light', 'dark', 'satellite', 'satellite-streets'], + styleValueDflt: 'basic', + stylesNonMapbox: stylesNonMapbox, + styleValuesNonMapbox: styleValuesNonMapbox, traceLayerPrefix: 'plotly-trace-layer-', layoutLayerPrefix: 'plotly-layout-layer-', @@ -168,6 +171,12 @@ module.exports = { 'More info here: https://www.mapbox.com/help/define-access-token/' ].join('\n'), + missingStyleErrorMsg: [ + 'No valid mapbox style found, please set `mapbox.style` to one of:', + styleValuesNonMapbox.join(', '), + 'or register a Mapbox access token to use a Mapbox-served style.' + ].join('\n'), + multipleTokensErrorMsg: [ 'Set multiple mapbox access token across different mapbox subplot,', 'using first token found as mapbox-gl does not allow multiple' + diff --git a/src/plots/mapbox/index.js b/src/plots/mapbox/index.js index 98643e4186f..8563582efbf 100644 --- a/src/plots/mapbox/index.js +++ b/src/plots/mapbox/index.js @@ -220,20 +220,23 @@ function findAccessToken(gd, mapboxIds) { var tokensUseful = []; var tokensListed = []; + var hasOneSetMapboxStyle = false; var wontWork = false; // Take the first token we find in a mapbox subplot. // These default to the context value but may be overridden. for(var i = 0; i < mapboxIds.length; i++) { var opts = fullLayout[mapboxIds[i]]; - var style = opts.style; var token = opts.accesstoken; - if(typeof style === 'string' && constants.styleValuesMapbox.indexOf(style) !== -1) { + if(isMapboxStyle(opts.style)) { if(token) { Lib.pushUnique(tokensUseful, token); } else { - Lib.error('Uses Mapbox map style, but did not set an access token.'); + if(isMapboxStyle(opts._input.style)) { + Lib.error('Uses Mapbox map style, but did not set an access token.'); + hasOneSetMapboxStyle = true; + } wontWork = true; } } @@ -244,7 +247,10 @@ function findAccessToken(gd, mapboxIds) { } if(wontWork) { - throw new Error(constants.noAccessTokenErrorMsg); + var msg = hasOneSetMapboxStyle ? + constants.noAccessTokenErrorMsg : + constants.missingStyleErrorMsg; + throw new Error(msg); } if(tokensUseful.length) { @@ -263,6 +269,10 @@ function findAccessToken(gd, mapboxIds) { } } +function isMapboxStyle(s) { + return typeof s === 'string' && constants.styleValuesMapbox.indexOf(s) !== -1; +} + exports.updateFx = function(gd) { var fullLayout = gd._fullLayout; var subplotIds = fullLayout._subplots[MAPBOX]; diff --git a/src/plots/mapbox/layout_attributes.js b/src/plots/mapbox/layout_attributes.js index 98afe21c7d3..7cdec7305eb 100644 --- a/src/plots/mapbox/layout_attributes.js +++ b/src/plots/mapbox/layout_attributes.js @@ -39,19 +39,38 @@ var attrs = module.exports = overrideAll({ description: [ 'Sets the mapbox access token to be used for this mapbox map.', 'Alternatively, the mapbox access token can be set in the', - 'configuration options under `mapboxAccessToken`.' + 'configuration options under `mapboxAccessToken`.', + 'Note that accessToken are only required when `style`', + '(e.g with values :', constants.styleValuesMapbox.join(', '), ')', + 'and/or a layout layer references the Mapbox server.' ].join(' ') }, style: { valType: 'any', - values: constants.styleValuesMapbox.concat(constants.styleValueOSM), + values: constants.styleValuesMapbox.concat(Object.keys(constants.styleValuesNonMapbox)), dflt: constants.styleValueDflt, role: 'style', description: [ - 'Sets the Mapbox map style.', - 'Either input one of the default Mapbox style names or the URL to a custom style', - 'or a valid Mapbox style JSON.', - 'From OpenStreetMap raster tiles, use *open-street-map*.' + 'Defines the map layers that are rendered by default below the trace layers defined in `data`,', + 'which are themselves by default rendered below the layers defined in `layout.mapbox.layers`.', + '', + 'These layers can be defined either explicitly as a Mapbox Style object which can contain multiple', + 'layer definitions that load data from any public or private Tile Map Service (TMS or XYZ) or Web Map Service (WMS)', + 'or implicitly by using one of the built-in style objects which use WMSes which do not require any', + 'access tokens, or by using a default Mapbox style or custom Mapbox style URL, both of', + 'which require a Mapbox access token', + '', + 'Note that Mapbox access token can be set in the `accesstoken` attribute', + 'or in the `mapboxAccessToken` config option.', + '', + 'Mapbox Style objects are of the form described in the Mapbox GL JS documentation available at', + 'https://docs.mapbox.com/mapbox-gl-js/style-spec', + '', + 'The built-in plotly.js styles objects are:', constants.styleValuesNonMapbox.join(', '), + '', + 'The built-in Mapbox styles are:', constants.styleValuesMapbox.join(', '), + '', + 'Mapbox style URLs are of the form: mapbox://mapbox.mapbox--' ].join(' ') }, @@ -106,7 +125,8 @@ var attrs = module.exports = overrideAll({ dflt: 'geojson', role: 'info', description: [ - 'Sets the source type for this layer.' + 'Sets the source type for this layer,', + 'that is the type of the layer data.' ].join(' ') }, @@ -115,9 +135,11 @@ var attrs = module.exports = overrideAll({ role: 'info', description: [ 'Sets the source data for this layer (mapbox.layer.source).', - 'Source can be either a URL,', - 'a geojson object (with `sourcetype` set to *geojson*)', - 'or an array of tile URLS (with `sourcetype` set to *vector*).' + 'When `sourcetype` is set to *geojson*, `source` can be a URL to a GeoJSON', + 'or a GeoJSON object.', + 'When `sourcetype` is set to *vector* or *raster*, `source` can be a URL or', + 'an array of tile URLs.', + 'When `sourcetype` is set to *image*, `source` can be a URL to an image.' ].join(' ') }, @@ -145,10 +167,15 @@ var attrs = module.exports = overrideAll({ dflt: 'circle', role: 'info', description: [ - 'Sets the layer type (mapbox.layer.type).', - 'Support for *raster*, *background* types is coming soon.', - 'Note that *line* and *fill* are not compatible with Point', - 'GeoJSON geometries.' + 'Sets the layer type,', + 'that is the how the layer data set in `source` will be rendered', + 'With `sourcetype` set to *geojson*, the following values are allowed:', + '*circle*, *line*, *fill* and *symbol*.', + 'but note that *line* and *fill* are not compatible with Point', + 'GeoJSON geometries.', + 'With `sourcetype` set to *vector*, the following values are allowed:', + ' *circle*, *line*, *fill* and *symbol*.', + 'With `sourcetype` set to *raster* or `*image*`, only the *raster* value is allowed.' ].join(' ') }, diff --git a/src/plots/mapbox/mapbox.js b/src/plots/mapbox/mapbox.js index 8fdc2d55b7d..86e678ca228 100644 --- a/src/plots/mapbox/mapbox.js +++ b/src/plots/mapbox/mapbox.js @@ -769,8 +769,8 @@ function getStyleObj(val) { if(constants.styleValuesMapbox.indexOf(val) !== -1) { styleObj.style = convertStyleVal(val); - } else if(constants.styles[val]) { - styleObj.style = constants.styles[val]; + } else if(constants.stylesNonMapbox[val]) { + styleObj.style = constants.stylesNonMapbox[val]; } else { styleObj.style = val; } diff --git a/test/jasmine/tests/mapbox_test.js b/test/jasmine/tests/mapbox_test.js index fac5e2ef259..9916cb177b6 100644 --- a/test/jasmine/tests/mapbox_test.js +++ b/test/jasmine/tests/mapbox_test.js @@ -283,7 +283,7 @@ describe('mapbox credentials', function() { }); }); - it('@gl should throw error if token is not registered', function() { + it('@gl should throw error when no non-mapbox style is set and missing a mapbox access token token', function() { spyOn(Lib, 'error'); expect(function() { @@ -292,6 +292,22 @@ describe('mapbox credentials', function() { lon: [10, 20, 30], lat: [10, 20, 30] }]); + }).toThrow(new Error(constants.missingStyleErrorMsg)); + + expect(Lib.error).toHaveBeenCalledTimes(0); + }, LONG_TIMEOUT_INTERVAL); + + it('@gl should throw error when setting a Mapbox style w/o a registered token', function() { + spyOn(Lib, 'error'); + + expect(function() { + Plotly.plot(gd, [{ + type: 'scattermapbox', + lon: [10, 20, 30], + lat: [10, 20, 30] + }], { + mapbox: {style: 'basic'} + }); }).toThrow(new Error(constants.noAccessTokenErrorMsg)); expect(Lib.error).toHaveBeenCalledWith('Uses Mapbox map style, but did not set an access token.');