139 |
140 |
141 |
142 | - **one-circle** - put all the elements into one circle
143 |
144 |
145 |
146 |
147 |
148 | - **concentric** - elements are placed automatically into concentric circles, there is a maximum of 4 circles
149 |
150 |
151 |
152 |
153 |
154 | - **clock** - fills circles around the cluster marker in the style of clocks
155 |
156 |
157 |
158 |
159 |
160 | - **clock-concentric** - in the case of one circle, elements are places based on the concentric style, more circles are dislocated in the clock style
161 |
162 |
163 |
164 |
165 |
166 | - **original-locations** - elements are placed at their original locations
167 |
168 |
169 |
170 |
171 |
172 | \*_can be changed - \_circleSpiralSwitchover variable in the original markerCluster code_
173 |
174 | ## Helping Circles
175 |
176 | the new type geometry called "helpingCircle" to make the cluster more visually-consistent (not supported for **origin-locations** strategy and **spiral** strategy)
177 |
178 | ## Options
179 |
180 | - **elementsPlacementStrategy** (default value 'clock-concentric') - defines the strategy for placing markers in cluster, see above
181 | - **spiderfiedClassName** (default value false) - a classname value for spiderfied markers, usefull for styling...
182 |
183 | ### Options that are valid for placement strategies 'concentric', 'clock' and 'clock-concentric'
184 |
185 | - **firstCircleElements** (default value **10**) - the number of elements in the first circle
186 | - **elementsMultiplier** (default value **1.5**) - the multiplicator of elements number for the next circle
187 | - **spiderfyDistanceSurplus** (default value **30**) - the value to be added to each new circle distance value
188 | - **helpingCircles** (default value **true**) - switch drawing helping circles on
189 | - **helpingCircleOptions** (default value **{ fillOpacity: 0, color: 'grey', weight: 0.6 }** ) - the style object for helpingCircle element
190 |
191 | ## Notes:
192 |
193 | - this subplugin was not tested with the animations turned on (`animation` and `animateAddingMarkers` variables)
194 | - `circleMarkers` should be preferred to markers
195 | - use with `L.SVG` renderer if possible (`L.Canvas` renderer has technical issues with some visual properties, see [#6](https://github.com/adammertel/Leaflet.MarkerCluster.PlacementStrategies/issues/6))
196 |
--------------------------------------------------------------------------------
/dist/leaflet-markercluster.placementstrategies.js:
--------------------------------------------------------------------------------
1 |
2 | /*
3 | markerCluster placement-strategies subplugin for leaflet.markercluster
4 | https://github.com/adammertel/Leaflet.MarkerCluster.PlacementStrategies
5 | Adam Mertel | univie
6 | */
7 | "use strict";/*global L:true*/L.MarkerClusterGroup.include({_noanimationUnspiderfy:function a(){if(this._spiderfied){//this._spiderfied._noanimationUnspiderfy();
8 | this._spiderfied.unspiderfy()}}});L.MarkerCluster.include({spiderfy:function a(){var b=this._group;var c=b.options;if(b._spiderfied===this||b._inZoomAnimation){return}var d=this.getAllChildMarkers();var e=b._map;var f=e.latLngToLayerPoint(this._latlng);var g=[];// add options.spiderfiedClassName to the spiderfied markers
9 | if(c.spiderfiedClassName){for(var h in d){var i=d[h];// marker
10 | if(i.getIcon){var j=i.getIcon();if(j){if(j.options.className){if(!j.options.className.includes(c.spiderfiedClassName)){j.options.className+=" "+c.spiderfiedClassName}}else{j.options.className=c.spiderfiedClassName}}//circleMarker
11 | }else if(d[h].setStyle){var k=d[h].options.className;d[h].setStyle({className:k+" "+c.spiderfiedClassName})}}}b._unspiderfy();b._spiderfied=this;this._clockHelpingGeometries=[];// TODO Maybe: childMarkers order by distance to center
12 | // applies chosen placement strategy
13 | switch(c.elementsPlacementStrategy){case"default":if(d.length>=this._circleSpiralSwitchover){g=this._generatePointsSpiral(d.length,f)}else{g=this._generatePointsCircle(d.length,f)}break;case"spiral":g=this._generatePointsSpiral(d.length,f);break;case"one-circle":g=this._generatePointsCircle(d.length,f);break;case"concentric":g=this._generatePointsConcentricCircles(d.length,f);break;case"clock":g=this._generatePointsClocksCircles(d.length,f,false);break;case"clock-concentric":g=this._generatePointsClocksCircles(d.length,f,true);break;case"original-locations":g=this._getOriginalLocations(d,b._map);break;default:}this._animationSpiderfy(d,g)},unspiderfy:function a(b){// remove _supportiveGeometries from map
14 | this._removeClockHelpingCircles();/// Argument from zoomanim if being called in a zoom animation or null otherwise
15 | if(this._group._inZoomAnimation){return}this._animationUnspiderfy(b);this._group._spiderfied=null},_generatePointsCircle:function a(b,c){var d=this._group.options.spiderfyDistanceMultiplier*this._circleFootSeparation*(2+b),e=d/this._2PI,//radius from circumference
16 | f=this._2PI/b,g=[];var h,j;g.length=b;for(h=b-1;h>=0;h--){j=this._circleStartAngle+h*f;g[h]=new L.Point(c.x+e*Math.cos(j),c.y+e*Math.sin(j))._round()}this._createHelpingCircle(c,e);return g},_generatePointsSpiral:function a(b,c){var d=this._group.options.spiderfyDistanceMultiplier,e=d*this._spiralFootSeparation,f=d*this._spiralLengthFactor*this._2PI,g=[];var h,j=0;var k=d*this._spiralLengthStart;g.length=b;// Higher index, closer position to cluster center.
17 | for(h=b-1;h>=0;h--){j+=e/k+h*0.0005;g[h]=new L.Point(c.x+k*Math.cos(j),c.y+k*Math.sin(j))._round();k+=f/j}return g},// auxiliary method - returns placement of vertex of given regular n-side polygon
18 | _regularPolygonVertexPlacement:function a(b,c,d,e){var f=this._2PI/c;var g=f*b;// in case of two vertices, right-left placement is more estetic
19 | if(c!==2){g-=1.6}return new L.Point(d.x+Math.cos(g)*e,d.y+Math.sin(g)*e)._round()},// clock strategy placement.
20 | // regularFirstCicle parameter - true if first elements in the first circle are placed regularly
21 | _generatePointsClocksCircles:function a(b,c,d){var e=[];var f=this._group.options;var g=f.firstCircleElements;var h=this._circleFootSeparation*1.5,// offset of the first circle
22 | j=f.spiderfyDistanceMultiplier,// multiplier of the offset for a next circle
23 | k=f.spiderfyDistanceSurplus,// multiplier of the offset for a next circle
24 | l=f.elementsMultiplier;// multiplier of number of elements in the next circle
25 | var m=1,n=g,o=h,p=0;this._createHelpingCircle(c,o);// iterating elements
26 | for(var q=1;q<=b;q++){var r=q-p;// position of current element in this circle
27 | // changing the circle
28 | if(r>n){m+=1;p+=n;r=q-p;// position of current element in this circle
29 | n=Math.floor(n*l);o=(k+o)*j;this._createHelpingCircle(c,o)}if(d&&m===1){e[q-1]=this._regularPolygonVertexPlacement(r-1,Math.min(g,b),c,o)}else{e[q-1]=this._regularPolygonVertexPlacement(r-1,n,c,o)}}return e},// method for creating and storing helping circles for clock/concentric circles strategy
30 | _createHelpingCircle:function a(b,c){var d=this._group;var e=d.options;if(e.helpingCircles){var f={radius:c};// keeping without fill if it is not defined
31 | if(!e.clockHelpingCircleOptions.fill){e.clockHelpingCircleOptions.fillColor="none"}L.extend(f,e.clockHelpingCircleOptions);var g=new L.CircleMarker(d._map.layerPointToLatLng(b),f);d._featureGroup.addLayer(g);this._clockHelpingGeometries.push(g)}},// concentric circles strategy placement.
32 | // divide elements of cluster into concentric zones based on elementsMultiplier and firstCircleElements parameters
33 | _generatePointsConcentricCircles:function a(b,d){var e=this;var f=this._group.options;var g=[];var h=f.firstCircleElements,j=this._circleFootSeparation*1.5,// offset of the first circle
34 | k=f.spiderfyDistanceMultiplier,// multiplier of the offset for a next circle
35 | c=f.elementsMultiplier,// multiplier of number of elements in the next circle
36 | l=f.spiderfyDistanceSurplus,// multiplier of the offset for a next circle
37 | m=Math.round(h*c);// number of elements in the second circle
38 | var n=[{distance:j,noElements:0},{distance:(l+j)*k,noElements:0},{distance:(2*l+j)*k*k,noElements:0},{distance:(3*l+j)*k*k*k,noElements:0}];// number of points in the second circle
39 | if(b>h){n[1].noElements=m;if(hh+Math.round(h*c)){n[2].noElements=Math.round(h*c)}if(b>h+2*Math.round(h*c)){n[2].noElements=Math.round(h*c*c)}if(b>h+Math.round(h*c)+Math.round(h*c*c)){n[2].noElements=Math.round(h*c)}if(b>h+2*Math.round(h*c)+Math.round(h*c*c)){n[2].noElements=Math.round(h*c*c)}// number of points in the first circle
41 | n[0].noElements=Math.min(b-n[1].noElements-n[2].noElements,h);// number of points in the fourth circle
42 | n[3].noElements=Math.max(b-n[0].noElements-n[1].noElements-n[2].noElements,0);var o=0;// number of elements in the finished circles
43 | var p=n[0];// curretly driven circle
44 | // iterating elements
45 | for(var q=1;q<=b;q++){// changing to the new circle
46 | if(n[1].noElements>0){if(q>n[0].noElements){p=n[1];o=n[0].noElements}if(q>n[0].noElements+n[1].noElements&&n[2].noElements>0){p=n[2];o=n[0].noElements+n[1].noElements}if(q>n[0].noElements+n[1].noElements+n[2].noElements&&n[3].noElements>0){p=n[3];o=n[0].noElements-n[1].noElements-n[2].noElements}}g[q-1]=this._regularPolygonVertexPlacement(q-o,p.noElements,d,p.distance)}n.filter(function(a){return a.noElements}).map(function(a){return e._createHelpingCircle(d,a.distance)});return g},_removeClockHelpingCircles:function a(b){if(this._group.options.helpingCircles){for(var c in this._clockHelpingGeometries){var d=this._group._featureGroup;d.removeLayer(this._clockHelpingGeometries[c])}}},_getOriginalLocations:function a(b,c){var d=[];b.forEach(function(a){d.push(c.latLngToLayerPoint(a.getLatLng()))});return d}});"use strict";/*global L:true*/L.MarkerClusterGroup.include({options:{maxClusterRadius:80,//A cluster will cover at most this many pixels from its center
47 | iconCreateFunction:null,clusterPane:L.Marker.prototype.options.pane,spiderfyOnMaxZoom:true,showCoverageOnHover:true,zoomToBoundsOnClick:true,singleMarkerMode:false,disableClusteringAtZoom:null,// extra className that will be assigned to spiderfied child markers
48 | spiderfiedClassName:false,// Setting this to false prevents the removal of any clusters outside of the viewpoint, which
49 | // is the default behaviour for performance reasons.
50 | removeOutsideVisibleBounds:true,// Method of cluster elements placements
51 | // 'default' - one-circle strategy up to 8 elements (could be changed), then spiral strategy
52 | // 'spiral' - snail/spiral placement
53 | // 'one-circle' - put all the elements into one circle
54 | // 'concentric' - elements are placed automatically into concentric circles, there is a maximum of 4 circles
55 | // 'clock' - fills circles around the cluster marker in the style of clocks
56 | // 'clock-concentric' - in case of one circle, elements are places based on the concentric style, more circles are in clock style
57 | elementsPlacementStrategy:"clock-concentric",// Options that are valid for placement strategies 'concentric', 'clock' and 'clock-concentric'
58 | // Number of elements in the first circle
59 | firstCircleElements:10,// multiplicator of elements number for the next circle
60 | elementsMultiplier:1.5,// Value to be added to each new circle
61 | spiderfyDistanceSurplus:30,// will draw additional helping circles
62 | helpingCircles:true,// Possibility to specify helpingCircle style
63 | clockHelpingCircleOptions:{color:"grey",dashArray:"5",fillOpacity:0,opacity:0.5,weight:3},// Set to false to disable all animations (zoom and spiderfy).
64 | // If false, option animateAddingMarkers below has no effect.
65 | // If L.DomUtil.TRANSITION is falsy, this option has no effect.
66 | animate:false,// Whether to animate adding markers after adding the MarkerClusterGroup to the map
67 | // If you are adding individual markers set to true, if adding bulk markers leave false for massive performance gains.
68 | animateAddingMarkers:false,// Increase to increase the distance away that spiderfied markers appear from the center
69 | spiderfyDistanceMultiplier:1,// Make it possible to specify a polyline options on a spider leg
70 | spiderLegPolylineOptions:{weight:1.5,color:"#222",opacity:0.5},// When bulk adding layers, adds markers in chunks. Means addLayers may not add all the layers in the call, others will be loaded during setTimeouts
71 | chunkedLoading:false,chunkInterval:200,// process markers for a maximum of ~ n milliseconds (then trigger the chunkProgress callback)
72 | chunkDelay:50,// at the end of each interval, give n milliseconds back to system/browser
73 | chunkProgress:null,// progress callback: function(processed, total, elapsed) (e.g. for a progress indicator)
74 | // Options to pass to the L.Polygon constructor
75 | polygonOptions:{}}});
76 |
--------------------------------------------------------------------------------
/src/markercluster.strategies.js:
--------------------------------------------------------------------------------
1 | /*global L:true*/
2 |
3 | L.MarkerClusterGroup.include({
4 | _noanimationUnspiderfy: function () {
5 | if (this._spiderfied) {
6 | //this._spiderfied._noanimationUnspiderfy();
7 | this._spiderfied.unspiderfy();
8 | }
9 | },
10 | });
11 |
12 | L.MarkerCluster.include({
13 | spiderfy: function () {
14 | const group = this._group;
15 | const options = group.options;
16 |
17 | if (group._spiderfied === this || group._inZoomAnimation) {
18 | return;
19 | }
20 |
21 | const childMarkers = this.getAllChildMarkers();
22 | const map = group._map;
23 | const center = map.latLngToLayerPoint(this._latlng);
24 | let positions = [];
25 |
26 | // add options.spiderfiedClassName to the spiderfied markers
27 | if (options.spiderfiedClassName) {
28 | for (var chmi in childMarkers) {
29 | const marker = childMarkers[chmi];
30 |
31 | // marker
32 | if (marker.getIcon) {
33 | const icon = marker.getIcon();
34 | if (icon) {
35 | if (icon.options.className) {
36 | if (
37 | !icon.options.className.includes(options.spiderfiedClassName)
38 | ) {
39 | icon.options.className += " " + options.spiderfiedClassName;
40 | }
41 | } else {
42 | icon.options.className = options.spiderfiedClassName;
43 | }
44 | }
45 |
46 | //circleMarker
47 | } else if (childMarkers[chmi].setStyle) {
48 | const classNames = childMarkers[chmi].options.className;
49 | childMarkers[chmi].setStyle({
50 | className: classNames + " " + options.spiderfiedClassName,
51 | });
52 | }
53 | }
54 | }
55 |
56 | group._unspiderfy();
57 | group._spiderfied = this;
58 |
59 | this._clockHelpingGeometries = [];
60 |
61 | // TODO Maybe: childMarkers order by distance to center
62 |
63 | // applies chosen placement strategy
64 | switch (options.elementsPlacementStrategy) {
65 | case "default":
66 | if (childMarkers.length >= this._circleSpiralSwitchover) {
67 | positions = this._generatePointsSpiral(childMarkers.length, center);
68 | } else {
69 | positions = this._generatePointsCircle(childMarkers.length, center);
70 | }
71 | break;
72 |
73 | case "spiral":
74 | positions = this._generatePointsSpiral(childMarkers.length, center);
75 | break;
76 |
77 | case "one-circle":
78 | positions = this._generatePointsCircle(childMarkers.length, center);
79 | break;
80 |
81 | case "concentric":
82 | positions = this._generatePointsConcentricCircles(
83 | childMarkers.length,
84 | center
85 | );
86 | break;
87 |
88 | case "clock":
89 | positions = this._generatePointsClocksCircles(
90 | childMarkers.length,
91 | center,
92 | false
93 | );
94 | break;
95 |
96 | case "clock-concentric":
97 | positions = this._generatePointsClocksCircles(
98 | childMarkers.length,
99 | center,
100 | true
101 | );
102 | break;
103 |
104 | case "original-locations":
105 | positions = this._getOriginalLocations(childMarkers, group._map);
106 | break;
107 |
108 | default:
109 | console.log(
110 | '!!unknown placement strategy value. Allowed strategy names are : "default", "spiral", "one-circle", "concentric", "clock", "clock-concentric" and "original-locations" '
111 | );
112 | }
113 |
114 | this._animationSpiderfy(childMarkers, positions);
115 | },
116 |
117 | unspiderfy: function (zoomDetails) {
118 | // remove _supportiveGeometries from map
119 | this._removeClockHelpingCircles();
120 |
121 | /// Argument from zoomanim if being called in a zoom animation or null otherwise
122 | if (this._group._inZoomAnimation) {
123 | return;
124 | }
125 | this._animationUnspiderfy(zoomDetails);
126 |
127 | this._group._spiderfied = null;
128 | },
129 |
130 | _generatePointsCircle: function (count, centerPt) {
131 | const circumference =
132 | this._group.options.spiderfyDistanceMultiplier *
133 | this._circleFootSeparation *
134 | (2 + count),
135 | legLength = circumference / this._2PI, //radius from circumference
136 | angleStep = this._2PI / count,
137 | res = [];
138 |
139 | let i, angle;
140 |
141 | res.length = count;
142 |
143 | for (i = count - 1; i >= 0; i--) {
144 | angle = this._circleStartAngle + i * angleStep;
145 | res[i] = new L.Point(
146 | centerPt.x + legLength * Math.cos(angle),
147 | centerPt.y + legLength * Math.sin(angle)
148 | )._round();
149 | }
150 |
151 | this._createHelpingCircle(centerPt, legLength);
152 |
153 | return res;
154 | },
155 |
156 | _generatePointsSpiral: function (count, centerPt) {
157 | const spiderfyDistanceMultiplier = this._group.options
158 | .spiderfyDistanceMultiplier,
159 | separation = spiderfyDistanceMultiplier * this._spiralFootSeparation,
160 | lengthFactor =
161 | spiderfyDistanceMultiplier * this._spiralLengthFactor * this._2PI,
162 | res = [];
163 | let i,
164 | angle = 0;
165 | let legLength = spiderfyDistanceMultiplier * this._spiralLengthStart;
166 |
167 | res.length = count;
168 |
169 | // Higher index, closer position to cluster center.
170 | for (i = count - 1; i >= 0; i--) {
171 | angle += separation / legLength + i * 0.0005;
172 | res[i] = new L.Point(
173 | centerPt.x + legLength * Math.cos(angle),
174 | centerPt.y + legLength * Math.sin(angle)
175 | )._round();
176 | legLength += lengthFactor / angle;
177 | }
178 | return res;
179 | },
180 |
181 | // auxiliary method - returns placement of vertex of given regular n-side polygon
182 | _regularPolygonVertexPlacement: function (
183 | vertexNo,
184 | totalVertices,
185 | centerPt,
186 | distanceFromCenter
187 | ) {
188 | const deltaAngle = this._2PI / totalVertices;
189 | let thisAngle = deltaAngle * vertexNo;
190 |
191 | // in case of two vertices, right-left placement is more estetic
192 | if (totalVertices !== 2) {
193 | thisAngle -= 1.6;
194 | }
195 |
196 | return new L.Point(
197 | centerPt.x + Math.cos(thisAngle) * distanceFromCenter,
198 | centerPt.y + Math.sin(thisAngle) * distanceFromCenter
199 | )._round();
200 | },
201 |
202 | // clock strategy placement.
203 | // regularFirstCicle parameter - true if first elements in the first circle are placed regularly
204 | _generatePointsClocksCircles: function (count, centerPt, regularFirstCircle) {
205 | const res = [];
206 | const options = this._group.options;
207 | const fce = options.firstCircleElements;
208 |
209 | const baseDistance = this._circleFootSeparation * 1.5, // offset of the first circle
210 | dm = options.spiderfyDistanceMultiplier, // multiplier of the offset for a next circle
211 | distanceSurplus = options.spiderfyDistanceSurplus, // multiplier of the offset for a next circle
212 | elementsMultiplier = options.elementsMultiplier; // multiplier of number of elements in the next circle
213 |
214 | let iCircleNumber = 1,
215 | iCircleNoElements = fce,
216 | iCircleDistance = baseDistance,
217 | elementsInPreviousCircles = 0;
218 |
219 | this._createHelpingCircle(centerPt, iCircleDistance);
220 |
221 | // iterating elements
222 | for (var i = 1; i <= count; i++) {
223 | var elementOrder = i - elementsInPreviousCircles; // position of current element in this circle
224 |
225 | // changing the circle
226 | if (elementOrder > iCircleNoElements) {
227 | iCircleNumber += 1;
228 | elementsInPreviousCircles += iCircleNoElements;
229 | elementOrder = i - elementsInPreviousCircles; // position of current element in this circle
230 |
231 | iCircleNoElements = Math.floor(iCircleNoElements * elementsMultiplier);
232 | iCircleDistance = (distanceSurplus + iCircleDistance) * dm;
233 |
234 | this._createHelpingCircle(centerPt, iCircleDistance);
235 | }
236 |
237 | if (regularFirstCircle && iCircleNumber === 1) {
238 | res[i - 1] = this._regularPolygonVertexPlacement(
239 | elementOrder - 1,
240 | Math.min(fce, count),
241 | centerPt,
242 | iCircleDistance
243 | );
244 | } else {
245 | res[i - 1] = this._regularPolygonVertexPlacement(
246 | elementOrder - 1,
247 | iCircleNoElements,
248 | centerPt,
249 | iCircleDistance
250 | );
251 | }
252 | }
253 |
254 | return res;
255 | },
256 |
257 | // method for creating and storing helping circles for clock/concentric circles strategy
258 | _createHelpingCircle: function (center, radius) {
259 | const group = this._group;
260 | const options = group.options;
261 |
262 | if (options.helpingCircles) {
263 | const clockCircleStyle = { radius: radius };
264 |
265 | // keeping without fill if it is not defined
266 | if (!options.clockHelpingCircleOptions.fill) {
267 | options.clockHelpingCircleOptions.fillColor = "none";
268 | }
269 | L.extend(clockCircleStyle, options.clockHelpingCircleOptions);
270 |
271 | const clockCircle = new L.CircleMarker(
272 | group._map.layerPointToLatLng(center),
273 | clockCircleStyle
274 | );
275 | group._featureGroup.addLayer(clockCircle);
276 | this._clockHelpingGeometries.push(clockCircle);
277 | }
278 | },
279 |
280 | // concentric circles strategy placement.
281 | // divide elements of cluster into concentric zones based on elementsMultiplier and firstCircleElements parameters
282 | _generatePointsConcentricCircles: function (count, centerPt) {
283 | const options = this._group.options;
284 | const res = [];
285 |
286 | const fce = options.firstCircleElements,
287 | baseDistance = this._circleFootSeparation * 1.5, // offset of the first circle
288 | dm = options.spiderfyDistanceMultiplier, // multiplier of the offset for a next circle
289 | elementsMultiplier = options.elementsMultiplier, // multiplier of number of elements in the next circle
290 | distanceSurplus = options.spiderfyDistanceSurplus, // multiplier of the offset for a next circle
291 | secondCircleElements = Math.round(fce * elementsMultiplier); // number of elements in the second circle
292 |
293 | var circles = [
294 | {
295 | distance: baseDistance,
296 | noElements: 0,
297 | },
298 | {
299 | distance: (distanceSurplus + baseDistance) * dm,
300 | noElements: 0,
301 | },
302 | {
303 | distance: (2 * distanceSurplus + baseDistance) * dm * dm,
304 | noElements: 0,
305 | },
306 | {
307 | distance: (3 * distanceSurplus + baseDistance) * dm * dm * dm,
308 | noElements: 0,
309 | },
310 | ];
311 |
312 | // number of points in the second circle
313 | if (count > fce) {
314 | circles[1].noElements = secondCircleElements;
315 |
316 | if (
317 | (fce < count && count < 2 * fce) ||
318 | (fce + secondCircleElements < count &&
319 | count < 2 * fce + secondCircleElements)
320 | ) {
321 | circles[1].noElements = fce;
322 | }
323 | }
324 |
325 | // number of points in the third circle
326 | if (count > fce + Math.round(fce * elementsMultiplier)) {
327 | circles[2].noElements = Math.round(fce * elementsMultiplier);
328 | }
329 | if (count > fce + 2 * Math.round(fce * elementsMultiplier)) {
330 | circles[2].noElements = Math.round(
331 | fce * elementsMultiplier * elementsMultiplier
332 | );
333 | }
334 | if (
335 | count >
336 | fce +
337 | Math.round(fce * elementsMultiplier) +
338 | Math.round(fce * elementsMultiplier * elementsMultiplier)
339 | ) {
340 | circles[2].noElements = Math.round(fce * elementsMultiplier);
341 | }
342 | if (
343 | count >
344 | fce +
345 | 2 * Math.round(fce * elementsMultiplier) +
346 | Math.round(fce * elementsMultiplier * elementsMultiplier)
347 | ) {
348 | circles[2].noElements = Math.round(
349 | fce * elementsMultiplier * elementsMultiplier
350 | );
351 | }
352 |
353 | // number of points in the first circle
354 | circles[0].noElements = Math.min(
355 | count - circles[1].noElements - circles[2].noElements,
356 | fce
357 | );
358 |
359 | // number of points in the fourth circle
360 | circles[3].noElements = Math.max(
361 | count -
362 | circles[0].noElements -
363 | circles[1].noElements -
364 | circles[2].noElements,
365 | 0
366 | );
367 |
368 | let prevCirclesEls = 0; // number of elements in the finished circles
369 | let iCircle = circles[0]; // curretly driven circle
370 |
371 | // iterating elements
372 | for (var i = 1; i <= count; i++) {
373 | // changing to the new circle
374 | if (circles[1].noElements > 0) {
375 | if (i > circles[0].noElements) {
376 | iCircle = circles[1];
377 | prevCirclesEls = circles[0].noElements;
378 | }
379 | if (
380 | i > circles[0].noElements + circles[1].noElements &&
381 | circles[2].noElements > 0
382 | ) {
383 | iCircle = circles[2];
384 | prevCirclesEls = circles[0].noElements + circles[1].noElements;
385 | }
386 | if (
387 | i >
388 | circles[0].noElements +
389 | circles[1].noElements +
390 | circles[2].noElements &&
391 | circles[3].noElements > 0
392 | ) {
393 | iCircle = circles[3];
394 | prevCirclesEls =
395 | circles[0].noElements -
396 | circles[1].noElements -
397 | circles[2].noElements;
398 | }
399 | }
400 |
401 | res[i - 1] = this._regularPolygonVertexPlacement(
402 | i - prevCirclesEls,
403 | iCircle.noElements,
404 | centerPt,
405 | iCircle.distance
406 | );
407 | }
408 |
409 | circles
410 | .filter((c) => c.noElements)
411 | .map((c) => this._createHelpingCircle(centerPt, c.distance));
412 |
413 | return res;
414 | },
415 |
416 | _removeClockHelpingCircles: function (fg) {
417 | if (this._group.options.helpingCircles) {
418 | for (var hg in this._clockHelpingGeometries) {
419 | const featureGroup = this._group._featureGroup;
420 | featureGroup.removeLayer(this._clockHelpingGeometries[hg]);
421 | }
422 | }
423 | },
424 |
425 | _getOriginalLocations: function (childMarkers, map) {
426 | var res = [];
427 |
428 | childMarkers.forEach(function (marker) {
429 | res.push(map.latLngToLayerPoint(marker.getLatLng()));
430 | });
431 |
432 | return res;
433 | },
434 | });
435 |
--------------------------------------------------------------------------------
/demo/random-data.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
16 |
22 |
25 |
26 |
31 |
36 |
41 |
42 |
43 |
44 |
45 |
98 |
99 |
100 |
101 |
102 | elementsPlacementStrategy:
105 | 114 | 115 |opacity of base map:
116 | 123 | 124 |base map:
125 | 130 | 131 |shapes:
132 | 136 | 137 |showCoverageOnHover:
139 | 143 |spiderfyOnMaxZoom:
144 | 148 |zoomToBoundsOnClick:
149 | 153 |maxClusterRadius:
154 | 166 | 167 |spiderfyDistanceSurplus:
170 | 180 | 181 |distanceMultiplier:
182 | 189 | 190 |firstCircleElements:
191 | 197 | 198 |elementsMultiplier:
199 | 209 | 210 |helpingCircles :
213 | 217 | 218 |color:
219 | 226 | 227 |stroke weight:
228 | 249 | 250 |stroke opacity:
251 | 263 | 264 |stroke style:
265 | 288 | 289 |Data: random points
290 |