text: //
  // This is a plugin for drawing [population pyramids](http://en.wikipedia.org/wiki/Population_pyramid) with
  // [flot](http://code.google.com/p/flot/).
  //
  
  // Ok, let's wrap everything in a warm, secure closure.
  var FlotPyramid = (function(){
  
    // Some declarations follow:
  
    // An object which will be thrown when the data to plot
    // is invalid.
    var InvalidData = {
      plugin: 'flot.pyramid',
      msg: 'Invalid series for pyramid plot! The supplied data must have exactly the same labels!'
    },
  
    // And another one which will be thrown when any of the data series
    // directions specified by the user has a wrong value.
    InvalidDirection = {
      plugin: 'flot.pyramid',
      msg: 'Invalid direction specified for pyramid series. Use \'L\' or \'W\' for left, or \'R\' or \'E\' for right (default is right)'
    }
  
    // ## Flot plumbing
  
    // Hooks the pyramid plugin options processor when _flot_ initializes the
    // _plot_.
    function init(plot) {
      plot.hooks.processOptions.push(processOptions);
    }
  
    // Processes the plot options.
    function processOptions(plot, options) {
      plot.pyramidYaxisTicks = [];
  
      // When the pyramid plugin is active (i.e. shown)
      if (options.series.pyramid && options.series.pyramid.show) {
        // Configure _flot_ options in order to plot the data using _bars_,
        // extending any user defined _bars_ options to get _hotizontal_,
        // _centered_ bars. If the user supplies a custom barWidth, use it.
        $.extend(options.series.bars, {
          show: true,
          horizontal: true,
          align: 'center',
          barWidth: options.series.pyramid.barWidth || 0.6
        });
  
        var xaxis = options.xaxes[options.series.xaxis - 1 || 0];
  
        // Configure the custom pyramid X axis tick formatter, preserving the
        // user defined one, if any.
        $.extend(xaxis, {
          tickFormatter: xaxisTickFormatter(xaxis.tickFormatter)
        });
  
        var yaxis = options.yaxes[options.series.yaxis - 1 || 0];
  
        // Use custom Y axis ticks.
        $.extend(yaxis, {
          ticks: plot.pyramidYaxisTicks
        });
  
        // Register the pyramid raw data processor...
        plot.hooks.processRawData.push(processRawData);
        // ... and the pyramid data points processor.
        plot.hooks.processDatapoints.push(processDatapoints);
      }
    }
  
    // Processes the data _as-is_
    function processRawData(plot, series, datapoints) {
      // The pyramid plugin needs to change the data label values, so, in order
      // to preserve the original data, a deep clone is made
      series.data = $.extend(true, [], series.data);
      // Now the Y axis can be fixed using the series data.
      fixYaxis(series.data, plot.pyramidYaxisTicks);
      // And also the X axis.
      fixXaxis(plot.getOptions(), series);
    }
  
    // Processes the data points generated by _flot_.
    function processDatapoints(plot, series, datapoints) {
      var swapped = [],
        points = datapoints.points,
        // Calculate the _multiplier_ factor given the data plot direction
        // (_left_ or _west_ values must be drawn towards the negative part of
        // the X axis)
        mult = (series.pyramid.direction || 'R').match(/L|W/) ? -1 : 1;
  
      // For each data point, originally in the form _(X,Y,whatever)_, swap the X
      // and Y axis values, giving the X value the right direction using the
      // previously calculated _multiplier_.
      for(var i = 0, len = points.length; i < len; i += datapoints.format.length) {
        swapped.push(points[i+1] * mult);
        swapped.push(points[i]);
        swapped.push(points[i+2]);
      }
  
      // Complete the swap!
      datapoints.points = swapped;
    }
  
    // ## X axis formatting
  
    // Formats the X axis tick values
    function xaxisTickFormatter(oldFormatter) {
      return function(val, axis) {
        // turning negative values into positive ones.
        val = val < 0 ? -val: val;
        // Returns the formatted (abs) value, optionally formatted through the original
        // user-supplied formatter (that's a formatting party!).
        return oldFormatter ? oldFormatter(val, axis) : val;
      }
    }
  
    // Fixes the X axis values in the series.
    function fixXaxis(options, series) {
      var max,
          currentMax = options.xaxes[0].max || 0,
          data = series.data,
          values;
  
      // Check whether the series direction declaration is OK.
      checkSeriesDirection(series);
  
      // Define a helper function which reduces the given _data_ applying _f_.
      function reduce(data, f) {
        return data.reduce(function(prev, current, index, array) {
          return f(prev, current);
        });
      }
  
      // Get the maximum value in the series data.
      var values = $.map(data, function(d){return d[1]}),
  		max = reduce(values, Math.max);
  
  	
  	
      // Compare the maximum value to the global maximum for the axis.
      // TODO: replace that 0 with the relevant axis index
      options.xaxes[0].max = Math.max(max, currentMax);
      // Set the minimum to _-maximum_ in order to get a symmetrical chart.
      options.xaxes[0].min = -options.xaxes[0].max;
    }
  
    // Checks the series direction declaration, if present.
    function checkSeriesDirection(series) {
      var direction = series.pyramid.direction;
  
      // And if, indeed, it is present, and has an unknown value
      if (direction && !direction.match(/L|W|R|E/)) {
        // throw _InvalidDirection_.
        throw(InvalidDirection);
      }
    }
  
    // ## Y axis formatting
  
    // Fixes the Y axis values in the data and fixes the Y axis ticks values.
    function fixYaxis(data, yaxisTicks) {
      // If this is the first series to be processed (and therefore the
      // _yaxisTicks_ array is empty)
      if (yaxisTicks.length == 0) {
        // use it to extract the tick values. All the data labels (which will be
        // used as tick values) in all the plotted series must be exactly the
        // same,
        extractTicks(data, yaxisTicks);
      } else {
        // so, if the data labels have already been extracted, every other
        // series' data is checked.
        checkTicks(data, yaxisTicks);
      }
  
      // Once extracted (or checked) the labels, the data can be rewritten in
      // order to be plotted.
      rewriteTicks(data);
    }
  
    // Extracts the Y axis ticks given the data to be plotted,
    function extractTicks(data, yaxisTicks) {
      var i, len;
      for(i = 0, len = data.length; i < len; i += 1) {
        // extracting the data label and building an array in the form _[[1,
        // "0-10"], [2, "10-20"],..., [9, "90+"]]_
        yaxisTicks.push([i, data[i][0]]);
      }
    }
  
    // Checks whether the given data is "well formed",
    function checkTicks(data, yaxisTicks) {
      // i.e. it has so many data values as Y axis ticks, and every data value is
      // labeled with a label which is present in the Y axis ticks (labels) array. Wow.
      if (!sameTicksLength(data, yaxisTicks) || !allTicksPresent(data, yaxisTicks)) {
        // Oh, if something is wrong with the data, just throw _InvalidData_.
        throw(InvalidData);
      }
    }
  
    // Checks whether the given data has the same length as the Y axis extracted
    // ticks array.
    function sameTicksLength(data, yaxisTicks) {
      return yaxisTicks.length == data.length;
    }
  
    // Checks whether every tick in the Y axis has a corresponding labeled data
    // entry in the _data_ array.
    function allTicksPresent(data, yaxisTicks) {
      var labels, expected_labels;
  
      // Get the actual and expected labels
      expected_labels = $.map(yaxisTicks, function(e) { return e[1] });
      labels = $.map(data, function(e) { return e[0] });
  
      // And compare them.
      return expected_labels.toString() == labels.toString();
    }
  
    // Rewrites the data label values,
    function rewriteTicks(data) {
      // removing the label and replacing it with the data value ordinal.
      for (var i = 0, len = data.length; i < len; i+= 1) {
        data[i][0] = i;
      }
    }
  
  // ## Wrap it up!
  
  // Expose only the minimum stuff, i.e.
    return {
  // The init function...
      init: init,
  // And the InvalidDirection and InvalidData "exception" objects...
      InvalidDirection: InvalidDirection,
      InvalidData: InvalidData
    }
  }());
  
  // Once the document is ready, add the plugin to the list of available flot
  // plugins.
  (function ($) {
    $.plot.plugins.push({
        init: FlotPyramid.init,
        name: "pyramid",
        version: "1.0.1"
    });
  })(jQuery);
  