Warning, file /office/tellico/xslt/report-templates/jquery.flot.pie.js was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002 Flot plugin for rendering pie charts. The plugin assumes the data is
0003 coming is as a single data value for each series, and each of those
0004 values is a positive value or zero (negative numbers don't make
0005 any sense and will cause strange effects). The data values do
0006 NOT need to be passed in as percentage values because it
0007 internally calculates the total and percentages.
0008 
0009 * Created by Brian Medendorp, June 2009
0010 * Updated by Anthony Aragues, July 2009 http://suddenDevelopment.com
0011 
0012 Available options are:
0013 series: {
0014         pie: {
0015                 show: true/false
0016                 radius: 0-1 for percentage of fullsize, or a specified pixel length, or 'auto'
0017                 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
0018                 tilt: 0-1 for percentage to tilt the pie, where 1 is no tilt, and 0 is completely flat (nothing will show)
0019                 offset: {
0020                         top: integer value to move the pie up or down
0021                         left: integer value to move the pie left or right, or 'auto'
0022                 },
0023                 stroke: {
0024                         color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#FFF')
0025                         width: integer pixel width of the stroke
0026                 },
0027                 label: {
0028                         show: true/false, or 'auto'
0029                         formatter:  a user-defined function that modifies the text/style of the label text
0030                         radius: 0-1 for percentage of fullsize, or a specified pixel length
0031                         background: {
0032                                 color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#000')
0033                                 opacity: 0-1
0034                         },
0035                         threshold: 0-1 for the percentage value at which to hide labels (if they're too small)
0036                 },
0037                 combine: {
0038                         threshold: 0-1 for the percentage value at which to combine slices (if they're too small)
0039                         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
0040                         label: any text value of what the combined slice should be labeled
0041                 }
0042         }
0043 }
0044 
0045 Border Layout for labels:
0046 options.series.pie.label.show: 'border'
0047 
0048 Donut hole:
0049 options.series.pie.innerRadius: > 0
0050 
0051 More detail and specific examples can be found in the included HTML
0052 file.
0053 
0054 */
0055 
0056 (function ($)
0057 {
0058         function init(plot) // this is the "body" of the plugin
0059         {
0060                 var canvas = null,
0061                         ctx = null,
0062                         target = null,
0063                         eventHolder = null,
0064                         options = null,
0065                         maxRadius = null,
0066                         centerLeft = null,
0067                         centerTop = null,
0068                         total = 0,
0069                         redraw = true,
0070                         redrawAttempts = 10,
0071                         shrink = 0.95,
0072                         legendWidth = 0,
0073                         processed = false,
0074                         raw = false,
0075 
0076                         // interactive variables
0077                     lastMousePos = { pageX: null, pageY: null },
0078             highlights = [];
0079 
0080                 // add hook to determine if pie plugin in enabled, and then perform necessary operations
0081                 plot.hooks.processOptions.push(checkPieEnabled);
0082                 plot.hooks.bindEvents.push(bindEvents);
0083 
0084                 // check to see if the pie plugin is enabled
0085                 function checkPieEnabled(plot, options)
0086                 {
0087                         if (options.series.pie.show)
0088                         {
0089                                 //disable grid
0090                                 options.grid.show = false;
0091 
0092                                 // set labels.show
0093                                 if (options.series.pie.label.show=='auto')
0094                                         if (options.legend.show)
0095                                                 options.series.pie.label.show = false;
0096                                         else
0097                                                 options.series.pie.label.show = true;
0098 
0099                                 // set radius
0100                                 if (options.series.pie.radius=='auto')
0101                                         if (options.series.pie.label.show)
0102                                                 options.series.pie.radius = 3/4;
0103                                         else
0104                                                 options.series.pie.radius = 1;
0105 
0106                                 // ensure sane tilt
0107                                 if (options.series.pie.tilt>1)
0108                                         options.series.pie.tilt=1;
0109                                 if (options.series.pie.tilt<0)
0110                                         options.series.pie.tilt=0;
0111 
0112                                 // add processData hook to do transformations on the data
0113                                 plot.hooks.processDatapoints.push(processDatapoints);
0114                                 plot.hooks.drawOverlay.push(drawOverlay);
0115 
0116                                 // add draw hook
0117                                 plot.hooks.draw.push(draw);
0118                         }
0119                 }
0120 
0121                 // bind hoverable events
0122                 function bindEvents(plot, newEventHolder) {
0123                         eventHolder = newEventHolder;
0124                         var options = plot.getOptions();
0125 
0126                         if (options.series.pie.show &&
0127                             options.grid.hoverable)
0128                 eventHolder.unbind('mousemove').mousemove(onMouseMove);
0129                 }
0130 
0131                 // debugging function that prints out an object
0132                 function alertObject(obj)
0133                 {
0134                         var msg = '';
0135                         function traverse(obj, depth)
0136                         {
0137                                 if (!depth)
0138                                         depth = 0;
0139                                 for(var i in obj)
0140                                 {
0141                                         for (var j=0; j<depth; j++)
0142                                                 msg += '\t';
0143 
0144                                         if( typeof obj[i] == "object")
0145                                         {       // its an object
0146                                                 msg += ''+i+':\n';
0147                                                 traverse(obj[i], depth+1);
0148                                         }
0149                                         else
0150                                         {       // its a value
0151                                                 msg += ''+i+': '+obj[i]+'\n';
0152                                         }
0153                                 }
0154                         }
0155                         traverse(obj);
0156                         alert(msg);
0157                 }
0158 
0159                 function calcTotal(data)
0160                 {
0161                         for (var i in data)
0162                         {
0163                                 var item = parseFloat(data[i].data[0][1]);
0164                                 if (item)
0165                                         total += item;
0166                         }
0167                 }
0168 
0169                 function processDatapoints(plot, series, data, datapoints)
0170                 {
0171                         if (!processed)
0172                         {
0173                                 processed = true;
0174 
0175                                 canvas = plot.getCanvas();
0176                                 target = $(canvas).parent();
0177                                 options = plot.getOptions();
0178 
0179                                 plot.setData(combine(plot.getData()));
0180                         }
0181                 }
0182 
0183                 function setupPie()
0184                 {
0185                         legendWidth = target.children().filter('.legend').children().width();
0186 
0187                         // calculate maximum radius and center point
0188                         maxRadius =  Math.min(canvas.width,canvas.height)/2;
0189                         centerTop = (canvas.height/2)+options.series.pie.offset.top;
0190                         centerLeft = (canvas.width/2);
0191 
0192                         if (options.series.pie.offset.left=='auto')
0193                                 if (options.legend.position.match('w'))
0194                                         centerLeft += legendWidth/2;
0195                                 else
0196                                         centerLeft -= legendWidth/2;
0197                         else
0198                                 centerLeft += options.series.pie.offset.left;
0199 
0200                         if (centerLeft<maxRadius)
0201                                 centerLeft = maxRadius;
0202                         else if (centerLeft>canvas.width-maxRadius)
0203                                 centerLeft = canvas.width-maxRadius;
0204                 }
0205 
0206                 function fixData(data)
0207                 {
0208                         for (var i in data)
0209                         {
0210                                 if (typeof(data[i].data)=='number')
0211                                         data[i].data = [[1,data[i].data]];
0212                                 else if (typeof(data[i].data)=='undefined' || typeof(data[i].data[0])=='undefined')
0213                                 {
0214                                         if (typeof(data[i].data)!='undefined' && typeof(data[i].data.label)!='undefined')
0215                                                 data[i].label = data[i].data.label; // fix weirdness coming from flot
0216                                         data[i].data = [[1,0]];
0217 
0218                                 }
0219                         }
0220                         return data;
0221                 }
0222 
0223                 function combine(data)
0224                 {
0225                         data = fixData(data);
0226                         calcTotal(data);
0227                         var combined = 0;
0228                         var numCombined = 0;
0229                         var color = options.series.pie.combine.color;
0230 
0231                         var newdata = [];
0232                         for (var i in data)
0233                         {
0234                                 // make sure its a number
0235                                 data[i].data[0][1] = parseFloat(data[i].data[0][1]);
0236                                 if (!data[i].data[0][1])
0237                                         data[i].data[0][1] = 0;
0238 
0239                                 if (data[i].data[0][1]/total<=options.series.pie.combine.threshold)
0240                                 {
0241                                         combined += data[i].data[0][1];
0242                                         numCombined++;
0243                                         if (!color)
0244                                                 color = data[i].color;
0245                                 }
0246                                 else
0247                                 {
0248                                         newdata.push({
0249                                                 data: [[1,data[i].data[0][1]]],
0250                                                 color: data[i].color,
0251                                                 label: data[i].label,
0252                                                 angle: (data[i].data[0][1]*(Math.PI*2))/total,
0253                                                 percent: (data[i].data[0][1]/total*100)
0254                                         });
0255                                 }
0256                         }
0257                         if (numCombined>0)
0258                                 newdata.push({
0259                                         data: [[1,combined]],
0260                                         color: color,
0261                                         label: options.series.pie.combine.label,
0262                                         angle: (combined*(Math.PI*2))/total,
0263                                         percent: (combined/total*100)
0264                                 });
0265                         return newdata;
0266                 }
0267 
0268                 function draw(plot, newCtx)
0269                 {
0270                         if (!target) return; // if no series were passed
0271 
0272                         ctx = newCtx;
0273 
0274                         setupPie();
0275                         var slices = plot.getData();
0276 
0277                         var attempts = 0;
0278                         while (redraw && attempts<redrawAttempts)
0279                         {
0280                                 redraw = false;
0281                                 if (attempts>0)
0282                                         maxRadius *= shrink;
0283                                 attempts += 1;
0284                                 clear();
0285                                 if (options.series.pie.tilt<=0.8)
0286                                         drawShadow();
0287                                 drawPie();
0288                         }
0289                         if (attempts >= redrawAttempts) {
0290                                 clear();
0291                                 target.prepend('<div class="error">Could not draw pie with labels contained inside canvas</div>');
0292                         }
0293 
0294                         if ( plot.setSeries && plot.insertLegend )
0295                         {
0296                                 plot.setSeries(slices);
0297                                 plot.insertLegend();
0298                         }
0299 
0300                         // we're actually done at this point, just defining internal functions at this point
0301 
0302                         function clear()
0303                         {
0304                                 ctx.clearRect(0,0,canvas.width,canvas.height);
0305                                 target.children().filter('.pieLabel, .pieLabelBackground').remove();
0306                         }
0307 
0308                         function drawShadow()
0309                         {
0310                                 var shadowLeft = 5;
0311                                 var shadowTop = 15;
0312                                 var edge = 10;
0313                                 var alpha = 0.02;
0314 
0315                                 // set radius
0316                                 if (options.series.pie.radius>1)
0317                                         var radius = options.series.pie.radius;
0318                                 else
0319                                         var radius = maxRadius * options.series.pie.radius;
0320 
0321                                 if (radius>=(canvas.width/2)-shadowLeft || radius*options.series.pie.tilt>=(canvas.height/2)-shadowTop || radius<=edge)
0322                                         return; // shadow would be outside canvas, so don't draw it
0323 
0324                                 ctx.save();
0325                                 ctx.translate(shadowLeft,shadowTop);
0326                                 ctx.globalAlpha = alpha;
0327                                 ctx.fillStyle = '#000';
0328 
0329                                 // center and rotate to starting position
0330                                 ctx.translate(centerLeft,centerTop);
0331                                 ctx.scale(1, options.series.pie.tilt);
0332 
0333                                 //radius -= edge;
0334                                 for (var i=1; i<=edge; i++)
0335                                 {
0336                                         ctx.beginPath();
0337                                         ctx.arc(0,0,radius,0,Math.PI*2,false);
0338                                         ctx.fill();
0339                                         radius -= i;
0340                                 }
0341 
0342                                 ctx.restore();
0343                         }
0344 
0345                         function drawPie()
0346                         {
0347                                 startAngle = Math.PI*options.series.pie.startAngle;
0348 
0349                                 // set radius
0350                                 if (options.series.pie.radius>1)
0351                                         var radius = options.series.pie.radius;
0352                                 else
0353                                         var radius = maxRadius * options.series.pie.radius;
0354 
0355                                 // center and rotate to starting position
0356                                 ctx.save();
0357                                 ctx.translate(centerLeft,centerTop);
0358                                 ctx.scale(1, options.series.pie.tilt);
0359                                 //ctx.rotate(startAngle); // start at top; -- This doesn't work properly in Opera
0360 
0361                                 // draw slices
0362                                 ctx.save();
0363                                 var currentAngle = startAngle;
0364                                 for (var i in slices) {
0365                                         slices[i].startAngle = currentAngle;
0366                                         drawSlice(slices[i].angle, slices[i].color, true);
0367                                 }
0368                                 ctx.restore();
0369 
0370                                 // draw slice outlines
0371                                 ctx.save();
0372                                 ctx.lineWidth = options.series.pie.stroke.width;
0373                                 currentAngle = startAngle;
0374                                 for (var i in slices)
0375                                         drawSlice(slices[i].angle, options.series.pie.stroke.color, false);
0376                                 ctx.restore();
0377 
0378                                 // draw labels
0379                                 if (options.series.pie.label.show)
0380                                         drawLabels();
0381 
0382                                 // restore to original state
0383                                 ctx.restore();
0384 
0385                                 function drawSlice(angle, color, fill)
0386                                 {
0387                                         if (angle<=0)
0388                                                 return;
0389 
0390                                         if (fill){ ctx.fillStyle = color; }
0391                                         else{ ctx.strokeStyle = color; }
0392 
0393                                         ctx.beginPath();
0394                                         if (angle!=Math.PI*2)
0395                                                 ctx.moveTo(0,0); // Center of the pie
0396                                         else if ($.browser.msie)
0397                                                 angle -= 0.0001;
0398                                         //ctx.arc(0,0,radius,0,angle,false); // This doesn't work properly in Opera
0399                                         ctx.arc(0,0,radius,currentAngle,currentAngle+angle,false);
0400                                         ctx.closePath();
0401                                         //ctx.rotate(angle); // This doesn't work properly in Opera
0402                                         currentAngle += angle;
0403 
0404                                         if (fill)
0405                                                 ctx.fill();
0406                                         else
0407                                                 ctx.stroke();
0408                                 }
0409 
0410                                 function drawLabels()
0411                                 {
0412                                         var currentAngle = startAngle;
0413 
0414                                         // set radius
0415                                         if (options.series.pie.label.radius>1)
0416                                                 var radius = options.series.pie.label.radius;
0417                                         else
0418                                                 var radius = maxRadius * options.series.pie.label.radius;
0419 
0420                                         for(var i in slices)
0421                                         {
0422                                                 if (slices[i].percent >= options.series.pie.label.threshold*100)
0423                                                         drawLabel(slices[i], currentAngle, i);
0424                                                 currentAngle += slices[i].angle;
0425                                         }
0426 
0427                                         function drawLabel(slice, startAngle, index)
0428                                         {
0429                                                 if (slice.data[0][1]==0)
0430                                                         return;
0431 
0432                                                 // format label text
0433                                                 var lf = options.legend.labelFormatter, text, plf = options.series.pie.label.formatter;
0434                                                 if (lf){
0435                                                         text = lf(slice.label, slice);}
0436                                                 else{
0437                                                         text = slice.label;}
0438                                                 if (plf){
0439                                                         text = plf(text, slice);}
0440 
0441                                                 var halfAngle = ((startAngle+slice.angle) + startAngle)/2;
0442                                                 var x = centerLeft + Math.round(Math.cos(halfAngle) * radius);
0443                                                 var y = centerTop + Math.round(Math.sin(halfAngle) * radius) * options.series.pie.tilt;
0444 
0445                                                 var html = '<span class="pieLabel" id="pieLabel'+index+'" style="position:absolute;top:' + y + 'px;left:' + x + 'px;">' + text + "</span>";
0446                                                 target.append(html);
0447                                                 var label = target.children('#pieLabel'+index);
0448                                                 var labelTop = (y - label.height()/2);
0449                                                 var labelLeft = (x - label.width()/2);
0450 
0451                                                 /* Border layout for labels */
0452                                                 if(options.series.pie.label.show == 'border'){
0453                                                         ctx.restore();
0454                                                         ctx.beginPath();
0455                                                         ctx.strokeStyle = '#000000';
0456                                                         ctx.moveTo(x,y);
0457                                                         if(x > (canvas.width/2)){
0458                                                                 var endX = canvas.width - label.width();
0459                                                                 var endY = y;
0460                                                                 labelLeft = endX;
0461                                                         }else{
0462                                                                 var endX = label.width();
0463                                                                 var endY = y;
0464                                                                 labelLeft = 0;
0465                                                         }
0466                                                         ctx.lineTo(endX, endY);
0467                                                         ctx.stroke();
0468                                                         ctx.save();
0469                                                 }
0470 
0471                                                 label.css('top', labelTop);
0472                                                 label.css('left', labelLeft);
0473 
0474                                                 // check to make sure that the label is not outside the canvas
0475                                                 if (0-labelTop>0 || 0-labelLeft>0 || canvas.height-(labelTop+label.height())<0 || canvas.width-(labelLeft+label.width())<0)
0476                                                         redraw = true;
0477 
0478                                                 if (options.series.pie.label.background.opacity != 0) {
0479                                                         // put in the transparent background separately to avoid blended labels and label boxes
0480                                                         var c = options.series.pie.label.background.color;
0481                                                         if (c == null) {
0482                                                                 c = slice.color;
0483                                                         }
0484                                                         var pos = 'top:'+labelTop+'px;left:'+labelLeft+'px;';
0485                                                         $('<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);
0486                                                 }
0487                                         } // end individual label function
0488                                 } // end drawLabels function
0489                         } // end drawPie function
0490 
0491                         if(options.series.pie.innerRadius > 0){
0492                                 /* donut hole */
0493                                 radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius;
0494                                 ctx.translate(centerLeft,centerTop);
0495                                 ctx.scale(1, options.series.pie.tilt);
0496                                 ctx.save();
0497                                 ctx.beginPath();
0498                                 ctx.strokeStyle = options.series.pie.stroke.color;
0499                                 ctx.fillStyle = options.series.pie.stroke.color;
0500                                 ctx.arc(0,0,radius*options.series.pie.innerRadius,0,Math.PI*2,false);
0501                                 ctx.fill();
0502                                 ctx.closePath();
0503                         }
0504                         /* debug */
0505 
0506                         /*  end debug  */
0507                 } // end draw function
0508 
0509         function isPointInPoly(poly, pt){
0510         for(var c = false, i = -1, l = poly.length, j = l - 1; ++i < l; j = i)
0511                 ((poly[i][1] <= pt[1] && pt[1] < poly[j][1]) || (poly[j][1] <= pt[1] && pt[1]< poly[i][1]))
0512                 && (pt[0] < (poly[j][0] - poly[i][0]) * (pt[1] - poly[i][1]) / (poly[j][1] - poly[i][1]) + poly[i][0])
0513                 && (c = !c);
0514         return c;
0515         }
0516                 // returns the data item the mouse is over, or null if none is found
0517         function findNearbyItem(mouseX, mouseY, seriesFilter) {
0518             var item = null, foundPoint = false, i, j,
0519                                 series = plot.getData(),
0520                                 radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius;
0521 
0522             for (var i = 0; i < series.length; ++i) {
0523                 if (!seriesFilter(series[i]))
0524                     continue;
0525 
0526                 var s = series[i];
0527 
0528                 if (s.pie.show) {
0529                                         ctx.save();
0530                                         ctx.beginPath();
0531                                         ctx.translate(centerLeft,centerTop);
0532                                         ctx.scale(1, options.series.pie.tilt);
0533                                         ctx.moveTo(0,0); // Center of the pie
0534                                         ctx.arc(0,0,radius,s.startAngle,s.startAngle+s.angle,false);
0535                                         ctx.closePath();
0536                                         x = mouseX-centerLeft;
0537                                         y = mouseY-centerTop;
0538                                         if(ctx.isPointInPath){
0539                                                 if (ctx.isPointInPath(x, y)){
0540                                                         item = {datapoint: [series[i].percent, series[i].data], dataIndex: 0, series: series[i], seriesIndex: i};
0541                                                 }
0542                                         }else{
0543                                                 /* excanvas for IE doesn;t support isPointInPath, this is a workaround. */
0544                                                 p1X = (radius * Math.cos(s.startAngle));
0545                                                 p1Y = (radius * Math.sin(s.startAngle));
0546                                                 p2X = (radius * Math.cos(s.startAngle+(s.angle/4)));
0547                                                 p2Y = (radius * Math.sin(s.startAngle+(s.angle/4)));
0548                                                 p3X = (radius * Math.cos(s.startAngle+(s.angle/2)));
0549                                                 p3Y = (radius * Math.sin(s.startAngle+(s.angle/2)));
0550                                                 p4X = (radius * Math.cos(s.startAngle+(s.angle/1.5)));
0551                                                 p4Y = (radius * Math.sin(s.startAngle+(s.angle/1.5)));
0552                                                 p5X = (radius * Math.cos(s.startAngle+s.angle));
0553                                                 p5Y = (radius * Math.sin(s.startAngle+s.angle));
0554                                                 arrPoly = [[0,0],[p1X,p1Y],[p2X,p2Y],[p3X,p3Y],[p4X,p4Y],[p5X,p5Y]];
0555                                                 arrPoint = [x,y];
0556                                                 if(isPointInPoly(arrPoly, arrPoint)){
0557                                                         item = {datapoint: [series[i].percent, series[i].data], dataIndex: 0, series: series[i], seriesIndex: i};
0558                                                 }
0559                                         }
0560                                         ctx.restore();
0561                 }
0562             }
0563 
0564             return item;
0565         }
0566 
0567                 function onMouseMove(e) {
0568                         if (!processed) return;
0569 
0570             lastMousePos.pageX = e.pageX;
0571             lastMousePos.pageY = e.pageY;
0572 
0573             if (options.grid.hoverable){
0574                 triggerClickHoverEvent("plothover", lastMousePos,
0575                                        function (s) { return s["hoverable"] != false;});
0576                         }
0577                 }
0578 
0579                 // trigger click or hover event (they send the same parameters
0580         // so we share their code)
0581         function triggerClickHoverEvent(eventname, event, seriesFilter) {
0582                         var plotOffset = plot.getPlotOffset(),
0583                                 offset = eventHolder.offset(),
0584                 pos = { pageX: event.pageX, pageY: event.pageY },
0585                 canvasX = event.pageX - offset.left - plotOffset.left,
0586                 canvasY = event.pageY - offset.top - plotOffset.top;
0587 
0588             var item = findNearbyItem(canvasX, canvasY, seriesFilter);
0589             if (item) {
0590 
0591                                 // fill in mouse pos for any listeners out there
0592                 item.pageX = event.pageX;
0593                                 item.pageY = event.pageY;
0594 
0595             }
0596 
0597             if (options.grid.autoHighlight) {
0598                 // clear auto-highlights
0599                 for (var i = 0; i < highlights.length; ++i) {
0600                     var h = highlights[i];
0601                     if (h.auto == eventname &&
0602                         !(item && h.series == item.series))
0603                         unhighlight(h.series);
0604                 }
0605 
0606                 if (item)
0607                     highlight(item.series, eventname);
0608             }
0609 
0610             target.trigger(eventname, [ pos, item ]);
0611         }
0612 
0613                 function highlight(s, auto) {
0614             if (typeof s == "number")
0615                 s = series[s];
0616 
0617             var i = indexOfHighlight(s);
0618             if (i == -1) {
0619                 highlights.push({ series: s, auto: auto });
0620 
0621                 plot.triggerRedrawOverlay();
0622             }
0623             else if (!auto)
0624                 highlights[i].auto = false;
0625         }
0626 
0627         function unhighlight(s) {
0628             if (s == null) {
0629                 highlights = [];
0630                 plot.triggerRedrawOverlay();
0631             }
0632 
0633             if (typeof s == "number")
0634                 s = series[s];
0635 
0636             var i = indexOfHighlight(s);
0637             if (i != -1) {
0638                 highlights.splice(i, 1);
0639 
0640                 plot.triggerRedrawOverlay();
0641             }
0642         }
0643 
0644         function indexOfHighlight(s) {
0645             for (var i = 0; i < highlights.length; ++i) {
0646                 var h = highlights[i];
0647                 if (h.series == s)
0648                     return i;
0649             }
0650             return -1;
0651         }
0652 
0653                 function drawOverlay(plot, octx) {
0654             var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius,
0655                                 i, hi;
0656 
0657                         octx.save();
0658             octx.translate(centerLeft, centerTop);
0659 
0660             for (i = 0; i < highlights.length; ++i) {
0661                                 drawHighlight(highlights[i].series);
0662             }
0663 
0664                         octx.restore();
0665 
0666                         function drawHighlight(series) {
0667                                 if (series.angle < 0) return;
0668 
0669                                 octx.fillStyle = parseColor(options.series.pie.highlight.color).scale(null, null, null, options.series.pie.highlight.opacity).toString();
0670 
0671                                 octx.beginPath();
0672                                 if (series.angle!=Math.PI*2)
0673                                         octx.moveTo(0,0); // Center of the pie
0674                                 octx.arc(0,0,radius,series.startAngle,series.startAngle+series.angle,false);
0675                                 octx.closePath();
0676 
0677                                 octx.fill();
0678                         }
0679 
0680                 }
0681 
0682         } // end init (plugin body)
0683 
0684         function clamp(min, value, max) {
0685         if (value < min)
0686             return min;
0687         else if (value > max)
0688             return max;
0689         else
0690             return value;
0691     }
0692 
0693         // color helpers, inspiration from the jquery color animation
0694     // plugin by John Resig
0695     function Color (r, g, b, a) {
0696 
0697         var rgba = ['r','g','b','a'];
0698         var x = 4; //rgba.length
0699 
0700         while (-1<--x) {
0701             this[rgba[x]] = arguments[x] || ((x==3) ? 1.0 : 0);
0702         }
0703 
0704         this.toString = function() {
0705             if (this.a >= 1.0) {
0706                 return "rgb("+[this.r,this.g,this.b].join(",")+")";
0707             } else {
0708                 return "rgba("+[this.r,this.g,this.b,this.a].join(",")+")";
0709             }
0710         };
0711 
0712         this.scale = function(rf, gf, bf, af) {
0713             x = 4; //rgba.length
0714             while (-1<--x) {
0715                 if (arguments[x] != null)
0716                     this[rgba[x]] *= arguments[x];
0717             }
0718             return this.normalize();
0719         };
0720 
0721         this.adjust = function(rd, gd, bd, ad) {
0722             x = 4; //rgba.length
0723             while (-1<--x) {
0724                 if (arguments[x] != null)
0725                     this[rgba[x]] += arguments[x];
0726             }
0727             return this.normalize();
0728         };
0729 
0730         this.clone = function() {
0731             return new Color(this.r, this.b, this.g, this.a);
0732         };
0733 
0734         var limit = function(val,minVal,maxVal) {
0735             return Math.max(Math.min(val, maxVal), minVal);
0736         };
0737 
0738         this.normalize = function() {
0739             this.r = clamp(0, parseInt(this.r), 255);
0740             this.g = clamp(0, parseInt(this.g), 255);
0741             this.b = clamp(0, parseInt(this.b), 255);
0742             this.a = clamp(0, this.a, 1);
0743             return this;
0744         };
0745 
0746         this.normalize();
0747     }
0748 
0749         function parseColor(str) {
0750         var result;
0751 
0752         // Look for rgb(num,num,num)
0753         if (result = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(str))
0754             return new Color(parseInt(result[1], 10), parseInt(result[2], 10), parseInt(result[3], 10));
0755 
0756         // Look for rgba(num,num,num,num)
0757         if (result = /rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))
0758             return new Color(parseInt(result[1], 10), parseInt(result[2], 10), parseInt(result[3], 10), parseFloat(result[4]));
0759 
0760         // Look for rgb(num%,num%,num%)
0761         if (result = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(str))
0762             return new Color(parseFloat(result[1])*2.55, parseFloat(result[2])*2.55, parseFloat(result[3])*2.55);
0763 
0764         // Look for rgba(num%,num%,num%,num)
0765         if (result = /rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))
0766             return new Color(parseFloat(result[1])*2.55, parseFloat(result[2])*2.55, parseFloat(result[3])*2.55, parseFloat(result[4]));
0767 
0768         // Look for #a0b1c2
0769         if (result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(str))
0770             return new Color(parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16));
0771 
0772         // Look for #fff
0773         if (result = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(str))
0774             return new Color(parseInt(result[1]+result[1], 16), parseInt(result[2]+result[2], 16), parseInt(result[3]+result[3], 16));
0775 
0776         // Otherwise, we're most likely dealing with a named color
0777         var name = $.trim(str).toLowerCase();
0778         if (name == "transparent")
0779             return new Color(255, 255, 255, 0);
0780         else {
0781             result = lookupColors[name];
0782             return new Color(result[0], result[1], result[2]);
0783         }
0784     }
0785 
0786         // define pie specific options and their default values
0787         var options = {
0788                 series: {
0789                         pie: {
0790                                 show: false,
0791                                 radius: 'auto', // actual radius of the visible pie (based on full calculated radius if <=1, or hard pixel value)
0792                                 innerRadius:0, /* for donut */
0793                                 startAngle: 0,
0794                                 tilt: 1,
0795                                 offset: {
0796                                         top: 0,
0797                                         left: 'auto'
0798                                 },
0799                                 stroke: {
0800                                         color: '#FFF',
0801                                         width: 1
0802                                 },
0803                                 label: {
0804                                         show: 'auto',
0805                                         formatter: function(label, slice){
0806                                                 return '<div style="font-size:x-small;text-align:center;padding:2px;color:'+slice.color+';">'+label+'<br/>'+Math.round(slice.percent)+'%</div>';
0807                                         },      // formatter function
0808                                         radius: 1,      // radius at which to place the labels (based on full calculated radius if <=1, or hard pixel value)
0809                                         background: {
0810                                                 color: null,
0811                                                 opacity: 0
0812                                         },
0813                                         threshold: 0    // percentage at which to hide the label (i.e. the slice is too narrow)
0814                                 },
0815                                 combine: {
0816                                         threshold: -1,  // percentage at which to combine little slices into one larger slice
0817                                         color: null,    // color to give the new slice (auto-generated if null)
0818                                         label: 'Other'  // label to give the new slice
0819                                 },
0820                                 highlight: {
0821                                         color: '#ffee77',       // color to use for highlights
0822                                         opacity: 0.2            // opacity to use for highlights
0823                                 }
0824                         }
0825                 }
0826         };
0827 
0828         $.plot.plugins.push({
0829                 init: init,
0830                 options: options,
0831                 name: "pie",
0832                 version: "0.4"
0833         });
0834 })(jQuery);