You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
437 lines
18 KiB
437 lines
18 KiB
/** |
|
* jqPlot |
|
* Pure JavaScript plotting plugin using jQuery |
|
* |
|
* Version: 1.0.8 |
|
* Revision: 1250 |
|
* |
|
* Copyright (c) 2009-2013 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 its 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);
|
|
|