nova
nova

Reputation: 175

In Mapbox GL is there a way to have the labels for symbols be custom HTML

I'm creating a symbol layer with the layer symbols using custom images which is working well. I also want to create custom labels with HTML (basically a blue background with an eased border and the label) but I'm unsure whether that's possible or how. I'm including what I'm using right now to render the points, the get icons renders the custom images for each point which are loaded in advance using map.loadImage.

map.addLayer({
        id: 'points',
        type: 'symbol',
        source: 'points',
        paint: {
          "text-color": "#ffffff",
        },
        layout: {
          'icon-image': ['get', 'icon'], // 'cat',
          'icon-size': 1,
          'icon-allow-overlap': true,
          
           'text-field': ['get', 'name'],
           'text-font': ['Open Sans Semibold', 'Arial Unicode MS Bold'],
           'text-offset': [0, 2.00],
           'text-size': 14,
           'text-anchor': 'top',
           'text-allow-overlap': false,
        },
      })

Upvotes: 1

Views: 4178

Answers (2)

Sélim Achour
Sélim Achour

Reputation: 728

This is almost what I've done in the past for a house rental agency. Basically, I had to show a label with a price, whether the house had 3d pictures, add a blue background ... Below is the source code that you can modify to satisfy your needs

What I did, is code all the infos in "icon-image" like this:

...
'icon-image': ['concat', 'projectmarker|', ['get', 'id'], '|', ['get', 'price'], '|', ['get', '3d'], '|', ['get', 'name'], '|', ['get', 'highlight']]
...

What happens then is that mapbox does not find the image and calls a "styleimagemissing" callback which does all the work using the element and converting it to a dataimage at the very end


const missingImages = [];
map.on('styleimagemissing', function (e) {
        const id = e.id;
        const blue = '#1f2d41';
        const white = '#ffffff';
        const yellow = '#a3a326';

        // only create once
        if (missingImages.indexOf(id) !== -1) return;
        missingImages.push(id);

        // check if this missing icon is one this function can generate
        if (id.indexOf('projectmarker') !== 0) return;

        // extract infos
        const projectId = parseInt((id.split('|')[1]));
        let price = parseInt((id.split('|')[2])).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
        const hasPrice = price !== "0";
        const threeD = 'true' === (id.split('|')[3]);
        const highlight = '1' === (id.split('|')[5]);

        if (!hasPrice) {
            price = id.split('|')[4];
        } else {
            price += ' ' + currencyCode;
        }

        // create canvas
        const canvas = document.createElement("canvas");
        const ctx = canvas.getContext("2d");

        const height = 20;
        const leftWidth = 40;
        const rightWidth = (8 * price.length) + 10;
        const leftBg = blue;
        const rightBg = highlight ? yellow : white;
        const radius = 4;

        if (threeD) {
            // 3d bg
            ctx.fillStyle = leftBg;
            roundRect(ctx, 0, 0, leftWidth, height, {tl: radius, tr: 0, br: 0, bl: radius}, true, true);

            // 3d text
            ctx.textAlign = "center";
            ctx.font = "bold 14px Arial";
            ctx.fillStyle = white;
            ctx.fillText('360°', leftWidth / 2, 16);

            // price bg
            ctx.fillStyle = rightBg;
            roundRect(ctx, leftWidth, 0, rightWidth, height, {tl: 0, tr: radius, br: radius, bl: 0}, true, true);
        } else {
            // price bg
            ctx.fillStyle = rightBg;
            roundRect(ctx, 0, 0, rightWidth, height, radius, true, true);
        }

        // price
        ctx.textAlign = "center";
        ctx.font = "14px Arial";
        ctx.fillStyle = blue;
        ctx.fillText(price.replace(',', ' '), (threeD ? leftWidth : 0) + (rightWidth / 2), 15);

        // extract data and create mapbox image
        const imageData = ctx.getImageData(0, 0, (threeD ? leftWidth : 0) + rightWidth, height);
        map.addImage(id, imageData);

    });

Below is the roundRect helper

const roundRect = (ctx, x, y, width, height, radius, fill, stroke) => {
    if (typeof stroke === 'undefined') {
        stroke = true;
    }
    if (typeof radius === 'undefined') {
        radius = 5;
    }
    if (typeof radius === 'number') {
        radius = {tl: radius, tr: radius, br: radius, bl: radius};
    } else {
        const defaultRadius = {tl: 0, tr: 0, br: 0, bl: 0};
        for (let side in defaultRadius) {
            radius[side] = radius[side] || defaultRadius[side];
        }
    }
    ctx.beginPath();
    ctx.moveTo(x + radius.tl, y);
    ctx.lineTo(x + width - radius.tr, y);
    ctx.quadraticCurveTo(x + width, y, x + width, y + radius.tr);
    ctx.lineTo(x + width, y + height - radius.br);
    ctx.quadraticCurveTo(x + width, y + height, x + width - radius.br, y + height);
    ctx.lineTo(x + radius.bl, y + height);
    ctx.quadraticCurveTo(x, y + height, x, y + height - radius.bl);
    ctx.lineTo(x, y + radius.tl);
    ctx.quadraticCurveTo(x, y, x + radius.tl, y);
    ctx.closePath();
    if (fill) {
        ctx.fill();
    }
    if (stroke) {
        ctx.stroke();
    }

}

Upvotes: 0

Steve Bennett
Steve Bennett

Reputation: 126355

You can't use HTML in symbol layers. You can:

  • Use Marker objects instead of symbol layers.
  • Use formatted content within symbol layers, with a mixture of fonts, font weights etc.
  • Use 9-part images to make custom borders.

Upvotes: 2

Related Questions