First naive L.Label version

This commit is contained in:
Yohan Boniface 2015-10-17 22:42:17 +02:00
parent 55fe462508
commit d7b721877c
11 changed files with 607 additions and 155 deletions

3
.gitignore vendored
View File

@ -13,4 +13,5 @@ coverage/
index.html
.mailmap
bower.json
component.json
component.json
debug/local/

View File

@ -69,6 +69,7 @@ var deps = {
Popup: {
src: [
'layer/PopupBase.js',
'layer/Popup.js',
'layer/Layer.Popup.js',
'layer/marker/Marker.Popup.js'
@ -77,6 +78,16 @@ var deps = {
desc: 'Used to display the map popup (used mostly for binding HTML data to markers and paths on click).'
},
Label: {
src: [
'layer/Label.js',
'layer/Layer.Label.js',
'layer/marker/Marker.Label.js'
],
deps: ['Popup', 'Marker'],
desc: 'Used to display the map popup (used mostly for binding HTML data to markers and paths on click).'
},
LayerGroup: {
src: ['layer/LayerGroup.js'],
desc: 'Allows grouping several layers to handle them as one.'

52
debug/map/label.html Normal file
View File

@ -0,0 +1,52 @@
<!DOCTYPE html>
<html>
<head>
<title>Leaflet debug page</title>
<link rel="stylesheet" href="../../dist/leaflet.css" />
<link rel="stylesheet" href="../css/screen.css" />
<script type="text/javascript" src="../../build/deps.js"></script>
<script src="../leaflet-include.js"></script>
<style type="text/css">
.my-div-icon {
background-color: goldenrod;
text-align: center;
}
</style>
</head>
<body>
<div id="map"></div>
<script type="text/javascript">
var center = [41.2058, 9.4307];
var map = L.map('map').setView(center, 13);
L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
attribution: '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map);
L.polygon([[41.21, 9.42], [41.22, 9.40], [41.23, 9.40]]).addTo(map).bindLabel('Default centered polygon label');
L.polygon([[41.20, 9.41], [41.20, 9.39], [41.21, 9.40]]).addTo(map).bindLabel('Polygon label following mouse', {followMouse: true});
L.polygon([[41.18, 9.42], [41.17, 9.40], [41.19, 9.38]]).addTo(map).bindLabel('Permanent polygon label', {static: true});
L.marker([41.20, 9.4307]).addTo(map).bindLabel('label on the left', {direction: 'left'});
L.marker([41.206, 9.44]).addTo(map).bindLabel('permanent label', {static: true});
L.circleMarker([41.206, 9.48], {color: "Chocolate", radius: 12}).bindLabel( "Hello Left World", {direction: 'left'}).addTo(map);
var icon = L.divIcon({
className: 'my-div-icon',
html: "<p>A div icon</p>",
iconSize: [50, 50],
labelAnchor: [20, 0]
});
L.marker([41.22, 9.48], {icon: icon}).addTo(map).bindLabel('A div icon label following mouse', {followMouse: true, direction: 'auto'});
L.marker([41.23, 9.47], {icon: icon}).addTo(map).bindLabel('A div icon label');
L.marker([41.23, 9.42], {draggable: true}).addTo(map).bindLabel('Draggable marker label', {static: true});
L.marker([41.19, 9.45]).addTo(map).bindLabel('Clickable marker label', {static: true, clickable: true}).on('click', function () { alert('clicked!'); });
</script>
</body>
</html>

58
dist/leaflet.css vendored
View File

@ -74,6 +74,7 @@
.leaflet-overlay-pane { z-index: 400; }
.leaflet-shadow-pane { z-index: 500; }
.leaflet-marker-pane { z-index: 600; }
.leaflet-label-pane { z-index: 650; }
.leaflet-popup-pane { z-index: 700; }
.leaflet-map-pane canvas { z-index: 100; }
@ -507,3 +508,60 @@
background: #fff;
border: 1px solid #666;
}
/* Label */
.leaflet-label {
background: rgb(235, 235, 235);
background: rgba(235, 235, 235, 0.81);
background-clip: padding-box;
border-color: #777;
border-color: rgba(0,0,0,0.25);
border-radius: 4px;
border-style: solid;
border-width: 4px;
color: #111;
display: block;
font: 12px/20px "Helvetica Neue", Arial, Helvetica, sans-serif;
font-weight: bold;
padding: 1px 6px;
position: absolute;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
pointer-events: none;
white-space: nowrap;
z-index: 6;
}
.leaflet-label.leaflet-clickable {
cursor: pointer;
pointer-events: auto;
}
.leaflet-label:before,
.leaflet-label:after {
border-top: 6px solid transparent;
border-bottom: 6px solid transparent;
content: none;
position: absolute;
top: 5px;
}
.leaflet-label:before {
border-right: 6px solid black;
border-right-color: inherit;
left: -10px;
}
.leaflet-label:after {
border-left: 6px solid black;
border-left-color: inherit;
right: -10px;
}
.leaflet-label-right:before,
.leaflet-label-left:after {
content: "";
}

114
src/layer/Label.js Normal file
View File

@ -0,0 +1,114 @@
/*
* L.Label is used for displaying small texts on the map.
*/
L.Label = L.PopupBase.extend({
options: {
pane: 'labelPane',
offset: [12, -15],
direction: 'right',
static: false, // Reserved word, use "permanent" instead?
followMouse: false,
clickable: false,
// className: '',
zoomAnimation: true
},
openOn: function (map) {
map.openLabel(this);
return this;
},
_close: function () {
if (this._map) {
this._map.closeLabel(this);
}
},
_initLayout: function () {
var prefix = 'leaflet-label',
className = prefix + ' ' + (this.options.className || '') + ' leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
this._contentNode = this._container = L.DomUtil.create('div', className);
},
_updateLayout: function () {},
_adjustPan: function () {},
_updatePosition: function () {
var map = this._map,
pos = map.latLngToLayerPoint(this._latlng),
container = this._container,
centerPoint = map.latLngToContainerPoint(map.getCenter()),
labelPoint = map.layerPointToContainerPoint(pos),
direction = this.options.direction,
labelWidth = container.offsetWidth,
offset = L.point(this.options.offset);
// position to the right (right or auto & needs to)
if (direction === 'right' || direction === 'auto' && labelPoint.x < centerPoint.x) {
L.DomUtil.addClass(container, 'leaflet-label-right');
L.DomUtil.removeClass(container, 'leaflet-label-left');
// if (!this._zoomAnimated) { pos = pos.add(offset); }
pos = pos.add(offset);
} else { // position to the left
L.DomUtil.addClass(container, 'leaflet-label-left');
L.DomUtil.removeClass(container, 'leaflet-label-right');
pos = pos.add(L.point(-offset.x - labelWidth, offset.y));
}
L.DomUtil.setPosition(container, pos);
},
setOpacity: function (opacity) {
this.options.opacity = opacity;
if (this._container) {
L.DomUtil.setOpacity(this._container, opacity);
}
},
_animateZoom: function (e) {
var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center), offset;
if (this.options.offset) {
offset = L.point(this.options.offset);
pos = pos.add(offset);
}
L.DomUtil.setPosition(this._container, pos);
}
});
L.label = function (options, source) {
return new L.Label(options, source);
};
L.Map.include({
openLabel: function (label, latlng, options) {
if (!(label instanceof L.Label)) {
label = new L.Label(options).setContent(label);
}
if (latlng) {
label.setLatLng(latlng);
}
if (this.hasLayer(label)) {
return this;
}
return this.addLayer(label);
},
closeLabel: function (label) {
if (label) {
this.removeLayer(label);
}
return this;
}
});

164
src/layer/Layer.Label.js Normal file
View File

@ -0,0 +1,164 @@
/*
* Adds label-related methods to all layers.
*/
L.Layer.include({
bindLabel: function (content, options) {
if (content instanceof L.Label) {
L.setOptions(content, options);
this._label = content;
content._source = this;
} else {
if (!this._label || options) {
this._label = L.label(options, this);
}
this._label.setContent(content);
}
this._initLabelInteractions();
if (this._label.options.static) { this.openLabel(); }
// save the originally passed offset
this._originalLabelOffset = this._label.options.offset;
return this;
},
unbindLabel: function () {
if (this._label) {
this._initLabelInteractions(true);
this._label = null;
}
return this;
},
_initLabelInteractions: function (remove) {
if (!remove && this._labelHandlersAdded) { return; }
var onOff = remove ? 'off' : 'on',
events = {
remove: this.closeLabel,
move: this._moveLabel
};
if (!this._label.options.static) {
events.mouseover = this._openLabel;
events.mouseout = this.closeLabel;
if (this._label.options.followMouse) {
events.mousemove = this._moveLabel;
}
if (L.Browser.touch) {
events.click = this._openLabel;
}
}
this[onOff](events);
this._labelHandlersAdded = !remove;
},
openLabel: function (layer, latlng) {
if (!(layer instanceof L.Layer)) {
latlng = layer;
layer = this;
}
if (layer instanceof L.FeatureGroup) {
for (var id in this._layers) {
layer = this._layers[id];
break;
}
}
if (!latlng) {
latlng = layer.getCenter ? layer.getCenter() : layer.getLatLng();
}
if (this._label && this._map) {
// set the label offset for this layer
this._label.options.offset = this._labelAnchor(layer);
// set label source to this layer
this._label._source = layer;
// update the label (content, layout, ect...)
this._label.update();
// open the label on the map
this._map.openLabel(this._label, latlng);
if (this._label.options.clickable) {
L.DomUtil.addClass(this._label._container, 'leaflet-clickable');
this.addInteractiveTarget(this._label._container);
}
}
return this;
},
closeLabel: function () {
if (this._label) {
this._label._close();
if (this._label.options.clickable) {
L.DomUtil.removeClass(this._label._container, 'leaflet-clickable');
this.removeInteractiveTarget(this._label._container);
}
}
return this;
},
toggleLabel: function (target) {
if (this._label) {
if (this._label._map) {
this.closeLabel();
} else {
this.openLabel(target);
}
}
return this;
},
isLabelOpen: function () {
return this._label.isOpen();
},
setLabelContent: function (content) {
if (this._label) {
this._label.setContent(content);
}
return this;
},
getLabel: function () {
return this._label;
},
_openLabel: function (e) {
var layer = e.layer || e.target;
if (!this._label || !this._map) {
return;
}
this.openLabel(layer, this._label.options.followMouse ? e.latlng : undefined);
},
_labelAnchor: function (layer) {
// where shold we anchor the label on this layer?
var anchor = layer._getLabelAnchor && !this._label.options.followMouse ? layer._getLabelAnchor() : [0, 0];
// add the users passed offset to that
var offsetToAdd = this._originalLabelOffset || L.Label.prototype.options.offset;
// return the final point to anchor the label
return L.point(anchor).add(offsetToAdd);
},
_moveLabel: function (e) {
var latlng = e.latlng, containerPoint, layerPoint;
if (this._label.options.followMouse && e.originalEvent) {
containerPoint = this._map.mouseEventToContainerPoint(e.originalEvent);
layerPoint = this._map.containerPointToLayerPoint(containerPoint);
latlng = this._map.layerPointToLatLng(layerPoint);
}
this._label.setLatLng(latlng);
}
});

View File

@ -6,7 +6,7 @@ L.Map.mergeOptions({
closePopupOnClick: true
});
L.Popup = L.Layer.extend({
L.Popup = L.PopupBase.extend({
options: {
pane: 'popupPane',
@ -28,36 +28,18 @@ L.Popup = L.Layer.extend({
zoomAnimation: true
},
initialize: function (options, source) {
L.setOptions(this, options);
getEvents: function () {
var events = L.PopupBase.prototype.getEvents.call(this);
this._source = source;
},
onAdd: function (map) {
this._zoomAnimated = this._zoomAnimated && this.options.zoomAnimation;
if (!this._container) {
this._initLayout();
if ('closeOnClick' in this.options ? this.options.closeOnClick : this._map.options.closePopupOnClick) {
events.preclick = this._close;
}
if (map._fadeAnimated) {
L.DomUtil.setOpacity(this._container, 0);
if (this.options.keepInView) {
events.moveend = this._adjustPan;
}
clearTimeout(this._removeTimeout);
this.getPane().appendChild(this._container);
this.update();
if (map._fadeAnimated) {
L.DomUtil.setOpacity(this._container, 1);
}
map.fire('popupopen', {popup: this});
if (this._source) {
this._source.fire('popupopen', {popup: this}, true);
}
return events;
},
openOn: function (map) {
@ -65,98 +47,6 @@ L.Popup = L.Layer.extend({
return this;
},
onRemove: function (map) {
if (map._fadeAnimated) {
L.DomUtil.setOpacity(this._container, 0);
this._removeTimeout = setTimeout(L.bind(L.DomUtil.remove, L.DomUtil, this._container), 200);
} else {
L.DomUtil.remove(this._container);
}
map.fire('popupclose', {popup: this});
if (this._source) {
this._source.fire('popupclose', {popup: this}, true);
}
},
getLatLng: function () {
return this._latlng;
},
setLatLng: function (latlng) {
this._latlng = L.latLng(latlng);
if (this._map) {
this._updatePosition();
this._adjustPan();
}
return this;
},
getContent: function () {
return this._content;
},
setContent: function (content) {
this._content = content;
this.update();
return this;
},
getElement: function () {
return this._container;
},
update: function () {
if (!this._map) { return; }
this._container.style.visibility = 'hidden';
this._updateContent();
this._updateLayout();
this._updatePosition();
this._container.style.visibility = '';
this._adjustPan();
},
getEvents: function () {
var events = {
zoom: this._updatePosition,
viewreset: this._updatePosition
};
if (this._zoomAnimated) {
events.zoomanim = this._animateZoom;
}
if ('closeOnClick' in this.options ? this.options.closeOnClick : this._map.options.closePopupOnClick) {
events.preclick = this._close;
}
if (this.options.keepInView) {
events.moveend = this._adjustPan;
}
return events;
},
isOpen: function () {
return !!this._map && this._map.hasLayer(this);
},
bringToFront: function () {
if (this._map) {
L.DomUtil.toFront(this._container);
}
return this;
},
bringToBack: function () {
if (this._map) {
L.DomUtil.toBack(this._container);
}
return this;
},
_close: function () {
if (this._map) {
this._map.closePopup(this);
@ -189,21 +79,24 @@ L.Popup = L.Layer.extend({
this._tip = L.DomUtil.create('div', prefix + '-tip', this._tipContainer);
},
_updateContent: function () {
if (!this._content) { return; }
_updatePosition: function () {
if (!this._map) { return; }
var node = this._contentNode;
var content = (typeof this._content === 'function') ? this._content(this._source || this) : this._content;
var pos = this._map.latLngToLayerPoint(this._latlng),
offset = L.point(this.options.offset);
if (typeof content === 'string') {
node.innerHTML = content;
if (this._zoomAnimated) {
L.DomUtil.setPosition(this._container, pos);
} else {
while (node.hasChildNodes()) {
node.removeChild(node.firstChild);
}
node.appendChild(content);
offset = offset.add(pos);
}
this.fire('contentupdate');
var bottom = this._containerBottom = -offset.y,
left = this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x;
// bottom position the popup in case the height of the popup changes (images loading etc)
this._container.style.bottom = bottom + 'px';
this._container.style.left = left + 'px';
},
_updateLayout: function () {
@ -236,31 +129,6 @@ L.Popup = L.Layer.extend({
this._containerWidth = this._container.offsetWidth;
},
_updatePosition: function () {
if (!this._map) { return; }
var pos = this._map.latLngToLayerPoint(this._latlng),
offset = L.point(this.options.offset);
if (this._zoomAnimated) {
L.DomUtil.setPosition(this._container, pos);
} else {
offset = offset.add(pos);
}
var bottom = this._containerBottom = -offset.y,
left = this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x;
// bottom position the popup in case the height of the popup changes (images loading etc)
this._container.style.bottom = bottom + 'px';
this._container.style.left = left + 'px';
},
_animateZoom: function (e) {
var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center);
L.DomUtil.setPosition(this._container, pos);
},
_adjustPan: function () {
if (!this.options.autoPan || (this._map._panAnim && this._map._panAnim._inProgress)) { return; }
@ -304,7 +172,14 @@ L.Popup = L.Layer.extend({
_onCloseButtonClick: function (e) {
this._close();
L.DomEvent.stop(e);
},
_animateZoom: function (e) {
var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center);
L.DomUtil.setPosition(this._container, pos);
}
});
L.popup = function (options, source) {

166
src/layer/PopupBase.js Normal file
View File

@ -0,0 +1,166 @@
/*
* L.Popup is used for displaying popups on the map.
*/
L.Map.mergeOptions({
closePopupOnClick: true
});
L.PopupBase = L.Layer.extend({
options: {
minWidth: 50,
maxWidth: 300,
// maxHeight: <Number>,
offset: [0, 7],
autoPan: true,
autoPanPadding: [5, 5],
// autoPanPaddingTopLeft: <Point>,
// autoPanPaddingBottomRight: <Point>,
closeButton: true,
autoClose: true,
// keepInView: false,
// className: '',
zoomAnimation: true
},
initialize: function (options, source) {
L.setOptions(this, options);
this._source = source;
},
onAdd: function (map) {
this._zoomAnimated = this._zoomAnimated && this.options.zoomAnimation;
if (!this._container) {
this._initLayout();
}
if (map._fadeAnimated) {
L.DomUtil.setOpacity(this._container, 0);
}
clearTimeout(this._removeTimeout);
this.getPane().appendChild(this._container);
this.update();
if (map._fadeAnimated) {
L.DomUtil.setOpacity(this._container, 1);
}
this.bringToFront();
map.fire('popupopen', {popup: this});
if (this._source) {
this._source.fire('popupopen', {popup: this}, true);
}
},
onRemove: function (map) {
if (map._fadeAnimated) {
L.DomUtil.setOpacity(this._container, 0);
this._removeTimeout = setTimeout(L.bind(L.DomUtil.remove, L.DomUtil, this._container), 200);
} else {
L.DomUtil.remove(this._container);
}
map.fire('popupclose', {popup: this});
if (this._source) {
this._source.fire('popupclose', {popup: this}, true);
}
},
getLatLng: function () {
return this._latlng;
},
setLatLng: function (latlng) {
this._latlng = L.latLng(latlng);
if (this._map) {
this._updatePosition();
this._adjustPan();
}
return this;
},
getContent: function () {
return this._content;
},
setContent: function (content) {
this._content = content;
this.update();
return this;
},
getElement: function () {
return this._container;
},
update: function () {
if (!this._map) { return; }
this._container.style.visibility = 'hidden';
this._updateContent();
this._updateLayout();
this._updatePosition();
this._container.style.visibility = '';
this._adjustPan();
},
getEvents: function () {
var events = {
zoom: this._updatePosition,
viewreset: this._updatePosition
};
if (this._zoomAnimated) {
events.zoomanim = this._animateZoom;
}
return events;
},
isOpen: function () {
return !!this._map && this._map.hasLayer(this);
},
bringToFront: function () {
if (this._map) {
L.DomUtil.toFront(this._container);
}
return this;
},
bringToBack: function () {
if (this._map) {
L.DomUtil.toBack(this._container);
}
return this;
},
_updateContent: function () {
if (!this._content) { return; }
var node = this._contentNode;
var content = (typeof this._content === 'function') ? this._content(this._source || this) : this._content;
if (typeof content === 'string') {
node.innerHTML = content;
} else {
while (node.hasChildNodes()) {
node.removeChild(node.firstChild);
}
node.appendChild(content);
}
this.fire('contentupdate');
}
});

View File

@ -8,6 +8,7 @@ L.Icon.Default = L.Icon.extend({
iconSize: [25, 41],
iconAnchor: [12, 41],
popupAnchor: [1, -34],
labelAnchor: [9, -20],
shadowSize: [41, 41]
},

View File

@ -0,0 +1,9 @@
/*
* Label extension to L.Marker, adding label-related methods.
*/
L.Marker.include({
_getLabelAnchor: function () {
return this.options.icon.options.labelAnchor || [0, 0];
}
});

View File

@ -519,6 +519,7 @@ L.Map = L.Evented.extend({
this.createPane('shadowPane');
this.createPane('overlayPane');
this.createPane('markerPane');
this.createPane('labelPane');
this.createPane('popupPane');
if (!this.options.markerZoomAnimation) {