text: // global vars
  var $graphic = null;
  var pymChild = null;
  
  var BAR_HEIGHT = 30;
  var BAR_GAP = 0;
  var DOT_RADIUS = 6;
  // var GRAPHIC_DATA; <!-- DEFINED IN CHILD_TEMPLATE.HTML
  var GRAPHIC_DEFAULT_WIDTH = 600;
  var LABEL_MARGIN = 6;
  var LABEL_WIDTH = 65;
  var MOBILE_THRESHOLD = 500;
  var VALUE_MIN_WIDTH = 30;
  
  var colors = {
      'red1': '#6C2315', 'red2': '#A23520', 'red3': '#D8472B', 'red4': '#E27560', 'red5': '#ECA395', 'red6': '#F5D1CA',
      'orange1': '#714616', 'orange2': '#AA6A21', 'orange3': '#E38D2C', 'orange4': '#EAAA61', 'orange5': '#F1C696', 'orange6': '#F8E2CA',
      'yellow1': '#77631B', 'yellow2': '#B39429', 'yellow3': '#EFC637', 'yellow4': '#F3D469', 'yellow5': '#F7E39B', 'yellow6': '#FBF1CD',
      'teal1': '#0B403F', 'teal2': '#11605E', 'teal3': '#17807E', 'teal4': '#51A09E', 'teal5': '#8BC0BF', 'teal6': '#C5DFDF',
      'blue1': '#28556F', 'blue2': '#3D7FA6', 'blue3': '#51AADE', 'blue4': '#7DBFE6', 'blue5': '#A8D5EF', 'blue6': '#D3EAF7'
  };
  var isMobile = false;
  var keys = null;
  
  // D3 formatters
  var fmtComma = d3.format(',');
  var fmtYearAbbrev = d3.time.format('%y');
  var fmtYearFull = d3.time.format('%Y');
  
  
  /*
   * Initialize
   */
  var onWindowLoaded = function() {
      if (Modernizr.svg) {
          $graphic = $('#graphic');
          
          keys = d3.keys(GRAPHIC_DATA[0]).filter(function(key) {
              return key !== 'app';
          });
  
          GRAPHIC_DATA.forEach(function(d) {
              keys.forEach(function(k) {
                  d[k] = +d[k];
              });
          });
          
          pymChild = new pym.Child({
              renderCallback: render
          });
      } else {
          pymChild = new pym.Child({ });
      }
  }
  
  
  /*
   * RENDER THE GRAPHIC
   */
  var render = function(containerWidth) {
      // fallback if page is loaded outside of an iframe
      if (!containerWidth) {
          containerWidth = GRAPHIC_DEFAULT_WIDTH;
      }
  
      // check the container width; set mobile flag if applicable
      if (containerWidth <= MOBILE_THRESHOLD) {
          isMobile = true;
      } else {
          isMobile = false;
      }
  
      // clear out existing graphics
      $graphic.empty();
  
      // draw the new graphic
      // (this is a separate function in case I want to be able to draw multiple charts later.)
      drawGraph(containerWidth);
  
      // update iframe
      if (pymChild) {
          pymChild.sendHeight();
      }
  }
  
  
  /*
   * DRAW THE GRAPH
   */
  var drawGraph = function(graphicWidth) {
      var graph = d3.select('#graphic');
      var margin = {
          top: 0,
          right: 15,
          bottom: 20,
          left: (LABEL_WIDTH + LABEL_MARGIN)
      };
      var numBars = GRAPHIC_DATA.length;
      var ticksX = 6;
      
      var colorD3 = d3.scale.ordinal()
          .range([ colors['blue3'], colors['orange3'], colors['orange2'], colors['orange3'], colors['orange5'] ])
          .domain(keys);
  
      // define chart dimensions
      var width = graphicWidth - margin['left'] - margin['right'];
      var height = ((BAR_HEIGHT + BAR_GAP) * numBars);
  
      var x = d3.scale.linear()
          .domain([ 0, 300])
          .range([0, width]);
  
      var y = d3.scale.linear()
          .range([ height, 0 ]);
  
      // define axis and grid
      var xAxis = d3.svg.axis()
          .scale(x)
          .orient('bottom')
          .ticks(ticksX)
          .tickFormat(function(d) {
              return d.toFixed(0);
          });
  
      var xAxisGrid = function() {
          return xAxis;
      }
  
      // draw the legend
      var legend = graph.append('ul')
  		.attr('class', 'key')
  		.selectAll('g')
  			.data(keys)
  		.enter().append('li')
  			.attr('class', function(d, i) {
  				return 'key-item key-' + i + ' ' + classify(d);
  			});
      legend.append('b')
          .attr('style', function(d, i) {
              var s = '';
              if (i <= 1) {
                  s += 'background-color: ' + colorD3(d);
              } else {
                  s += 'width: 11px; ';
                  s += 'height: 11px; ';
                  s += 'border: 2px solid ' + colorD3(d) + '; ';
              }
              return s;
          });
      legend.append('label')
          .html(function(d) {
              return d;
          });
  
      // draw the chart
      var chart = graph.append('div')
          .attr('class', 'chart');
  
      var svg = chart.append('svg')
          .attr('width', width + margin['left'] + margin['right'])
          .attr('height', height + margin['top'] + margin['bottom'])
          .append('g')
          .attr('transform', 'translate(' + margin['left'] + ',' + margin['top'] + ')');
  
      // x-axis (bottom)
      svg.append('g')
          .attr('class', 'x axis')
          .attr('transform', 'translate(0,' + height + ')')
          .call(xAxis);
  
      // alternating shaded rows
      var shaded_rows = svg.append('g')
          .attr('class', 'rows')
          .selectAll('rect')
              .data(GRAPHIC_DATA)
          .enter().append('rect')
              .attr('class', 'bg-row')
              .attr('fill', function(d,i) {
                  if (i % 2 == 0) {
                      return '#eee';
                  } else {
                      return '#fff';
                  }
              })
              .attr('style', 'opacity: 0.5;')
              .attr('width', width)
              .attr('height', BAR_HEIGHT)
              .attr('x', 0)
              .attr('y', function(d,i) {
                  return (BAR_HEIGHT + BAR_GAP) * i;
              });
  
      // x-axis gridlines
      svg.append('g')
          .attr('class', 'x grid')
          .attr('transform', 'translate(0,' + height + ')')
          .call(xAxisGrid()
              .tickSize(-height, 0, 0)
              .tickFormat('')
          );
  
      // draw the dots
      keys.forEach(function(key, k) {
          svg.append('g')
              .attr('class', 'dots')
              .selectAll('circle')
                  .data(GRAPHIC_DATA)
              .enter().append('circle')
                  .attr('cx', function(d) { 
                      return x(d[key]);
                  })
                  .attr('cy', function(d,i) { 
                      return i * (BAR_HEIGHT + BAR_GAP) + (BAR_HEIGHT / 2);
                  })
                  .attr('r', function(d) {
                      if (k <= 1) {
                          return DOT_RADIUS;
                      } else {
                          return Math.ceil(DOT_RADIUS * .65);
                      }
                  })
                  .attr('style', function(d,i) {
                      var s = '';
                      if (k <= 1) {
                          s += 'fill: ' + colorD3(key) + '; ';
                          s += 'stroke: ' + colorD3(key) + '; ';
                      } else {
                          s += 'fill: rgba(255,255,255,.7); ';
                          s += 'stroke: ' + colorD3(key) + '; ';
                          s += 'stroke-width: 1.5px; ';
                      }
                      return s;
                  });
      });
      
      // draw labels for each bar
      var labels = chart.append('ul')
          .attr('class', 'labels')
          .attr('style', 'width: ' + LABEL_WIDTH + 'px; top: ' + margin['top'] + 'px; left: 0;')
          .selectAll('li')
              .data(GRAPHIC_DATA)
          .enter().append('li')
              .attr('style', function(d,i) {
                  var s = '';
                  s += 'width: ' + LABEL_WIDTH + 'px; ';
                  s += 'height: ' + BAR_HEIGHT + 'px; ';
                  s += 'left: ' + 0 + 'px; ';
                  s += 'top: ' + (i * (BAR_HEIGHT + BAR_GAP)) + 'px; ';
                  return s;
              })
              .attr('class', function(d) {
                  return classify(d['app']);
              })
              .append('span')
                  .text(function(d) {
                      return d['app'];
                  });
  }
  
  
  /*
   * HELPER FUNCTIONS
   */
  var classify = function(str) { // clean up strings to use as CSS classes
      return str.replace(/\s+/g, '-').toLowerCase();
  }
  
  
  /*
   * Initially load the graphic
   * (NB: Use window.load instead of document.ready
   * to ensure all images have loaded)
   */
  $(window).load(onWindowLoaded);
  