Create marker labels lazily when showLabels is false

This commit is contained in:
James Lyne 2021-07-29 17:46:11 +01:00
parent 02c705de36
commit a7fa366635
4 changed files with 103 additions and 45 deletions

View File

@ -47,8 +47,14 @@ export class GenericIcon extends DivIcon {
// @ts-ignore
options: GenericIconOptions;
_image?: HTMLImageElement;
_label?: HTMLSpanElement;
private _image?: HTMLImageElement;
private _label?: HTMLSpanElement;
private _container?: HTMLDivElement;
private _labelCreated: boolean = false;
private _onHover: EventListener = () => {
this.createLabel();
};
constructor(options: GenericIconOptions) {
super(Object.assign(GenericIcon.defaultOptions, options));
@ -64,14 +70,41 @@ export class GenericIcon extends DivIcon {
size = point(this.options.iconSize as PointExpression);
this._image = markerIcon.cloneNode(false) as HTMLImageElement;
this._label = markerLabel.cloneNode(false) as HTMLSpanElement;
const sizeClass = [size.x, size.y].join('x');
this._image.width = size.x;
this._image.height = size.y;
this._image.src = url;
// @ts-ignore
Icon.prototype._setIconStyles.call(this, div, 'icon');
div.appendChild(this._image);
div.classList.add('marker');
if(this.options.className) {
div.classList.add(this.options.className);
}
//Create label lazily on hover
this._image.addEventListener('mouseover', this._onHover);
this._container = div;
return div;
}
createLabel() {
if(!this._container || this._labelCreated) {
return;
}
this._image?.removeEventListener('mouseover', this._onHover);
const size = point(this.options.iconSize as PointExpression),
sizeClass = [size.x, size.y].join('x');
this._label = markerLabel.cloneNode(false) as HTMLSpanElement;
this._label.classList.add(/*'markerName_' + set.id,*/ `marker__label--${sizeClass}`);
if (this.options.isHtml) {
@ -80,18 +113,8 @@ export class GenericIcon extends DivIcon {
this._label.textContent = this.options.label;
}
// @ts-ignore
Icon.prototype._setIconStyles.call(this, div, 'icon');
div.appendChild(this._image);
div.appendChild(this._label);
div.classList.add('marker');
if(this.options.className) {
div.classList.add(this.options.className);
}
return div;
this._container!.appendChild(this._label);
this._labelCreated = true;
}
update(options: GenericIconOptions) {

View File

@ -15,6 +15,7 @@
*/
import {Layer, Map as LeafletMap, LayerGroup, LayerOptions, Util, Marker, Path} from "leaflet";
import {GenericMarker} from "@/leaflet/marker/GenericMarker";
export interface LiveAtlasLayerGroupOptions extends LayerOptions {
id: string; //Added to the name of layer group panes
@ -29,11 +30,11 @@ export interface LiveAtlasLayerGroupOptions extends LayerOptions {
export default class LiveAtlasLayerGroup extends LayerGroup {
// @ts-ignore
options: LiveAtlasLayerGroupOptions;
_zoomLimitedLayers: Set<Layer>; //Layers which are zoom limited and should be checked on zoom
private _zoomLimitedLayers: Set<Layer>; //Layers which are zoom limited and should be checked on zoom
_layers: any;
_markerPane?: HTMLElement;
_zoomEndCallback = () => this._updateLayerVisibility();
private _zoomEndCallback = () => this._updateLayerVisibility();
constructor(options: LiveAtlasLayerGroupOptions) {
super([], options);
@ -80,7 +81,7 @@ export default class LiveAtlasLayerGroup extends LayerGroup {
layer.options.pane = `vectors`;
}
const zoomLimited = this._isLayerZoomLimited(layer);
const zoomLimited = LiveAtlasLayerGroup._isLayerZoomLimited(layer);
if (zoomLimited) {
this._zoomLimitedLayers.add(layer);
@ -89,11 +90,11 @@ export default class LiveAtlasLayerGroup extends LayerGroup {
if (this._map) {
//If layer is zoom limited, only add to map if it should be visible
if (zoomLimited) {
if (this._isLayerVisible(layer, this._map.getZoom())) {
this._map.addLayer(layer);
if (LiveAtlasLayerGroup._isLayerVisible(layer, this._map.getZoom())) {
this._addToMap(layer);
}
} else {
this._map.addLayer(layer);
this._addToMap(layer);
}
}
@ -106,7 +107,19 @@ export default class LiveAtlasLayerGroup extends LayerGroup {
}
update(options: LiveAtlasLayerGroupOptions) {
this.options.showLabels = options.showLabels;
if(this.options.showLabels !== options.showLabels) {
//Create labels if they are now always visible
//TODO: This will be slow when many markers exist. Is it worth doing?
if(options.showLabels) {
this.eachLayer((layer) => {
if(layer instanceof GenericMarker) {
(layer as GenericMarker).createLabel();
}
});
}
this.options.showLabels = options.showLabels;
}
if(this._markerPane) {
this._markerPane.classList.toggle('leaflet-pane--show-labels', options.showLabels);
@ -128,7 +141,7 @@ export default class LiveAtlasLayerGroup extends LayerGroup {
}
}
_updateLayerVisibility(onAdd?: boolean) {
private _updateLayerVisibility(onAdd?: boolean) {
if(!this._map) {
return;
}
@ -142,34 +155,34 @@ export default class LiveAtlasLayerGroup extends LayerGroup {
this.eachLayer((layer) => {
//Per marker zoom limits take precedence, if present
if(this._zoomLimitedLayers.has(layer)) {
this._isLayerVisible(layer, zoom) ? this._map.addLayer(layer) : this._map.removeLayer(layer);
LiveAtlasLayerGroup._isLayerVisible(layer, zoom) ? this._addToMap(layer) : this._removeFromMap(layer);
} else { //Otherwise apply group zoom limit
visible ? this._map.addLayer(layer) : this._map.removeLayer(layer);
visible ? this._addToMap(layer) : this._removeFromMap(layer);
}
}, this);
//Group isn't zoom limited, but some individual markers are
} else if(this._zoomLimitedLayers.size) {
this._zoomLimitedLayers.forEach((layer) => {
this._isLayerVisible(layer, zoom) ? this._map.addLayer(layer) : this._map.removeLayer(layer);
LiveAtlasLayerGroup._isLayerVisible(layer, zoom) ? this._addToMap(layer) : this._removeFromMap(layer);
});
//Nothing is zoom limited, but we've just been added to the map
} else if(onAdd) {
this.eachLayer(this._map.addLayer, this._map);
this.eachLayer((layer: Layer) => this._addToMap(layer), this._map);
}
}
//Returns if this layer group has zoom limits defined
_isZoomLimited() {
private _isZoomLimited() {
return this.options.maxZoom !== undefined || this.options.minZoom !== undefined;
}
//Returns if the given layer has its own zoom limits defined
_isLayerZoomLimited(layer: Layer) {
private static _isLayerZoomLimited(layer: Layer) {
return ((layer as any).options && (layer as any).options.minZoom !== undefined)
&& ((layer as any).options && (layer as any).options.maxZoom !== undefined);
}
_isLayerVisible(layer: Layer, currentZoom: number) {
private static _isLayerVisible(layer: Layer, currentZoom: number) {
let minZoom = -Infinity,
maxZoom = Infinity;
@ -183,4 +196,17 @@ export default class LiveAtlasLayerGroup extends LayerGroup {
return currentZoom >= minZoom && currentZoom <= maxZoom;
}
private _addToMap(layer: Layer) {
this._map.addLayer(layer)
//Create marker label immediately if labels are visible by default
if(layer instanceof GenericMarker && this.options.showLabels) {
(layer as GenericMarker).createLabel();
}
}
private _removeFromMap(layer: Layer) {
this._map.removeLayer(layer)
}
}

View File

@ -15,16 +15,30 @@
*/
import {MarkerOptions, Marker, Util, LatLngExpression, Icon} from 'leaflet';
import {LiveAtlasMarker} from "@/index";
import {GenericIcon} from "@/leaflet/icon/GenericIcon";
export interface GenericMarkerOptions extends MarkerOptions {
icon: GenericIcon;
minZoom?: number;
maxZoom?: number;
}
export class GenericMarker extends Marker {
constructor(latLng: LatLngExpression, options: GenericMarkerOptions) {
super(latLng, options);
Util.setOptions(this, options);
declare options: GenericMarkerOptions;
constructor(latLng: LatLngExpression, options: LiveAtlasMarker) {
super(latLng, {});
this.options.icon = new GenericIcon({
icon: options.icon,
label: options.label,
iconSize: options.dimensions,
isHtml: options.isLabelHTML,
});
this.options.maxZoom = options.maxZoom;
this.options.minZoom = options.maxZoom;
}
// noinspection JSUnusedGlobalSymbols
@ -35,4 +49,8 @@ export class GenericMarker extends Marker {
getIcon(): Icon.Default {
return this.options.icon as Icon.Default;
}
createLabel(): void {
this.options.icon.createLabel();
}
}

View File

@ -23,16 +23,7 @@ import {GenericMarker} from "@/leaflet/marker/GenericMarker";
import {LiveAtlasMarker} from "@/index";
export const createMarker = (options: LiveAtlasMarker, converter: Function): Marker => {
const marker = new GenericMarker(converter(options.location), {
icon: new GenericIcon({
icon: options.icon,
label: options.label,
iconSize: options.dimensions,
isHtml: options.isLabelHTML,
}),
maxZoom: options.maxZoom,
minZoom: options.minZoom,
});
const marker = new GenericMarker(converter(options.location), options);
marker.on('click', (e: LeafletMouseEvent) => {
e.target._map.panTo(e.target.getLatLng());