PageSourceSearch

https://www.grantforward.com/public/js/flot/jquery.flot.pie.js?v=285

js grantforward.com collected 2026-10-02 04:29:06 UTC 22,695 bytes, 778 lines download raw bytes

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.