Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
holoviz
GitHub Repository: holoviz/panel
Path: blob/main/doc/how_to/custom_components/examples/esm_leaflet.md
2012 views

Build a Custom Leaflet Component

The custom LeafletHeatMap component demonstrates a number of concepts. Let us start by defining the component:

import param import pandas as pd import panel as pn import numpy as np from panel.custom import JSComponent class LeafletHeatMap(JSComponent): attribution = param.String(doc="Tile source attribution.") blur = param.Integer(default=18, bounds=(5, 50), doc="Amount of blur to apply to heatmap") center = param.XYCoordinates(default=(0, 0), doc="The center of the map.") data = param.DataFrame(doc="The heatmap data to plot, should have 'x', 'y' and 'value' columns.") tile_url = param.String(doc="Tile source URL with {x}, {y} and {z} parameter") min_alpha = param.Number(default=0.2, bounds=(0, 1), doc="Minimum alpha of the heatmap") radius = param.Integer(default=25, bounds=(5, 50), doc="The radius of heatmap values on the map") x = param.String(default='longitude', doc="Column in the data with longitude coordinates") y = param.String(default='latitude', doc="Column in the data with latitude coordinates") value = param.String(doc="Column in the data with the data values") zoom = param.Integer(default=13, bounds=(0, 21), doc="The plots zoom-level") _esm = """ import L from "https://esm.sh/[email protected]" import * as Lheat from "https://esm.sh/[email protected]" function get_records(model) { const records = [] for (let i=0; i<model.data.index.length; i++) records.push([model.data[model.y][i], model.data[model.x][i], model.data[model.value][i]]) return records } export function render({ model, el }) { const map = L.map(el).setView(model.center, model.zoom); map.on('change:zoom', () => { model.zoom = map.getZoom() }) const tileLayer = L.tileLayer(model.tile_url, { attribution: model.attribution, maxZoom: 21, tileSize: 512, zoomOffset: -1, }).addTo(map) model.on("after_render", () => { console.log(Lheat) map.invalidateSize() const data = get_records(model) const heatLayer = L.heatLayer( data, { blur: model.blur, radius: model.radius, max: 10, minOpacity: model.min_alpha }).addTo(map) model.on(['blur', 'min_alpha', 'radius'], () => { heatLayer.setOptions({ blur: model.blur, minOpacity: model.min_alpha, radius: model.radius, }) }) model.on('change:data', () => heatLayer.setLatLngs(get_records(model))) }) }""" _stylesheets = ['https://unpkg.com/[email protected]/dist/leaflet.css']

Some of the concepts this component demonstrates:

  • Loading of external libraries, specifically leaflet.js and the leaflet.heat plugin.

  • Adding event listeners with model.on

  • Delaying rendering by defining an after_render lifecycle hook.

  • Loading of an external stylesheet by including it in the list of _stylesheets.

Now let's try this component:

pn.extension(template='bootstrap') url = "https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_month.csv" earthquakes = pd.read_csv(url) heatmap = LeafletHeatMap( attribution='Map data &copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors', data=earthquakes[['longitude', 'latitude', 'mag']], min_height=500, tile_url='https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}.jpg', radius=30, sizing_mode='stretch_both', value='mag', zoom=2, ) description=pn.pane.Markdown(f'## Earthquakes between {earthquakes.time.min()} and {earthquakes.time.max()}\n\n[Data Source]({url})', sizing_mode="stretch_width") pn.Column( description, pn.Row( heatmap.controls(['blur', 'min_alpha', 'radius', 'zoom']).servable(target='sidebar'), heatmap.servable(), sizing_mode='stretch_both' ), sizing_mode='stretch_both' )