I am trying to modify one of the samples that combines the Legend and LayerList widgets to also accommodate sublayers. It seems that if leave the default layerlist declaration (as the sample provides), it gives me all of the symbology for the sublayers at the top, and nothing inside of each sublayer.
Originally, I had thought that I would just assign the "legend" to the panel of the children the parent layer, but that didn't seem to work. Any thoughts or references where this has been done before?
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no">
<title>Add a Legend to LayerList - 4.9</title>
<link rel="stylesheet" href="https://js.arcgis.com/4.9/esri/themes/light/main.css">
<style>
html,
body,
#viewDiv {
padding: 0;
margin: 0;
height: 100%;
width: 100%;
overflow: hidden;
}
</style>
<script src="https://js.arcgis.com/4.9/"></script>
<script>
require([
"esri/Map",
"esri/views/MapView",
"esri/layers/MapImageLayer",
"esri/widgets/LayerList"
], function (
Map, MapView, MapImageLayer, LayerList
) {
const citiesStatesHighways = new MapImageLayer({
url: "https://sampleserver6.arcgis.com/arcgis/rest/services/USA/MapServer",
sublayers: [{
id: 2,
title: "States"
},
{
id: 1,
title: "Highways"
},
{
id: 0,
title: "Cities"
}
]
});
const map = new Map({
basemap: "dark-gray",
layers: [citiesStatesHighways]
});
// Add the map to a MapView
const view = new MapView({
container: "viewDiv",
map: map
});
// Add a legend instance to the panel of a
// ListItem in a LayerList instance
const layerList = new LayerList({
view: view,
listItemCreatedFunction: function (event) {
const item = event.item;
if (item.layer.type != "group") { // don't show legend twice
item.panel = {
content: "legend",
open: true
};
console.log(item.panel.content);
}
}
});
// Assigning "legend" to children of parent layer - not working
// const layerList = new LayerList({
// view: view,
// listItemCreatedFunction: function (event) {
// const item = event.item;
// console.log(item);
// // target elements without children (the sublayers)
// if (item.children.items.length !== 0) {
// let childrenArr = item.children.items
// for (let i = 0; i < childrenArr.length; i++) {
// childrenArr.panel = {
// content: "legend",
// open: true
// }
// }
// }
// }
// });
view.ui.add(layerList, "top-right");
});
</script>
</head>
<body>
<div id="viewDiv"></div>
</body>
</html>
Solved! Go to Solution.
Hi All,
I did not find any solution for this anywhere and few solutions were leading to too many network calls, so developed a workaround. Sharing the code below might help others:
1. Querying the legend images url and store in an object
I'm not really sure if this was a good solution, but I was able to put something together. Does anybody have any suggestions? In addition, it seems like this 'listItemCreatedFunction' is happening a bunch of times (executing more times than the number of layers'. It doesn't seem to interfere with anything, but I'm wondering if it will slow down the map when expanding to multiple services with many sublayers.
See codepen for live example: Add a Legend to a Layerlist (Sublayers Edition)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no">
<title>Add a Legend to LayerList - 4.9</title>
<link rel="stylesheet" href="https://js.arcgis.com/4.9/esri/themes/light/main.css">
<style>
html,
body,
#viewDiv {
padding: 0;
margin: 0;
height: 100%;
width: 100%;
overflow: hidden;
}
</style>
<script src="https://js.arcgis.com/4.9/"></script>
<script>
require([
"esri/Map",
"esri/request",
"esri/views/MapView",
"esri/layers/MapImageLayer",
"esri/widgets/LayerList",
"esri/widgets/Legend"
], function (
Map, esriRequest, MapView, MapImageLayer, LayerList, Legend
) {
const citiesStatesHighways = new MapImageLayer({
url: "https://sampleserver6.arcgisonline.com/arcgis/rest/services/USA/MapServer",
sublayers: [{
id: 2,
title: "States"
},
{
id: 1,
title: "Highways"
},
{
id: 0,
title: "Cities"
}
]
});
const map = new Map({
basemap: "dark-gray",
layers: [citiesStatesHighways]
});
// Add the map to a MapView
const view = new MapView({
container: "viewDiv",
map: map
});
// Assigning "legend" to children of parent layer
const layerList = new LayerList({
view: view,
listItemCreatedFunction: function (event) {
const item = event.item;
console.log(item);
// target elements without children (the sublayers)
if (item.children.items.length !== 0) {
// make array of the sublayers
let childrenArr = item.children.items
console.log(childrenArr);
for (let i = 0; i < childrenArr.length; i++) {
// make a request to the server to retrieve the layer image url
esriRequest(childrenArr[i].layer.url.slice(0, -1) + "legend", {
query: {
f: 'json'
},
responseType: "json"
}).then(function (response) {
let img = document.createElement("img");
// build unique url for the legend symbol
img.src = childrenArr[i].layer.url + "/images/" + response.data
.layers[i].legend[0].url;
// assign image to the sublayers in layerlist
childrenArr[i].panel = {
content: img,
open: true
}
});
}
}
}
});
view.ui.add(layerList, "top-right");
});
</script>
</head>
<body>
<div id="viewDiv"></div>
</body>
</html>
I have the exact same requirement, and I actually think it's really strange that the arcgis js dev team haven't provided a widget or example that properly combines legend with layerlist, ALSO for sublayers. This is something that I think a lot of people would appreciate, as it greatly improves the usability of any map that uses dynamic services with sublayers.
Hello,
I noticed that it is making several calls as well. I just made one call to the legend. Below is my solution. Its a lot, but it looks and acts the way I want. I would like to remove the opacity setting for non-polygon layers and add a slider, but that is for a latter time. Thanks for sharing your code it got me started.
matt price awesome, great work! I did have to do one update.. toggling Group on/off did not work (for me). With a dynamic map service with some Groups and subGroups loaded as a mapImageLayer.
I first noticed interestingly that Group layer items (parents with children or subchildren sublayers) were not showing up with checkboxes, at all. Just the triangle expand/minimize arrows. So there was no way to turn on layers in a group if the parent Group was off.
So...I removed this chunk of CSS, below. Ok, so the Group checkboxes came back and appear as normally. But clicking the parent/group checkboxes doesn't do anything.
https://codepen.io/kevinsagis/pen/RwajJRa
.esri-layer-list__child-toggle + .esri-layer-list__item-label:not([role="radio"]) > .esri-layer-list__item-toggle {
display:none;
}
However, the fix was that I deleted this part of a line:
item.children.items.length === 0 &&
So instead of if (item.children.items.length === 0 && item.title != "Legend") {
it's now just if (item.title != "Legend") {
Now it works. Hope this helped everyone.
Hi all,
In the latest version of the API, 4.12, the function "listItemCreatedFunction" is still loading multiple times and there is no solution to show legend for sublayers (other than the custom ones above, that they work).
Are there any updates about that?
Thanks in advance!
This still seems to be an issue in 4.15, is there any intention to address this issue?
Still present 4.16 - 4.17. mapImage services with grouped sublayers layers are best for various scenarios. Matt's approach above works with 4.17 with a small update as noted (and in my Pen example https://codepen.io/kevinsagis/full/RwajJRa )
It would be great to see this integrated into the widget itself, with Legend properties such as "Legend: true" to simply enable legend for all layers and all sublayers. Expanded should be an option for all; or per layer by layer. And other options like in WebApp Builder, like hiding certain layers. And even allow per-sublayer exclusion of the legend as override.
Hi All,
I did not find any solution for this anywhere and few solutions were leading to too many network calls, so developed a workaround. Sharing the code below might help others:
1. Querying the legend images url and store in an object