leaflet-labeled-circle
Version:
Special type of SVG marker with a label inside and draggable around the anchor point
220 lines (178 loc) • 4.42 kB
JavaScript
const L = require('leaflet');
const Circle = L.CircleMarker.extend({
options: {
textStyle: {
color: '#fff',
fontSize: 12,
fontWeight: 300
},
shiftY: 7,
},
/**
* @class LabeledCircle
* @constructor
* @extends {L.CircleMarker}
* @param {String} text
* @param {L.LatLng} latlng
* @param {Object=} options
*/
initialize(text, latlng, options) {
/**
* @type {String}
*/
this._text = text;
/**
* @type {SVGTextElement}
*/
this._textElement = null;
/**
* @type {TextNode}
*/
this._textNode = null;
/**
* @type {Object|Null}
*/
this._textLayer = null;
L.CircleMarker.prototype.initialize.call(this, latlng, options);
},
/**
* @param {String} text
* @return {LabeledCircle}
*/
setText(text) {
this._text = text;
if (this._textNode) {
this._textElement.removeChild(this._textNode);
}
this._textNode = document.createTextNode(this._text);
this._textElement.appendChild(this._textNode);
return this;
},
/**
* @return {String}
*/
getText() {
return this._text;
},
/**
* Also bring text to front
* @override
*/
bringToFront() {
L.CircleMarker.prototype.bringToFront.call(this);
this._groupTextToPath();
},
/**
* @override
*/
bringToBack() {
L.CircleMarker.prototype.bringToBack.call(this);
this._groupTextToPath();
},
/**
* Put text in the right position in the dom
*/
_groupTextToPath() {
const path = this._path;
const textElement = this._textElement;
const next = path.nextSibling;
const parent = path.parentNode;
if (textElement && parent) {
if (next && next !== textElement) {
parent.insertBefore(textElement, next);
} else {
parent.appendChild(textElement);
}
}
},
/**
* Position the text in container
*/
_updatePath() {
L.CircleMarker.prototype._updatePath.call(this);
this._updateTextPosition();
},
/**
* @override
*/
_transform(matrix) {
L.CircleMarker.prototype._transform.call(this, matrix);
// wrap textElement with a fake layer for renderer
// to be able to transform it
this._textLayer = this._textLayer || { _path: this._textElement };
if (matrix) {
this._renderer.transformPath(this._textLayer, matrix);
} else {
this._renderer._resetTransformPath(this._textLayer);
this._updateTextPosition();
this._textLayer = null;
}
},
/**
* @param {L.Map} map
* @return {LabeledCircle}
*/
onAdd(map) {
L.CircleMarker.prototype.onAdd.call(this, map);
this._initText();
this._updateTextPosition();
this.setStyle({});
return this;
},
/**
* Create and insert text
*/
_initText() {
this._textElement = L.SVG.create('text');
this.setText(this._text);
this._renderer._rootGroup.appendChild(this._textElement);
},
/**
* Calculate position for text
*/
_updateTextPosition() {
const textElement = this._textElement;
if (textElement) {
const bbox = textElement.getBBox();
const textPosition = this._point.subtract(
L.point(bbox.width, -bbox.height + this.options.shiftY).divideBy(2));
textElement.setAttribute('x', textPosition.x);
textElement.setAttribute('y', textPosition.y);
this._groupTextToPath();
}
},
/**
* Set text style
*/
setStyle(style) {
L.CircleMarker.prototype.setStyle.call(this, style);
if (this._textElement) {
const styles = this.options.textStyle;
for (let prop in styles) {
if (styles.hasOwnProperty(prop)) {
let styleProp = prop;
if (prop === 'color') {
styleProp = 'stroke';
}
this._textElement.style[styleProp] = styles[prop];
}
}
}
},
/**
* Remove text
*/
onRemove(map) {
if (this._textElement) {
if (this._textElement.parentNode) {
this._textElement.parentNode.removeChild(this._textElement);
}
this._textElement = null;
this._textNode = null;
this._textLayer = null;
}
return L.CircleMarker.prototype.onRemove.call(this, map);
}
});
module.exports = L.TextCircle = Circle;
L.textCircle = (text, latlng, options) => new Circle(text, latlng, options);