Creating dynamic text on icons in Leaflet using SVG

Posted: | Tags: til webdev

Map of the Netherlands with a circular marker with the test written in the middle.

Leaflet is a popular and feature-rich JavaScript library for displaying maps. One of these features includes creating and placing markers on a map with an icon, that could represent a dropped pin or something custom. I need to create icons for markers that also contain a number that could be different for each marker and adding text to Leaflet icons can be done in a few ways.

  1. Using L.DivIcon to insert a div element inside of an image. A StackOverflow answer from iH8 gives an example of how to accomplish this.
  2. Using a markers Tooltip and some CSS to place the tooltip text over or around an icon. This StackOverflow answer from IvanSanchez demonstrates this with an example and also shows the L.DivIcon icon route as well.

Option 2 was not possible for me as I was already using the tooltip for each marker to display related information about the location. That left me with option 1, which I was oblivious to until after I discovered a third way. So here’s option 3, creating an SVG icon and editing the contents based on the marker.1

    let svgText = 'test'
    let circleSVGString = '<svg version="1.2" baseProfile="tiny" xmlns="http://www.w3.org/2000/svg" width="250" height="250"><circle cx="125" cy="125" r="100" fill="#db7900"/><text x="50%" y="50%" text-anchor="middle" fill="white" font-size="100px" font-family="Arial" dy=".3em">'+svgText+'</text></svg>'
    let iconURL = "data:image/svg+xml," + encodeURIComponent(circleSVGString);
    var circleIcon = L.icon({
        iconUrl: iconURL,
        iconSize: [40, 40]
    });
    var testMarker = new L.marker(
        [52.0877, 5.13199],
        { icon:circleIcon }
    ).addTo(map_1);

From the code snippet above variable circleSVGString contains the contents of the SVG. This is a radius with centered text concatenated from the string defined in the svgText variable. Credits go to BruceBC for creating the circle and text in a CodePen. The entire SVG string is then converted to a data URL and a Leaflet icon is created with a height and width of 40px. I was only able to figure out this part from the discussion in this GitHub gist and in particular the comment made by DynamicArray.

Finally, a new marker is created using the icon, set to coordinates, and added to the map.

The results of the code can be viewed in the first image of this post, the full code to reproduce this is available below.

<!DOCTYPE html>
<head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
    <title>Leaflet Marker with custom text</title>
    
    <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/leaflet@1.9.3/dist/leaflet.css"/>
    <script src="https://cdn.jsdelivr.net/npm/leaflet@1.9.3/dist/leaflet.js"></script>
    <meta name="viewport" content="width=device-width,initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
        
    <script src="https://tinyworldmap.com/dist/v3/tiny-world-all-4000.js"></script>

    <style>
        #map {
            height: 90vh;
            width: 90vw;
        }
    </style>
</head>
<body>
    <div id="map"></div>
    <script>
        var map_1 = L.map(
            "map",
            {
                center: [52.0, 6.0],
                crs: L.CRS.EPSG3857,
                zoom: 8,
                zoomControl: false,
                preferCanvas: true,
                worldCopyJump: true,
            }
        );
        new L.GridLayer.TinyWorld({tileSize: 256, maxZoom: 18}).addTo(map_1)
        
        let svgText = 'test'
        let circleSVGString = '<svg version="1.2" baseProfile="tiny" xmlns="http://www.w3.org/2000/svg" width="250" height="250"><circle cx="125" cy="125" r="100" fill="#db7900"/><text x="50%" y="50%" text-anchor="middle" fill="white" font-size="100px" font-family="Arial" dy=".3em">'+svgText+'</text></svg>'
        let iconURL = "data:image/svg+xml," + encodeURIComponent(circleSVGString);
        var circleIcon = L.icon({
            iconUrl: iconURL,
            iconSize: [40, 40]
        });
        var testMarker = new L.marker(
            [52.0877, 5.13199],
            { icon:circleIcon }
        ).addTo(map_1);
        testMarker.bindTooltip("Some other tooltip text.")
    </script>
</body>

  1. I’m fairly new to Leaflet so this may not be the best way of doing it, feel free to point out any improvements, comments, or questions on Mastodon↩︎


Related ramblings