mapboxgl-spiderifier
Version:
Spiderify markers on mapbox-gl using marker overlays.
239 lines (206 loc) • 8.02 kB
JavaScript
(function(root, factory) {
if (typeof exports === 'object' && typeof module !== 'undefined') {
module.exports = factory(require('mapbox-gl'));
} else if (typeof define === 'function' && define.amd) {
define(['MapboxglSpiderifier'], factory);
} else {
root.MapboxglSpiderifier = factory(root.mapboxgl);
}
}(this, function(mapboxgl) {
function MapboxglSpiderifier(map, userOptions) {
var util = {
each: eachFn,
map: mapFn,
mapTimes: mapTimesFn,
eachTimes: eachTimesFn
},
NULL_FUNCTION = function () {},
options = {
animate: false, // to animate the spiral
animationSpeed: 0, // animation speed in milliseconds
customPin: false, // If false, sets a default icon for pins in spider legs.
initializeLeg: NULL_FUNCTION,
onClick: NULL_FUNCTION,
// --- <SPIDER TUNING Params>
// circleSpiralSwitchover: show spiral instead of circle from this marker count upwards
// 0 -> always spiral; Infinity -> always circle
circleSpiralSwitchover: 9,
circleFootSeparation: 25, // related to circumference of circle
spiralFootSeparation: 28, // related to size of spiral (experiment!)
spiralLengthStart: 15, // ditto
spiralLengthFactor: 4, // ditto
// ---
},
twoPi = Math.PI * 2,
previousSpiderLegs = [];
for (var attrname in userOptions) {
options[attrname] = userOptions[attrname];
}
// Public:
this.spiderfy = spiderfy;
this.unspiderfy = unspiderfy;
this.each = function (callback) {
util.each(previousSpiderLegs, callback);
};
// Private:
function spiderfy(latLng, features) {
var spiderLegParams = generateSpiderLegParams(features.length);
var spiderLegs;
unspiderfy();
spiderLegs = util.map(features, function (feature, index) {
var spiderLegParam = spiderLegParams[index];
var elements = createMarkerElements(spiderLegParam, feature);
var mapboxMarker;
var spiderLeg;
mapboxMarker = new mapboxgl.Marker(elements.container)
.setLngLat(latLng);
spiderLeg = {
feature: feature,
elements: elements,
mapboxMarker: mapboxMarker,
param: spiderLegParam
};
options.initializeLeg(spiderLeg);
elements.container.onclick = function (e) {
options.onClick(e, spiderLeg);
};
return spiderLeg;
});
util.each(spiderLegs.reverse(), function (spiderLeg) {
spiderLeg.mapboxMarker.addTo(map);
});
if (options.animate) {
setTimeout(function () {
util.each(spiderLegs.reverse(), function (spiderLeg, index) {
spiderLeg.elements.container.className = (spiderLeg.elements.container.className || '').replace('initial', '');
spiderLeg.elements.container.style['transitionDelay'] = ((options.animationSpeed / 1000) / spiderLegs.length * index) + 's';
});
});
}
previousSpiderLegs = spiderLegs;
}
function unspiderfy() {
util.each(previousSpiderLegs.reverse(), function (spiderLeg, index) {
if (options.animate) {
spiderLeg.elements.container.style['transitionDelay'] = ((options.animationSpeed / 1000) / previousSpiderLegs.length * index) + 's';
spiderLeg.elements.container.className += ' exit';
setTimeout(function () {
spiderLeg.mapboxMarker.remove();
}, options.animationSpeed + 100); //Wait for 100ms more before clearing the DOM
} else {
spiderLeg.mapboxMarker.remove();
}
});
previousSpiderLegs = [];
}
function generateSpiderLegParams(count) {
if (count >= options.circleSpiralSwitchover) {
return generateSpiralParams(count);
} else {
return generateCircleParams(count);
}
}
function generateSpiralParams(count) {
var legLength = options.spiralLengthStart,
angle = 0;
return util.mapTimes(count, function (index) {
var pt;
angle = angle + (options.spiralFootSeparation / legLength + index * 0.0005);
pt = {
x: legLength * Math.cos(angle),
y: legLength * Math.sin(angle),
angle: angle,
legLength: legLength,
index: index
};
legLength = legLength + (twoPi * options.spiralLengthFactor / angle);
return pt;
});
}
function generateCircleParams(count) {
var circumference = options.circleFootSeparation * (2 + count),
legLength = circumference / twoPi, // = radius from circumference
angleStep = twoPi / count;
return util.mapTimes(count, function (index) {
var angle = index * angleStep;
return {
x: legLength * Math.cos(angle),
y: legLength * Math.sin(angle),
angle: angle,
legLength: legLength,
index: index
};
});
}
function createMarkerElements(spiderLegParam) {
var containerElem = document.createElement('div'),
pinElem = document.createElement('div'),
lineElem = document.createElement('div');
containerElem.className = 'spider-leg-container' + ( options.animate ? ' animate initial ' : ' ');
lineElem.className = 'spider-leg-line';
pinElem.className = 'spider-leg-pin' + (options.customPin ? '' : ' default-spider-pin');
containerElem.appendChild(lineElem);
containerElem.appendChild(pinElem);
containerElem.style['margin-left'] = spiderLegParam.x + 'px';
containerElem.style['margin-top'] = spiderLegParam.y + 'px';
lineElem.style.height = spiderLegParam.legLength + 'px';
// lineElem.style.transform = 'rotate(' + (2*Math.PI - spiderLegParam.angle) +'rad)';
lineElem.style.transform = 'rotate(' + (spiderLegParam.angle - Math.PI / 2) + 'rad)';
return { container: containerElem, line: lineElem, pin: pinElem };
}
// Utility
function eachFn(array, iterator) {
var i = 0;
if (!array || !array.length) {
return [];
}
for (i = 0; i < array.length; i++) {
iterator(array[i], i);
}
}
function eachTimesFn(count, iterator) {
if (!count) {
return [];
}
for (var i = 0; i < count; i++) {
iterator(i);
}
}
function mapFn(array, iterator) {
var result = [];
eachFn(array, function (item, i) {
result.push(iterator(item, i));
});
return result;
}
function mapTimesFn(count, iterator) {
var result = [];
eachTimesFn(count, function (i) {
result.push(iterator(i));
});
return result;
}
}
// Returns Offset option for mapbox poup, so that the popup for pins in the spider
// appears next to the pin, rather than at the center of the spider.
// offset: <number> Offset of the popup from the pin.
MapboxglSpiderifier.popupOffsetForSpiderLeg = function popupOffsetForSpiderLeg(spiderLeg, offset){
var pinOffsetX = spiderLeg.param.x;
var pinOffsetY = spiderLeg.param.y;
offset = offset || 0;
return {
'top': offsetVariant([0, offset], pinOffsetX, pinOffsetY),
'top-left': offsetVariant([offset,offset], pinOffsetX, pinOffsetY),
'top-right': offsetVariant([-offset,offset], pinOffsetX, pinOffsetY),
'bottom': offsetVariant([0, -offset], pinOffsetX, pinOffsetY),
'bottom-left': offsetVariant([offset, -offset], pinOffsetX, pinOffsetY),
'bottom-right': offsetVariant([-offset, -offset], pinOffsetX, pinOffsetY),
'left': offsetVariant([offset, -offset], pinOffsetX, pinOffsetY),
'right': offsetVariant([-offset, -offset], pinOffsetX, pinOffsetY)
};
};
function offsetVariant(offset, variantX, variantY) {
return [offset[0]+ (variantX || 0), offset[1]+ (variantY || 0)];
}
return MapboxglSpiderifier;
}));