The Symbol objects discussed earlier can be used in conjunction with Graphic objects to depict small numbers of geometries. However, they are more commonly used together with Renderer objects, which are used to visualize Layers. In this part of the lesson, we’ll look at the three Renderer classes: SimpleRenderer, ClassBreaksRenderer and UniqueValuesRenderer.
The SimpleRenderer class is used when you want to depict all of a layer’s features using the same symbol. This class was used several times in the Jen & Barry’s examples in the previous section. As shown in the examples, the important property to set is the symbol property. Optionally, you can also set the label property, which specifies what will appear in the Legend widget, if you decide to display it. (More on widgets and GUI development later in the course.)
The ClassBreaksRenderer class is used to symbolize features in a layer based on the values in a numeric field. A common example is using a color ramp to symbolize areal units by their population, with areas of low population shown in a light hue and areas of high population in a dark hue.
The first step in implementing this sort of visualization is specifying the field you want to base your rendering on (done through the field property). Optionally, you can normalize the values in this field by a) setting the normalizationType to “percent-of-total” and the normalizationTotal to the sum of the values, or b) setting the normalizationType to “field” and the normalizationField to some other field. For example, it’s common to normalize population by area to produce a map of population density.
Once you’ve specified what it is you’ll be mapping, you need to define the classes themselves. To define a class, you set its minValue, maxValue, symbol and label properties. The ClassBreaksRenderer actually provides two ways to specify classes. The first is by setting the classBreaksInfos property to an array of objects. The second is by using the addClassBreakInfo() method to add classes to the renderer one at a time.
The example above shows the Jen & Barry's counties symbolized based on population density. The code should be fairly straightforward to follow. What might be worth expanding on is how the class breaks and colors were decided. My suggestion is to use ArcGIS Desktop or ArcGIS Online to map the data using the desired classification method (e.g., Natural Breaks, Quintiles, etc.) and copy the min and max values for each class into your code. You can also select a color ramp in either platform, open the color picker for each class, and copy the hexadecimal color value into your code. Alternatively, you could use ColorBrewer [4] to obtain the color values.
One thing I hope you noticed about the code from this example is that the section defining the class breaks is repetitive. This sort of repetitiveness should be avoided when possible. For example, what if you decided to change the app from 3D to 2D? After switching from a SceneView to a MapView, you would need to change from a PolygonSymbol3D to a SimpleFillSymbol five times (once for each class in the renderer).
The pen above reduces this redundancy and makes the app easier to modify. (It renders the same map as the first pen, so I have the Result toggled off by default.) Note that the code that adds a class to the renderer is moved to a function called addClass. This function requires five pieces of information to do its job: the minimum and maximum bounds for the new class, the color to display it with, its label (which appears if the layer is displayed in the Legend widget) and a reference to the Renderer. These values/objects are then used to set the appropriate properties in the addClassBreakInfo() method call.
With the function defined, it can be called upon five times to create each of the desired classes. This version of the code is an improvement over the previous one because a) changing the kind of symbol used requires just one change instead of five, b) the values that define the classes are clustered together, making it easier to edit them, and c) the length of the script is reduced.
Note that we’ll talk about the Legend widget and other GUI-related topics later in the course.
The setup of a UniqueValueRenderer is quite similar to that of a ClassBreaksRenderer, with the differences owing to the fact that it involves matching values in a string field rather than a range of values in a numeric field. A common example is symbolizing land parcels based on their land use (residential, commercial, industrial, etc.) To set up an object of this class, you specify a field to base the renderer on (actually, up to 3 fields), then define the renderer’s classes using either the uniqueValueInfos property or the addUniqueValueInfo() method.
The pen above shows the Jen & Barry's cities symbolized by the presence of a university in the city. The renderer is based on the UNIVERSITY field, which holds a value of 1 (has a university) or 0 (doesn’t have a university). Like the previous example, a function is used to handle the creation of the renderer classes. In this function, an if-then construct is used to create a different symbol and legend label depending on whether the UNIVERSITY value is 1 or 0. Note that two IconSymbol3DLayers (a cross and a circle) are combined to form the symbol for cities with universities.
In addition to the Renderer functionality we’ve seen already, Esri also makes it possible to produce thematic depictions of data through what they call visual variables. For example, you might create a map of country populations using a light-to-dark color ramp. If you’re thinking you already saw how to do this with the ClassBreaksRenderer, that’s true. The difference with visual variables is that you’re not creating a limited number of classes to handle all of the values in the field you’re mapping. Instead, you’re creating a continuous ramp of color based on the field’s minimum and maximum values. A feature whose value is midway between the min and max values will be drawn in a color midway between the color you associated with the min value and the color you associated with the max value.
Color is one commonly used visual variable. Size is another. Opacity and rotation are the two others, though those options are less commonly used.
Esri refers to the use of visual variables as data-driven mapping, since the features are not being partitioned into classes whose bounds can be rather subjective. The symbolization is instead driven by the data.
A data-driven visualization can be created using any of the three Renderers we’ve discussed. Each Renderer class has a visualVariables property that can be set to an array of stop objects. At minimum, each object in the array should have a type (set to “color”, “size”, “opacity” or “rotation”) and a field (set to the name of the desired field). Optionally, you can also set a legendOptions property to specify if and how the visualization should appear in the legend.
The critical step in setting up a visual variable is defining the ramp. For both the color and size types, this can be done by creating an array of at least two stops. The code might look like this:
// Stops for a color visual variable stops: [ { value: 0, color: "#ece7f2", // light blue label: "< 0" }, { value: 100, color: "#2b8cbe", // dark blue label: "> 100" } ] |
// Stops for a size visual variable stops: [ { value: 0, size: 8 label: "< 0" }, { value: 100, size: 24, label: "> 100" } ] |
The pen above shows the use of a color visual variable to depict average household income in PA census tracts. Note that a diverging red-to-green color ramp (found at ColorBrewer) was employed, with red being associated with the minimum data value, yellow/beige associated with the mean, and green with the maximum. These data values were obtained by mapping the field in ArcGIS Online.
As mentioned, you are not limited to just one visual variable per renderer. Above is a pen that builds on the previous one by adding a second visual variable to the renderer and adding the layer to a SceneView rather than a MapView. This second visual variable extrudes (adds height to) the census tracts proportional to their population. (Note that this is not the best example since by design there isn’t a great deal of variation in census tract populations.)
Speaking of polygon extrusion, one of its common uses is in extruding building footprint polygons by the building height to produce a more realistic depiction of a cityscape. Above is an example using buildings in Philadelphia.
Links
[1] https://codepen.io/jimdetwiler/pen/ppXowN
[2] https://codepen.io/jimdetwiler
[3] https://codepen.io
[4] http://colorbrewer2.org
[5] https://codepen.io/jimdetwiler/pen/xpoxXj
[6] https://codepen.io/jimdetwiler/pen/OzeJvP
[7] https://codepen.io/jimdetwiler/pen/Rxzwym
[8] https://codepen.io/jimdetwiler/pen/mpZdKN/
[9] https://codepen.io/jimdetwiler/pen/qpzBMJ/