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

Introducing densitymapbox traces #3993

Merged
merged 5 commits into from
Jun 28, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions lib/densitymapbox.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/**
* Copyright 2012-2019, Plotly, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

'use strict';

module.exports = require('../src/traces/densitymapbox');
3 changes: 2 additions & 1 deletion lib/index-mapbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ var Plotly = require('./core');

Plotly.register([
require('./scattermapbox'),
require('./choroplethmapbox')
require('./choroplethmapbox'),
require('./densitymapbox')
]);

module.exports = Plotly;
1 change: 1 addition & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ Plotly.register([

require('./scattermapbox'),
require('./choroplethmapbox'),
require('./densitymapbox'),

require('./sankey'),

Expand Down
5 changes: 3 additions & 2 deletions src/plots/mapbox/mapbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -199,8 +199,9 @@ proto.updateMap = function(calcData, fullLayout, resolve, reject) {
};

var traceType2orderIndex = {
'choroplethmapbox': 0,
'scattermapbox': 1
choroplethmapbox: 0,
densitymapbox: 1,
scattermapbox: 2
};

proto.updateData = function(calcData) {
Expand Down
2 changes: 1 addition & 1 deletion src/traces/choroplethmapbox/plot.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ proto._removeLayers = function() {
var map = this.subplot.map;
var layerList = this.layerList;

for(var i = 0; i < layerList.length; i++) {
for(var i = layerList.length - 1; i >= 0; i--) {
map.removeLayer(layerList[i][1]);
}
};
Expand Down
93 changes: 93 additions & 0 deletions src/traces/densitymapbox/attributes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/**
* Copyright 2012-2019, Plotly, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

'use strict';

var colorScaleAttrs = require('../../components/colorscale/attributes');
var hovertemplateAttrs = require('../../components/fx/hovertemplate_attributes');
var plotAttrs = require('../../plots/attributes');
var scatterMapboxAttrs = require('../scattermapbox/attributes');

var extendFlat = require('../../lib/extend').extendFlat;

/*
* - https://docs.mapbox.com/help/tutorials/make-a-heatmap-with-mapbox-gl-js/
* - https://docs.mapbox.com/mapbox-gl-js/example/heatmap-layer/
* - https://docs.mapbox.com/mapbox-gl-js/style-spec/#layers-heatmap
* - https://blog.mapbox.com/introducing-heatmaps-in-mapbox-gl-js-71355ada9e6c
*
* Gotchas:
* - https://github.com/mapbox/mapbox-gl-js/issues/6463
* - https://github.com/mapbox/mapbox-gl-js/issues/6112
*/

/*
*
* In mathematical terms, Mapbox GL heatmaps are a bivariate (2D) kernel density
* estimation with a Gaussian kernel. It means that each data point has an area
* of “influence” around it (called a kernel) where the numerical value of
* influence (which we call density) decreases as you go further from the point.
* If we sum density values of all points in every pixel of the screen, we get a
* combined density value which we then map to a heatmap color.
*
*/

module.exports = extendFlat({
lon: scatterMapboxAttrs.lon,
lat: scatterMapboxAttrs.lat,

z: {
valType: 'data_array',
editType: 'calc',
description: [
'Sets the points\' weight.',
'For example, a value of 10 would be equivalent to having 10 points of weight 1',
'in the same spot'
].join(' ')
},

radius: {
valType: 'number',
role: 'info',
editType: 'plot',
arrayOk: true,
min: 1,
dflt: 30,
description: [
'Sets the radius of influence of one `lon` / `lat` point in pixels.',
'Increasing the value makes the densitymapbox trace smoother, but less detailed.'
].join(' ')
},

below: {
valType: 'string',
role: 'info',
editType: 'plot',
description: [
'Determines if the densitymapbox trace will be inserted',
'before the layer with the specified ID.',
'By default, densitymapbox traces are placed below the first',
'layer of type symbol',
'If set to \'\',',
'the layer will be inserted above every existing layer.'
].join(' ')
},

text: scatterMapboxAttrs.text,
hovertext: scatterMapboxAttrs.hovertext,

hoverinfo: extendFlat({}, plotAttrs.hoverinfo, {
flags: ['lon', 'lat', 'z', 'text', 'name']
}),
hovertemplate: hovertemplateAttrs()
},
colorScaleAttrs('', {
cLetter: 'z',
editTypeOverride: 'calc'
})
);
57 changes: 57 additions & 0 deletions src/traces/densitymapbox/calc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/**
* Copyright 2012-2019, Plotly, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

'use strict';

var isNumeric = require('fast-isnumeric');

var isArrayOrTypedArray = require('../../lib').isArrayOrTypedArray;
var BADNUM = require('../../constants/numerical').BADNUM;

var colorscaleCalc = require('../../components/colorscale/calc');
var _ = require('../../lib')._;

module.exports = function calc(gd, trace) {
var len = trace._length;
var calcTrace = new Array(len);
var z = trace.z;
var hasZ = isArrayOrTypedArray(z) && z.length;

for(var i = 0; i < len; i++) {
var cdi = calcTrace[i] = {};

var lon = trace.lon[i];
var lat = trace.lat[i];

cdi.lonlat = isNumeric(lon) && isNumeric(lat) ?
[+lon, +lat] :
[BADNUM, BADNUM];

if(hasZ) {
var zi = z[i];
cdi.z = isNumeric(zi) ? zi : BADNUM;
}
}

colorscaleCalc(gd, trace, {
vals: hasZ ? z : [0, 1],
containerStr: '',
cLetter: 'z'
});

if(len) {
calcTrace[0].t = {
labels: {
lat: _(gd, 'lat:') + ' ',
lon: _(gd, 'lon:') + ' '
}
};
}

return calcTrace;
};
115 changes: 115 additions & 0 deletions src/traces/densitymapbox/convert.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/**
* Copyright 2012-2019, Plotly, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

'use strict';

var isNumeric = require('fast-isnumeric');

var Lib = require('../../lib');
var Color = require('../../components/color');
var Colorscale = require('../../components/colorscale');

var BADNUM = require('../../constants/numerical').BADNUM;
var makeBlank = require('../../lib/geojson_utils').makeBlank;

module.exports = function convert(calcTrace) {
var trace = calcTrace[0].trace;
var isVisible = (trace.visible === true && trace._length !== 0);

var heatmap = {
layout: {visibility: 'none'},
paint: {}
};

var opts = trace._opts = {
heatmap: heatmap,
geojson: makeBlank()
};

// early return if not visible or placeholder
if(!isVisible) return opts;

var features = [];
var i;

var z = trace.z;
var radius = trace.radius;
var hasZ = Lib.isArrayOrTypedArray(z) && z.length;
var hasArrayRadius = Lib.isArrayOrTypedArray(radius);

for(i = 0; i < calcTrace.length; i++) {
var cdi = calcTrace[i];
var lonlat = cdi.lonlat;

if(lonlat[0] !== BADNUM) {
var props = {};

if(hasZ) {
var zi = cdi.z;
props.z = zi !== BADNUM ? zi : 0;
}
if(hasArrayRadius) {
props.r = (isNumeric(radius[i]) && radius[i] > 0) ? +radius[i] : 0;
}

features.push({
type: 'Feature',
geometry: {type: 'Point', coordinates: lonlat},
properties: props
});
}
}

var cOpts = Colorscale.extractOpts(trace);
var scl = cOpts.reversescale ?
Colorscale.flipScale(cOpts.colorscale) :
cOpts.colorscale;

// Add alpha channel to first colorscale step.
// If not, we would essentially color the entire map.
// See https://docs.mapbox.com/mapbox-gl-js/example/heatmap-layer/
var scl01 = scl[0][1];
var color0 = Color.opacity(scl01) < 1 ? scl01 : Color.addOpacity(scl01, 0);

var heatmapColor = [
'interpolate', ['linear'],
['heatmap-density'],
0, color0
];
for(i = 1; i < scl.length; i++) {
heatmapColor.push(scl[i][0], scl[i][1]);
}

// Those "weights" have to be in [0, 1], we can do this either:
// - as here using a mapbox-gl expression
// - or, scale the 'z' property in the feature loop
var zExp = [
'interpolate', ['linear'],
['get', 'z'],
cOpts.min, 0,
cOpts.max, 1
];

Lib.extendFlat(opts.heatmap.paint, {
'heatmap-weight': hasZ ? zExp : 1 / (cOpts.max - cOpts.min),

'heatmap-color': heatmapColor,

'heatmap-radius': hasArrayRadius ?
{type: 'identity', property: 'r'} :
trace.radius,

'heatmap-opacity': trace.opacity
});

opts.geojson = {type: 'FeatureCollection', features: features};
opts.heatmap.layout.visibility = 'visible';
opts.below = trace.below;

return opts;
};
40 changes: 40 additions & 0 deletions src/traces/densitymapbox/defaults.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* Copyright 2012-2019, Plotly, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

'use strict';

var Lib = require('../../lib');
var colorscaleDefaults = require('../../components/colorscale/defaults');
var attributes = require('./attributes');

module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
function coerce(attr, dflt) {
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
}

var lon = coerce('lon') || [];
var lat = coerce('lat') || [];

var len = Math.min(lon.length, lat.length);
if(!len) {
traceOut.visible = false;
return;
}

traceOut._length = len;

coerce('z');
coerce('radius');
coerce('below');

coerce('text');
coerce('hovertext');
coerce('hovertemplate');

colorscaleDefaults(traceIn, traceOut, layout, coerce, {prefix: '', cLetter: 'z'});
};
16 changes: 16 additions & 0 deletions src/traces/densitymapbox/event_data.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/**
* Copyright 2012-2019, Plotly, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

'use strict';

module.exports = function eventData(out, pt) {
out.lon = pt.lon;
out.lat = pt.lat;
out.z = pt.z;
return out;
};
Loading