var uv = (function (){
  /**
  * uv is the local namespace within the anonymous function, which holds everything else related to the library
  * @type {Object}
  */
  var uv = {};

uv.util = {};

/**
* Utility method to extend prototype for JavaScript classes, to act like inheritance
* @param  {Class} f Original class which is being extended
* @return {Prototype}   Prototype containing the functions from the super class
*/
uv.util.inherits = function (f) {
  function G() {}
  G.prototype = f.prototype || f;
  return new G();
};

/**
* Utility method to return a unique identification id
* @return {number} Timestamp in ms is returned as a unique id
*/
uv.util.getUniqueId = function () {
  return new Date().getTime();
};

uv.util.getMax = function (graphdef, classification) {
  switch (classification) {
    case 'stepup':
      return this.getStepMaxValue(graphdef);
    case 'normal':
      return this.getMaxValue(graphdef);
    case 'percent':
      return 100;
    case 'waterfall':
      return this.getWaterfallMaxValue(graphdef);
    default:
      console.error("Unknown classification for chart", classification);
  }
}

/**
*
*/
uv.util.getMaxValue = function (graphdef) {
  return d3.max(graphdef.categories.map(function (d) {
    return d3.max(graphdef.dataset[d].map(function (d) {
      return d.value;
    }));
  }));
};

uv.util.getStepMaxValue = function (graphdef) {
  var sumMap = graphdef.dataset[graphdef.categories[0]].map(function () {return 0; }),
    maxMap = sumMap.map(function () { return 0; });

  graphdef.categories.map(function (d) {
    graphdef.dataset[d].map(function (d, i) {
      sumMap[i] += d.value;
      maxMap[i] = d3.max([sumMap[i], maxMap[i]]);
    });
  });

  return d3.max(maxMap);
};

uv.util.getWaterfallMaxValue = function(graphdef) {
  var sumMap = graphdef.categories.map(function() {return 0;});
  graphdef.categories.map(function (d, i) {
    var localMax = 0;
    graphdef.dataset[d].map(function(d) {
      localMax += d.value;
      if(sumMap[i] < localMax) {
        sumMap[i] = localMax;
      }
    });
  });

  return d3.max(sumMap);
};

uv.util.getMin = function (graphdef, classification) {
  switch (classification) {
    case 'normal':
      return this.getMinValue(graphdef);
    case 'stepup':
      return this.getStepMinValue(graphdef);
    default:
      return 0;
  }
}

/**
*
*/
uv.util.getMinValue = function (graphdef) {
  return d3.min([0, d3.min(graphdef.categories.map(function (d) {
      return d3.min(graphdef.dataset[d].map(function (d) {
        return d.value;
      }));
    }))
  ]);
};

uv.util.getStepMinValue = function (graphdef) {
  var sumMap = graphdef.dataset[graphdef.categories[0]].map(function () {return 0; }),
    minMap = sumMap.map(function () { return 0; });

  graphdef.categories.map(function (d) {
    graphdef.dataset[d].map(function (d, i) {
      sumMap[i] += d.value;
      minMap[i] = d3.min([sumMap[i], minMap[i]]);
    });
  });

  return d3.min(minMap);
}

uv.util.getSumUpArray = function (graphdef) {
  var sumMap = graphdef.dataset[graphdef.categories[0]].map(function () {return 0; });
  graphdef.categories.map(function (d) {
    graphdef.dataset[d].map(function (d, i) {
      sumMap[i] += d.value;
    });
  });

  return sumMap;
};

uv.util.getPercentage = function (value, total) {
  return value* 100 / total;
};

uv.util.getDataArray = function (graphdef) {
  return graphdef.categories.map(function (d) { return graphdef.dataset[d]; });
};

uv.util.getTabularArray = function (graphdef) {
  var table = [], i, j, catlen, len, arr = [];
  for (i = 0, len = graphdef.dataset[graphdef.categories[0]].length; i < len; i = i + 1) {
    arr = [];
    arr.push(graphdef.dataset[graphdef.categories[0]][i].name);
    for (j = 0, catlen = graphdef.categories.length; j < catlen; j = j + 1) {
      arr.push(graphdef.dataset[graphdef.categories[j]][i].value);
    }
    table.push(arr);
  }
  return table;
};

uv.util.getLabelArray = function (graphdef, idx) {
  return graphdef.dataset[graphdef.categories[idx ? idx : 0]].map(function (d) { return d.name; });
};

uv.util.getCategoryArray = function (graphdef) {
  return graphdef.categories.map(function (d) { return d; });
};

uv.util.getCategoryData = function (graphdef, categories) {
  return categories.map(function (d) {
    return graphdef.dataset[d].map(function (d) {
      return d.value;
    });
  });
};

uv.util.transposeData = function (graphdef) {
  var dataset = {}, i, j, length, jlength,
    name, label, value, categories = graphdef.dataset[graphdef.categories[0]].map(function (d) { return d.name; });

  for (i = 0, length = categories.length; i < length; i = i + 1) { dataset[categories[i]] = []; }

  for (i = 0, length = graphdef.categories.length; i < length; i = i + 1) {
    name = graphdef.categories[i];
    for (j = 0, jlength = graphdef.dataset[name].length; j < jlength; j = j + 1) {
      label = graphdef.dataset[name][j].name;
      value = graphdef.dataset[name][j].value;
      dataset[label].push({ 'name' : name, 'value' : value });
    }
  }

  graphdef.categories = categories;
  graphdef.dataset = dataset;
};

uv.util.getPascalCasedName = function (name) {
  return name.substring(0, 1).toUpperCase() + name.substring(1);
};

uv.util.getColorBand = function (config, index) {
  var len = 0,
    palette = config.graph.palette || 'Default';

  if (config.graph.custompalette && config.graph.custompalette.length !== 0) {
    len = config.graph.custompalette.length;
    return config.graph.custompalette[index % len];
  } else {
    len = uv.palette[config.graph.palette].length;
    return uv.palette[config.graph.palette][index % len];
  }
};

/**
* This function finds regular expressions other than Alphabets, Numbers,
* "_" and "-" and replaces it with "-".
* @param  {string} name The string which needs to be formatted
* @return {string}      Returns the formatted String
*/
uv.util.formatClassName = function(name){
  var returnName = name.trim().replace(/[^A-Za-z0-9_\-]/g,"-").toLowerCase();
  return returnName;
};

uv.util.svgToPng = function (downloadElmtRef, callback) {
  if (this.isCanvasSupported()) {
    var svgContent = d3.select(downloadElmtRef.frame.node().parentNode).html();
    var canvas = document.createElement('canvas');
    var ctx = canvas.getContext("2d");
    canvas.setAttribute('width',d3.select(downloadElmtRef.frame.node()).attr('width'));
    canvas.setAttribute('height',d3.select(downloadElmtRef.frame.node()).attr('height'));
    ctx.drawSvg(svgContent);
    canvas.toBlob(function(blob) {
      saveAs(blob, "png_download"+Math.ceil(Math.random()*100000)+".png");
    }, "image/png");
    callback.call();
  } else {
    console.log('this feature is not supported in this version of browser');
  }
};

uv.util.isDownloadSupported = function() {
  var canvas = document.createElement('canvas');
  var ctx = canvas.getContext("2d");
  return typeof(ctx.drawSvg) === 'function' && typeof(canvas.toBlob) === 'function';
};

uv.util.isCanvasSupported = function () {
  var elem = document.createElement('canvas');
  return !!(elem.getContext && elem.getContext('2d'));
};

/**
* This function waits till the end of the transition and then call the callback
* function which is passed as an argument
* @param  {transition}   transition It's the current transition
* @param  {Function} callback   function which is called at the end of
*                               transition
*/
uv.util.endAll = function (transition, callback){
  var n = 0;
  transition.each(function() { ++n; }).each("end", function() {
    if (!--n) {
      callback.apply(this, arguments);
    }
  });
};

/**
* This function returns all class names of the element including new class name.
* Useful in cases where we need to avoid over-writting of classes.
* @param  {} self this referring to svgElement
* @param  {String} name new class name to be added
* @return {String}      All class names as string.
*/
uv.util.getClassName = function(self, name) {
  var formattedName = uv.util.formatClassName(name);
  if( !d3.select(self).attr('class')) {
    return formattedName;
  }
  if(d3.select(self).attr('class').split(' ').indexOf(formattedName) !== -1) {
    return d3.select(self).attr('class');
  }
  return d3.select(self).attr('class') + " " + formattedName;
};

/**
* Returns specified value of given data object if integer, else returns formatted value considering precision.
* @param  self
* @param  {Number} d    data object
* @return {Strinig}     value with precision
*/
uv.util.getLabelValue = function(self, d) {
  // if(typeof d.value !== 'number') return null;
  if (self.config.label.formatter !== undefined) {
    return self.config.label.formatter(d.value);
  }

  var val = (d.value % 1 === 0) ? d.value : d.value.toFixed(self.config.label.precision);
  return self.config.label.prefix + String(val) + self.config.label.suffix;
};

uv.util._deepClone = function(target, src) {
    if(typeof src === 'object') {
        var isEmpty = true;
        for(var key in src) {
            isEmpty = false;
            if(src.hasOwnProperty(key)) {
              if(target === undefined) {
                  target = Array.isArray(src) ? [] : {};
              }
              target[key] = uv.util._deepClone(target[key], src[key]);
            }
        }
        if(isEmpty){
            target = Array.isArray(src) ? [] : {};
        }
    } else {
        target = src;
    }
    return target;
};


/**
* Extends properies of rest of the arguments to the first argument.
* @param  {Object} target
* @param  {Object} argument1
* @param  {Object} argumentN
* @return target object
*/
uv.util.extend = function() {
  if(arguments[0] === undefined || arguments[0] === null) {
    return arguments[0];
  }
    for(var i=1; i<arguments.length; i++) {
        for(var key in arguments[i]) {
            if(arguments[i].hasOwnProperty(key)) {
                arguments[0][key] = uv.util._deepClone(arguments[0][key], arguments[i][key]);
            }
        }
    }
    return arguments[0];
};

/**
* This method returns tool tip text value if config option ‘show tooltiptext’ is true else returns empty string.
* The text is as per the format specified using %c, %l, %v for category, label and values respectively.
* Default format is - ‘%c [%l] : %v’
* @param   self
* @param  {String} category
* @param  {String} label
* @param  {String} value
* @return {[String]}  tool tip text with specified format.
*/
uv.util.getTooltipText = function(self, category, label, value) {
  if(!self.config.tooltip.show) {
    return '';
  }
  var format = self.config.tooltip.format;
  if(format === '') {
    return category + ' [' + label + '] : ' + uv.util.getLabelValue(value);
  }
  return format.replace(/%c/gi, category).replace(/%l/gi, label).replace(/%v/gi, uv.util.getLabelValue(self, value));
};

uv.config = {
  graph: {
    palette: 'Default',
    bgcolor: '#FFFFFF',
    orientation: 'Horizontal',
    max: 0,
    min: 0,
    custompalette: [],
    opacity: 1,
    responsive: false,
    align: 'xMinYMin',
    meetOrSlice: 'meet'
  },

  meta: {
    position: '#uv-div',
    caption: '',
    subcaption: '',
    hlabel: '',
    vlabel: '',
    hsublabel: '',
    vsublabel: '',
    isDownloadable: false,
    downloadLabel: 'Download'
  },

  dimension: {
    width: 400,
    height: 400
  },

  margin: {
    top: 50,
    bottom: 150,
    left: 100,
    right: 100
  },

  frame: {
    bgcolor: '#FFFFFF'
  },

  axis: {
    ticks: 8,
    subticks: 2,
    padding: 5,
    minor: -10,
    strokecolor: '#000000',
    fontfamily: 'Arial',
    fontsize: '14',
    fontweight: 'bold',
    showticks: true,
    showsubticks: true,
    showtext: true,
    showhortext: true,
    showvertext: true,
    opacity: 0.1
  },

  label: {
    fontfamily: 'Arial',
    fontsize: '11',
    fontweight: 'normal',
    strokecolor: '#000000',
    showlabel: true,
    precision: 2,
    prefix: '',
    suffix: '',
    formatter: undefined
  },

  tooltip: {
    show: true,
    format: '%c [%l]: %v'
  },

  scale: {
    type: 'linear',
    ordinality: 0.2
  },

  bar: {
    strokecolor: 'none',
    fontfamily: 'Arial',
    fontsize: '10',
    fontweight: 'bold',
    textcolor: '#000'
  },

  line: {
    interpolation: 'linear',
    showcircles: true,
    circleradius: 3.5,
    circleopacity: 0.6,
    fontfamily: 'Arial',
    fontsize : '10',
    fontweight: 'bold',
    textcolor: '#000',
    strokewidth: 1.5,
    strokeopacity: 1
  },

  area: {
    interpolation: 'cardinal',
    offset: 'zero',
    opacity: 0.2
  },

  pie: {
    fontfamily: 'Arial',
    fontsize: '14',
    fontweight: 'normal',
    fontvariant: 'small-caps',
    fontfill: '#FFFFFF',
    strokecolor: '#FFFFFF',
    strokewidth: 1
  },

  donut: {
    fontfamily: 'Arial',
    fontsize: '14',
    fontweight: 'normal',
    fontvariant: 'small-caps',
    fontfill: '#000',
    factor: 0.4,
    strokecolor: '#FFFFFF',
    strokewidth: 1
  },

  caption: {
    fontfamily: 'Arial',
    fontsize: '14',
    fontweight: 'bold',
    fontvariant: 'small-caps',
    textdecoration: 'none',
    hovercolor: '#696969',
    strokecolor: '#0000FF',
    textanchor: 'middle',
    cursor: 'pointer'
  },

  subCaption: {
    fontfamily: 'Arial',
    fontsize: '9',
    fontweight: 'normal',
    fontvariant: 'normal',
    textdecoration: 'none',
    textanchor: 'middle'
  },

  legend: {
    position: 'bottom',
    fontfamily: 'Arial',
    fontsize: '11',
    fontweight: 'normal',
    color: "#000000",
    strokewidth: 0.15,
    textmargin: 15,
    symbolsize: 10,
    inactivecolor: '#DDD',
    legendstart: 0,
    legendtype: 'categories',
    showlegends: true,
  },

  effects: {
    hovercolor: '#FF0000',
    strokecolor: 'none',
    textcolor: '#000000',
    duration: 800,
    hover: 400,
    showhovertext: false
  }
};

uv.constants = {};

uv.constants.classes = {
  uv : 'uv',
  pos : 'uv-div',
  frame : 'uv-frame',
  panel : 'uv-panel',
  bg : 'uv-chart-bg',
  chart : 'uv-chart',
  axes : 'uv-axes',
  legend : 'uv-legend',
  framebg : 'uv-frame-bg',
  horaxis : 'uv-hor-axis',
  veraxis : 'uv-ver-axis',
  caption : 'uv-caption',
  captiontext : 'uv-caption-text',
  subcaption : 'uv-subcaption',
  subcaptiontext : 'uv-subcaption-text',
  axeslabelgroup : 'uv-axes-label-group',
  axeslabel : 'uv-axes-label',
  axessublabel : 'uv-axes-sub-label',
  legendsign : 'uv-legend-sign',
  legendlabel : 'uv-legend-label',
  hoverbg : 'uv-hover-bg',
  arc : 'uv-arc-',
  areapath : 'uv-areapath-',
  linepath :'uv-linepath-',
  area : 'uv-area-',
  line : 'uv-line-',
  dot : 'uv-dot',
  chartdiv : 'uv-chart-div',
  circleticks : 'circle-ticks',
  download : 'uv-download-options'
};

uv.constants.downloads = {
  downloadLabel: 'Download'
};

uv.types = {};

uv.addChart = function (type, functionName) {
  uv.types[type] = functionName;
};

uv.addChart('Bar','BarGraph');
uv.addChart('Line','LineGraph');
uv.addChart('StackedBar','StackedBarGraph');
uv.addChart('StepUpBar','StepUpBarGraph');
uv.addChart('Area','AreaGraph');
uv.addChart('StackedArea','StackedAreaGraph');
uv.addChart('PercentBar','PercentBarGraph');
uv.addChart('PercentArea','PercentAreaGraph');
uv.addChart('Pie','PieGraph');
uv.addChart('Donut','DonutGraph');
uv.addChart('Waterfall','WaterfallGraph');
uv.addChart('PolarArea','PolarAreaGraph');

uv.chart = function (type, graphdef, config) {
  if (uv.types[type] !== undefined) {
    return new uv[uv.types[type]](graphdef, config);
  }
};

uv.effects = {};

uv.effects.bar = {};
uv.effects.bar.mouseover = function (graph, idx) {
  var config = graph.config,
    category = graph.categories[idx],
    label = graph.labels[idx];

  var effect = function () {
    graph.frame.selectAll('rect.cr-' + uv.util.formatClassName(category))
      .transition().duration(config.effects.hover)
        .style('fill', config.effects.hovercolor)
        .style('stroke', config.effects.strokecolor);

    if(config.effects.showhovertext){
      graph.frame.selectAll('text.cr-' + uv.util.formatClassName(category))
        .transition().duration(config.effects.hover)
          .style('fill', config.effects.textcolor)
          .style('opacity', 1);
    }
  };

  if(config.legend.legendtype === 'categories'){
    graph.effects[category].mouseover = effect;
  }else{
    graph.effects[label].mouseover = effect;
  }
  return effect;
};

uv.effects.bar.mouseout = function (graph, idx, defColor) {
  var config = graph.config,
    category = graph.categories[idx],
    barColor = uv.util.getColorBand(graph.config, idx),
    textColor = defColor || uv.util.getColorBand(graph.config, idx),
    label = graph.labels[idx];

  var effect = function () {
    graph.frame.selectAll('rect.cr-' + uv.util.formatClassName(category))
      .transition().duration(config.effects.hover)
        .style('fill', barColor)
        .style('stroke', 'none');

    graph.frame.selectAll('text.cr-' + uv.util.formatClassName(category))
      .transition().duration(config.effects.hover)
        .style('fill', graph.config.label.showlabel ? textColor : 'none');
  };

  if(config.legend.legendtype === 'categories'){
    graph.effects[category].mouseout = effect;
  }else{
    graph.effects[label].mouseout = effect;
  }
  return effect;
};

uv.effects.area = {};
uv.effects.area.mouseover = function (graph, idx) {
  var config = graph.config,
    category = graph.categories[idx];

  var effect = function () {
    graph.frame.selectAll('.cge-' + uv.util.formatClassName(category)).select('path.' + uv.constants.classes.area + uv.util.formatClassName(category))
    .transition().duration(config.effects.hover)
    .style('fill',config.effects.hovercolor);
  };

  graph.effects[category].mouseover = effect;
  return effect;
};

uv.effects.area.mouseout = function (graph, idx) {
  var config = graph.config,
    category = graph.categories[idx];

  var effect = function () {
    graph.frame.selectAll('.cge-'+ uv.util.formatClassName(category)).select('path.'+ uv.constants.classes.area + uv.util.formatClassName(category));
    graph.frame.selectAll('.cge-'+uv.util.formatClassName(category)).select('path.' + uv.constants.classes.area +uv.util.formatClassName(category))
    .transition().duration(config.effects.hover)
    .style('fill',uv.util.getColorBand(config,idx));
  };

  graph.effects[category].mouseout = effect;
  return effect;
};


uv.effects.line = {};
uv.effects.line.mouseover = function (graph, idx) {
  var config = graph.config,
    category = graph.categories[idx];

  var effect = function () {
    graph.frame.selectAll('.cge-' + uv.util.formatClassName(category)).selectAll('circle')
      .transition().duration(config.effects.hover)
        .style('fill', config.effects.hovercolor)
        .style('fill-opacity', 1)
        .style('stroke', config.effects.hovercolor);

    graph.frame.selectAll('.cge-' + uv.util.formatClassName(category)).select('path')
      .transition().duration(config.effects.hover)
        .style('stroke', config.effects.hovercolor);

    if(config.effects.showhovertext){
      graph.frame.selectAll('.cge-' + uv.util.formatClassName(category)).selectAll('text')
        .transition().duration(config.effects.hover)
          .style('fill', config.effects.textcolor);
    }
  };
  graph.effects[category].mouseover = effect;

  return effect;
};

uv.effects.line.mouseout = function (graph, idx, defColor) {
  var config = graph.config,
    category = graph.categories[idx],
    color = defColor || uv.util.getColorBand(graph.config, idx);

  var effect = function () {
    graph.frame.selectAll('.cge-' + uv.util.formatClassName(category)).selectAll('circle')
      .transition().duration(config.effects.hover)
        .style('fill', color)
        .style('fill-opacity', 0.6)
        .style('stroke', '#fff');

    graph.frame.selectAll('.cge-' + uv.util.formatClassName(category)).select('path')
      .transition().duration(config.effects.hover)
        .style('stroke', color);

    graph.frame.selectAll('.cge-' + uv.util.formatClassName(category)).selectAll('text')
      .transition().duration(config.effects.hover)
        .style('fill', graph.config.label.showlabel ? color : 'none');

  };
  graph.effects[category].mouseout = effect;
  return effect;
};

uv.effects.caption = {};
uv.effects.caption.mouseover = function (config) {
  return function () {
    d3.select(this.parentNode.parentNode).select('.' + uv.constants.classes.hoverbg)
      .transition().duration(config.effects.duration)
        .style('fill', config.caption.hovercolor);
  };
};

uv.effects.caption.mouseout = function (config) {
  return function () {
    d3.select(this.parentNode.parentNode).select('.' + uv.constants.classes.hoverbg)
      .transition().duration(config.effects.duration)
        .style('fill', config.graph.bgcolor);
  };
};

uv.effects.donut = {};
uv.effects.donut.mouseover = function (center, arcfunc, config, d) {
  return function (d) {
    var dev = {
        x : arcfunc.centroid(d)[0] / 5,
        y : arcfunc.centroid(d)[1] / 5
      };

    d3.select(this.parentNode)
      .transition().duration(config.effects.duration)
        .attr('transform', 'translate(' + (center.x + dev.x) + ',' + (center.y + dev.y) + ')');
  };
};

uv.effects.donut.mouseout = function (center, config) {
  return function () {
    d3.select(this.parentNode)
      .transition().duration(config.effects.duration)
        .attr('transform', 'translate(' + center.x + ',' + center.y + ')');
  };
};

uv.effects.pie = {};
uv.effects.pie.mouseover = function (graph ,center, arcfunc, config) {
  var effect =  function (d) {
    var dev = {
        x : arcfunc.centroid(d)[0] / 5,
        y : arcfunc.centroid(d)[1] / 5
      };
    d3.select(this.parentNode)
      .transition().duration(config.effects.duration)
        .attr('transform', 'translate(' + (center.x + dev.x) + ',' + (center.y + dev.y) + ')');
  };
  return effect;
};

uv.effects.pie.mouseout = function (graph, center, config) {
  var effect =  function () {
    d3.select(this.parentNode)
      .transition().duration(config.effects.duration)
        .attr('transform', 'translate(' + center.x + ',' + center.y + ')');
  };
  return effect;
};

uv.effects.legend = {};
uv.effects.legend.mouseover = function (self, idx) {
  if(self.config.legend.legendtype === 'categories'){
    return self.effects.group[self.categories[idx]].mouseover;
  }else{
    return self.effects.group[self.labels[idx]].mouseover;
  }
};

uv.effects.legend.mouseout = function (self, idx) {
  if(self.config.legend.legendtype === 'categories'){
    return self.effects.group[self.categories[idx]].mouseout;
  }else{
    return self.effects.group[self.labels[idx]].mouseout;
  }
};

uv.effects.legend.click = function (i, ctx, graph) {
  var disabled = (d3.select(ctx).attr('disabled') === 'false') ? false : true;
  graph.toggleGraphGroup(i);
  d3.select(ctx).select('rect').style('fill', disabled ? uv.util.getColorBand(graph.config, i) : uv.config.legend.inactivecolor);
  d3.select(ctx).select('text').style('fill', disabled ? null : uv.config.legend.inactivecolor);
  d3.select(ctx).attr('disabled', disabled ? 'false' : 'true');
};

uv.palette = {
  'Default' : ['#7E6DA1', '#C2CF30', '#FF8900', '#FE2600', '#E3003F', '#8E1E5F', '#FE2AC2', '#CCF030', '#9900EC', '#3A1AA8', '#3932FE', '#3276FF', '#35B9F6', '#42BC6A', '#91E0CB'],
  'Plain' : ['#B1EB68', '#B1B9B5', '#FFA16C', '#9B64E7', '#CEE113', '#2F9CFA', '#CA6877', '#EC3D8C', '#9CC66D', '#C73640', '#7D9532', '#B064DC' ],
  'Android' : ['#33B5E5', '#AA66CC', '#99CC00', '#FFBB33', '#FF4444', '#0099CC', '#9933CC', '#669900', '#FF8800', '#CC0000'],
  'Soft' : [ '#9ED8D2', '#FFD478', '#F16D9A', '#A8D59D', '#FDC180', '#F05133', '#EDED8A', '#F6A0A5', '#9F218B' ],
  'Simple' : [ '#FF8181', '#FFB081', '#FFE081', '#EFFF81', '#BFFF81', '#90FF81', '#81FFA2', '#81FFD1', '#9681FF', '#C281FF', '#FF81DD' ],
  'Egypt' : [ '#3A3E04','#784818','#FCFCA8','#C03C0C','#F0A830','#A8783C','#FCFCFC','#FCE460','#540C00','#C0C084','#3C303C','#1EA34A','#606C54','#F06048' ],
  'Olive' : [ '#18240C','#3C6C18','#60A824','#90D824','#A8CC60','#789C60','#CCF030','#B4CCA8','#D8F078','#40190D','#E4F0CC' ],
  'Candid' : [ '#AF5E14','#81400C','#E5785D','#FEBFBF','#A66363','#C7B752','#EFF1A7','#83ADB7','#528F98','#BCEDF5','#446B3D','#8BD96F','#E4FFB9' ],
  'Sulphide' : [ '#594440','#0392A7','#FFC343','#E2492F','#007257','#B0BC4A','#2E5493','#7C2738','#FF538B','#A593A1','#EBBA86','#E2D9CA' ],
  'Lint' : ['#A8A878','#F0D89C','#60909C','#242418','#E49C30','#54483C','#306090','#C06C00','#C0C0C0','#847854','#6C3C00','#9C3C3C','#183C60','#FCCC00','#840000','#FCFCFC']
};

/**
* uv.Graph is an abstract class of sorts which serves as the base for all other graphs. Instances of it wouldnt be anything except bare bones needed to create a chart.
* id          - unique id corresponding to the graph, created using current timestamp {#TODO: needs improved logic}
* graphdef    - definition of the graph, containing data on which the visualization is built
* config      - configuration of the graph, affecting the visual styling of the graph
* frame      - <svg> element acting as the parent graph container
* panel      - <g> element containing everything else, making it easier to move all elements across the svg
* bg          - <rect> element which acts as the background for the graph
* effects    - object containing functions which cause the various interactions on the graph
* labels      - labels from the dataset provided
* categories  - categories from the dataset provided
* axes        - object containing axes related stuff: group, func, scale, axis, line, label
*
*/
uv.Graph = function (graphdef, config) {
  var self = this;
  self.id = uv.util.getUniqueId();
  self.graphdef = null;
  self.config = null;

  self.frame = null;
  self.panel = null;
  self.chart = null;
  self.bg = null;
  self.effects = {};
  self.axes = {
    hor: { group: null, scale : null, func: null, axis : null, line : null, label : null },
    ver: { group: null, scale : null, func: null, axis : null, line : null, label : null },
    meta: { min: null, max: null }
  };

  self.labels = null;
  self.categories = null;

  self.graphdef = graphdef;
  self.config = uv.util.extend({}, uv.config, config);
  return this;
};

/**
* As the name suggests, this function initializes graph object construction based on the config and graphdef
* @param  {Object} graphdef Definition of the graph, take a look at constants.js for complete documentation
* @param  {Object} config   Configuration of the graph, take a look at config.js for complete documentation
* @return {Object}          The graph object itself, to support method chaining
*
* #TODO: Remove dependency on jQuery/$
*/
uv.Graph.prototype.init = function () {
  var self = this;
  self.max()
    .min()
    .position(self.config.meta.position || 'body')
    .setDimensions()
    .setFrame()
    .setPanel()
    .setBackground()
    .setCaption()
    .setSubCaption()
    .setMetadata()
    .setHorizontalAxis()
    .setVerticalAxis()
    .setEffectsObject();

  if(self.config.meta.isDownloadable){
    self.setDownloadOptions();
  }
  if(self.config.legend.showlegends){
    self.setLegend();
  }

  return self;
};

/**
* Sets the dimensions of the graphs, namely height, width and margins: left, right, top and bottom
* @return {Object}        The graph object itself, to support method chaining
*/
uv.Graph.prototype.setDimensions = function () {
  var self = this;
  self.height(self.config.dimension.height)
    .width(self.config.dimension.width)
    .top(self.config.margin.top)
    .bottom(self.config.margin.bottom)
    .left(self.config.margin.left)
    .right(self.config.margin.right);

  return this;
};

/**
* This function downloads the graph in png format.
*
*/
uv.Graph.prototype.setDownloadOptions = function () {
  if (uv.util.isDownloadSupported()) {
    var self = this;
    self.download = self.panel.append('g').classed(uv.constants.classes.download, true);
    self.download.append('text').classed(uv.constants.classes.download, true)
    .text(self.config.meta.downloadLabel)
    .attr('y', -self.config.margin.top / 2)
    .attr('x', self.config.dimension.width-25)
    .attr('text-anchor', self.config.caption.textanchor)
    .style('font-family', self.config.caption.fontfamily)
    .style('font-size', '12')
    .style('cursor', self.config.caption.cursor)
    .style('stroke', self.config.caption.strokecolor)
    .style('text-decoration', 'underline')
    .on('mouseover', function() {
      var dnldBtn = d3.select(this);
      dnldBtn.style('color','#0000FF');
    })
    .on('mouseout', function() {
      var dnldBtn = d3.select(this);
      dnldBtn.style('color','#8D8D8D');
    })
    .on('click', function () {
      var dnldBtn = d3.select(this);
      dnldBtn.style('display','none');
      uv.util.svgToPng(self, function() {
          dnldBtn.style('display',null);
      });
    });
  }
};


/**
* Sets the main <svg> element which contains rest of the graph elements
* @return {Object}        The graph object itself, to support method chaining
*/
uv.Graph.prototype.setFrame = function () {
  var self = this;
  if (!self.frame) {
    self.frame = d3.select(self.position() || 'body').append('div')
      .classed(uv.constants.classes.chartdiv, true)
      .style('display','inline-block')
      .style('width', '100%')
      .style('height', '100%')
      .append('svg');
  }

  self.frame.attr('id', uv.constants.classes.uv + '-' + self.id)
    .classed(uv.constants.classes.frame, true);

  if (self.config.graph.responsive === true) {
    self.frame.attr('width', '100%')
      .attr('height', '100%')
      .attr('preserveAspectRatio', self.config.graph.align + ' ' + self.config.graph.meetOrSlice)
      .attr('viewBox', '0 0 ' + (self.width() + self.left() + self.right()) + ' ' + (self.height() + self.top() + self.bottom()));
  } else {
    self.frame.attr('width', self.width() + self.left() + self.right())
      .attr('height', self.height() + self.top() + self.bottom());
  }

  self.frame.append('rect').classed(uv.constants.classes.framebg, true)
    .attr('width', self.width() + self.left() + self.right())
    .attr('height', self.height() + self.top() + self.bottom())
    .style('fill', self.config.frame.bgcolor);

  return this;
};

/**
* Sets the <g> element which serves as the base position for the graph elements
* @return {Object}        The graph object itself, to support method chaining
*/
uv.Graph.prototype.setPanel = function () {
  var self = this;
  if (!self.panel) {
    self.panel = self.frame.append('g');
  }

  self.panel.attr('id', uv.constants.classes.panel + '-' + self.id)
    .classed(uv.constants.classes.panel, true)
    .attr('transform', 'translate(' + self.left() + ',' + self.top() + ')');

  return this;
};

/**
* Sets the <rect> element which serves as the background for the chart
* @param {String} color Color code for the background, set to config value if not specified
* @return {Object}      The graph object itself, to support method chaining
*/
uv.Graph.prototype.setBackground = function (color) {
  var self = this;
  if (!self.bg) {
    self.bg = self.panel.append('rect').classed(uv.constants.classes.bg, true)
            .attr('height', self.height())
            .attr('width', self.width());
  }
  self.bg.style('fill', color || self.config.graph.bgcolor);

  self.chart = self.panel.append('g').classed(uv.constants.classes.chart, true)
          .style('opacity', self.config.graph.opacity);
  return this;
};

/**
* Sets the caption for the graph
* @return {Object}      The graph object itself, to support method chaining
*/
uv.Graph.prototype.setCaption = function () {
  var self = this;
  self.caption = self.panel.append('g').classed(uv.constants.classes.caption, true);

  self.caption.append('text').classed(uv.constants.classes.captiontext, true)
    .text(self.config.meta.caption)
    .attr('y', -self.config.margin.top / 2)
    .attr('x', self.config.dimension.width / 2)
    .attr('text-anchor', self.config.caption.textanchor)
    .style('font-family', self.config.caption.fontfamily)
    .style('font-size', self.config.caption.fontsize)
    .style('font-weight', self.config.caption.fontweight)
    .style('font-variant', self.config.caption.fontvariant)
    .style('text-decoration', self.config.caption.textdecoration)
    .on('mouseover', uv.effects.caption.mouseover(self.config))
    .on('mouseout', uv.effects.caption.mouseout(self.config));

  return this;
};


/**
* Sets the subcaption for the graph
* @return {Object}      The graph object itself, to support method chaining
*/
uv.Graph.prototype.setSubCaption = function () {
  var self = this;
  self.subCaption = self.panel.append('g').classed(uv.constants.classes.subcaption, true);

  self.subCaption.append('text').classed(uv.constants.classes.subcaptiontext, true)
    .text(self.config.meta.subcaption)
    .attr('y', -self.config.margin.top / 2 + 1*self.config.caption.fontsize)
    .attr('x', self.config.dimension.width / 2)
    .attr('text-anchor', self.config.caption.textanchor)
    .style('font-family', self.config.subCaption.fontfamily)
    .style('font-size', self.config.subCaption.fontsize)
    .style('font-weight', self.config.subCaption.fontweight)
    .style('font-variant', self.config.subCaption.fontvariant)
    .style('text-decoration', self.config.subCaption.textdecoration);

  return this;
};


/**
* Sets the metadata for the graph, this includes the labels and the categories
* @return {Object}      The graph object itself, to support method chaining
*/
uv.Graph.prototype.setMetadata = function () {
  var self = this;
  self.labels = uv.util.getLabelArray(self.graphdef);
  self.categories = uv.util.getCategoryArray(self.graphdef);
  return this;
};

/**
* Sets the Horizontal Axis functions but doesnt render it yet
* return {Object}      The graph object itself, to support method chaining
*/
uv.Graph.prototype.setHorizontalAxis = function () {
  var self = this;
  var graphdef = self.graphdef;
  if (!self.axes.hor.group) {
    self.axes.hor.group = self.panel.append('g').classed(uv.constants.classes.horaxis, true)
                  .attr('transform', 'translate(0,' + self.height() + ')')
                  .style('shape-rendering','crispEdges');
  }

  if (self.config.graph.orientation === 'Horizontal') {
    self.axes.hor.scale  = d3.scale[self.config.scale.type]()
                .domain([self.config.scale.type === 'log' ? 1: self.min(), self.max()])
                .range([0, self.width()]);

    if (self.axes.hor.scale.nice) {
      self.axes.hor.scale.nice();
    }

    if(!self.config.axis.showsubticks){
      self.config.axis.subticks = 0;
    }
    self.axes.hor.func = d3.svg.axis()
                .scale(self.axes.hor.scale)
                .ticks(self.config.axis.ticks)
                .tickSize(-self.height(), self.config.axis.minor, 0)
                .tickPadding(self.config.axis.padding)
                .tickSubdivide(self.config.axis.subticks)
                .orient('bottom');

  } else {
    self.axes.hor.scale = d3.scale.ordinal()
                .rangeRoundBands([0, self.width()], self.config.scale.ordinality);

    self.axes.hor.func = d3.svg.axis()
                .scale(self.axes.hor.scale)
                .tickPadding(self.config.axis.padding)
                .orient('bottom');

    if(!self.config.axis.showtext || !self.config.axis.showhortext) {
      self.axes.hor.func.tickSize(0);
    }
  }
  if(!self.config.axis.showtext || !self.config.axis.showhortext) {
      self.axes.hor.func.tickFormat(function (d) { return ''; });
  }

  return this;
};

/**
* Sets the Vertical axis functions, but doesnt render it yet
* @return {Object}        The graph object itself, to support method chaining
*/
uv.Graph.prototype.setVerticalAxis = function () {
  var self = this;
  var graphdef = self.graphdef;
  if (!self.axes.ver.group) {
    self.axes.ver.group = self.panel.append('g').classed(uv.constants.classes.veraxis, true)
                              .style('shape-rendering','crispEdges');
  }

  if (self.config.graph.orientation === 'Vertical') {
    self.axes.ver.scale  = d3.scale[self.config.scale.type]()
                .domain([self.max(), self.config.scale.type === 'log' ? 1 : self.min()])
                .range([0, self.height()]);

    if (self.axes.ver.scale.nice) {
      self.axes.ver.scale.nice();
    }

    if(!self.config.axis.showsubticks){
      self.config.axis.subticks = 0;
    }
    self.axes.ver.func = d3.svg.axis()
                .scale(self.axes.ver.scale)
                .ticks(self.config.axis.ticks)
                .tickSize(-self.width(), self.config.axis.minor, 0)
                .tickPadding(self.config.axis.padding)
                .tickSubdivide(self.config.axis.subticks)
                .orient('left');


  } else {
    self.axes.ver.scale = d3.scale.ordinal()
                .rangeRoundBands([0, self.height()], self.config.scale.ordinality);

    self.axes.ver.func = d3.svg.axis()
                .scale(self.axes.ver.scale)
                .tickPadding(self.config.axis.padding)
                .orient('left');

    if(!self.config.axis.showtext || !self.config.axis.showvertext){
      self.axes.ver.func.tickSize(0);
    }
  }
  if(!self.config.axis.showtext || !self.config.axis.showvertext) {
    self.axes.ver.func.tickFormat(function (d) { return ''; });
  }

  return this;
};

/**
* Creates placeholders for functions which cause the various animations across the graph to be able invoke it from other places
* @return {Object}        The graph object itself, to support method chaining
*/
uv.Graph.prototype.setEffectsObject = function () {
  var self = this;
  var effectArray = (self.config.legend.legendtype === 'categories') ? self.categories : self.labels;
  for (var i = 0; i < effectArray.length ; i++) {
    self.effects[effectArray[i]] = {};
  }
  return self;
};

/**
* Draws the horizontal axis within the frame based on the orientation and functions already created
* @return {Object} The graph object itself, to support method chaining
*/
uv.Graph.prototype.drawHorizontalAxis = function () {
  var self = this;
  self.axes.hor.axis = self.axes.hor.group.append('g')
                .style('font-family', self.config.label.fontfamily)
                .style('font-size', self.config.label.fontsize)
                .style('font-weight', self.config.label.fontweight)
                .call(self.axes.hor.func);

  if (self.config.axis.showticks) {
    self.axes.hor.axis.selectAll('line').style('stroke', self.config.axis.strokecolor)
                .style('opacity', self.config.axis.opacity);
  }
  self.axes.hor.axis.selectAll('path').style('fill','none');

  self.axes.hor.line = self.panel.append('line')
                .classed(uv.constants.classes.horaxis, true)
                .attr('y1', self.config.graph.orientation === 'Horizontal' ? self.height() : self.axes.ver.scale(0))
                .attr('y2', self.config.graph.orientation === 'Horizontal' ? self.height() : self.axes.ver.scale(0))
                .attr('x1', '0')
                .attr('x2', self.width())
                .style('stroke', self.config.axis.strokecolor);

  self.axes.hor.label = self.axes.hor.group.append('g')
                            .classed(uv.constants.classes.axeslabelgroup, true)
                            .attr('transform', 'translate(' + self.width()/2 + ',' + (1*self.config.margin.bottom/4 + 1*self.config.label.fontsize) + ')');

  self.axes.hor.label.append('text')
                .attr('display','block')
                .classed(uv.constants.classes.axeslabel, true).classed('cal', true)
                .attr('text-anchor','middle')
                .style('font-size', self.config.axis.fontsize)
                .style('font-family', self.config.axis.fontfamily)
                .text(self.config.meta.hlabel);

  self.axes.hor.label.append('text')
                .attr('display','block')
                .attr('y', 1*self.config.axis.fontsize)
                .attr(uv.constants.classes.axessublabel, true).classed('casl', true)
                .attr('text-anchor','middle')
                .style('font-size', self.config.axis.fontsize - 2)
                .style('font-family', self.config.axis.fontfamily)
                .text(self.config.meta.hsublabel);

  return this;
};

/**
* Draws the vertical axis within the frame based on the orientation and functions already created
* @return {Object} The graph object itself, to support method chaining
*/
uv.Graph.prototype.drawVerticalAxis = function () {
  var self = this;
  self.axes.ver.axis = self.axes.ver.group.append('g')
                .classed(uv.constants.classes.axes, true)
                .style('font-family', self.config.label.fontfamily)
                .style('font-size', self.config.label.fontsize)
                .style('font-weight', self.config.label.fontweight)
                .call(self.axes.ver.func);

  if (self.config.axis.showticks) {
    self.axes.ver.axis.selectAll('line').style('stroke', self.config.axis.strokecolor)
                .style('opacity', self.config.axis.opacity);
  }
  self.axes.ver.axis.selectAll('path').style('fill','none');

  self.axes.ver.line = self.panel.append('line')
                .classed(uv.constants.classes.veraxis, true)
                .attr('x1', self.config.graph.orientation === 'Horizontal'? self.axes.hor.scale(0): 0)
                .attr('x2', self.config.graph.orientation === 'Horizontal'? self.axes.hor.scale(0): 0)
                .attr('y1', 0)
                .attr('y2', self.height())
                .style('stroke', self.config.axis.strokecolor);

  self.axes.ver.label = self.axes.ver.group.append('g')
                .attr('transform', 'translate(' + -4*self.config.margin.left/5 + ',' + self.height()/2 + ')rotate(270)');

  self.axes.ver.label.append('text').classed(uv.constants.classes.axeslabel, true)
                .attr('text-anchor', 'middle')
                .classed('cal', true)
                .style('font-family', self.config.axis.fontfamily)
                .style('font-size', self.config.axis.fontsize)
                .text(self.config.meta.vlabel);

  self.axes.ver.label.append('text').classed(uv.constants.classes.axessublabel, true)
                .attr('text-anchor', 'middle')
                .attr('y', +self.config.axis.fontsize)
                .classed('casl', true)
                .style('font-family', self.config.axis.fontfamily)
                .style('font-size', self.config.axis.fontsize - 2)
                .text(self.config.meta.vsublabel);

  return this;
};

/**
* Sets the legend and related interactions for the graph based on the configuration
* @return {Object}  The graph object itself, to support method chaining
*/
uv.Graph.prototype.setLegend = function () {
  var self = this;

  var legendgroup = self.panel.append('g').classed(uv.constants.classes.legend, true)
            .attr('transform', function(d, i){
              if(self.config.legend.position === 'right'){
                return 'translate(' + self.width() + ', 10)';
              }else if(self.config.legend.position === 'bottom'){
                var pos =  self.height() + self.config.margin.bottom/2 + Number(self.config.axis.fontsize);
                return 'translate(0, ' + pos +  ')';
              }
            });

  self.legends = legendgroup.selectAll('g').data(
    (self.config.legend.legendtype === 'categories') ? self.categories:self.labels
  );


  self.legends.enter().append('g')
      .attr('transform', function (d, i) {
        if(self.config.legend.position === 'right'){
          return 'translate(10,' + 10* (2* i - 1) + ')';
        }else if(self.config.legend.position === 'bottom'){
          var hPos = 100*i - self.config.dimension.width*self.config.legend.legendstart;
          var vPos = 20*self.config.legend.legendstart;
          if(hPos >= self.config.dimension.width){
            self.config.legend.legendstart = self.config.legend.legendstart + 1;
            hPos = 100*i - self.config.dimension.width*self.config.legend.legendstart;
            vPos = 20*self.config.legend.legendstart;
          }
          return 'translate(' + hPos + ',' + vPos + ')';
        }
      })
      .attr('class', function (d, i) {
        var className = (self.config.legend.legendtype === 'categories') ? self.categories[i]:self.labels[i];
        return uv.util.getClassName(this, 'cl-' + className);
      })
      .attr('disabled', 'false')
      .on('mouseover', function (d, i) {
        if (self.effects[d].mouseover && typeof self.effects[d].mouseover === 'function') {
          self.effects[d].mouseover();
        }
      })
      .on('mouseout', function (d, i) {
        if (self.effects[d].mouseout && typeof self.effects[d].mouseout === 'function') {
          self.effects[d].mouseout();
        }
      })
      .on('click', function (d, i) {
        uv.effects.legend.click(i, this, self);
      });

  self.legends.append('rect').classed(uv.constants.classes.legendsign, true)
        .attr('height', self.config.legend.symbolsize)
        .attr('width', self.config.legend.symbolsize)
        .style('fill', function (d, i) { return uv.util.getColorBand(self.config, i); })
        .style('stroke', 'none');

  self.legends.append('text').classed(uv.constants.classes.legendlabel, true)
        .text(function (d, i) { return (self.config.legend.legendtype === 'categories') ? self.categories[i]:self.labels[i]; })
        .attr('dx', self.config.legend.textmargin)
        .attr('dy', '.71em')
        .attr('text-anchor', 'start')
        .style('stroke', self.config.legend.color)
        .style('fill', self.config.legend.color)
        .style('stroke-width', self.config.legend.strokewidth)
        .style('font-family', self.config.legend.fontfamily)
        .style('font-size', self.config.legend.fontsize)
        .style('font-weight', self.config.legend.fontweight);

  return this;
};

/**
* Finalizes stuff related to graph, used in conjuction with init to setup all the generic graph stuff
* @param  {Boolean} isLoggable Specifies whether the graph object should be logged or not, for debug purpose only
* @return {Object}             The graph object itself, to support method chaining
*/
uv.Graph.prototype.finalize = function (isLoggable) {
  var self = this;
  self.drawHorizontalAxis()
    .drawVerticalAxis();
  //  .setLegend();

  // Cursor for text is unset from text to normal
  self.frame.selectAll('text').style('cursor', 'default');

  //Log Graph object if flag set to truthy value
  //if (isLoggable) {
    console.log(self);
  //}
  return this;
};

/*
* Functions to remove individual elements of an graph
*/

/**
* Removes the entire graph object
* @return {Object} The graph object itself, to support method chaining
*/
uv.Graph.prototype.remove = function () {
  this.frame.remove();
  return this;
};

/**
* Removes the caption component of the graph
* @return {Object} The graph object itself, to support method chaining
*/
uv.Graph.prototype.removeCaption = function () {
  this.caption.remove();
  return this;
};

/**
* Removes the legend component of the graph
* @return {Object} The graph object itself, to support method chaining
*/
uv.Graph.prototype.removeLegend = function () {
  if (this.legends[0]) {
    this.legends[0].parentNode.remove();
  }

  return this;
};

uv.Graph.prototype.removePanel = function () {
  this.panel.remove();
  return this;
};

uv.Graph.prototype.removeHorAxis = function () {
  this.panel.selectAll('g.' + uv.constants.classes.horaxis + " >*").remove();
  this.panel.selectAll('line.' + uv.constants.classes.horaxis).remove();
  return this;
};

uv.Graph.prototype.removeVerAxis = function () {
  this.panel.selectAll('g.' + uv.constants.classes.veraxis + " >*").remove();
  this.panel.selectAll('line.' + uv.constants.classes.veraxis).remove();
  return this;
};

/*
* Setters and getters for various common properties of the graph
*/

uv.Graph.prototype.width = function (w) {
  if (w) {
    this.config.dimension.width = w;
    return this;
  }

  return this.config.dimension.width;
};

uv.Graph.prototype.height = function (h) {
  if (h) {
    this.config.dimension.height = h;
    return this;
  }

  return this.config.dimension.height;
};

uv.Graph.prototype.top = function (t) {
  if (t) {
    this.config.margin.top = t;
    return this;
  }

  return this.config.margin.top;
};

uv.Graph.prototype.bottom = function (b) {
  if (b) {
    this.config.margin.bottom = b;
    return this;
  }

  return this.config.margin.bottom;
};

uv.Graph.prototype.left = function (l) {
  if (l) {
    this.config.margin.left = l;
    return this;
  }

  return this.config.margin.left;
};

uv.Graph.prototype.right = function (r) {
  if (r) {
    this.config.margin.right = r;
    return this;
  }

  return this.config.margin.right;
};

uv.Graph.prototype.position = function (pos) {
  if (pos) {
    this.config.meta.position = pos;
    return this;
  }

  return this.config.meta.position;
};

uv.Graph.prototype.caption = function (caption) {
  if (caption) {
    this.config.meta.caption = caption;
    return this;
  }

  return this.config.meta.caption;
};

uv.Graph.prototype.subCaption = function (subCaption) {
  if (subCaption) {
    this.config.meta.subCaption = subCaption;
    return this;
  }

  return this.config.meta.caption;
};

uv.Graph.prototype.isDownloadable = function (isDownload) {
  if (isDownload) {
    this.config.meta.isDownload = isDownload;
    return this;
  }

  return this.config.meta.isDownload;
};

uv.Graph.prototype.max = function () {
  if (this.axes.meta.max !== null) {
    return this.axes.meta.max;
  }

  this.axes.meta.max = uv.util.getMax(this.graphdef, this.graphdef.stepup);
  return this;
}

uv.Graph.prototype.min = function () {
  if (this.axes.meta.min !== null) {
    return this.axes.meta.min;
  }

  this.axes.meta.min = uv.util.getMin(this.graphdef, this.graphdef.stepup);
  return this;
}

/* Additional Graph functions*/
uv.Graph.prototype.toggleGraphGroup = function (i) {
  var self = this, category = self.categories[i],
      state = self.frame.select('g.cge-' + uv.util.formatClassName(category)).style('display'),
      color = uv.util.getColorBand(self.config, i);

  self.frame.selectAll('g.cge-' + uv.util.formatClassName(category)).style('display', (state === 'none')? null : 'none');
  return this;
};

uv.AreaGraph = function (graphdef, config) {
  var self = this;
  uv.Graph.call(self, graphdef, config).setDefaults().init();

  self.areagroups = [];
  self.dataset = uv.util.getDataArray(self.graphdef);

  var areagroup, areapath, areafunc, idx, len,
    domainData = self.graphdef.dataset[self.graphdef.categories[0]];

  self.axes[self.config.graph.orientation === 'Horizontal' ? 'ver' : 'hor'].scale.domain(domainData.map(function (d) { return d.name; }));

  for (idx = 0, len = self.dataset.length; idx < len; idx = idx + 1) {
    areapath = self.chart.append('g').classed('cg-' + uv.util.formatClassName(self.categories[idx]), true)
                        .append('g').classed('cge-' + uv.util.formatClassName(self.categories[idx]), true).datum(self.dataset[idx]);
    areagroup = { path: areapath, linefunc: undefined, areafunc: undefined, line: undefined, area: undefined };
    self['draw' + self.config.graph.orientation + 'Area'](areagroup, idx);
    self.areagroups.push(areagroup);
  }

  self.finalize();
};

uv.AreaGraph.prototype = uv.util.inherits(uv.Graph);

uv.AreaGraph.prototype.setDefaults = function () {
  var self = this;
  self.graphdef.stepup = 'normal';
  return this;
};

uv.AreaGraph.prototype.drawHorizontalArea = function (areagroup, idx) {
  var self = this,
    color = uv.util.getColorBand(self.config, idx);

  self.axes.ver.scale.rangePoints([0, self.height()]);

  areagroup.linefunc = d3.svg.line()
        .x(function (d) { return self.axes.hor.scale(d.value); })
        .y(function (d) { return self.axes.ver.scale(d.name) + self.axes.ver.scale.rangeBand() / 2; })
        .interpolate(self.config.area.interpolation);

  areagroup.areafunc = d3.svg.area()
        .x0(self.axes.hor.scale(0))
        .x1(areagroup.linefunc.x())
        .y(areagroup.linefunc.y())
        .interpolate(self.config.area.interpolation);

  areagroup.area = areagroup.path.append('svg:path')
        .classed(uv.constants.classes.areapath + idx, true)
        .attr('d', areagroup.areafunc)
        .style('opacity', self.config.area.opacity)
        .style('-moz-opacity', self.config.area.opacity)
        .style('fill', color);

  areagroup.line = areagroup.path.append('svg:path')
        .classed(uv.constants.classes.linepath + idx, true)
        .attr('d', areagroup.linefunc)
        .style('stroke', 'white')
        .style('fill', 'none');

  areagroup.path.selectAll('.' + uv.constants.classes.dot)
        .data(self.dataset[idx])
        .enter().append('circle')
        .classed(uv.constants.classes.dot, true)
        .attr('cx', areagroup.linefunc.x())
        .attr('cy', areagroup.linefunc.y())
        .attr('r', 3.5)
        .style('fill', 'white');
};

uv.AreaGraph.prototype.drawVerticalArea = function (areagroup, idx) {
  var self = this,
    color = uv.util.getColorBand(self.config, idx);

  self.axes.hor.scale.rangePoints([0, self.width()]);

  areagroup.linefunc = d3.svg.line()
        .x(function (d) { return self.axes.hor.scale(d.name) + self.axes.hor.scale.rangeBand() / 2; })
        .y(function (d) { return self.axes.ver.scale(d.value); })
        .interpolate(self.config.area.interpolation);

  areagroup.areafunc = d3.svg.area()
        .x(areagroup.linefunc.x())
        .y0(areagroup.linefunc.y())
        .y1(self.axes.ver.scale(0))
        .interpolate(self.config.area.interpolation);

  areagroup.area = areagroup.path.append('svg:path')
        .classed(uv.constants.classes.areapath + idx, true)
        .attr('d', areagroup.areafunc)
        .style('opacity', self.config.area.opacity)
        .style('-moz-opacity', self.config.area.opacity)
        .style('fill', color);

  areagroup.line = areagroup.path.append('svg:path')
        .classed(uv.constants.classes.linepath + idx, true)
        .attr('d', areagroup.linefunc)
        .style('stroke', 'white')
        .style('fill', 'none');

  areagroup.path.selectAll('.' + uv.constants.classes.dot)
        .data(self.dataset[idx])
        .enter().append('circle')
        .classed(uv.constants.classes.dot, true)
        .attr('cx', areagroup.linefunc.x())
        .attr('cy', areagroup.linefunc.y())
        .attr('r', 3.5)
        .style('fill', 'white');
};

/**
* A normal 2d bar chart capable of being rendered in horizontal and vertical manner
* @param {Object} graphdef Definition of the graph being rendered
* @param {Object} config   Configuration of the graph being rendered
*/
uv.BarGraph = function (graphdef, config) {
  var self = this;
  uv.Graph.call(self, graphdef, config).setDefaults().init();

  self.bargroups = {};

  self.axes[self.config.graph.orientation === 'Horizontal' ? 'ver' : 'hor'].scale.domain(self.labels);

  var idx, length = self.categories.length, category;
  for (idx = 0; idx < length; idx = idx + 1) {
    category = self.categories[idx];
    self.bargroups[category] = self.chart.append('g').classed('cg-' + uv.util.formatClassName(category), true);
    self['draw' + self.config.graph.orientation + 'Bars'](idx);
  }

  self.finalize();
};

uv.BarGraph.prototype = uv.util.inherits(uv.Graph);

uv.BarGraph.prototype.setDefaults = function () {
  var self = this;
  self.graphdef.stepup = 'normal';
  return this;
};

uv.BarGraph.prototype.drawHorizontalBars = function (idx) {
  var self = this,
    color = uv.util.getColorBand(this.config, idx),
    len = self.categories.length;

  var bars = self.bargroups[self.categories[idx]].selectAll('g').data(self.graphdef.dataset[self.categories[idx]]).enter()
        .append('g').classed('cge-' + uv.util.formatClassName(self.categories[idx]), true)
        .attr('transform', function (d) { if (d.value < 0) return 'scale(-1,1)'; });

  bars.append('rect')
    .classed(uv.util.formatClassName(self.categories[idx]), true)
    .classed('cr-' + uv.util.formatClassName(self.categories[idx]), true)
    .attr('height', self.axes.ver.scale.rangeBand() / len)
    .attr('x', function (d) {
      return (d.value < 0) ? (-self.axes.hor.scale(0)) : self.axes.hor.scale(0);
    })
    .attr('y', function (d) {return self.axes.ver.scale(d.name); })
    .attr('width', 0)
    .style('stroke', self.config.bar.strokecolor)
    .style('fill', color)
    .transition()
      .duration(self.config.effects.duration)
      .delay(function (d, i) { return i * self.config.effects.duration; })
      .attr('width', function (d) { return self.axes.hor.scale(Math.abs(d.value)) - self.axes.hor.scale(0); })
      .call(uv.util.endAll, function (d,i){
        d3.select(this.parentNode.parentNode).selectAll('rect').on('mouseover', uv.effects.bar.mouseover(self, idx));
        d3.select(this.parentNode.parentNode).selectAll('rect').on('mouseout', uv.effects.bar.mouseout(self, idx));
        if (typeof self.config.graph.clickCallback === "function") {
          d3.select(this.parentNode.parentNode).selectAll('rect').on('click', function(_d){
              self.config.graph.clickCallback.apply(null, [_d]);
          });
        }
      });

  bars.append('text')
    .attr('x', function(d) { return self.axes.hor.scale(0); })
    .attr('y', function(d) { return self.axes.ver.scale(d.name) + (self.axes.ver.scale.rangeBand()/len)/2; })
    .attr('dx', function (d) { return (d.value < 0)? '-20px': '4px' })
    .attr('dy', '.35em')
    .attr('text-anchor', 'start')
    .classed('cr-' + uv.util.formatClassName(self.categories[idx]), true)
    .style('fill', self.config.label.showlabel ? uv.util.getColorBand(self.config, idx) : 'none')
    .style('font-family', self.config.bar.fontfamily)
    .style('font-size', self.config.bar.fontsize)
    .style('font-weight', self.config.bar.fontweight)
    .style('transform', function (d) { return d.value < 0? 'scale(-1,1)': 'scale(1,1)'; })
    .style('opacity', 0)
    .text(function(d) { return uv.util.getLabelValue(self, d); })
    .transition()
      .duration(self.config.effects.duration)
      .delay(function (d, i) { return i * self.config.effects.duration; })
      .style('opacity', 1)
      .attr('x', function (d) { return self.axes.hor.scale(d.value); });



  bars.append('svg:title')
    .text( function (d, i) { return uv.util.getTooltipText(self, self.categories[idx], self.labels[i], d);});

  self.bargroups[self.categories[idx]].attr('transform', 'translate(0,' + idx * self.axes.ver.scale.rangeBand() / len + ')');
};

uv.BarGraph.prototype.drawVerticalBars = function (idx) {
  var self = this,
    color = uv.util.getColorBand(this.config, idx),
    len = self.categories.length;

  var bars = self.bargroups[self.categories[idx]].selectAll('g').data(self.graphdef.dataset[self.categories[idx]]).enter()
      .append('g').classed('cge-' + uv.util.formatClassName(self.categories[idx]), true)
      .attr('transform', function (d) { if (d.value < 0) return 'scale(1, -1)'; });

  bars.append('rect')
      .classed(uv.util.formatClassName(self.categories[idx]), true)
      .classed('cr-' + uv.util.formatClassName(self.categories[idx]), true)
      .attr('height', 0)
      .attr('width', self.axes.hor.scale.rangeBand() / len)
      .attr('x', function (d) { return self.axes.hor.scale(d.name); })
      .attr('y', function (d) { return (d.value < 0? -1: 1) * (self.height() - self.axes.ver.scale(0)); })
      .style('stroke', self.config.bar.strokecolor).style('fill', color)
      .transition()
        .duration(self.config.effects.duration)
        .delay(idx * self.config.effects.duration)
        .attr('height', function (d) { return Math.abs(self.axes.ver.scale(0) - self.axes.ver.scale(d.value)); })
        .call(uv.util.endAll, function (d,i){
          d3.select(this.parentNode.parentNode).selectAll('rect').on('mouseover', uv.effects.bar.mouseover(self, idx));
          d3.select(this.parentNode.parentNode).selectAll('rect').on('mouseout', uv.effects.bar.mouseout(self, idx));
          if(typeof self.config.graph.clickCallback === "function") {
            d3.select(this.parentNode.parentNode).selectAll('rect').on('click', function(_d){
              self.config.graph.clickCallback.apply(null, [_d]);
          });
        }
        });


  bars.append('text').attr('transform', function (d) { return (d.value < 0) ? 'scale(1,1)': 'scale(1,-1)'; })
      .attr('x', function(d) { return self.axes.hor.scale(d.name) + (self.axes.hor.scale.rangeBand()/len)/2; })
      .attr('y', function(d) { return self.height() - self.axes.ver.scale(0) })
      .attr('dx', 0)
      .attr('dy', function (d) { return d.value < 0 ? '2em' : '.35em' })
      .attr('text-anchor', 'middle')
      .classed('cr-' + uv.util.formatClassName(self.categories[idx]), true)
      .style('fill', self.config.label.showlabel ? uv.util.getColorBand(self.config, idx) : 'none')
      .style('font-family', self.config.bar.fontfamily)
      .style('font-size', self.config.bar.fontsize)
      .style('font-weight', self.config.bar.fontweight)
      .style('opacity', 0)
      .text(function(d) { return uv.util.getLabelValue(self, d); })
      .transition()
        .duration(self.config.effects.duration)
        .delay(idx * self.config.effects.duration)
        .style('opacity', 1)
        .attr('y', function (d) { return -(self.height() - self.axes.ver.scale(d.value)) - 10; });

  bars.append('svg:title')
    .text( function (d, i) { return uv.util.getTooltipText(self, self.categories[idx], self.labels[i], d);});


  self.bargroups[self.categories[idx]].attr('transform', 'translate(' + idx * self.axes.hor.scale.rangeBand() / len + ',' + self.height() + ') scale(1,-1)');
};

uv.DonutGraph = function (graphdef, config) {
  var self = this;
  uv.Graph.call(self, graphdef, config).setDefaults().init();

  self.radius = Math.min(self.height(), self.width()) * 2 / 5;
  self.center = {
    x : self.width() / 2,
    y : self.height() / 2
  };

  self.category = graphdef.categories[0];

  var data = uv.util.getCategoryData(self.graphdef, [self.category]),
    arcfunc = d3.svg.arc().innerRadius(self.radius * self.config.donut.factor).outerRadius(self.radius),
    layout = d3.layout.pie();

  self.chart.data(data);
  self.arcs = self.chart.selectAll('g.arc')
          .data(layout).enter()
          .append('g').classed(uv.constants.classes.arc + uv.util.formatClassName(self.category), true)
          .attr('transform', 'translate(' + self.center.x + ',' + self.center.y + ')');

  self.arcs.append('path')
      .attr('d', arcfunc)
      .style('fill', function (d, i) { return uv.util.getColorBand(self.config, i); })
      .style('stroke', self.config.donut.strokecolor)
      .style('stroke-width', self.config.donut.strokewidth)
    .on('mouseover', uv.effects.donut.mouseover(self.center, arcfunc, self.config))
    .on('mouseout', uv.effects.donut.mouseout(self.center, self.config))

  if (typeof self.config.graph.clickCallback === "function") {
    self.arcs.on('click', function (d) {
      self.config.graph.clickCallback.apply(null, [d]);
    });
  }

  self.arcs.append('text')
      .attr('transform', function (d) { return 'translate(' + arcfunc.centroid(d) + ')'; })
      .attr('dy', '.35em')
      .attr('text-anchor', 'middle')
      .style('fill', self.config.label.showlabel ? self.config.donut.fontfill : 'none')
      .style('font-family', self.config.donut.fontfamily)
      .style('font-size', self.config.donut.fontsize)
      .style('font-weight', self.config.donut.fontweight)
      .style('font-variant', self.config.donut.fontvariant)
      .text(function (d) { return uv.util.getLabelValue(self, d); });

  self.arcs.append('svg:title')
    .text(function (d, i) { return uv.util.getTooltipText(self, self.category, self.labels[i], d);});

};

uv.DonutGraph.prototype = uv.util.inherits(uv.Graph);

uv.DonutGraph.prototype.setDefaults = function () {
  var self = this;
  self.graphdef.stepup = 'normal';
  return this;
};

uv.LineGraph = function (graphdef, config) {
  var self = this;
  uv.Graph.call(self, graphdef, config).setDefaults().init();

  self.linegroups = {};
  self.dataset = uv.util.getDataArray(self.graphdef);

  var linegroup, linepath, linefunc, idx, len = self.categories.length,
    domainData = self.labels;

  self.axes[self.config.graph.orientation === 'Horizontal' ? 'ver' : 'hor'].scale.domain(domainData);

  for (idx = 0; idx < len; idx = idx + 1) {
    linepath = self.chart.append('g').classed('cg-' + uv.util.formatClassName(self.categories[idx]), true)
                        .append('g').classed('cge-' + uv.util.formatClassName(self.categories[idx]), true).datum(self.dataset[idx]);
    linegroup = {
      path: linepath,
      func: undefined
    };

    self['draw' + self.config.graph.orientation + 'Lines'](linegroup, idx);
    self.linegroups[self.categories[idx]] = linegroup;
  }

  self.finalize();
};

uv.LineGraph.prototype = uv.util.inherits(uv.Graph);

uv.LineGraph.prototype.setDefaults = function () {
  var self = this;
  self.graphdef.stepup = 'normal';
  self.config.scale.ordinality = 0;
  return this;
};

uv.LineGraph.prototype.drawHorizontalLines = function (linegroup, idx) {
  var self = this,
    axes = self.axes,
    config = self.config,
    color = uv.util.getColorBand(self.config, idx);

  self.axes.ver.scale.rangePoints([0, self.height()]);

  linegroup.func = d3.svg.line()
        .x(function (d) { return axes.hor.scale(d.value); })
        .y(function (d) { return axes.ver.scale(d.name) + axes.ver.scale.rangeBand() / 2; })
        .interpolate(uv.config.line.interpolation);

  linegroup.path.append('path')
        .classed('cr-' + uv.util.formatClassName(self.categories[idx]), true)
        .attr('d', linegroup.func)
        .style('fill', 'none')
        .style('stroke', color)
        .style('stroke-width', self.config.line.strokewidth)
        .style('stroke-opacity', self.config.line.strokeopacity)
        .transition()
          .duration(3 * self.config.effects.duration)
          .delay(2 * idx * self.config.effects.duration)
          .style('stroke-opacity', 1)
          .call(uv.util.endAll, function (d,i){
            d3.select(this.parentNode.parentNode).selectAll('path').on('mouseover', uv.effects.line.mouseover(self, idx));
            d3.select(this.parentNode.parentNode).selectAll('path').on('mouseout', uv.effects.line.mouseout(self, idx));
            d3.select(this.parentNode.parentNode).selectAll('circle').on('mouseover', uv.effects.line.mouseover(self, idx));
            d3.select(this.parentNode.parentNode).selectAll('circle').on('mouseout', uv.effects.line.mouseout(self, idx));
          });

  if (self.config.line.showcircles) {
    linegroup.path.selectAll('circle')
          .data(self.dataset[idx])
          .enter().append('circle')
          .classed('cr-' + uv.util.formatClassName(self.categories[idx]), true)
          .attr('cx', linegroup.func.x())
          .attr('cy', linegroup.func.y())
          .attr('r', self.config.line.circleradius)
          .style('fill', color)
          .style('fill-opacity', self.config.line.circleopacity)
          .style('stroke', '#fff')
            .append('svg:title')
            .text( function (d, i) { return uv.util.getTooltipText(self, self.categories[idx], self.labels[i], d);});
  }

  linegroup.path.selectAll('text')
        .data(self.dataset[idx])
        .enter().append('text')
        .attr('x', function (d) { return axes.hor.scale(d.value); })
        .attr('y', function(d) { return axes.ver.scale(d.name) + axes.ver.scale.rangeBand()/2; })
        .attr('dx', 10)
        .attr('dy', '.35em')
        .attr('text-anchor', 'start')
        .style('opacity', 0)
        .classed('cr-' + uv.util.formatClassName(self.categories[idx]), true)
        .style('fill', self.config.label.showlabel ? uv.util.getColorBand(self.config, idx) : 'none')
        .style('font-family', self.config.line.fontfamily)
        .style('font-size', self.config.line.fontsize)
        .style('font-weight', self.config.line.fontweight)
        .text(function(d) { return uv.util.getLabelValue(self, d); })
        .transition()
          .duration(3 * self.config.effects.duration)
          .delay(2 * idx * self.config.effects.duration)
          .style('opacity', 1);

  return this;
};

uv.LineGraph.prototype.drawVerticalLines = function (linegroup, idx) {
  var self = this,
    axes = self.axes,
    config = self.config,
    color = uv.util.getColorBand(self.config, idx);

  self.axes.hor.scale.rangePoints([0, self.width()]);

  linegroup.func = d3.svg.line()
        .x(function (d) { return axes.hor.scale(d.name) + axes.hor.scale.rangeBand() / 2; })
        .y(function (d) { return axes.ver.scale(d.value); })
        .interpolate(uv.config.line.interpolation);

  linegroup.path.append('path')
        .attr('d', linegroup.func)
        .classed('cr-' + uv.util.formatClassName(self.categories[idx]), true)
        .style('fill', 'none')
        .style('stroke', color)
        .style('stroke-width', self.config.line.strokewidth)
        .style('stroke-opacity', self.config.line.strokeopacity)
        .transition()
          .duration(self.config.effects.duration)
          .delay(2 * idx * self.config.effects.duration)
          .style('stroke-opacity', 1)
          .call(uv.util.endAll, function (d,i){
            d3.select(this.parentNode.parentNode).selectAll('path').on('mouseover', uv.effects.line.mouseover(self, idx));
            d3.select(this.parentNode.parentNode).selectAll('path').on('mouseout', uv.effects.line.mouseout(self, idx));
            d3.select(this.parentNode.parentNode).selectAll('circle').on('mouseover', uv.effects.line.mouseover(self, idx));
            d3.select(this.parentNode.parentNode).selectAll('circle').on('mouseout', uv.effects.line.mouseout(self, idx));
          });

  if (self.config.line.showcircles) {
    linegroup.path.selectAll('circle')
          .data(self.dataset[idx])
          .enter().append('circle')
          .attr('cx', linegroup.func.x())
          .attr('cy', linegroup.func.y())
          .attr('r', self.config.line.circleradius)
          .classed('cr-' + uv.util.formatClassName(self.categories[idx]), true)
          .style('fill', color)
          .style('fill-opacity', self.config.line.circleopacity)
          .style('stroke', '#fff')
            .append('svg:title')
            .text( function (d, i) { return uv.util.getTooltipText(self, self.categories[idx], self.labels[i], d);});
  }

  linegroup.path.selectAll('text')
        .data(self.dataset[idx])
        .enter().append('text')
        .attr('x', function (d) { return axes.hor.scale(d.name) + axes.hor.scale.rangeBand() / 2; })
        .attr('y', function (d) { return axes.ver.scale(d.value) - 20; })
        .attr('dx', 0)
        .attr('dy', '.71em')
        .attr('text-anchor', 'middle')
        .classed('cr-' + uv.util.formatClassName(self.categories[idx]), true)
        .style('fill', self.config.label.showlabel ? uv.util.getColorBand(self.config, idx) : 'none')
        .style('font-family', self.config.line.fontfamily)
        .style('font-size', self.config.line.fontsize)
        .style('font-weight', self.config.line.fontweight)
        .style('opacity', 0)
        .text(function(d) { return uv.util.getLabelValue(self, d); })
        .transition()
          .duration(3 * self.config.effects.duration)
          .delay(2 * idx * self.config.effects.duration)
          .style('opacity', 1);

  return this;
};

uv.PercentAreaGraph = function (graphdef, config) {
  var self = this;
  uv.Graph.call(self, graphdef, config).setDefaults().init();

  var stacklayout = d3.layout.stack().offset('zero')(
    self.categories.map(function (d) {
      return graphdef.dataset[d].map(function (d) {
        return {x: d.name, y: +d.value};
      });
    })
  );

  var areagroup, areapath, areafunc,
    domainData = self.labels,
    categories = self.categories;

  self.axes[self.config.graph.orientation === 'Horizontal' ? 'ver' : 'hor'].scale.domain(domainData);
  self.areagroup = self.chart.selectAll('g.areagroup').data(stacklayout).enter().append('g')
                .attr('class', function (d,i) {
                  return uv.util.getClassName(this, 'cge-' + self.categories[i]);
                });

  self['draw' + self.config.graph.orientation + 'Area']();

  self.finalize(true);
};

uv.PercentAreaGraph.prototype = uv.util.inherits(uv.Graph);

uv.PercentAreaGraph.prototype.setDefaults = function () {
  var self = this;
  self.graphdef.stepup = 'percent';
  return this;
};

uv.PercentAreaGraph.prototype.drawHorizontalArea = function () {
  var self = this, axes = self.axes,
    categories = self.categories,
    config = self.config,
    sumMap = uv.util.getSumUpArray(self.graphdef);

  axes.ver.scale.rangePoints([0, self.height()]);

  for(var i = 0; i < categories.length; i = i + 1) {
    uv.effects.area.mouseover(self, i);
    uv.effects.area.mouseout(self,i);
  }

  self.areagroup.append('path')
      .attr('class', function (d, i) {
        return uv.util.getClassName(this, uv.constants.classes.area + uv.util.formatClassName(categories[i]));
      })
      .style('fill', function (d, i) { return uv.util.getColorBand(config, i); })
      .attr('d', d3.svg.area()
        .y(function (d) { return axes.ver.scale(d.x) + axes.ver.scale.rangeBand() / 2; })
        .x0(function (d, i) { return axes.hor.scale(uv.util.getPercentage(d.y0, sumMap[i])); })
        .x1(function (d, i) { return axes.hor.scale(uv.util.getPercentage(d.y0 + d.y, sumMap[i])); })
        .interpolate(self.config.area.interpolation)
    )
    .on('mouseover', function (d,i) { self.effects[categories[i]].mouseover(); })
    .on('mouseout', function (d,i) { self.effects[categories[i]].mouseout(); });

  self.areagroup.append('path')
    .attr('class', function (d, i) {
      return uv.util.getClassName(this, uv.constants.classes.line + uv.util.formatClassName(categories[i]));
    })
    .style('stroke', 'white')
    .style('fill', 'none')
    .style('stroke-width', 2)
    .attr('d', d3.svg.line()
        .y(function (d) { return axes.ver.scale(d.x) + axes.ver.scale.rangeBand() / 2; })
        .x(function (d, i) { return axes.hor.scale(uv.util.getPercentage(d.y0 + d.y, sumMap[i])); })
        .interpolate(self.config.area.interpolation)
    );
};

uv.PercentAreaGraph.prototype.drawVerticalArea = function () {
  var self = this, axes = self.axes,
    categories = self.categories,
    config = self.config,
    sumMap = uv.util.getSumUpArray(self.graphdef);

  axes.hor.scale.rangePoints([0, self.width()]);

  for(var i = 0; i < categories.length; i = i + 1){
    uv.effects.area.mouseover(self, i);
    uv.effects.area.mouseout(self,i);
  }

  self.areagroup.append('path')
      .attr('class', function (d, i) {
        return uv.util.getClassName(this, uv.constants.classes.area + uv.util.formatClassName(categories[i]));
      })
      .style('fill', function (d, i) { return uv.util.getColorBand(config, i); })
      .attr('d', d3.svg.area()
        .x(function (d) { return axes.hor.scale(d.x) + axes.hor.scale.rangeBand() / 2; })
        .y0(function (d, i) { return axes.ver.scale(uv.util.getPercentage(d.y0, sumMap[i])); })
        .y1(function (d, i) { return axes.ver.scale(uv.util.getPercentage(d.y0 + d.y, sumMap[i])); })
        .interpolate(self.config.area.interpolation)
      )
    .on('mouseover', function (d,i) {self.effects[categories[i]].mouseover(); })
    .on('mouseout', function (d,i) { self.effects[categories[i]].mouseout(); });

  self.areagroup.append('path')
      .attr('class', function (d, i) {
        return uv.util.getClassName(this, uv.constants.classes.line + uv.util.formatClassName(categories[i]));
      })
      .style('stroke', 'white')
      .style('fill', 'none')
      .style('stroke-width', 2)
      .attr('d', d3.svg.line()
        .x(function (d, i) { return axes.hor.scale(d.x) + axes.hor.scale.rangeBand() / 2; })
        .y(function (d, i) { return axes.ver.scale(uv.util.getPercentage(d.y0 + d.y, sumMap[i])); })
        .interpolate(self.config.area.interpolation)
      );
};

uv.PercentBarGraph = function (graphdef, config) {
  var self = this;
  uv.Graph.call(self, graphdef, config).setDefaults().init();

  self.bargroups = [];

  var bargroup, bars, idx, len, color,
    domainData = self.labels,
    csum = domainData.map(function (d) {return 0; }),
    tsum = domainData.map(function (d) {return 0; });

  self.axes[self.config.graph.orientation === 'Horizontal' ? 'ver' : 'hor'].scale.domain(domainData);

  for (idx = 0, len = self.categories.length; idx < len; idx = idx + 1) {
    color = uv.util.getColorBand(self.config, idx);
    bargroup = self.chart.append('g').classed('cg-' + self.categories[idx], true);
    bars = bargroup.selectAll('g').data(self.graphdef.dataset[self.categories[idx]]).enter().append('g').classed('cge-' + uv.util.formatClassName(self.categories[idx]), true);

    self['draw' + uv.util.getPascalCasedName(self.config.graph.orientation) + 'Bars'](bars, csum, tsum, idx);

    if (self.config.graph.orientation === 'Vertical') {
      bargroup.attr('transform', 'translate(0,' + 2 * self.height() + ') scale(1,-1)');
    }

    self.bargroups.push(bargroup);
  }

  self.finalize();
};

uv.PercentBarGraph.prototype = uv.util.inherits(uv.Graph);

uv.PercentBarGraph.prototype.setDefaults = function () {
  var self = this;
  self.graphdef.stepup = 'percent';
  self.config.scale.ordinality = 0;
  return this;
};

uv.PercentBarGraph.prototype.drawHorizontalBars = function (bars, csum, tsum, idx) {
  var self = this,
    axes = this.axes,
    color = uv.util.getColorBand(this.config, idx),
    config = this.config,
    sumMap = uv.util.getSumUpArray(this.graphdef);

  bars.append('rect')
    .attr('height', axes.ver.scale.rangeBand())
    .attr('width', 0)
    .attr('x', function (d, i) { var value = axes.hor.scale(uv.util.getPercentage(csum[i], sumMap[i])); csum[i] += d.value; return value; })
    .attr('y', function (d) {return axes.ver.scale(d.name); })
    .classed('cr-' + uv.util.formatClassName(self.categories[idx]), true)
    .style('stroke', 'none')
    .style('fill', color)
    .transition()
      .duration(uv.config.effects.duration)
      .delay(idx * uv.config.effects.duration)
      .attr('width', function (d, i) { return axes.hor.scale(uv.util.getPercentage(d.value, sumMap[i]));})
      .call(uv.util.endAll, function (d,i){
        d3.select(this.parentNode.parentNode).selectAll('rect').on('mouseover', uv.effects.bar.mouseover(self, idx, self.config.effects.textcolor));
        d3.select(this.parentNode.parentNode).selectAll('rect').on('mouseout', uv.effects.bar.mouseout(self, idx, self.config.effects.textcolor));
      });


  bars.append('text')
    .attr('y', function(d) { return axes.ver.scale(d.name) + axes.ver.scale.rangeBand()/2; })
    .attr('dx', 0)
    .attr('dy', '.35em')
    .attr('text-anchor', 'end')
    .classed('cr-' + uv.util.formatClassName(self.categories[idx]), true)
    .style('fill', self.config.label.showlabel ? self.config.effects.textcolor : 'none')
    .style('font-family', this.config.bar.fontfamily)
    .style('font-size', this.config.bar.fontsize)
    .style('font-weight', this.config.bar.fontweight)
    .text(function(d, i) { return ( axes.hor.scale(uv.util.getPercentage(csum[i], sumMap[i])) > 15 ) ? String(Math.round(uv.util.getPercentage(d.value, sumMap[i]))) : null; })
    .transition()
      .duration(uv.config.effects.duration)
      .delay(idx * uv.config.effects.duration)
      .attr('x', function (d, i) { tsum[i] += d.value; return axes.hor.scale(uv.util.getPercentage(tsum[i], sumMap[i])) - 5; });
};

uv.PercentBarGraph.prototype.drawVerticalBars = function (bars, csum, tsum, idx) {
  var self = this,
    height = this.height(),
    axes = this.axes,
    color = uv.util.getColorBand(this.config, idx),
    config = this.config,
    sumMap = uv.util.getSumUpArray(this.graphdef);

  bars.append('rect')
    .attr('height', 0)
    .attr('width', axes.hor.scale.rangeBand())
    .attr('x', function (d) { return axes.hor.scale(d.name); })
    .attr('y', function (d, i) { var value = axes.ver.scale(uv.util.getPercentage(csum[i], sumMap[i])); csum[i] -= d.value; return value; })
    .classed('cr-' + uv.util.formatClassName(self.categories[idx]), true)
    .style('stroke', 'none')
    .style('fill', color)
    .transition()
      .duration(uv.config.effects.duration)
      .delay(idx * uv.config.effects.duration)
      .attr('height', function (d, i) { return height - axes.ver.scale(uv.util.getPercentage(d.value, sumMap[i])); })
      .call(uv.util.endAll, function (d,i){
        d3.select(this.parentNode.parentNode).selectAll('rect').on('mouseover', uv.effects.bar.mouseover(self, idx, self.config.effects.textcolor));
        d3.select(this.parentNode.parentNode).selectAll('rect').on('mouseout', uv.effects.bar.mouseout(self, idx, self.config.effects.textcolor));
      });

  bars.append('text').attr('transform','scale(1,-1)')
    .attr('x', function(d) { return axes.hor.scale(d.name) + axes.hor.scale.rangeBand()/2; })
    .attr('y', -height + 5)
    .attr('dy', '.71em')
    .attr('text-anchor', 'middle')
    .classed('cr-' + uv.util.formatClassName(self.categories[idx]), true)
    .style('fill', self.config.label.showlabel ? self.config.effects.textcolor : 'none')
    .style('font-family', this.config.bar.fontfamily)
    .style('font-size', this.config.bar.fontsize)
    .style('font-weight', this.config.bar.fontweight)
    .text(function(d, i) { return ( height - axes.ver.scale(uv.util.getPercentage(d.value, sumMap[i])) > 15) ? String(Math.round(uv.util.getPercentage(d.value, sumMap[i]))) : null; })
    .transition()
      .duration(uv.config.effects.duration)
      .delay(idx * uv.config.effects.duration)
      .attr('y', function (d, i) { tsum[i] += d.value; return -(2*height - axes.ver.scale(uv.util.getPercentage(tsum[i], sumMap[i]))) + 5; });
};

uv.PieGraph = function (graphdef, config) {
  var self = this;
  uv.Graph.call(self, graphdef, config).setDefaults().init();

  self.radius = Math.min(self.height(), self.width()) * 2 / 5;
  self.center = {
    x : self.width() / 2,
    y : self.height() / 2
  };

  self.category = graphdef.categories[0];

  var data = uv.util.getCategoryData(self.graphdef, [self.category]),
    arcfunc = d3.svg.arc().innerRadius(0).outerRadius(self.radius),
    layout = d3.layout.pie();

  self.chart.data(data);
  self.arcs = self.chart.selectAll('g.arc')
          .data(layout).enter()
          .append('g').classed(uv.constants.classes.arc + uv.util.formatClassName(self.category), true)
          .attr('class', function (d, i){
            return uv.util.getClassName(this, 'cge-' + self.labels[i]);
          })
          .attr('transform', 'translate(' + self.center.x + ',' + self.center.y + ')');

  self.arcs.append('path')
      .attr('d', arcfunc)
      .style('fill', function (d, i) { return uv.util.getColorBand(self.config, i); })
      .style('stroke', self.config.pie.strokecolor)
      .style('stroke-width', self.config.pie.strokewidth)
    .on('mouseover', uv.effects.pie.mouseover(self, self.center, arcfunc, self.config))
    .on('mouseout', uv.effects.pie.mouseout(self, self.center, self.config));

  self.arcs.append('text')
      .attr('transform', function (d) { return 'translate(' + arcfunc.centroid(d) + ')'; })
      .attr('dy', '.35em')
      .attr('text-anchor', 'middle')
      .style('fill', self.config.label.showlabel ? self.config.donut.fontfill : 'none')
      .style('font-family', self.config.pie.fontfamily)
      .style('font-size', self.config.pie.fontsize)
      .style('font-weight', self.config.pie.fontweight)
      .style('font-variant', self.config.pie.fontvariant)
      .text(function (d) { return uv.util.getLabelValue(self, d); });

  self.arcs.append('svg:title')
    .text(function (d, i) { return uv.util.getTooltipText(self, self.category, self.labels[i], d);});

  if (typeof self.config.graph.clickCallback === "function") {
    self.arcs.on('click', function (d) {
      self.config.graph.clickCallback.apply(null, [d]);
    });
  }
};

uv.PieGraph.prototype = uv.util.inherits(uv.Graph);

uv.PieGraph.prototype.setDefaults = function () {
  var self = this;
  self.graphdef.stepup = 'normal';
  self.config.legend.legendtype = 'labels';
  return this;
};

uv.PolarAreaGraph = function (graphdef, config) {
  var self = this;

  uv.Graph.call(self, graphdef, config).setDefaults().init();

  self.maxRadius = Math.min(self.height(), self.width()) * 2/5;
  self.center = {
    x : self.width() / 2,
    y : self.height() / 2
  };

  self.category = self.categories[0];

  var data = uv.util.getCategoryData(self.graphdef, [self.category]),
    dataMap = data[0].map(function(d,i){ return d; }),
    layout = d3.layout.pie().value(function(d){return self.max()/ data[0].length; }),
    tickRadius = [],
    arcfuncs = d3.svg.arc().innerRadius(0)
            .outerRadius(function(d,i){return ((dataMap[i] * self.maxRadius) / self.max());});

  for (var i=1; i<=self.config.axis.ticks; i++) {
    tickRadius[i] = (self.maxRadius/self.config.axis.ticks) * i;
  }

  self.chart.data(data);
  self.arcs = self.chart.selectAll('g.arc')
                  .data(layout).enter()
                  .append('g').classed(uv.constants.classes.arc + uv.util.formatClassName(self.category), true)
                  .attr('transform', 'translate(' + self.center.x + ',' + self.center.y + ')');

  self.arcs.append('path')
    .attr('d', arcfuncs)
    .style('fill', function (d, i) { return uv.util.getColorBand(self.config, i);})
    .style('stroke', self.config.pie.strokecolor)
    .style('stroke-width', self.config.pie.strokewidth);

  /*self.arcs.append('text')
      .attr('transform', function (d, i) { return 'translate(' + arcfuncs.centroid(d) + ')'; })
      .attr('dy', '.35em')
      .attr('text-anchor', 'middle')
      .style('fill', self.config.pie.fontfill)
      .style('font-family', self.config.pie.fontfamily)
      .style('font-size', self.config.pie.fontsize)
      .style('font-weight', self.config.pie.fontweight)
      .style('font-variant', self.config.pie.fontvariant)
      .text(function (d) { return uv.util.getLabelValue(self, d); }); */

  self.arcs.append('svg:title')
    .text(function (d, i) { return uv.util.getTooltipText(self, self.category, self.labels[i], d);});

  self.chart.selectAll('.' + uv.constants.classes.circleticks)
    .data(tickRadius)
    .enter().append('svg:g').classed(uv.constants.classes.circleticks, true)
    .append("svg:circle")
    .attr("r", function (d, i) { return d; })
    .style("stroke", self.config.axis.strokecolor)
    .style("opacity", self.config.axis.opacity)
    .style("fill", "none")
    .attr('transform', 'translate(' + self.center.x + ',' + self.center.y + ')');

  if (typeof self.config.graph.clickCallback === "function") {
    self.arcs.on('click', function (d) {
      self.config.graph.clickCallback.apply(null, [d]);
    });
  }
};

uv.PolarAreaGraph.prototype = uv.util.inherits(uv.Graph);

uv.PolarAreaGraph.prototype.setDefaults = function () {
  var self = this;
  self.graphdef.stepup = 'normal';
  return this;
};

uv.StackedAreaGraph = function (graphdef, config) {
  var self = this;
  uv.Graph.call(self, graphdef, config).setDefaults().init();

  var stacklayout = d3.layout.stack().offset(self.config.area.offset)(self.categories.map(function (d) {
      return graphdef.dataset[d].map(function (d) { return {x: d.name, y: +d.value}; });
  }));

  self.axes[self.config.graph.orientation === 'Horizontal' ? 'ver' : 'hor'].scale.domain(self.labels.map(function (d) { return d; }));
  self.areagroup = self.chart.append('g').selectAll('g')
                      .data(stacklayout).enter().append('g').attr('class', function (d, i) {
                        return uv.util.getClassName(this, 'cge-' + self.categories[i]);
                      });

  self['draw' + self.config.graph.orientation + 'Area']();

  self.finalize();
};

uv.StackedAreaGraph.prototype = uv.util.inherits(uv.Graph);

uv.StackedAreaGraph.prototype.setDefaults = function () {
  var self = this;
  self.graphdef.stepup = 'stepup';
  return this;
};

uv.StackedAreaGraph.prototype.drawHorizontalArea = function () {
  var self = this, axes = self.axes,
    categories = self.categories,
    config = self.config;

  axes.ver.scale.rangePoints([0, self.height()]);

  for(var i = 0; i < categories.length; i = i + 1){
    uv.effects.area.mouseover(self, i);
    uv.effects.area.mouseout(self, i);
  }

  self.areagroup.append('path')
      .attr('class', function (d, i) {
        return uv.util.getClassName(this, uv.constants.classes.area + categories[i]);
      })
      .style('fill', function (d, i) { return uv.util.getColorBand(config, i); })
      .attr('d', d3.svg.area()
        .y(function (d) { return axes.ver.scale(d.x) + axes.ver.scale.rangeBand() / 2; })
        .x0(function (d) { return axes.hor.scale(d.y0); })
        .x1(function (d) { return axes.hor.scale(d.y0 + d.y); })
        .interpolate(self.config.area.interpolation)
      )
    .on('mouseover', function (d,i){ self.effects[categories[i]].mouseover(); })
    .on('mouseout',  function (d,i) { self.effects[categories[i]].mouseout(); });

  self.areagroup.append('path')
    .attr('class', function (d, i) {
      return uv.util.getClassName(this, uv.constants.classes.line + categories[i]);
    })
    .style('stroke', 'white')
    .style('fill', 'none')
    .style('stroke-width', 2)
    .attr('d', d3.svg.line()
      .y(function (d) { return axes.ver.scale(d.x) + axes.ver.scale.rangeBand() / 2; })
      .x(function (d) { return axes.hor.scale(d.y0 + d.y); })
      .interpolate(self.config.area.interpolation)
    );

  return self;
};

uv.StackedAreaGraph.prototype.drawVerticalArea = function () {
  var self = this, axes = self.axes,
    categories = self.categories,
    config = self.config;

  axes.hor.scale.rangePoints([0, self.width()]);

  for(var i = 0; i < categories.length; i = i + 1){
    uv.effects.area.mouseover(self, i);
    uv.effects.area.mouseout(self, i);
  }

  self.areagroup.append('path')
      .attr('class', function (d, i) {
        return uv.util.getClassName(this, uv.constants.classes.area + categories[i]);
      })
      .style('fill', function (d, i) { return uv.util.getColorBand(config, i); })
      .attr('d', d3.svg.area()
        .x(function (d) { return axes.hor.scale(d.x) + axes.hor.scale.rangeBand() / 2; })
        .y0(function (d) { return axes.ver.scale(d.y0); })
        .y1(function (d) { return axes.ver.scale(d.y0 + d.y); })
        .interpolate(self.config.area.interpolation)
      )
    .on('mouseover', function (d,i){ self.effects[categories[i]].mouseover(); })
    .on('mouseout',  function (d,i) { self.effects[categories[i]].mouseout(); });


  self.areagroup.append('path')
      .attr('class', function (d, i) {
        return uv.util.getClassName(this, uv.constants.classes.line + categories[i]);
      })
      .style('stroke', 'white')
      .style('fill', 'none')
      .style('stroke-width', 2)
      .attr('d', d3.svg.line()
        .x(function (d) { return axes.hor.scale(d.x) + axes.hor.scale.rangeBand() / 2; })
        .y(function (d) { return axes.ver.scale(d.y0 + d.y); })
        .interpolate(self.config.area.interpolation)
      );

  return self;
};

uv.StackedBarGraph = function (graphdef, config) {
  var self = this;
  uv.Graph.call(self, graphdef, config).setDefaults().init();

  self.bargroups = {};

  var bargroup, bars, idx, len, color,
    domainData = self.labels,
    csum = domainData.map(function (d) {return 0; }),
    tsum = domainData.map(function (d) {return 0; });

  self.axes[self.config.graph.orientation === 'Horizontal' ? 'ver' : 'hor'].scale.domain(domainData);

  for (idx = 0, len = self.categories.length; idx < len; idx = idx + 1) {
    self.bargroups[self.categories[idx]] = self.chart.append('g').classed('cg-' + uv.util.formatClassName(self.categories[idx]), true);
    self['draw' + self.config.graph.orientation + 'Bars'](idx, csum, tsum);
  }

  self.finalize();
};

uv.StackedBarGraph.prototype = uv.util.inherits(uv.Graph);

uv.StackedBarGraph.prototype.setDefaults = function () {
  var self = this;
  self.graphdef.stepup = 'stepup';
  return this;
};

uv.StackedBarGraph.prototype.drawHorizontalBars = function (idx, csum, tsum) {
  var self = this,
    axes = this.axes,
    color = uv.util.getColorBand(this.config, idx),
    config = this.config,
    bargroup = this.bargroups[this.categories[idx]];

  var bars = bargroup.selectAll('g').data(this.graphdef.dataset[self.categories[idx]])
        .enter().append('g').classed('cge-' + uv.util.formatClassName(self.categories[idx]), true);

  bars.append('rect')
    .attr('height', axes.ver.scale.rangeBand())
    .attr('width', 0)
    .attr('x', function (d, i) { var value = axes.hor.scale(csum[i]); csum[i] += d.value; return value; })
    .attr('y', function (d) {return axes.ver.scale(d.name); })
    .classed('cr-' + uv.util.formatClassName(self.categories[idx]), true)
    .style('stroke', 'none')
    .style('fill', color)
    .transition()
      .duration(uv.config.effects.duration)
      .delay(idx * uv.config.effects.duration)
      .attr('width', function (d,i) { return axes.hor.scale(csum[i]) - axes.hor.scale(csum[i]-d.value); })
      .each("end", function (d,i){
        d3.select(this).on('mouseover', uv.effects.bar.mouseover(self, idx, self.config.effects.textcolor));
        d3.select(this).on('mouseout', uv.effects.bar.mouseout(self, idx, self.config.effects.textcolor));
        if(typeof self.config.graph.clickCallback === "function") {
          d3.select(this).on('click', function(_d){
              self.config.graph.clickCallback.apply(null, [_d]);
          });
        }
      });


  bars.append('text')
    .attr('y', function(d) { return axes.ver.scale(d.name) + axes.ver.scale.rangeBand()/2; })
    .attr('dx', 0)
    .attr('dy', '.35em')
    .attr('text-anchor', 'end')
    .classed('cr-' + uv.util.formatClassName(self.categories[idx]), true)
    .style('fill', self.config.label.showlabel ? self.config.effects.textcolor : 'none')
    .style('font-family', config.bar.fontfamily)
    .style('font-size', config.bar.fontsize)
    .style('font-weight', config.bar.fontweight)
    .style('opacity', 0)
    .text(function(d) { return ( axes.hor.scale(d.value) > 15 ) ? uv.util.getLabelValue(self, d) : null; })
    .transition()
      .duration(uv.config.effects.duration)
      .delay(idx * uv.config.effects.duration)
      .style('opacity', 1)
      .attr('x', function (d, i) { tsum[i] += d.value; return axes.hor.scale(tsum[i]) - 5; });

  bars.append('svg:title')
    .text( function (d, i) { return uv.util.getTooltipText(self, self.categories[idx], self.labels[i], d);});
};

uv.StackedBarGraph.prototype.drawVerticalBars = function (idx, csum, tsum) {
  var self = this,
    height = this.height(),
    axes = this.axes,
    color = uv.util.getColorBand(this.config, idx),
    config = this.config,
    bargroup = this.bargroups[self.categories[idx]];

  var bars = bargroup.selectAll('g').data(this.graphdef.dataset[self.categories[idx]])
        .enter().append('g').classed('cge-' + uv.util.formatClassName(self.categories[idx]), true);

  bars.append('rect')
    .attr('height', 0)
    .attr('width', axes.hor.scale.rangeBand())
    .attr('x', function (d) { return axes.hor.scale(d.name); })
    .attr('y', function (d, i) { var value = axes.ver.scale(csum[i]); csum[i] -= d.value; return value; })
    .classed('cr-' + uv.util.formatClassName(self.categories[idx]), true)
    .style('stroke', 'none')
    .style('fill', color)
    .transition()
      .duration(uv.config.effects.duration)
      .delay(idx * uv.config.effects.duration)
      .attr('height', function (d,i) { return -(axes.ver.scale(-csum[i]) - axes.ver.scale(-csum[i]-d.value)); })
      .each("end", function (d,i){
        d3.select(this).on('mouseover', uv.effects.bar.mouseover(self, idx, self.config.effects.textcolor));
        d3.select(this).on('mouseout', uv.effects.bar.mouseout(self, idx, self.config.effects.textcolor));
        if(typeof self.config.graph.clickCallback === "function") {
          d3.select(this).on('click', function(_d){
            self.config.graph.clickCallback.apply(null, [_d]);
          });
        }
      });


  bars.append('text').attr('transform','scale(1,-1)')
    .attr('x', function(d) { return axes.hor.scale(d.name) + axes.hor.scale.rangeBand()/2; })
    .attr('y', -height + 5)
    .attr('dy', '.71em')
    .attr('text-anchor', 'middle')
    .classed('cr-' + uv.util.formatClassName(self.categories[idx]), true)
    .style('fill', self.config.label.showlabel ? self.config.effects.textcolor : 'none')
    .style('font-family', config.bar.fontfamily)
    .style('font-size', config.bar.fontsize)
    .style('font-weight', config.bar.fontweight)
    .style('opacity', 0)
    .text(function(d) { return ( height - axes.ver.scale(d.value) > 15) ? uv.util.getLabelValue(self, d) : null; })
    .transition()
      .duration(uv.config.effects.duration)
      .delay(idx * uv.config.effects.duration)
      .style('opacity', 1)
      .attr('y', function (d, i) { tsum[i] += d.value; return -(2*height - axes.ver.scale(tsum[i])) + 5; });

  bars.append('svg:title')
    .text( function (d, i) { return uv.util.getTooltipText(self, self.categories[idx], self.labels[i], d);});

  bargroup.attr('transform', 'translate(0,' + 2 * this.height() + ') scale(1,-1)');
};

uv.StepUpBarGraph = function (graphdef, config) {
  var self = this;
  uv.Graph.call(self, graphdef, config).setDefaults().init();

  this.bargroups = {};

  var idx, length = self.categories.length,
    csum = self.labels.map(function (d) {return 0; }),
    tsum = self.labels.map(function (d) {return 0; });

  self.axes[this.config.graph.orientation === 'Horizontal' ? 'ver' : 'hor'].scale.domain(this.labels);

  for (idx = 0; idx < length; idx = idx + 1) {
    self.bargroups[self.categories[idx]] = this.chart.append('g').classed('cg-' + uv.util.formatClassName(self.categories[idx]), true);
    self['draw' + self.config.graph.orientation + 'Bars'](idx, csum, tsum);
  }

  self.finalize();
};

uv.StepUpBarGraph.prototype = uv.util.inherits(uv.Graph);

uv.StepUpBarGraph.prototype.setDefaults = function () {
  var self = this;
  self.graphdef.stepup = 'stepup';
  return this;
};

uv.StepUpBarGraph.prototype.drawHorizontalBars = function (idx, csum, tsum) {
  var self = this, len = self.categories.length,
    color = uv.util.getColorBand(self.config, idx),
    bargroup = self.bargroups[self.categories[idx]];

  var bars = bargroup.selectAll('g').data(self.graphdef.dataset[self.categories[idx]]).enter().append('g').classed('cge-' + uv.util.formatClassName(self.categories[idx]), true);
  bars.append('rect')
    .attr('height', self.axes.ver.scale.rangeBand() / len)
    .attr('width', 0)
    .attr('x', function (d, i) {
      var value = self.axes.hor.scale(csum[i]);
      csum[i] += d.value;
      return d.value < 0 ? -value: value;
    })
    .attr('y', function (d) {return self.axes.ver.scale(d.name); })
    .classed('cr-' + uv.util.formatClassName(self.categories[idx]), true)
    .style('stroke', 'none')
    .style('fill', color)
    .style('transform', function (d) {
      return (d.value < 0) ? 'scale(-1,1)': 'scale(1,1)';
    })
    .transition()
      .duration(self.config.effects.duration)
      .delay(idx * self.config.effects.duration)
      .attr('width', function (d, i) { return Math.abs(self.axes.hor.scale(csum[i]) - self.axes.hor.scale(csum[i]-d.value)); })
      .call(uv.util.endAll, function (d,i){
        d3.select(this.parentNode.parentNode).selectAll('rect').on('mouseover', uv.effects.bar.mouseover(self, idx));
        d3.select(this.parentNode.parentNode).selectAll('rect').on('mouseout', uv.effects.bar.mouseout(self, idx));
      });

  bars.append('text')
    .attr('y', function(d) { return self.axes.ver.scale(d.name) + (self.axes.ver.scale.rangeBand()/len)/2; })
    .attr('dx', function (d) { return (d.value < 0)? -16: 4; })
    .attr('dy', '.35em')
    .attr('text-anchor', 'start')
    .classed('cr-' + uv.util.formatClassName(self.categories[idx]), true)
    .style('fill', self.config.label.showlabel ? uv.util.getColorBand(self.config, idx) : 'none')
    .style('opacity', 0)
    .style('font-family', self.config.bar.fontfamily)
    .style('font-size', self.config.bar.fontsize)
    .style('font-weight', self.config.bar.fontweight)
    .text(function(d) { return uv.util.getLabelValue(self, d); })
    .transition()
      .duration(self.config.effects.duration)
      .delay(idx * self.config.effects.duration)
      .style('opacity', 1)
      .attr('x', function (d, i) { tsum[i] += d.value; return self.axes.hor.scale(tsum[i]); });

  bars.append('svg:title')
    .text( function (d, i) { return uv.util.getTooltipText(self, self.categories[idx], self.labels[i], d);});

  bargroup.attr('transform', 'translate(0,' + idx * self.axes.ver.scale.rangeBand() / len + ')');
};

uv.StepUpBarGraph.prototype.drawVerticalBars = function (idx, csum, tsum) {
  var self = this, len = self.categories.length,
    color = uv.util.getColorBand(self.config, idx),
    bargroup = self.bargroups[self.categories[idx]],
    scaledSum = 0;

  var bars = bargroup.selectAll('g').data(self.graphdef.dataset[self.categories[idx]]).enter().append('g').classed('cge-' + uv.util.formatClassName(self.categories[idx]), true);

  bars.append('rect')
    .attr('height', 0)
    .attr('width', self.axes.hor.scale.rangeBand() / len)
    .attr('x', function (d) { return self.axes.hor.scale(d.name); })
    .attr('y', function (d, i) {
      var value = (2*self.height() - self.axes.ver.scale(csum[i]));
      csum[i] += d.value;
      return (d.value < 0)? -value: value;
    })
    .classed('cr-' + uv.util.formatClassName(self.categories[idx]), true)
    .style('stroke', 'none')
    .style('fill', color)
    .style('transform', function (d) {
      return (d.value < 0)? 'scale(1,-1)': 'scale(1,1)'
    })
    .transition()
      .duration(self.config.effects.duration)
      .delay(idx * self.config.effects.duration)
      .attr('height', function (d, i) {
        return Math.abs(self.axes.ver.scale(-csum[i]) - self.axes.ver.scale(-csum[i]-d.value));
      })
      .call(uv.util.endAll, function (d,i){
        d3.select(this.parentNode.parentNode).selectAll('rect').on('mouseover', uv.effects.bar.mouseover(self, idx));
        d3.select(this.parentNode.parentNode).selectAll('rect').on('mouseout', uv.effects.bar.mouseout(self, idx));
      });

  bars.append('text').attr('transform','scale(1,-1)')
    .attr('x', function(d) { return self.axes.hor.scale(d.name) + (self.axes.hor.scale.rangeBand()/len)/2; })
    .attr('y', -self.height() - 10)
    .attr('dy', function (d) { return (d.value < 0)? '2.3em': '.71em'})
    .attr('text-anchor', 'middle')
    .classed('cr-' + uv.util.formatClassName(self.categories[idx]), true)
    .style('fill', self.config.label.showlabel ? uv.util.getColorBand(self.config, idx) : 'none')
    .style('font-family', self.config.bar.fontfamily)
    .style('font-size', self.config.bar.fontsize)
    .style('font-weight', self.config.bar.fontweight)
    .style('opacity', 0)
    .text(function(d) { return uv.util.getLabelValue(self, d); })
    .transition()
      .duration(self.config.effects.duration)
      .delay(idx * self.config.effects.duration)
      .style('opacity', 1)
      .attr('y', function (d, i) { tsum[i] += d.value; return -(2*self.height() - self.axes.ver.scale(tsum[i])) - 10; });

  bars.append('svg:title')
    .text( function (d, i) { return uv.util.getTooltipText(self, self.categories[idx], self.labels[i], d);});

  bargroup.attr('transform', 'translate(' + idx * self.axes.hor.scale.rangeBand() / len + ',' + 2 * self.height() + ') scale(1,-1)');
};

uv.Table = function () {
  this.caption = undefined;
  this.position = undefined;
  this.graphdef = undefined;

  this.table = undefined;
  this.header = undefined;
  this.body = undefined;
  this.bodyrows = {};
};

uv.Table.prototype.init = function (graphdef, config) {
  this.graphdef = graphdef;
  this.config = uv.util.extend({}, config);
  this.position = this.config.meta.pos || 'body';

  this.table = d3.select(this.position).append('table').classed(this.config.table.tableclass, true);
  this.header = this.table.append('thead').classed(this.config.table.headerclass, true);
  this.body = this.table.append('tbody').classed(this.config.table.bodyclass, true);
  this.footer = this.table.append('tfoot').classed(this.config.table.footerclass, true);
};

uv.Table.prototype.finalize = function () {
  //console.log(this);
};

uv.TableGraph = function (graphdef, config) {
  uv.Table.apply(this, [graphdef]);
  this.init(graphdef, config);

  if (this.config.graph.orientation === 'Horizontal') {
    this.setHorTable();
  } else {
    this.setVerTable();
  }

  this.finalize();
};

uv.TableGraph.prototype = uv.util.inherits(uv.Table);

uv.TableGraph.prototype.setHorTable = function () {
  var categories = this.graphdef.categories, tableData = uv.util.getTabularArray(this.graphdef);

  categories.unshift('');
  this.header.append('tr').selectAll('td').data(categories).enter().append('td').text(function (d) { return d; });
  categories.shift();

  this.bodyrows = this.body.selectAll('tr').data(tableData)
          .enter().append('tr');

  this.bodyrows.selectAll('td').data(function (d, i) { return tableData[i]; })
          .enter().append('td')
          .attr('class', function (d, i) {
            var classNameString = (i === 0) ? 'chart3rtablelabel' : 'chart3rtabledata';
            return d3.select(this).attr('class') + classNameString;
          })
          .text(function (d) {return d; });
};

uv.TableGraph.prototype.setVerTable = function () {
  var labels = uv.util.getLabelArray(this.graphdef), dataset = this.graphdef.dataset;

  labels.unshift('');
  this.header.append('tr').selectAll('td').data(labels).enter().append('td').text(function (d) { return d; });
  labels.shift();

  this.bodyrows = this.body.selectAll('tr').data(this.graphdef.categories)
          .enter().append('tr');

  this.bodyrows.selectAll('td')
    .data(function (d) {
      var arr = [], i, len;
      arr.push(d);
      for (i = 0, len = dataset[d].length; i < len; i = i + 1) { arr.push(dataset[d][i].value); }
      return arr;
    }).enter().append('td')
      .attr('class', function (d, i) {
        var classNameString = (i === 0) ? 'chart3rtablelabel' : 'chart3rtabledata';
        return d3.select(this).attr('class') + classNameString;
      })
      .text(function (d) {return d; });
};

/**
* A waterfall chart capable of being rendered in horizontal and vertical manner
* @param {Object} graphdef Definition of the graph being rendered
* @param {Object} config   Configuration of the graph being rendered
*/
uv.WaterfallGraph = function (graphdef, config) {
  var self = this;
  uv.Graph.call(self, graphdef, config).setDefaults().init();

  self.bargroups = {};

  self.axes[self.config.graph.orientation === 'Horizontal' ? 'ver' : 'hor'].scale.domain(self.labels);

  var idx, length = self.categories.length, category;

  category = self.categories[0];
  self.bargroups[category] = self.chart.append('g').classed('cg-' + uv.util.formatClassName(category), true);
  self['draw' + self.config.graph.orientation + 'Bars'](0);

  self.finalize();
};

uv.WaterfallGraph.prototype = uv.util.inherits(uv.Graph);

uv.WaterfallGraph.prototype.setDefaults = function () {
  var self = this;
  self.graphdef.stepup = 'waterfall';
  self.config.legend.showlegends = false;
  return this;
};

uv.WaterfallGraph.prototype.drawHorizontalBars = function (idx) {
  var self = this, len = self.categories.length,
    color = uv.util.getColorBand(self.config, idx),
    bargroup = self.bargroups[self.categories[idx]];
  var  csum = 0, tsum =0;

  var bars = bargroup.selectAll('g').data(self.graphdef.dataset[self.categories[idx]]).enter().append('g').classed('cge-' + uv.util.formatClassName(self.categories[idx]), true);
  bars.append('rect')
    .attr('height', (self.axes.ver.scale.rangeBand() / len)-2)
    .attr('width', 0)
    .attr('x', function (d, i) {
      var value = (d.value < 0) ? csum + d.value : csum;
      csum += d.value;
      return self.axes.hor.scale(value); })
    .attr('y', function (d) {return self.axes.ver.scale(d.name); })
    .classed('cr-' + uv.util.formatClassName(self.categories[idx]), true)
    .style('stroke', 'none')
    .style('fill', color)
    .transition()
      .duration(self.config.effects.duration)
      .delay(idx * self.config.effects.duration)
      .attr('width', function (d) { return  self.axes.hor.scale(Math.abs(d.value)); })
      .call(uv.util.endAll, function (d,i){
        d3.select(this.parentNode.parentNode).selectAll('rect').on('mouseover', uv.effects.bar.mouseover(self, idx));
        d3.select(this.parentNode.parentNode).selectAll('rect').on('mouseout', uv.effects.bar.mouseout(self, idx));
      });

  bars.append('text')
    .attr('y', function(d) { return self.axes.ver.scale(d.name) + (self.axes.ver.scale.rangeBand()/len)/2; })
    .attr('dx', 4)
    .attr('dy', '.35em')
    .attr('text-anchor', 'start')
    .classed('cr-' + uv.util.formatClassName(self.categories[idx]), true)
    .style('fill', self.config.label.showlabel ? uv.util.getColorBand(self.config, idx) : 'none')
    .style('font-family', self.config.bar.fontfamily)
    .style('font-size', self.config.bar.fontsize)
    .style('font-weight', self.config.bar.fontweight)
    .text(function(d) { return uv.util.getLabelValue(self, d); })
    .transition()
      .duration(self.config.effects.duration)
      .delay(idx * self.config.effects.duration)
      .attr('x', function (d, i) {
        var value = d.value < 0 ? tsum : tsum + d.value;
        tsum += d.value;
        return self.axes.hor.scale(value);
      });

  bars.append('svg:title')
    .text( function (d, i) { return uv.util.getTooltipText(self, self.categories[idx], self.labels[i], d);});

  bargroup.attr('transform', 'translate(0,' + idx * self.axes.ver.scale.rangeBand() / len + ')');
};

uv.WaterfallGraph.prototype.drawVerticalBars = function (idx) {
  var self = this,
    color = uv.util.getColorBand(this.config, idx),
    len = self.categories.length;
  var csum =0, tsum = 0;

  var bars = self.bargroups[self.categories[idx]].selectAll('g').data(self.graphdef.dataset[self.categories[idx]]).enter()
      .append('g').classed('cge-' + uv.util.formatClassName(self.categories[idx]), true);

  bars.append('rect')
      .classed('cr-' + uv.util.formatClassName(self.categories[idx]), true)
      .attr('height', 0)
      .attr('width', 0)
      .attr('x', function (d) {return self.axes.hor.scale(d.name); })
      .attr('y', function(d) {
        var value = (d.value < 0) ? csum + d.value : csum;
        csum += d.value;
        return self.height() - self.axes.ver.scale(value);
      })
      .style('stroke', self.config.bar.strokecolor).style('fill', color)
      .transition()
        .duration(self.config.effects.duration)
        .delay(idx * self.config.effects.duration)
        .attr('height', function (d) {  return self.height() - self.axes.ver.scale(Math.abs(d.value)); })
        .attr('width', (self.axes.hor.scale.rangeBand() / len)-2)
        .call(uv.util.endAll, function (d,i){
          d3.select(this.parentNode.parentNode).selectAll('rect').on('mouseover', uv.effects.bar.mouseover(self, idx));
          d3.select(this.parentNode.parentNode).selectAll('rect').on('mouseout', uv.effects.bar.mouseout(self, idx));
        });


  bars.append('text').attr('transform','scale(1,-1)')
      .attr('x', function(d) { return self.axes.hor.scale(d.name) + (self.axes.hor.scale.rangeBand()/len)/2; })
      .attr('y', -10)
      .attr('dx', 0)
      .attr('dy', '.35em')
      .attr('text-anchor', 'middle')
      .classed('cr-' + uv.util.formatClassName(self.categories[idx]), true)
      .style('fill', self.config.label.showlabel ? uv.util.getColorBand(self.config, idx) : 'none')
      .style('font-family', self.config.bar.fontfamily)
      .style('font-size', self.config.bar.fontsize)
      .style('font-weight', self.config.bar.fontweight)
      .text(function(d) { return uv.util.getLabelValue(self, d); })
      .transition()
        .duration(self.config.effects.duration)
        .delay(idx * self.config.effects.duration)
        .attr('y', function (d) {
          tsum += d.value;
          var value = d.value < 0 ? tsum - d.value : tsum;
          return -(self.height() - self.axes.ver.scale(value)) - 10; });

  bars.append('svg:title')
    .text( function (d, i) { return uv.util.getTooltipText(self, self.categories[idx], self.labels[i], d);});

  self.bargroups[self.categories[idx]].attr('transform', 'translate(' + idx * self.axes.hor.scale.rangeBand() / len + ',' + self.height() + ') scale(1,-1)');
};

  return {
    chart: uv.chart
  };
})();
