Skip to content

Commit de65c44

Browse files
authored
Merge pull request #626 from plotly/mapbox
Introducing plotly.js + Mapbox GL
2 parents 63e41e0 + df77966 commit de65c44

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+4427
-7
lines changed

CONTRIBUTING.md

+12
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,18 @@ which shows the baseline image, the generated image, the diff and the json mocks
157157

158158
To view the results of a run on CircleCI, download the `build/test_images/` and `build/test_images_diff/` artifacts into your local repo and then run `npm run start-image_viewer`.
159159

160+
### Note on testing our `mapbox-gl` integration
161+
162+
Creating `mapbox-gl` graphs requires an
163+
[`accessToken`](https://www.mapbox.com/help/define-access-token/). To make sure
164+
that mapbox image and jasmine tests run properly, locate your Mapbox access
165+
token and run:
166+
167+
168+
```bash
169+
export MAPBOX_ACCESS_TOKEN="<your access token>" && npm run pretest
170+
```
171+
160172

161173
## Repo organization
162174

circle.yml

+2-1
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,13 @@ dependencies:
1515
- docker pull plotly/testbed:latest
1616
post:
1717
- npm run cibuild
18+
- npm run pretest
1819
- docker run -d --name mytestbed -v $PWD:/var/www/streambed/image_server/plotly.js -p 9010:9010 plotly/testbed:latest
1920
- sudo ./tasks/run_in_testbed.sh mytestbed "cp -f test/image/index.html ../server_app/index.html"
2021
- wget --server-response --spider --tries=8 --retry-connrefused http://localhost:9010/ping
2122
test:
2223
override:
23-
- sudo ./tasks/run_in_testbed.sh mytestbed "node test/image/compare_pixels_test.js"
24+
- sudo ./tasks/run_in_testbed.sh mytestbed "export CIRCLECI=1 && node test/image/compare_pixels_test.js"
2425
- sudo ./tasks/run_in_testbed.sh mytestbed "node test/image/export_test.js"
2526
- npm run citest-jasmine
2627
- npm run test-bundle

devtools/test_dashboard/devtools.js

+8
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
var Fuse = require('fuse.js');
66
var mocks = require('../../build/test_dashboard_mocks.json');
7+
var credentials = require('../../build/credentials.json');
78

89
// put d3 in window scope
910
var d3 = window.d3 = Plotly.d3;
@@ -14,8 +15,15 @@ var Tabs = {
1415
// Set plot config options
1516
setPlotConfig: function() {
1617
Plotly.setPlotConfig({
18+
1719
// use local topojson files
1820
topojsonURL: '../../dist/topojson/',
21+
22+
// register mapbox access token
23+
// run `npm run preset` if you haven't yet
24+
mapboxAccessToken: credentials.MAPBOX_ACCESS_TOKEN,
25+
26+
// show all logs in console
1927
logging: 2
2028
});
2129
},

lib/scattermapbox.js

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/**
2+
* Copyright 2012-2016, Plotly, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
module.exports = require('../src/traces/scattermapbox');

package.json

+2
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
"watch": "node tasks/watch_plotly.js",
3030
"lint": "eslint . || true",
3131
"lint-fix": "eslint . --fix",
32+
"pretest": "node tasks/pretest.js",
3233
"test-jasmine": "karma start test/jasmine/karma.conf.js",
3334
"citest-jasmine": "karma start test/jasmine/karma.ciconf.js",
3435
"test-image": "./tasks/test_image.sh",
@@ -70,6 +71,7 @@
7071
"gl-select-box": "^1.0.1",
7172
"gl-spikes2d": "^1.0.1",
7273
"gl-surface3d": "^1.2.3",
74+
"mapbox-gl": "^0.18.0",
7375
"mouse-change": "^1.1.1",
7476
"mouse-wheel": "^1.0.2",
7577
"ndarray": "^1.0.16",

src/plot_api/plot_api.js

+14-1
Original file line numberDiff line numberDiff line change
@@ -1652,7 +1652,7 @@ Plotly.restyle = function restyle(gd, astr, val, traces) {
16521652
// to not go through a full replot
16531653
var doPlotWhiteList = ['cartesian', 'pie', 'ternary'];
16541654
fullLayout._basePlotModules.forEach(function(_module) {
1655-
if(doPlotWhiteList.indexOf(_module.name) === -1) doplot = true;
1655+
if(doPlotWhiteList.indexOf(_module.name) === -1) docalc = true;
16561656
});
16571657

16581658
// make a new empty vals array for undoit
@@ -2298,6 +2298,19 @@ Plotly.relayout = function relayout(gd, astr, val) {
22982298
Images.supplyLayoutDefaults(gd.layout, gd._fullLayout);
22992299
Images.draw(gd);
23002300
}
2301+
else if(p.parts[0] === 'mapbox' && p.parts[1] === 'layers') {
2302+
Lib.extendDeepAll(gd.layout, Lib.objectFromPath(ai, vi));
2303+
2304+
// append empty container to mapbox.layers
2305+
// so that relinkPrivateKeys does not complain
2306+
2307+
var fullLayers = (gd._fullLayout.mapbox || {}).layers || [];
2308+
var diff = (p.parts[2] + 1) - fullLayers.length;
2309+
2310+
for(i = 0; i < diff; i++) fullLayers.push({});
2311+
2312+
doplot = true;
2313+
}
23012314
// alter gd.layout
23022315
else {
23032316
// check whether we can short-circuit a full redraw

src/plot_api/plot_config.js

+3
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,9 @@ module.exports = {
8686
// URL to topojson files used in geo charts
8787
topojsonURL: 'https://cdn.plot.ly/',
8888

89+
// Mapbox access token (required to plot mapbox trace types)
90+
mapboxAccessToken: null,
91+
8992
// Turn all console logging on or off (errors will be thrown)
9093
// This should ONLY be set via Plotly.setPlotConfig
9194
logging: false

src/plots/layout_attributes.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ module.exports = {
183183
'annotations': 'Annotations',
184184
'shapes': 'Shapes',
185185
'images': 'Images',
186-
'ternary': 'ternary'
186+
'ternary': 'ternary',
187+
'mapbox': 'mapbox'
187188
}
188189
};

src/plots/mapbox/constants.js

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/**
2+
* Copyright 2012-2016, Plotly, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
10+
'use strict';
11+
12+
13+
module.exports = {
14+
styleUrlPrefix: 'mapbox://styles/mapbox/',
15+
styleUrlSuffix: 'v9',
16+
17+
controlContainerClassName: 'mapboxgl-control-container',
18+
19+
noAccessTokenErrorMsg: [
20+
'Missing Mapbox access token.',
21+
'Mapbox trace type require a Mapbox access token to be registered.',
22+
'For example:',
23+
' Plotly.plot(gd, data, layout, { mapboxAccessToken: \'my-access-token\' });',
24+
'More info here: https://www.mapbox.com/help/define-access-token/'
25+
].join('\n'),
26+
27+
mapOnErrorMsg: 'Mapbox error.'
28+
};

src/plots/mapbox/index.js

+133
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
/**
2+
* Copyright 2012-2016, Plotly, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
10+
'use strict';
11+
12+
var mapboxgl = require('mapbox-gl');
13+
14+
var Plots = require('../plots');
15+
var xmlnsNamespaces = require('../../constants/xmlns_namespaces');
16+
17+
var createMapbox = require('./mapbox');
18+
var constants = require('./constants');
19+
20+
21+
exports.name = 'mapbox';
22+
23+
exports.attr = 'subplot';
24+
25+
exports.idRoot = 'mapbox';
26+
27+
exports.idRegex = /^mapbox([2-9]|[1-9][0-9]+)?$/;
28+
29+
exports.attrRegex = /^mapbox([2-9]|[1-9][0-9]+)?$/;
30+
31+
exports.attributes = {
32+
subplot: {
33+
valType: 'subplotid',
34+
role: 'info',
35+
dflt: 'mapbox',
36+
description: [
37+
'Sets a reference between this trace\'s data coordinates and',
38+
'a mapbox subplot.',
39+
'If *mapbox* (the default value), the data refer to `layout.mapbox`.',
40+
'If *mapbox2*, the data refer to `layout.mapbox2`, and so on.'
41+
].join(' ')
42+
}
43+
};
44+
45+
exports.layoutAttributes = require('./layout_attributes');
46+
47+
exports.supplyLayoutDefaults = require('./layout_defaults');
48+
49+
exports.plot = function plotMapbox(gd) {
50+
51+
if(!gd._context.mapboxAccessToken) {
52+
throw new Error(constants.noAccessTokenErrorMsg);
53+
}
54+
else {
55+
mapboxgl.accessToken = gd._context.mapboxAccessToken;
56+
}
57+
58+
var fullLayout = gd._fullLayout,
59+
calcData = gd.calcdata,
60+
mapboxIds = Plots.getSubplotIds(fullLayout, 'mapbox');
61+
62+
for(var i = 0; i < mapboxIds.length; i++) {
63+
var id = mapboxIds[i],
64+
subplotCalcData = getSubplotCalcData(calcData, id),
65+
mapbox = fullLayout[id]._subplot;
66+
67+
if(!mapbox) {
68+
mapbox = createMapbox({
69+
gd: gd,
70+
container: fullLayout._glcontainer.node(),
71+
id: id,
72+
fullLayout: fullLayout,
73+
staticPlot: gd._context.staticPlot
74+
});
75+
76+
fullLayout[id]._subplot = mapbox;
77+
}
78+
79+
mapbox.plot(subplotCalcData, fullLayout, gd._promises);
80+
}
81+
};
82+
83+
exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout) {
84+
var oldMapboxKeys = Plots.getSubplotIds(oldFullLayout, 'mapbox');
85+
86+
for(var i = 0; i < oldMapboxKeys.length; i++) {
87+
var oldMapboxKey = oldMapboxKeys[i];
88+
89+
if(!newFullLayout[oldMapboxKey] && !!oldFullLayout[oldMapboxKey]._subplot) {
90+
oldFullLayout[oldMapboxKey]._subplot.destroy();
91+
}
92+
}
93+
};
94+
95+
exports.toSVG = function(gd) {
96+
var fullLayout = gd._fullLayout,
97+
subplotIds = Plots.getSubplotIds(fullLayout, 'mapbox'),
98+
size = fullLayout._size;
99+
100+
for(var i = 0; i < subplotIds.length; i++) {
101+
var opts = fullLayout[subplotIds[i]],
102+
domain = opts.domain,
103+
mapbox = opts._subplot;
104+
105+
var imageData = mapbox.toImage('png');
106+
var image = fullLayout._glimages.append('svg:image');
107+
108+
image.attr({
109+
xmlns: xmlnsNamespaces.svg,
110+
'xlink:href': imageData,
111+
x: size.l + size.w * domain.x[0],
112+
y: size.t + size.h * (1 - domain.y[1]),
113+
width: size.w * (domain.x[1] - domain.x[0]),
114+
height: size.h * (domain.y[1] - domain.y[0]),
115+
preserveAspectRatio: 'none'
116+
});
117+
118+
mapbox.destroy();
119+
}
120+
};
121+
122+
function getSubplotCalcData(calcData, id) {
123+
var subplotCalcData = [];
124+
125+
for(var i = 0; i < calcData.length; i++) {
126+
var calcTrace = calcData[i],
127+
trace = calcTrace[0].trace;
128+
129+
if(trace.subplot === id) subplotCalcData.push(calcTrace);
130+
}
131+
132+
return subplotCalcData;
133+
}

0 commit comments

Comments
 (0)