Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

mapbox: add attributions #4069

Merged
merged 8 commits into from
Jul 22, 2019
Merged
186 changes: 167 additions & 19 deletions src/plots/mapbox/constants.js

Large diffs are not rendered by default.

89 changes: 88 additions & 1 deletion src/plots/mapbox/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ var mapboxgl = require('mapbox-gl');
var Lib = require('../../lib');
var getSubplotCalcData = require('../../plots/get_data').getSubplotCalcData;
var xmlnsNamespaces = require('../../constants/xmlns_namespaces');
var d3 = require('d3');
var Drawing = require('../../components/drawing');
var svgTextUtils = require('../../lib/svg_text_utils');

var Mapbox = require('./mapbox');

Expand Down Expand Up @@ -118,7 +121,91 @@ exports.toSVG = function(gd) {
preserveAspectRatio: 'none'
});

mapbox.destroy();
var subplotDiv = d3.select(opts._subplot.div);

// Append logo if visible
var hidden = subplotDiv.select('.mapboxgl-ctrl-logo').node().offsetParent === null;
if(!hidden) {
var logo = fullLayout._glimages.append('g');
logo.attr('transform', 'translate(' + (size.l + size.w * domain.x[0] + 10) + ', ' + (size.t + size.h * (1 - domain.y[0]) - 31) + ')');
logo.append('path')
.attr('d', constants.mapboxLogo.path0)
.style({
opacity: 0.9,
fill: '#ffffff',
'enable-background': 'new'
});

logo.append('path')
.attr('d', constants.mapboxLogo.path1)
.style('opacity', 0.35)
.style('enable-background', 'new');

logo.append('path')
.attr('d', constants.mapboxLogo.path2)
.style('opacity', 0.35)
.style('enable-background', 'new');

logo.append('polygon')
.attr('points', constants.mapboxLogo.polygon)
.style({
opacity: 0.9,
fill: '#ffffff',
'enable-background': 'new'
});
}

// Add attributions
var attributions = subplotDiv
.select('.mapboxgl-ctrl-attrib').text()
.replace('Improve this map', '');

var attributionGroup = fullLayout._glimages.append('g');

var attributionText = attributionGroup.append('text');
attributionText
.text(attributions)
.classed('static-attribution', true)
.attr({
'font-size': 12,
'font-family': 'Arial',
'color': 'rgba(0, 0, 0, 0.75)',
'text-anchor': 'end',
'data-unformatted': attributions
});

var bBox = Drawing.bBox(attributionText.node());

// Break into multiple lines twice larger than domain
var maxWidth = size.w * (domain.x[1] - domain.x[0]);
if((bBox.width > maxWidth / 2)) {
var multilineAttributions = attributions.split('|').join('<br>');
attributionText
.text(multilineAttributions)
.attr('data-unformatted', multilineAttributions)
.call(svgTextUtils.convertToTspans, gd);

bBox = Drawing.bBox(attributionText.node());
}
attributionText.attr('transform', 'translate(-3, ' + (-bBox.height + 8) + ')');

// Draw white rectangle behind text
attributionGroup
.insert('rect', '.static-attribution')
.attr({
x: -bBox.width - 6,
y: -bBox.height - 3,
width: bBox.width + 6,
height: bBox.height + 3,
fill: 'rgba(255, 255, 255, 0.75)'
});

// Scale down if larger than domain
var scaleRatio = 1;
if((bBox.width + 6) > maxWidth) scaleRatio = maxWidth / (bBox.width + 6);

var offset = [(size.l + size.w * domain.x[1]), (size.t + size.h * (1 - domain.y[0]))];
attributionGroup.attr('transform', 'translate(' + offset[0] + ',' + offset[1] + ') scale(' + scaleRatio + ')');
}
};

Expand Down
2 changes: 2 additions & 0 deletions src/plots/mapbox/layers.js
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,8 @@ function convertSourceOpts(opts) {

sourceOpts[field] = source;

if(opts.sourceattribution) sourceOpts.attribution = opts.sourceattribution;

return sourceOpts;
}

Expand Down
8 changes: 8 additions & 0 deletions src/plots/mapbox/layout_attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,14 @@ var attrs = module.exports = overrideAll({
].join(' ')
},

sourceattribution: {
valType: 'string',
role: 'info',
description: [
'Sets the attribution for this source.'
].join(' ')
},

type: {
valType: 'enumerated',
values: ['circle', 'line', 'fill', 'symbol', 'raster'],
Expand Down
1 change: 1 addition & 0 deletions src/plots/mapbox/layout_defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ function handleLayerDefaults(layerIn, layerOut) {
var mustBeRasterLayer = sourceType === 'raster' || sourceType === 'image';

coerce('source');
coerce('sourceattribution');

if(sourceType === 'vector') {
coerce('sourcelayer');
Expand Down
14 changes: 10 additions & 4 deletions src/plots/mapbox/mapbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,14 @@ proto.createMap = function(calcData, fullLayout, resolve, reject) {
preserveDrawingBuffer: self.isStatic,

doubleClickZoom: false,
boxZoom: false
});
boxZoom: false,

attributionControl: false
})
.addControl(new mapboxgl.AttributionControl({
compact: true
}));


// make sure canvas does not inherit left and top css
map._canvas.style.left = '0px';
Expand Down Expand Up @@ -641,8 +647,8 @@ function getStyleObj(val) {

if(constants.styleValuesMapbox.indexOf(val) !== -1) {
styleObj.style = convertStyleVal(val);
} else if(val === constants.styleValueOSM) {
styleObj.style = constants.styleOSM;
} else if(constants.styles[val]) {
styleObj.style = constants.styles[val];
} else {
styleObj.style = val;
}
Expand Down
2 changes: 1 addition & 1 deletion src/registry.js
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ function registerTraceModule(_module) {
if(bpmName === 'mapbox') {
var styleRules = basePlotModule.constants.styleRules;
for(var k in styleRules) {
addStyleRule('.mapboxgl-' + k, styleRules[k]);
addStyleRule('.js-plotly-plot .plotly .mapboxgl-' + k, styleRules[k]);
}
}

Expand Down
Binary file modified test/image/baselines/mapbox_0.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified test/image/baselines/mapbox_angles.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified test/image/baselines/mapbox_bubbles-text.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified test/image/baselines/mapbox_bubbles.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/image/baselines/mapbox_carto-style.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified test/image/baselines/mapbox_choropleth-multiple.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified test/image/baselines/mapbox_choropleth-raw-geojson.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified test/image/baselines/mapbox_choropleth0.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified test/image/baselines/mapbox_connectgaps.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified test/image/baselines/mapbox_custom-style.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified test/image/baselines/mapbox_density-mulitple.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified test/image/baselines/mapbox_density0.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified test/image/baselines/mapbox_earthquake-density.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified test/image/baselines/mapbox_fill.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified test/image/baselines/mapbox_geojson-attributes.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified test/image/baselines/mapbox_layers.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified test/image/baselines/mapbox_osm-style.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/image/baselines/mapbox_stamen-style.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified test/image/baselines/mapbox_symbol-text.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/image/baselines/mapbox_white-bg-style.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
34 changes: 34 additions & 0 deletions test/image/mocks/mapbox_carto-style.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"data": [
{
"type": "scattermapbox",
"name": "carto-positron",
"lon": [ 10, 20 ],
"lat": [ 20, 10 ]
},
{
"type": "scattermapbox",
"name": "carto-darkmatter",
"lon": [ 10, 20 ],
"lat": [ 20, 10 ],
"subplot": "mapbox2"
}
],
"layout": {
"grid": {"rows": 1, "columns": 2},

"legend": {
"x": 0,
"y": 1, "yanchor": "bottom"
},

"mapbox": {
"domain": {"row": 0, "column": 0},
"style": "carto-positron"
},
"mapbox2": {
"domain": {"row": 0, "column": 1},
"style": "carto-darkmatter"
}
}
}
44 changes: 44 additions & 0 deletions test/image/mocks/mapbox_stamen-style.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{
"data": [
{
"type": "scattermapbox",
"name": "stamen-terrain",
"lon": [ 10, 20 ],
"lat": [ 20, 10 ]
},
{
"type": "scattermapbox",
"name": "stamen-toner",
"lon": [ 10, 20 ],
"lat": [ 20, 10 ],
"subplot": "mapbox2"
},
{
"type": "scattermapbox",
"name": "stamen-watercolor",
"lon": [ 10, 20 ],
"lat": [ 20, 10 ],
"subplot": "mapbox3"
}
],
"layout": {
"grid": {"rows": 1, "columns": 3},

"legend": {
"x": 0,
"y": 1, "yanchor": "bottom"
},
"mapbox": {
"domain": {"row": 0, "column": 0},
"style": "stamen-terrain"
},
"mapbox2": {
"domain": {"row": 0, "column": 1},
"style": "stamen-toner"
},
"mapbox3": {
"domain": {"row": 0, "column": 2},
"style": "stamen-watercolor"
}
}
}
18 changes: 18 additions & 0 deletions test/image/mocks/mapbox_white-bg-style.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"data": [
{
"type": "scattermapbox",
"name": "white-bg",
"lon": [ 10, 20 ],
"lat": [ 20, 10 ]
}
],
"layout": {
"width": 200,
"height": 200,
"margin": {"t": 0, "b": 0, "l": 0, "r": 0},
"mapbox": {
"style": "white-bg"
}
}
}
97 changes: 97 additions & 0 deletions test/jasmine/tests/mapbox_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1201,6 +1201,103 @@ describe('@noCI, mapbox plots', function() {
.then(done);
}, LONG_TIMEOUT_INTERVAL);

describe('attributions', function() {
it('@gl should be displayed for style "open-street-map"', function(done) {
Plotly.newPlot(gd, [{type: 'scattermapbox'}], {mapbox: {style: 'open-street-map'}})
.then(function() {
var s = Plotly.d3.selectAll('.mapboxgl-ctrl-attrib');
expect(s.size()).toBe(1);
expect(s.text()).toEqual('© OpenStreetMap');
})
.catch(failTest)
.then(done);
});

it('@gl should be displayed for style from Mapbox', function(done) {
Plotly.newPlot(gd, [{type: 'scattermapbox'}], {mapbox: {style: 'basic'}})
.then(function() {
var s = Plotly.d3.selectAll('.mapboxgl-ctrl-attrib');
expect(s.size()).toBe(1);
expect(s.text()).toEqual('© Mapbox © OpenStreetMap Improve this map');
})
.catch(failTest)
.then(done);
});

function mockLayoutCustomStyle() {
return {
'mapbox': {
'style': {
'id': 'osm',
'version': 8,
'sources': {
'simple-tiles': {
'type': 'raster',
'tiles': [
'https://a.tile.openstreetmap.org/{z}/{x}/{y}.png',
'https://b.tile.openstreetmap.org/{z}/{x}/{y}.png'
],
'tileSize': 256
}
},
'layers': [
{
'id': 'simple-tiles',
'type': 'raster',
'source': 'simple-tiles',
'minzoom': 0,
'maxzoom': 22
}
]
}
}
};
}

it('@gl should not be displayed for custom style without attribution', function(done) {
Plotly.newPlot(gd, [{type: 'scattermapbox'}], mockLayoutCustomStyle())
.then(function() {
var s = Plotly.d3.selectAll('.mapboxgl-ctrl-attrib');
expect(s.size()).toBe(1);
expect(s.text()).toEqual('');
})
.catch(failTest)
.then(done);
});

it('@gl should be displayed for custom style with attribution', function(done) {
var attr = 'custom attribution';
var layout = mockLayoutCustomStyle();
layout.mapbox.style.sources['simple-tiles'].attribution = attr;
Plotly.newPlot(gd, [{type: 'scattermapbox'}], layout)
.then(function() {
var s = Plotly.d3.selectAll('.mapboxgl-ctrl-attrib');
expect(s.size()).toBe(1);
expect(s.text()).toEqual(attr);
})
.catch(failTest)
.then(done);
});

it('@gl should be displayed for attributions defined in layers\' sourceattribution', function(done) {
var mock = require('@mocks/mapbox_layers.json');
var customMock = Lib.extendDeep(mock);

var attr = 'super custom attribution';
customMock.data.pop();
customMock.layout.mapbox.layers[0].sourceattribution = attr;

Plotly.newPlot(gd, customMock)
.then(function() {
var s = Plotly.d3.selectAll('.mapboxgl-ctrl-attrib');
expect(s.size()).toBe(1);
expect(s.text()).toEqual([attr, '© Mapbox © OpenStreetMap Improve this map'].join(' | '));
})
.catch(failTest)
.then(done);
});
});

function getMapInfo(gd) {
var subplot = gd._fullLayout.mapbox._subplot;
var map = subplot.map;
Expand Down