National Broadband Map (jQuery Demo)

This post is more than 2 years old.

Earlier today an anonymous reader (not sure why they didn't share their name) shared a cool site with me - the National Broadband Map ( The site is a collection of data related to geography, education, age, income factors compared to broadband access. It has multiple ways to parse the data. For example, just entering your zip (here is mine) returns a report showing local providers and their advertised speeds. You can also use a "Google-Map-ish" type interface (here) to see factors broadly across the country. But the coolest of all is that there is a large list of APIs available on their developer page. Multiple APIs are offered in both JSON and XML flavors. As of my testing tonight their JSONP format is not working for me. I've sent an email to them but it could certainly be my fault. I thought it would be kind of fun to build a demo with their data. While my demo isn't as pretty as theirs, it highlights some interesting things that I've never had a chance to play with before in jQuery. Let's take a look.

First - I thought it would be nice to work with an image map of the United States. I did a quick Google search and came across this Wikipedia page that had both the graphic and the image map as well. It's been forever since I worked with image maps but I was surprised how quickly the syntax came back to me. In my code samples below I'll be trimming some of the data a bit, but you can view source on the Wikipedia page (or my demo) for the full image map code. I began with that and a simple div I'll be using to show some data.

<!-- credit: --> <map name="USImageMap" id="USImageMap"> <area href="/wiki/Alabama" shape="poly" coords="537,310,556,377,518,382,518,395,506,391,504,312" alt="Alabama" title="Alabama" /> <area href="/wiki/Alaska" shape="poly" coords="127,381,128,451,148,457,171,481,171,491,153,491,132,461,98,456,83,473,10,482,55,456,34,431,43,391,60,375,89,365" alt="Alaska" title="Alaska" /> <area href="/wiki/Kansas" shape="poly" coords="307,224,301,278,407,280,407,243,401,238,404,231,393,224" alt="Kansas" title="Kansas" /> <area href="/wiki/Kentucky" shape="poly" coords="485,286,565,275,582,259,569,241,544,234,528,258,502,261" alt="Kentucky" title="Kentucky" /> <area href="/wiki/Louisiana" shape="poly" coords="421,407,426,382,416,367,418,351,461,351,463,363,456,385,479,385,488,396,495,416,456,421" alt="Louisiana" title="Louisiana" /> <area href="/wiki/Vermont" shape="rect" coords="607,53,651,72" alt="Vermont" title="Vermont" /> <area href="/wiki/Rhode_Island" shape="rect" coords="720,163,796,184" alt="Rhode Island" title="Rhode Island" /> </map> <img alt="Map of USA with state names.svg" src="800px-Map_of_USA_with_state_names.svg.png" width="800" height="495" usemap="#USImageMap" />

<div id="result"></div>

Just to be clear - I deleted a lot from the map above but even if you've never seen an image map before you can guess as to how it's working. Each state has a polygon of coordinates defined. The image tag points to the map block and this creates a "hot" link over the image that can link to different URLs. I didn't bother removing the HREFs but note that in my demo they aren't going to be used. Ok, let's get into the jQuery!

var cache = {};

$("#USImageMap area").click(function(e) { var item = $(this).attr("title"); e.preventDefault(); if(item in cache) { renderResult(item); return; } $.getJSON("load.cfm", {"state":item}, function(res,code) { if(res.status != "OK") { alert("Oops - it broke."); return; }

	//We should have one object we care about in Results
	var myResult = res.Results[0];
	cache[item] = myResult;


I begin with a click handler based on area tags within my image map. Since the title is the same as the state, I grab that from the dom and see if it's in my cache. I don't use a cache very often but I'm trying to be more cognizant lately of how my Ajax demos perform. If there isn't a cache for the item I have to make a network call. I said earlier that their APIs are supposed to support JSON/P. It wasn't working for me so I quickly switched to using a ColdFusion proxy. All that file does is take the state and make a call to the API. My demo is making use of the demographic API which provides population, race, age, and income data. Once ColdFusion "echos" the JSON back to the client I do basic result handling and store it in the cache if the API call was good. Now let's look at renderResult. This is where my design skills get kicked into full gear.

function perc(x) { var perc_form = {decimalSeparator:".", thousandSeparator:",",numberOfDecimals:2}; x = x*100; return $().number_format(x,perc_form) + "%"; };

function renderResult(state) { //used by number_format var num_form = {decimalSeparator:".", thousandSeparator:",",numberOfDecimals:0};

var myResult = cache[state];
var s= "&lt;h2&gt;" + myResult.geographyName + "&lt;/h2&gt;";
s += "&lt;p&gt;";
s += "&lt;table width=\"400\"&gt;";
s += "&lt;tr class=\"trHeader\"&gt;&lt;td colspan=\"2\"&gt;&lt;strong&gt;Population&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;";
s += "&lt;tr&gt;&lt;td&gt;Total:&lt;/td&gt;&lt;td&gt;" + $().number_format(myResult.population,num_form) +"&lt;/td&gt;&lt;/tr&gt;";
s += "&lt;tr&gt;&lt;td&gt;Asians:&lt;/td&gt;&lt;td&gt;" + perc(myResult.raceAsian) +"&lt;/td&gt;&lt;/tr&gt;";
s += "&lt;tr&gt;&lt;td&gt;Blacks:&lt;/td&gt;&lt;td&gt;" + perc(myResult.raceBlack) +"&lt;/td&gt;&lt;/tr&gt;";
s += "&lt;tr&gt;&lt;td&gt;Hispanics:&lt;/td&gt;&lt;td&gt;" + perc(myResult.raceHispanic) +"&lt;/td&gt;&lt;/tr&gt;";
s += "&lt;tr&gt;&lt;td&gt;Native Americans:&lt;/td&gt;&lt;td&gt;" + perc(myResult.raceNativeAmerican) +"&lt;/td&gt;&lt;/tr&gt;";
s += "&lt;tr&gt;&lt;td&gt;Whites:&lt;/td&gt;&lt;td&gt;" + perc(myResult.raceWhite) +"&lt;/td&gt;&lt;/tr&gt;";
s += "&lt;tr &gt;&lt;td colspan=\"2\"&gt;&lt;br/&gt;&lt;/td&gt;&lt;/tr&gt;";
s += "&lt;tr class=\"trHeader\"&gt;&lt;td colspan=\"2\"&gt;&lt;strong&gt;Income&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;";
s += "&lt;tr&gt;&lt;td&gt;Median Income&lt;/td&gt;&lt;td&gt;" + "$" + $().number_format(myResult.medianIncome, num_form) + "&lt;/td&gt;&lt;/tr&gt;";
s += "&lt;tr&gt;&lt;td&gt;Income Below Poverty&lt;/td&gt;&lt;td&gt;" + "$" + $().number_format(myResult.incomeBelowPoverty, num_form) + "&lt;/td&gt;&lt;/tr&gt;";
s += "&lt;tr&gt;&lt;td&gt;Income &lt; 25K:&lt;/td&gt;&lt;td&gt;" + perc(myResult.incomeLessThan25) +"&lt;/td&gt;&lt;/tr&gt;";
s += "&lt;tr&gt;&lt;td&gt;Income 25K-50K:&lt;/td&gt;&lt;td&gt;" + perc(myResult.incomeBetween25to50) +"&lt;/td&gt;&lt;/tr&gt;";
s += "&lt;tr&gt;&lt;td&gt;Income 50K-100K:&lt;/td&gt;&lt;td&gt;" + perc(myResult.incomeBetween50to100) +"&lt;/td&gt;&lt;/tr&gt;";
s += "&lt;tr&gt;&lt;td&gt;Income 100K-200K:&lt;/td&gt;&lt;td&gt;" + perc(myResult.incomeBetween100to200) +"&lt;/td&gt;&lt;/tr&gt;";
s += "&lt;tr&gt;&lt;td&gt;Income &gt; 200K:&lt;/td&gt;&lt;td&gt;" + perc(myResult.incomeGreater200) +"&lt;/td&gt;&lt;/tr&gt;";

s += "&lt;tr &gt;&lt;td colspan=\"2\"&gt;&lt;br/&gt;&lt;/td&gt;&lt;/tr&gt;";
s += "&lt;tr class=\"trHeader\"&gt;&lt;td colspan=\"2\"&gt;&lt;strong&gt;Age&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;";
s += "&lt;tr&gt;&lt;td&gt;Age Below 5&lt;/td&gt;&lt;td&gt;" + perc(myResult.ageUnder5) + "&lt;/td&gt;&lt;/tr&gt;";
s += "&lt;tr&gt;&lt;td&gt;Age 5-19:&lt;/td&gt;&lt;td&gt;" + perc(myResult.ageBetween5to19) +"&lt;/td&gt;&lt;/tr&gt;";
s += "&lt;tr&gt;&lt;td&gt;Age 20-34:&lt;/td&gt;&lt;td&gt;" + perc(myResult.ageBetween20to34) +"&lt;/td&gt;&lt;/tr&gt;";
s += "&lt;tr&gt;&lt;td&gt;Age 35-59:&lt;/td&gt;&lt;td&gt;" + perc(myResult.ageBetween35to59) +"&lt;/td&gt;&lt;/tr&gt;";
s += "&lt;tr&gt;&lt;td&gt;Age Above 60:&lt;/td&gt;&lt;td&gt;" + perc(myResult.ageGreaterThan60) +"&lt;/td&gt;&lt;/tr&gt;";

s += "&lt;tr &gt;&lt;td colspan=\"2\"&gt;&lt;br/&gt;&lt;/td&gt;&lt;/tr&gt;";
s += "&lt;tr class=\"trHeader\"&gt;&lt;td colspan=\"2\"&gt;&lt;strong&gt;Education&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;";
s += "&lt;tr&gt;&lt;td&gt;Bachelor or Higher&lt;/td&gt;&lt;td&gt;" + perc(myResult.educationBachelorOrGreater) + "&lt;/td&gt;&lt;/tr&gt;";
s += "&lt;tr&gt;&lt;td&gt;Highschool Graduater&lt;/td&gt;&lt;td&gt;" + perc(myResult.educationHighSchoolGraduate) + "&lt;/td&gt;&lt;/tr&gt;";

s += "&lt;/table&gt;";
s += "&lt;/p&gt;";


That's a huge code block - and I apologize - but it really comes down to a few small things. "myResult" is the data result from the API. It contains a number of demographic values for a state. The perc function I wrote is a simple way to handle their percentile values. Lastly - the number_format function you see is a jQuery plugin by Ricardo Andrietta Mendes. (You can find the source here.) But really - it's just making a big ole HTML string and printing it to the DOM. This is a great example of where jQuery Templates would make things a lot nicer.

So the end result is: You've got a map. You click on a state and an Ajax call is made (if it was the first time) to National Broadband's site to get the data. This data is then displayed below the map. Take a look at the demo below.

Raymond Camden's Picture

About Raymond Camden

Raymond is a senior developer evangelist for Adobe. He focuses on document services, JavaScript, and enterprise cat demos. If you like this article, please consider visiting my Amazon Wishlist or donating via PayPal to show your support. You can even buy me a coffee!

Lafayette, LA

Archived Comments

Comment 1 by Kris Korsmo posted on 2/18/2011 at 8:08 AM

I'm getting a 404 when I click on the map.

Comment 2 by Raymond Camden posted on 2/18/2011 at 8:09 AM

Try again - I forgot to remove a few console.log messages.

Comment 3 by Kris Korsmo posted on 2/18/2011 at 8:12 AM

Very cool!!!

Comment 4 by James Edmunds posted on 2/20/2011 at 12:41 AM

Very interesting, and very interesting as well how rich that API is, with info that you don't immediately, necessarily associate with broadband availability. Could come in very handy!

Comment 5 by Drew Wells posted on 2/24/2011 at 11:36 PM

An alternative to your series of s += is to use an array.join(). In fact, s+= provides the worst performance across all browsers vs [].join or s = 'str' + 'str' + 'str'. IIRC, s = 'str' + 'str' + 'str' is the fastest type of concatenation in firefox, whereas [].join() is fastest way for IE. I tend to use Array.join because it makes the best looking code even if it's slower in chrome/ff than iterations of string concatenation.

Code would look like:
s = ['<p>',
'<table width="400">'];


You could do some further cleanup by using jQuery.tmpl. Code would look like this:

var tmpl = $.tmpl('<tr class="trHeader"><td colspan="2"><strong>${section}</strong></td></tr><tr><td>Total:</td><td>${total}</td></tr><tr><td>${header1}</td><td>${data1}</td></tr>');

var html = $.template( tmpl, {
section: 'Population',
total: $().number_format(myResults.population, num_form),
header1: 'Asians',
data1: perc(myResults.raceAsian)


The code would not need to be in a loop for further groups. I believe if you pass it an array, it loops through the array and generates all the html for you. //Creates template to be used with $.template() //Combines tmpl and your data

Comment 6 by Raymond Camden posted on 2/25/2011 at 3:57 AM

@Drew: Good points there. I'm aware of string concat being slow - seems like it is everywhere in the world. In my defense - I was thinking readability over performance. ;) (Course, I did add caching too so that's a partial lie.)

I had considered templates but I'm still wrapping my head around how easy they are to work with custom data. Ie, how much work I'd have to do to get the data in a form that would work well with the template.

Comment 7 by Justin Toth posted on 8/22/2011 at 1:58 AM

Has anyone been able to get ajax calls to their services to work? I'm trying using jquery and json or jsonp as data type, however it always gives me an invalid label error, as if the json isn't formatted correctly...

Comment 8 by sanm posted on 2/9/2012 at 8:30 PM

Could you please share your complete code, because the link which you have provided is not working.


Comment 9 by Raymond Camden posted on 2/9/2012 at 9:31 PM

What isn't working? I just tried the demo and it seems ok. What code do you want - the CF code? The front end code you can get by just doing View Source.