Raphael.fn.drawGrid = function (x, y, w, h, wv, hv, color) {
    color = color || "#000";
    var path = ["M", Math.round(x) + .5, Math.round(y) + .5, "L", Math.round(x + w) + .5, Math.round(y) + .5, Math.round(x + w) + .5, Math.round(y + h) + .5, Math.round(x) + .5, Math.round(y + h) + .5, Math.round(x) + .5, Math.round(y) + .5],
        rowHeight = h / hv,
        columnWidth = w / wv;
    for (var i = 1; i < hv; i++) {
        path = path.concat(["M", Math.round(x) + .5, Math.round(y + i * rowHeight) + .5, "H", Math.round(x + w) + .5]);
    }
    for (i = 1; i < wv; i++) {
        path = path.concat(["M", Math.round(x + i * columnWidth) + .5, Math.round(y) + .5, "V", Math.round(y + h) + .5]);
    }
    return this.path(path.join(",")).attr({stroke: color});
};

Raphael.fn.minBarChartWidth = 200;
Raphael.fn.barWidth = 20;
Raphael.fn.barPaddingX = 5;
Raphael.fn.seriesPaddingX = 25;

Raphael.fn.barChartMetrics = function (seriess, masterLabels) {
	var r = this;
	var max = 0;
	var maxValueCount = 0;
	var shouldDisplayValuesAsRatios = false;
	for (var i = 0, ii = seriess.length; i < ii; i++) {
		max = Math.max(max, Math.max.apply(Math, seriess[i].values));
		maxValueCount = Math.max(maxValueCount, seriess[i].values.length);
		
		if ( seriess[i].shouldDisplayValuesAsRatios ) {
			shouldDisplayValuesAsRatios = true;
		}
	}
	if ( shouldDisplayValuesAsRatios ) {
		max *= 100.0;
	}
	var txt = {font: '12px "Helvetica Neue", Helvetica, sans-serif', fill: "black"};
	var numberOfVerticalGridLines = 4, minMaxValue = 2;
	max = Math.ceil(Math.max(minMaxValue, max));
	max += (max % minMaxValue);
	var maxValueLabelWidth = 0;
	for (var i = 0, ii = numberOfVerticalGridLines; i <= ii; i++) {
		var t = r.text(0, 0, max - i * (max/numberOfVerticalGridLines) + ( shouldDisplayValuesAsRatios ? '%' : '' ));
		t.attr(txt);
		t.attr('text-anchor', 'start');
		maxValueLabelWidth = Math.max(maxValueLabelWidth, t.getBBox().width);
		t.remove();
	}
    var seriesWidth = maxValueCount * r.barWidth + ( maxValueCount - 1 ) * r.barPaddingX,
		leftgutter = maxValueLabelWidth + 10,
        bottomgutter = (masterLabels ? 45 : 20),
        topgutter = 20,
		width = leftgutter + seriesWidth * seriess.length + r.seriesPaddingX * ( seriess.length + 1 );
					
	return {
		max: max,
		shouldDisplayValuesAsRatios: shouldDisplayValuesAsRatios,
		txt: txt,
		numberOfVerticalGridLines: numberOfVerticalGridLines,
		minMaxValue: minMaxValue,
		maxValueLabelWidth: maxValueLabelWidth,
		seriesWidth: seriesWidth,
		leftgutter: leftgutter,
		bottomgutter: bottomgutter,
		topgutter: topgutter,
		width: width,
		chartElementWidth: width + r.minBarChartWidth
	};
}

Raphael.fn.barChart = function (metrics, height, seriess, masterLabels, colors, clickHandler, mouseOverHandler, mouseOutHandler) {
	var r = this;
	var max = metrics.max;
	var txt = metrics.txt;
	var numberOfVerticalGridLines = metrics.numberOfVerticalGridLines, minMaxValue = metrics.minMaxValue;
	var valueLabels = r.set();
	var maxValueLabelWidth = metrics.maxValueLabelWidth;
	for (var i = 0, ii = numberOfVerticalGridLines; i <= ii; i++) {
		var t = r.text(0, 0, max - i * (max/numberOfVerticalGridLines) + (metrics.shouldDisplayValuesAsRatios ? '%' : ''));
		t.attr(txt);
		t.attr('text-anchor', 'start');
		valueLabels.push(t);
	}
    var seriesWidth = metrics.seriesWidth,
		originX = r.minBarChartWidth * 0.5,
		leftgutter = metrics.leftgutter,
        bottomgutter = metrics.bottomgutter,
        topgutter = metrics.topgutter,
		width = metrics.width,
        txt1 = {font: '10px "Helvetica Neue", Helvetica, sans-serif', fill: "black"},
        Y = (height - bottomgutter - topgutter) / ( max * (metrics.shouldDisplayValuesAsRatios ? 0.01 : 1.0) ),
		gridX = originX + leftgutter,
		gridY = topgutter + .5,
		gridW = width - leftgutter,
		gridH = height - topgutter - bottomgutter;
	for (var i = 0, ii = valueLabels.length; i < ii; i++) {
		var t = valueLabels[i];
		t.attr({
			x: gridX - t.getBBox().width - 10,
			y: gridY + (gridH/numberOfVerticalGridLines) * i
		});
	}	
    r.drawGrid(gridX, gridY, gridW, gridH, seriess.length - 1, numberOfVerticalGridLines, "hsba(0.0, 0.0, 1.0, 0.1)");
    var label = r.set(),
        is_label_visible = false,
        leave_timer;
    label.push(r.text(60, 12, "Popup Label").attr(txt));
    label.push(r.text(60, 27, "Popup Sublabel").attr(txt1));
    label.hide();
    var frame = r.popup(100, 100, label, "right").attr({fill: "white", stroke: "#666", "stroke-width": 2}).hide();
	var colorhue = 0.0;
	var seriesOriginX = originX + leftgutter + r.seriesPaddingX;
    
	for (var j = 0, jj = seriess.length; j < jj; j++) {
		var data = seriess[j].values;
		var labels = seriess[j].labels;
		var barOriginX = seriesOriginX;
		for (var i = 0, ii = data.length; i < ii; i++) {
			var colorhsba = (colors ? DirectMail.hsbaFromColor(colors[i]) : {h:colorhue + 0.1*i, s:0.6, b:1.0, a:1.0});
			var color = DirectMail.hsbaDeclarationWithValues(colorhsba);
			var barHeight = Math.max( 1, Math.round(data[i] * Y) );
			var barY      = height - bottomgutter - barHeight + 0.5;
			var barX	  = barOriginX;
			var bar       = r.rect(barX, barY, r.barWidth, barHeight).attr({fill: color, "stroke-width": 0});
            
			barOriginX += r.barWidth + r.barPaddingX;
            
			(function (hoverObject, x, y, data, series, lbl, color) {
	            var timer, i = 0;
	            hoverObject.hover(function (event) {
	                clearTimeout(leave_timer);
	                var side = "right";
	                if (x + frame.getBBox().width > width) {
	                    side = "left";
	                }
					label[0].attr({text: lbl.label});
					label[1].attr({text: lbl.sublabel, fill: color});
	                var ppp = r.popup(x, y, label, side, 1);
					var popupLabelTranslation = [0, 0];
					if (!is_label_visible) {
						frame.attr({path: ppp.path});
						label.attr({translation: [ppp.dx, ppp.dy]});
					}
					else {
						popupLabelTranslation = [ppp.dx, ppp.dy];
					}
					frame.show().attr({ path: ppp.path });
					label.show().attr({ translation: popupLabelTranslation });
	                is_label_visible = true;
					if (mouseOverHandler) {
						mouseOverHandler(event, series, lbl);
					}
	            }, function (event) {
	                leave_timer = setTimeout(function () {
	                    frame.hide();
	                    label.hide();
	                    is_label_visible = false;
	                }, 1);
					if (mouseOutHandler) {
						mouseOutHandler(event, series, lbl);
					}
	            });
				if (clickHandler) {
					hoverObject.click(function (event) {
						clickHandler(event, series, lbl);
					});
				}	
	        })(bar, barX, barY, data[i], seriess[j], labels[i], color);
		}
        
		var t = r.text(0, height, masterLabels[j].label + (masterLabels[j].sublabel ? "\n" + masterLabels[j].sublabel : "")).attr(txt).toBack();
        
		t.attr("y", height - bottomgutter + 0.5 * t.getBBox().height + 10);
		t.attr("x", seriesOriginX + 0.5 * seriesWidth);
        
		seriesOriginX += seriesWidth + r.seriesPaddingX;
	}
    
    frame.toFront();
	label.toFront();
};