/**
 * --------------------------------------------------------------------
 * jQuery-Plugin "visualize"
 * by Scott Jehl, scott@filamentgroup.com
 * http://www.filamentgroup.com
 * Copyright (c) 2009 Filament Group 
 * Dual licensed under the MIT (filamentgroup.com/examples/mit-license.txt) and GPL (filamentgroup.com/examples/gpl-license.txt) licenses.
 *   
 * --------------------------------------------------------------------
 */
(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
      colors: ['#be1e2d','#666699','#92d5ea','#ee8310','#8d10ee','#5a3b16','#26a4ed','#f45a90','#e9e744'],
      textColors: [], //corresponds with colors array. null/undefined items will fall back to CSS
      parseDirection: 'x', //which direction to parse the table data
      pieMargin: 10, //pie charts only - spacing around pie
      pieLabelsAsPercent: true,
      pieLabelPos: 'inside',
      lineWeight: 4, //for line and area - stroke weight
      lineDots: false, //also available: 'single', 'double'
      dotInnerColor: "#ffffff", // only used for lineDots:'double'
      lineMargin: (options.lineDots?15:0), //for line and area - spacing around lines
      barGroupMargin: 10,
      chartId: '',
      xLabelParser: null, // function to parse labels as values
      valueParser: null, // function to parse values. must return a Number
      chartId: '',
      chartClass: '',
      barMargin: 1, //space around bars in bar chart (added to both sides of bar)
      yLabelInterval: 30, //distance between y labels
      interaction: false // only used for lineDots != false -- triggers mouseover and mouseout on original table
    },options);
    
    //reset width, height to numbers
    o.width = parseFloat(o.width);
    o.height = parseFloat(o.height);
    
    // reset padding if graph is not lines
    if(o.type != 'line' && o.type != 'area' ) {
      o.lineMargin = 0;
    }
    
    var self = $(this);
    
    // scrape data from html table
    var tableData = {};
    var colors = o.colors;
    var textColors = o.textColors;
    
    
    var parseLabels = function(direction){
      var labels = [];
      if(direction == 'x'){
        self.find('thead tr').each(function(i){
          $(this).find('th').each(function(j){
            if(!labels[j]) {
              labels[j] = [];
            }
            labels[j][i] = $(this).text()
          })
        });
      }
      else {
        self.find('tbody tr').each(function(i){
          $(this).find('th').each(function(j) {
            if(!labels[i]) {
              labels[i] = [];
            }
            labels[i][j] = $(this).text()
          });
        });
      }
      return labels;
    };
    
    var fnParse = o.valueParser || parseFloat;
    var dataGroups = tableData.dataGroups = [];
    if(o.parseDirection == 'x'){
      self.find('tbody tr').each(function(i,tr){
        dataGroups[i] = {};
        dataGroups[i].points = [];
        dataGroups[i].color = colors[i];
        if(textColors[i]){ dataGroups[i].textColor = textColors[i]; }
        $(tr).find('td').each(function(j,td){
          dataGroups[i].points.push( {
            value: fnParse($(td).text()),
            elem: td,
            tableCords: [i,j]
          } );
        });
      });
    } else {
      var cols = self.find('tbody tr:eq(0) td').size();
      for(var i=0; i<cols; i++){
        dataGroups[i] = {};
        dataGroups[i].points = [];
        dataGroups[i].color = colors[i];
        if(textColors[i]){ dataGroups[i].textColor = textColors[i]; }
        self.find('tbody tr').each(function(j){
          dataGroups[i].points.push( {
            value:  $(this).find('td').eq(i).text()*1,
            elem: this,
            tableCords: [i,j]
          } );
        });
      };
    }
    
  
    var allItems = tableData.allItems = [];
    $(dataGroups).each(function(i,row){
      var count = 0;
      $.each(row.points,function(j,point){
        allItems.push(point);
        count += point.value;
      });
      row.groupTotal = count;
    });
    
    tableData.dataSum = 0;
    tableData.topValue = 0;
    tableData.bottomValue = Infinity;
    $.each(allItems,function(i,item){
      tableData.dataSum += fnParse(item.value);
      if(fnParse(item.value,10)>tableData.topValue) {
        tableData.topValue = fnParse(item.value,10);
      }
      if(item.value<tableData.bottomValue) {
        tableData.bottomValue = fnParse(item.value);  
      }
    });
    var dataSum = tableData.dataSum;
    var topValue = tableData.topValue;
    var bottomValue = tableData.bottomValue;
    
    var xAllLabels = tableData.xAllLabels = parseLabels(o.parseDirection);
    var yAllLabels = tableData.yAllLabels = parseLabels(o.parseDirection==='x'?'y':'x');
  
    var xLabels = tableData.xLabels = [];
    $.each(tableData.xAllLabels,function(i,labels) {
      tableData.xLabels.push(labels[0]);
    });
    
    var totalYRange = tableData.totalYRange = tableData.topValue - tableData.bottomValue;
    
    var zeroLocX = tableData.zeroLocX = 0;
    
    if($.isFunction(o.xLabelParser)) {
      
      var xTopValue = null;
      var xBottomValue = null;

      $.each(xLabels,function(i,label) {
        label = xLabels[i] = o.xLabelParser(label);
        if(i === 0) {
          xTopValue = label;
          xBottomValue = label;
        }
        if(label>xTopValue) {
          xTopValue = label;
        }
        if(label<xBottomValue) {
          xBottomValue = label;
        }
      });

      var totalXRange = tableData.totalXRange = xTopValue - xBottomValue;

      
      var  xScale = tableData.xScale = (o.width -2*o.lineMargin) / totalXRange;
      var marginDiffX = 0;
      if(o.lineMargin) {
        var marginDiffX = -2*xScale-o.lineMargin;
      }
      zeroLocX = tableData.zeroLocX = xBottomValue + o.lineMargin;
      
      tableData.xBottomValue = xBottomValue;
      tableData.xTopValue = xTopValue;
      tableData.totalXRange = totalXRange;
    }
    
    var  yScale = tableData.yScale = (o.height - 2*o.lineMargin) / totalYRange;
    var zeroLocY = tableData.zeroLocY = (o.height-2*o.lineMargin) * (tableData.topValue/tableData.totalYRange) + o.lineMargin;
    
    var yLabels = tableData.yLabels = [];

    var numLabels = Math.floor((o.height - 2*o.lineMargin) / 30);
    var loopInterval = tableData.totalYRange / numLabels; //fix provided from lab
    loopInterval = Math.round(parseFloat(loopInterval)/5)*5;
    loopInterval = Math.max(loopInterval, 1);
    // var start = 
    for(var j=Math.round(parseInt(tableData.bottomValue)/5)*5; j<=tableData.topValue+loopInterval; j+=loopInterval){
      yLabels.push(j); 
    }
    if(yLabels[yLabels.length-1] > tableData.topValue+loopInterval) {
      yLabels.pop();
    } else if (yLabels[yLabels.length-1] <= tableData.topValue-10) {
      yLabels.push(tableData.topValue);
    }
        
    // populate some data
    $.each(dataGroups,function(i,row){
      row.yLabels = tableData.yAllLabels[i];
      $.each(row.points, function(j,point){
        point.zeroLocY = tableData.zeroLocY;
        point.zeroLocX = tableData.zeroLocX;
        point.xLabels = tableData.xAllLabels[j];
        point.yLabels = tableData.yAllLabels[i];
        point.color = row.color;
      });
    });
    
    try{console.log(tableData);}catch(e){}
    
    var charts = {};
    
    charts.pie = {
      interactionPoints: dataGroups,
      
      setup: function() {
        charts.pie.draw(true);
      },
      draw: function(drawHtml){  

        var centerx = Math.round(canvas.width()/2);
        var centery = Math.round(canvas.height()/2);
        var radius = centery - o.pieMargin;        
        var counter = 0.0;

        if(drawHtml) {
          canvasContain.addClass('visualize-pie');

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

          var toRad = function(integer){ return (Math.PI/180)*integer; };
          var labels = $('<ul class="visualize-labels"></ul>')
            .insertAfter(canvas);
        }


        //draw the pie pieces
        $.each(dataGroups, function(i,row){
          var fraction = row.groupTotal / dataSum;
                    if (fraction <= 0 || isNaN(fraction))
                        return;
          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();
              // draw labels
          if(drawHtml) {
                 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));

            // interaction variables
            row.canvasCords = [labelx,labely];
            row.zeroLocY = tableData.zeroLocY = 0; // related to zeroLocY and plugin API
            row.zeroLocX = tableData.zeroLocX = 0; // related to zeroLocX and plugin API
            row.value = row.groupTotal;


                if(percentage){
                  var labelval = (o.pieLabelsAsPercent) ? percentage + '%' : row.groupTotal;
                  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;
        });
      }
    };
    
    (function(){
      
      var xInterval;

      var drawPoint = function (ctx,x,y,color,size) {
        ctx.moveTo(x,y);
        ctx.beginPath();
        ctx.arc(x,y,size/2,0,2*Math.PI,false);
        ctx.closePath();
        ctx.fillStyle = color;
        ctx.fill();
      };

      charts.line = {
        
        interactionPoints: allItems,

        setup: function(area){

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

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

          if(!o.customXLabels) {
            xInterval = (canvas.width() - 2*o.lineMargin) / (xLabels.length -1);
            $.each(xLabels, function(i){ 
              var thisLi = $('<li><span>'+this+'</span></li>')
                .prepend('<span class="line" />')
                .css('left', o.lineMargin + xInterval * i)
                .appendTo(xlabelsUL);            
              var label = thisLi.find('span:not(.line)');
              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');
            });
          } else {
            o.customXLabels(tableData,xlabelsUL);
          }

          //write Y labels
          var liBottom = (canvas.height() - 2*o.lineMargin) / (yLabels.length-1);
          var ylabelsUL = $('<ul class="visualize-labels-y"></ul>')
            .width(canvas.width())
            .height(canvas.height())
            // .css('margin-top',-o.lineMargin)
            .insertBefore(scroller);

          $.each(yLabels, function(i){
            var value = Math.floor(this);
            var posB = (value-bottomValue)*yScale + o.lineMargin;
            if(posB >= o.height-1 || posB < 0) {
              return;
            }
            var thisLi = $('<li><span>'+value+'</span></li>')
              .css('bottom', posB);
            if(Math.abs(posB) < o.height-1) {
              thisLi.prepend('<span class="line"  />');
            }
            thisLi.prependTo(ylabelsUL);
            
            var label = thisLi.find('span:not(.line)');
            var topOffset = label.height()/-2;
            if(!o.lineMargin) {
              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(zeroLocX,zeroLocY);
          
          charts.line.draw(area);

        },
        
        draw: function(area) {
          // prevent drawing on top of previous draw
          ctx.clearRect(-zeroLocX,-zeroLocY,o.width,o.height);
          // Calculate each point properties before hand
          var integer;
          $.each(dataGroups,function(i,row){
            integer = o.lineMargin; // the current offset
            $.each(row.points, function(j,point){
              if(o.xLabelParser) {
                point.canvasCords = [(xLabels[j]-zeroLocX)*xScale - xBottomValue,-(point.value*yScale)];
              } else {
                point.canvasCords = [integer,-(point.value*yScale)];
              }
              
              if(o.lineDots) {
                point.dotSize = o.dotSize||o.lineWeight*Math.PI;
                point.dotInnerSize = o.dotInnerSize||o.lineWeight*Math.PI/2;
                if(o.lineDots == 'double') {
                  point.innerColor = o.dotInnerColor;
                }
              }
              integer+=xInterval;
            });
          });
          // fire custom event so we can enable rich interaction
          self.trigger('vizualizeBeforeDraw',{options:o,table:self,canvasContain:canvasContain,tableData:tableData});
          // draw lines and areas
          $.each(dataGroups,function(h){
            // Draw lines
            ctx.beginPath();
            ctx.lineWidth = o.lineWeight;
            ctx.lineJoin = 'round';
            $.each(this.points, function(g){
              var loc = this.canvasCords;
              if(g == 0) {
                ctx.moveTo(loc[0],loc[1]);
              }
              ctx.lineTo(loc[0],loc[1]);
            });
            ctx.strokeStyle = this.color;
            ctx.stroke();
            // Draw fills
            if(area){
              var integer = this.points[this.points.length-1].canvasCords[0];
              if (isFinite(integer))
                ctx.lineTo(integer,0);
              ctx.lineTo(o.lineMargin,0);
              ctx.closePath();
              ctx.fillStyle = this.color;
              ctx.globalAlpha = .3;
              ctx.fill();
              ctx.globalAlpha = 1.0;
            }
            else {ctx.closePath();}
          });
          // draw points
          if(o.lineDots) {
            $.each(dataGroups,function(h){
              $.each(this.points, function(g){
                drawPoint(ctx,this.canvasCords[0],this.canvasCords[1],this.color,this.dotSize);
                if(o.lineDots === 'double') {
                  drawPoint(ctx,this.canvasCords[0],this.canvasCords[1],this.innerColor,this.dotInnerSize);
                }
              });
            });
          }
          
        }
      };
    
    })();
    
    charts.area = {
      setup: function() {
        charts.line.setup(true);
      },
      draw: charts.line.draw
    };
    
    (function(){

      var horizontal,bottomLabels;

      charts.bar = {
        setup:function(){
          /**
           * We can draw horizontal or vertical bars depending on the
           * value of the 'barDirection' option (which may be 'vertical' or
           * 'horizontal').
           */

          horizontal = (o.barDirection == 'horizontal');

          canvasContain.addClass('visualize-bar');

          /**
           * Write labels along the bottom of the chart.  If we're drawing
           * horizontal bars, these will be the yLabels, otherwise they
           * will be the xLabels.  The positioning also varies slightly:
           * yLabels are values, hence they will span the whole width of
           * the canvas, whereas xLabels are supposed to line up with the
           * bars.
           */
          bottomLabels = horizontal ? yLabels : xLabels;

          var xInterval = canvas.width() / (bottomLabels.length - (horizontal ? 1 : 0));

          var xlabelsUL = $('<ul class="visualize-labels-x"></ul>')
            .width(canvas.width())
            .height(canvas.height())
            .insertBefore(canvas);

          $.each(bottomLabels, function(i){
            var thisLi = $('<li><span class="label">'+this+'</span></li>')
              .prepend('<span class="line" />')
              .css('left', xInterval * i)
              .width(xInterval)
              .appendTo(xlabelsUL);

            if (horizontal)  {
              var label = thisLi.find('span.label');
              label.css("margin-left", -label.width() / 2);
            }
          });

          /**
           * Write labels along the left of the chart.  Follows the same idea
           * as the bottom labels.
           */
          var leftLabels = horizontal ? xLabels : yLabels;
          var liBottom = canvas.height() / (leftLabels.length - (horizontal ? 0 : 1));

          var ylabelsUL = $('<ul class="visualize-labels-y"></ul>')
            .width(canvas.width())
            .height(canvas.height())
            .insertBefore(canvas);

          $.each(leftLabels, function(i){
            var thisLi = $('<li><span>'+this+'</span></li>').prependTo(ylabelsUL);

            var label = thisLi.find('span:not(.line)').addClass('label');

            if (horizontal) {
              /**
               * For left labels, we want to vertically align the text
               * to the middle of its container, but we don't know how
               * many lines of text we will have, since the labels could
               * be very long.
               *
               * So we set a min-height of liBottom, and a max-height
               * of liBottom + 1, so we can then check the label's actual
               * height to determine if it spans one line or more lines.
               */
              label.css({
                'min-height': liBottom,
                'max-height': liBottom + 1,
                'vertical-align': 'middle'
              });
              thisLi.css({'top': liBottom * i, 'min-height': liBottom});

              var r = label[0].getClientRects()[0];
              if (r.bottom - r.top == liBottom) {
                /* This means we have only one line of text; hence
                 * we can centre the text vertically by setting the line-height,
                 * as described at:
                 *   http://www.ampsoft.net/webdesign-l/vertical-aligned-nav-list.html
                 *
                 * (Although firefox has .height on the rectangle, IE doesn't,
                 * so we use r.bottom - r.top rather than r.height.)
                 */
                label.css('line-height', parseInt(liBottom) + 'px');
              }
              else {
                /*
                 * If there is more than one line of text, then we shouldn't
                 * touch the line height, but we should make sure the text
                 * doesn't overflow the container.
                 */
                label.css("overflow", "hidden");
              }
            }
            else {
              thisLi.css('bottom', liBottom * i).prepend('<span class="line" />');
              label.css('margin-top', -label.height() / 2)
            }
          });

          charts.bar.draw();

        },

        draw: function() {
          // Draw bars

          if (horizontal) {
            // for horizontal, keep the same code, but rotate everything 90 degrees
            // clockwise.
            ctx.rotate(Math.PI / 2);
          }
          else {
            // for vertical, translate to the top left corner.
            ctx.translate(0, zeroLocY);
          }

          // Don't attempt to draw anything if all the values are zero,
          // otherwise we will get weird exceptions from the canvas methods.
          if (totalYRange <= 0)
            return;

          var yScale = (horizontal ? canvas.width() : canvas.height()) / totalYRange;
          var barWidth = horizontal ? (canvas.height() / xLabels.length) : (canvas.width() / (bottomLabels.length));
          var linewidth = (barWidth - o.barGroupMargin*2) / dataGroups.length;

          for(var h=0; h<dataGroups.length; h++){
            ctx.beginPath();

            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++){
              // If the last value is zero, IE will go nuts and not draw anything,
              // so don't try to draw zero values at all.
              if (points[i].value != 0) {
                var xVal = (integer-o.barGroupMargin)+(h*linewidth)+linewidth/2;
                xVal += o.barGroupMargin*2;

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

        }
      };
      
    })();
  
    //create new canvas, set w&h attrs (not inline styles)
    var canvasNode = document.createElement("canvas"); 
    var canvas = $(canvasNode)
      .attr({
        'height': o.height,
        'width': o.width
      });
    
    //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 '+(o.chartId?'id="'+o.chartId+'" ':'')+'class="visualize '+o.chartClass+'" role="img" aria-label="Chart representing data from the table: '+ title +'" />'))
      .height(o.height)
      .width(o.width);
    
    var scroller = $('<div class="visualize-scroller"></div>')
      .appendTo(canvasContain)
      .append(canvas);

    //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>');
      $.each(yAllLabels, function(i,label){
        $('<li><span class="visualize-key-color" style="background: '+dataGroups[i].color+'"></span><span class="visualize-key-label">'+ label +'</span></li>')
          .appendTo(newKey);
      });
      newKey.appendTo(infoContain);
    };    
    
    // init interaction
    if(o.interaction) {
      // sets the canvas to track interaction
      // IE needs one div on top of the canvas since the VML shapes prevent mousemove from triggering correctly.
      // Pie charts needs tracker because labels goes on top of the canvas and also messes up with mousemove
      var tracker = $('<div class="visualize-interaction-tracker"/>')
        .css({
          'height': o.height + 'px',
          'width': o.width + 'px',
          'position':'relative',
          'z-index': 200
        })
        .insertAfter(canvas);

      var triggerInteraction = function(overOut,data) {
        var data = $.extend({
          canvasContain:canvasContain,
          tableData:tableData
        },data);
        self.trigger('vizualize'+overOut,data);
      };

      var over=false, last=false, started=false;
      tracker.mousemove(function(e){
        var x,y,x1,y1,data,dist,i,current,selector,zLabel,elem,color,minDist,found,ev=e.originalEvent;

        // get mouse position relative to the tracker/canvas
        x = ev.layerX || ev.offsetX || 0;
        y = ev.layerY || ev.offsetY || 0;

        found = false;
        minDist = started?30000:(o.type=='pie'?(Math.round(canvas.height()/2)-o.pieMargin)/3:o.lineWeight*4);
        // iterate datagroups to find points with matching
        $.each(charts[o.type].interactionPoints,function(i,current){
          x1 = current.canvasCords[0] + zeroLocX;
          y1 = current.canvasCords[1] + (o.type=="pie"?0:zeroLocY);
          dist = Math.sqrt( (x1 - x)*(x1 - x) + (y1 - y)*(y1 - y) );
          if(dist < minDist) {
            found = current;
            minDist = dist;
          }
        });
        
        if(o.multiHover && found) {
          x = found.canvasCords[0] + zeroLocX;
          y = found.canvasCords[1] + (o.type=="pie"?0:zeroLocY);
          found = [found];
          $.each(charts[o.type].interactionPoints,function(i,current){
            if(current == found[0]) {return;}
            x1 = current.canvasCords[0] + zeroLocX;
            y1 = current.canvasCords[1] + zeroLocY;
            dist = Math.sqrt( (x1 - x)*(x1 - x) + (y1 - y)*(y1 - y) );
            if(dist <= o.multiHover) {
              found.push(current);
            }
          });
        }
        // trigger over and out only when state changes, instead of on every mousemove
        over = found;
        if(over != last) {
          if(over) {
            if(last) {
              triggerInteraction('Out',{point:last});
            }
            triggerInteraction('Over',{point:over});
            last = over;
          }
          if(last && !over) {
            triggerInteraction('Out',{point:last});
            last=false;
          }
          started=true;
        }
      });
      tracker.mouseleave(function(){
        triggerInteraction('Out',{
          point:last,
          mouseOutGraph:true
        });
        over = (last = false);
      });
    }
    
    //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');
    
    // Scroll graphs
    scroller.scrollLeft(o.width-scroller.width());

    // init plugins
    $.each($.visualizePlugins,function(i,plugin){
      plugin.call(self,o,tableData);
    });

    //create chart
    charts[o.type].setup();
    
    if(!container){
      //add event for updating
      self.bind('visualizeRefresh', function(){
        self.visualize(o, $(this).empty()); 
      });
      //add event for redraw
      self.bind('visualizeRedraw', function(){
        charts[o.type].draw();
      });
    }
  }).next(); //returns canvas(es)
};
// create array for plugins. if you wish to make a plugin,
// just push your init funcion into this array
$.visualizePlugins = [];

})(jQuery);


