1/* 2Flot plugin for rendering pie charts. The plugin assumes the data is 3coming is as a single data value for each series, and each of those 4values is a positive value or zero (negative numbers don't make 5any sense and will cause strange effects). The data values do 6NOT need to be passed in as percentage values because it 7internally calculates the total and percentages. 8 9* Created by Brian Medendorp, June 2009 10* Updated November 2009 with contributions from: btburnett3, Anthony Aragues and Xavi Ivars 11 12* Changes: 13 2009-10-22: lineJoin set to round 14 2009-10-23: IE full circle fix, donut 15 2009-11-11: Added basic hover from btburnett3 - does not work in IE, and center is off in Chrome and Opera 16 2009-11-17: Added IE hover capability submitted by Anthony Aragues 17 2009-11-18: Added bug fix submitted by Xavi Ivars (issues with arrays when other JS libraries are included as well) 18 19 20Available options are: 21series: { 22 pie: { 23 show: true/false 24 radius: 0-1 for percentage of fullsize, or a specified pixel length, or 'auto' 25 innerRadius: 0-1 for percentage of fullsize or a specified pixel length, for creating a donut effect 26 startAngle: 0-2 factor of PI used for starting angle (in radians) i.e 3/2 starts at the top, 0 and 2 have the same result 27 tilt: 0-1 for percentage to tilt the pie, where 1 is no tilt, and 0 is completely flat (nothing will show) 28 offset: { 29 top: integer value to move the pie up or down
30 left: integer value to move the pie left or right, or 'auto' 31 }, 32 stroke: { 33 color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#FFF') 34 width: integer pixel width of the stroke 35 }, 36 label: { 37 show: true/false, or 'auto' 38 formatter: a user-defined function that modifies the text/style of the label text 39 radius: 0-1 for percentage of fullsize, or a specified pixel length 40 background: { 41 color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#000') 42 opacity: 0-1 43 }, 44 threshold: 0-1 for the percentage value at which to hide labels (if they're too small) 45 }, 46 combine: { 47 threshold: 0-1 for the percentage value at which to combine slices (if they're too small) 48 color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#CCC'), if null, the plugin will automatically use the color of the first slice to be combined 49 label: any text value of what the combined slice should be labeled 50 } 51 highlight: { 52 opacity: 0-1 53 } 54 } 55} 56 57More detail and specific examples can be found in the included HTML file. 58 59*/ 60 61(function ($) 62{ 63 function init(plot) // this is the "body" of the plugin 64 { 65 var canvas = null; 66 var target = null; 67 var maxRadius = null; 68 var centerLeft = null; 69 var centerTop = null; 70 var total = 0; 71 var redraw = true; 72 var redrawAttempts = 10; 73 var shrink = 0.95; 74 var legendWidth = 0; 75 var processed = false; 76 var raw = false; 77 78 // interactive variables 79 var highlights = []; 80 81 // add hook to determine if pie plugin in enabled, and then perform necessary operations 82 plot.hooks.processOptions.push(checkPieEnabled); 83 plot.hooks.bindEvents.push(bindEvents); 84 85 // check to see if the pie plugin is enabled 86 function checkPieEnabled(plot, options) 87 { 88 if (options.series.pie.show) 89 { 90 //disable grid 91 options.grid.show = false; 92 93 // set labels.show 94 if (options.series.pie.label.show=='auto') 95 if (options.legend.show) 96 options.series.pie.label.show = false; 97 else 98 options.series.pie.label.show = true; 99 100 // set radius 101 if (options.series.pie.radius=='auto') 102 if (options.series.pie.label.show) 103 options.series.pie.radius = 3/4; 104 else 105 options.series.pie.radius = 1; 106 107 // ensure sane tilt 108 if (options.series.pie.tilt>1) 109 options.series.pie.tilt=1; 110 if (options.series.pie.tilt<0) 111 options.series.pie.tilt=0; 112 113 // add processData hook to do transformations on the data 114 plot.hooks.processDatapoints.push(processDatapoints); 115 plot.hooks.drawOverlay.push(drawOverlay); 116 117 // add draw hook 118 plot.hooks.draw.push(draw); 119 } 120 } 121 122 // bind hoverable events 123 function bindEvents(plot, eventHolder) 124 { 125 var options = plot.getOptions(); 126 127 if (options.series.pie.show && options.grid.hoverable) 128 eventHolder.unbind('mousemove').mousemove(onMouseMove); 129 130 if (options.series.pie.show && options.grid.clickable) 131 eventHolder.unbind('click').click(onClick); 132 } 133 134 135 // debugging function that prints out an object 136 function alertObject(obj) 137 { 138 var msg = ''; 139 function traverse(obj, depth) 140 { 141 if (!depth) 142 depth = 0; 143 for (var i = 0; i < obj.length; ++i) 144 { 145 for (var j=0; j<depth; j++) 146 msg += '\t'; 147 148 if( typeof obj[i] == "object") 149 { // its an object 150 msg += ''+i+':\n'; 151 traverse(obj[i], depth+1); 152 } 153 else 154 { // its a value 155 msg += ''+i+': '+obj[i]+'\n'; 156 } 157 } 158 } 159 traverse(obj); 160 alert(msg); 161 } 162 163 function calcTotal(data) 164 { 165 for (var i = 0; i < data.length; ++i) 166 { 167 var item = parseFloat(data[i].data[0][1]); 168 if (item) 169 total += item; 170 } 171 } 172 173 function processDatapoints(plot, series, data, datapoints) 174 { 175 if (!processed) 176 { 177 processed = true; 178 179 canvas = plot.getCanvas(); 180 target = $(canvas).parent(); 181 options = plot.getOptions(); 182 183 plot.setData(combine(plot.getData())); 184 } 185 } 186 187 function setupPie() 188 { 189 legendWidth = target.children().filter('.legend').children().width(); 190 191 // calculate maximum radius and center point 192 maxRadius = Math.min(canvas.width,(canvas.height/options.series.pie.tilt))/2; 193 centerTop = (canvas.height/2)+options.series.pie.offset.top; 194 centerLeft = (canvas.width/2); 195 196 if (options.series.pie.offset.left=='auto') 197 if (options.legend.position.match('w')) 198 centerLeft += legendWidth/2; 199 else 200 centerLeft -= legendWidth/2; 201 else 202 centerLeft += options.series.pie.offset.left; 203 204 if (centerLeft<maxRadius) 205 centerLeft = maxRadius; 206 else if (centerLeft>canvas.width-maxRadius) 207 centerLeft = canvas.width-maxRadius; 208 } 209
210 function fixData(data) 211 { 212 for (var i = 0; i < data.length; ++i) 213 { 214 if (typeof(data[i].data)=='number') 215 data[i].data = [[1,data[i].data]]; 216 else if (typeof(data[i].data)=='undefined' || typeof(data[i].data[0])=='undefined') 217 { 218 if (typeof(data[i].data)!='undefined' && typeof(data[i].data.label)!='undefined') 219 data[i].label = data[i].data.label; // fix weirdness coming from flot 220 data[i].data = [[1,0]]; 221 222 } 223 } 224 return data; 225 } 226 227 function combine(data) 228 { 229 data = fixData(data); 230 calcTotal(data); 231 var combined = 0; 232 var numCombined = 0; 233 var color = options.series.pie.combine.color; 234 235 var newdata = []; 236 for (var i = 0; i < data.length; ++i) 237 { 238 // make sure its a number 239 data[i].data[0][1] = parseFloat(data[i].data[0][1]); 240 if (!data[i].data[0][1]) 241 data[i].data[0][1] = 0; 242 243 if (data[i].data[0][1]/total<=options.series.pie.combine.threshold) 244 { 245 combined += data[i].data[0][1]; 246 numCombined++; 247 if (!color) 248 color = data[i].color; 249 } 250 else 251 { 252 newdata.push({ 253 data: [[1,data[i].data[0][1]]], 254 color: data[i].color, 255 label: data[i].label, 256 angle: (data[i].data[0][1]*(Math.PI*2))/total, 257 percent: (data[i].data[0][1]/total*100) 258 }); 259 } 260 } 261 if (numCombined>0) 262 newdata.push({ 263 data: [[1,combined]], 264 color: color, 265 label: options.series.pie.combine.label, 266 angle: (combined*(Math.PI*2))/total, 267 percent: (combined/total*100) 268 }); 269 return newdata; 270 } 271 272 function draw(plot, newCtx) 273 { 274 if (!target) return; // if no series were passed 275 ctx = newCtx; 276 277 setupPie(); 278 var slices = plot.getData(); 279 280 var attempts = 0; 281 while (redraw && attempts<redrawAttempts) 282 { 283 redraw = false; 284 if (attempts>0) 285 maxRadius *= shrink; 286 attempts += 1; 287 clear(); 288 if (options.series.pie.tilt<=0.8) 289 drawShadow(); 290 drawPie(); 291 }
292 if (attempts >= redrawAttempts) { 293 clear(); 294 target.prepend('<div class="error">Could not draw pie with labels contained inside canvas</div>'); 295 } 296 297 if ( plot.setSeries && plot.insertLegend ) 298 { 299 plot.setSeries(slices); 300 plot.insertLegend(); 301 } 302 303 // we're actually done at this point, just defining internal functions at this point 304 305 function clear() 306 { 307 ctx.clearRect(0,0,canvas.width,canvas.height); 308 target.children().filter('.pieLabel, .pieLabelBackground').remove(); 309 } 310 311 function drawShadow() 312 { 313 var shadowLeft = 5; 314 var shadowTop = 15; 315 var edge = 10; 316 var alpha = 0.02; 317 318 // set radius 319 if (options.series.pie.radius>1) 320 var radius = options.series.pie.radius; 321 else 322 var radius = maxRadius * options.series.pie.radius; 323 324 if (radius>=(canvas.width/2)-shadowLeft || radius*options.series.pie.tilt>=(canvas.height/2)-shadowTop || radius<=edge) 325 return; // shadow would be outside canvas, so don't draw it 326 327 ctx.save(); 328 ctx.translate(shadowLeft,shadowTop); 329 ctx.globalAlpha = alpha; 330 ctx.fillStyle = '#000'; 331 332 // center and rotate to starting position 333 ctx.translate(centerLeft,centerTop); 334 ctx.scale(1, options.series.pie.tilt); 335 336 //radius -= edge; 337 for (var i=1; i<=edge; i++) 338 { 339 ctx.beginPath(); 340 ctx.arc(0,0,radius,0,Math.PI*2,false); 341 ctx.fill(); 342 radius -= i; 343 } 344 345 ctx.restore(); 346 } 347 348 function drawPie() 349 { 350 startAngle = Math.PI*options.series.pie.startAngle; 351 352 // set radius 353 if (options.series.pie.radius>1) 354 var radius = options.series.pie.radius; 355 else 356 var radius = maxRadius * options.series.pie.radius; 357 358 // center and rotate to starting position 359 ctx.save(); 360 ctx.translate(centerLeft,centerTop); 361 ctx.scale(1, options.series.pie.tilt); 362 //ctx.rotate(startAngle); // start at top; -- This doesn't work properly in Opera 363 364 // draw slices 365 ctx.save(); 366 var currentAngle = startAngle; 367 for (var i = 0; i < slices.length; ++i) 368 { 369 slices[i].startAngle = currentAngle; 370 drawSlice(slices[i].angle, slices[i].color, true); 371 } 372 ctx.restore(); 373 374 // draw slice outlines 375 ctx.save(); 376 ctx.lineWidth = options.series.pie.stroke.width; 377 currentAngle = startAngle; 378 for (var i = 0; i < slices.length; ++i) 379 drawSlice(slices[i].angle, options.series.pie.stroke.color, false); 380 ctx.restore(); 381 382 // draw donut hole 383 drawDonutHole(ctx); 384 385 // draw labels 386 if (options.series.pie.label.show) 387 drawLabels(); 388 389 // restore to original state 390 ctx.restore(); 391 392 function drawSlice(angle, color, fill) 393 { 394 if (angle<=0) 395 return; 396 397 if (fill) 398 ctx.fillStyle = color; 399 else 400 { 401 ctx.strokeStyle = color; 402 ctx.lineJoin = 'round'; 403 } 404 405 ctx.beginPath(); 406 if (Math.abs(angle - Math.PI*2) > 0.000000001) 407 ctx.moveTo(0,0); // Center of the pie 408 else if ($.browser.msie) 409 angle -= 0.0001; 410 //ctx.arc(0,0,radius,0,angle,false); // This doesn't work properly in Opera 411 ctx.arc(0,0,radius,currentAngle,currentAngle+angle,false); 412 ctx.closePath(); 413 //ctx.rotate(angle); // This doesn't work properly in Opera 414 currentAngle += angle; 415 416 if (fill) 417 ctx.fill(); 418 else 419 ctx.stroke(); 420 } 421 422 function drawLabels() 423 { 424 var currentAngle = startAngle; 425 426 // set radius 427 if (options.series.pie.label.radius>1) 428 var radius = options.series.pie.label.radius; 429 else 430 var radius = maxRadius * options.series.pie.label.radius; 431 432 for (var i = 0; i < slices.length; ++i) 433 { 434 if (slices[i].percent >= options.series.pie.label.threshold*100) 435 drawLabel(slices[i], currentAngle, i); 436 currentAngle += slices[i].angle; 437 } 438 439 function drawLabel(slice, startAngle, index) 440 { 441 if (slice.data[0][1]==0) 442 return; 443 444 // format label text 445 var lf = options.legend.labelFormatter, text, plf = options.series.pie.label.formatter; 446 if (lf) 447 text = lf(slice.label, slice); 448 else 449 text = slice.label; 450 if (plf) 451 text = plf(text, slice); 452 453 var halfAngle = ((startAngle+slice.angle) + startAngle)/2; 454 var x = centerLeft + Math.round(Math.cos(halfAngle) * radius); 455 var y = centerTop + Math.round(Math.sin(halfAngle) * radius) * options.series.pie.tilt; 456 457 var html = '<span class="pieLabel" id="pieLabel'+index+'" style="position:absolute;top:' + y + 'px;left:' + x + 'px;">
457' + text + "</span>"; 458 target.append(html); 459 var label = target.children('#pieLabel'+index); 460 var labelTop = (y - label.height()/2); 461 var labelLeft = (x - label.width()/2); 462 label.css('top', labelTop); 463 label.css('left', labelLeft); 464 465 // check to make sure that the label is not outside the canvas 466 if (0-labelTop>0 || 0-labelLeft>0 || canvas.height-(labelTop+label.height())<0 || canvas.width-(labelLeft+label.width())<0) 467 redraw = true; 468 469 if (options.series.pie.label.background.opacity != 0) { 470 // put in the transparent background separately to avoid blended labels and label boxes 471 var c = options.series.pie.label.background.color; 472 if (c == null) { 473 c = slice.color; 474 } 475 var pos = 'top:'+labelTop+'px;left:'+labelLeft+'px;'; 476 $('<div class="pieLabelBackground" style="position:absolute;width:' + label.width() + 'px;height:' + label.height() + 'px;' + pos +'background-color:' + c + ';"> </div>').insertBefore(label).css('opacity', options.series.pie.label.background.opacity); 477 } 478 } // end individual label function 479 } // end drawLabels function 480 } // end drawPie function 481 } // end draw function 482 483 // Placed here because it needs to be accessed from multiple locations 484 function drawDonutHole(layer) 485 { 486 // draw donut hole 487 if(options.series.pie.innerRadius > 0) 488 { 489 // subtract the center 490 layer.save(); 491 innerRadius = options.series.pie.innerRadius > 1 ? options.series.pie.innerRadius : maxRadius * options.series.pie.innerRadius; 492 layer.globalCompositeOperation = 'destination-out'; // this does not work with excanvas, but it will fall back to using the stroke color 493 layer.beginPath(); 494 layer.fillStyle = options.series.pie.stroke.color; 495 layer.arc(0,0,innerRadius,0,Math.PI*2,false); 496 layer.fill(); 497 layer.closePath(); 498 layer.restore(); 499 500 // add inner stroke 501 layer.save(); 502 layer.beginPath(); 503 layer.strokeStyle = options.series.pie.stroke.color; 504 layer.arc(0,0,innerRadius,0,Math.PI*2,false); 505 layer.stroke(); 506 layer.closePath(); 507 layer.restore(); 508 // TODO: add extra shadow inside hole (with a mask) if the pie is tilted. 509 } 510 } 511 512 //-- Additional Interactive related functions -- 513 514 function isPointInPoly(poly, pt) 515 { 516 for(var c = false, i = -1, l = poly.length, j = l - 1; ++i < l; j = i) 517 ((poly[i][1] <= pt[1] && pt[1] < poly[j][1]) || (poly[j][1] <= pt[1] && pt[1]< poly[i][1])) 518 && (pt[0] < (poly[j][0] - poly[i][0]) * (pt[1] - poly[i][1]) / (poly[j][1] - poly[i][1]) + poly[i][0]) 519 && (c = !c); 520 return c; 521 } 522 523 function findNearbySlice(mouseX, mouseY) 524 { 525 var slices = plot.getData(), 526 options = plot.getOptions(), 527 radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius; 528 529 for (var i = 0; i < slices.length; ++i) 530 { 531 var s = slices[i]; 532 533 if(s.pie.show) 534 { 535 ctx.save(); 536 ctx.beginPath(); 537 ctx.moveTo(0,0); // Center of the pie 538 //ctx.scale(1, options.series.pie.tilt); // this actually seems to break everything when here. 539 ctx.arc(0,0,radius,s.startAngle,s.startAngle+s.angle,false); 540 ctx.closePath(); 541 x = mouseX-centerLeft; 542 y = mouseY-centerTop; 543 if(ctx.isPointInPath) 544 { 545 if (ctx.isPointInPath(mouseX-centerLeft, mouseY-centerTop)) 546 { 547 //alert('found slice!'); 548 ctx.restore(); 549 return {datapoint: [s.percent, s.data], dataIndex: 0, series: s, seriesIndex: i}; 550 } 551 } 552 else 553 { 554 // excanvas for IE doesn;t support isPointInPath, this is a workaround. 555 p1X = (radius * Math.cos(s.startAngle)); 556 p1Y = (radius * Math.sin(s.startAngle)); 557 p2X = (radius * Math.cos(s.startAngle+(s.angle/4))); 558 p2Y = (radius * Math.sin(s.startAngle+(s.angle/4))); 559 p3X = (radius * Math.cos(s.startAngle+(s.angle/2))); 560 p3Y = (radius * Math.sin(s.startAngle+(s.angle/2))); 561 p4X = (radius * Math.cos(s.startAngle+(s.angle/1.5))); 562 p4Y = (radius * Math.sin(s.startAngle+(s.angle/1.5))); 563 p5X = (radius * Math.cos(s.startAngle+s.angle)); 564 p5Y = (radius * Math.sin(s.startAngle+s.angle)); 565 arrPoly = [[0,0],[p1X,p1Y],[p2X,p2Y],[p3X,p3Y],[p4X,p4Y],[p5X,p5Y]]; 566 arrPoint = [x,y]; 567 // TODO: perhaps do some mathmatical trickery here with the Y-coordinate to compensate for pie tilt? 568 if(isPointInPoly(arrPoly, arrPoint)) 569 {
570 ctx.restore(); 571 return {datapoint: [s.percent, s.data], dataIndex: 0, series: s, seriesIndex: i}; 572 } 573 } 574 ctx.restore(); 575 } 576 } 577 578 return null; 579 } 580 581 function onMouseMove(e) 582 { 583 triggerClickHoverEvent('plothover', e); 584 } 585 586 function onClick(e) 587 { 588 triggerClickHoverEvent('plotclick', e); 589 } 590 591 // trigger click or hover event (they send the same parameters so we share their code) 592 function triggerClickHoverEvent(eventname, e) 593 { 594 var offset = plot.offset(), 595 canvasX = parseInt(e.pageX - offset.left), 596 canvasY = parseInt(e.pageY - offset.top), 597 item = findNearbySlice(canvasX, canvasY); 598 599 if (options.grid.autoHighlight) 600 { 601 // clear auto-highlights 602 for (var i = 0; i < highlights.length; ++i) 603 { 604 var h = highlights[i]; 605 if (h.auto == eventname && !(item && h.series == item.series)) 606 unhighlight(h.series); 607 } 608 } 609 610 // highlight the slice 611 if (item) 612 highlight(item.series, eventname); 613 614 // trigger any hover bind events 615 var pos = { pageX: e.pageX, pageY: e.pageY }; 616 target.trigger(eventname, [ pos, item ]); 617 } 618 619 function highlight(s, auto) 620 { 621 if (typeof s == "number") 622 s = series[s]; 623 624 var i = indexOfHighlight(s); 625 if (i == -1) 626 { 627 highlights.push({ series: s, auto: auto }); 628 plot.triggerRedrawOverlay(); 629 } 630 else if (!auto) 631 highlights[i].auto = false; 632 } 633 634 function unhighlight(s) 635 { 636 if (s == null) 637 { 638 highlights = []; 639 plot.triggerRedrawOverlay(); 640 } 641 642 if (typeof s == "number") 643 s = series[s]; 644 645 var i = indexOfHighlight(s); 646 if (i != -1) 647 { 648 highlights.splice(i, 1); 649 plot.triggerRedrawOverlay(); 650 } 651 } 652 653 function indexOfHighlight(s) 654 { 655 for (var i = 0; i < highlights.length; ++i) 656 { 657 var h = highlights[i]; 658 if (h.series == s) 659 return i; 660 } 661 return -1; 662 } 663 664 function drawOverlay(plot, octx) 665 { 666 //alert(options.series.pie.radius); 667 var options = plot.getOptions(); 668 //alert(options.series.pie.radius); 669 670 var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius; 671 672 octx.save(); 673 octx.translate(centerLeft, centerTop); 674 octx.scale(1, options.series.pie.tilt); 675 676 for (i = 0; i < highlights.length; ++i) 677 drawHighlight(highlights[i].series); 678 679 drawDonutHole(octx); 680 681 octx.restore(); 682 683 function drawHighlight(series) 684 { 685 if (series.angle < 0) return; 686 687 //octx.fillStyle = parseColor(options.series.pie.highlight.color).scale(null, null, null, options.series.pie.highlight.opacity).toString(); 688 octx.fillStyle = "rgba(255, 255, 255, "+options.series.pie.highlight.opacity+")"; // this is temporary until we have access to parseColor 689 690 octx.beginPath(); 691 if (Math.abs(series.angle - Math.PI*2) > 0.000000001) 692 octx.moveTo(0,0); // Center of the pie 693 octx.arc(0,0,radius,series.startAngle,series.startAngle+series.angle,false); 694 octx.closePath(); 695 octx.fill(); 696 } 697 698 } 699 700 } // end init (plugin body) 701 702 /** 703 * function to change color's luminance 704 * @param {Any} hex hexstring display of the color 705 * @param {Number} lum the amount of luminance to be changed. negative = darker, positive = lighter 706 * @return {String} the new hextring display of the color 707 */ 708 function colorLuminance(hex, lum) { 709 710 // validate hex string 711 hex = String(hex).replace(/[^0-9a-f]/gi, ''); 712 if (hex.length < 6) { 713 hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2]; 714 } 715 lum = lum || 0; 716 717 // convert to decimal and change luminosity 718 var rgb = "#", c, i; 719 for (i = 0; i < 3; i++) { 720 c = parseInt(hex.substr(i*2,2), 16); 721 c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16); 722 rgb += ("00"+c).substr(c.length); 723 } 724 return rgb; 725 } 726 727 728 // define pie specific options and their default values 729 var options = { 730 series: { 731 pie: { 732 show: false, 733 radius: 'auto', // actual radius of the visible pie (based on full calculated radius if <=1, or hard pixel value) 734 innerRadius:0, /* for donut */ 735 startAngle: 3/2, 736 tilt: 1, 737 offset: { 738 top: 0, 739 left: 'auto' 740 }, 741 stroke: {
742 color: '#FFF', 743 width: 1 744 }, 745 label: { 746 show: 'auto', 747 formatter: function(label, slice){ 748 //console.log(colorLuminance(slice.color, -0.5)) 749 //console.log(label) 750 return '<div style="font-size:x-small;text-align:center;padding:2px;color:'+ colorLuminance(slice.color, -0.5) +';">'+label+'<br/>'+Math.round(slice.percent)+'%</div>'; 751 }, // formatter function 752 radius: 1, // radius at which to place the labels (based on full calculated radius if <=1, or hard pixel value) 753 background: { 754 color: null, 755 opacity: 0 756 }, 757 threshold: 0 // percentage at which to hide the label (i.e. the slice is too narrow) 758 }, 759 combine: { 760 threshold: -1, // percentage at which to combine little slices into one larger slice 761 color: null, // color to give the new slice (auto-generated if null) 762 label: 'Other' // label to give the new slice 763 }, 764 highlight: { 765 //color: '#FFF', // will add this functionality once parseColor is available 766 opacity: 0.5 767 } 768 } 769 } 770 }; 771 772 $.plot.plugins.push({ 773 init: init, 774 options: options, 775 name: "pie", 776 version: "1.0" 777 }); 778})(jQuery);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.