Earlier today an anonymous reader (not sure why they didn't share their name) shared a cool site with me - the National Broadband Map (www.broadbandmap.gov). 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.
<div id="result"></div>
<!-- credit: http://en.wikipedia.org/wiki/Template:USA_imagemap_with_state_names -->
<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" />
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!
$("#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;
renderResult(item); });
});
var cache = {};
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 renderResult(state) {
//used by number_format
var num_form = {decimalSeparator:".", thousandSeparator:",",numberOfDecimals:0}; var myResult = cache[state];
var s= "<h2>" + myResult.geographyName + "</h2>";
s += "<p>";
s += "<table width="400">";
s += "<tr class="trHeader"><td colspan="2"><strong>Population</strong></td></tr>";
s += "<tr><td>Total:</td><td>" + $().number_format(myResult.population,num_form) +"</td></tr>";
s += "<tr><td>Asians:</td><td>" + perc(myResult.raceAsian) +"</td></tr>";
s += "<tr><td>Blacks:</td><td>" + perc(myResult.raceBlack) +"</td></tr>";
s += "<tr><td>Hispanics:</td><td>" + perc(myResult.raceHispanic) +"</td></tr>";
s += "<tr><td>Native Americans:</td><td>" + perc(myResult.raceNativeAmerican) +"</td></tr>";
s += "<tr><td>Whites:</td><td>" + perc(myResult.raceWhite) +"</td></tr>"; s += "<tr ><td colspan="2"><br/></td></tr>";
s += "<tr class="trHeader"><td colspan="2"><strong>Income</strong></td></tr>";
s += "<tr><td>Median Income</td><td>" + "$" + $().number_format(myResult.medianIncome, num_form) + "</td></tr>";
s += "<tr><td>Income Below Poverty</td><td>" + "$" + $().number_format(myResult.incomeBelowPoverty, num_form) + "</td></tr>";
s += "<tr><td>Income < 25K:</td><td>" + perc(myResult.incomeLessThan25) +"</td></tr>";
s += "<tr><td>Income 25K-50K:</td><td>" + perc(myResult.incomeBetween25to50) +"</td></tr>";
s += "<tr><td>Income 50K-100K:</td><td>" + perc(myResult.incomeBetween50to100) +"</td></tr>";
s += "<tr><td>Income 100K-200K:</td><td>" + perc(myResult.incomeBetween100to200) +"</td></tr>";
s += "<tr><td>Income > 200K:</td><td>" + perc(myResult.incomeGreater200) +"</td></tr>"; s += "<tr ><td colspan="2"><br/></td></tr>";
s += "<tr class="trHeader"><td colspan="2"><strong>Age</strong></td></tr>";
s += "<tr><td>Age Below 5</td><td>" + perc(myResult.ageUnder5) + "</td></tr>";
s += "<tr><td>Age 5-19:</td><td>" + perc(myResult.ageBetween5to19) +"</td></tr>";
s += "<tr><td>Age 20-34:</td><td>" + perc(myResult.ageBetween20to34) +"</td></tr>";
s += "<tr><td>Age 35-59:</td><td>" + perc(myResult.ageBetween35to59) +"</td></tr>";
s += "<tr><td>Age Above 60:</td><td>" + perc(myResult.ageGreaterThan60) +"</td></tr>"; s += "<tr ><td colspan="2"><br/></td></tr>";
s += "<tr class="trHeader"><td colspan="2"><strong>Education</strong></td></tr>";
s += "<tr><td>Bachelor or Higher</td><td>" + perc(myResult.educationBachelorOrGreater) + "</td></tr>";
s += "<tr><td>Highschool Graduater</td><td>" + perc(myResult.educationHighSchoolGraduate) + "</td></tr>"; s += "</table>";
s += "</p>"; $("#result").html(s); }
function perc(x) {
var perc_form = {decimalSeparator:".", thousandSeparator:",",numberOfDecimals:2};
x = x*100;
return $().number_format(x,perc_form) + "%";
};
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.