plotly.js 2.15.1 → 2.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "plotly.js",
3
- "version": "2.15.1",
3
+ "version": "2.16.0",
4
4
  "description": "The open source javascript graphing library that powers plotly",
5
5
  "license": "MIT",
6
6
  "main": "./lib/index.js",
@@ -95,6 +95,37 @@ var attrs = module.exports = overrideAll({
95
95
  ].join(' ')
96
96
  },
97
97
 
98
+ bounds: {
99
+ west: {
100
+ valType: 'number',
101
+ description: [
102
+ 'Sets the minimum longitude of the map (in degrees East)',
103
+ 'if `east`, `south` and `north` are declared.'
104
+ ].join(' ')
105
+ },
106
+ east: {
107
+ valType: 'number',
108
+ description: [
109
+ 'Sets the maximum longitude of the map (in degrees East)',
110
+ 'if `west`, `south` and `north` are declared.'
111
+ ].join(' ')
112
+ },
113
+ south: {
114
+ valType: 'number',
115
+ description: [
116
+ 'Sets the minimum latitude of the map (in degrees North)',
117
+ 'if `east`, `west` and `north` are declared.'
118
+ ].join(' ')
119
+ },
120
+ north: {
121
+ valType: 'number',
122
+ description: [
123
+ 'Sets the maximum latitude of the map (in degrees North)',
124
+ 'if `east`, `west` and `south` are declared.'
125
+ ].join(' ')
126
+ }
127
+ },
128
+
98
129
  layers: templatedArray('layer', {
99
130
  visible: {
100
131
  valType: 'boolean',
@@ -26,6 +26,19 @@ function handleDefaults(containerIn, containerOut, coerce, opts) {
26
26
  coerce('bearing');
27
27
  coerce('pitch');
28
28
 
29
+ var west = coerce('bounds.west');
30
+ var east = coerce('bounds.east');
31
+ var south = coerce('bounds.south');
32
+ var north = coerce('bounds.north');
33
+ if(
34
+ west === undefined ||
35
+ east === undefined ||
36
+ south === undefined ||
37
+ north === undefined
38
+ ) {
39
+ delete containerOut.bounds;
40
+ }
41
+
29
42
  handleArrayContainerDefaults(containerIn, containerOut, {
30
43
  name: 'layers',
31
44
  handleItemDefaults: handleLayerDefaults
@@ -91,6 +91,9 @@ proto.createMap = function(calcData, fullLayout, resolve, reject) {
91
91
  // store access token associated with this map
92
92
  self.accessToken = opts.accesstoken;
93
93
 
94
+ var bounds = opts.bounds;
95
+ var maxBounds = bounds ? [[bounds.west, bounds.south], [bounds.east, bounds.north]] : null;
96
+
94
97
  // create the map!
95
98
  var map = self.map = new mapboxgl.Map({
96
99
  container: self.div,
@@ -100,6 +103,7 @@ proto.createMap = function(calcData, fullLayout, resolve, reject) {
100
103
  zoom: opts.zoom,
101
104
  bearing: opts.bearing,
102
105
  pitch: opts.pitch,
106
+ maxBounds: maxBounds,
103
107
 
104
108
  interactive: !self.isStatic,
105
109
  preserveDrawingBuffer: self.isStatic,
@@ -10,6 +10,7 @@ var colorScaleAttrs = require('../../components/colorscale/attributes');
10
10
 
11
11
  var extendFlat = require('../../lib/extend').extendFlat;
12
12
  var overrideAll = require('../../plot_api/edit_types').overrideAll;
13
+ var mapboxLayoutAtributes = require('../../plots/mapbox/layout_attributes');
13
14
 
14
15
  var lineAttrs = scatterGeoAttrs.line;
15
16
  var markerAttrs = scatterGeoAttrs.marker;
@@ -18,6 +19,50 @@ module.exports = overrideAll({
18
19
  lon: scatterGeoAttrs.lon,
19
20
  lat: scatterGeoAttrs.lat,
20
21
 
22
+ cluster: {
23
+ enabled: {
24
+ valType: 'boolean',
25
+ description: 'Determines whether clustering is enabled or disabled.'
26
+ },
27
+ maxzoom: extendFlat({}, mapboxLayoutAtributes.layers.maxzoom, {
28
+ description: [
29
+ 'Sets the maximum zoom level.',
30
+ 'At zoom levels equal to or greater than this, points will never be clustered.'
31
+ ].join(' ')
32
+ }),
33
+ step: {
34
+ valType: 'number',
35
+ arrayOk: true,
36
+ dflt: -1,
37
+ min: -1,
38
+ description: [
39
+ 'Sets how many points it takes to create a cluster or advance to the next cluster step.',
40
+ 'Use this in conjunction with arrays for `size` and / or `color`.',
41
+ 'If an integer, steps start at multiples of this number.',
42
+ 'If an array, each step extends from the given value until one less than the next value.'
43
+ ].join(' ')
44
+ },
45
+ size: {
46
+ valType: 'number',
47
+ arrayOk: true,
48
+ dflt: 20,
49
+ min: 0,
50
+ description: [
51
+ 'Sets the size for each cluster step.'
52
+ ].join(' ')
53
+ },
54
+ color: {
55
+ valType: 'color',
56
+ arrayOk: true,
57
+ description: [
58
+ 'Sets the color for each cluster step.'
59
+ ].join(' ')
60
+ },
61
+ opacity: extendFlat({}, markerAttrs.opacity, {
62
+ dflt: 1
63
+ })
64
+ },
65
+
21
66
  // locations
22
67
  // locationmode
23
68
 
@@ -26,11 +26,12 @@ module.exports = function convert(gd, calcTrace) {
26
26
  var hasText = subTypes.hasText(trace);
27
27
  var hasCircles = (hasMarkers && trace.marker.symbol === 'circle');
28
28
  var hasSymbols = (hasMarkers && trace.marker.symbol !== 'circle');
29
+ var hasCluster = trace.cluster && trace.cluster.enabled;
29
30
 
30
- var fill = initContainer();
31
- var line = initContainer();
32
- var circle = initContainer();
33
- var symbol = initContainer();
31
+ var fill = initContainer('fill');
32
+ var line = initContainer('line');
33
+ var circle = initContainer('circle');
34
+ var symbol = initContainer('symbol');
34
35
 
35
36
  var opts = {
36
37
  fill: fill,
@@ -74,6 +75,29 @@ module.exports = function convert(gd, calcTrace) {
74
75
  var circleOpts = makeCircleOpts(calcTrace);
75
76
  circle.geojson = circleOpts.geojson;
76
77
  circle.layout.visibility = 'visible';
78
+ if(hasCluster) {
79
+ circle.filter = ['!', ['has', 'point_count']];
80
+ opts.cluster = {
81
+ type: 'circle',
82
+ filter: ['has', 'point_count'],
83
+ layout: {visibility: 'visible'},
84
+ paint: {
85
+ 'circle-color': arrayifyAttribute(trace.cluster.color, trace.cluster.step),
86
+ 'circle-radius': arrayifyAttribute(trace.cluster.size, trace.cluster.step),
87
+ 'circle-opacity': arrayifyAttribute(trace.cluster.opacity, trace.cluster.step),
88
+ },
89
+ };
90
+ opts.clusterCount = {
91
+ type: 'symbol',
92
+ filter: ['has', 'point_count'],
93
+ paint: {},
94
+ layout: {
95
+ 'text-field': '{point_count_abbreviated}',
96
+ 'text-font': ['Open Sans Regular', 'Arial Unicode MS Regular'],
97
+ 'text-size': 12
98
+ }
99
+ };
100
+ }
77
101
 
78
102
  Lib.extendFlat(circle.paint, {
79
103
  'circle-color': circleOpts.mcc,
@@ -82,6 +106,10 @@ module.exports = function convert(gd, calcTrace) {
82
106
  });
83
107
  }
84
108
 
109
+ if(hasCircles && hasCluster) {
110
+ circle.filter = ['!', ['has', 'point_count']];
111
+ }
112
+
85
113
  if(hasSymbols || hasText) {
86
114
  symbol.geojson = makeSymbolGeoJSON(calcTrace, gd);
87
115
 
@@ -142,10 +170,12 @@ module.exports = function convert(gd, calcTrace) {
142
170
  return opts;
143
171
  };
144
172
 
145
- function initContainer() {
173
+ function initContainer(type) {
146
174
  return {
175
+ type: type,
147
176
  geojson: geoJsonUtils.makeBlank(),
148
177
  layout: { visibility: 'none' },
178
+ filter: null,
149
179
  paint: {}
150
180
  };
151
181
  }
@@ -200,7 +230,8 @@ function makeCircleOpts(calcTrace) {
200
230
 
201
231
  features.push({
202
232
  type: 'Feature',
203
- geometry: {type: 'Point', coordinates: lonlat},
233
+ id: i + 1,
234
+ geometry: { type: 'Point', coordinates: lonlat },
204
235
  properties: props
205
236
  });
206
237
  }
@@ -323,3 +354,17 @@ function blankFillFunc() { return ''; }
323
354
  function isBADNUM(lonlat) {
324
355
  return lonlat[0] === BADNUM;
325
356
  }
357
+
358
+ function arrayifyAttribute(values, step) {
359
+ var newAttribute;
360
+ if(Lib.isArrayOrTypedArray(values) && Lib.isArrayOrTypedArray(step)) {
361
+ newAttribute = ['step', ['get', 'point_count'], values[0]];
362
+
363
+ for(var idx = 1; idx < values.length; idx++) {
364
+ newAttribute.push(step[idx - 1], values[idx]);
365
+ }
366
+ } else {
367
+ newAttribute = values;
368
+ }
369
+ return newAttribute;
370
+ }
@@ -14,6 +14,10 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
14
14
  return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
15
15
  }
16
16
 
17
+ function coerce2(attr, dflt) {
18
+ return Lib.coerce2(traceIn, traceOut, attributes, attr, dflt);
19
+ }
20
+
17
21
  var len = handleLonLatDefaults(traceIn, traceOut, coerce);
18
22
  if(!len) {
19
23
  traceOut.visible = false;
@@ -46,6 +50,21 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
46
50
  }
47
51
  }
48
52
 
53
+ var clusterMaxzoom = coerce2('cluster.maxzoom');
54
+ var clusterStep = coerce2('cluster.step');
55
+ var clusterColor = coerce2('cluster.color', (traceOut.marker && traceOut.marker.color) || defaultColor);
56
+ var clusterSize = coerce2('cluster.size');
57
+ var clusterOpacity = coerce2('cluster.opacity');
58
+
59
+ var clusterEnabledDflt =
60
+ clusterMaxzoom !== false ||
61
+ clusterStep !== false ||
62
+ clusterColor !== false ||
63
+ clusterSize !== false ||
64
+ clusterOpacity !== false;
65
+
66
+ coerce('cluster.enabled', clusterEnabledDflt);
67
+
49
68
  if(subTypes.hasText(traceOut)) {
50
69
  handleTextDefaults(traceIn, traceOut, layout, coerce, {noSelect: true});
51
70
  }
@@ -5,6 +5,7 @@ var Lib = require('../../lib');
5
5
  var getTraceColor = require('../scatter/get_trace_color');
6
6
  var fillText = Lib.fillText;
7
7
  var BADNUM = require('../../constants/numerical').BADNUM;
8
+ var LAYER_PREFIX = require('../../plots/mapbox/constants').traceLayerPrefix;
8
9
 
9
10
  function hoverPoints(pointData, xval, yval) {
10
11
  var cd = pointData.cd;
@@ -12,6 +13,14 @@ function hoverPoints(pointData, xval, yval) {
12
13
  var xa = pointData.xa;
13
14
  var ya = pointData.ya;
14
15
  var subplot = pointData.subplot;
16
+ var clusteredPointsIds = [];
17
+ var layer = LAYER_PREFIX + trace.uid + '-circle';
18
+ var hasCluster = trace.cluster && trace.cluster.enabled;
19
+
20
+ if(hasCluster) {
21
+ var elems = subplot.map.queryRenderedFeatures(null, {layers: [layer]});
22
+ clusteredPointsIds = elems.map(function(elem) {return elem.id;});
23
+ }
15
24
 
16
25
  // compute winding number about [-180, 180] globe
17
26
  var winding = (xval >= 0) ?
@@ -25,6 +34,7 @@ function hoverPoints(pointData, xval, yval) {
25
34
  function distFn(d) {
26
35
  var lonlat = d.lonlat;
27
36
  if(lonlat[0] === BADNUM) return Infinity;
37
+ if(hasCluster && clusteredPointsIds.indexOf(d.i + 1) === -1) return Infinity;
28
38
 
29
39
  var lon = Lib.modHalf(lonlat[0], 360);
30
40
  var lat = lonlat[1];
@@ -1,26 +1,35 @@
1
1
  'use strict';
2
2
 
3
+ var Lib = require('../../lib');
3
4
  var convert = require('./convert');
4
5
  var LAYER_PREFIX = require('../../plots/mapbox/constants').traceLayerPrefix;
5
- var ORDER = ['fill', 'line', 'circle', 'symbol'];
6
+ var ORDER = {
7
+ cluster: ['cluster', 'clusterCount', 'circle'],
8
+ nonCluster: ['fill', 'line', 'circle', 'symbol'],
9
+ };
6
10
 
7
- function ScatterMapbox(subplot, uid) {
11
+ function ScatterMapbox(subplot, uid, clusterEnabled) {
8
12
  this.type = 'scattermapbox';
9
13
  this.subplot = subplot;
10
14
  this.uid = uid;
15
+ this.clusterEnabled = clusterEnabled;
11
16
 
12
17
  this.sourceIds = {
13
18
  fill: 'source-' + uid + '-fill',
14
19
  line: 'source-' + uid + '-line',
15
20
  circle: 'source-' + uid + '-circle',
16
- symbol: 'source-' + uid + '-symbol'
21
+ symbol: 'source-' + uid + '-symbol',
22
+ cluster: 'source-' + uid + '-circle',
23
+ clusterCount: 'source-' + uid + '-circle',
17
24
  };
18
25
 
19
26
  this.layerIds = {
20
27
  fill: LAYER_PREFIX + uid + '-fill',
21
28
  line: LAYER_PREFIX + uid + '-line',
22
29
  circle: LAYER_PREFIX + uid + '-circle',
23
- symbol: LAYER_PREFIX + uid + '-symbol'
30
+ symbol: LAYER_PREFIX + uid + '-symbol',
31
+ cluster: LAYER_PREFIX + uid + '-cluster',
32
+ clusterCount: LAYER_PREFIX + uid + '-cluster-count',
24
33
  };
25
34
 
26
35
  // We could merge the 'fill' source with the 'line' source and
@@ -34,11 +43,20 @@ function ScatterMapbox(subplot, uid) {
34
43
 
35
44
  var proto = ScatterMapbox.prototype;
36
45
 
37
- proto.addSource = function(k, opts) {
38
- this.subplot.map.addSource(this.sourceIds[k], {
46
+ proto.addSource = function(k, opts, cluster) {
47
+ var sourceOpts = {
39
48
  type: 'geojson',
40
- data: opts.geojson
41
- });
49
+ data: opts.geojson,
50
+ };
51
+
52
+ if(cluster && cluster.enabled) {
53
+ Lib.extendFlat(sourceOpts, {
54
+ cluster: true,
55
+ clusterMaxZoom: cluster.maxzoom,
56
+ });
57
+ }
58
+
59
+ this.subplot.map.addSource(this.sourceIds[k], sourceOpts);
42
60
  };
43
61
 
44
62
  proto.setSourceData = function(k, opts) {
@@ -48,56 +66,79 @@ proto.setSourceData = function(k, opts) {
48
66
  };
49
67
 
50
68
  proto.addLayer = function(k, opts, below) {
51
- this.subplot.addLayer({
52
- type: k,
69
+ var source = {
70
+ type: opts.type,
53
71
  id: this.layerIds[k],
54
72
  source: this.sourceIds[k],
55
73
  layout: opts.layout,
56
- paint: opts.paint
57
- }, below);
74
+ paint: opts.paint,
75
+ };
76
+ if(opts.filter) {
77
+ source.filter = opts.filter;
78
+ }
79
+ this.subplot.addLayer(source, below);
58
80
  };
59
81
 
60
82
  proto.update = function update(calcTrace) {
83
+ var trace = calcTrace[0].trace;
61
84
  var subplot = this.subplot;
62
85
  var map = subplot.map;
63
86
  var optsAll = convert(subplot.gd, calcTrace);
64
87
  var below = subplot.belowLookup['trace-' + this.uid];
65
88
  var i, k, opts;
89
+ var hasCluster = !!(trace.cluster && trace.cluster.enabled);
90
+ var hadCluster = !!this.clusterEnabled;
66
91
 
67
92
  if(below !== this.below) {
68
- for(i = ORDER.length - 1; i >= 0; i--) {
69
- k = ORDER[i];
93
+ var order = ORDER.nonCluster;
94
+
95
+ for(i = order.length - 1; i >= 0; i--) {
96
+ k = order[i];
70
97
  map.removeLayer(this.layerIds[k]);
71
98
  }
72
- for(i = 0; i < ORDER.length; i++) {
73
- k = ORDER[i];
99
+ for(i = 0; i < order.length; i++) {
100
+ k = order[i];
74
101
  opts = optsAll[k];
75
102
  this.addLayer(k, opts, below);
76
103
  }
77
104
  this.below = below;
78
- }
79
-
80
- for(i = 0; i < ORDER.length; i++) {
81
- k = ORDER[i];
82
- opts = optsAll[k];
83
-
84
- subplot.setOptions(this.layerIds[k], 'setLayoutProperty', opts.layout);
85
-
86
- if(opts.layout.visibility === 'visible') {
87
- this.setSourceData(k, opts);
88
- subplot.setOptions(this.layerIds[k], 'setPaintProperty', opts.paint);
105
+ } else if(hasCluster && !hadCluster) {
106
+ for(i = ORDER.nonCluster.length - 1; i >= 0; i--) {
107
+ k = ORDER.nonCluster[i];
108
+ map.removeLayer(this.layerIds[k]);
109
+ map.removeSource(this.sourceIds[k]);
110
+ }
111
+ this.addSource('circle', optsAll.circle, trace.cluster);
112
+ for(i = 0; i < ORDER.cluster.length; i++) {
113
+ k = ORDER.cluster[i];
114
+ opts = optsAll[k];
115
+ this.addLayer(k, opts, below);
116
+ }
117
+ this.clusterEnabled = hasCluster;
118
+ } else if(!hasCluster && hadCluster) {
119
+ for(i = 0; i < ORDER.cluster.length; i++) {
120
+ k = ORDER.cluster[i];
121
+ map.removeLayer(this.layerIds[k]);
89
122
  }
123
+ map.removeSource(this.sourceIds.circle);
124
+ for(i = 0; i < ORDER.nonCluster.length; i++) {
125
+ k = ORDER.nonCluster[i];
126
+ opts = optsAll[k];
127
+ this.addSource(k, opts, trace.cluster);
128
+ this.addLayer(k, opts, below);
129
+ }
130
+ this.clusterEnabled = hasCluster;
90
131
  }
91
132
 
92
- // link ref for quick update during selections
133
+ // link ref for quick update during selections
93
134
  calcTrace[0].trace._glTrace = this;
94
135
  };
95
136
 
96
137
  proto.dispose = function dispose() {
97
138
  var map = this.subplot.map;
98
-
99
- for(var i = ORDER.length - 1; i >= 0; i--) {
100
- var k = ORDER[i];
139
+ var order = this.clusterEnabled ? ORDER.cluster : ORDER.nonCluster;
140
+ for(var i = order.length - 1; i >= 0; i--) {
141
+ var k = order[i];
101
142
  map.removeLayer(this.layerIds[k]);
102
143
  map.removeSource(this.sourceIds[k]);
103
144
  }
@@ -105,15 +146,31 @@ proto.dispose = function dispose() {
105
146
 
106
147
  module.exports = function createScatterMapbox(subplot, calcTrace) {
107
148
  var trace = calcTrace[0].trace;
108
- var scatterMapbox = new ScatterMapbox(subplot, trace.uid);
149
+ var hasCluster = trace.cluster && trace.cluster.enabled;
150
+ var scatterMapbox = new ScatterMapbox(
151
+ subplot,
152
+ trace.uid,
153
+ hasCluster
154
+ );
155
+
109
156
  var optsAll = convert(subplot.gd, calcTrace);
110
157
  var below = scatterMapbox.below = subplot.belowLookup['trace-' + trace.uid];
158
+ var i, k, opts;
111
159
 
112
- for(var i = 0; i < ORDER.length; i++) {
113
- var k = ORDER[i];
114
- var opts = optsAll[k];
115
- scatterMapbox.addSource(k, opts);
116
- scatterMapbox.addLayer(k, opts, below);
160
+ if(hasCluster) {
161
+ scatterMapbox.addSource('circle', optsAll.circle, trace.cluster);
162
+ for(i = 0; i < ORDER.cluster.length; i++) {
163
+ k = ORDER.cluster[i];
164
+ opts = optsAll[k];
165
+ scatterMapbox.addLayer(k, opts, below);
166
+ }
167
+ } else {
168
+ for(i = 0; i < ORDER.nonCluster.length; i++) {
169
+ k = ORDER.nonCluster[i];
170
+ opts = optsAll[k];
171
+ scatterMapbox.addSource(k, opts, trace.cluster);
172
+ scatterMapbox.addLayer(k, opts, below);
173
+ }
117
174
  }
118
175
 
119
176
  // link ref for quick update during selections
package/src/version.js CHANGED
@@ -1,4 +1,4 @@
1
1
  'use strict';
2
2
 
3
3
  // package version injected by `npm run preprocess`
4
- exports.version = '2.15.1';
4
+ exports.version = '2.16.0';