Data Visualization in the Browser

Rob Larsen

2014.2.13

Download (https://github.com/roblarsen/dataviz)

What's in this Presentation

  • Me
  • Intro to SVG & Canvas
  • Live examples and code

Me

Rob Larsen

Me

an image of Rob Larsen's Beginning HTML and CSS Coming Soon: The Uncertain Web

I'm looking for a job

Full time/Consulting. Available 3/3/2014.

www.linkedin.com/in/robreact/

Remote or the Boston area

Art

DrunkenFist.com and Java+++

Data Visualization?

Visualization Examples Rhymes with crufty. Photo from Andreas_MB on Flickr

Basically

Taking this

{"username": "rob_react", "weeks": {"1152446400": {"Pink": 2, "The Decemberists": 3, "Jerry Jeff Walker": 7, "The Clash": 2, "Jim Carroll Band": 2, "Big Audio Dynamite II": 1, "The Beatles": 12, "Cheap Trick": 1, "Rod Stewart": 3, "Ninja Tunes": 1, "Nas": 1, "Boogie Down Productions": 1, "Everclear": 8, "DANGERDOOM": 2, "Paul Simon": 1, "Macy Gray": 1, "Jem": 1, "Terror Danjah Ft. Riko, Bruza, D Double E And Hyper": 1, "Kelly Willis": 1, "Herman's Hermits": 1, "Tool": 1, "Sound Tracks": 1, "The Cure": 1, "Belle and Sebastian": 9, "Tom Waits": 2, "Nick Drake": 1, "Jammer Ft. Wiley, D Double E, Kano and Goodz": 2, "Oasis": 4, "Acid House Kings": 2, "Ralph Stanley & Friends": 1, "Joe Jackson": 1, "The Libertines": 7, "Wilco": 1, "Sublime": 5, "The Cardigans": 1, "Bill Hicks": 3, "Harry Nilsson": 1, "Operation Ivy": 1, "Bob Dylan": 11, "Joni Mitchell": 1, "Led Zeppelin": 1, "50 Cent": 1, "Black Crowes & Wilco": 1, "Kano": 1, "Mason Jennings": 7, "Dave Matthews Band": 1, "The White Stripes": 1, "Michael Penn": 4, "Ralph Stanley": 1, "Kirsty MacColl / The Pogues": 1, "Billy Bragg & Wilco": 2, "Misfits": 3, "The Pretenders": 2, "The Pogues": 12, "Sangue Misto": 1, "Rufus": 1, "The Beach Boys": 2, "Marvin Gaye": 2, "The Strokes": 3, "The Streets": 5, "The Stanley Brothers": 5, "Franz Ferdinand": 1, "Dave Matthews": 1, "My Morning Jacket": 1

And turning it into this

Au Revoir http://lastgraph3.aeracode.org/, you were awesome.

Core Visualization Technologies

Scalable Vector Graphics (SVG)

What is SVG?

SVG is vector graphics mixed up with XML.

Much like peanut butter and chocolate.

When created in the context of an HTML document SVG elements are fancy DOM elements-- properties are stored as part of the DOM and access to individual elements and properties is available using the DOM.

It can also be styled with CSS.

I Think SVG is Great

In addition to being coded by hand or generated by software, SVG can also be output from a vector based drawing program. You can then use that .svg file as the src of an <img> element or as the background of an element as defined by CSS. Plus, it's still just a text format you can hand edit or pull into your document with XHR and still tweak with JavaScript.

Vector means: it scales!

I just solved the responsive images conundrum.

Here's an SVG element

Viewing Source


<svg width="200" height="200"
    viewPort="0 0 200 200" version="1.1"
xmlns="http://www.w3.org/2000/svg">
<circle cx="100" cy="100" r="75" fill="#fe57a1"/>
</svg>

SVG can also look like this

Viewing Source


<img src="img/circle.svg">
            

General SVG Support

data from caniuse.com

Canvas

What's Canvas?

The canvas element provides a scriptable interface for drawing two-dimensional images in the browser.

One of the early stars of the HTML5 era.

Here's a Canvas Element

Viewing Source


<canvas id="circle" width="800" height="300"></canvas>
            

   var ctx = document.getElementById( "circle" ).getContext( "2d" );
   ctx.beginPath();
   ctx.arc( 400, 150, 75, 0, Math.PI*2, true ); 
   ctx.fillStyle = "#fe57a1";
   ctx.closePath();
   ctx.fill();
            

It's a low level API

Everyone knows arc is all you need to draw a circle when you have Math.PI*2, right?


            ctx.arc( 400, 150, 75, 0, Math.PI*2, true );
            
The arguments are "center x", "center y", "radius", "start angle, in radians", "end angle, in radians" and "clockwise, as a Boolean"

General Canvas Support

data from caniuse.com

Comparing Canvas & SVG

At a High Level

  • Go SVG when dealing with a limited number of elements, lower animation requirements and higher interactivity needs.
  • Canvas is better for applications that require intense animation, real-time interaction and/or many more elements.

Your Mileage May Vary

This issue could be a whole presentation all by itself. It's a nuanced topic. To help, here are a couple of articles which cover this question in greater depth:

Use what works

Canvas, SVG, HTML elements + CSS animations, Google Maps, etc. It's all good as long as you're telling a story.

In Practice

What follows is a cross section of the tools and approaches I've used over the past few years.

AngularJS and SVG

Working with Angular and SVG is a natural fit. Angular allows you to bind data to DOM elements. SVG elements are DOM elements.

Magic.

If Angular gives you hives, then squint and pretend it's some other framework. The basic idea, manipulating SVG attributes and values directly with JS still stands

AngularJS and SVG

This demo also shows that SVG doesn't need to be wholly generated. This is basically an SVG template output from Adobe Illustrator with some data plugged in and managed with Angular.

Comic Book Sales Over $100,000 from 1993 - 2013. More details on this visualization on my blog.

View Source: The HTML/SVG


<div data-ng-app="comicsApp">
<div data-ng-controller="chartCtrl">
  <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="100%" viewBox="0 0 980 600" enable-background="new 0 0 980 600" xml:space="preserve">
    <filter id="drop-shadow">
      <feGaussianBlur in="SourceAlpha" stdDeviation="2" result="blur"/>
      <feOffset in="blur" dx="4" dy="4" result="offsetBlur"/>
      <feMerge>
        <feMergeNode in="offsetBlur"/>
        <feMergeNode in="SourceGraphic"/>
      </feMerge>
    </filter>
    <g id="lines">
        <line fill="none" stroke="#000000" stroke-miterlimit="10" x1="90.828" y1="-8" x2="90.828" y2="579.297"/>
        <line fill="none" stroke="#B5B5B6" stroke-miterlimit="10" x1="0" y1="579.297" x2="981.996" y2="579.297"/>
        <-- MANY LINES SNIPPED -->
    </g>
    <g id="text">
      <text x="10" y="16">price in USD</text>
      <text x="71" y="574">$0</text>
      <text x="480" y="595">2003</text>
      <text x="78" y="595">1993</text>
      <text x="881" y="595">2013</text>
      <text x="25" y="553">$100,000</text>
      <text x="25" y="468">$500,000</text>
      <text x="16" y="363">$1,000,000</text>
      <text x="16" y="256">$1,500,000</text>
      <text x="16" y="151">$2,000,000</text>
      <text x="16" y="45" >$2,500,000</text>
    </g>
    <g id="dynamic">
      <circle  data-ng-repeat="it in items | filter : venues" 
        opacity="0.8" data-ng-attr-fill="{{colorPicker(it.venue)}}" 
        data-ng-attr-cx="{{it.date | xDate}}" 
        data-ng-mouseover="updateTooltip(it)" 
        data-ng-mouseout="updateTooltip(0)"  
        data-ng-attr-cy="{{it.price | yPrice}}" 
        r="7" />
      <rect data-ng-show="tooltip.price" 
        data-ng-model="tooltip" 
        transform="translate(-55,-100)" 
        data-ng-attr-x="{{tooltip.date | xDate}}" 
        data-ng-attr-y="{{tooltip.price | yPrice}}" 
        width="150" height="80" fill="white" 
        stroke=""  style="filter:url(#drop-shadow)" />
      <text data-ng-show="tooltip.price" 
        data-ng-model="tooltip" 
        transform="translate(-45,-90)" 
        data-ng-attr-x="{{tooltip.date | xDate}}" 
        data-ng-attr-y="{{tooltip.price | yPrice}}" >{{tooltip.title}} {{tooltip.issue}}</text>
      <text data-ng-show="tooltip.price" 
        data-ng-model="tooltip" 
        transform="translate(-45,-75)" 
        data-ng-attr-x="{{tooltip.date | xDate}}" 
        data-ng-attr-y="{{tooltip.price | yPrice}}" >{{tooltip.pedigree}}{{tooltip.collection}}{{tooltip.provenance}} {{tooltip.grade_src | srcFilter}} {{tooltip.grade}}</text>
      <text data-ng-show="tooltip.price" 
        data-ng-model="tooltip" 
        transform="translate(-45,-60)" 
        data-ng-attr-x="{{tooltip.date | xDate}}" 
        data-ng-attr-y="{{tooltip.price | yPrice}}" >{{tooltip.price|currency}}</text>
      <text data-ng-show="tooltip.price" 
        data-ng-model="tooltip" 
        transform="translate(-45,-45)" 
        data-ng-attr-x="{{tooltip.date | xDate}}" 
        data-ng-attr-y="{{tooltip.price | yPrice}}" >{{tooltip.date}}</text>
      <text data-ng-show="tooltip.price" 
        data-ng-model="tooltip" 
        transform="translate(-45,-30)" 
        data-ng-attr-x="{{tooltip.date | xDate}}" 
        data-ng-attr-y="{{tooltip.price | yPrice}}" >{{tooltip.venue}}</text>
    </g>
    <g id="legend">
      <rect x="125" y="5" 
        width="15" height="15" fill="#D95B43" 
        data-ng-mouseover="venues='comic connect'" 
        data-ng-mouseout="venues=''"></rect>
      <text x="145" y="15" 
        class="legend">Comic Connect</text>
      <rect x="245" y="5" width="15" 
        height="15" fill="#ECD078" 
        data-ng-mouseover="venues='heritage'" 
        data-ng-mouseout="venues=''"></rect>
      <text x="265" y="15" 
        class="legend">Heritage</text>
      <rect x="325" y="5" width="15" 
        height="15" fill="#C02942" 
        data-ng-mouseover="venues='comiclink'" 
        data-ng-mouseout="venues=''"></rect>
      <text x="345" y="15" 
        class="legend">ComicLink</text>
      <rect x="405" y="5" width="15" 
        height="15" fill="#542437" 
        data-ng-mouseover="venues='pedigree'" 
        data-ng-mouseout="venues=''"></rect>
      <text x="425" y="15" 
        class="legend">Pedigree</text>
      <rect x="485" y="5" width="15" 
        height="15" fill="#53777A" 
        data-ng-mouseover="venues='metropolis'" 
        data-ng-mouseout="venues=''"></rect>
      <text x="505" y="15" 
        class="legend">Metropolis</text>
      <rect x="565" y="5" width="15" 
        height="15" fill="#69D2E7" 
        data-ng-mouseover="venues='jp'" 
        data-ng-mouseout="venues=''"></rect>
      <text x="585" y="15" 
        class="legend">JP/Mint</text>
      <rect x="645" y="5" width="15" 
        height="15" fill="#FA6900" 
        data-ng-mouseover="venues='mastronet'" 
        data-ng-mouseout="venues=''"></rect>
      <text x="665" y="15" 
        class="legend">Mastronet</text>
      <rect x="725" y="5" width="15" 
        height="15" fill="#FE4365" 
        data-ng-mouseover="venues='pgc'" 
        data-ng-mouseout="venues=''"></rect>
      <text x="745" y="15" 
        class="legend">PGCMint</text>
      <rect x="805" y="5" width="15" 
        height="15" fill="#666666" 
        data-ng-mouseover="venues='unknown'" 
        data-ng-mouseout="venues=''"></rect>
      <text x="825" y="15" 
        class="legend">Other/Unkown</text>
    </g>
  </svg> 
</div>
</div>
          

View Source


angular.module('comicsApp.controllers', ['comicFilters','comicsFactories']).
  .controller('chartCtrl', ["$scope",'dataService',
    function( $scope, dataService )  {
    //ACTUALLY JUST A PLAIN OLD JAVASCRIPT OBJECT.
    //I'M JUST SHARING THE XHR CODE AND DATA MANIPULATION
    $scope.items = dataService;
    $scope.tooltip = {
      price:0
    }
    $scope.updateTooltip = function(it) {
      $scope.tooltip = {
        price : it.price || 0,
        venue : it.venue,
        date : it.date,
        title : it.title,
        issue : it.issue,
        pedigree : it.pedigree,
        collection : it.collection,
        provenance : it.provenance,
        grade_src : it.grade_src,
        grade : it.grade
      } 
    }
    $scope.colorPicker= function( venue ){
      switch (venue) {
        case "Heritage":
          return $scope.colors[0];
        case  "Comic Connect":
          return $scope.colors[1];
        case "Comiclink":
          return $scope.colors[2];
        case  "Pedigree":
          return $scope.colors[3];
        case "Metropolis":
          return $scope.colors[4];
        case  "JP The Mint":
          return $scope.colors[5];
        case "Mastronet":
          return $scope.colors[6];
        case  "PGCMint":
          return $scope.colors[7];
        default:
          return $scope.colors[8];
       }
    }
    $scope.colors = ["#ECD078","#D95B43","#C02942","#542437",
    "#53777A","#69D2E7","#FA6900", "#FE4365","#666666"];
  }
  
]);
          

View Source


angular.module('comicFilters', []).filter('xDate', function () {
  "use strict";
  return function (input) {
    if (input !== undefined) {
      var date = input.split("-");
      var years = date[0] - 1993;
      var months = (years * 12) + parseInt(date[1]);
      return 90 + months * 3.3333;
      
    }
  };
}).filter('yPrice', function () {
  "use strict";
  return function (input) {
    if (input){
      return 579 - (input/4699.248120300752);
    }
  };
});
          

View Source


angular.module('comicsFactories', [])
  .factory('dataService', function ($http) {
    var records = [];
     $http({"method" : "GET", "url" : "data/books.json"}).success(
      function(data){
        for (var i = 0, len = data.books.length; i < len; i++){
          if (data.books[i].sales.length){
            for (var j = 0, l = data.books[i].sales.length; j < l; j++){
              if (parseFloat(data.books[i].sales[j].price) >= 100000){
                records.push({
                  "title":data.books[i].title,
                  "issue": data.books[i].issue, 
                  "pedigree": data.books[i].pedigree,
                  "collection": data.books[i].collection,
                  "provenance": data.books[i].provenance,
                  "grade": data.books[i].grade,
                  "grade_src":data.books[i].grade_src,
                  "uid": Math.floor(data.books[i].uid),
                  "date":data.books[i].sales[j].sale_date,
                  "venue":data.books[i].sales[j].venue,
                  "price": Math.floor(data.books[i].sales[j].price),
                  "link":data.books[i].sales[j].link
                })
              }
            }
          }
        }
    });
    return records;
  });
          

SVG with D3js

D3.js is a JavaScript library for manipulating documents based on data. D3 helps you bring data to life using HTML, SVG and CSS. D3’s emphasis on web standards gives you the full capabilities of modern browsers without tying yourself to a proprietary framework, combining powerful visualization components and a data-driven approach to DOM manipulation.
Trip connections between the top 10 Hubway departure stations. Data from the Hubway Data Visualization Challenge

View Source


//What is the Matrix?            
[/* ss   td   bpl   boy  back  cc    ken   bea   lw    newb */  
  [2689, 508, 1170, 189, 1007, 187,  745,  248,  263,  2311], //ss
  [1064, 121, 830,  323, 2473, 393,  453,  312,  533,  599],  //td
  [506,  296, 813,  530, 988,  540,  1936, 578,  747,  268],  //bpl
  [706,  311, 1568, 526, 1273, 371,  618,  694,  481,  227],  //boy
  [178,  701, 277,  176, 663,  227,  379,  284,  330,  111],  //back
  [550,  270, 548,  445, 196,  769,  868,  317,  1477, 195],  //cc
  [344,  141, 468,  955, 172,  346,  502,  388,  415,  97],   //ken
  [333,  207, 455,  545, 196,  1322, 618,  254,  659,  62],   //bea
  [655,  120, 301,  90,  2368, 108,  226,  99,   229,  875],  //lw
  [270,  221, 625,  436, 239,  278,  548,  1158, 320,  90]    //newb
]
          

View Source


//from bl.ocks.org/mbostock/4062006 etc. 

/*
* Create a chord layout
* Pass in our matrix
*/
var chord = d3.layout.chord()
    .padding( .05 )
    .sortSubgroups( d3.descending )
    .matrix( matrix );
var width = 900,
    height = 500,
    innerRadius = Math.min( width, height ) * .35,
    outerRadius = innerRadius * 1.1;

/*
* https://github.com/mbostock/d3/wiki/Ordinal-Scales
*/
var fill = d3.scale.ordinal()
             .domain( d3.range(4) )
             .range(["#336699", "#99ccff", "#6699cc", "#0066cc"]);

var svg = d3.select( "body" )
            .append( "svg" )
            .attr( "width", width )
            .attr( "height", height )
            .append( "g" )
            .attr( "transform", "translate(" + width / 2 + "," + height / 2 + ")");

svg.append( "g" )
   .selectAll( "path" )
   .data( chord.groups )
   .enter()
   .append( "path" )
   .style( "fill", function(d) { return fill(d.index); })
   .style( "stroke", function(d) { return fill(d.index); })
   .attr( "d", d3.svg
                 .arc()
                 .innerRadius( innerRadius )
                 .outerRadius( outerRadius ))
   .on( "mouseover" , fade(.1) )
   .on( "mouseout" , fade(1) );
 
var ticks = svg.append( "g" )
               .selectAll( "g" )
               .data( chord.groups )
               .enter().append( "g" )
               .selectAll( "g" )
               .data( groupTicks )
               .enter()
               .append( "g" )
               .attr( "transform", function( d ) {
                 return "rotate(" + (d.angle * 180 / Math.PI - 90) + ")"
                    + "translate(" + outerRadius + ",0)";
                });

ticks.append( "line" )
     .attr( "x1", 1 )
     .attr( "y1", 0 )
     .attr( "x2", 5 )
     .attr( "y2", 0 )
     .style( "stroke", "#000" );

var arc = d3.svg
            .arc()
            .innerRadius( innerRadius );

ticks.append( "text" )
     .attr( "x", 8 )
     .attr( "dy", ".35em" )
     .attr( "transform", function(d) { 
        return d.angle > Math.PI ? "rotate(180)translate(-16)" : null; })
     .style( "text-anchor", function(d) { 
        return d.angle > Math.PI ? "end" : null; })
     .text( function(d) { 
        return d.label; });

svg.append( "g" )
   .attr( "class", "chord" )
   .selectAll( "path" )
   .data( chord.chords )
   .enter()
   .append( "path" )
   .attr( "d", d3.svg.chord().radius( innerRadius ))
   .style( "fill", function(d) { 
      return fill(d.target.index); })
   .style( "opacity", 1)
   .outerRadius( outerRadius );

var g = svg.selectAll( "g.group" )
          .data( chord.groups )
          .enter()
          .append( "svg:g" )
          .attr( "class", "group")
          .on( "mouseover", fade(.02))
          .on( "mouseout", fade(.80));

g.append( "svg:path" )
 .style( "stroke", function(d) { 
    return fill(d.index); })
 .style( "fill", function(d) { 
    return fill(d.index); })
 .attr( "d", arc);

/*
* Customized
*/
g.append("svg:text")
  .each(function(d) { d.angle = (d.startAngle + d.endAngle) / 2; })
  .attr("dy", ".35em")
  .attr("text-anchor", function(d) { return d.angle > Math.PI ? "end" : null; })
  .attr("transform", function(d) {
/*
* Get the lengths of some triangle legs. Return the newX and newY of the label
*/
    var newX = Math.sin( d.angle ) * (outerRadius +10),
    newY = Math.cos( d.angle ) * (outerRadius+30);
    return "translate(" + newX +","+ (-newY) + ")";
  })
  .text(function(d) { return names[d.index]; });


function groupTicks(d) {
  var k = ( d.endAngle - d.startAngle ) / d.value;
  return d3.range( 0, d.value, 1000 ).map( function(v, i) {
    return {
      angle: v * k + d.startAngle,
      label: i % 5 ? null : v / 1000 + "k"
    };
  });
}

function fade( opacity ) {
  return function( g, i ) {
    svg.selectAll( ".chord path" )
        .filter(function(d) { return d.source.index != i && d.target.index != i; })
      .transition()
        .style( "opacity", opacity );
  };
}
                

Canvas

This specification defines the 2D Context, Level 2 for the HTML canvas element. The 2D Context provides objects, methods, and properties to draw and manipulate graphics on a canvas drawing surface.

BACH

View Source


function draw( data ) {
  ctx.fillRect( 0, 0, 900, 300 );
  var len = data.length,
      width = 900/len,
      gradient = ctx.createLinearGradient( 0, 0, 0, 300 );
      gradient.addColorStop( ".5", "#003366" );
      gradient.addColorStop( "1.0", "#ccff00" );
      ctx.fillStyle = gradient;
  for ( var i=0; i<len; i++ ) {
      ctx.fillRect( i, 300, width, -data[i] );    
  }
}   

Google Maps

Google Maps

The Google Maps Javascript API lets you embed Google Maps in your own web pages. Version 3 of this API is especially designed to be faster and more applicable to mobile devices, as well as traditional desktop browser applications.

About Google Maps

The Maps API is already rich, adding a layer of data on top of it opens up a wide range of possibilities

Read all about this visualization http://dfst.us/1e5y

View Source


;( function( window, document, $, undefined ) {
  "use strict";
  if( window.HW === undefined ) {
    window.HW = {};
  };
  var HW = window.HW;
  HW.common = {
    init: function() {
      $( "#map" ).height( $( window ).height() );
      var GM = google.maps,
        defaultPosition = new GM.LatLng( 42.3520, -71.0560 ),
        mapOptions = {
          zoom: 12,
          center: defaultPosition,
          mapTypeId: GM.MapTypeId.ROADMAP
        },
        map = new GM.Map( document.getElementById( 'map' ), mapOptions ),
        geocoder = new GM.Geocoder(),
        position, endPosition, marker, polylineOptions, DD, DS;
      $( "#map" ).data( "map" , map );
      createMarker( 0 );
      var i = 1, timer;
      function render(){
        if ( i == 10 ){
          clearInterval( timer );
          return;
        }
        createMarker( i );
        i++;   
      }
      timer = setInterval( render, 10000 );
      function createMarker( i ){
        var marker, position, start, end, dist;
        position = new GM.LatLng( data.stations[i].latLng[0] , data.stations[i].latLng[1] );
        marker = new GM.Marker({
          position: position,
          map: map,
          title: data.stations[i].name,
          data: data.stations[i].destinations
        });
//http://www.movable-type.co.uk/scripts/latlong.html
//http://stackoverflow.com/questions/27928/how-do-i-calculate-distance-between-two-latitude-longitude-points
        function distance( lat1, lon1, lat2, lon2) {
          var R = 6371; // Radius of the earth in km
          var dLat = deg2rad( lat2-lat1 );  // deg2rad below
          var dLon = deg2rad( lon2-lon1 ); 
          var a = 
            Math.sin( dLat/2 ) * Math.sin( dLat/2 ) +
            Math.cos( deg2rad(lat1) ) * Math.cos( deg2rad( lat2 ) ) * 
            Math.sin( dLon/2 ) * Math.sin( dLon/2 ); 
          var c = 2 * Math.atan2( Math.sqrt( a ), Math.sqrt( 1-a ) ); 
          var d = R * c; // Distance in km
          return d;
        }

        function deg2rad( deg ) {
          return deg * ( Math.PI/180 )
        }

        var j = 0, len = data.stations[i].destinations.length, rendertimer;
        function newrender(){
          if ( j == len ){
            clearInterval( rendertimer )
          return;
          }
          dist = distance( data.stations[i].latLng[0],data.stations[i].latLng[1], data.stations[i].destinations[j].latLng[0], data.stations[i].destinations[j].latLng[1] )
          createRoutes( i, j, position, dist);
          j++;   
        }
        rendertimer = setInterval( newrender, 2000 );
        function createRoutes( i, j, start, distance) {
          start = position;
          end = data.stations[i].destinations[j].latLng;
          var trips = data.stations[i].destinations[j].trips;
          HW.common.renderer( start,end, trips, i, distance, data.stations[i].name, data.stations[i].destinations[j].name );
        }
      }       
    },
    renderer: function( start, end, trips, index, distance, startName, endName ) {
      var GM = google.maps,
        polylineOptions, DD, DS, map = $( "#map" ).data( "map" ),
        colors = ["#B22937","#DE5003","#80C837","#229F6E","#60B6CA","#6F6DA7", "#1F1D6D","#80529A","#A6358C","#A2395B"];
      end = new GM.LatLng( end[0], end[1] );
      var polylineOptions = new GM.Polyline({
        strokeColor : colors[index],
        strokeOpacity : .4,
        strokeWeight : trips/100
      }),
        DS = new GM.DirectionsService(),
        DD = new GM.DirectionsRenderer({
          polylineOptions : polylineOptions,
          markerOptions : { icon : "img/Hubwaylogo.png" }
        });
      DD.setMap( map );
      var request = {
        origin : start,
        destination : end,
        travelMode : GM.TravelMode.BICYCLING
      }; 
      DS.route( request, function( result, status ) {
        if( status == GM.DirectionsStatus.OK ) {
          DD.setDirections( result );
        }
      });
    }
  }
}( window, document, jQuery ));
         

SVG: Other Libraries

  • Raphaël "Raphaël is a small JavaScript library that should simplify your work with vector graphics on the web. If you want to create your own specific chart or image crop and rotate widget, for example, you can achieve it simply and easily with this library."
  • Snap.svg "SVG is an excellent way to create interactive, resolution-independent vector graphics that will look great on any size screen. And the Snap.svg JavaScript library makes working with your SVG assets as easy as jQuery makes working with the DOM."
  • SVG.js "a lightweight JavaScript library for manipulating and animating svg using an uncluttered syntax"
  • Polymaps "a free JavaScript library for making dynamic, interactive maps in modern web browsers."

Canvas: Other Libraries

  • sigma.js "an open-source lightweight JavaScript library to draw graphs, using the HTML canvas"
  • paper.js "Scriptographer ported to JavaScript and browser, using HTML5 Canvas"
  • Peity "progressive <canvas> piecharts"
  • arbor.js "a graph visualization library using web workers and jQuery"
  • envision "a library for creating fast, dynamic and interactive HTML5 visualizations.
  • CanvasQuery "use HTML5 Canvas with jQuery syntax"
  • Processing.js "the sister project of the popular Processing visual programming language"
  • Fabric.js Fabric.js is a powerful and simple Javascript HTML5 canvas library

My Columbo Moment

just one more thing...

Datavisualization.ch Selected Tools

A list of all these tools, plus more.

Thanks!

roblarsen on Github | @robreact/@roblarsenwww on Twitter | blog @ htmlcssjavascript.com