Creating a Leaflet Map

Part 4 - Legend

Symbology for the Legend

In this sample the style for the data to be symbolized is done in a function outside the feature layer option. The reason for this is to allow not just the feature layer to utilize the colors and break points but to let write a function that creates a legend and uses the same functions for the colors and break points.

A total of 3 functions will be written to create the color ramp, style, and legend. The first 2 functions will be declared at the top of the JavaScript file so they can be called later on.

The first is the function to determine the color based on a value sent through. It looks like this:

function color(d){
  return d >=5 ? "#b30000":
          d >= 4 ? "#e34a33":
          d >= 3 ? "#fc8d59":
          d >= 2 ? "#fdcc8a":
                    "#fef0d9";
};

It is a pretty simple function if you are familiar with JavaScript. I’m still just learning JavaScript so it took me a bit to understand what is going on. So I’m going to explain it line by line.

function colore(d){ This first line is the easiest to understand as it is declaring the function as a named function called “color” that takes one parameter named d

return d >=5 ? "#b30000":This line is saying the function will return something but what? I’m more familiar with Python and basic JavaScript so I think of returns as something that come within if/elif/else statements or at the end of a function after something has been done. But all this function has is a single return followed by a comparison and a ?. What does it all mean? What it really is, is simplifying the if/else if statement block. It is called the Conditional Ternary Operator and this answer on Stack Overflow is pretty helpful. This is condensing the lines of codes in an if/else if statement down. What it basically says is this:

if (d >= 5){
return  ‘#b30000’
}else if (d>=4){
	return ‘#e34a33’
} . . .
}else{
	return ‘#fef0d9’
};

So the color function is taking a value d and comparing it to my break points and returning a color based on that break point value. Very similar to what my function in the style property was doing. Except since it isn’t in the property of the L.esri.featureLayer method I can access it easily throughout my code. And I will access it next to assign that color to a zipcode in the feature layer.

The next function is going to look similar to the function I wrote in the style property but be a little bit condensed and will just be returning the object as the value to the style property of the L.esri.featureLayer method.

function style(feature) {
  return{
    weight: 1,
    opacity: 0.8,
    color: color(feature.properties.PercentagePositiveTests)
  };
};

This creates the named function style that takes a parameter called feature. Very similar to the unnamed function we had as the value for the style property in that it is returning an object with the line weight, opacity, and color. It is also using the properties of the feature layer in feature.properties.PercetagePositiveTests to determine the color. The big difference is the if/else if statement that was used to determine the color is not here. That is because the feature.properties.PercentagePostiveTests is the parameter being fed to the color function we defined above. So the color returned will be determined by the value in the PercentagePositiveTests field for each zipcode from the color function.

This is great. I have 2 functions that will determine the color of each polygon based on its attribute. But I have to apply it to that feature layer. How do I do that? It is pretty simple actually. I just call the style(feature) function as the value to the style property in the L.esri.featureLayer method. So it would look like this

var alamedaCovid = L.esri.featureLayer({
  url: 'https://services5.arcgis.com/ROBnTHSNjoZ2Wm1P/arcgis/rest/services/COVID_19_Statistics/FeatureServer/1/',
  simplifyFactor: 1,
  style: style
});

The style for each polygon is going to run the style function and return the color based on the attribute in the PercentagePositiveTests column.

Legend

Like before this is great but I still don’t have a legend. But now that I have a function with my breakpoints and the color associated with them I can pretty easily create one. This sample is again very similar to the code in the Choropleth example. But I change the direction of my ramp as I want my bigger value and darker color at the top.

var legend = L.control({position: 'bottomleft'});

legend.onAdd = function(map) {
  var div = L.DomUtil.create('div', 'info legend'),
  breaks = [0,2,3,4,5],
  labels=[],
  from, to;

  for (var i = breaks.length; i > 0 ; i--) {
    from = breaks[i-1];
    to = breaks[i];
    labels.push(
      '<i style= "background:' + color(from) + '"></i>'+
      from + (to ? '–' + to : '+'));
  }
  div.innerHTML = "<h4>Percentage Positive Test</h4>"
  div.innerHTML += labels.join('<br>');
  return div;
};

So what is all of that code doing? First I am declaring a variable called legend and assigning to it a Leaflet method called L.control() and defining the property for position to be bottomleft, which means the legend will display in the bottom left corner. It could be put at any of the 4 map corners. For more information on L.control() see the Leaflet documentation for Control.

Next I am declaring a function to the onAdd method for my control. Since this function is going to be in the onAddmethod it should do something to the DOM, as is suggested by the documetdocumentation nion for the onAdd method. This is exactly what I do next. I create a variable called div that calls the L.DomUtil.create() function. This leaflet function will create a new HTML element. This can take up to 3 parameters. The first 2 are required and are the tag name and the class name. The third is optional and is a container name you could append this to. For this legend I am just going to create a div with a class name info legend.

Now that I have created a div for my legend I need to fill it with information. I start by continuing to create more variables. I create a list of the breaks, an empty list for the labels, and an empty from and to variable. The empty variables will be given values in the for loop that will be used to write the legend to the div.

The for loop will loop through the breaks list starting at the end. It does this by creating the starting value of i as the length of the breaks list, and goes until i > 0, subtracting 1 from i each time. This is different from the Choropleth example as it starts at the bottom of the breaks list and moves up, increasing i each time by 1 until it reaches the length of the list. By doing it this way my first value written will be the largest and my color ramp will have that on top.

Within the for loop I get a value for from and to. The from value is the i-1 value in the breaks list and the to value is i. What does that mean for my first iteration of the for loop? My first i value is breaks.length, which is going to be 5. The from variable is looking for the i-1 location in the breaks list. That would be the 4 value. Since list are 0 based that would then be the last value of 5. The to value is going to be the 5 value in the breaks list, which is undefined because it only goes to 4. That will be fine though as the code further down has a conditional ternary operator to handle this.

The last part of the for loop is creating the labels. It uses the .push() method to add data to the labels list. What it is adding is HMTL that will be then added to the new div I created. The parameters that .push() is taking is first going to create a box with the color. That is the ‘<i style = ‘background: ‘ + color(from) + ‘“></i>’. This does mean I will need to create some css for i style, and I will show that after this. But once I have done that, I am just setting the background to the color returned when I put the from value through the color() function. My first from value is the largest, 5, so it will return that color. Then it adds the value from the from variable, 5, and does conditional ternary operator. These are again not something I am familiar with so what is this (to ? '–' + to : '+') saying? It is evaluating the to variable. It is saying that if to exists put in a - (written as ‘%ndash’ for HTML) + to. But if not put in a +. In this first case to is undefined so it will just put in 5+. As the for loop decreases i, I will get the other values of the breaks list separated by a dash, so next would be 4-5.

The last parts to this function is the adding everything to the inner HTML of the div. First I add a label. Then I add all the data in the labels list. Since it is a list I need to split it out and I use the .join() function to turn the list into a string, and join them with a line break </br>. That will create the legend in the div being created. And finally the function needs to return that div.

Like with layers though it has to be added to the map. Which is just a single line of code legend.addTo (map);

Now about that css to make the legend color boxes. The actual values here are going to differ depending on your map but this is what I used

.legend i {
  width: 18px;
  height: 18px;
  float: left;
  margin-right: 8px;
  opacity: 0.7;
}
That is going to create boxes that are 18 x 18 pixels, floated to the left, with a margin on the right that will push the text out 8px, and an opacity of 0.7.

I also needed to add some css for just the legend div I created in the above function.

.info{
  padding: 6px 8px;
  font-size: 12px;
  font-family: "Verdana", "sans-serif";
  background: white;
  background: rgba(255,255,255,0.8);
}

.legend {
  line-height: 18px;
  color: #555;
}
That is going to create, font, padding, font size, and background for the legend box. Like with the color boxes these can be updated with what works on the map.

Next Steps

Now I have a legend on my map that shows what the different colors mean. But you can't access the values within each polygon. To do that I need to add popups.