Add a title to leaflet layers control - javascript

Does anyone have a method for adding a title to a leaflet layers control? Just a line of text, for example "Available layers." Ideally I'd like to add a link to the text as well.
I thought it would be simple, but I haven't been able to find a solution. I tried methods similar to this question (radio button/checkbox remains after adding "dummy" layer) and this question (adds div to end of layers, seems more complex than my needs). Unfortunately with my experience level, I haven't been able to connect the dots. Any suggestions?

Input Elements in Layer Control are present under
.leaflet-control-layers-overlays
$(".leaflet-control-layers-overlays").prepend("<label>Available layers</label>");
You can also assign a class and add some styling on this. This is not good solution but hope it helps you.

#Leaflet1.7
Here is the vanilla JS minimal solution.
// 0. Your's WMS layers:
var fooLayers = {
"Aaa" : ign_ari,
"Bbb" : ign_cri,
"Ccc" : ign_grvi
};
// 1. start with Control
var fooLegend = L.control({position: 'topleft'});
fooLegend.onAdd = function () {
var div = L.DomUtil.create('div');
// here is Your part:
div.innerHTML = '<span class='your-class'>Your Title Text</span>';
return div;
};
fooLegend.addTo(map);
var fooCtrl = L.control.layers(fooLayers, null,
{collapsed : false, position: 'topleft'})
.addTo(map);
//
// Nothing unusual, until now:
var fooCtrlDiv = fooCtrl.getContainer();
fooCtrlDiv.insertBefore(fooLegend.getContainer(), fooCtrlDiv.firstChild);

You can assign a title attribute to the control. Here's an obviously incomplete snippet from a current project:
// Add settings button
var atlasMapSettings = L.Control.extend({
options: {
position: 'topleft'
},
onAdd: function (map) {
var control = L.DomUtil.create('div', 'leaflet-bar leaflet-control leaflet-control-custom atlas-settings-control closed');
var icon = L.DomUtil.create('div', 'fa fa-gear closed', control);
var content = L.DomUtil.create('div', 'control-content empty', control);
$(icon).attr('title', 'Map settings');

Leaflet 1.7: Addition to #Mruk's answer: If you want to use collapsed: true, then you have to dig a little bit deeper in the DOM tree:
const fooCtrlDiv = fooCtrl.getContainer();
fooCtrlDiv
.querySelector('.leaflet-control-layers-list')
.insertBefore(
fooLegend.getContainer(),
layerControlDiv.querySelector('.leaflet-control-layers-list').firstChild
);

Related

Changing cursor style on pointermove for specified layer

quick (and I believe for some of you an easy) question regarding cursor styling while hovering above geojson layer/s.
So, I have one clip layer that I'm using to create a mask around wms layers, and another one that represents some administrative areas.
As you can see in picture below
What I would like is to change style of cursor when I'm hovering above administrative areas but it seems that I'm missing something.
I'm trying to isolate to layer only administrative borders layer using this block of code:
map.on('pointermove', function(e) {
if (e.dragging) return;
var pixel = e.map.getEventPixel(e.originalEvent);
var hit = e.map.forEachFeatureAtPixel(pixel, function(feature, layer) {
return vectorJLS.get('layer_name') === 'jls';
});
e.map.getTargetElement().style.cursor = hit ? 'pointer' : '';
});
UPDATE
While JGH tweak code a bit it still doesn't work. I've detected that problem lies in layer that I'm using for mask clipping, when removed, code that JGH provided, works.
Here is code that I'm using for mask clipping
var clipLayer = new ol.layer.Image({
source: new ol.source.ImageVector({
source: new ol.source.Vector({
url: 'geojson/clip_wgs.geojson',
format: new ol.format.GeoJSON()
}),
style: new ol.style.Style({
fill: new ol.style.Fill({
color: 'black'
})
})
})
});
clipLayer.on('postcompose', function(e) {
e.context.globalCompositeOperation = 'source-over';
});
clipLayer.on('precompose', function(e) {
e.context.globalCompositeOperation = 'destination-in';
});
clipLayer.setMap(map);
Is it possible to somehow ignore clip layer when changing cursor style or should I take another approach?
UPDATE - 2
I've tweaked code a bit, but still without any success while clipedLayer is on.
map.on('pointermove', function(e) {
if (e.dragging) return;
var pixel = e.map.getEventPixel(e.originalEvent);
// initialize the hit variable to false (not found)
var hit = map.hasFeatureAtPixel(e.pixel, {
layerFilter: function(layer) {
return vectorJLS.get('layer_name') === 'jls';
}
});
console.log(hit)
});
Interesting problem if I might add
Finally, with help from fellow JGH I've found appropriate solution for my problem.
Searching release pages and google machine I've stumbled upon some interesting information regarding layer filters and its usage in method hasFeatureAtPixel. This block of code is valid for versions below 3.20.1 but more about that on OpenLayers Git
map.on('pointermove', function(e) {
if (e.dragging) return;
var pixel = e.map.getEventPixel(e.originalEvent);
var hit = map.hasFeatureAtPixel(e.pixel, {
layerFilter: function(layer) {
return layer.get('layer_name') === 'jls';
}
});
e.map.getTargetElement().style.cursor = hit ? 'pointer' : '';
});
For newer versions you should use layer filter like this (I'm using 4.6.5)
map.hasFeatureAtPixel(pixel, {
layerFilter: layerFilterFn.bind(layerFilterThis)
});
Or for my particular problem like this
map.on('pointermove', function(e) {
if (e.dragging) return;
var pixel = e.map.getEventPixel(e.originalEvent);
var hit = map.hasFeatureAtPixel(e.pixel, {
layerFilter: function(layer) {
return layer.get('layer_name') === 'jls';
}
});
e.map.getTargetElement().style.cursor = hit ? 'pointer' : '';
});
Hope it helps :)
In your function, you are basically looping through all the layers at the mouse location. In that loop, if the layer has the proper name you set the pointer, else if it has a different name, you remove the pointer (or set it to something else).
As it is, it is dependent on the layer order:
ex: layer 1 = target -> set custom pointer. Layer 2 = other layer -> remove pointer. ==> final pointer: removed
ex: Layer 1 = other layer -> remove pointer. Layer 2 = target -> set custom pointer. ==> final pointer: custom pointer
The looping occurs when you set the hit variable, i.e. it corresponds to the last layer only as you are overriding the value for each layer.
map.on('pointermove', function(e) {
if (e.dragging) return;
var pixel = e.map.getEventPixel(e.originalEvent);
// initialize the hit variable to false (not found)
var hit = false;
e.map.forEachFeatureAtPixel(pixel, function(feature, layer) {
if ( vectorJLS.get('layer_name') === 'jls') {
//IF we have found the layer, flag it (but don't return anything!)
hit = true;
}
});
e.map.getTargetElement().style.cursor = hit ? 'pointer' : '';
});

Generating onclick text at the bottom of my SVG, modifying 'selected' colour

With the aid of a tutorial, I've built a map of five regions of England in SVG. I've used Raphael to work with it a little. Most of it seems to be turning out alright so far:
http://codepen.io/msummers40/pen/EjExeO
I'm trying to add two more features and am just not sure how to do it. Are you able to help, please?
I'd like to: Set up the effect of the regions turning red upon clicking so that only the most recently clicked region is coloured. Can you help explain what I'm supposed to do to make this shift from region to region? At the moment, a region gets clicked and stays highlighted.
I'd like to figure out how to add more text to the bottom of the canvas. It may mean adding more information to my JSON but I'm hoping that I can add the text - about two paragraphs with a hyperlink - as a string.
Can you please let me know if you have thoughts about ways that I can do the two things outlined above?
Thank you,
Matt
The full code is on Codepen. What I've added below is a representative sample of the code.
var regions = [
{'title':"northeast_england", 'path' : "M219.02,6.876l-0.079,0.05l-0.482,0.371L218.23,7.47l-0.858,0.346h-0.008l-0.307,0.26l-0.779,0.666 l-0.104,0.278l-0.005,0.019l0.056,0.481l0.116,0.846l0.048,0.395l-0.344,1.05l-0.052-0.007v0.007l-0.635-0.081l-0.375,0.167 l-0.148,0.061v0.006l-0.1,0.328l0.178,0.338l-0.104,0.353h-0.006l-0.32,0.179l-0.056,0.031l-0.161,0.729h-0.006v0.012l-0.271,0.117 l-0.08,0.031l-0.031-0.019l-0.043,0.019l-0.327-0.167l-0.147-0.079l-0.117-0.007h-0.021l-0.216-0.006l-0.419,0.252l-0.009,0.007 l-0.004,0.302v0.605l-0.117,0.292l-0.037,0.11h-0.006v0.006h-0.025l-0.37,0.056l-0.536,0.079l-0.562,0.372l0.017,0.165l0.033,0.187 l0.481,0.788l0.023,0.038l0.008,0.013l-0.988,0.425l-0.594,0.637l-0.011,0.03l-0.187,0.637l-0.068,0.062l-0.801,0.747l-0.409,0.617 l0.062,0.414l0.068,0.414l-0.012,0.012l-0.203,0.228h-0.008l-0.123,0.05l-0.006,0.005l-0.377,0.136l-0.073,0.074l-0.13,0.143 l-0.401,0.426l-0.081,0.08l-0.055,0.055l-0.116,0.136l-0.05,0.364l0.646,0.191l0.025,0.119l0.05,0.153l-0.265,0.148l-0.26,0.155 l-0.155-0.006l-0.005,0.006l-0.309-0.006l-0.648-0.365l-0.624,0.142l-0.363,0.087l-......LOTS MORE COORDINATES...."},
THERE ARE SEVERAL OTHER SVG REGIONS/SHAPES IN THE CODEPEN LINK
var MAP_WIDTH = 600;
var MAP_HEIGHT = 600;
var mapContainer = document.getElementById("map");
var map = new Raphael(mapContainer, MAP_WIDTH, MAP_HEIGHT);
var group = map.set();
var style = {
fill: "#ddd",
stroke: "#aaa",
"stroke-width": 1,
"stroke-linejoin": "round",
cursor: "pointer"
};
regions.forEach(function(region){
group.push(
map.path(region.path).attr('title', region.title)
);
});
group.attr(style);
group.click(function(){
var slug = this.attr('title');
var title;
var fill = this.attr('fill') == 'red' ? '#1f1f1f' : 'red';
// format the title
title = slug.split('-')
.map(function(subString){
return subString[0].toUpperCase() + subString.substr(1);
})
.join(' ')
.trim();
// add some color
this.attr('fill', fill);
// do something useful
document.getElementById('title').textContent = title;
});
For the highlight thing, what I would do is have your click function as follows (pseudocode)::
on-region-clicked {
remove class "highlight" from all regions
add class "highlight" to clicked region
}
Where class "highlight" is:
.highlight {
fill: red;
}
I'll leave the actual Raphael code up to you.

Meteor 1.1 - Callback when specific element renders on the dom

I have some DOM elements that I don't directly have access to, as they are rendered from an API call. What I would like to do is add a class to this element once it's rendered on the DOM.
Using Template.rendered does not work, as the template renders properly before these DOM elements appear from the API.
My current solution is a Meteor.setTimeout—which might be the definition of a hack—and it only works about 90% of the time.
What is the best way to trigger a function when a particular DOM element is rendered?
Here is some of the relevant code from the API call:
Template.map.rendered = function() {
return this.autorun(function() {
var drawControl, drawnItems, mmap;
if (Mapbox.loaded()) {
L.mapbox.accessToken = '<API KEY>';
L.mapbox.config.FORCE_HTTPS = true;
mmap = L.mapbox.map('map', '<TOKEN>');
L.control.scale().addTo(mmap);
var featureGroup = L.featureGroup().addTo(mmap);
drawControl = new L.Control.Draw({
draw: {
polygon: false,
polyline: false,
rectangle: true,
circle: false,
marker: false
}
});
mmap.addControl(drawControl);
mmap.addControl(L.mapbox.geocoderControl('mapbox.places', {
autocomplete: true
}));
function showPolygonAreaEdited(e) {
e.layers.eachLayer(function(layer) {
showPolygonArea({ layer: layer });
});
}
function showPolygonArea(e) {
coords = {
lat1: normalizeLon(e.layer.toGeoJSON().geometry.coordinates[0][1][0]),
lon1: normalizeLat(e.layer.toGeoJSON().geometry.coordinates[0][1][1]),
lat2: normalizeLon(e.layer.toGeoJSON().geometry.coordinates[0][3][0]),
lon2: normalizeLat(e.layer.toGeoJSON().geometry.coordinates[0][3][1])
}
featureGroup.clearLayers();
featureGroup.addLayer(e.layer);
e.layer.openPopup();
}
mmap.on('draw:created', showPolygonArea);
mmap.on('draw:edited', showPolygonAreaEdited);
}
});
};
I've removed a lot of extraneous code, so this might not compile properly... But it has all the relevant bits.
The selector I initially tried to use was this:
Template.map.rendered = function() {
$('.leaflet-draw-section').attr('data-intro', 'Hello step one!')
...
...
But it didn't work, since the API elements hadn't rendered yet.
Looking at the map box api, it appears that L.mapbox.map does the rendering:
<script>
// Provide your access token
L.mapbox.accessToken = 'id1' //
// Create a map in the div #map
L.mapbox.map('map', 'id2');
// add your class to #map right here!!
</script>
If that doesnt work, them maybe L.mapbox.map is doing something asynchronous. They don't give you a callback, so a window.setTimout(func, 0) may be necessary
Make sure your code looks like this
Template.templatename.onRendered(function(){
//yourcode
});
The solution comes from the Mapbox API. There is a callback on "ready":
var layer = L.mapbox.tileLayer('mapbox.streets');
layer.on('ready', function() {
// the layer has been fully loaded now, and you can
// call .getTileJSON and investigate its properties
});

Putting HTML code on JointJS link

I have worked with JointJS now for a while, managing to create elements with HTML in them.
However, I am stuck with another problem, is it possible to place HTML code, like
href, img etc, on a JointJS link and how do I do this?
For example, if I have this link, how do I modify it to contain HTML:
var link = new joint.dia.Link({
source: { id: sourceId },
target: { id: targetId },
attrs: {
'.connection': { 'stroke-width': 3, stroke: '#000000' }
}
});
Thank you!
JointJS doesn't have a direct support for HTML in links. However, it is possible to do with a little bit of JointJS trickery:
// Update position of our HTML whenever source/target or vertices of our link change:
link.on('change:source change:target change:vertices', function() { updateHTMLPosition(link, $html) });
// Update position of our HTML whenever a position of an element in the graph changes:
graph.on('change:position', function() { updateHTMLPosition(link, $html) });
var $html = $('<ul><li>one</li><li>two</li></ul>');
$html.css({ position: 'absolute' }).appendTo(paper.el);
// Function for updating position of our HTML list.
function updateHTMLPosition(link, $html) {
var linkView = paper.findViewByModel(link);
var connectionEl = linkView.$('.connection')[0];
var connectionLength = connectionEl.getTotalLength();
// Position our HTML to the middle of the link.
var position = connectionEl.getPointAtLength(connectionLength/2);
$html.css({ left: position.x, top: position.y });
}
Bit of an old question, but thought I'd add some more ideas. You can add extra svg markup to the label in a link if you like by extending the link object and then setting attributes where needed. For example:
joint.shapes.custom.Link = joint.dia.Link.extend({
labelMarkup: '<g class="label"><rect /><text /></g>'
});
This code overrides the markup for the label, so you can add extra elements in there. You can also update attributes on these elements by:
link.attr('text/text', "new text");
However hyperlinks won't work (at least I haven't got them working in Chrome) and I believe this is because Jointjs listens for all events in the model. So what you should do is use inbuilt events in Jointjs to listen for connection clicks:
paper.on('cell:pointerclick', function(cellView, evt, x, y){
console.log(cellView);
});

Adding/removing L.control from leaflet.js map

I have a map that changes tiles based on four radio buttons. I need the popup window that appears when you roll over a tile to change as the different map layers change. I've gotten it to appear but when I switch layers the map just adds another popup window. I tried using control.removeFrom(map) but it doesn't seem to work. I think my logic may be screwed up somewhere. Here is one of the if statements:
if (two == true && black == true) {
function blkNineStyle(feature) {
return {
fillColor: getColor(feature.properties.pctBlack9000),
weight: 2,
opacity: 1,
color: '#666',
dashArray: '2',
fillOpacity: 0.9
};
}
//Tried to us this to take off the control.
info.removeFrom(map);
map.removeLayer(geojson);
geojson = L.geoJson(tracts, {style: blkNineStyle, onEachFeature: onEachFeature}).addTo(map);
var info = L.control();
info.onAdd = function (map) {
this._div = L.DomUtil.create('div', 'info');
this.update();
return this._div;
};
info.update = function (props) {
this._div.innerHTML = '<h4>Percent White population change</h4>' + (props ? '<b>' + props.name + '</b><br />' + props.pctBlack9000 + '%' : 'Hover over a tract');
};
info.addTo(map);
}
You can see the (broken) map here.
I had this same problem myself and I just solved it.
I had to define an empty variable in the global environment (outside any functions you're using). This isn't a full script or anything, but the general idea I'm describing is below:
var info; // CREATING INFO VARIABLE IN GLOBAL ENVIRONMENT
function makeMap() {
..... geojsons, styles, other stuff ....
// REMOVING PREVIOUS INFO BOX
if (info != undefined) {
info.removeFrom(map)
}
// making current layer's info box
info = L.control();
info.onAdd = function (map) {
this._div = L.DomUtil.create('div', 'info');
this.update();
return this._div;
};
info.update = function (props) {
this._div.innerHTML = '<h4>Data by Zip Code</h4>' + (props ?
'<b>Zip Code: ' + props.id + '</b><br />Value: ' + matchKey(props.id, meanById)
: 'Hover over a zip code');
};
info.addTo(map);
..... other stuff again ......
} // end function
I am very new to both Leaflet and javascript, so I have to say that I'm not exactly sure where to place the info.removeFrom(map) line in the code you have posted at the map link you provided, but you are on the right track with 'info.removeFrom(map)' .
I was able to problem-solve my issue with dynamic legends and info boxes by fiddling around here: http://jsfiddle.net/opensas/TnX96/
I believe you want to remove the control similarly how you added it.
In this case leaflet provides direct remove() method similar to addTo(map) method.
Example-
Whenever you want to remove the legend control use following code-
Create Control-
var legendControl = L.control({position: 'bottomleft'});
legendControl.addTo(mymap);
Remove Control-
legendControl.remove();
For more details refer/click here...
Despite the fact that this question was asked a year ago, I recently had to come up with a solution to a similar problem myself so feel as if I should share in case anybody else ends up here like I did.
The L.control() object in Leaflet isn't technically a layer, and this is why trying to add and remove it some times doesn't work in the same way as for layers.
http://leafletjs.com/reference.html#icontrol
As the L.control constructor requires you only to "create all the neccessary DOM elements for the control", the HTML content of the div itself can be updated and deleted as and when required. Thus, to make a control feature appear and disappear from the map, and instead of adding and removing the L.control object, just adjust the HTML contents of the div contained by it. An empty div would result in no control feature being shown by the map.
Thus the above snippet would become:
//construct control, initialize div
info = L.control();
info.onAdd = function (map) {
this._div = L.DomUtil.create('div', 'info');
this.update();
return this._div;
};
if (two == true && black == true) {
function blkNineStyle(feature) {
return {
fillColor: getColor(feature.properties.pctBlack9000),
weight: 2,
opacity: 1,
color: '#666',
dashArray: '2',
fillOpacity: 0.9
};
}
//set div content to empty string; makes control disappear from map
info.getContainer().innerHTML='';
map.removeLayer(geojson);
geojson = L.geoJson(tracts, {style: blkNineStyle, onEachFeature: onEachFeature}).addTo(map);
//update content of control to make the control reappear
info.update = function (props) {
this._div.innerHTML = '<h4>Percent White population change</h4>' + (props ? '<b>' + props.name + '</b><br />' + props.pctBlack9000 + '%' : 'Hover over a tract');
};
}
//other cases...
if (two == false && black == true) {
//delete and update control where necessary
info.getContainer().innerHTML='';

Categories