makinacorpus / leaflet.textpath Goto Github PK
View Code? Open in Web Editor NEWShow text along Polyline with Leaflet
Home Page: https://makinacorpus.github.io/Leaflet.TextPath/
License: MIT License
Show text along Polyline with Leaflet
Home Page: https://makinacorpus.github.io/Leaflet.TextPath/
License: MIT License
We have a couple of mobile apps for mountain biking and hiking trails. Users request the trail name along the polyline, so we tried your plugin.
Due to the screen size on mobile devices the trail name along the path looks quite weird, letters overlap or are spread out or single letters rotate etc.
Is there an option or recommendation to draw the text in a more readable way along a path?
When you set the center attribute to true, code is throwing an error at this line on completion of a polyline:
var pathWidth = this._path.getBBox().width;
I guess you can't get the width of a path element (it only has coordinates)....so I changed the above line to this and it works now:
//Fix:
var pathWidth = this._path.getBoundingClientRect().width;
We are building a mobile hiking app and on a couple of trails we noticed that setting
center: true
seems to have no effect, while it works on the other trails.
For the two trails where it is not working as supposed, I think the problem is due to the fact that those trails have a cross section. This might result in a duplicated lat,lng pair in the polyline. Here is a screenshot:
The last available version on npm isn't up to date with github.
Need to make a new tag version including below option.
https://www.npmjs.com/package/leaflet-textpath
Thank you !
When removing a layer from the map and re-adding it. The _map property becomes "null" and setText no longer works. Not sure if this is a core Leaflet problem or TextPath problem.
Hi,
I'm struggling with a strange problem,
If I set polyline opacity to 1.0, texts getting lost.
How can we fix this?
Hi,
I have several polylines that I add to a specific layergroup. Is there any way to use the plugin like this? If I register the events on the polylines, the mouseover does not trigger and if i try it with on.click I am getting exceptions like "rootpath not found" or Object [object global] has no method 'setText'
edit: I have seen that the mouseover is a canvas problem, canvas does not support mouse over/out events.
edit2: I have a jsfiddle here, that does NOT use a layergroup but a click event, and it tells me "Uncaught TypeError: Cannot call method 'setAttribute' of undefined "
http://jsfiddle.net/dP9aG/25/
Make a PR to Leaflet gh-pages branch (plugins.md).
Is it possible to include an option to align the text with the center of the polyline?
This is especially useful when the user has greater zoom than the polyline size and wants to be able to see the text, that in my case is the length (in meters) of the polyline.
Leaflet 1.0 is breaking your plugin, are there any plans to upgrade?
I am using the "material-icons" for the font of `TextPath". The effect looks like this
Following is my code using "Leaflet.TextPath"
pathLayer.setText ' ',
repeat: true
offset: 8
attributes:
'font-weight': 'bold',
'font-size': '24'
'class': "material-icons"
I found it didn't work well because 
is escaped by the setText
method.. Is there a way to tell setText
to not escape this?
Dear developers,
Could you please update this so that it could be used with the new leaflet.js (1.1.0). It would be soooo great!!!.
Error from console: TypeError: L.Path.prototype._createElement is not a function[Learn More] when I used it with leaflet 1.1.0.
Thanks.
When lines are drawn from right to left, the text is shown upside down. It would be nice if the text would always read left to right or top to bottom if vertical line.
There is a workaround just setText with null as text.
polyline.setText(null, textOptions); // erase the previous text
polyline.setText(newText, textOptions); // draw the new one
not an issue, just wondering what your thoughts about the feasibility of adapting this kinda textPath for use with the Google Maps API?
I see how your creating a SVG textpath element. I just am not sure this is something can be bound to a polyline in Google Maps?
When label is an integer, exception is thrown:
"leaflet.textpath.js:76 Uncaught TypeError: text.replace is not a function"
text = text.replace(/ /g, '\u00A0'); // Non breakable spaces
We can solve this by converting int to string before passing it to the function, but I think that this should be done by the plugin itself.
Thank you for the plug-in!
I am getting the error NS_ERROR_FAILURE in Firefox 49 when adding the orientation option to the following:
layer.setText(feature.properties.Name, {center: true, offset: -5, orientation: 'flip', attributes: { ... }});
Along with the error, the text for a single feature renders, in an unexpected location. Without adding the orientation attribute, it works fine. The orientation works in Chrome. It doesn't matter what value is given for the orientation.
I need a little help, ok maybe a lot. The labels are not displaying on my map. I am trying to create a webpage with a large amount of polylines in several external GeoJSON files. Any advice or clues would be appreciated.
// sample code for website:
var centralsierra = $.getJSON("geojson/centralsierra.geojson")
.then(function(data){
L.geoJson(data, {
style: function (feature) {
return {color: feature.properties.color};
},
onEachFeature: function (feature, layer) {
layer.setText(feature.properties.NAME);
},
onEachFeature: function (feature, layer) {
layer.bindPopup('Name: ' + feature.properties.NAME + '
' +
'Open: ' + feature.properties.OPEN + '
' +
'Limited: ' + feature.properties.LIMITED + '
' +
'Route Type: ' + feature.properties.ROUTE_TYPE + '
' +
'Field Office: ' + feature.properties.FIELD_OFC + '
' +
'Subregion: ' + feature.properties.SUBREGION + '
' +
'GPX: ' + feature.properties.GPX + '
' +
'KML: ' + feature.properties.KML);
}
}).addTo(map);
});
// sample geojson file:
{
"type": "FeatureCollection",
"crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } },
"features": [
{ "type": "Feature", "properties": { "GID": null, "NAME": "12N38", "OPEN": "Y", "LIMITED": "None", "ROUTE_TYPE": "Full Size","color":"#537F21","AGENCY": "USFS", "FIELD_OFC": "El Dorado NF", "PRIVATE": null, "ROUTE_DB": "USFS", "NEW_NAME": null, "SUBREGION": "Central Sierra Nevada", "GPX": "<a href="http://www.owlsheadgps.com/downloads/gpx/central_sierra_nevada/zip/12n38.gpx.zip">Click Here</a>", "KML": "<a href="http://www.owlsheadgps.com/downloads/kml/central_sierra_nevada/12n38.kml">Click Here</a>" }, "geometry": { "type": "LineString", "coordinates": [...] } },
{ "type": "Feature", "properties": { "GID": null, "NAME": "12N38A", "OPEN": "Y", "LIMITED": "None", "ROUTE_TYPE": "Full Size", "color":"#537F21","AGENCY": "USFS", "FIELD_OFC": "El Dorado NF", "PRIVATE": null, "ROUTE_DB": "USFS", "NEW_NAME": null, "SUBREGION": "Central Sierra Nevada", "GPX": "<a href="http://www.owlsheadgps.com/downloads/gpx/central_sierra_nevada/zip/12n38a.gpx.zip">Click Here</a>", "KML": "<a href="http://www.owlsheadgps.com/downloads/kml/central_sierra_nevada/12n38a.kml">Click Here</a>" }, "geometry": { "type": "LineString", "coordinates": [ ... ] } },
I am using version 0.7.3 of Leaflet, and the gh-pages branch. I am using a layergroup of polylines, and on each I am calling setText. I keep however getting an error because this._path is undefined.
Hi, in my project I am using leaflet panes http://leafletjs.com/examples/map-panes.html
It turns out that this plugin does not consider them at all, hence the paths get drawn in the custom pane while the text is drawn on the overlay pane. This leads to paths been shown above other features while text gets drawn below them. It would be great if the plugin could check on which pane its path gets drawn and put the text on the same one accordingly.
Is there any way to flip the text when the line runs left to right (so it isn't upside down?)
cheers
This plugin does not work for me when i enable HTML canvas via <script>L_PREFER_CANVAS = true;</script>
.
It would be great if it did.
Don't know how hard this feature would be to implement.
Hi,
Am working on updating my code to Leaflet 1.x, and in my old code the textpath used to be clickable (allowing me to launch a popup by clicking on the text). Not having much luck getting this working now, am using latest version of leaflet.textpath, and am setting options.clickable to true (even tried initialising the mouse events without the options check).
Anyone have any ideas/tips? Is this a bug or something that needs a new PR?
Cheers
Hi there,
I noticed that in Chromium/Chrome browsers, the clickable area of the text path of a linestring is the entire bounding box of the linestring. Yikes! You can see that on the Leaflet.TextPath demo page (and for more detail, highlight the bounding box using the Chromium/Chrome "Inspect element" feature). Consequently, when one has several clickable linestrings on a page, it's difficult to click the one one wants :-(
This problem does not occur with Firefox, wherein the clickable area is just the text path, thank goodness.
Hi, I'm incorporating this nifty script in a map I'm producing and so far it works pretty well. However, it seems that on _updatePath, the map's layer order gets shifted, breaking (previously working) popups, as the text becomes the topmost layer. Any ideas for how to deal with this?
It would be great to have an option to choose between static (current behaviour) and dynamic centring of the label on partially visible paths. By dynamic I mean that the label position should be recomputed on moveend so that it stands in the centre of the visible portion of it.
This way the label would be visible to the user until there is no space left to place it instead of standing outside of the map only because the centre of the whole path it labels is out of view.
Hi,
I just noticed the new 1.0.x tags and was wondering if they are meant to indicate compatibility to leaflet 1.0 It seems they are on the gh-pages branch but I see no merge of the 0.8-dev one which added leaflet 1.0 support. Should it be merged in to target the new version officially for any new development?
var popup = new L.popup({ minWidth: 400, closeButton: false, autoPanPaddingTopLeft: L.point(0, 50) });
layer = L.polyline(points, {color: color, weight: 5});
layer.bindPopup(popup);
layer.on('mouseover', function(e) {
this.setText(' ► ', {repeat: true, attributes: {fill: textColor.toHexString() }});
});
layer.on('mouseout', function(e) {
this.setText(null);
});
The popUp doesn't work anymore if this.setText is executed.
Is it possible to determine the zoom level from which labels are displayed along the polylines?
I have a number of paths to display that can grow large at lower zoom levels, hence I noticed a sensible performance degradation when displaying more than 100 paths, up to few hundreds. Around 200 it takes a bit more than 3 seconds to get them on the map at 700 it gets up to 7 seconds, which is a bit too much for an interactive map.
With a bit of profiling in FF I pointed out the issue to lie at textRedraw and setText methods.
Attached a section of the profile which indicates in green the time taken to populate a layer with labelled paths, while the darker red section shows the time taken to add event handlers to the same number of paths.
To be noted also that most of the paths, since they are at a low zoom level (hence seen from up above) end up with having no label at all as it won't fit the path.
Hi,
I need to add to my geojson layer text/label for each feature.
Is it possible to do this with Leaflet.TextPath? If not, can it be added to this plugin?
:)
Found a little bug. To reproduce:
var map = new L.map(...);
var layer = L.polyLine(...).addTo(map);
layer.on('mouseover', function () {
this.setText(' ► ', {repeat: true, attributes: {fill: 'red'}});
});
layer.on('mouseout', function () {
this.setText(null);
});
map.removeLayer(layer);
At this point the javascript will crash with (in Chrome):
Uncaught NotFoundError: Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.
There's a jsfiddle that shows this issue. (shamelessly based this fiddle from a fiddle from another issue)
http://jsfiddle.net/vhpgomes/cmhbQ/19/
The error occurs because the textNode is removed when setting the text to null, but the plugin tries to remove it again when removing the polyline. I fixed it simply by deleting the text when it's first removed (the code already checks for it in the second removal):
delete this._textNode;
I'm new to this github stuff... what should I do? A pull request? Just for this one single line?
Hello. Thanks for this library. Can you publish v1.1.0 to npm when you have a moment? Thanks!
I am a bit confused by the branches and what version of leaflet they are supposed to be used for.
On branch master
it states: "The version on the gh-pages
branch targets Leaflet 1.3.1."
Then when you switch to the gh-pages
branch it states: "The version on the gh-pages
branch targets Leaflet 0.7.3."
If a path is south -> north oriented, the label does not get correctly centred as the centring seems to occur only on the W-E axis. For a "mostly vertical" feature the centre should be computed accounting for the (visible) vertical part of the path.
To sum up: 2D centre detection, then projected on the path to avoid "out of path" labelling (e.g. L shaped feature)
Hi,
What I'd like to achieve is putting a textpath at the end of the polyline. Would it be possible to add an option for that?
Adding this code for 'option.end' works for my particular use-case, but text placement is dependent on angle of the path still:
/* put text at end of the path */
if (options.end) {
var textWidth = textNode.getBBox().width;
var pathWidth = this._path.getBBox().width;
/* Set the position for the left side of the textNode */
textNode.setAttribute('dx', pathWidth - textWidth );
}
Together with '\u25BA' something like this would allow for arrowheads on polylines.
hth,
Emile
The demo at http://makinacorpus.github.com/Leaflet.TextPath/ fails to load its map tiles (bad request) and it also appears to have a bad link to the font-awesome css (404s).
Hi there, the TextPath demo at https://makinacorpus.github.io/Leaflet.TextPath/ does not work on my browser (Firefox 42.0), because the webpage has mixed content (HTTP and HTTPS calls).
Could you fix this by making all calls over HTTPS?
Hi,
Thanks for this awesome plugin. I was just wondering if anyone was working on a Leaflet 1.0 compatible version of this?
For example, I have a character looks like this:
If I use this for setText()
method, it will be perpendicular to the path, which is not ideal. So I need to rotate each character by 90 degree.. However, I found it may not be easy by setting the attribute of <textpath>
element..
Will Leaflet.TextPath support rotation for character?
Hi,
First of all thanks for your plugin, it's really helpful.
Currently if I install your plugin using bower with bower install https://github.com/makinacorpus/Leaflet.TextPath.git
it will retrieve the 0.1.0
release which dates from 10 month ago, and is malfunctioning in IE8.
The temporary solution is to run bower install https://github.com/makinacorpus/Leaflet.TextPath.git#4b67826e4df1e56d56ad3c6e143379a1ce7f4a71
with the last part being the last commit (because I like bleeding edge software) but it's not really user friendly or scalable, if I want to stay up to date...
Hi, everything in the title :)
i tried to set the font-family in the attributes to use font awesome, but when i try to set the text:
layer.setText(' \f1b0 ', {repeat: false , center :true,offset:8, attributes: {fill: 'red', 'font-size': '24','font-family': 'FontAwesome'}});
but the text shows as 1b0.
Thanks
Updated to use L.SVG and this._map._renderer:
`/*
import L from 'leaflet';
(function () {
var __onAdd = L.Polyline.prototype.onAdd,
__onRemove = L.Polyline.prototype.onRemove,
__updatePath = L.Polyline.prototype._updatePath,
__bringToFront = L.Polyline.prototype.bringToFront;
var PolylineTextPath = {
onAdd: function (map) {
__onAdd.call(this, map);
this._textRedraw();
},
onRemove: function (map) {
map = map || this._map;
if (map && this._textNode)
map._pathRoot.removeChild(this._textNode);
__onRemove.call(this, map);
},
bringToFront: function () {
__bringToFront.call(this);
this._textRedraw();
},
_updatePath: function () {
__updatePath.call(this);
this._textRedraw();
},
_textRedraw: function () {
var text = this._text,
options = this._textOptions;
if (text) {
this.setText(null).setText(text, options);
}
},
setText: function (text, options) {
this._text = text;
this._textOptions = options;
/* If not in SVG mode or Polyline not added to map yet return */
/* setText will be called by onAdd, using value stored in this._text */
if (!L.Browser.svg || typeof this._map === 'undefined') {
return this;
}
var defaults = {
repeat: false,
fillColor: 'black',
attributes: {},
below: false,
};
options = L.Util.extend(defaults, options);
/* If empty text, hide */
if (!text) {
if (this._textNode && this._textNode.parentNode) {
this._map._renderer._container.removeChild(this._textNode);
/* delete the node, so it will not be removed a 2nd time if the layer is later removed from the map */
delete this._textNode;
}
return this;
}
text = text.replace(/ /g, '\u00A0'); // Non breakable spaces
var id = 'pathdef-' + L.Util.stamp(this);
var svg = this._map._renderer;
this._path.setAttribute('id', id);
if (options.repeat) {
/* Compute single pattern length */
var pattern = document.createElementNS('ttp://www.w3.org/2000/svg', 'text');
for (var attr in options.attributes)
pattern.setAttribute(attr, options.attributes[attr]);
pattern.appendChild(document.createTextNode(text));
svg.appendChild(pattern);
var alength = pattern.getComputedTextLength();
svg.removeChild(pattern);
/* Create string as long as path */
text = new Array(Math.ceil(this._path.getTotalLength() / alength)).join(text);
}
/* Put it along the path using textPath */
var textNode = L.SVG.create('text'),
textPath = L.SVG.create('textPath');
var dy = options.offset || this._path.getAttribute('stroke-width');
textPath.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", '#'+id);
textNode.setAttribute('dy', dy);
for (var attr in options.attributes)
textNode.setAttribute(attr, options.attributes[attr]);
textPath.appendChild(document.createTextNode(text));
textNode.appendChild(textPath);
this._textNode = textNode;
if (options.below) {
svg.insertBefore(textNode, svg.firstChild);
}
else {
svg._container.appendChild(textNode);
}
/* Center text according to the path's bounding box */
if (options.center) {
var textLength = textNode.getComputedTextLength();
var pathLength = this._path.getTotalLength();
/* Set the position for the left side of the textNode */
textNode.setAttribute('dx', ((pathLength / 2) - (textLength / 2)));
}
/* Change label rotation (if required) */
if (options.orientation) {
var rotateAngle = 0;
switch (options.orientation) {
case 'flip':
rotateAngle = 180;
break;
case 'perpendicular':
rotateAngle = 90;
break;
default:
rotateAngle = options.orientation;
}
var rotatecenterX = (textNode.getBBox().x + textNode.getBBox().width / 2);
var rotatecenterY = (textNode.getBBox().y + textNode.getBBox().height / 2);
textNode.setAttribute('transform','rotate(' + rotateAngle + ' ' + rotatecenterX + ' ' + rotatecenterY + ')');
}
/* Initialize mouse events for the additional nodes */
if (this.options.clickable) {
if (L.Browser.svg || !L.Browser.vml) {
textPath.setAttribute('class', 'leaflet-clickable');
}
L.DomEvent.on(textNode, 'click', this._onMouseClick, this);
var events = ['dblclick', 'mousedown', 'mouseover',
'mouseout', 'mousemove', 'contextmenu'];
for (var i = 0; i < events.length; i++) {
L.DomEvent.on(textNode, events[i], this._fireMouseEvent, this);
}
}
return this;
}
};
L.Polyline.include(PolylineTextPath);
L.LayerGroup.include({
setText: function(text, options) {
for (var layer in this._layers) {
if (typeof this._layers[layer].setText === 'function') {
this._layers[layer].setText(text, options);
}
}
return this;
}
});
})();
`
Hello,
I have problem with TextPath plugin when I use LayerControl to show / hide the layer which includes polyline with symbol.
NotFoundError: Node was not found
this._map._pathRoot.removeChild(this._textNode);
<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7/leaflet.css" />
<script src="http://cdn.leafletjs.com/leaflet-0.7/leaflet.js"></script>
<script src="./leaflet.textpath/leaflet.textpath.js"></script>
<style>
#map {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%
}
</style>
<script type="text/javascript">
var osm = L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: 'Map data © 2013 OpenStreetMap contributors',
});
var map = L.map('map')
.fitWorld()
.addLayer(osm);
var layersControl = new L.Control.Layers();
var trailLayer = new L.LayerGroup();
map.addLayer(trailLayer);
layersControl.addOverlay(trailLayer, 'Trail');
map.addControl(layersControl);
var polyline = new L.polyline([[-40.311, -31.952], [-12.086, -18.727]], {
weight: 10,
color: 'orange',
opacity: 0.8
});
trailLayer.addLayer(polyline);
polyline.setText('\u25BA', {
repeat: true,
offset: 6,
attributes: {fill: 'red'}
});
</script>
Thanks for your advice.
Dusan
It's possible for this._path.getTotalLength() / alength
to be computed as 0/0
. This is not possible in math and breaks the script. A simple check for NaN will resolve the bug.
See my pull request - #79
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.