Skip to content

Commit 12fd6ce

Browse files
authored
Merge pull request #5334 from elben10/cluster-scattermapbox
Add clustering to scattermapbox
2 parents 9d48c6e + d534bf7 commit 12fd6ce

File tree

9 files changed

+520
-47
lines changed

9 files changed

+520
-47
lines changed

src/traces/scattermapbox/attributes.js

+47
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ var colorScaleAttrs = require('../../components/colorscale/attributes');
1818

1919
var extendFlat = require('../../lib/extend').extendFlat;
2020
var overrideAll = require('../../plot_api/edit_types').overrideAll;
21+
var mapboxLayoutAtributes = require('../../plots/mapbox/layout_attributes');
2122

2223
var lineAttrs = scatterGeoAttrs.line;
2324
var markerAttrs = scatterGeoAttrs.marker;
@@ -26,6 +27,52 @@ module.exports = overrideAll({
2627
lon: scatterGeoAttrs.lon,
2728
lat: scatterGeoAttrs.lat,
2829

30+
cluster: {
31+
enabled: {
32+
valType: 'boolean',
33+
role: 'info',
34+
dflt: false,
35+
description: 'Determines whether clustering is enabled or disabled.'
36+
},
37+
maxzoom: extendFlat({}, mapboxLayoutAtributes.layers.maxzoom, {
38+
description: [
39+
'Sets the maximum zoom level.',
40+
'At zoom levels equal to or greater than the maxzoom, the layer will be hidden.'
41+
].join(' ')
42+
}),
43+
step: {
44+
role: 'info',
45+
valType: 'number',
46+
arrayOk: true,
47+
dflt: -1,
48+
min: -1,
49+
description: [
50+
'Sets the steps for each cluster.'
51+
].join(' ')
52+
},
53+
size: {
54+
role: 'info',
55+
valType: 'number',
56+
arrayOk: true,
57+
dflt: 20,
58+
min: 0,
59+
description: [
60+
'Sets the size for each cluster step.'
61+
].join(' ')
62+
},
63+
color: {
64+
valType: 'color',
65+
arrayOk: true,
66+
role: 'style',
67+
description: [
68+
'Sets the color for each cluster step.'
69+
].join(' ')
70+
},
71+
opacity: extendFlat({}, markerAttrs.opacity, {
72+
dflt: 1
73+
})
74+
},
75+
2976
// locations
3077
// locationmode
3178

src/traces/scattermapbox/convert.js

+50-6
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,12 @@ module.exports = function convert(gd, calcTrace) {
3434
var hasText = subTypes.hasText(trace);
3535
var hasCircles = (hasMarkers && trace.marker.symbol === 'circle');
3636
var hasSymbols = (hasMarkers && trace.marker.symbol !== 'circle');
37+
var hasCluster = trace.cluster.enabled;
3738

38-
var fill = initContainer();
39-
var line = initContainer();
40-
var circle = initContainer();
41-
var symbol = initContainer();
39+
var fill = initContainer('fill');
40+
var line = initContainer('line');
41+
var circle = initContainer('circle');
42+
var symbol = initContainer('symbol');
4243

4344
var opts = {
4445
fill: fill,
@@ -82,6 +83,29 @@ module.exports = function convert(gd, calcTrace) {
8283
var circleOpts = makeCircleOpts(calcTrace);
8384
circle.geojson = circleOpts.geojson;
8485
circle.layout.visibility = 'visible';
86+
if(hasCluster) {
87+
circle.filter = ['!', ['has', 'point_count']];
88+
opts.cluster = {
89+
type: 'circle',
90+
filter: ['has', 'point_count'],
91+
layout: {visibility: 'visible'},
92+
paint: {
93+
'circle-color': arrayifyAttribute(trace.cluster.color, trace.cluster.step),
94+
'circle-radius': arrayifyAttribute(trace.cluster.size, trace.cluster.step),
95+
'circle-opacity': arrayifyAttribute(trace.cluster.opacity, trace.cluster.step),
96+
},
97+
};
98+
opts.clusterCount = {
99+
type: 'symbol',
100+
filter: ['has', 'point_count'],
101+
paint: {},
102+
layout: {
103+
'text-field': '{point_count_abbreviated}',
104+
'text-font': ['Open Sans Regular', 'Arial Unicode MS Regular'],
105+
'text-size': 12
106+
}
107+
};
108+
}
85109

86110
Lib.extendFlat(circle.paint, {
87111
'circle-color': circleOpts.mcc,
@@ -90,6 +114,10 @@ module.exports = function convert(gd, calcTrace) {
90114
});
91115
}
92116

117+
if(hasCircles && hasCluster) {
118+
circle.filter = ['!', ['has', 'point_count']];
119+
}
120+
93121
if(hasSymbols || hasText) {
94122
symbol.geojson = makeSymbolGeoJSON(calcTrace, gd);
95123

@@ -150,10 +178,12 @@ module.exports = function convert(gd, calcTrace) {
150178
return opts;
151179
};
152180

153-
function initContainer() {
181+
function initContainer(type) {
154182
return {
183+
type: type,
155184
geojson: geoJsonUtils.makeBlank(),
156185
layout: { visibility: 'none' },
186+
filter: null,
157187
paint: {}
158188
};
159189
}
@@ -208,7 +238,8 @@ function makeCircleOpts(calcTrace) {
208238

209239
features.push({
210240
type: 'Feature',
211-
geometry: {type: 'Point', coordinates: lonlat},
241+
id: i + 1,
242+
geometry: { type: 'Point', coordinates: lonlat },
212243
properties: props
213244
});
214245
}
@@ -331,3 +362,16 @@ function blankFillFunc() { return ''; }
331362
function isBADNUM(lonlat) {
332363
return lonlat[0] === BADNUM;
333364
}
365+
366+
function arrayifyAttribute(attribute, step) {
367+
var newAttribute;
368+
if(Lib.isArrayOrTypedArray(attribute) && Lib.isArrayOrTypedArray(step)) {
369+
newAttribute = ['step', ['get', 'point_count'], attribute[0]];
370+
for(var idx = 1; idx < attribute.length; idx++) {
371+
newAttribute.push(step[idx - 1], attribute[idx]);
372+
}
373+
} else {
374+
newAttribute = attribute;
375+
}
376+
return newAttribute;
377+
}

src/traces/scattermapbox/defaults.js

+9
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,15 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
5454
}
5555
}
5656

57+
var clusterEnabled = coerce('cluster.enabled');
58+
if(clusterEnabled) {
59+
coerce('cluster.maxzoom');
60+
coerce('cluster.step');
61+
coerce('cluster.color', traceOut.marker.color || defaultColor);
62+
coerce('cluster.size');
63+
coerce('cluster.opacity');
64+
}
65+
5766
if(subTypes.hasText(traceOut)) {
5867
handleTextDefaults(traceIn, traceOut, layout, coerce, {noSelect: true});
5968
}

src/traces/scattermapbox/hover.js

+9
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,21 @@ var Lib = require('../../lib');
1414
var getTraceColor = require('../scatter/get_trace_color');
1515
var fillText = Lib.fillText;
1616
var BADNUM = require('../../constants/numerical').BADNUM;
17+
var LAYER_PREFIX = require('../../plots/mapbox/constants').traceLayerPrefix;
1718

1819
module.exports = function hoverPoints(pointData, xval, yval) {
1920
var cd = pointData.cd;
2021
var trace = cd[0].trace;
2122
var xa = pointData.xa;
2223
var ya = pointData.ya;
2324
var subplot = pointData.subplot;
25+
var clusteredPointsIds = [];
26+
var layer = LAYER_PREFIX + trace.uid + '-circle';
27+
28+
if(trace.cluster.enabled) {
29+
var elems = subplot.map.queryRenderedFeatures(null, {layers: [layer]});
30+
clusteredPointsIds = elems.map(function(elem) {return elem.id;});
31+
}
2432

2533
// compute winding number about [-180, 180] globe
2634
var winding = (xval >= 0) ?
@@ -34,6 +42,7 @@ module.exports = function hoverPoints(pointData, xval, yval) {
3442
function distFn(d) {
3543
var lonlat = d.lonlat;
3644
if(lonlat[0] === BADNUM) return Infinity;
45+
if(trace.cluster.enabled && clusteredPointsIds.indexOf(d.i + 1) === -1) return Infinity;
3746

3847
var lon = Lib.modHalf(lonlat[0], 360);
3948
var lat = lonlat[1];

0 commit comments

Comments
 (0)