
/*
 * --------------------------------------------------------------------
 * jQuery inputToButton plugin
 * Author: Scott Jehl, scott@filamentgroup.com
 * Copyright (c) 2009 Filament Group
 * licensed under MIT (filamentgroup.com/examples/mit-license.txt)
 * --------------------------------------------------------------------
*/




function digit_grouping(nStr){
 nStr += '';
 x = nStr.split('.');
 x1 = x[0];
 x2 = x.length > 1 ? '.' + x[1] : '';
 var rgx = /(\d+)(\d{3})/;
 while (rgx.test(x1)) {
  x1 = x1.replace(rgx, '$1' + ',' + '$2');
 }
 return x1 + x2;
}

(function($) {
$.fn.visualize = function(options, container){
 return $(this).each(function(){
  //configuration
  var o = $.extend({
   type: 'bar', //also available: area, pie, line
   width: $(this).width(), //height of canvas - defaults to table height
   height: $(this).height(), //height of canvas - defaults to table height
   appendTitle: true, //table caption text is added to chart
   title: null, //grabs from table caption if null
   appendKey: true, //color key is added to chart
   rowFilter: ' ',
   colFilter: ' ',
   colors: ['#be1e2d','#666699','#92d5ea','#ee8310','#8d10ee','#5a3b16','#26a4ed','#f45a90','#e9e744'],
   areaColors: [],
   textColors: [], //corresponds with colors array. null/undefined items will fall back to CSS
   parseDirection: 'x', //which direction to parse the table data
   pieMargin: 20, //pie charts only - spacing around pie
   pieLabelsAsPercent: true,
   pieLabelPos: 'inside',
   lineBullets : false,
   bulletFill : "#ffffff",
   bulletWeight : 4,
   bulletSize : 3,
   lineWeight: 4, //for line and area - stroke weight
   barGroupMargin: 10,
   barMargin: 1, //space around bars in bar chart (added to both sides of bar)
   yLabelInterval: 30 //distance between y labels
  },options);

  //reset width, height to numbers
  o.width = parseFloat(o.width);
  o.height = parseFloat(o.height);


  var self = $(this);

  //function to scrape data from html table
  function scrapeTable(){
   var colors = o.colors;
   var areaColors = o.areaColors;
   var textColors = o.textColors;
   var tableData = {
    dataGroups: function(){
     var dataGroups = [];
     if(o.parseDirection == 'x'){
      self.find('tr:gt(0)').filter(o.rowFilter).each(function(i){
       dataGroups[i] = {};
       dataGroups[i].points = [];
       dataGroups[i].color = colors[i];
       if(areaColors[i]){ dataGroups[i].areaColor = areaColors[i]; }
       if(textColors[i]){ dataGroups[i].textColor = textColors[i]; }
       $(this).find('td').filter(o.colFilter).each(function(){
        dataGroups[i].points.push( parseFloat($(this).text()) );
       });
      });
     }
     else {
      var cols = self.find('tr:eq(1) td').filter(o.colFilter).size();
      for(var i=0; i<cols; i++){
       dataGroups[i] = {};
       dataGroups[i].points = [];
       dataGroups[i].color = colors[i];
       if(areaColors[i]){ dataGroups[i].areaColor = areaColors[i]; }
       if(textColors[i]){ dataGroups[i].textColor = textColors[i]; }
       self.find('tr:gt(0)').filter(o.rowFilter).each(function(){
        dataGroups[i].points.push( $(this).find('td').filter(o.colFilter).eq(i).text()*1 );
       });
      };
     }
     return dataGroups;
    },
    allData: function(){
     var allData = [];
     $(this.dataGroups()).each(function(){
      allData.push(this.points);
     });
     return allData;
    },
    dataSum: function(){
     var dataSum = 0;
     var allData = this.allData().join(',').split(',');
     $(allData).each(function(){
      dataSum += parseFloat(this);
     });
     return dataSum
    },
    topValue: function(){
     if (o.topValue===false) {
      var topValue = 0;
      var allData = this.allData().join(',').split(',');
      $(allData).each(function(){
       if(parseFloat(this,10)>topValue) topValue = parseFloat(this);
      });
      return topValue;
     }
     else {
      return typeof o.topValue === "function" ? o.topValue() : o.topValue;
     }
    },
    bottomValue: function(){
     if (o.bottomValue===false) {
      var bottomValue = 0;
      var allData = this.allData().join(',').split(',');
      $(allData).each(function(){
       if(this<bottomValue) bottomValue = parseFloat(this);
      });
      return bottomValue;
     }
     else {
      return typeof o.bottomValue === "function" ? o.bottomValue() : o.bottomValue;
     }
    },
    memberTotals: function(){
     var memberTotals = [];
     var dataGroups = this.dataGroups();
     $(dataGroups).each(function(l){
      var count = 0;
      $(dataGroups[l].points).each(function(m){
       count +=dataGroups[l].points[m];
      });
      memberTotals.push(count);
     });
     return memberTotals;
    },
    yTotals: function(){
     var yTotals = [];
     var dataGroups = this.dataGroups();
     var loopLength = this.xLabels().length;
     for(var i = 0; i<loopLength; i++){
      yTotals[i] =[];
      var thisTotal = 0;
      $(dataGroups).each(function(l){
       yTotals[i].push(this.points[i]);
      });
      yTotals[i].join(',').split(',');
      $(yTotals[i]).each(function(){
       thisTotal += parseFloat(this);
      });
      yTotals[i] = thisTotal;

     }
     return yTotals;
    },
    topYtotal: function(){
     var topYtotal = 0;
      var yTotals = this.yTotals().join(',').split(',');
      $(yTotals).each(function(){
       if(parseFloat(this,10)>topYtotal) topYtotal = parseFloat(this);
      });
      return topYtotal;
    },
    totalYRange: function(){
     return this.topValue() - this.bottomValue();
    },
    xLabels: function(){
     var xLabels = [];
     if(o.parseDirection == 'x'){
      self.find('tr:eq(0) th').filter(o.colFilter).each(function(){
       xLabels.push($(this).html());
      });
     }
     else {
      self.find('tr:gt(0) th').filter(o.rowFilter).each(function(){
       xLabels.push($(this).html());
      });
     }
     return xLabels;
    },
    yLabels: function(){
     var yLabels = [];
     yLabels.push(bottomValue);
     var numLabels = Math.round(o.height / o.yLabelInterval);
     var loopInterval = Math.ceil(totalYRange / numLabels) || 1;
     while( yLabels[yLabels.length-1] < topValue - loopInterval){
      yLabels.push(yLabels[yLabels.length-1] + loopInterval);
     }
     yLabels.push(topValue);
     for(var i = 0; i < yLabels.length; i++){
      yLabels[i] = digit_grouping(yLabels[i]);
     }
     return yLabels;
    }
   };

   return tableData;
  };


  //function to create a chart
  var createChart = {
   pie: function(){

    canvasContain.addClass('visualize-pie');

    if(o.pieLabelPos == 'outside'){ canvasContain.addClass('visualize-pie-outside'); }

    var centerx = Math.round(canvas.width()/2);
    var centery = Math.round(canvas.height()/2);
    var radius = centery - o.pieMargin;
    var counter = 0.0;
    var toRad = function(integer){ return (Math.PI/180)*integer; };
    var labels = $('<ul class="visualize-labels"></ul>')
     .insertAfter(canvas);

    //draw the pie pieces
    $.each(memberTotals, function(i){
     var fraction = (this <= 0 || isNaN(this))? 0 : this / dataSum;
     ctx.beginPath();
     ctx.moveTo(centerx, centery);
     ctx.arc(centerx, centery, radius,
      counter * Math.PI * 2 - Math.PI * 0.5,
      (counter + fraction) * Math.PI * 2 - Math.PI * 0.5,
                  false);
           ctx.lineTo(centerx, centery);
           ctx.closePath();
           ctx.fillStyle = dataGroups[i].color;
           ctx.fill();

     ctx.lineWidth = o.bulletWeight;
     ctx.strokeStyle = o.pieStrokeColor;
     ctx.stroke();

           // draw labels
           var sliceMiddle = (counter + fraction/2);
           var distance = o.pieLabelPos == 'inside' ? radius/1.5 : radius +  radius / 5;
           var labelx = Math.round(centerx + Math.sin(sliceMiddle * Math.PI * 2) * (distance));
           var labely = Math.round(centery - Math.cos(sliceMiddle * Math.PI * 2) * (distance));
           var leftRight = (labelx > centerx) ? 'right' : 'left';
           var topBottom = (labely > centery) ? 'bottom' : 'top';
           var percentage = parseFloat((fraction*100).toFixed(2));

           if(percentage){
            var labelval = (o.pieLabelsAsPercent) ? percentage + '%' : this;
            var labeltext = $('<span class="visualize-label">' + labelval +'</span>')
             .css(leftRight, 0)
             .css(topBottom, 0);
             if(labeltext)
            var label = $('<li class="visualize-label-pos"></li>')
              .appendTo(labels)
              .css({left: labelx, top: labely})
              .append(labeltext);
            labeltext
             .css('font-size', radius / 8)
             .css('margin-'+leftRight, -labeltext.width()/2)
             .css('margin-'+topBottom, -labeltext.outerHeight()/2);

            if(dataGroups[i].textColor){ labeltext.css('color', dataGroups[i].textColor); }
           }
          counter+=fraction;
    });

    ctx.beginPath();
    ctx.moveTo(centerx, centery);
    ctx.arc(centerx, centery, radius, 0, Math.PI * 2);
    ctx.closePath();

    var gradient = ctx.createLinearGradient(0, 0, centerx * 2, centery * 2);
    gradient.addColorStop(0, "rgba(0, 0, 0, 0)");
    gradient.addColorStop(0.5, "rgba(0, 0, 0, 0)");
    gradient.addColorStop(1, "rgba(0, 0, 0, 0.8)");

    ctx.fillStyle = gradient;
    ctx.fill();
   },

   line: function(area){

    if(area){ canvasContain.addClass('visualize-area'); }
    else{ canvasContain.addClass('visualize-line'); }

    //write X labels
    var xInterval = canvas.width() / (xLabels.length -1);
    var xlabelsUL = $('<ul class="visualize-labels-x"></ul>')
     .width(canvas.width())
     .height(canvas.height())
     .insertBefore(canvas);

    var i, j, labelText,
     xDubInterval = canvas.width() / xLabels.length;

    for (i = 0, j = xLabels.length * 2; i < j; i++) {
     labelText = xLabels[i];

     var thisLi = $('<li><span></span></li>')
      .prepend('<span class="line" />')
      .css('left', (xDubInterval * 0.5) * i)
      .appendTo(xlabelsUL);

     if ((i % 2)) {
      var label = thisLi.find('span:not(.line)');
      label.text(xLabels[(i - 1) / 2]);
      var leftOffset = label.width()/-2;
      if(i == 0){ leftOffset = 0; }
      else if(i== xLabels.length-1){ leftOffset = -label.width(); }
      label
       .css('margin-left', leftOffset)
       .addClass('label');
     }
    }

    //write Y labels
    var yScale = canvas.height() / totalYRange;
    var liBottom = canvas.height() / (yLabels.length-1);
    var ylabelsUL = $('<ul class="visualize-labels-y"></ul>')
     .width(canvas.width())
     .height(canvas.height())
     .insertBefore(canvas);

    $.each(yLabels, function(i){
     var thisLi = $('<li><span>'+this+'</span></li>')
      .prepend('<span class="line"  />')
      .css('bottom',liBottom*i)
      .prependTo(ylabelsUL);
     var label = thisLi.find('span:not(.line)');
     var topOffset = label.height()/-2;
     if(i == 0){ topOffset = -label.height(); }
     else if(i== yLabels.length-1){ topOffset = 0; }
     label
      .css('margin-top', topOffset)
      .addClass('label');
    });

    //start from the bottom left
    ctx.translate(0,zeroLoc);
    //iterate and draw
    $.each(dataGroups,function(h){
     ctx.beginPath();
     ctx.lineWidth = o.lineWeight;
     ctx.lineJoin = 'round';
     var points = this.points;
     var integer = 0;
     ctx.moveTo(0,-(points[0]*yScale));
     $.each(points, function(){
      ctx.lineTo(integer,-(this * yScale));
      integer += xInterval;
     });
     ctx.strokeStyle = this.color;
     ctx.stroke();
     if(area){
      ctx.lineTo(integer,0);
      ctx.lineTo(0,0);
      ctx.closePath();
      ctx.fillStyle = this.areaColor || this.color;
      ctx.globalAlpha = 0.5;
      ctx.fill();
      ctx.globalAlpha = 1.0;
     }
     else {ctx.closePath();}

     if (o.lineBullets) {
      integer = 0;

      ctx.lineWidth = o.bulletWeight;
      ctx.strokeStyle = this.color;
      ctx.fillStyle = o.bulletFill;

      $.each(points, function () {
       ctx.beginPath();

       ctx.arc(integer, -(this * yScale), o.bulletSize, 0, Math.PI * 2);
       ctx.closePath();

       ctx.stroke();
       ctx.fill();

       integer += xInterval;
      });
     }
    });
   },

   area: function(){
    createChart.line(true);
   },

   bar: function(){

    canvasContain.addClass('visualize-bar');

    //write X labels
    var xInterval = canvas.width() / (xLabels.length);
    var xlabelsUL = $('<ul class="visualize-labels-x"></ul>')
     .width(canvas.width())
     .height(canvas.height())
     .insertBefore(canvas);
    $.each(xLabels, function(i){
     var thisLi = $('<li><span class="label">'+this+'</span></li>')
      .prepend('<span class="line" />')
      .css('left', xInterval * i)
      .width(xInterval)
      .appendTo(xlabelsUL);
     var label = thisLi.find('span.label');
     label.addClass('label');
    });

    //write Y labels
    var yScale = canvas.height() / totalYRange;
    var liBottom = canvas.height() / (yLabels.length-1);
    var ylabelsUL = $('<ul class="visualize-labels-y"></ul>')
     .width(canvas.width())
     .height(canvas.height())
     .insertBefore(canvas);
    $.each(yLabels, function(i){
     var thisLi = $('<li><span>'+this+'</span></li>')
      .prepend('<span class="line"  />')
      .css('bottom',liBottom*i)
      .prependTo(ylabelsUL);
      var label = thisLi.find('span:not(.line)');
      var topOffset = label.height()/-2;
      if(i == 0){ topOffset = -label.height(); }
      else if(i== yLabels.length-1){ topOffset = 0; }
      label
       .css('margin-top', topOffset)
       .addClass('label');
    });

    //start from the bottom left
    ctx.translate(0,zeroLoc);
    //iterate and draw
    for(var h=0; h<dataGroups.length; h++){
     ctx.beginPath();
     var linewidth = (xInterval-o.barGroupMargin*2) / dataGroups.length; //removed +1
     var strokeWidth = linewidth - (o.barMargin*2);
     ctx.lineWidth = strokeWidth;
     var points = dataGroups[h].points;
     var integer = 0;
     for(var i=0; i<points.length; i++){
      var xVal = (integer-o.barGroupMargin)+(h*linewidth)+linewidth/2;
      xVal += o.barGroupMargin*2;

      ctx.moveTo(xVal, 0);
      ctx.lineTo(xVal, Math.round(-points[i]*yScale));
      integer+=xInterval;
     }
     ctx.strokeStyle = dataGroups[h].color;
     ctx.stroke();
     ctx.closePath();
    }
   }
  };

  //create new canvas, set w&h attrs (not inline styles)
  var canvasNode = document.createElement("canvas");
  canvasNode.setAttribute('height',o.height);
  canvasNode.setAttribute('width',o.width);
  var canvas = $(canvasNode);

  //get title for chart
  var title = o.title || self.find('caption').text();

  //create canvas wrapper div, set inline w&h, append
  var canvasContain = (container || $('<div class="visualize" role="img" aria-label="Chart representing data from the table: '+ title +'" />'))
   .height(o.height)
   .width(o.width)
   .append(canvas);

  //scrape table (this should be cleaned up into an obj)
  var tableData = scrapeTable();
  var dataGroups = tableData.dataGroups();
  var allData = tableData.allData();
  var dataSum = tableData.dataSum();
  var topValue = tableData.topValue();
  var bottomValue = tableData.bottomValue();
  var memberTotals = tableData.memberTotals();
  var totalYRange = tableData.totalYRange();
  var zeroLoc = o.height * (topValue/totalYRange);
  var xLabels = tableData.xLabels();
  var yLabels = tableData.yLabels();

  //title/key container
  if(o.appendTitle || o.appendKey){
   var infoContain = $('<div class="visualize-info"></div>')
    .appendTo(canvasContain);
  }

  //append title
  if(o.appendTitle){
   $('<div class="visualize-title">'+ title +'</div>').appendTo(infoContain);
  }


  //append key
  if(o.appendKey){
   var newKey = $('<ul class="visualize-key"></ul>');
   var selector;
   if(o.parseDirection == 'x'){
    selector = self.find('tr:gt(0) th').filter(o.rowFilter);
   }
   else{
    selector = self.find('tr:eq(0) th').filter(o.colFilter);
   }

   selector.each(function(i){
    $('<li><span class="visualize-key-color" style="background: '+dataGroups[i].color+'"></span><span class="visualize-key-label">'+ $(this).text() +'</span></li>')
     .appendTo(newKey);
   });
   newKey.appendTo(infoContain);
  };

  //append new canvas to page

  if(!container){canvasContain.insertAfter(this); }
  if( typeof(G_vmlCanvasManager) != 'undefined' ){ G_vmlCanvasManager.init(); G_vmlCanvasManager.initElement(canvas[0]); }

  //set up the drawing board
  var ctx = canvas[0].getContext('2d');

  //create chart
  createChart[o.type]();

  //clean up some doubled lines that sit on top of canvas borders (done via JS due to IE)
  // $('.visualize-line li:first-child span.line, .visualize-line li:last-child span.line, .visualize-area li:first-child span.line, .visualize-area li:last-child span.line, .visualize-bar li:first-child span.line,.visualize-bar .visualize-labels-y li:last-child span.line').css('border','none');
  if(!container){
  //add event for updating
  canvasContain.bind('visualizeRefresh', function(){
   self.visualize(o, $(this).empty());
  });
  }
 }).next(); //returns canvas(es)
};
})(jQuery);



