egroupware/phpgwapi/js/jquery/jqplot/plugins/jqplot.mekkoRenderer.js

436 lines
18 KiB
JavaScript
Raw Normal View History

2011-08-10 01:10:50 +02:00
/**
* jqPlot
* Pure JavaScript plotting plugin using jQuery
*
* Version: 1.0.0b2_r947
2011-08-10 01:10:50 +02:00
*
* Copyright (c) 2009-2011 Chris Leonello
* jqPlot is currently available for use in all personal or commercial projects
* under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL
* version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can
* choose the license that best suits your project and use it accordingly.
*
* Although not required, the author would appreciate an email letting him
* know of any substantial use of jqPlot. You can reach the author at:
* chris at jqplot dot com or see http://www.jqplot.com/info.php .
*
* If you are feeling kind and generous, consider supporting the project by
* making a donation at: http://www.jqplot.com/donate.php .
*
* sprintf functions contained in jqplot.sprintf.js by Ash Searle:
*
* version 2007.04.27
* author Ash Searle
* http://hexmen.com/blog/2007/03/printf-sprintf/
* http://hexmen.com/js/sprintf.js
* The author (Ash Searle) has placed this code in the public domain:
* "This code is unrestricted: you are free to use it however you like."
*
*/
(function($) {
/**
* Class: $.jqplot.MekkoRenderer
* Draws a Mekko style chart which shows 3 dimensional data on a 2 dimensional graph.
* the <$.jqplot.MekkoAxisRenderer> should be used with mekko charts. The mekko renderer
* overrides the default legend renderer with it's own $.jqplot.MekkoLegendRenderer
* which allows more flexibility to specify number of rows and columns in the legend.
*
* Data is specified per bar in the chart. You can specify data as an array of y values, or as
* an array of [label, value] pairs. Note that labels are used only on the first series.
* Labels on subsequent series are ignored:
*
* > bar1 = [['shirts', 8],['hats', 14],['shoes', 6],['gloves', 16],['dolls', 12]];
* > bar2 = [15,6,9,13,6];
* > bar3 = [['grumpy',4],['sneezy',2],['happy',7],['sleepy',9],['doc',7]];
*
* If you want to place labels for each bar under the axis, you use the barLabels option on
* the axes. The bar labels can be styled with the ".jqplot-mekko-barLabel" css class.
*
* > barLabels = ['Mickey Mouse', 'Donald Duck', 'Goofy'];
* > axes:{xaxis:{barLabels:barLabels}}
*
*/
$.jqplot.MekkoRenderer = function(){
this.shapeRenderer = new $.jqplot.ShapeRenderer();
// prop: borderColor
// color of the borders between areas on the chart
this.borderColor = null;
// prop: showBorders
// True to draw borders lines between areas on the chart.
// False will draw borders lines with the same color as the area.
this.showBorders = true;
};
// called with scope of series.
$.jqplot.MekkoRenderer.prototype.init = function(options, plot) {
this.fill = false;
this.fillRect = true;
this.strokeRect = true;
this.shadow = false;
// width of bar on x axis.
this._xwidth = 0;
this._xstart = 0;
$.extend(true, this.renderer, options);
// set the shape renderer options
var opts = {lineJoin:'miter', lineCap:'butt', isarc:false, fillRect:this.fillRect, strokeRect:this.strokeRect};
this.renderer.shapeRenderer.init(opts);
plot.axes.x2axis._series.push(this);
this._type = 'mekko';
};
// Method: setGridData
// converts the user data values to grid coordinates and stores them
// in the gridData array. Will convert user data into appropriate
// rectangles.
// Called with scope of a series.
$.jqplot.MekkoRenderer.prototype.setGridData = function(plot) {
// recalculate the grid data
var xp = this._xaxis.series_u2p;
var yp = this._yaxis.series_u2p;
var data = this._plotData;
this.gridData = [];
// figure out width on x axis.
// this._xwidth = this._sumy / plot._sumy * this.canvas.getWidth();
this._xwidth = xp(this._sumy) - xp(0);
if (this.index>0) {
this._xstart = plot.series[this.index-1]._xstart + plot.series[this.index-1]._xwidth;
}
var totheight = this.canvas.getHeight();
var sumy = 0;
var cury;
var curheight;
for (var i=0; i<data.length; i++) {
if (data[i] != null) {
sumy += data[i][1];
cury = totheight - (sumy / this._sumy * totheight);
curheight = data[i][1] / this._sumy * totheight;
this.gridData.push([this._xstart, cury, this._xwidth, curheight]);
}
}
};
// Method: makeGridData
// converts any arbitrary data values to grid coordinates and
// returns them. This method exists so that plugins can use a series'
// linerenderer to generate grid data points without overwriting the
// grid data associated with that series.
// Called with scope of a series.
$.jqplot.MekkoRenderer.prototype.makeGridData = function(data, plot) {
// recalculate the grid data
// figure out width on x axis.
var xp = this._xaxis.series_u2p;
var totheight = this.canvas.getHeight();
var sumy = 0;
var cury;
var curheight;
var gd = [];
for (var i=0; i<data.length; i++) {
if (data[i] != null) {
sumy += data[i][1];
cury = totheight - (sumy / this._sumy * totheight);
curheight = data[i][1] / this._sumy * totheight;
gd.push([this._xstart, cury, this._xwidth, curheight]);
}
}
return gd;
};
// called within scope of series.
$.jqplot.MekkoRenderer.prototype.draw = function(ctx, gd, options) {
var i;
var opts = (options != undefined) ? options : {};
var showLine = (opts.showLine != undefined) ? opts.showLine : this.showLine;
var colorGenerator = new $.jqplot.ColorGenerator(this.seriesColors);
ctx.save();
if (gd.length) {
if (showLine) {
for (i=0; i<gd.length; i++){
opts.fillStyle = colorGenerator.next();
if (this.renderer.showBorders) {
opts.strokeStyle = this.renderer.borderColor;
}
else {
opts.strokeStyle = opts.fillStyle;
}
this.renderer.shapeRenderer.draw(ctx, gd[i], opts);
}
}
}
ctx.restore();
};
$.jqplot.MekkoRenderer.prototype.drawShadow = function(ctx, gd, options) {
// This is a no-op, no shadows on mekko charts.
};
/**
* Class: $.jqplot.MekkoLegendRenderer
* Legend renderer used by mekko charts with options for
* controlling number or rows and columns as well as placement
* outside of plot area.
*
*/
$.jqplot.MekkoLegendRenderer = function(){
//
};
$.jqplot.MekkoLegendRenderer.prototype.init = function(options) {
// prop: numberRows
// Maximum number of rows in the legend. 0 or null for unlimited.
this.numberRows = null;
// prop: numberColumns
// Maximum number of columns in the legend. 0 or null for unlimited.
this.numberColumns = null;
// this will override the placement option on the Legend object
this.placement = "outside";
$.extend(true, this, options);
};
// called with scope of legend
$.jqplot.MekkoLegendRenderer.prototype.draw = function() {
var legend = this;
if (this.show) {
var series = this._series;
var ss = 'position:absolute;';
ss += (this.background) ? 'background:'+this.background+';' : '';
ss += (this.border) ? 'border:'+this.border+';' : '';
ss += (this.fontSize) ? 'font-size:'+this.fontSize+';' : '';
ss += (this.fontFamily) ? 'font-family:'+this.fontFamily+';' : '';
ss += (this.textColor) ? 'color:'+this.textColor+';' : '';
this._elem = $('<table class="jqplot-table-legend" style="'+ss+'"></table>');
// Mekko charts legends don't go by number of series, but by number of data points
// in the series. Refactor things here for that.
var pad = false,
reverse = true, // mekko charts are always stacked, so reverse
nr, nc;
var s = series[0];
var colorGenerator = new $.jqplot.ColorGenerator(s.seriesColors);
if (s.show) {
var pd = s.data;
if (this.numberRows) {
nr = this.numberRows;
if (!this.numberColumns){
nc = Math.ceil(pd.length/nr);
}
else{
nc = this.numberColumns;
}
}
else if (this.numberColumns) {
nc = this.numberColumns;
nr = Math.ceil(pd.length/this.numberColumns);
}
else {
nr = pd.length;
nc = 1;
}
var i, j, tr, td1, td2, lt, rs, color;
var idx = 0;
for (i=0; i<nr; i++) {
if (reverse){
tr = $('<tr class="jqplot-table-legend"></tr>').prependTo(this._elem);
}
else{
tr = $('<tr class="jqplot-table-legend"></tr>').appendTo(this._elem);
}
for (j=0; j<nc; j++) {
if (idx < pd.length) {
lt = this.labels[idx] || pd[idx][0].toString();
color = colorGenerator.next();
if (!reverse){
if (i>0){
pad = true;
}
else{
pad = false;
}
}
else{
if (i == nr -1){
pad = false;
}
else{
pad = true;
}
}
rs = (pad) ? this.rowSpacing : '0';
td1 = $('<td class="jqplot-table-legend" style="text-align:center;padding-top:'+rs+';">'+
'<div><div class="jqplot-table-legend-swatch" style="border-color:'+color+';"></div>'+
'</div></td>');
td2 = $('<td class="jqplot-table-legend" style="padding-top:'+rs+';"></td>');
if (this.escapeHtml){
td2.text(lt);
}
else {
td2.html(lt);
}
if (reverse) {
td2.prependTo(tr);
td1.prependTo(tr);
}
else {
td1.appendTo(tr);
td2.appendTo(tr);
}
pad = true;
}
idx++;
}
}
tr = null;
td1 = null;
td2 = null;
}
}
return this._elem;
};
$.jqplot.MekkoLegendRenderer.prototype.pack = function(offsets) {
if (this.show) {
// fake a grid for positioning
var grid = {_top:offsets.top, _left:offsets.left, _right:offsets.right, _bottom:this._plotDimensions.height - offsets.bottom};
if (this.placement == 'insideGrid') {
switch (this.location) {
case 'nw':
var a = grid._left + this.xoffset;
var b = grid._top + this.yoffset;
this._elem.css('left', a);
this._elem.css('top', b);
break;
case 'n':
var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2;
var b = grid._top + this.yoffset;
this._elem.css('left', a);
this._elem.css('top', b);
break;
case 'ne':
var a = offsets.right + this.xoffset;
var b = grid._top + this.yoffset;
this._elem.css({right:a, top:b});
break;
case 'e':
var a = offsets.right + this.xoffset;
var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2;
this._elem.css({right:a, top:b});
break;
case 'se':
var a = offsets.right + this.xoffset;
var b = offsets.bottom + this.yoffset;
this._elem.css({right:a, bottom:b});
break;
case 's':
var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2;
var b = offsets.bottom + this.yoffset;
this._elem.css({left:a, bottom:b});
break;
case 'sw':
var a = grid._left + this.xoffset;
var b = offsets.bottom + this.yoffset;
this._elem.css({left:a, bottom:b});
break;
case 'w':
var a = grid._left + this.xoffset;
var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2;
this._elem.css({left:a, top:b});
break;
default: // same as 'se'
var a = grid._right - this.xoffset;
var b = grid._bottom + this.yoffset;
this._elem.css({right:a, bottom:b});
break;
}
}
else {
switch (this.location) {
case 'nw':
var a = this._plotDimensions.width - grid._left + this.xoffset;
var b = grid._top + this.yoffset;
this._elem.css('right', a);
this._elem.css('top', b);
break;
case 'n':
var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2;
var b = this._plotDimensions.height - grid._top + this.yoffset;
this._elem.css('left', a);
this._elem.css('bottom', b);
break;
case 'ne':
var a = this._plotDimensions.width - offsets.right + this.xoffset;
var b = grid._top + this.yoffset;
this._elem.css({left:a, top:b});
break;
case 'e':
var a = this._plotDimensions.width - offsets.right + this.xoffset;
var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2;
this._elem.css({left:a, top:b});
break;
case 'se':
var a = this._plotDimensions.width - offsets.right + this.xoffset;
var b = offsets.bottom + this.yoffset;
this._elem.css({left:a, bottom:b});
break;
case 's':
var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2;
var b = this._plotDimensions.height - offsets.bottom + this.yoffset;
this._elem.css({left:a, top:b});
break;
case 'sw':
var a = this._plotDimensions.width - grid._left + this.xoffset;
var b = offsets.bottom + this.yoffset;
this._elem.css({right:a, bottom:b});
break;
case 'w':
var a = this._plotDimensions.width - grid._left + this.xoffset;
var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2;
this._elem.css({right:a, top:b});
break;
default: // same as 'se'
var a = grid._right - this.xoffset;
var b = grid._bottom + this.yoffset;
this._elem.css({right:a, bottom:b});
break;
}
}
}
};
// setup default renderers for axes and legend so user doesn't have to
// called with scope of plot
function preInit(target, data, options) {
options = options || {};
options.axesDefaults = options.axesDefaults || {};
options.legend = options.legend || {};
options.seriesDefaults = options.seriesDefaults || {};
var setopts = false;
if (options.seriesDefaults.renderer == $.jqplot.MekkoRenderer) {
setopts = true;
}
else if (options.series) {
for (var i=0; i < options.series.length; i++) {
if (options.series[i].renderer == $.jqplot.MekkoRenderer) {
setopts = true;
}
}
}
if (setopts) {
options.axesDefaults.renderer = $.jqplot.MekkoAxisRenderer;
options.legend.renderer = $.jqplot.MekkoLegendRenderer;
options.legend.preDraw = true;
}
}
$.jqplot.preInitHooks.push(preInit);
})(jQuery);