A Spine.js with Handlebars.js JavaScript Tutorial

A couple of the JavaScript MVC frameworks I’ve been playing with lately are Spine.js and Backbone.js. While they share many similarities, one area they differ in is the number of tutorials. Backbone has many working native JavaScript tutorials. Spine’s are largely written in Coffeescript. While I appreciate Coffeescript, I wanted to write a simple native JavaScript Spine app to give the framework a spin. None of the working examples I were found were written in JavaScript, so I thought “Hey! Good opportunity to write a simple Spine tutorial!”.

The tutorial assumes you’ve already read at least some of Spine’s documentation. My goal is not to repeat the existing documentation, only to provide a simple tutorial that works, so I’ll keep this post short and the code comments thorough.

The sample data is similar to what you might find in a Google Adwords interface. Campaigns have ad groups, and ad groups have keywords. Campaigns, Ad Groups, and Keywords all have their own models. Each cell in the grid, and the grid itself, get Controllers. The click events on the cells are handled by the jQuery fisheyeGrid plugin, so they’re absent from the controllers. In the keyword cells are a link you can click to change its color. That color state is stored in localstorage in the examples below where it’s enabled. The views are created by Handlebars.js, and rendered to the page in the controllers.

Technologies used in this tutorial:

Ways to access the code:

A post discussing the visual/interaction design is coming next. Feel free to post questions/comments here or on GitHub.

Finally, here’s what you should see when running the example:



Keywords
Ad Group
Campaign

 

 

Fisheye Grids – A User Interface Design Pattern

While perusing the University of Maryland's Human-Computer Interaction Lab site, I ran into an interesting interface named Datelens. It improves mobile calendar usability, especially on complex tasks like date comparisons.

While Datelens is tied to Outlook/calendars, the fisheye grid UI pattern is more broadly applicable.  Uses include any information seeking scenario where users want to zoom in on some information while retaining some context. Concrete uses include replacements for carousels, slideshows, calendars, shopping carts, and many of the ubiquitous modal (popup) window implementations.

Despite the benefits I haven't seen the grid pattern in any web apps, so I prototyped a jQuery plugin to play with it.  Click below to expand example cells. Clicking a closed cell when one is open will transition to that cell.

For the source, see Github.  For the research, here's a PDF. If you use it some where, I'd love to hear how your UX analytics data compares with other info zooming methods.

Note: As it’s only a prototype, I haven’t tested it in IE.

Example Calendar Grid

 

 

Example Image Grid

 
 

Visualizing Cambridge Chicago’s Data with jQuery and Google Maps + Charts + Refine + Fusion Tables

“What story do I want to tell?”

That question lies at the heart of every visualization.  After two things were stolen in my first two weeks in Cambridge, I got curious about thefts trends.  Questions help me clarify stories. In this case there are two:

  • How does total theft in some areas compare to total theft in others?
  • How does each area’s theft trend over time compare with others around it?

My initial intent was to map the two questions using Cambridge and/or Boston metro area data.  The closest I found was a reference from the Cambridge data links page to some pre-made 2005 maps.  Mapping the questions sounded fun despite Cambridge data availability issues (apparently a shared problem), so I went ahead using data from Chicago’s awesome city data portal.  The resulting map is on the right.

2003-2010 total Chicago thefts under $300 by ward

Total per Ward
- Darker == more total theft

- % change from 2003 on a 20%-120% scale

Loading…

2003-2010 total Chicago thefts under $300 US dollars by ward and year, including ID thefts.  Whether $300 is adjusted for inflation is unknown.  Reporting procedures and other potential bias sources are also unknown.  Excludes 2001-2002 due to irregular/infrequent entries, and a small number of entries lacking wards. From City of Chicago crime data view on 2011/09/22. Originally from 2001-Present full crime data table.

Crafting the Stories

Time series and spatial relationships are a challenge to combine in a single visualization. Three options include animation, small multiples, and embedded charts.

Animation

One solution is motion – i.e., representing change over eight years by showing eight maps over eight seconds. I’m not a huge fan of animated choropleths since humans cannot effectively comprehend color transitions in fifty polygons (7.84MB).

Small Multiples

Another option advocated by Edward Tufte is small multiple maps.  In this scenario, it requires substantial effort to compare many proximal polygons over time, so it wasn’t my first choice.

Embedded Charts

Embedded charts are ideal.  The combination of line charts and geographically positioned wards shows both spatial relationships and trends effectively.  Still, they require some tweaking to get there – desaturation, map feature removal, selective recoloring, hiding polygon boundaries to emphasize the trend charts, and varying theft total saturation and lightness all help both stories stand out depending on focus. While absent polygon borders make individual wards differentiation harder, the major areas are more visible – a reasonable tradeoff of low-level details for high-level patterns and trends.

Results

Assuming the data is valid for this purpose, reported thefts in all wards showed net declines between 2003 and 2010. Contrasts between wards with high and low total theft are easy to see – higher theft in the city center extends to the Northwest, West, and South.

Overall I’m happy with the outcome and had fun creating it. In fact, If you’re reading this and you happened to steal a fridge in front of my steps a few weeks ago, consider it a gift.  Cheers!

Supporting Technologies

Technologies that went into this visualization (roughly in order applied):

  • Chicago Theft Data (CSV)
  • Chicago Ward Boundaries (ESRI Shapefiles)
  • Shpescape.com (Convert ESRI shapefiles to ward Fusion Tables)
  • Fusion Tables (Merge ward geo data and thefts data. Export to Google Refine for cleaning. Re-import cleaned data. Format KML via handy style formatter in visualize>map menu)
  • Google Refine (Import merged data as CSV. Remove irrelevant rows, including rows with no ward and years with spotty data.  Export merged table as CSV for Fusion Tables re-import. Export years, thefts per year, and ward centroids as JSON for JavaScript to create line and bar charts)
  • Google API Loader (Load the maps API)
  • Google Maps API (Framework for interacting with Google Maps)
  • Google Charts API – Image Charts (Info window bar charts and embedded line charts)
  • JavaScript / jQuery (File loading, API interaction, and general display)

Upcoming

Ahead eventually, probably, another visualization mapping near real-time human psychological well-being by country with the newly released NextStage SampleMatch data. Cool stuff!

Staging Server – Fast and Free

Staging environments can be expensive in time and materials.  Turns out there's a way to bypass all of that for testing JavaScript.  It's also easy to learn, quick to implement, works across platforms and browsers, and best of all, free.

Test Your Code On Your Live Site!

No kidding. Using a file loader like Require.js, you can get a JavaScript staging environment running in less time than it takes to decide on a physical server. Here's how:

//if you don't already have a namespace, create one to keep things tidy.
var foo={};

// List the live files you would normally load.
foo.loadFilesStr="file1.js,file2.js,file3.js";

// Get your favorite cookie reading function.  Here's one I use.
foo.strToObj=function(str,keySplit,pairSplit){var y={},k=keySplit||'&',fn=k.substr?'split':'match',a=str[fn](k),i=0,p,L=a.length;for(;L>i;i++){p=a[i][fn](pairSplit||'=');y[p[0]]=p[1]||''}return y};

// Pick a cookie name.  "devEnv" works.
foo.devCookieName= 'devEnv';

// Read the cookies
foo.cookie = foo.strToObj( document.cookie, '; ', '=');

// get the dev cookie's value.
foo.devCookieVal=foo.cookie[foo.devCookieName];

// if a value exists, replace your normal files with a string of files from the cookie
if( foo.devCookieVal ){ foo.loadFilesStr = foo.devCookieVal.split(',') }

// make them an array to prep them for your favorite script loader.
foo.loadFilesArray=foo.loadFilesStr.split(',');

// and load the scripts.
require(foo.loadFilesStr, function(){
    $(document).ready(function(){ do stuff here })
});

Finally, change the files you want to load with a bookmarklet that displays and sets the cookie values, like this one.

javascript:(function(foo,cookieName){
  var cookieVal=foo.cookie[cookieName]||'',
  filesArray = cookieVal.split(','), // get the cookie value
  i=0, // set an iterator
  L=filesArray.length, // and a length for the files array
  tempStr='', // and a temporary string to append file names to for display
  userEnteredStr = (function () {
    for (;L>i;i++) { // loop over file names
      (tempStr += ('nn' + filesArray[i])); // append them for display
    }
    var reply = prompt ( // prompt for any changes
      cookieName + ' will be set to the following paths:' + tempStr +
      'nnChange the cookie value below to alter them. '+
      'Leave blank or cancel to remove the cookie',
      cookieVal // display the val if one exists
    );
    return reply;
  })(),
  expir = (userEnteredStr ? (new Date((new Date).getTime() + 1e11).toGMTString()) : '-1'), // set the expiration
  newCookieVal = (cookieName + '=' + (userEnteredStr||'') + '; expires=' + expir + '; path=/'); // set the new value
  document.cookie = newCookieVal; // write the cookie
  alert (userEnteredStr ? ('settingn' + newCookieVal) : cookieName+' removed'); // notify the action taken
})(foo,foo.devCookieName);

Finally, for developer usability I set a notification to indicate what files are loading.

$('body').prepend(
   '<div id="devCookieVals" style="position:fixed;left:0;top:0;z-index:1000;">'+
   '<span style="background-color:#777;color:#DDD">Dev Files Loaded:</span> '+
   foo.cookie[foo.cookieName]+'</div>');

That's it.  Visitors will continue receiving the live files. You can load any set of files you'd like by clicking your bookmarklet and refreshing, including new jQuery versions, unit tests, and more. Enjoy!