| 1 |
/** |
| 2 |
* jqPlot |
| 3 |
* Pure JavaScript plotting plugin using jQuery |
| 4 |
* |
| 5 |
* Version: 1.0.8 |
| 6 |
* Revision: 1250 |
| 7 |
* |
| 8 |
* Copyright (c) 2009-2013 Chris Leonello |
| 9 |
* jqPlot is currently available for use in all personal or commercial projects |
| 10 |
* under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL |
| 11 |
* version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can |
| 12 |
* choose the license that best suits your project and use it accordingly. |
| 13 |
* |
| 14 |
* Although not required, the author would appreciate an email letting him |
| 15 |
* know of any substantial use of jqPlot. You can reach the author at: |
| 16 |
* chris at jqplot dot com or see http://www.jqplot.com/info.php . |
| 17 |
* |
| 18 |
* If you are feeling kind and generous, consider supporting the project by |
| 19 |
* making a donation at: http://www.jqplot.com/donate.php . |
| 20 |
* |
| 21 |
* sprintf functions contained in jqplot.sprintf.js by Ash Searle: |
| 22 |
* |
| 23 |
* version 2007.04.27 |
| 24 |
* author Ash Searle |
| 25 |
* http://hexmen.com/blog/2007/03/printf-sprintf/ |
| 26 |
* http://hexmen.com/js/sprintf.js |
| 27 |
* The author (Ash Searle) has placed this code in the public domain: |
| 28 |
* "This code is unrestricted: you are free to use it however you like." |
| 29 |
* |
| 30 |
*/ |
| 31 |
(function($) { |
| 32 |
/** |
| 33 |
* Class: $.jqplot.PieRenderer |
| 34 |
* Plugin renderer to draw a pie chart. |
| 35 |
* x values, if present, will be used as slice labels. |
| 36 |
* y values give slice size. |
| 37 |
* |
| 38 |
* To use this renderer, you need to include the |
| 39 |
* pie renderer plugin, for example: |
| 40 |
* |
| 41 |
* > <script type="text/javascript" src="plugins/jqplot.pieRenderer.js"></script> |
| 42 |
* |
| 43 |
* Properties described here are passed into the $.jqplot function |
| 44 |
* as options on the series renderer. For example: |
| 45 |
* |
| 46 |
* > plot2 = $.jqplot('chart2', [s1, s2], { |
| 47 |
* > seriesDefaults: { |
| 48 |
* > renderer:$.jqplot.PieRenderer, |
| 49 |
* > rendererOptions:{ |
| 50 |
* > sliceMargin: 2, |
| 51 |
* > startAngle: -90 |
| 52 |
* > } |
| 53 |
* > } |
| 54 |
* > }); |
| 55 |
* |
| 56 |
* A pie plot will trigger events on the plot target |
| 57 |
* according to user interaction. All events return the event object, |
| 58 |
* the series index, the point (slice) index, and the point data for |
| 59 |
* the appropriate slice. |
| 60 |
* |
| 61 |
* 'jqplotDataMouseOver' - triggered when user mouseing over a slice. |
| 62 |
* 'jqplotDataHighlight' - triggered the first time user mouses over a slice, |
| 63 |
* if highlighting is enabled. |
| 64 |
* 'jqplotDataUnhighlight' - triggered when a user moves the mouse out of |
| 65 |
* a highlighted slice. |
| 66 |
* 'jqplotDataClick' - triggered when the user clicks on a slice. |
| 67 |
* 'jqplotDataRightClick' - tiggered when the user right clicks on a slice if |
| 68 |
* the "captureRightClick" option is set to true on the plot. |
| 69 |
*/ |
| 70 |
$.jqplot.PieRenderer = function(){ |
| 71 |
$.jqplot.LineRenderer.call(this); |
| 72 |
}; |
| 73 |
|
| 74 |
$.jqplot.PieRenderer.prototype = new $.jqplot.LineRenderer(); |
| 75 |
$.jqplot.PieRenderer.prototype.constructor = $.jqplot.PieRenderer; |
| 76 |
|
| 77 |
// called with scope of a series |
| 78 |
$.jqplot.PieRenderer.prototype.init = function(options, plot) { |
| 79 |
// Group: Properties |
| 80 |
// |
| 81 |
// prop: diameter |
| 82 |
// Outer diameter of the pie, auto computed by default |
| 83 |
this.diameter = null; |
| 84 |
// prop: padding |
| 85 |
// padding between the pie and plot edges, legend, etc. |
| 86 |
this.padding = 20; |
| 87 |
// prop: sliceMargin |
| 88 |
// angular spacing between pie slices in degrees. |
| 89 |
this.sliceMargin = 0; |
| 90 |
// prop: fill |
| 91 |
// true or false, whether to fil the slices. |
| 92 |
this.fill = true; |
| 93 |
// prop: shadowOffset |
| 94 |
// offset of the shadow from the slice and offset of |
| 95 |
// each succesive stroke of the shadow from the last. |
| 96 |
this.shadowOffset = 2; |
| 97 |
// prop: shadowAlpha |
| 98 |
// transparency of the shadow (0 = transparent, 1 = opaque) |
| 99 |
this.shadowAlpha = 0.07; |
| 100 |
// prop: shadowDepth |
| 101 |
// number of strokes to apply to the shadow, |
| 102 |
// each stroke offset shadowOffset from the last. |
| 103 |
this.shadowDepth = 5; |
| 104 |
// prop: highlightMouseOver |
| 105 |
// True to highlight slice when moused over. |
| 106 |
// This must be false to enable highlightMouseDown to highlight when clicking on a slice. |
| 107 |
this.highlightMouseOver = true; |
| 108 |
// prop: highlightMouseDown |
| 109 |
// True to highlight when a mouse button is pressed over a slice. |
| 110 |
// This will be disabled if highlightMouseOver is true. |
| 111 |
this.highlightMouseDown = false; |
| 112 |
// prop: highlightColors |
| 113 |
// an array of colors to use when highlighting a slice. |
| 114 |
this.highlightColors = []; |
| 115 |
// prop: dataLabels |
| 116 |
// Either 'label', 'value', 'percent' or an array of labels to place on the pie slices. |
| 117 |
// Defaults to percentage of each pie slice. |
| 118 |
this.dataLabels = 'percent'; |
| 119 |
// prop: showDataLabels |
| 120 |
// true to show data labels on slices. |
| 121 |
this.showDataLabels = false; |
| 122 |
// prop: dataLabelFormatString |
| 123 |
// Format string for data labels. If none, '%s' is used for "label" and for arrays, '%d' for value and '%d%%' for percentage. |
| 124 |
this.dataLabelFormatString = null; |
| 125 |
// prop: dataLabelThreshold |
| 126 |
// Threshhold in percentage (0-100) of pie area, below which no label will be displayed. |
| 127 |
// This applies to all label types, not just to percentage labels. |
| 128 |
this.dataLabelThreshold = 3; |
| 129 |
// prop: dataLabelPositionFactor |
| 130 |
// A Multiplier (0-1) of the pie radius which controls position of label on slice. |
| 131 |
// Increasing will slide label toward edge of pie, decreasing will slide label toward center of pie. |
| 132 |
this.dataLabelPositionFactor = 0.52; |
| 133 |
// prop: dataLabelNudge |
| 134 |
// Number of pixels to slide the label away from (+) or toward (-) the center of the pie. |
| 135 |
this.dataLabelNudge = 2; |
| 136 |
// prop: dataLabelCenterOn |
| 137 |
// True to center the data label at its position. |
| 138 |
// False to set the inside facing edge of the label at its position. |
| 139 |
this.dataLabelCenterOn = true; |
| 140 |
// prop: startAngle |
| 141 |
// Angle to start drawing pie in degrees. |
| 142 |
// According to orientation of canvas coordinate system: |
| 143 |
// 0 = on the positive x axis |
| 144 |
// -90 = on the positive y axis. |
| 145 |
// 90 = on the negaive y axis. |
| 146 |
// 180 or - 180 = on the negative x axis. |
| 147 |
this.startAngle = 0; |
| 148 |
this.tickRenderer = $.jqplot.PieTickRenderer; |
| 149 |
// Used as check for conditions where pie shouldn't be drawn. |
| 150 |
this._drawData = true; |
| 151 |
this._type = 'pie'; |
| 152 |
|
| 153 |
// if user has passed in highlightMouseDown option and not set highlightMouseOver, disable highlightMouseOver |
| 154 |
if (options.highlightMouseDown && options.highlightMouseOver == null) { |
| 155 |
options.highlightMouseOver = false; |
| 156 |
} |
| 157 |
|
| 158 |
$.extend(true, this, options); |
| 159 |
|
| 160 |
if (this.sliceMargin < 0) { |
| 161 |
this.sliceMargin = 0; |
| 162 |
} |
| 163 |
|
| 164 |
this._diameter = null; |
| 165 |
this._radius = null; |
| 166 |
// array of [start,end] angles arrays, one for each slice. In radians. |
| 167 |
this._sliceAngles = []; |
| 168 |
// index of the currenty highlighted point, if any |
| 169 |
this._highlightedPoint = null; |
| 170 |
|
| 171 |
// set highlight colors if none provided |
| 172 |
if (this.highlightColors.length == 0) { |
| 173 |
for (var i=0; i<this.seriesColors.length; i++){ |
| 174 |
var rgba = $.jqplot.getColorComponents(this.seriesColors[i]); |
| 175 |
var newrgb = [rgba[0], rgba[1], rgba[2]]; |
| 176 |
var sum = newrgb[0] + newrgb[1] + newrgb[2]; |
| 177 |
for (var j=0; j<3; j++) { |
| 178 |
// when darkening, lowest color component can be is 60. |
| 179 |
newrgb[j] = (sum > 570) ? newrgb[j] * 0.8 : newrgb[j] + 0.3 * (255 - newrgb[j]); |
| 180 |
newrgb[j] = parseInt(newrgb[j], 10); |
| 181 |
} |
| 182 |
this.highlightColors.push('rgb('+newrgb[0]+','+newrgb[1]+','+newrgb[2]+')'); |
| 183 |
} |
| 184 |
} |
| 185 |
|
| 186 |
this.highlightColorGenerator = new $.jqplot.ColorGenerator(this.highlightColors); |
| 187 |
|
| 188 |
plot.postParseOptionsHooks.addOnce(postParseOptions); |
| 189 |
plot.postInitHooks.addOnce(postInit); |
| 190 |
plot.eventListenerHooks.addOnce('jqplotMouseMove', handleMove); |
| 191 |
plot.eventListenerHooks.addOnce('jqplotMouseDown', handleMouseDown); |
| 192 |
plot.eventListenerHooks.addOnce('jqplotMouseUp', handleMouseUp); |
| 193 |
plot.eventListenerHooks.addOnce('jqplotClick', handleClick); |
| 194 |
plot.eventListenerHooks.addOnce('jqplotRightClick', handleRightClick); |
| 195 |
plot.postDrawHooks.addOnce(postPlotDraw); |
| 196 |
}; |
| 197 |
|
| 198 |
$.jqplot.PieRenderer.prototype.setGridData = function(plot) { |
| 199 |
// set gridData property. This will hold angle in radians of each data point. |
| 200 |
var stack = []; |
| 201 |
var td = []; |
| 202 |
var sa = this.startAngle/180*Math.PI; |
| 203 |
var tot = 0; |
| 204 |
// don't know if we have any valid data yet, so set plot to not draw. |
| 205 |
this._drawData = false; |
| 206 |
for (var i=0; i<this.data.length; i++){ |
| 207 |
if (this.data[i][1] != 0) { |
| 208 |
// we have data, O.K. to draw. |
| 209 |
this._drawData = true; |
| 210 |
} |
| 211 |
stack.push(this.data[i][1]); |
| 212 |
td.push([this.data[i][0]]); |
| 213 |
if (i>0) { |
| 214 |
stack[i] += stack[i-1]; |
| 215 |
} |
| 216 |
tot += this.data[i][1]; |
| 217 |
} |
| 218 |
var fact = Math.PI*2/stack[stack.length - 1]; |
| 219 |
|
| 220 |
for (var i=0; i<stack.length; i++) { |
| 221 |
td[i][1] = stack[i] * fact; |
| 222 |
td[i][2] = this.data[i][1]/tot; |
| 223 |
} |
| 224 |
this.gridData = td; |
| 225 |
}; |
| 226 |
|
| 227 |
$.jqplot.PieRenderer.prototype.makeGridData = function(data, plot) { |
| 228 |
var stack = []; |
| 229 |
var td = []; |
| 230 |
var tot = 0; |
| 231 |
var sa = this.startAngle/180*Math.PI; |
| 232 |
// don't know if we have any valid data yet, so set plot to not draw. |
| 233 |
this._drawData = false; |
| 234 |
for (var i=0; i<data.length; i++){ |
| 235 |
if (this.data[i][1] != 0) { |
| 236 |
// we have data, O.K. to draw. |
| 237 |
this._drawData = true; |
| 238 |
} |
| 239 |
stack.push(data[i][1]); |
| 240 |
td.push([data[i][0]]); |
| 241 |
if (i>0) { |
| 242 |
stack[i] += stack[i-1]; |
| 243 |
} |
| 244 |
tot += data[i][1]; |
| 245 |
} |
| 246 |
var fact = Math.PI*2/stack[stack.length - 1]; |
| 247 |
|
| 248 |
for (var i=0; i<stack.length; i++) { |
| 249 |
td[i][1] = stack[i] * fact; |
| 250 |
td[i][2] = data[i][1]/tot; |
| 251 |
} |
| 252 |
return td; |
| 253 |
}; |
| 254 |
|
| 255 |
function calcRadiusAdjustment(ang) { |
| 256 |
return Math.sin((ang - (ang-Math.PI) / 8 / Math.PI )/2.0); |
| 257 |
} |
| 258 |
|
| 259 |
function calcRPrime(ang1, ang2, sliceMargin, fill, lineWidth) { |
| 260 |
var rprime = 0; |
| 261 |
var ang = ang2 - ang1; |
| 262 |
var absang = Math.abs(ang); |
| 263 |
var sm = sliceMargin; |
| 264 |
if (fill == false) { |
| 265 |
sm += lineWidth; |
| 266 |
} |
| 267 |
|
| 268 |
if (sm > 0 && absang > 0.01 && absang < 6.282) { |
| 269 |
rprime = parseFloat(sm) / 2.0 / calcRadiusAdjustment(ang); |
| 270 |
} |
| 271 |
|
| 272 |
return rprime; |
| 273 |
} |
| 274 |
|
| 275 |
$.jqplot.PieRenderer.prototype.drawSlice = function (ctx, ang1, ang2, color, isShadow) { |
| 276 |
if (this._drawData) { |
| 277 |
var r = this._radius; |
| 278 |
var fill = this.fill; |
| 279 |
var lineWidth = this.lineWidth; |
| 280 |
var sm = this.sliceMargin; |
| 281 |
if (this.fill == false) { |
| 282 |
sm += this.lineWidth; |
| 283 |
} |
| 284 |
ctx.save(); |
| 285 |
ctx.translate(this._center[0], this._center[1]); |
| 286 |
|
| 287 |
var rprime = calcRPrime(ang1, ang2, this.sliceMargin, this.fill, this.lineWidth); |
| 288 |
|
| 289 |
var transx = rprime * Math.cos((ang1 + ang2) / 2.0); |
| 290 |
var transy = rprime * Math.sin((ang1 + ang2) / 2.0); |
| 291 |
|
| 292 |
if ((ang2 - ang1) <= Math.PI) { |
| 293 |
r -= rprime; |
| 294 |
} |
| 295 |
else { |
| 296 |
r += rprime; |
| 297 |
} |
| 298 |
|
| 299 |
ctx.translate(transx, transy); |
| 300 |
|
| 301 |
if (isShadow) { |
| 302 |
for (var i=0, l=this.shadowDepth; i<l; i++) { |
| 303 |
ctx.save(); |
| 304 |
ctx.translate(this.shadowOffset*Math.cos(this.shadowAngle/180*Math.PI), this.shadowOffset*Math.sin(this.shadowAngle/180*Math.PI)); |
| 305 |
doDraw(r); |
| 306 |
} |
| 307 |
for (var i=0, l=this.shadowDepth; i<l; i++) { |
| 308 |
ctx.restore(); |
| 309 |
} |
| 310 |
} |
| 311 |
|
| 312 |
else { |
| 313 |
doDraw(r); |
| 314 |
} |
| 315 |
ctx.restore(); |
| 316 |
} |
| 317 |
|
| 318 |
function doDraw (rad) { |
| 319 |
// Fix for IE and Chrome that can't seem to draw circles correctly. |
| 320 |
// ang2 should always be <= 2 pi since that is the way the data is converted. |
| 321 |
// 2Pi = 6.2831853, Pi = 3.1415927 |
| 322 |
if (ang2 > 6.282 + this.startAngle) { |
| 323 |
ang2 = 6.282 + this.startAngle; |
| 324 |
if (ang1 > ang2) { |
| 325 |
ang1 = 6.281 + this.startAngle; |
| 326 |
} |
| 327 |
} |
| 328 |
// Fix for IE, where it can't seem to handle 0 degree angles. Also avoids |
| 329 |
// ugly line on unfilled pies. |
| 330 |
if (ang1 >= ang2) { |
| 331 |
return; |
| 332 |
} |
| 333 |
|
| 334 |
ctx.beginPath(); |
| 335 |
ctx.fillStyle = color; |
| 336 |
ctx.strokeStyle = color; |
| 337 |
ctx.lineWidth = lineWidth; |
| 338 |
ctx.arc(0, 0, rad, ang1, ang2, false); |
| 339 |
ctx.lineTo(0,0); |
| 340 |
ctx.closePath(); |
| 341 |
|
| 342 |
if (fill) { |
| 343 |
ctx.fill(); |
| 344 |
} |
| 345 |
else { |
| 346 |
ctx.stroke(); |
| 347 |
} |
| 348 |
} |
| 349 |
}; |
| 350 |
|
| 351 |
// called with scope of series |
| 352 |
$.jqplot.PieRenderer.prototype.draw = function (ctx, gd, options, plot) { |
| 353 |
var i; |
| 354 |
var opts = (options != undefined) ? options : {}; |
| 355 |
// offset and direction of offset due to legend placement |
| 356 |
var offx = 0; |
| 357 |
var offy = 0; |
| 358 |
var trans = 1; |
| 359 |
var colorGenerator = new $.jqplot.ColorGenerator(this.seriesColors); |
| 360 |
if (options.legendInfo && options.legendInfo.placement == 'insideGrid') { |
| 361 |
var li = options.legendInfo; |
| 362 |
switch (li.location) { |
| 363 |
case 'nw': |
| 364 |
offx = li.width + li.xoffset; |
| 365 |
break; |
| 366 |
case 'w': |
| 367 |
offx = li.width + li.xoffset; |
| 368 |
break; |
| 369 |
case 'sw': |
| 370 |
offx = li.width + li.xoffset; |
| 371 |
break; |
| 372 |
case 'ne': |
| 373 |
offx = li.width + li.xoffset; |
| 374 |
trans = -1; |
| 375 |
break; |
| 376 |
case 'e': |
| 377 |
offx = li.width + li.xoffset; |
| 378 |
trans = -1; |
| 379 |
break; |
| 380 |
case 'se': |
| 381 |
offx = li.width + li.xoffset; |
| 382 |
trans = -1; |
| 383 |
break; |
| 384 |
case 'n': |
| 385 |
offy = li.height + li.yoffset; |
| 386 |
break; |
| 387 |
case 's': |
| 388 |
offy = li.height + li.yoffset; |
| 389 |
trans = -1; |
| 390 |
break; |
| 391 |
default: |
| 392 |
break; |
| 393 |
} |
| 394 |
} |
| 395 |
|
| 396 |
var shadow = (opts.shadow != undefined) ? opts.shadow : this.shadow; |
| 397 |
var fill = (opts.fill != undefined) ? opts.fill : this.fill; |
| 398 |
var cw = ctx.canvas.width; |
| 399 |
var ch = ctx.canvas.height; |
| 400 |
var w = cw - offx - 2 * this.padding; |
| 401 |
var h = ch - offy - 2 * this.padding; |
| 402 |
var mindim = Math.min(w,h); |
| 403 |
var d = mindim; |
| 404 |
|
| 405 |
// Fixes issue #272. Thanks hugwijst! |
| 406 |
// reset slice angles array. |
| 407 |
this._sliceAngles = []; |
| 408 |
|
| 409 |
var sm = this.sliceMargin; |
| 410 |
if (this.fill == false) { |
| 411 |
sm += this.lineWidth; |
| 412 |
} |
| 413 |
|
| 414 |
var rprime; |
| 415 |
var maxrprime = 0; |
| 416 |
|
| 417 |
var ang, ang1, ang2, shadowColor; |
| 418 |
var sa = this.startAngle / 180 * Math.PI; |
| 419 |
|
| 420 |
// have to pre-draw shadows, so loop throgh here and calculate some values also. |
| 421 |
for (var i=0, l=gd.length; i<l; i++) { |
| 422 |
ang1 = (i == 0) ? sa : gd[i-1][1] + sa; |
| 423 |
ang2 = gd[i][1] + sa; |
| 424 |
|
| 425 |
this._sliceAngles.push([ang1, ang2]); |
| 426 |
|
| 427 |
rprime = calcRPrime(ang1, ang2, this.sliceMargin, this.fill, this.lineWidth); |
| 428 |
|
| 429 |
if (Math.abs(ang2-ang1) > Math.PI) { |
| 430 |
maxrprime = Math.max(rprime, maxrprime); |
| 431 |
} |
| 432 |
} |
| 433 |
|
| 434 |
if (this.diameter != null && this.diameter > 0) { |
| 435 |
this._diameter = this.diameter - 2*maxrprime; |
| 436 |
} |
| 437 |
else { |
| 438 |
this._diameter = d - 2*maxrprime; |
| 439 |
} |
| 440 |
|
| 441 |
// Need to check for undersized pie. This can happen if |
| 442 |
// plot area too small and legend is too big. |
| 443 |
if (this._diameter < 6) { |
| 444 |
$.jqplot.log('Diameter of pie too small, not rendering.'); |
| 445 |
return; |
| 446 |
} |
| 447 |
|
| 448 |
var r = this._radius = this._diameter/2; |
| 449 |
|
| 450 |
this._center = [(cw - trans * offx)/2 + trans * offx + maxrprime * Math.cos(sa), (ch - trans*offy)/2 + trans * offy + maxrprime * Math.sin(sa)]; |
| 451 |
|
| 452 |
if (this.shadow) { |
| 453 |
for (var i=0, l=gd.length; i<l; i++) { |
| 454 |
shadowColor = 'rgba(0,0,0,'+this.shadowAlpha+')'; |
| 455 |
this.renderer.drawSlice.call (this, ctx, this._sliceAngles[i][0], this._sliceAngles[i][1], shadowColor, true); |
| 456 |
} |
| 457 |
} |
| 458 |
|
| 459 |
for (var i=0; i<gd.length; i++) { |
| 460 |
|
| 461 |
this.renderer.drawSlice.call (this, ctx, this._sliceAngles[i][0], this._sliceAngles[i][1], colorGenerator.next(), false); |
| 462 |
|
| 463 |
if (this.showDataLabels && gd[i][2]*100 >= this.dataLabelThreshold) { |
| 464 |
var fstr, avgang = (this._sliceAngles[i][0] + this._sliceAngles[i][1])/2, label; |
| 465 |
|
| 466 |
if (this.dataLabels == 'label') { |
| 467 |
fstr = this.dataLabelFormatString || '%s'; |
| 468 |
label = $.jqplot.sprintf(fstr, gd[i][0]); |
| 469 |
} |
| 470 |
else if (this.dataLabels == 'value') { |
| 471 |
fstr = this.dataLabelFormatString || '%d'; |
| 472 |
label = $.jqplot.sprintf(fstr, this.data[i][1]); |
| 473 |
} |
| 474 |
else if (this.dataLabels == 'percent') { |
| 475 |
fstr = this.dataLabelFormatString || '%d%%'; |
| 476 |
label = $.jqplot.sprintf(fstr, gd[i][2]*100); |
| 477 |
} |
| 478 |
else if (this.dataLabels.constructor == Array) { |
| 479 |
fstr = this.dataLabelFormatString || '%s'; |
| 480 |
label = $.jqplot.sprintf(fstr, this.dataLabels[i]); |
| 481 |
} |
| 482 |
|
| 483 |
var fact = (this._radius ) * this.dataLabelPositionFactor + this.sliceMargin + this.dataLabelNudge; |
| 484 |
|
| 485 |
var x = this._center[0] + Math.cos(avgang) * fact + this.canvas._offsets.left; |
| 486 |
var y = this._center[1] + Math.sin(avgang) * fact + this.canvas._offsets.top; |
| 487 |
|
| 488 |
var labelelem = $('<div class="jqplot-pie-series jqplot-data-label" style="position:absolute;">' + label + '</div>').insertBefore(plot.eventCanvas._elem); |
| 489 |
if (this.dataLabelCenterOn) { |
| 490 |
x -= labelelem.width()/2; |
| 491 |
y -= labelelem.height()/2; |
| 492 |
} |
| 493 |
else { |
| 494 |
x -= labelelem.width() * Math.sin(avgang/2); |
| 495 |
y -= labelelem.height()/2; |
| 496 |
} |
| 497 |
x = Math.round(x); |
| 498 |
y = Math.round(y); |
| 499 |
labelelem.css({left: x, top: y}); |
| 500 |
} |
| 501 |
} |
| 502 |
}; |
| 503 |
|
| 504 |
$.jqplot.PieAxisRenderer = function() { |
| 505 |
$.jqplot.LinearAxisRenderer.call(this); |
| 506 |
}; |
| 507 |
|
| 508 |
$.jqplot.PieAxisRenderer.prototype = new $.jqplot.LinearAxisRenderer(); |
| 509 |
$.jqplot.PieAxisRenderer.prototype.constructor = $.jqplot.PieAxisRenderer; |
| 510 |
|
| 511 |
|
| 512 |
// There are no traditional axes on a pie chart. We just need to provide |
| 513 |
// dummy objects with properties so the plot will render. |
| 514 |
// called with scope of axis object. |
| 515 |
$.jqplot.PieAxisRenderer.prototype.init = function(options){ |
| 516 |
// |
| 517 |
this.tickRenderer = $.jqplot.PieTickRenderer; |
| 518 |
$.extend(true, this, options); |
| 519 |
// I don't think I'm going to need _dataBounds here. |
| 520 |
// have to go Axis scaling in a way to fit chart onto plot area |
| 521 |
// and provide u2p and p2u functionality for mouse cursor, etc. |
| 522 |
// for convienence set _dataBounds to 0 and 100 and |
| 523 |
// set min/max to 0 and 100. |
| 524 |
this._dataBounds = {min:0, max:100}; |
| 525 |
this.min = 0; |
| 526 |
this.max = 100; |
| 527 |
this.showTicks = false; |
| 528 |
this.ticks = []; |
| 529 |
this.showMark = false; |
| 530 |
this.show = false; |
| 531 |
}; |
| 532 |
|
| 533 |
|
| 534 |
|
| 535 |
|
| 536 |
$.jqplot.PieLegendRenderer = function(){ |
| 537 |
$.jqplot.TableLegendRenderer.call(this); |
| 538 |
}; |
| 539 |
|
| 540 |
$.jqplot.PieLegendRenderer.prototype = new $.jqplot.TableLegendRenderer(); |
| 541 |
$.jqplot.PieLegendRenderer.prototype.constructor = $.jqplot.PieLegendRenderer; |
| 542 |
|
| 543 |
/** |
| 544 |
* Class: $.jqplot.PieLegendRenderer |
| 545 |
* Legend Renderer specific to pie plots. Set by default |
| 546 |
* when user creates a pie plot. |
| 547 |
*/ |
| 548 |
$.jqplot.PieLegendRenderer.prototype.init = function(options) { |
| 549 |
// Group: Properties |
| 550 |
// |
| 551 |
// prop: numberRows |
| 552 |
// Maximum number of rows in the legend. 0 or null for unlimited. |
| 553 |
this.numberRows = null; |
| 554 |
// prop: numberColumns |
| 555 |
// Maximum number of columns in the legend. 0 or null for unlimited. |
| 556 |
this.numberColumns = null; |
| 557 |
$.extend(true, this, options); |
| 558 |
}; |
| 559 |
|
| 560 |
// called with context of legend |
| 561 |
$.jqplot.PieLegendRenderer.prototype.draw = function() { |
| 562 |
var legend = this; |
| 563 |
if (this.show) { |
| 564 |
var series = this._series; |
| 565 |
|
| 566 |
|
| 567 |
this._elem = $(document.createElement('table')); |
| 568 |
this._elem.addClass('jqplot-table-legend'); |
| 569 |
|
| 570 |
var ss = {position:'absolute'}; |
| 571 |
if (this.background) { |
| 572 |
ss['background'] = this.background; |
| 573 |
} |
| 574 |
if (this.border) { |
| 575 |
ss['border'] = this.border; |
| 576 |
} |
| 577 |
if (this.fontSize) { |
| 578 |
ss['fontSize'] = this.fontSize; |
| 579 |
} |
| 580 |
if (this.fontFamily) { |
| 581 |
ss['fontFamily'] = this.fontFamily; |
| 582 |
} |
| 583 |
if (this.textColor) { |
| 584 |
ss['textColor'] = this.textColor; |
| 585 |
} |
| 586 |
if (this.marginTop != null) { |
| 587 |
ss['marginTop'] = this.marginTop; |
| 588 |
} |
| 589 |
if (this.marginBottom != null) { |
| 590 |
ss['marginBottom'] = this.marginBottom; |
| 591 |
} |
| 592 |
if (this.marginLeft != null) { |
| 593 |
ss['marginLeft'] = this.marginLeft; |
| 594 |
} |
| 595 |
if (this.marginRight != null) { |
| 596 |
ss['marginRight'] = this.marginRight; |
| 597 |
} |
| 598 |
|
| 599 |
this._elem.css(ss); |
| 600 |
|
| 601 |
// Pie charts legends don't go by number of series, but by number of data points |
| 602 |
// in the series. Refactor things here for that. |
| 603 |
|
| 604 |
var pad = false, |
| 605 |
reverse = false, |
| 606 |
nr, |
| 607 |
nc; |
| 608 |
var s = series[0]; |
| 609 |
var colorGenerator = new $.jqplot.ColorGenerator(s.seriesColors); |
| 610 |
|
| 611 |
if (s.show) { |
| 612 |
var pd = s.data; |
| 613 |
if (this.numberRows) { |
| 614 |
nr = this.numberRows; |
| 615 |
if (!this.numberColumns){ |
| 616 |
nc = Math.ceil(pd.length/nr); |
| 617 |
} |
| 618 |
else{ |
| 619 |
nc = this.numberColumns; |
| 620 |
} |
| 621 |
} |
| 622 |
else if (this.numberColumns) { |
| 623 |
nc = this.numberColumns; |
| 624 |
nr = Math.ceil(pd.length/this.numberColumns); |
| 625 |
} |
| 626 |
else { |
| 627 |
nr = pd.length; |
| 628 |
nc = 1; |
| 629 |
} |
| 630 |
|
| 631 |
var i, j; |
| 632 |
var tr, td1, td2; |
| 633 |
var lt, rs, color; |
| 634 |
var idx = 0; |
| 635 |
var div0, div1; |
| 636 |
|
| 637 |
for (i=0; i<nr; i++) { |
| 638 |
tr = $(document.createElement('tr')); |
| 639 |
tr.addClass('jqplot-table-legend'); |
| 640 |
|
| 641 |
if (reverse){ |
| 642 |
tr.prependTo(this._elem); |
| 643 |
} |
| 644 |
|
| 645 |
else{ |
| 646 |
tr.appendTo(this._elem); |
| 647 |
} |
| 648 |
|
| 649 |
for (j=0; j<nc; j++) { |
| 650 |
if (idx < pd.length){ |
| 651 |
lt = this.labels[idx] || pd[idx][0].toString(); |
| 652 |
color = colorGenerator.next(); |
| 653 |
if (!reverse){ |
| 654 |
if (i>0){ |
| 655 |
pad = true; |
| 656 |
} |
| 657 |
else{ |
| 658 |
pad = false; |
| 659 |
} |
| 660 |
} |
| 661 |
else{ |
| 662 |
if (i == nr -1){ |
| 663 |
pad = false; |
| 664 |
} |
| 665 |
else{ |
| 666 |
pad = true; |
| 667 |
} |
| 668 |
} |
| 669 |
rs = (pad) ? this.rowSpacing : '0'; |
| 670 |
|
| 671 |
|
| 672 |
|
| 673 |
td1 = $(document.createElement('td')); |
| 674 |
td1.addClass('jqplot-table-legend jqplot-table-legend-swatch'); |
| 675 |
td1.css({textAlign: 'center', paddingTop: rs}); |
| 676 |
|
| 677 |
div0 = $(document.createElement('div')); |
| 678 |
div0.addClass('jqplot-table-legend-swatch-outline'); |
| 679 |
div1 = $(document.createElement('div')); |
| 680 |
div1.addClass('jqplot-table-legend-swatch'); |
| 681 |
div1.css({backgroundColor: color, borderColor: color}); |
| 682 |
td1.append(div0.append(div1)); |
| 683 |
|
| 684 |
td2 = $(document.createElement('td')); |
| 685 |
td2.addClass('jqplot-table-legend jqplot-table-legend-label'); |
| 686 |
td2.css('paddingTop', rs); |
| 687 |
|
| 688 |
if (this.escapeHtml){ |
| 689 |
td2.text(lt); |
| 690 |
} |
| 691 |
else { |
| 692 |
td2.html(lt); |
| 693 |
} |
| 694 |
if (reverse) { |
| 695 |
td2.prependTo(tr); |
| 696 |
td1.prependTo(tr); |
| 697 |
} |
| 698 |
else { |
| 699 |
td1.appendTo(tr); |
| 700 |
td2.appendTo(tr); |
| 701 |
} |
| 702 |
pad = true; |
| 703 |
} |
| 704 |
idx++; |
| 705 |
} |
| 706 |
} |
| 707 |
} |
| 708 |
} |
| 709 |
return this._elem; |
| 710 |
}; |
| 711 |
|
| 712 |
$.jqplot.PieRenderer.prototype.handleMove = function(ev, gridpos, datapos, neighbor, plot) { |
| 713 |
if (neighbor) { |
| 714 |
var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data]; |
| 715 |
plot.target.trigger('jqplotDataMouseOver', ins); |
| 716 |
if (plot.series[ins[0]].highlightMouseOver && !(ins[0] == plot.plugins.pieRenderer.highlightedSeriesIndex && ins[1] == plot.series[ins[0]]._highlightedPoint)) { |
| 717 |
plot.target.trigger('jqplotDataHighlight', ins); |
| 718 |
highlight (plot, ins[0], ins[1]); |
| 719 |
} |
| 720 |
} |
| 721 |
else if (neighbor == null) { |
| 722 |
unhighlight (plot); |
| 723 |
} |
| 724 |
}; |
| 725 |
|
| 726 |
|
| 727 |
// this.eventCanvas._elem.bind($.jqplot.eventListenerHooks[i][0], {plot:this}, $.jqplot.eventListenerHooks[i][1]); |
| 728 |
|
| 729 |
// setup default renderers for axes and legend so user doesn't have to |
| 730 |
// called with scope of plot |
| 731 |
function preInit(target, data, options) { |
| 732 |
options = options || {}; |
| 733 |
options.axesDefaults = options.axesDefaults || {}; |
| 734 |
options.legend = options.legend || {}; |
| 735 |
options.seriesDefaults = options.seriesDefaults || {}; |
| 736 |
// only set these if there is a pie series |
| 737 |
var setopts = false; |
| 738 |
if (options.seriesDefaults.renderer == $.jqplot.PieRenderer) { |
| 739 |
setopts = true; |
| 740 |
} |
| 741 |
else if (options.series) { |
| 742 |
for (var i=0; i < options.series.length; i++) { |
| 743 |
if (options.series[i].renderer == $.jqplot.PieRenderer) { |
| 744 |
setopts = true; |
| 745 |
} |
| 746 |
} |
| 747 |
} |
| 748 |
|
| 749 |
if (setopts) { |
| 750 |
options.axesDefaults.renderer = $.jqplot.PieAxisRenderer; |
| 751 |
options.legend.renderer = $.jqplot.PieLegendRenderer; |
| 752 |
options.legend.preDraw = true; |
| 753 |
options.seriesDefaults.pointLabels = {show: false}; |
| 754 |
} |
| 755 |
} |
| 756 |
|
| 757 |
function postInit(target, data, options) { |
| 758 |
for (var i=0; i<this.series.length; i++) { |
| 759 |
if (this.series[i].renderer.constructor == $.jqplot.PieRenderer) { |
| 760 |
// don't allow mouseover and mousedown at same time. |
| 761 |
if (this.series[i].highlightMouseOver) { |
| 762 |
this.series[i].highlightMouseDown = false; |
| 763 |
} |
| 764 |
} |
| 765 |
} |
| 766 |
} |
| 767 |
|
| 768 |
// called with scope of plot |
| 769 |
function postParseOptions(options) { |
| 770 |
for (var i=0; i<this.series.length; i++) { |
| 771 |
this.series[i].seriesColors = this.seriesColors; |
| 772 |
this.series[i].colorGenerator = $.jqplot.colorGenerator; |
| 773 |
} |
| 774 |
} |
| 775 |
|
| 776 |
function highlight (plot, sidx, pidx) { |
| 777 |
var s = plot.series[sidx]; |
| 778 |
var canvas = plot.plugins.pieRenderer.highlightCanvas; |
| 779 |
canvas._ctx.clearRect(0,0,canvas._ctx.canvas.width, canvas._ctx.canvas.height); |
| 780 |
s._highlightedPoint = pidx; |
| 781 |
plot.plugins.pieRenderer.highlightedSeriesIndex = sidx; |
| 782 |
s.renderer.drawSlice.call(s, canvas._ctx, s._sliceAngles[pidx][0], s._sliceAngles[pidx][1], s.highlightColorGenerator.get(pidx), false); |
| 783 |
} |
| 784 |
|
| 785 |
function unhighlight (plot) { |
| 786 |
var canvas = plot.plugins.pieRenderer.highlightCanvas; |
| 787 |
canvas._ctx.clearRect(0,0, canvas._ctx.canvas.width, canvas._ctx.canvas.height); |
| 788 |
for (var i=0; i<plot.series.length; i++) { |
| 789 |
plot.series[i]._highlightedPoint = null; |
| 790 |
} |
| 791 |
plot.plugins.pieRenderer.highlightedSeriesIndex = null; |
| 792 |
plot.target.trigger('jqplotDataUnhighlight'); |
| 793 |
} |
| 794 |
|
| 795 |
function handleMove(ev, gridpos, datapos, neighbor, plot) { |
| 796 |
if (neighbor) { |
| 797 |
var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data]; |
| 798 |
var evt1 = jQuery.Event('jqplotDataMouseOver'); |
| 799 |
evt1.pageX = ev.pageX; |
| 800 |
evt1.pageY = ev.pageY; |
| 801 |
plot.target.trigger(evt1, ins); |
| 802 |
if (plot.series[ins[0]].highlightMouseOver && !(ins[0] == plot.plugins.pieRenderer.highlightedSeriesIndex && ins[1] == plot.series[ins[0]]._highlightedPoint)) { |
| 803 |
var evt = jQuery.Event('jqplotDataHighlight'); |
| 804 |
evt.which = ev.which; |
| 805 |
evt.pageX = ev.pageX; |
| 806 |
evt.pageY = ev.pageY; |
| 807 |
plot.target.trigger(evt, ins); |
| 808 |
highlight (plot, ins[0], ins[1]); |
| 809 |
} |
| 810 |
} |
| 811 |
else if (neighbor == null) { |
| 812 |
unhighlight (plot); |
| 813 |
} |
| 814 |
} |
| 815 |
|
| 816 |
function handleMouseDown(ev, gridpos, datapos, neighbor, plot) { |
| 817 |
if (neighbor) { |
| 818 |
var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data]; |
| 819 |
if (plot.series[ins[0]].highlightMouseDown && !(ins[0] == plot.plugins.pieRenderer.highlightedSeriesIndex && ins[1] == plot.series[ins[0]]._highlightedPoint)) { |
| 820 |
var evt = jQuery.Event('jqplotDataHighlight'); |
| 821 |
evt.which = ev.which; |
| 822 |
evt.pageX = ev.pageX; |
| 823 |
evt.pageY = ev.pageY; |
| 824 |
plot.target.trigger(evt, ins); |
| 825 |
highlight (plot, ins[0], ins[1]); |
| 826 |
} |
| 827 |
} |
| 828 |
else if (neighbor == null) { |
| 829 |
unhighlight (plot); |
| 830 |
} |
| 831 |
} |
| 832 |
|
| 833 |
function handleMouseUp(ev, gridpos, datapos, neighbor, plot) { |
| 834 |
var idx = plot.plugins.pieRenderer.highlightedSeriesIndex; |
| 835 |
if (idx != null && plot.series[idx].highlightMouseDown) { |
| 836 |
unhighlight(plot); |
| 837 |
} |
| 838 |
} |
| 839 |
|
| 840 |
function handleClick(ev, gridpos, datapos, neighbor, plot) { |
| 841 |
if (neighbor) { |
| 842 |
var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data]; |
| 843 |
var evt = jQuery.Event('jqplotDataClick'); |
| 844 |
evt.which = ev.which; |
| 845 |
evt.pageX = ev.pageX; |
| 846 |
evt.pageY = ev.pageY; |
| 847 |
plot.target.trigger(evt, ins); |
| 848 |
} |
| 849 |
} |
| 850 |
|
| 851 |
function handleRightClick(ev, gridpos, datapos, neighbor, plot) { |
| 852 |
if (neighbor) { |
| 853 |
var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data]; |
| 854 |
var idx = plot.plugins.pieRenderer.highlightedSeriesIndex; |
| 855 |
if (idx != null && plot.series[idx].highlightMouseDown) { |
| 856 |
unhighlight(plot); |
| 857 |
} |
| 858 |
var evt = jQuery.Event('jqplotDataRightClick'); |
| 859 |
evt.which = ev.which; |
| 860 |
evt.pageX = ev.pageX; |
| 861 |
evt.pageY = ev.pageY; |
| 862 |
plot.target.trigger(evt, ins); |
| 863 |
} |
| 864 |
} |
| 865 |
|
| 866 |
// called within context of plot |
| 867 |
// create a canvas which we can draw on. |
| 868 |
// insert it before the eventCanvas, so eventCanvas will still capture events. |
| 869 |
function postPlotDraw() { |
| 870 |
// Memory Leaks patch |
| 871 |
if (this.plugins.pieRenderer && this.plugins.pieRenderer.highlightCanvas) { |
| 872 |
this.plugins.pieRenderer.highlightCanvas.resetCanvas(); |
| 873 |
this.plugins.pieRenderer.highlightCanvas = null; |
| 874 |
} |
| 875 |
|
| 876 |
this.plugins.pieRenderer = {highlightedSeriesIndex:null}; |
| 877 |
this.plugins.pieRenderer.highlightCanvas = new $.jqplot.GenericCanvas(); |
| 878 |
|
| 879 |
// do we have any data labels? if so, put highlight canvas before those |
| 880 |
var labels = $(this.targetId+' .jqplot-data-label'); |
| 881 |
if (labels.length) { |
| 882 |
$(labels[0]).before(this.plugins.pieRenderer.highlightCanvas.createElement(this._gridPadding, 'jqplot-pieRenderer-highlight-canvas', this._plotDimensions, this)); |
| 883 |
} |
| 884 |
// else put highlight canvas before event canvas. |
| 885 |
else { |
| 886 |
this.eventCanvas._elem.before(this.plugins.pieRenderer.highlightCanvas.createElement(this._gridPadding, 'jqplot-pieRenderer-highlight-canvas', this._plotDimensions, this)); |
| 887 |
} |
| 888 |
|
| 889 |
var hctx = this.plugins.pieRenderer.highlightCanvas.setContext(); |
| 890 |
this.eventCanvas._elem.bind('mouseleave', {plot:this}, function (ev) { unhighlight(ev.data.plot); }); |
| 891 |
} |
| 892 |
|
| 893 |
$.jqplot.preInitHooks.push(preInit); |
| 894 |
|
| 895 |
$.jqplot.PieTickRenderer = function() { |
| 896 |
$.jqplot.AxisTickRenderer.call(this); |
| 897 |
}; |
| 898 |
|
| 899 |
$.jqplot.PieTickRenderer.prototype = new $.jqplot.AxisTickRenderer(); |
| 900 |
$.jqplot.PieTickRenderer.prototype.constructor = $.jqplot.PieTickRenderer; |
| 901 |
|
| 902 |
})(jQuery); |
| 903 |
|
| 904 |
|