In the previous lesson, we looked at several of the different types of layers in Esri's API that can be added to a map. One of the most important tasks in building a successful geospatial web app is to render the map layers so that they effectively convey the desired information. That will be the focus of this lesson.
Lesson 6 is one week in length. (See the Calendar in Canvas for specific due dates.) To finish this lesson, you must complete the activities listed below. You may find it useful to print this page out first so that you can follow along with the directions.
Step | Activity | Access/Directions |
---|---|---|
1 | Work through Lesson 6. | Lesson 6 |
2 | Complete the project on the last page of the lesson.
|
Follow the directions throughout the lesson and on the last page. |
3 | Take Quiz 6 after you read the online content. |
Click on "Lesson 6 Quiz" to begin the quiz. |
Throughout the course, we’ve talked about using GUI-based tools whenever possible to avoid or simplify coding. In Lesson 1, we used app templates and Experience Builder to compose apps without writing any code. In Lesson 5, we were developing apps using code, but saw that we could greatly simplify the code needed to produce a map by doing the map development in ArcGIS Online and bringing the map into the app via its item ID.
In this lesson on layer visualization, we again have options that greatly reduce the coding burden. If you have ArcGIS Enterprise, you can use desktop tools to visualize your layer, publish that layer as a service through your Enterprise instance, then add your layer to an app as we saw in the last lesson.
If you don’t have ArcGIS Server, you can still easily handle the visualization of layers without coding. The workflow is to upload your shapefile or file geodatabase as a hosted feature layer to ArcGIS Online, symbolize the layer using the same GUI tools we saw in Lesson 1, then add the layer to the map in your code using its item ID. Let’s walk through that workflow.
const featureLayer = new FeatureLayer({ portalItem: { id: “59d4705e7d2e48beb94faaa6b78307e7” //replace w/ your own layer ID } }); map.add(featureLayer);
If you're wondering where that layer id comes from have a look at your browser window and you'll see something like :
http://pennstategis.maps.arcgis.com/home/webmap/viewer.html?useExisting=1&layers=59d4705e7d2e48beb94faaa6b78307e7
That last part after layers= is the layer id.
A quick and easy way to test this is to open the FeatureLayer sample referenced in Lesson 5, change the way the FeatureLayer is constructed, refresh the map, then zoom out (since the map is zoomed in on North Carolina and the Jen & Barry’s data is in Pennsylvania).
Note that it is also possible to do your layer visualization and publishing to AGO from ArcMap or ArcGIS Pro. The steps involved are described in detail in the AGO help [1] if you’re interested.
Despite the handy methods for producing easy-to-deploy visualizations without coding discussed above, you may find it necessary to code visualizations using classes built into the JS API. As we’ll see, it is possible to develop layer renderings in both 2D Maps and 3D Scenes. The basic workflow entails manipulating Symbol, Renderer, and Layer objects. The rest of this lesson will focus on these API classes.
In Lesson 4, we talked briefly about the API’s 2D Symbol classes in the context of adding graphics to a map. To refresh your memory, MarkerSymbols are used for Points, LineSymbols for Polylines and FillSymbols for Polygons. We saw that these three classes are actually abstract, and that the Symbol objects you can create are of the following classes:
Point: SimpleMarkerSymbol or PictureMarkerSymbol
Line: SimpleLineSymbol
Polygon: SimpleFillSymbol or PictureFillSymbol
The SimpleMarkerSymbol class has three particularly important properties:
color: can be set using a name string, a hexadecimal string, or RGB values;
size: can be set in points or pixels;
style: valid values include “circle”, “cross”, “diamond”, “square” and “x”
Less commonly used is the outline property, which defines how the outer edge of the symbol looks. (It is a thin black line by default.) This property must be set using a SimpleLineSymbol object (discussed below).
As its name implies, the PictureMarkerSymbol class is used to depict point geometries using an image. The important properties are url (set to the address of the image), along with width and height(set in points or pixels).
Like SimpleMarkerSymbol, the SimpleLineSymbol class also has three commonly set properties:
color: set like the color property above
style: default value is “solid”; others include “dash” and “dot”; see SDK [2] for full list
width: like the size property above, can be set in points or pixels
Finally, the SimpleFillSymbol class has three important properties of its own:
color: same as above
outline: set using a SimpleLineSymbol
style: default value is “solid”; “none” is also commonly used; see SDK [3] for full list
Below is an example that depicts the Jen & Barry’s data on a 2D map. The PictureMarkerSymbol class is applied to display the cities using an ice cream cone icon. Note that a SimpleMarkerSymbol (a yellow square) is also created and can be applied instead by commenting out line 40 and uncommenting line 39. Highways are shown as solid red lines using the SimpleLineSymbol class, and counties are shown in a solid gray fill with dashed outlines using the SimpleFillSymbol class.
Note that throughout the rest of the course, we'll be embedding pens from CodePen like the one below to give you a convenient way to interact with sample pages that we've written. The pens will initialize with the JS code shown on the left and the rendered page on the right. You can click on the HTML or CSS buttons to view those pieces of the app, click on the JS button to make the rendered page fill the whole box, or click the Result button to make the code fill the whole box. If you'd like the ability to modify the code a la Esri's sandbox, then you can click on the Edit on CodePen link in the upper right of the box.
If you look at the coding of the PictureMarkerSymbol, you'll see that the url has been set to an AGOL address, which may seem odd. The reason for this is found in the description of the url property:
We'll talk more about CORS later in the course, but for now it's good enough to understand that in order for the custom symbol to show up properly in the CodePen example above, it was necessary to upload the image to AGOL, share it with the public, and copy its URL. If you're instead creating a page that will run on your own web server, you can upload the image to the server and define the symbol's url property using the image's URL. For example:
const coneSym = new PictureMarkerSymbol({ url: "images/cone2.png", //etc.
While the 2D Symbols discussed above are supported in 3D scenes, Esri recommends creating 3D Symbols instead. Symbolizing Point, Polyline and Polygon objects in 3D scenes should be done using PointSymbol3D, LineSymbol3D and PolygonSymbol3D objects, respectively.
Working with these objects is complicated by the fact that Esri programmed them such that they are created by combining one or more shapes into one symbol. In most cases, you’ll probably be satisfied using just one of the available shapes (e.g., a sphere, cylinder, cube, or cone). In a moment, we’ll have a look at a 3D depiction of the Jen & Barry’s data in which the symbols are composed in this way (as a single shape).
Before that though, let’s have a look at this ArcGIS blog post [7], which does the best job I’ve found of demonstrating the creation of symbols from multiple shapes. Unfortunately, the links to the live examples described in the post are broken, but we can get the gist of the idea from the post itself.
The first map screenshot depicts earthquakes in southern California. Each quake point is shown using a symbol composed of three circles: two red-hued ones on the interior and a third hollow one on the exterior. Just below the map screenshot is the code snippet that creates the symbol. Here is what you should note from this snippet:
Now that you understand the concept of a symbol layer, let’s talk about the types of symbol layers you can use to create symbols. For each geometry type (Point, Polyline, Polygon), it is possible to create a flat symbol (one that is based on 2D shapes) or a volumetric symbol (one that is based on 3D shapes). Here’s a table summarizing the symbol layer classes associated with the geometry types:
Geometry type | Flat | Volumetric |
---|---|---|
Point | IconSymbol3DLayer | ObjectSymbol3DLayer |
Polyline | LineSymbol3DLayer | PathSymbol3DLayer |
Polygon | FillSymbol3DLayer | ExtrudeSymbol3DLayer |
The Point-related symbol layer classes (IconSymbol3DLayer and ObjectSymbol3DLayer) each have their shape defined by setting the resource property. This property is typically set using a primitive shape (circle, square, cross, x or kite for IconSymbol3DLayer; sphere, cylinder, cube, cone, inverted-cone, diamond and tetrahedron for ObjectSymbol3DLayer). For example:
{ primitive: “circle” }
It is also possible to set the resource property using an href value. For IconSymbol3DLayer, this would be the URL of an image. For ObjectSymbol3DLayer, this would be the URL to a 3D model (which could be constructed in ArcGIS Pro).
The color of the object is defined by setting the material property. For example, the circle in the first symbol layer in the blog post discussed above was made a 50%-transparent red color:
material: { color: [219,53,53, 0.5] }
The property used to size the object depends on the class. For IconSymbol3DLayer, the size property is used and can be set in either points or pixels. For example, the three circles that combine to form the PointSymbol3D in the blog post have sizes of 20, 8 and 50 points. For ObjectSymbol3DLayer, the object is sized through the setting of the width, height, and depth properties (all in meters).
When symbolizing your point data, you should consider how you want the symbols to appear as the user modifies the view’s tilt. By their nature, the volumetric Object3DSymbolLayer objects have a height, so they appear to extend above the ground (like a pushpin). With Icon3DSymbolLayer objects, you have the option of draping the flat symbol directly on the ground or billboarding it. A draped symbol will become harder to see the more the view is tilted, while a billboarded symbol will always face the user, regardless of the scene’s tilt or heading.
Icon3DSymbolLayer objects will billboard by default. To obtain the draped look, you need to modify the elevationInfo property of the FeatureLayer you’re symbolizing so that its mode is changed from the default of "relative-to-ground” to “on-the-ground”. At the end of this section, you’ll find samples that demonstrate draping and billboarding the Jen & Barry’s cities.
Another design possibility is to float the symbol above the terrain by some height. This can be done by setting the FeatureLayer’s elevationInfo mode to “relative-to-ground” and offset to the desired height. This elevation offset sample [8] demonstrates drawing points in scenes with an offset height.
For the line-related symbol layer classes (LineSymbol3DLayer and PathSymbol3DLayer), you have fewer options. Your main considerations will be the color (again, set through the material property), the width (set through the size property) and whether you want the line to be flat (LineSymbol3DLayer) or volumetric (PathSymbol3DLayer). A volumetric line symbol looks like a pipe, so it is most appropriate for depicting water/wastewater, oil and gas pipelines. As with points, lines can be floated above the surface (or “buried” underground using a negative offset).
Finally, there are the polygon-related symbol layer classes (FillSymbol3DLayer and ExtrudeSymbol3DLayer). As with the other symbol layer classes, the material property is used to set the color. Each of the classes has one additional important property. For the flat FillSymbol3DLayer class, that property is outline, which can be set using a JavaScript object defining the desired color and size (for the width) of the polygon outline. For the volumetric ExtrudeSymbol3DLayer class, the other important property is size, which defines how high above the surface the polygon should extend (in meters). A common use case for the ExtrudeSymbol3DLayer class is extruding building footprint polygons by their height. We’ll see an example later in this lesson.
Below are examples that show the Jen & Barry’s data in a 3D scene. In all cases, the highways are depicted using the flat LineSymbol3DLayer and the counties using the flat FillSymbol3DLayer, since there was no good reason to use a volumetric symbol. The examples demonstrate three different ways of symbolizing the cities, however.
Recognizing the complexity of their Symbol object model and the value in being able to tinker with properties through a graphical interface, Esri developed their “Symbol Builder [12]." Intended for developers, the app is divided into three sections: a map, a UI for selecting symbol options, and a code box. The basic idea is that you pick a symbol and use the UI to alter its properties (size, color, style, etc.). As you make your changes, a symbol with your selected properties will be drawn on top of the map and the code required to create the symbol will be displayed in the code box. When you’re satisfied with the symbol, you can copy the code and paste it into your script.
The app is fairly straightforward and I encourage you to try it out. Keep in mind that you can switch the map from 2D to 3D and change the basemap. This can be particularly useful since symbols often work much better on some basemaps than others. The only weakness I see in the app is that there’s no way to get back to the opening list of Symbol classes after you’ve already selected one. If you want to do that, you’ll need to reload the page.
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 [14] 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.
When setting up a map or scene programmatically, it can be difficult to figure out the initial viewpoint properties (e.g., a map’s center and zoom, or especially a scene’s tilt and heading). One way to determine these values is to write an app in which the MapView or SceneView is exposed as a global variable, run the app, adjust the view to your liking, then use the browser’s developer tools to obtain the values needed. I’ve written an app like this, so let’s walk through the process.
So… hopefully you can see the idea here. You can use this set of steps to find a viewpoint that meets your app’s purpose, then copy the necessary viewpoint settings into the app’s source code. Once done, it’s a good idea to switch the view variable’s scope back to normal as it’s considered good programming practice to declare variables with the narrowest scope possible. You might also consider keeping a copy of the script file with the view variable declared with global scope so you can refer back to it down the road.
Popups are a topic that would also fit in the lesson on UI development, but since they are usually configured along with the layer’s renderer, we’ll talk about them now.
We saw earlier in the course that popups can be associated with graphics, though they are typically used to present information contained in a layer’s attribute table.
The first point to understand on this topic is that views (MapViews and SceneViews) have a Popup object associated with them. Note the wording – “a Popup object.” Only one Popup window can be open at a time.
While it’s possible to set the Popup object’s contents, doing so would lock in what is shown regardless of which feature is clicked. The correct way to configure a layer’s popups is to set its PopupTemplate property. PopupTemplates are most often set up for FeatureLayers, but the ImageryLayer and CsvLayer classes also support them.
Looking at the popupTemplate property in the SDK, you should note that it’s labeled as an autocast property. Thus, we can create a PopupTemplate object without having to add its module to the require() list.
Starting with just a basic popup, have a look at the pen below based on the Jen & Barry's cities data.
Lines 18-34 create a JavaScript object that is later used to set the FeatureLayer’s popupTemplate property. title and content are two properties that you will almost certainly want to set in any context. The title is the text that will appear at the top of the window in bold print. The content is what will appear in the body of the window. Note in the example that both of the strings used to set these properties contain field names enclosed in braces. This is the syntax used for plugging in values from the layer’s attribute table. Also note that the content string can contain HTML.
While the popups would work with just the title and content set, the example also sets the fieldInfos property, which is used to format number and date field values. Here, the property is set to an array of two objects. The POPULATION field has its digitSeparator set to true, which adds a separator (e.g., a comma in the U.S., a period in the U.K.) for numbers in the thousands or greater. It also has its places property set to 0 to avoid showing digits after the decimal point. The CRIME_INDE field has its places set to 2 to round its values to the nearest hundredth.
The last important point to note in the example is the setting of the FeatureLayer’s outFields property. This property, set using an array, specifies which fields from the attribute table should be included in the data sent from the server to the client. In this case, the array ["*"] specifies that all fields be included. This is OK when you’re dealing with a small dataset as in this case. For larger datasets, you can improve your map’s load time by limiting the outFields to just the fields needed.
In addition to simple textual information, popups can also display different forms of media (e.g., images and charts). In the example below showing the United States, I’ve configured the popups to show the state flag, license plate, and a pie chart of the population by race.
Note that the content property is set to an array of JavaScript objects rather than a string. When specifying the objects in this array, you must define each object's type. Depending on the type, you’ll then define some other property. In this example, there are two objects in the content array.
Starting with the first object, note that it is of type: "text", which requires following up by setting the text property to your desired string. Here, I’ve set the string to some HTML defining a heading and a horizontal rule.
The second object in the content array is of type: "media", which requires following up by setting the mediaInfos property. This property is itself set to an array of mediaInfo objects, in this case a 3-object array. The first two objects in the array are of type: "image", while the third is of type: "pie-chart" (other media types allowed are "bar-chart", "column-chart" and "line-chart").
Note that each of the media objects has its caption and value properties set. With ImageMediaInfo objects, the value must be set to an object with a sourceUrl setting. With PieChartMediaInfo objects, the value must be set to an object with a fields setting. Here I've set the fields property to an array of field names defined on Line 32. I knew about these fields from the REST services directory [23].
You may have noticed while experimenting with popups that they contain a button in the upper right that can be used to toggle it between its default state as a callout window connected to the clicked feature and a docked state. Docking the popup can be especially useful when it contains a lot of information or if the app is used on mobile devices.
Customizing the docking behavior is done by working with the Popup object’s dockOptions property. This property can be set using an object with one or more of the following properties: buttonEnabled, position and breakpoint. The buttonEnabled property can be set to False if you’d like to remove the docking button from the popup interface. The position property can be set to one of six allowed values ('top-right', 'top-left', 'top-center', 'bottom-right', 'bottom-left', and 'bottom-center'). The breakpoint property can be set to an object that defines the dimensions at which the popup should be docked automatically. See the Esri's Popup Docking sample [24] if you’re interested in learning more on this topic.
In addition to displaying text and media, the popup can also be configured to perform actions. By default, it contains a Zoom In button, to zoom in to the clicked feature, but the popup can be customized to execute other tasks as well. This lesson is long enough already, so I’ll just direct you to a couple of Esri samples that provide nice examples:
This week's assignment is rather open ended. I'd like you to do the following:
If you're having trouble deciding on what to map, you can search for your favorite topic on Esri's Living Atlas of the World [27]. Go to Browse, restrict the search to layers and I'd recommend checking "Esri-only content" for better results. You can also search for a particular topic in the search bar. Then look for anything of interest that is a Feature Service. The renderers you'll need to use won't work with other layer types.
Once you've found something, take a look at it's attribute table to make sure it has a field suitable for one of the renderers.
This project is one week in length. Please refer to the course Calendar, in Canvas, for the due date.
In this lesson, you learned about the many 2D and 3D symbol classes used to depict points, lines and polygons in Esri's JS API. You then saw how thematic mapping can be done through the use of various Renderer objects. Finally, you were shown how popup windows can be configured via the API to display information about map features in the form of text, images and charts.
With the basics of layer implementation under your belt, Lesson 7 will look at how to build search and query functionality into an app.
Links
[1] http://doc.arcgis.com/en/arcgis-online/share-maps/publish-features.htm#ESRI_SECTION1_94021BE7D875474681DAD20D05A90AF6
[2] https://developers.arcgis.com/javascript/latest/api-reference/esri-symbols-SimpleLineSymbol.html#style
[3] https://developers.arcgis.com/javascript/latest/api-reference/esri-symbols-SimpleFillSymbol.html#style
[4] https://codepen.io/jimdetwiler/pen/Zvppgy
[5] https://codepen.io/jimdetwiler
[6] https://codepen.io
[7] https://blogs.esri.com/esri/arcgis/2016/01/19/3d-visualization-working-with-icons-lines-and-fill-symbols/
[8] https://developers.arcgis.com/javascript/latest/sample-code/scene-elevationinfo/index.html
[9] https://codepen.io/jimdetwiler/pen/EogNmp
[10] https://codepen.io/jimdetwiler/pen/NXRbXp
[11] https://codepen.io/jimdetwiler/pen/qpaqow
[12] https://developers.arcgis.com/javascript/latest/sample-code/playground/live/index.html
[13] https://codepen.io/jimdetwiler/pen/ppXowN
[14] http://colorbrewer2.org
[15] https://codepen.io/jimdetwiler/pen/xpoxXj
[16] https://codepen.io/jimdetwiler/pen/OzeJvP
[17] https://codepen.io/jimdetwiler/pen/Rxzwym
[18] https://codepen.io/jimdetwiler/pen/mpZdKN/
[19] https://codepen.io/jimdetwiler/pen/qpzBMJ/
[20] http://jkroon863.infinityfreeapp.com/camera_demo/
[21] https://codepen.io/jimdetwiler/pen/eywYby
[22] https://codepen.io/jimdetwiler/pen/MYgjmVB
[23] http://sampleserver6.arcgisonline.com/arcgis/rest/services/Census/MapServer/3
[24] https://developers.arcgis.com/javascript/latest/sample-code/sandbox/index.html?sample=popup-docking-position
[25] https://developers.arcgis.com/javascript/latest/sample-code/popup-actions/index.html
[26] https://developers.arcgis.com/javascript/latest/sample-code/popup-custom-action/index.html
[27] https://livingatlas.arcgis.com/en/home/