I am trying to add a legend to a map created by Asymmetrik/ngx-leaflet. The map is created by following the tutorial in https://github.com/Asymmetrik/ngx-leaflet . There are two different layers and for each layer there shoud be a different legend. The code is made by using angular CLI and leaflet. There is a map component. The map.component.ts file is as follows:
import {Component, Input, OnChanges, OnInit} from '@angular/core';
import {circle, geoJSON, GeoJSONOptions, latLng, Layer, LeafletMouseEvent, polygon, tileLayer} from 'leaflet';
import * as L from 'leaflet';
import {SimpleResult} from '../../models/SimpleResult';
import {HttpClient} from '@angular/common/http';
import {IDrilldownResult} from '../../models/DrilldownResult';
@Component({
selector: 'app-map-chart',
templateUrl: './map-chart.component.html',
styleUrls: ['./map-chart.component.css']
})
export class MapChartComponent implements OnInit, OnChanges {
@Input() private data: IDrilldownResult;
public options: any;
public layersControl = {
baseLayers: { }
};
private getColor(value, max, min) {
const val = (value - min) / (max - min) ;
const hue = (val * 120).toString(10);
return ['hsl(', hue, ',100%,50%)'].join('');
}
constructor(
private http: HttpClient
) { }
ngOnInit() {
this.createChart();
/*if (this.data) {
this.updateChart();
}*/
}
ngOnChanges() {
this.updateChart();
}
private createChart() {
this.options = {
layers: [
tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 18, attribution: '...' }),
],
zoom: 6,
center: latLng(51.5167, 9.9167)
};
}
private createGermanyLayer() {
this.http.get('assets/bundeslaender.geojson')
.subscribe((res: any) => {
const deIndex = this.data.children.findIndex(e => e.name === 'de');
const germanData = this.data.children[deIndex];
res.features.forEach(feat => {
const stateIndex = germanData.children.findIndex(e => {
if (e.name) {
return e.name.toLowerCase() === feat.properties.NAME_1.toLowerCase();
}
});
feat.properties.SALES = germanData.children[stateIndex].label;
});
const max = Math.max.apply(Math, res.features.map(feat => feat.properties.SALES));
const min = Math.min.apply(Math, res.features.map(feat => feat.properties.SALES));
const geoJsonGermanyLayer = {
id: 'geoJSON',
name: 'Geo JSON Polygon',
enabled: true,
layer: geoJSON(
res as any,
{
style: (d) => {
const color = this.getColor(d.properties.SALES, max, min);
return ({
color: color,
weight: 1
});
},
onEachFeature: (feature, layer) => {
layer.bindPopup('<h5>' + feature.properties.NAME_1 + '</h5><p>Revenue: ' + feature.properties.SALES.toFixed(2) + '</p>');
}
})
};
this.layersControl.baseLayers['Germany'] = geoJsonGermanyLayer.layer;
// begining of legend
const v1 = min;
const v2 = min + Math.round((max - min ) / 2);
const v3 = max;
const legend = new (L.Control.extend({
options: { position: 'bottomright' }
}));
// const legend = L.control({position: 'bottomright'});
const vm = this;
legend.onAdd = function (map) {
const div = L.DomUtil.create('div', 'legend');
const labels = [
'Sales greater than ' + v1,
'Sales greater than ' + v2,
'Sales equal or less than ' + v3
];
const grades = [v1 + 1, v2 + 1, v3 ];
div.innerHTML = '<div><b>Legend</b></div>';
for (let i = 0; i < grades.length; i++) {
div.innerHTML += '<i style="background:' + vm.getColor(grades[ i ], this.max, this.min) + '"> </i> '
+ labels[i] + '<br/>';
}
return div;
};
legend.addTo(geoJsonGermanyLayer);
// end of legend
});
}
private createEuropeLayer() {
this.http.get('assets/europe.geojson')
.subscribe((res: any) => {
res.features.forEach(feat => {
const countryIndex = this.data.children.findIndex(e => {
if (e.name) {
return e.name.toLowerCase() === feat.properties.FIPS.toLowerCase() || e.name.toLowerCase() === feat.properties.ISO2.toLowerCase();
}
});
feat.properties.SALES = countryIndex !== -1 ? this.data.children[countryIndex].label : undefined;
});
const max = Math.max.apply(Math, res.features.filter(feat => feat.properties.SALES !== undefined).map(feat => feat.properties.SALES));
const min = Math.min.apply(Math, res.features.filter(feat => feat.properties.SALES !== undefined).map(feat => feat.properties.SALES));
const maxLog = Math.log(max);
const minLog = Math.log(min);
const geoJsonEuropeLayer = {
id: 'geoJSON',
name: 'Geo JSON Polygon',
enabled: true,
layer: geoJSON(
res as any,
{
style: (d) => {
const color = this.getColor(Math.log(d.properties.SALES), maxLog, minLog);
return ({
color: color,
weight: 1
});
},
onEachFeature: (feature, layer) => {
const sales = feature.properties.SALES !== undefined ? feature.properties.SALES.toFixed(2) : 'No orders';
layer.bindPopup('<h5>' + feature.properties.NAME + '</h5>' +
'<p>Revenue: ' + sales + '</p>');
}
})
};
this.layersControl.baseLayers['Europe'] = geoJsonEuropeLayer.layer;
});
}
private updateChart() {
this.createGermanyLayer();
this.createEuropeLayer();
}
}
The legend does not appear on the page. The console shows the follwoing error: cannot read property 'bottomright' of undefined as shown in the image below:
The map is shown correctly but there is no legend. I appreciate if you tell me what is wrong with my code and why the legend is not showing. Thank you for your attention.
OK I find the answer myself based on the comment made. Legend can only be added to the map itself and not the layer. But the map is not available when you use the following code:
private createChart() {
this.options = {
layers: [
tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 18, attribution: '...' }),
],
zoom: 6,
center: latLng(51.5167, 9.9167)
};
}
In order to get the map made by leaflet itself, you need to bind it. This is done in template file:
<div style="height: 700px;"
leaflet
[leafletOptions]="options"
[leafletLayersControl]="layersControl"
(leafletMapReady)="onMapReady($event)">
</div>
and then I defined onMapReady() function as follows:
onMapReady(map: Map) {
this.updateChart();
// Do stuff with map
map.on('baselayerchange', (eventLayer) => {
const v1 = this.min;
const v2 = this.min + Math.round((this.max - this.min ) / 2);
const v3 = this.max;
const legend = new (L.Control.extend({
options: { position: 'bottomright' }
}));
const vm = this;
legend.onAdd = function (map) {
const div = L.DomUtil.create('div', 'legend');
const labels = [
'Sales greater than ' + v1,
'Sales greater than ' + v2,
'Sales equal or less than ' + v3
];
const grades = [v1+ 1, v2+ 1, v3 ];
div.innerHTML = '<div><b>Legend</b></div>';
for (let i = 0; i < grades.length; i++) {
div.innerHTML += '<i style="background:' + vm.getColor(grades[ i ], v3, v1) + '"> </i> '
+ labels[i] + '<br/>';
}
return div;
};
legend.addTo(map);
});
}
The legend appears only after the map is ready. Map is the first thing that is being created Then the layers appear. Therefore, I called updateChart() in onMapReady() to have access to min and max values of each layer.
Still there is an issue which is another legend is added when the layer is changed. But it is not relevant to this question.