text: // Global config
  var GRAPHIC_DEFAULT_WIDTH = 600;
  var MOBILE_THRESHOLD = 500;
  
  // Global vars
  var pymChild = null;
  var isMobile = false;
  var graphicData = null;
  var candidates = [];
  
  // D3 formatters
  var fmtComma = d3.format(',');
  
  /*
   * Initialize the graphic.
   */
  var onWindowLoaded = function() {
      if (Modernizr.svg) {
          loadLocalData(GRAPHIC_DATA);
          //loadCSV('data.csv')
      } else {
          pymChild = new pym.Child({});
      }
  }
  
  /*
   * Load graphic data from a local source.
   */
  var loadLocalData = function(data) {
      graphicData = data;
  
      formatData();
  
      pymChild = new pym.Child({
          renderCallback: render
      });
  }
  
  /*
   * Load graphic data from a CSV.
   */
  var loadCSV = function(url) {
      d3.csv(GRAPHIC_DATA_URL, function(error, data) {
          graphicData = data;
  
          formatData();
  
          pymChild = new pym.Child({
              renderCallback: render
          });
      });
  }
  
  /*
   * Format graphic data for processing by D3.
   */
  var formatData = function() {
      graphicData.forEach(function(d) {
          d['amt'] = +d['amt'];
      });
  
      candidates = _.pluck(graphicData, 'candidate');
      candidates = d3.set(candidates).values();
  }
  
  /*
   * Render the graphic(s). Called by pym with the container width.
   */
  var render = function(containerWidth) {
      if (!containerWidth) {
          containerWidth = GRAPHIC_DEFAULT_WIDTH;
      }
  
      var graphicWidth = null;
      var gutterWidth = 22;
      var numAcross = 3;
  
      if (containerWidth <= MOBILE_THRESHOLD) {
          isMobile = true;
          graphicWidth = containerWidth;
      } else {
          isMobile = false;
          graphicWidth = Math.floor((containerWidth - ((numAcross - 1) * gutterWidth)) / numAcross);
      }
  
      // Render the chart!
      var containerElement = d3.select('#graphic');
      containerElement.html('');
  
      candidates.forEach(function(d, i) {
          var candidateData = graphicData.filter(function(c) {
              return c['candidate'] == d;
          });
  
          containerElement.append('div')
              .attr('class', 'candidate ' + classify(d))
              .attr('style', function(d) {
                  if (!isMobile) {
                      var s = 'width: ' + graphicWidth + 'px; ';
                      s += 'float: left; ';
                      if (i % numAcross != 0) {
                          s += 'margin-left: ' + gutterWidth + 'px;';
                      }
                      return s;
                  }
              });
  
          renderColumnChart({
              container: '.' + classify(d),
              width: graphicWidth,
              data: candidateData,
              candidate: d
          });
      });
  
      // Update iframe
      if (pymChild) {
          pymChild.sendHeight();
      }
  }
  
  /*
   * Render a column chart.
   */
  var renderColumnChart = function(config) {
      /*
       * Setup chart container.
       */
      var labelColumn = 'label';
      var valueColumn = 'amt';
  
      var aspectWidth = isMobile ? 4 : 16;
      var aspectHeight = isMobile ? 3 : 9;
      var valueGap = 6;
  
      var margins = {
          top: 5,
          right: 5,
          bottom: 20,
          left: 37
      };
  
      var ticksY = [ 0, 25, 50, 75, 100 ];
      var roundTicksFactor = 50;
  
      // Calculate actual chart dimensions
      var chartWidth = config['width'] - margins['left'] - margins['right'];
      var chartHeight = Math.ceil((config['width'] * aspectHeight) / aspectWidth) - margins['top'] - margins['bottom'];
  
      // Clear existing graphic (for redraw)
      var containerElement = d3.select(config['container']);
      containerElement.html('');
  
      /*
       * Create the root SVG element.
       */
      containerElement.append('h3')
          .text(config['candidate']);
  
      var chartWrapper = containerElement.append('div')
          .attr('class', 'graphic-wrapper');
  
      var chartElement = chartWrapper.append('svg')
          .attr('width', chartWidth + margins['left'] + margins['right'])
          .attr('height', chartHeight + margins['top'] + margins['bottom'])
          .append('g')
          .attr('transform', 'translate(' + margins['left'] + ',' + margins['top'] + ')');
  
      /*
       * Create D3 scale objects.
       */
      var xScale = d3.scale.ordinal()
          .rangeRoundBands([0, chartWidth], .1)
          .domain(config['data'].map(function (d) {
              return d[labelColumn];
          }));
  
      var yScale = d3.scale.linear()
          .domain([ 0, 100 ])
          .range([chartHeight, 0]);
  
      /*
       * Create D3 axes.
       */
      var xAxis = d3.svg.axis()
          .scale(xScale)
          .orient('bottom')
          .tickValues([ xScale.domain()[0], xScale.domain()[xScale.domain().length - 1] ]);
  
      var yAxis = d3.svg.axis()
          .scale(yScale)
          .orient('left')
          .tickValues(ticksY)
          .tickFormat(function(d) {
              return fmtComma(d) + '%';
          });
  
      /*
       * Render axes to chart.
       */
      chartElement.append('g')
          .attr('class', 'x axis')
          .attr('transform', makeTranslate(0, chartHeight))
          .call(xAxis);
  
      chartElement.append('g')
          .attr('class', 'y axis')
          .call(yAxis)
  
      /*
       * Render grid to chart.
       */
      var yAxisGrid = function() {
          return yAxis;
      };
  
      chartElement.append('g')
          .attr('class', 'y grid')
          .call(yAxisGrid()
              .tickSize(-chartWidth, 0)
              .tickFormat('')
          );
  
      /*
       * Render bars to chart.
       */
  
      chartElement.append('g')
          .attr('class', 'bars')
          .selectAll('rect')
          .data(config['data'])
          .enter()
          .append('rect')
              .attr('x', function(d) {
                  return xScale(d[labelColumn]);
              })
              .attr('y', function(d) {
                  if (d[valueColumn] < 0) {
                      return yScale(0);
                  }
  
                  return yScale(d[valueColumn]);
              })
              .attr('width', xScale.rangeBand())
              .attr('height', function(d) {
                  if (d[valueColumn] < 0) {
                      return yScale(d[valueColumn]) - yScale(0);
                  }
  
                  return yScale(0) - yScale(d[valueColumn]);
              })
              .attr('class', function(d, i) {
                  return 'bar bar-' + i + ' ' + classify(config['data'][0]['party']);
              });
  
      /*
       * Render 0 value line.
       */
  //     chartElement.append('line')
  //         .attr('class', 'zero-line')
  //         .attr('x1', 0)
  //         .attr('x2', chartWidth)
  //         .attr('y1', yScale(0))
  //         .attr('y2', yScale(0));
  
      /*
       * Render bar values.
       */
      chartElement.append('g')
          .attr('class', 'value')
          .selectAll('text')
          .data(config['data'])
          .enter()
          .append('text')
              .text(function(d) {
                  return d[valueColumn].toFixed(0);
              })
              .attr('x', function(d, i) {
                  return xScale(d[labelColumn]) + (xScale.rangeBand() / 2);
              })
              .attr('y', function(d) {
                  return yScale(d[valueColumn]);
              })
              .attr('dy', function(d) {
                  var textHeight = d3.select(this).node().getBBox().height;
                  var barHeight = 0;
  
                  if (d[valueColumn] < 0) {
                      barHeight = yScale(d[valueColumn]) - yScale(0);
  
                      if (textHeight + valueGap * 2 < barHeight) {
                          d3.select(this).classed('in', true);
                          return -(textHeight - valueGap / 2);
                      } else {
                          d3.select(this).classed('out', true)
                          return textHeight + valueGap;
                      }
                  } else {
                      barHeight = yScale(0) - yScale(d[valueColumn]);
  
                      if (textHeight + valueGap * 2 < barHeight) {
                          d3.select(this).classed('in', true)
                          return textHeight + valueGap;
                      } else {
                          d3.select(this).classed('out', true)
                          return -(textHeight - valueGap / 2);
                      }
                  }
              })
              .attr('text-anchor', 'middle')
  }
  
  /*
   * Initially load the graphic
   * (NB: Use window.load to ensure all images have loaded)
   */
  window.onload = onWindowLoaded;
  