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