Leaflet - display rotated text with svg

Viewed 64

I'm trying to display text in Leaflet using a marker layer with icon as L.divIcon (with svg text). This works fine. However when the text is rotated the text is clipped outside the viewport. I've tried to adjust this in several ways, but to no avail. For example, I've tried to extract the BBox after rotation, and then update the svg viewBox parameters, but this doesn't work as expected.

Here you can see the viewbox of the svg containing rotated text. The text content 125BET is clipped. The text 10665 is barely rotated, and is displayed correctly.

enter image description here

Here is the viewbox of the text component, and I want to use this information to avoid clipping:

enter image description here

Code:

let svgIcon = L.divIcon({
  html: `
<svg
  version="1.1"
  id="${svgGcTextId}"
  preserveAspectRatio="none"
  xmlns="http://www.w3.org/2000/svg"
>
  <text width="5000" height="5000" x="0" y="${fontSize}" transform="rotate(${rotation})" font-size="${fontSize}">${text}</text>
</svg>`,
  className: "",
  iconSize: [24, 10],
  iconAnchor: [0, fontSize],
});

let marker = L.marker(latlng, { icon: svgIcon, interactive: false });

Edit:

Tried adding transform-origin as per Peters suggestion. Although this seems to work, it shifts the insertion point of the text which isn't desirable. See pic below, where the "1" in 125BET is translated south of the building. It's possible to see more of the text though, as more of it is inside the viewbox.

enter image description here

Edit2:

Here is a fiddle. Change the rotation and fontSize values to test different scenarios. Note that I have code for scaling the font size when zooming (but that shouldn't be relevant here)

https://jsfiddle.net/6vw5z20k/20/

Edit3 - solved

Thanks to Peters input I managed to solve it with the following code. It was necessary to specify a large viewbox (e.g. 600 x 600), then insert the text in the center using x and y. It was also necessary to specify the transform-origin, so the rotation was performed correctly. Last but not least the svg had to be centered at the reference latlng using iconAnchor.

const svgIcon = L.divIcon({
  html: `
<svg
  version="1.1"
  id="${svgGcTextId}"
  preserveAspectRatio="none"
  xmlns="http://www.w3.org/2000/svg"
  width="600"
  height="600"
>
  <text x="300" y="300" transform-origin="300 300" transform="rotate(${rotation})" font-size="${fontSize}">${text}</text>
</svg>`,
  className: "",
  iconSize: [24, 10],
  iconAnchor: [300, 300],
});

let marker = L.marker(latlng, { icon: svgIcon, interactive: false });

Lots of room to rotate the text 125BET here, but the viewbox might get to small if one zooms far in and the text is scaled too much.

2 Answers

Originally, I thought that all you needed to do was add: transform-origin="120 60" (or something similar) to the <text> element of your svg code.

However, once you updated your question, it appears that the problem may well be with setting the height and width on the svg itself.

You can still probably move the origin with transform-origin or just set the x and y on the <text> element.

Here is my code snippet (based on your jsFiddle). Please look for the rotated LONDON.

var map = L.map('map').setView([51.505, -0.09], 13);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
  maxZoom: 19,
  attribution: '© OpenStreetMap'
}).addTo(map);

let fontSize = "60px";
let svgGcTextId = "";
let rotation = "45";
let text = "LONDON";
let latlng = [51.505, -0.09];

let svgIcon = L.divIcon({
  html: `
<svg
  version="1.1"
  id="${svgGcTextId}"
  preserveAspectRatio="none"
  xmlns="http://www.w3.org/2000/svg"
  width="300"
  height="300"
>
  <text width="5000" height="5000" x="30" y="30" transform="rotate(${rotation})" font-size="${fontSize}">${text}</text>
</svg>`,
  className: "",
  iconSize: [24, 10],
  iconAnchor: [0, 0],
});

let marker = L.marker(latlng, {
  icon: svgIcon,
  interactive: true
});
map.addLayer(marker);

let referenceMarker = L.circleMarker(latlng, {radius: 5});
map.addLayer(referenceMarker);
#map {
  height: 400px;
}
<html>
<head>
  <link rel="stylesheet" href="https://unpkg.com/leaflet@1.8.0/dist/leaflet.css" integrity="sha512-hoalWLoI8r4UszCkZ5kL8vayOGVae1oxXe/2A4AO6J9+580uKHDO3JdHb7NzwwzK5xr/Fs0W40kiNHxM9vyTtQ==" crossorigin="" />
  <!-- Make sure you put this AFTER Leaflet's CSS -->
  <script src="https://unpkg.com/leaflet@1.8.0/dist/leaflet.js" integrity="sha512-BB3hKbKWOc9Ez/TAwyWxNXeoV9c1v6FIeYiBieIWkpLjauysF18NzgR1MBNBXf8/KABdlkX68nAhlwcDFLGPCQ==" crossorigin=""></script>
  <body>
    <div id="map"></div>
  </body>
</head>
</html>

You can also circumvent the need for width or height adjustments by setting a overflow="visible" attribute like so:

let svgIcon = L.divIcon({
  html: `
<svg
  version="1.1"
  id="${svgGcTextId}"
  xmlns="http://www.w3.org/2000/svg"
  overflow="visible"
>
  <text x="20" y="0" dy="${parseFloat(fontSize)*0.333}" transform="rotate(${rotation})" font-size="${fontSize}">${text}</text>
</svg>`,
  className: "",
  iconSize: [24, 10],
  iconAnchor: [0, 0],
});

Usually you would try to calculate a suitable width/height or viewBox to prevent colliding/overlapping svg elements.
For map usage it should work fine.

x="20" will add some space between your marker and the text.
dy="${parseFloat(fontSize)*0.333}" will give the text a baseline shift relative to the current font-size.

BTW: Neither width nor height attributes have any effects on a <text> element

var map = L.map('map').setView([51.505, -0.09], 13);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
  maxZoom: 19,
  attribution: '© OpenStreetMap'
}).addTo(map);

let fontSize = "60px";
let svgGcTextId = "";
let rotation = "45";
let text = "LONDON";
let latlng = [51.505, -0.09];

let svgIcon = L.divIcon({
  html: `
<svg
  version="1.1"
  id="${svgGcTextId}"
  xmlns="http://www.w3.org/2000/svg"
  overflow="visible"
>
  <text x="20" y="0" dy="${parseFloat(fontSize)*0.333}" transform="rotate(${rotation})" font-size="${fontSize}">${text}</text>
</svg>`,
  className: "",
  iconSize: [24, 10],
  iconAnchor: [0, 0],
});

let marker = L.marker(latlng, {
  icon: svgIcon,
  interactive: true
});
map.addLayer(marker);

let referenceMarker = L.circleMarker(latlng, {radius: 5});
map.addLayer(referenceMarker);
#map {
  height: 75vh;
}

.leaflet-map-pane svg {
    z-index: 200;
  border:1px solid green;
}
 <link rel="stylesheet" href="https://unpkg.com/leaflet@1.8.0/dist/leaflet.css" integrity="sha512-hoalWLoI8r4UszCkZ5kL8vayOGVae1oxXe/2A4AO6J9+580uKHDO3JdHb7NzwwzK5xr/Fs0W40kiNHxM9vyTtQ==" crossorigin="" />
  <!-- Make sure you put this AFTER Leaflet's CSS -->
  <script src="https://unpkg.com/leaflet@1.8.0/dist/leaflet.js" integrity="sha512-BB3hKbKWOc9Ez/TAwyWxNXeoV9c1v6FIeYiBieIWkpLjauysF18NzgR1MBNBXf8/KABdlkX68nAhlwcDFLGPCQ==" crossorigin=""></script>
    <div id="map"></div>

Related