Skip to content

Commit ec80e39

Browse files
authored
Merge pull request plotly#681 from plotly/mapbox-tweaks
Miscellaneous mapbox tweaks
2 parents 050d40c + 752c419 commit ec80e39

14 files changed

+348
-132
lines changed

src/plots/mapbox/convert_text_opts.js

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
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 Lib = require('../../lib');
13+
14+
15+
/**
16+
* Convert plotly.js 'textposition' to mapbox-gl 'anchor' and 'offset'
17+
* (with the help of the icon size).
18+
*
19+
* @param {string} textpostion : plotly.js textposition value
20+
* @param {number} iconSize : plotly.js icon size (e.g. marker.size for traces)
21+
*
22+
* @return {object}
23+
* - anchor
24+
* - offset
25+
*/
26+
module.exports = function convertTextOpts(textposition, iconSize) {
27+
var parts = textposition.split(' '),
28+
vPos = parts[0],
29+
hPos = parts[1];
30+
31+
// ballpack values
32+
var factor = Array.isArray(iconSize) ? Lib.mean(iconSize) : iconSize,
33+
xInc = 0.5 + (factor / 100),
34+
yInc = 1.5 + (factor / 100);
35+
36+
var anchorVals = ['', ''],
37+
offset = [0, 0];
38+
39+
switch(vPos) {
40+
case 'top':
41+
anchorVals[0] = 'top';
42+
offset[1] = -yInc;
43+
break;
44+
case 'bottom':
45+
anchorVals[0] = 'bottom';
46+
offset[1] = yInc;
47+
break;
48+
}
49+
50+
switch(hPos) {
51+
case 'left':
52+
anchorVals[1] = 'right';
53+
offset[0] = -xInc;
54+
break;
55+
case 'right':
56+
anchorVals[1] = 'left';
57+
offset[0] = xInc;
58+
break;
59+
}
60+
61+
// Mapbox text-anchor must be one of:
62+
// center, left, right, top, bottom,
63+
// top-left, top-right, bottom-left, bottom-right
64+
65+
var anchor;
66+
if(anchorVals[0] && anchorVals[1]) anchor = anchorVals.join('-');
67+
else if(anchorVals[0]) anchor = anchorVals[0];
68+
else if(anchorVals[1]) anchor = anchorVals[1];
69+
else anchor = 'center';
70+
71+
return { anchor: anchor, offset: offset };
72+
};

src/plots/mapbox/layers.js

+53-12
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
'use strict';
1111

1212
var Lib = require('../../lib');
13+
var convertTextOpts = require('./convert_text_opts');
1314

1415

1516
function MapboxLayer(mapbox, index) {
@@ -45,9 +46,15 @@ proto.update = function update(opts) {
4546
};
4647

4748
proto.needsNewSource = function(opts) {
49+
50+
// for some reason changing layer to 'fill' or 'symbol'
51+
// w/o changing the source throws an exception in mapbox-gl 0.18 ;
52+
// stay safe and make new source on type changes
53+
4854
return (
4955
this.sourceType !== opts.sourcetype ||
50-
this.source !== opts.source
56+
this.source !== opts.source ||
57+
this.layerType !== opts.type
5158
);
5259
};
5360

@@ -95,10 +102,11 @@ proto.updateLayer = function(opts) {
95102
};
96103

97104
proto.updateStyle = function(opts) {
98-
var paintOpts = convertPaintOpts(opts);
105+
var convertedOpts = convertOpts(opts);
99106

100107
if(isVisible(opts)) {
101-
this.mapbox.setOptions(this.idLayer, 'setPaintProperty', paintOpts);
108+
this.mapbox.setOptions(this.idLayer, 'setLayoutProperty', convertedOpts.layout);
109+
this.mapbox.setOptions(this.idLayer, 'setPaintProperty', convertedOpts.paint);
102110
}
103111
};
104112

@@ -121,31 +129,64 @@ function isVisible(opts) {
121129
);
122130
}
123131

124-
function convertPaintOpts(opts) {
125-
var paintOpts = {};
132+
function convertOpts(opts) {
133+
var layout = {},
134+
paint = {};
126135

127136
switch(opts.type) {
128137

138+
case 'circle':
139+
Lib.extendFlat(paint, {
140+
'circle-radius': opts.circle.radius,
141+
'circle-color': opts.color,
142+
'circle-opacity': opts.opacity
143+
});
144+
break;
145+
129146
case 'line':
130-
Lib.extendFlat(paintOpts, {
147+
Lib.extendFlat(paint, {
131148
'line-width': opts.line.width,
132-
'line-color': opts.line.color,
149+
'line-color': opts.color,
133150
'line-opacity': opts.opacity
134151
});
135152
break;
136153

137154
case 'fill':
138-
Lib.extendFlat(paintOpts, {
139-
'fill-color': opts.fillcolor,
140-
'fill-outline-color': opts.line.color,
155+
Lib.extendFlat(paint, {
156+
'fill-color': opts.color,
157+
'fill-outline-color': opts.fill.outlinecolor,
141158
'fill-opacity': opts.opacity
142159

143-
// no way to pass line.width at the moment
160+
// no way to pass specify outline width at the moment
161+
});
162+
break;
163+
164+
case 'symbol':
165+
var symbol = opts.symbol,
166+
textOpts = convertTextOpts(symbol.textposition, symbol.iconsize);
167+
168+
Lib.extendFlat(layout, {
169+
'icon-image': symbol.icon + '-15',
170+
'icon-size': symbol.iconsize / 10,
171+
172+
'text-field': symbol.text,
173+
'text-size': symbol.textfont.size,
174+
'text-anchor': textOpts.anchor,
175+
'text-offset': textOpts.offset
176+
177+
// TODO font family
178+
//'text-font': symbol.textfont.family.split(', '),
179+
});
180+
181+
Lib.extendFlat(paint, {
182+
'icon-color': opts.color,
183+
'text-color': symbol.textfont.color,
184+
'text-opacity': opts.opacity
144185
});
145186
break;
146187
}
147188

148-
return paintOpts;
189+
return { layout: layout, paint: paint };
149190
}
150191

151192
function convertSourceOpts(opts) {

src/plots/mapbox/layout_attributes.js

+96-16
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,10 @@
99

1010
'use strict';
1111

12-
var scatterMapboxAttrs = require('../../traces/scattermapbox/attributes');
12+
var Lib = require('../../lib');
1313
var defaultLine = require('../../components/color').defaultLine;
14-
var extendFlat = require('../../lib').extendFlat;
15-
16-
var lineAttrs = scatterMapboxAttrs.line;
14+
var fontAttrs = require('../font_attributes');
15+
var textposition = require('../../traces/scatter/attributes').textposition;
1716

1817

1918
module.exports = {
@@ -129,15 +128,18 @@ module.exports = {
129128

130129
type: {
131130
valType: 'enumerated',
132-
values: ['line', 'fill'],
133-
dflt: 'line',
131+
values: ['circle', 'line', 'fill', 'symbol'],
132+
dflt: 'circle',
134133
role: 'info',
135134
description: [
136135
'Sets the layer type.',
137-
'Support for *raster*, *background* types is coming soon.'
136+
'Support for *raster*, *background* types is coming soon.',
137+
'Note that *line* and *fill* are not compatible with Point',
138+
'GeoJSON geometries.'
138139
].join(' ')
139140
},
140141

142+
// attributes shared between all types
141143
below: {
142144
valType: 'string',
143145
dflt: '',
@@ -149,23 +151,101 @@ module.exports = {
149151
'the layer will be inserted above every existing layer.'
150152
].join(' ')
151153
},
152-
153-
line: {
154-
color: extendFlat({}, lineAttrs.color, {
155-
dflt: defaultLine
156-
}),
157-
width: lineAttrs.width
154+
color: {
155+
valType: 'color',
156+
dflt: defaultLine,
157+
role: 'style',
158+
description: [
159+
'Sets the primary layer color.',
160+
'If `type` is *circle*, color corresponds to the circle color',
161+
'If `type` is *line*, color corresponds to the line color',
162+
'If `type` is *fill*, color corresponds to the fill color',
163+
'If `type` is *symbol*, color corresponds to the icon color'
164+
].join(' ')
158165
},
159-
160-
fillcolor: scatterMapboxAttrs.fillcolor,
161-
162166
opacity: {
163167
valType: 'number',
164168
min: 0,
165169
max: 1,
166170
dflt: 1,
167171
role: 'info',
168172
description: 'Sets the opacity of the layer.'
173+
},
174+
175+
// type-specific style attributes
176+
circle: {
177+
radius: {
178+
valType: 'number',
179+
dflt: 15,
180+
role: 'style',
181+
description: [
182+
'Sets the circle radius.',
183+
'Has an effect only when `type` is set to *circle*.'
184+
].join(' ')
185+
}
186+
},
187+
188+
line: {
189+
width: {
190+
valType: 'number',
191+
dflt: 2,
192+
role: 'style',
193+
description: [
194+
'Sets the line radius.',
195+
'Has an effect only when `type` is set to *line*.'
196+
].join(' ')
197+
}
198+
},
199+
200+
fill: {
201+
outlinecolor: {
202+
valType: 'color',
203+
dflt: defaultLine,
204+
role: 'style',
205+
description: [
206+
'Sets the fill outline color.',
207+
'Has an effect only when `type` is set to *fill*.'
208+
].join(' ')
209+
}
210+
},
211+
212+
symbol: {
213+
icon: {
214+
valType: 'string',
215+
dflt: 'marker',
216+
role: 'style',
217+
description: [
218+
'Sets the symbol icon image.',
219+
'Full list: https://www.mapbox.com/maki-icons/'
220+
].join(' ')
221+
},
222+
iconsize: {
223+
valType: 'number',
224+
dflt: 10,
225+
role: 'style',
226+
description: [
227+
'Sets the symbol icon size.',
228+
'Has an effect only when `type` is set to *symbol*.'
229+
].join(' ')
230+
},
231+
text: {
232+
valType: 'string',
233+
dflt: '',
234+
role: 'info',
235+
description: [
236+
'Sets the symbol text.'
237+
].join(' ')
238+
},
239+
textfont: Lib.extendDeep({}, fontAttrs, {
240+
description: [
241+
'Sets the icon text font.',
242+
'Has an effect only when `type` is set to *symbol*.'
243+
].join(' '),
244+
family: {
245+
dflt: 'Open Sans Regular, Arial Unicode MS Regular'
246+
}
247+
}),
248+
textposition: Lib.extendFlat({}, textposition, { arrayOk: false })
169249
}
170250
}
171251

src/plots/mapbox/layout_defaults.js

+22-10
Original file line numberDiff line numberDiff line change
@@ -49,29 +49,41 @@ function handleLayerDefaults(containerIn, containerOut) {
4949
}
5050

5151
for(var i = 0; i < layersIn.length; i++) {
52-
layerIn = layersIn[i];
52+
layerIn = layersIn[i] || {};
5353
layerOut = {};
5454

5555
var sourceType = coerce('sourcetype');
5656
coerce('source');
5757

5858
if(sourceType === 'vector') coerce('sourcelayer');
5959

60-
// maybe add smart default based off 'fillcolor' ???
60+
// maybe add smart default based off GeoJSON geometry?
6161
var type = coerce('type');
6262

63-
var lineColor;
64-
if(type === 'line' || type === 'fill') {
65-
lineColor = coerce('line.color');
63+
coerce('below');
64+
coerce('color');
65+
coerce('opacity');
66+
67+
if(type === 'circle') {
68+
coerce('circle.radius');
6669
}
6770

68-
// no way to pass line.width to fill layers
69-
if(type === 'line') coerce('line.width');
71+
if(type === 'line') {
72+
coerce('line.width');
73+
}
7074

71-
if(type === 'fill') coerce('fillcolor', lineColor);
75+
if(type === 'fill') {
76+
coerce('fill.outlinecolor');
77+
}
7278

73-
coerce('below');
74-
coerce('opacity');
79+
if(type === 'symbol') {
80+
coerce('symbol.icon');
81+
coerce('symbol.iconsize');
82+
83+
coerce('symbol.text');
84+
Lib.coerceFont(coerce, 'symbol.textfont');
85+
coerce('symbol.textposition');
86+
}
7587

7688
layersOut.push(layerOut);
7789
}

0 commit comments

Comments
 (0)