Skip to main content

Leaflet Map

Basic Configuration

LeafletMap is a mapping component capable of rendering detailed maps. It doesn't come with preloaded maps, so you'll need to provide a vector tile source first to make it work. You can use an existing cloud map tile server (like MapTiler Cloud, which has a free tier) or host your own tiles (we've successfully used OpenMapTiles in the past).

Let's assume you've got an API key from MapTiler. You can initialize LeafletMap by providing a style URL or object to it. MapTiler has a few vector styles available by default, but you can also create your own. Don't forget to set the attribution properly.

function Component(props) {
const style = `${key}`
const attribution = [
`<a href="" target="_blank">MapTiler</a>`,
`<a href="" target="_blank">OpenStreetMap contributors</a>`

return (
<VisLeafletMap style={style} attribution={attribution}/>

Map Data

LeafletMap can plot an array of points provided to the component. The corresponding data objects can be in an arbitrary format, but they need to have properties storing the latitude and longitude values. A minimum viable datatype of the data objects looks like:

type MapDataRecord = {
latitude: number;
longitude: number;

You can provide alternative pointLatitude and pointLongitude accessor functions in your LeafletMap configuration.

Close points will automatically cluster and uncluster as you zoom in or out. When you click on a cluster, the map will smoothly zoom into a cluster (if map zoom is too far) or expand it showing the contained points.

<VisLeafletMap style={style} data={data}/>


Point Color

You can set custom color to the points by using the pointColor property, which accepts either a constant CSS color string or an accessor function. The default point color comes from the --vis-map-point-default-fill-color CSS variable.

<VisLeafletMap style={style} pointColor="#FFD651" data={data}/>

Point Shape

LeafletMap supports 4 different point shapes: "circle", "square", "triangle" and "ring". You can set the shape by using the pointShape property. It accepts a LeafletMapPointShape enum, a string, or an accessor function returning either of them.

<VisLeafletMap style={style} pointShape="triangle" data={data}/>

Point Radius

Point radius can be set by providing a constant value or a function to the pointRadius property.

<VisLeafletMap style={style} pointRadius={pointRadius} data={data}/>

Point Labels


Points can have two kinds of labels: central and bottom. The label at the center of the point can be set by using the pointLabel property. This label will fit into the point, so it is supposed to be short.


You can control the color of central labels with the pointLabelColor and clusterLabelColor config properties. By default, the label color will be set to a CSS variable, depending on the point brightness, either to --vis-map-point-inner-label-text-color-light or to --vis-map-point-inner-label-text-color-dark.


The bottom label is configurable via the pointBottomLabel property. All label properties can accept accessor functions as their value. In the example below we set pointBottomLabel to d =>



Configuration options for clusters are very similar to the configuration of regular points but there are a few differences. Clusters are always displayed as circles (or rings when colorMap is provided). And cluster related accessor functions will receive LeafletMapClusterDatum (instead of LeafletMapPointDatum) as an argument, which has a clusterPoints property containing all points within that cluster.

Cluster Color and Radius

Just like with points you can set custom cluster color by using the clusterColor property. The default color comes from the --vis-map-cluster-default-fill-color CSS variable.

Similarly, you can also set custom cluster radius by providing clusterRadius.


Cluster Labels

By default, clusters display the number of contained points in the center. That behaviour can be overridden with the clusterLabel property. The clusterBottomLabel property allows you to set the bottom label.


Similar to regular point labels, you can set the color of central labels with the clusterLabelColor config property, and use the --vis-map-cluster-inner-label-text-color-light and --vis-map-cluster-inner-label-text-color-dark CSS variables to change the default behaviour.

Expand on Click

If you want clusters to never expand on click, you can set clusterExpandOnClick to false. In that case when you click on a cluster, the map will keep zooming in until the zoom level is high enough for the cluster to break.


Color Map

If you're using LeafletMapPointShape.Circle or LeafletMapPointShape.Ring as a point shape, and your points have multiple values of the same kind associated with them, you can show the distribution of those values on a tiny pie / donut diagram.

Let's say we want to show city population by age, and our data point looks like:

type MapDataRecord = {
age0_18: number;
age19_64: number;
age65plus: number;

You can use the colorMap property to define the color (and custom CSS class if necessary) for the corresponding pie / donut segments:

const colorMap = {
age0_18: { color: '#0085FF' },
age19_64: { color: '#41A4FF' },
age65plus: { color: '#8BC7FF', className: 'age65plus' },

Clusters will automatically reflect that too — they'll be rendered as small donuts. You can control the thickness of the donut ring by using the clusterRingWidth and pointRingWidth properties.


TopoJSON Overlay

LeafletMap can also display a topojson overlay on top of the base map. It's configurable via the topoJSONLayer config property. The configuration object for the overlay consists of several properties:

topoJSONLayer: {
/* Your `TopoJSON.Topology` object. Default: `undefined` */
sources: TopoJSON.Topology;
/* Name of the feature collection to be rendered. Default: `undefined` */
featureName: string;
/* Fill color property name. Default: `undefined` */
fillProperty: string;
/* Fill opacity. Default: `0.6` */
fillOpacity: number;
/* Stroke color property name. Default: `undefined` */
strokeProperty: string;
/* Stroke opacity. Default: `0.8` */
strokeOpacity: number;
/* Stroke width. Default: `2` */
strokeWidth: number;

Let's say your Topojson looks like:

const topojson = {
"type": "Topology",
"arcs": [...],
"objects": {
"countries": {
"type": "GeometryCollection",
"geometries": [
"arcs": [...],
"type": "MultiPolygon",
"properties": {
"name": "Canada",
"color": "#1f77b4",
"color-stroke": "#3E5FFF"
"id": "CA"

Then you can use the following topoJSONLayer configuration to display the layer in LeafletMap:

const topoJSONLayer = {
sources: topojson,
featureName: 'countries',
fillProperty: 'color',
strokeProperty: 'color-stroke',
<VisLeafletMap style={style} topoJSONLayer={topoJSONLayer}/>


This feature is not supported when the raster renderer is enabled.

Map Callbacks

LeafletMap has several configurable callback functions that will be called upon various map interactions. All of these functions (except onMapInitialized) will have { mapCenter, zoomLevel, bounds } provided as an argument.

/** Function to be called after the map's async initialization is done. Default: `undefined` */
onMapInitialized: (() => void)
/** Map Move / Zoom unified callback function. Default: `undefined` */
onMapMoveZoom: (({ mapCenter, zoomLevel, bounds }: MapZoomState) => void)
/** Map Move Start callback function. Default: `undefined` */
onMapMoveStart: (({ mapCenter, zoomLevel, bounds }: MapZoomState) => void)
/** Map Move End callback function. Default: `undefined` */
onMapMoveEnd: (({ mapCenter, zoomLevel, bounds }: MapZoomState) => void)
/** Map Zoom Start callback function. Default: `undefined` */
onMapZoomStart: (({ mapCenter, zoomLevel, bounds }: MapZoomState) => void)
/** Map Zoom End callback function. Default: `undefined` */
onMapZoomEnd: (({ mapCenter, zoomLevel, bounds }: MapZoomState) => void)
/** Map Zoom Click callback function. Default: `undefined` */
onMapClick: (({ mapCenter, zoomLevel, bounds }: MapZoomState) => void)

The callback functions above were added to enable deeper LeafletMap integration into other project. If you want to set up a callback functions for the data layer (points, clusters, ...) you can use Unovis's traditional events configuration:

events: {
// One event for regular points and clusters
[LeafletMap.selectors.point]: {
click: (d: LeafletMapPoint<MapDataRecord> => {

Controlling the View

By default, if you provide data to the map before the initialization, it'll automatically fit the view to show all the data points. You can disable that behaviour by setting fitViewOnInit to false, in that case the whole world will be displayed. If you want the map to re-fit the view on data updates, you can set fitViewOnUpdate to true. The fit padding can be controlled with the fitViewPadding property.

Alternatively, if you want the map to display a specific area upon initialization, you can set the initialBounds property in the following format:

type Bounds = {
northEast: { lat: number; lng: number };
southWest: { lat: number; lng: number };
<VisLeafletMap style={style} initialBounds={initialBounds}/>

Also, if you want specific bounds to be set on every map update, you can achieve that by setting the fitBoundsOnUpdate config property.

The LeafletMap Typescript component has several public methods allowing you to control the view:

public zoomIn (increment: number)
public zoomOut (decrement: number)
public setZoom (zoomLevel: number)
public fitView ()

If you're using Unovis with a UI framework you can still access those functions. In React you can use the useRef hook to access the Typescript instance of LeafletMap. In Angular there's a ViewChild decorator allowing you to do the same. Svelte's LeafletMap wrapper re-exports the functions, so you can simply import them in you code along with the component.


If you want to learn more about various zoom levels, Mapbox has a great article on that topic.

Selecting Points

There are two ways how you can select a point on the map: provide a unique point id in the selectedPointId config property or call one of the imperative methods of the Typescript component using the technique described right above.

Available methods:

/* Select a point by id and optionally center the map view.
* This method was designed to be used mainly with the `[LeafletMap.selectors.point]` click events
* (when the user actually clicks on a point) and if the specified point is inside one of the collapsed
* clusters it won't be selected. You can use the `zoomToPointById` method to achieve that.
public selectPointById (id: string, centerView = false)
/* Zoom to a point by id and optionally select it.
* If the point is inside a cluster, it'll be automatically expanded to show the enclosed point.
* You can also force set the zoom level by providing the `customZoomLevel` argument.
public zoomToPointById (id: string, selectPoint = false, customZoomLevel?: number)
/* Get the id of the selected point */
public getSelectedPointId ()
/* Unselect point if it was selected before */
public unselectPoint ()

Dark Theme

You can provide a separate style for the dark theme via the styleDarkTheme property, if you want LeafletMap to change its look when the dark theme is enabled.

Using Raster files

By default, LeafletMap uses the MapLibre GL JS renderer and the vector tile source you provide for the base map. You can use a raster tile source instead by providing a valid URL template string to style, and setting renderer to LeafletMapRenderer.Raster or "raster".


When this rendering mode is enabled, style (and styleDarkTheme, if using) must be of type string in the form of a URL template. Only when renderer is set to LeafletMapRenderer.MapLibre (the default) can MapLibre's StyleSpecification object be used.


For points and clusters

LeafletMap accepts a Tooltip instance as a config property. If you want to add a tooltip to the points and clusters, you can use the [LeafletMap.selectors.point] selector in your Tooltip configuration for both.

For TopoJSON features

If you have a TopoJSON overlay on your map, you can make the tooltip work with it by using the [] selector. In that case the tooltip callback will be called on every mousemove event, but the feature argument will be passed only when the mouse is above a topoJSONLayer.featureName feature.


The following selectors are available for events:

import { LeafletMap } from '@unovis/ts'
events = {
[LeafletMap.selectors.point]: {},
[LeafletMap.selectors.innerLabel]: {},
[LeafletMap.selectors.bottomLabel]: {},
[]: {}

Component Props

* required property