Reputation: 175
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
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
Reputation: 126355
You can't use HTML in symbol layers. You can:
Marker
objects instead of symbol layers.Upvotes: 2