Creating an interactive United States Map
In this post we're going to create a Service Portal widget that displays a United States map that colors the individual states based on the amount of incidents opened in that state relative to the other states. We are going to use D3js to accomplish this. If you are unfamiliar with D3, check out this previous post introducing D3 to ServiceNow. This post will follow an example from the D3 community which can be found here.
To create our state map we will need to grab our incident data in our server script, create a dependency to a new UI script that contains the coordinates for the state map, and pass our data to the UI script from our client script.
Server script
In our server script, we will create an array of objects within the data object. This array will contain 50 objects; each representing a state. Once that array of objects is created, we will use a pair of GlideAggregate calls to retrieve the counts of open and closed incidents by location. We'll use these calls to populate our array of objects with the counts for each state.
Below is a screenshot of the server script along with the pasted script:
(function() {
// Create an array of state abbreviations in the same order as our UI script
var states = ["HI", "AK", "FL", "SC", "GA", "AL", "NC", "TN", "RI", "CT", "MA",
"ME", "NH", "VT", "NY", "NJ", "PA", "DE", "MD", "WV", "KY", "OH",
"MI", "WY", "MT", "ID", "WA", "DC", "TX", "CA", "AZ", "NV", "UT",
"CO", "NM", "OR", "ND", "SD", "NE", "IA", "MS", "IN", "IL", "MN",
"WI", "MO", "AR", "OK", "KS", "LA", "VA"];
// Create an array of objects in our data object with placeholder properties
data.states = [];
for (i=0;i<states.length;i++) {
data.states.push({state: states[i], open: 0, closed: 0});
}
// Find counts of open incidents by location
var openCount = new GlideAggregate('incident');
openCount.addQuery('active', 'true');
openCount.addAggregate('COUNT', 'location');
openCount.query();
while (openCount.next()) {
var openState = openCount.location.state;
var openStateCount = openCount.getAggregate('COUNT', 'location')*1;
for (i=0; i<data.states.length; i++) {
// Increase the open property if there is a match with a state
if (openState == data.states[i].state) {
data.states[i].open += openStateCount;
break;
}
}
}
// Find counts of closed incidents by location
var closedCount = new GlideAggregate('incident');
closedCount.addQuery('active', 'false');
closedCount.addAggregate('COUNT', 'location');
closedCount.query();
while (closedCount.next()) {
var closedState = closedCount.location.state;
var closedStateCount = closedCount.getAggregate('COUNT', 'location')*1;
for (i=0; i<data.states.length; i++) {
// Increase the closed property if there is a match with a state
if (closedState == data.states[i].state) {
data.states[i].closed += closedStateCount;
break;
}
}
}
})();
UI script
Similar to the widget dependency we created in the previous posts, we will create dependencies to call D3 and also to call a UI script that we will create. The code for our new UI script can be found here. It doesn't matter what you name this UI script as long as you remember what you name it; I named mine u_states. Once you have created this UI script, reference it with a widget script dependency.
Client Script
Our client script will be used to determine what color each state should be, define the HTML template that will be used as a tooltip, and make the call to our new UI script to actually draw our state map.
We will loop through the array of objects that we created in our server script and use the D3 interpolate to determine each state's color. The D3 interpolate allows us to define a range of two colors: one for the lowest incident density and one for the highest incident density. Based on a given state's incident count, its color will fall somewhere in this range. For this example we will use #ffffcc as our low color and #800026 as our high color, but you can use any colors you want.
Below is a screenshot of my client script along with the pasted script:
function() {
/* widget controller */
var c = this;
// Find the max number of open incidents in a single state
var maxOpen = d3.max(c.data.states, function(d) { return d.open; });
// Create object containing the state data and determine what color
// each state should be
var mapData ={};
c.data.states
.forEach(function(d){
mapData[d.state] = {color: d3.interpolate("#ffffcc", "#800026")(d.open/maxOpen), open: d.open, closed: d.closed};
});
// Define the HTML for the tooltip
function tooltipHtml(n, d){
return "
"+n+"
"+"
"+"
"+"
"+"
| Open | "+(d.open)+" |
| Closed | "+(d.closed)+" |
| Total | "+(d.open + d.closed)+" |
}
// Calls the draw function from our u_states UI script which uses
// D3 to draw our map
uStates.draw("#statesvg", mapData, tooltipHtml);
d3.select(self.frameElement).style("height", "800px");
}
HTML
Below is the pasted HTML I used for this widget:
Incident State Map
CSS
Below is the pasted CSS I used for this widget:
.state{
fill: none;
stroke: #a9a9a9;
stroke-width: 1;
}
.state:hover{
fill-opacity:0.5;
}
tooltip {
position: absolute;
text-align: center;
padding: 20px;
margin: 10px;
font: 12px sans-serif;
background: lightsteelblue;
border: 1px;
border-radius: 2px;
pointer-events: none;
}
tooltip h4{
margin:0;
font-size:14px;
}
tooltip{
background:rgba(0,0,0,0.9);
border:1px solid grey;
border-radius:5px;
font-size:12px;
width:auto;
padding:4px;
color:white;
opacity:0;
}
tooltip table{
table-layout:fixed;
}
tooltip tr td{
padding:0;
margin:0;
}
tooltip tr td:nth-child(1){
width:50px;
}
tooltip tr td:nth-child(2){
text-align:center;
}
Final product
Below is a screenshot of my finished widget. Now that we have the basic framework for a map widget, we can theoretically create a widget with any map and use any data from ServiceNow. The main part that we skipped for this post is the actual creation of the SVG map coordinates, but there are plenty of online resources that help with that process (potentially a future post?).
Mitch Stutler
VividCharts Founder
linkedin.com/in/mitchellstutler
Sources
https://www.servicenow.com/community/developer-blog/creating-an-interactive-united-states-map/ba-p/2291856