text: /*!
   * d3pie
   * @author Ben Keen
   * @version 0.1.9
   * @date June 17th, 2015
   * @repo http://github.com/benkeen/d3pie
   */
  
  // UMD pattern from https://github.com/umdjs/umd/blob/master/returnExports.js
  (function(root, factory) {
    if (typeof define === 'function' && define.amd) {
      // AMD. Register as an anonymous module
      define([], factory);
    } else if (typeof exports === 'object') {
      // Node. Does not work with strict CommonJS, but only CommonJS-like environments that support module.exports,
      // like Node
      module.exports = factory();
    } else {
      // browser globals (root is window)
      root.d3pie = factory(root);
    }
  }(this, function() {
  
  	var _scriptName = "d3pie";
  	var _version = "0.2.1";
  
  	// used to uniquely generate IDs and classes, ensuring no conflict between multiple pies on the same page
  	var _uniqueIDCounter = 0;
  
  
  	// this section includes all helper libs on the d3pie object. They're populated via grunt-template. Note: to keep
  	// the syntax highlighting from getting all messed up, I commented out each line. That REQUIRES each of the files
  	// to have an empty first line. Crumby, yes, but acceptable.
  	//// --------- _default-settings.js -----------/**
  /**
   * Contains the out-the-box settings for the script. Any of these settings that aren't explicitly overridden for the
   * d3pie instance will inherit from these. This is also included on the main website for use in the generation script.
   */
  var defaultSettings = {
  	header: {
  		title: {
  			text:     "",
  			color:    "#333333",
  			fontSize: 18,
  			font:     "arial"
  		},
  		subtitle: {
  			text:     "",
  			color:    "#666666",
  			fontSize: 14,
  			font:     "arial"
  		},
  		location: "top-center",
  		titleSubtitlePadding: 8
  	},
  	footer: {
  		text: 	  "",
  		color:    "#666666",
  		fontSize: 14,
  		font:     "arial",
  		location: "left"
  	},
  	size: {
  		canvasHeight: 500,
  		canvasWidth: 500,
  		pieInnerRadius: "0%",
  		pieOuterRadius: null
  	},
  	data: {
  		sortOrder: "none",
  		ignoreSmallSegments: {
  			enabled: false,
  			valueType: "percentage",
  			value: null
  		},
  		smallSegmentGrouping: {
  			enabled: false,
  			value: 1,
  			valueType: "percentage",
  			label: "Other",
  			color: "#cccccc"
  		},
  		content: []
  	},
  	labels: {
  		outer: {
  			format: "label",
  			hideWhenLessThanPercentage: null,
  			pieDistance: 30
  		},
  		inner: {
  			format: "percentage",
  			hideWhenLessThanPercentage: null
  		},
  		mainLabel: {
  			color: "#333333",
  			font: "arial",
  			fontSize: 10
  		},
  		percentage: {
  			color: "#dddddd",
  			font: "arial",
  			fontSize: 10,
  			decimalPlaces: 0
  		},
  		value: {
  			color: "#cccc44",
  			font: "arial",
  			fontSize: 10
  		},
  		lines: {
  			enabled: true,
  			style: "curved",
  			color: "segment"
  		},
  		truncation: {
  			enabled: false,
  			truncateLength: 30
  		},
      formatter: null
  	},
  	effects: {
  		load: {
  			effect: "default",
  			speed: 1000
  		},
  		pullOutSegmentOnClick: {
  			effect: "bounce",
  			speed: 300,
  			size: 10
  		},
  		highlightSegmentOnMouseover: true,
  		highlightLuminosity: -0.2
  	},
  	tooltips: {
  		enabled: false,
  		type: "placeholder", // caption|placeholder
      string: "",
      placeholderParser: null,
  		styles: {
        fadeInSpeed: 250,
  			backgroundColor: "#000000",
        backgroundOpacity: 0.5,
  			color: "#efefef",
        borderRadius: 2,
        font: "arial",
        fontSize: 10,
        padding: 4
  		}
  	},
  	misc: {
  		colors: {
  			background: null,
  			segments: [
  				"#2484c1", "#65a620", "#7b6888", "#a05d56", "#961a1a", "#d8d23a", "#e98125", "#d0743c", "#635222", "#6ada6a",
  				"#0c6197", "#7d9058", "#207f33", "#44b9b0", "#bca44a", "#e4a14b", "#a3acb2", "#8cc3e9", "#69a6f9", "#5b388f",
  				"#546e91", "#8bde95", "#d2ab58", "#273c71", "#98bf6e", "#4daa4b", "#98abc5", "#cc1010", "#31383b", "#006391",
  				"#c2643f", "#b0a474", "#a5a39c", "#a9c2bc", "#22af8c", "#7fcecf", "#987ac6", "#3d3b87", "#b77b1c", "#c9c2b6",
  				"#807ece", "#8db27c", "#be66a2", "#9ed3c6", "#00644b", "#005064", "#77979f", "#77e079", "#9c73ab", "#1f79a7"
  			],
  			segmentStroke: "#ffffff"
  		},
  		gradient: {
  			enabled: false,
  			percentage: 95,
  			color: "#000000"
  		},
  		canvasPadding: {
  			top: 5,
  			right: 5,
  			bottom: 5,
  			left: 5
  		},
  		pieCenterOffset: {
  			x: 0,
  			y: 0
  		},
  		cssPrefix: null
  	},
  	callbacks: {
  		onload: null,
  		onMouseoverSegment: null,
  		onMouseoutSegment: null,
  		onClickSegment: null
  	}
  };
  
  	//// --------- validate.js -----------
  var validate = {
  
  	// called whenever a new pie chart is created
  	initialCheck: function(pie) {
  		var cssPrefix = pie.cssPrefix;
  		var element = pie.element;
  		var options = pie.options;
  
  		// confirm d3 is available [check minimum version]
  		if (!window.d3 || !window.d3.hasOwnProperty("version")) {
  			console.error("d3pie error: d3 is not available");
  			return false;
  		}
  
  		// confirm element is either a DOM element or a valid string for a DOM element
  		if (!(element instanceof HTMLElement || element instanceof SVGElement)) {
  			console.error("d3pie error: the first d3pie() param must be a valid DOM element (not jQuery) or a ID string.");
  			return false;
  		}
  
  		// confirm the CSS prefix is valid. It has to start with a-Z and contain nothing but a-Z0-9_-
  		if (!(/[a-zA-Z][a-zA-Z0-9_-]*$/.test(cssPrefix))) {
  			console.error("d3pie error: invalid options.misc.cssPrefix");
  			return false;
  		}
  
  		// confirm some data has been supplied
  		if (!helpers.isArray(options.data.content)) {
  			console.error("d3pie error: invalid config structure: missing data.content property.");
  			return false;
  		}
  		if (options.data.content.length === 0) {
  			console.error("d3pie error: no data supplied.");
  			return false;
  		}
  
  		// clear out any invalid data. Each data row needs a valid positive number and a label
  		var data = [];
  		for (var i=0; i<options.data.content.length; i++) {
  			if (typeof options.data.content[i].value !== "number" || isNaN(options.data.content[i].value)) {
  				console.log("not valid: ", options.data.content[i]);
  				continue;
  			}
  //			if (options.data.content[i].value <= 0) {
  //				console.log("not valid - should have positive value: ", options.data.content[i]);
  //				continue;
  //			}
  			data.push(options.data.content[i]);
  		}
  		pie.options.data.content = data;
  
  		// labels.outer.hideWhenLessThanPercentage - 1-100
  		// labels.inner.hideWhenLessThanPercentage - 1-100
  
  		return true;
  	}
  };
  
  	//// --------- helpers.js -----------
  var helpers = {
  
  	// creates the SVG element
  	addSVGSpace: function(pie) {
  		var element = pie.element;
  		var canvasWidth = pie.options.size.canvasWidth;
  		var canvasHeight = pie.options.size.canvasHeight;
  		var backgroundColor = pie.options.misc.colors.background;
  
  		var svg = d3.select(element).append("svg:svg")
  			.attr("width", canvasWidth)
  			.attr("height", canvasHeight);
  
  		if (backgroundColor !== "transparent") {
  			svg.style("background-color", function() { return backgroundColor; });
  		}
  
  		return svg;
  	},
  
  	whenIdExists: function(id, callback) {
  		var inc = 1;
  		var giveupIterationCount = 1000;
  
  		var interval = setInterval(function() {
  			if (document.getElementById(id)) {
  				clearInterval(interval);
  				callback();
  			}
  			if (inc > giveupIterationCount) {
  				clearInterval(interval);
  			}
  			inc++;
  		}, 1);
  	},
  
  	whenElementsExist: function(els, callback) {
  		var inc = 1;
  		var giveupIterationCount = 1000;
  
  		var interval = setInterval(function() {
  			var allExist = true;
  			for (var i=0; i<els.length; i++) {
  				if (!document.getElementById(els[i])) {
  					allExist = false;
  					break;
  				}
  			}
  			if (allExist) {
  				clearInterval(interval);
  				callback();
  			}
  			if (inc > giveupIterationCount) {
  				clearInterval(interval);
  			}
  			inc++;
  		}, 1);
  	},
  
  	shuffleArray: function(array) {
  		var currentIndex = array.length, tmpVal, randomIndex;
  
  		while (0 !== currentIndex) {
  			randomIndex = Math.floor(Math.random() * currentIndex);
  			currentIndex -= 1;
  
  			// and swap it with the current element
  			tmpVal = array[currentIndex];
  			array[currentIndex] = array[randomIndex];
  			array[randomIndex] = tmpVal;
  		}
  		return array;
  	},
  
  	processObj: function(obj, is, value) {
  		if (typeof is === 'string') {
  			return helpers.processObj(obj, is.split('.'), value);
  		} else if (is.length === 1 && value !== undefined) {
              obj[is[0]] = value;
  			return obj[is[0]];
  		} else if (is.length === 0) {
  			return obj;
  		} else {
  			return helpers.processObj(obj[is[0]], is.slice(1), value);
  		}
  	},
  
  	getDimensions: function(id) {
  		var el = document.getElementById(id);
  		var w = 0, h = 0;
  		if (el) {
  			var dimensions = el.getBBox();
  			w = dimensions.width;
  			h = dimensions.height;
  		} else {
  			console.log("error: getDimensions() " + id + " not found.");
  		}
  		return { w: w, h: h };
  	},
  
  	/**
  	 * This is based on the SVG coordinate system, where top-left is 0,0 and bottom right is n-n.
  	 * @param r1
  	 * @param r2
  	 * @returns {boolean}
  	 */
  	rectIntersect: function(r1, r2) {
  		var returnVal = (
  			// r2.left > r1.right
  			(r2.x > (r1.x + r1.w)) ||
  
  			// r2.right < r1.left
  			((r2.x + r2.w) < r1.x) ||
  
  			// r2.top < r1.bottom
  			((r2.y + r2.h) < r1.y) ||
  
  			// r2.bottom > r1.top
  			(r2.y > (r1.y + r1.h))
  		);
  
  		return !returnVal;
  	},
  
  	/**
  	 * Returns a lighter/darker shade of a hex value, based on a luminance value passed.
  	 * @param hex a hex color value such as “#abc” or “#123456″ (the hash is optional)
  	 * @param lum the luminosity factor: -0.1 is 10% darker, 0.2 is 20% lighter, etc.
  	 * @returns {string}
  	 */
  	getColorShade: function(hex, lum) {
  
  		// validate hex string
  		hex = String(hex).replace(/[^0-9a-f]/gi, '');
  		if (hex.length < 6) {
  			hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
  		}
  		lum = lum || 0;
  
  		// convert to decimal and change luminosity
  		var newHex = "#";
  		for (var i=0; i<3; i++) {
  			var c = parseInt(hex.substr(i * 2, 2), 16);
  			c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
  			newHex += ("00" + c).substr(c.length);
  		}
  
  		return newHex;
  	},
  
  	/**
  	 * Users can choose to specify segment colors in three ways (in order of precedence):
  	 * 	1. include a "color" attribute for each row in data.content
  	 * 	2. include a misc.colors.segments property which contains an array of hex codes
  	 * 	3. specify nothing at all and rely on this lib provide some reasonable defaults
  	 *
  	 * This function sees what's included and populates this.options.colors with whatever's required
  	 * for this pie chart.
  	 * @param data
  	 */
  	initSegmentColors: function(pie) {
  		var data   = pie.options.data.content;
  		var colors = pie.options.misc.colors.segments;
  
  		// TODO this needs a ton of error handling
  
  		var finalColors = [];
  		for (var i=0; i<data.length; i++) {
  			if (data[i].hasOwnProperty("color")) {
  				finalColors.push(data[i].color);
  			} else {
  				finalColors.push(colors[i]);
  			}
  		}
  
  		return finalColors;
  	},
  
  	applySmallSegmentGrouping: function(data, smallSegmentGrouping) {
  		var totalSize;
  		if (smallSegmentGrouping.valueType === "percentage") {
  			totalSize = math.getTotalPieSize(data);
  		}
  
  		// loop through each data item
  		var newData = [];
  		var groupedData = [];
  		var totalGroupedData = 0;
  		for (var i=0; i<data.length; i++) {
  			if (smallSegmentGrouping.valueType === "percentage") {
  				var dataPercent = (data[i].value / totalSize) * 100;
  				if (dataPercent <= smallSegmentGrouping.value) {
  					groupedData.push(data[i]);
  					totalGroupedData += data[i].value;
  					continue;
  				}
  				data[i].isGrouped = false;
  				newData.push(data[i]);
  			} else {
  				if (data[i].value <= smallSegmentGrouping.value) {
  					groupedData.push(data[i]);
  					totalGroupedData += data[i].value;
  					continue;
  				}
  				data[i].isGrouped = false;
  				newData.push(data[i]);
  			}
  		}
  
  		// we're done! See if there's any small segment groups to add
  		if (groupedData.length) {
  			newData.push({
  				color: smallSegmentGrouping.color,
  				label: smallSegmentGrouping.label,
  				value: totalGroupedData,
  				isGrouped: true,
  				groupedData: groupedData
  			});
  		}
  
  		return newData;
  	},
  
  	// for debugging
  	showPoint: function(svg, x, y) {
  		svg.append("circle").attr("cx", x).attr("cy", y).attr("r", 2).style("fill", "black");
  	},
  
  	isFunction: function(functionToCheck) {
  		var getType = {};
  		return functionToCheck && getType.toString.call(functionToCheck) === '[object Function]';
  	},
  
  	isArray: function(o) {
  		return Object.prototype.toString.call(o) === '[object Array]';
  	}
  };
  
  
  // taken from jQuery
  var extend = function() {
  	var options, name, src, copy, copyIsArray, clone, target = arguments[0] || {},
  		i = 1,
  		length = arguments.length,
  		deep = false,
  		toString = Object.prototype.toString,
  		hasOwn = Object.prototype.hasOwnProperty,
  		class2type = {
  			"[object Boolean]": "boolean",
  			"[object Number]": "number",
  			"[object String]": "string",
  			"[object Function]": "function",
  			"[object Array]": "array",
  			"[object Date]": "date",
  			"[object RegExp]": "regexp",
  			"[object Object]": "object"
  		},
  
  		jQuery = {
  			isFunction: function (obj) {
  				return jQuery.type(obj) === "function";
  			},
  			isArray: Array.isArray ||
  				function (obj) {
  					return jQuery.type(obj) === "array";
  				},
  			isWindow: function (obj) {
  				return obj !== null && obj === obj.window;
  			},
  			isNumeric: function (obj) {
  				return !isNaN(parseFloat(obj)) && isFinite(obj);
  			},
  			type: function (obj) {
  				return obj === null ? String(obj) : class2type[toString.call(obj)] || "object";
  			},
  			isPlainObject: function (obj) {
  				if (!obj || jQuery.type(obj) !== "object" || obj.nodeType) {
  					return false;
  				}
  				try {
  					if (obj.constructor && !hasOwn.call(obj, "constructor") && !hasOwn.call(obj.constructor.prototype, "isPrototypeOf")) {
  						return false;
  					}
  				} catch (e) {
  					return false;
  				}
  				var key;
  				for (key in obj) {}
  				return key === undefined || hasOwn.call(obj, key);
  			}
  		};
  	if (typeof target === "boolean") {
  		deep = target;
  		target = arguments[1] || {};
  		i = 2;
  	}
  	if (typeof target !== "object" && !jQuery.isFunction(target)) {
  		target = {};
  	}
  	if (length === i) {
  		target = this;
  		--i;
  	}
  	for (i; i < length; i++) {
  		if ((options = arguments[i]) !== null) {
  			for (name in options) {
  				src = target[name];
  				copy = options[name];
  				if (target === copy) {
  					continue;
  				}
  				if (deep && copy && (jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)))) {
  					if (copyIsArray) {
  						copyIsArray = false;
  						clone = src && jQuery.isArray(src) ? src : [];
  					} else {
  						clone = src && jQuery.isPlainObject(src) ? src : {};
  					}
  					// WARNING: RECURSION
  					target[name] = extend(deep, clone, copy);
  				} else if (copy !== undefined) {
  					target[name] = copy;
  				}
  			}
  		}
  	}
  	return target;
  };
  	//// --------- math.js -----------
  var math = {
  
  	toRadians: function(degrees) {
  		return degrees * (Math.PI / 180);
  	},
  
  	toDegrees: function(radians) {
  		return radians * (180 / Math.PI);
  	},
  
  	computePieRadius: function(pie) {
  		var size = pie.options.size;
  		var canvasPadding = pie.options.misc.canvasPadding;
  
  		// outer radius is either specified (e.g. through the generator), or omitted altogether
  		// and calculated based on the canvas dimensions. Right now the estimated version isn't great - it should
  		// be possible to calculate it to precisely generate the maximum sized pie, but it's fussy as heck. Something
  		// for the next release.
  
  		// first, calculate the default _outerRadius
  		var w = size.canvasWidth - canvasPadding.left - canvasPadding.right;
  		var h = size.canvasHeight - canvasPadding.top - canvasPadding.bottom;
  
  		// now factor in the footer, title & subtitle
  		if (pie.options.header.location !== "pie-center") {
  			h -= pie.textComponents.headerHeight;
  		}
  
  		if (pie.textComponents.footer.exists) {
  			h -= pie.textComponents.footer.h;
  		}
  
  		// for really teeny pies, h may be < 0. Adjust it back
  		h = (h < 0) ? 0 : h;
  
  		var outerRadius = ((w < h) ? w : h) / 3;
  		var innerRadius, percent;
  
  		// if the user specified something, use that instead
  		if (size.pieOuterRadius !== null) {
  			if (/%/.test(size.pieOuterRadius)) {
  				percent = parseInt(size.pieOuterRadius.replace(/[\D]/, ""), 10);
  				percent = (percent > 99) ? 99 : percent;
  				percent = (percent < 0) ? 0 : percent;
  
  				var smallestDimension = (w < h) ? w : h;
  
  				// now factor in the label line size
  				if (pie.options.labels.outer.format !== "none") {
  					var pieDistanceSpace = parseInt(pie.options.labels.outer.pieDistance, 10) * 2;
  					if (smallestDimension - pieDistanceSpace > 0) {
  						smallestDimension -= pieDistanceSpace;
  					}
  				}
  
  				outerRadius = Math.floor((smallestDimension / 100) * percent) / 2;
  			} else {
  				outerRadius = parseInt(size.pieOuterRadius, 10);
  			}
  		}
  
  		// inner radius
  		if (/%/.test(size.pieInnerRadius)) {
  			percent = parseInt(size.pieInnerRadius.replace(/[\D]/, ""), 10);
  			percent = (percent > 99) ? 99 : percent;
  			percent = (percent < 0) ? 0 : percent;
  			innerRadius = Math.floor((outerRadius / 100) * percent);
  		} else {
  			innerRadius = parseInt(size.pieInnerRadius, 10);
  		}
  
  		pie.innerRadius = innerRadius;
  		pie.outerRadius = outerRadius;
  	},
  
  	getTotalPieSize: function(data) {
  		var totalSize = 0;
  		for (var i=0; i<data.length; i++) {
  			totalSize += data[i].value;
  		}
  		return totalSize;
  	},
  
  	sortPieData: function(pie) {
  		var data                 = pie.options.data.content;
  		var sortOrder            = pie.options.data.sortOrder;
  
  		switch (sortOrder) {
  			case "none":
  				// do nothing
  				break;
  			case "random":
  				data = helpers.shuffleArray(data);
  				break;
  			case "value-asc":
  				data.sort(function(a, b) { return (a.value < b.value) ? -1 : 1; });
  				break;
  			case "value-desc":
  				data.sort(function(a, b) { return (a.value < b.value) ? 1 : -1; });
  				break;
  			case "label-asc":
  				data.sort(function(a, b) { return (a.label.toLowerCase() > b.label.toLowerCase()) ? 1 : -1; });
  				break;
  			case "label-desc":
  				data.sort(function(a, b) { return (a.label.toLowerCase() < b.label.toLowerCase()) ? 1 : -1; });
  				break;
  		}
  
  		return data;
  	},
  
  	// var pieCenter = math.getPieCenter();
  	getPieTranslateCenter: function(pieCenter) {
  		return "translate(" + pieCenter.x + "," + pieCenter.y + ")";
  	},
  
  	/**
  	 * Used to determine where on the canvas the center of the pie chart should be. It takes into account the
  	 * height and position of the title, subtitle and footer, and the various paddings.
  	 * @private
  	 */
  	calculatePieCenter: function(pie) {
  		var pieCenterOffset = pie.options.misc.pieCenterOffset;
  		var hasTopTitle    = (pie.textComponents.title.exists && pie.options.header.location !== "pie-center");
  		var hasTopSubtitle = (pie.textComponents.subtitle.exists && pie.options.header.location !== "pie-center");
  
  		var headerOffset = pie.options.misc.canvasPadding.top;
  		if (hasTopTitle && hasTopSubtitle) {
  			headerOffset += pie.textComponents.title.h + pie.options.header.titleSubtitlePadding + pie.textComponents.subtitle.h;
  		} else if (hasTopTitle) {
  			headerOffset += pie.textComponents.title.h;
  		} else if (hasTopSubtitle) {
  			headerOffset += pie.textComponents.subtitle.h;
  		}
  
  		var footerOffset = 0;
  		if (pie.textComponents.footer.exists) {
  			footerOffset = pie.textComponents.footer.h + pie.options.misc.canvasPadding.bottom;
  		}
  
  		var x = ((pie.options.size.canvasWidth - pie.options.misc.canvasPadding.left - pie.options.misc.canvasPadding.right) / 2) + pie.options.misc.canvasPadding.left;
  		var y = ((pie.options.size.canvasHeight - footerOffset - headerOffset) / 2) + headerOffset;
  
  		x += pieCenterOffset.x;
  		y += pieCenterOffset.y;
  
  		pie.pieCenter = { x: x, y: y };
  	},
  
  
  	/**
  	 * Rotates a point (x, y) around an axis (xm, ym) by degrees (a).
  	 * @param x
  	 * @param y
  	 * @param xm
  	 * @param ym
  	 * @param a angle in degrees
  	 * @returns {Array}
  	 */
  	rotate: function(x, y, xm, ym, a) {
  
          a = a * Math.PI / 180; // convert to radians
  
          var cos = Math.cos,
  			sin = Math.sin,
  		// subtract midpoints, so that midpoint is translated to origin and add it in the end again
  		xr = (x - xm) * cos(a) - (y - ym) * sin(a) + xm,
  		yr = (x - xm) * sin(a) + (y - ym) * cos(a) + ym;
  
  		return { x: xr, y: yr };
  	},
  
  	/**
  	 * Translates a point x, y by distance d, and by angle a.
  	 * @param x
  	 * @param y
  	 * @param dist
  	 * @param a angle in degrees
  	 */
  	translate: function(x, y, d, a) {
  		var rads = math.toRadians(a);
  		return {
  			x: x + d * Math.sin(rads),
  			y: y - d * Math.cos(rads)
  		};
  	},
  
  	// from: http://stackoverflow.com/questions/19792552/d3-put-arc-labels-in-a-pie-chart-if-there-is-enough-space
  	pointIsInArc: function(pt, ptData, d3Arc) {
  		// Center of the arc is assumed to be 0,0
  		// (pt.x, pt.y) are assumed to be relative to the center
  		var r1 = d3Arc.innerRadius()(ptData), // Note: Using the innerRadius
  			r2 = d3Arc.outerRadius()(ptData),
  			theta1 = d3Arc.startAngle()(ptData),
  			theta2 = d3Arc.endAngle()(ptData);
  
  		var dist = pt.x * pt.x + pt.y * pt.y,
  			angle = Math.atan2(pt.x, -pt.y); // Note: different coordinate system
  
  		angle = (angle < 0) ? (angle + Math.PI * 2) : angle;
  
  		return (r1 * r1 <= dist) && (dist <= r2 * r2) &&
  			(theta1 <= angle) && (angle <= theta2);
  	}
  };
  
  	//// --------- labels.js -----------
  var labels = {
  
  	/**
  	 * Adds the labels to the pie chart, but doesn't position them. There are two locations for the
  	 * labels: inside (center) of the segments, or outside the segments on the edge.
  	 * @param section "inner" or "outer"
  	 * @param sectionDisplayType "percentage", "value", "label", "label-value1", etc.
  	 * @param pie
  	 */
  	add: function(pie, section, sectionDisplayType) {
  		var include = labels.getIncludes(sectionDisplayType);
  		var settings = pie.options.labels;
  
  		// group the label groups (label, percentage, value) into a single element for simpler positioning
  		var outerLabel = pie.svg.insert("g", "." + pie.cssPrefix + "labels-" + section)
  			.attr("class", pie.cssPrefix + "labels-" + section);
  
  		var labelGroup = outerLabel.selectAll("." + pie.cssPrefix + "labelGroup-" + section)
  			.data(pie.options.data.content)
  			.enter()
  			.append("g")
  			.attr("id", function(d, i) { return pie.cssPrefix + "labelGroup" + i + "-" + section; })
  			.attr("data-index", function(d, i) { return i; })
  			.attr("class", pie.cssPrefix + "labelGroup-" + section)
  			.style("opacity", 0);
  
  		var formatterContext = { section: section, sectionDisplayType: sectionDisplayType };
  
  		// 1. Add the main label
  		if (include.mainLabel) {
  			labelGroup.append("text")
  				.attr("id", function(d, i) { return pie.cssPrefix + "segmentMainLabel" + i + "-" + section; })
  				.attr("class", pie.cssPrefix + "segmentMainLabel-" + section)
  				.text(function(d, i) {
  					var str = d.label;
  
  					// if a custom formatter has been defined, pass it the raw label string - it can do whatever it wants with it.
  					// we only apply truncation if it's not defined
  					if (settings.formatter) {
  						formatterContext.index = i;
  						formatterContext.part = 'mainLabel';
  						formatterContext.value = d.value;
  						formatterContext.label = str;
  						str = settings.formatter(formatterContext);
  					} else if (settings.truncation.enabled && d.label.length > settings.truncation.truncateLength) {
  						str = d.label.substring(0, settings.truncation.truncateLength) + "...";
  					}
  					return str;
  				})
  				.style("font-size", settings.mainLabel.fontSize + "px")
  				.style("font-family", settings.mainLabel.font)
  				.style("fill", settings.mainLabel.color);
  		}
  
  		// 2. Add the percentage label
  		if (include.percentage) {
  			labelGroup.append("text")
  				.attr("id", function(d, i) { return pie.cssPrefix + "segmentPercentage" + i + "-" + section; })
  				.attr("class", pie.cssPrefix + "segmentPercentage-" + section)
  				.text(function(d, i) {
  					var percentage = d.percentage;
  					if (settings.formatter) {
  						formatterContext.index = i;
  						formatterContext.part = "percentage";
  						formatterContext.value = d.value;
  						formatterContext.label = d.percentage;
  						percentage = settings.formatter(formatterContext);
  					} else {
  						percentage += "%";
  					}
  					return percentage;
  				})
  				.style("font-size", settings.percentage.fontSize + "px")
  				.style("font-family", settings.percentage.font)
  				.style("fill", settings.percentage.color);
  		}
  
  		// 3. Add the value label
  		if (include.value) {
  			labelGroup.append("text")
  				.attr("id", function(d, i) { return pie.cssPrefix +  "segmentValue" + i + "-" + section; })
  				.attr("class", pie.cssPrefix + "segmentValue-" + section)
  				.text(function(d, i) {
  					formatterContext.index = i;
  					formatterContext.part = "value";
  					formatterContext.value = d.value;
  					formatterContext.label = d.value;
  					return settings.formatter ? settings.formatter(formatterContext, d.value) : d.value;
  				})
  				.style("font-size", settings.value.fontSize + "px")
  				.style("font-family", settings.value.font)
  				.style("fill", settings.value.color);
  		}
  	},
  
  	/**
  	 * @param section "inner" / "outer"
  	 */
  	positionLabelElements: function(pie, section, sectionDisplayType) {
  		labels["dimensions-" + section] = [];
  
  		// get the latest widths, heights
  		var labelGroups = d3.selectAll("." + pie.cssPrefix + "labelGroup-" + section);
  		labelGroups.each(function(d, i) {
  			var mainLabel  = d3.select(this).selectAll("." + pie.cssPrefix + "segmentMainLabel-" + section);
  			var percentage = d3.select(this).selectAll("." + pie.cssPrefix + "segmentPercentage-" + section);
  			var value      = d3.select(this).selectAll("." + pie.cssPrefix + "segmentValue-" + section);
  
  			labels["dimensions-" + section].push({
  				mainLabel:  (mainLabel.node() !== null) ? mainLabel.node().getBBox() : null,
  				percentage: (percentage.node() !== null) ? percentage.node().getBBox() : null,
  				value:      (value.node() !== null) ? value.node().getBBox() : null
  			});
  		});
  
  		var singleLinePad = 5;
  		var dims = labels["dimensions-" + section];
  		switch (sectionDisplayType) {
  			case "label-value1":
  				d3.selectAll("." + pie.cssPrefix + "segmentValue-" + section)
  					.attr("dx", function(d, i) { return dims[i].mainLabel.width + singleLinePad; });
  				break;
  			case "label-value2":
  				d3.selectAll("." + pie.cssPrefix + "segmentValue-" + section)
  					.attr("dy", function(d, i) { return dims[i].mainLabel.height; });
  				break;
  			case "label-percentage1":
  				d3.selectAll("." + pie.cssPrefix + "segmentPercentage-" + section)
  					.attr("dx", function(d, i) { return dims[i].mainLabel.width + singleLinePad; });
  				break;
  			case "label-percentage2":
  				d3.selectAll("." + pie.cssPrefix + "segmentPercentage-" + section)
  					.attr("dx", function(d, i) { return (dims[i].mainLabel.width / 2) - (dims[i].percentage.width / 2); })
  					.attr("dy", function(d, i) { return dims[i].mainLabel.height; });
  				break;
  	 	}
  	},
  
  	computeLabelLinePositions: function(pie) {
  		pie.lineCoordGroups = [];
  		d3.selectAll("." + pie.cssPrefix + "labelGroup-outer")
  			.each(function(d, i) { return labels.computeLinePosition(pie, i); });
  	},
  
  	computeLinePosition: function(pie, i) {
  		var angle = segments.getSegmentAngle(i, pie.options.data.content, pie.totalSize, { midpoint: true });
  		var originCoords = math.rotate(pie.pieCenter.x, pie.pieCenter.y - pie.outerRadius, pie.pieCenter.x, pie.pieCenter.y, angle);
  		var heightOffset = pie.outerLabelGroupData[i].h / 5; // TODO check
  		var labelXMargin = 6; // the x-distance of the label from the end of the line [TODO configurable]
  
  		var quarter = Math.floor(angle / 90);
  		var midPoint = 4;
  		var x2, y2, x3, y3;
  
  		// this resolves an issue when the
  		if (quarter === 2 && angle === 180) {
  			quarter = 1;
  		}
  
  		switch (quarter) {
  			case 0:
  				x2 = pie.outerLabelGroupData[i].x - labelXMargin - ((pie.outerLabelGroupData[i].x - labelXMargin - originCoords.x) / 2);
  				y2 = pie.outerLabelGroupData[i].y + ((originCoords.y - pie.outerLabelGroupData[i].y) / midPoint);
  				x3 = pie.outerLabelGroupData[i].x - labelXMargin;
  				y3 = pie.outerLabelGroupData[i].y - heightOffset;
  				break;
  			case 1:
  				x2 = originCoords.x + (pie.outerLabelGroupData[i].x - originCoords.x) / midPoint;
  				y2 = originCoords.y + (pie.outerLabelGroupData[i].y - originCoords.y) / midPoint;
  				x3 = pie.outerLabelGroupData[i].x - labelXMargin;
  				y3 = pie.outerLabelGroupData[i].y - heightOffset;
  				break;
  			case 2:
  				var startOfLabelX = pie.outerLabelGroupData[i].x + pie.outerLabelGroupData[i].w + labelXMargin;
  				x2 = originCoords.x - (originCoords.x - startOfLabelX) / midPoint;
  				y2 = originCoords.y + (pie.outerLabelGroupData[i].y - originCoords.y) / midPoint;
  				x3 = pie.outerLabelGroupData[i].x + pie.outerLabelGroupData[i].w + labelXMargin;
  				y3 = pie.outerLabelGroupData[i].y - heightOffset;
  				break;
  			case 3:
  				var startOfLabel = pie.outerLabelGroupData[i].x + pie.outerLabelGroupData[i].w + labelXMargin;
  				x2 = startOfLabel + ((originCoords.x - startOfLabel) / midPoint);
  				y2 = pie.outerLabelGroupData[i].y + (originCoords.y - pie.outerLabelGroupData[i].y) / midPoint;
  				x3 = pie.outerLabelGroupData[i].x + pie.outerLabelGroupData[i].w + labelXMargin;
  				y3 = pie.outerLabelGroupData[i].y - heightOffset;
  				break;
  		}
  
  		/*
  		 * x1 / y1: the x/y coords of the start of the line, at the mid point of the segments arc on the pie circumference
  		 * x2 / y2: if "curved" line style is being used, this is the midpoint of the line. Other
  		 * x3 / y3: the end of the line; closest point to the label
  		 */
  		if (pie.options.labels.lines.style === "straight") {
  			pie.lineCoordGroups[i] = [
  				{ x: originCoords.x, y: originCoords.y },
  				{ x: x3, y: y3 }
  			];
  		} else {
  			pie.lineCoordGroups[i] = [
  				{ x: originCoords.x, y: originCoords.y },
  				{ x: x2, y: y2 },
  				{ x: x3, y: y3 }
  			];
  		}
  	},
  
  	addLabelLines: function(pie) {
  		var lineGroups = pie.svg.insert("g", "." + pie.cssPrefix + "pieChart") // meaning, BEFORE .pieChart
  			.attr("class", pie.cssPrefix + "lineGroups")
  			.style("opacity", 0);
  
  		var lineGroup = lineGroups.selectAll("." + pie.cssPrefix + "lineGroup")
  			.data(pie.lineCoordGroups)
  			.enter()
  			.append("g")
  			.attr("class", pie.cssPrefix + "lineGroup");
  
          var lineFunction = d3.line()
  			.curve(d3.curveBasis)
  			.x(function(d) { return d.x; })
  			.y(function(d) { return d.y; });
  
  		lineGroup.append("path")
  			.attr("d", lineFunction)
  			.attr("stroke", function(d, i) {
  				return (pie.options.labels.lines.color === "segment") ? pie.options.colors[i] : pie.options.labels.lines.color;
  			})
  			.attr("stroke-width", 1)
  			.attr("fill", "none")
  			.style("opacity", function(d, i) {
  				var percentage = pie.options.labels.outer.hideWhenLessThanPercentage;
  				var isHidden = (percentage !== null && d.percentage < percentage) || pie.options.data.content[i].label === "";
  				return isHidden ? 0 : 1;
  			});
  	},
  
  	positionLabelGroups: function(pie, section) {
      if (pie.options.labels[section].format === "none") {
        return;
      }
  
  		d3.selectAll("." + pie.cssPrefix + "labelGroup-" + section)
  			.style("opacity", 0)
  			.attr("transform", function(d, i) {
  				var x, y;
  				if (section === "outer") {
  					x = pie.outerLabelGroupData[i].x;
  					y = pie.outerLabelGroupData[i].y;
  				} else {
  					var pieCenterCopy = extend(true, {}, pie.pieCenter);
  
  					// now recompute the "center" based on the current _innerRadius
  					if (pie.innerRadius > 0) {
  						var angle = segments.getSegmentAngle(i, pie.options.data.content, pie.totalSize, { midpoint: true });
  						var newCoords = math.translate(pie.pieCenter.x, pie.pieCenter.y, pie.innerRadius, angle);
  						pieCenterCopy.x = newCoords.x;
  						pieCenterCopy.y = newCoords.y;
  					}
  
  					var dims = helpers.getDimensions(pie.cssPrefix + "labelGroup" + i + "-inner");
  					var xOffset = dims.w / 2;
  					var yOffset = dims.h / 4; // confusing! Why 4? should be 2, but it doesn't look right
  
  					x = pieCenterCopy.x + (pie.lineCoordGroups[i][0].x - pieCenterCopy.x) / 1.8;
  					y = pieCenterCopy.y + (pie.lineCoordGroups[i][0].y - pieCenterCopy.y) / 1.8;
  
  					x = x - xOffset;
  					y = y + yOffset;
  				}
  
  				return "translate(" + x + "," + y + ")";
  			});
  	},
  
  
  	fadeInLabelsAndLines: function(pie) {
  
  		// fade in the labels when the load effect is complete - or immediately if there's no load effect
  		var loadSpeed = (pie.options.effects.load.effect === "default") ? pie.options.effects.load.speed : 1;
  		setTimeout(function() {
  			var labelFadeInTime = (pie.options.effects.load.effect === "default") ? 400 : 1; // 400 is hardcoded for the present
  
  			d3.selectAll("." + pie.cssPrefix + "labelGroup-outer")
  				.transition()
  				.duration(labelFadeInTime)
  				.style("opacity", function(d, i) {
  					var percentage = pie.options.labels.outer.hideWhenLessThanPercentage;
  					return (percentage !== null && d.percentage < percentage) ? 0 : 1;
  				});
  
  			d3.selectAll("." + pie.cssPrefix + "labelGroup-inner")
  				.transition()
  				.duration(labelFadeInTime)
  				.style("opacity", function(d, i) {
  					var percentage = pie.options.labels.inner.hideWhenLessThanPercentage;
  					return (percentage !== null && d.percentage < percentage) ? 0 : 1;
  				});
  
  			d3.selectAll("g." + pie.cssPrefix + "lineGroups")
  				.transition()
  				.duration(labelFadeInTime)
  				.style("opacity", 1);
  
  			// once everything's done loading, trigger the onload callback if defined
  			if (helpers.isFunction(pie.options.callbacks.onload)) {
  				setTimeout(function() {
  					try {
  						pie.options.callbacks.onload();
  					} catch (e) { }
  				}, labelFadeInTime);
  			}
  		}, loadSpeed);
  	},
  
  	getIncludes: function(val) {
  		var addMainLabel  = false;
  		var addValue      = false;
  		var addPercentage = false;
  
  		switch (val) {
  			case "label":
  				addMainLabel = true;
  				break;
  			case "value":
  				addValue = true;
  				break;
  			case "percentage":
  				addPercentage = true;
  				break;
  			case "label-value1":
  			case "label-value2":
  				addMainLabel = true;
  				addValue = true;
  				break;
  			case "label-percentage1":
  			case "label-percentage2":
  				addMainLabel = true;
  				addPercentage = true;
  				break;
  		}
  		return {
  			mainLabel: addMainLabel,
  			value: addValue,
  			percentage: addPercentage
  		};
  	},
  
  
  	/**
  	 * This does the heavy-lifting to compute the actual coordinates for the outer label groups. It does two things:
  	 * 1. Make a first pass and position them in the ideal positions, based on the pie sizes
  	 * 2. Do some basic collision avoidance.
  	 */
  	computeOuterLabelCoords: function(pie) {
  
  		// 1. figure out the ideal positions for the outer labels
  		pie.svg.selectAll("." + pie.cssPrefix + "labelGroup-outer")
  			.each(function(d, i) {
  				return labels.getIdealOuterLabelPositions(pie, i);
  			});
  
  		// 2. now adjust those positions to try to accommodate conflicts
  		labels.resolveOuterLabelCollisions(pie);
  	},
  
  	/**
  	 * This attempts to resolve label positioning collisions.
  	 */
  	resolveOuterLabelCollisions: function(pie) {
      if (pie.options.labels.outer.format === "none") {
        return;
      }
  
  		var size = pie.options.data.content.length;
  		labels.checkConflict(pie, 0, "clockwise", size);
  		labels.checkConflict(pie, size-1, "anticlockwise", size);
  	},
  
  	checkConflict: function(pie, currIndex, direction, size) {
      var i, curr;
  
  		if (size <= 1) {
  			return;
  		}
  
  		var currIndexHemisphere = pie.outerLabelGroupData[currIndex].hs;
  		if (direction === "clockwise" && currIndexHemisphere !== "right") {
  			return;
  		}
  		if (direction === "anticlockwise" && currIndexHemisphere !== "left") {
  			return;
  		}
  		var nextIndex = (direction === "clockwise") ? currIndex+1 : currIndex-1;
  
  		// this is the current label group being looked at. We KNOW it's positioned properly (the first item
  		// is always correct)
  		var currLabelGroup = pie.outerLabelGroupData[currIndex];
  
  		// this one we don't know about. That's the one we're going to look at and move if necessary
  		var examinedLabelGroup = pie.outerLabelGroupData[nextIndex];
  
  		var info = {
  			labelHeights: pie.outerLabelGroupData[0].h,
  			center: pie.pieCenter,
  			lineLength: (pie.outerRadius + pie.options.labels.outer.pieDistance),
  			heightChange: pie.outerLabelGroupData[0].h + 1 // 1 = padding
  		};
  
  		// loop through *ALL* label groups examined so far to check for conflicts. This is because when they're
  		// very tightly fitted, a later label group may still appear high up on the page
  		if (direction === "clockwise") {
        i = 0;
  			for (; i<=currIndex; i++) {
  				curr = pie.outerLabelGroupData[i];
  
  				// if there's a conflict with this label group, shift the label to be AFTER the last known
  				// one that's been properly placed
  				if (!labels.isLabelHidden(pie, i) && helpers.rectIntersect(curr, examinedLabelGroup)) {
  					labels.adjustLabelPos(pie, nextIndex, currLabelGroup, info);
  					break;
  				}
  			}
  		} else {
        i = size - 1;
  			for (; i >= currIndex; i--) {
  				curr = pie.outerLabelGroupData[i];
  
  				// if there's a conflict with this label group, shift the label to be AFTER the last known
  				// one that's been properly placed
  				if (!labels.isLabelHidden(pie, i) && helpers.rectIntersect(curr, examinedLabelGroup)) {
  					labels.adjustLabelPos(pie, nextIndex, currLabelGroup, info);
  					break;
  				}
  			}
  		}
  		labels.checkConflict(pie, nextIndex, direction, size);
  	},
  
  	isLabelHidden: function(pie, index) {
  		var percentage = pie.options.labels.outer.hideWhenLessThanPercentage;
  		return (percentage !== null && d.percentage < percentage) || pie.options.data.content[index].label === "";
  	},
  
  	// does a little math to shift a label into a new position based on the last properly placed one
  	adjustLabelPos: function(pie, nextIndex, lastCorrectlyPositionedLabel, info) {
  		var xDiff, yDiff, newXPos, newYPos;
  		newYPos = lastCorrectlyPositionedLabel.y + info.heightChange;
  		yDiff = info.center.y - newYPos;
  
  		if (Math.abs(info.lineLength) > Math.abs(yDiff)) {
  			xDiff = Math.sqrt((info.lineLength * info.lineLength) - (yDiff * yDiff));
  		} else {
  			xDiff = Math.sqrt((yDiff * yDiff) - (info.lineLength * info.lineLength));
  		}
  
  		if (lastCorrectlyPositionedLabel.hs === "right") {
  			newXPos = info.center.x + xDiff;
  		} else {
  			newXPos = info.center.x - xDiff - pie.outerLabelGroupData[nextIndex].w;
  		}
  
  		pie.outerLabelGroupData[nextIndex].x = newXPos;
  		pie.outerLabelGroupData[nextIndex].y = newYPos;
  	},
  
  	/**
  	 * @param i 0-N where N is the dataset size - 1.
  	 */
  	getIdealOuterLabelPositions: function(pie, i) {
      var labelGroupNode = d3.select("#" + pie.cssPrefix + "labelGroup" + i + "-outer").node();
      if (!labelGroupNode) {
        return;
      }
      var labelGroupDims = labelGroupNode.getBBox();
  		var angle = segments.getSegmentAngle(i, pie.options.data.content, pie.totalSize, { midpoint: true });
  
  		var originalX = pie.pieCenter.x;
  		var originalY = pie.pieCenter.y - (pie.outerRadius + pie.options.labels.outer.pieDistance);
  		var newCoords = math.rotate(originalX, originalY, pie.pieCenter.x, pie.pieCenter.y, angle);
  
  		// if the label is on the left half of the pie, adjust the values
  		var hemisphere = "right"; // hemisphere
  		if (angle > 180) {
  			newCoords.x -= (labelGroupDims.width + 8);
  			hemisphere = "left";
  		} else {
  			newCoords.x += 8;
  		}
  
  		pie.outerLabelGroupData[i] = {
  			x: newCoords.x,
  			y: newCoords.y,
  			w: labelGroupDims.width,
  			h: labelGroupDims.height,
  			hs: hemisphere
  		};
  	}
  };
  
  	//// --------- segments.js -----------
  var segments = {
  
      effectMap: {
          "none": d3.easeLinear,
          "bounce": d3.easeBounce,
          "linear": d3.easeLinear,
          "sin": d3.easeSin,
          "elastic": d3.easeElastic,
          "back": d3.easeBack,
          "quad": d3.easeQuad,
          "circle": d3.easeCircle,
          "exp": d3.easeExp
      },
  
  	/**
  	 * Creates the pie chart segments and displays them according to the desired load effect.
  	 * @private
  	 */
  	create: function(pie) {
  		var pieCenter = pie.pieCenter;
  		var colors = pie.options.colors;
  		var loadEffects = pie.options.effects.load;
  		var segmentStroke = pie.options.misc.colors.segmentStroke;
  
  		// we insert the pie chart BEFORE the title, to ensure the title overlaps the pie
  		var pieChartElement = pie.svg.insert("g", "#" + pie.cssPrefix + "title")
  			.attr("transform", function() { return math.getPieTranslateCenter(pieCenter); })
  			.attr("class", pie.cssPrefix + "pieChart");
  
  		var arc = d3.arc()
  			.innerRadius(pie.innerRadius)
  			.outerRadius(pie.outerRadius)
  			.startAngle(0)
  			.endAngle(function(d) {
  				return (d.value / pie.totalSize) * 2 * Math.PI;
  			});
  
  		var g = pieChartElement.selectAll("." + pie.cssPrefix + "arc")
  			.data(pie.options.data.content)
  			.enter()
  			.append("g")
  			.attr("class", pie.cssPrefix + "arc");
  
  		// if we're not fading in the pie, just set the load speed to 0
  		var loadSpeed = loadEffects.speed;
  		if (loadEffects.effect === "none") {
  			loadSpeed = 0;
  		}
  
  		g.append("path")
  			.attr("id", function(d, i) { return pie.cssPrefix + "segment" + i; })
  			.attr("fill", function(d, i) {
  				var color = colors[i];
  				if (pie.options.misc.gradient.enabled) {
  					color = "url(#" + pie.cssPrefix + "grad" + i + ")";
  				}
  				return color;
  			})
  			.style("stroke", segmentStroke)
  			.style("stroke-width", 1)
  			.transition()
  			.ease(d3.easeCubicInOut)
  			.duration(loadSpeed)
  			.attr("data-index", function(d, i) { return i; })
  			.attrTween("d", function(b) {
  				var i = d3.interpolate({ value: 0 }, b);
  				return function(t) {
  					return pie.arc(i(t));
  				};
  			});
  
  		pie.svg.selectAll("g." + pie.cssPrefix + "arc")
  			.attr("transform",
  			function(d, i) {
  				var angle = 0;
  				if (i > 0) {
  					angle = segments.getSegmentAngle(i-1, pie.options.data.content, pie.totalSize);
  				}
  				return "rotate(" + angle + ")";
  			}
  		);
  		pie.arc = arc;
  	},
  
  	addGradients: function(pie) {
  		var grads = pie.svg.append("defs")
  			.selectAll("radialGradient")
  			.data(pie.options.data.content)
  			.enter().append("radialGradient")
  			.attr("gradientUnits", "userSpaceOnUse")
  			.attr("cx", 0)
  			.attr("cy", 0)
  			.attr("r", "120%")
  			.attr("id", function(d, i) { return pie.cssPrefix + "grad" + i; });
  
  		grads.append("stop").attr("offset", "0%").style("stop-color", function(d, i) { return pie.options.colors[i]; });
  		grads.append("stop").attr("offset", pie.options.misc.gradient.percentage + "%").style("stop-color", pie.options.misc.gradient.color);
  	},
  
  	addSegmentEventHandlers: function(pie) {
  		var arc = d3.selectAll("." + pie.cssPrefix + "arc,." + pie.cssPrefix + "labelGroup-inner,." + pie.cssPrefix + "labelGroup-outer");
  
  		arc.on("click", function() {
  			var currentEl = d3.select(this);
  			var segment;
  
  			// mouseover works on both the segments AND the segment labels, hence the following
  			if (currentEl.attr("class") === pie.cssPrefix + "arc") {
  				segment = currentEl.select("path");
  			} else {
  				var index = currentEl.attr("data-index");
  				segment = d3.select("#" + pie.cssPrefix + "segment" + index);
  			}
  
  			var isExpanded = segment.attr("class") === pie.cssPrefix + "expanded";
  			segments.onSegmentEvent(pie, pie.options.callbacks.onClickSegment, segment, isExpanded);
  			if (pie.options.effects.pullOutSegmentOnClick.effect !== "none") {
  				if (isExpanded) {
  					segments.closeSegment(pie, segment.node());
  				} else {
  					segments.openSegment(pie, segment.node());
  				}
  			}
  		});
  
  		arc.on("mouseover", function() {
  			var currentEl = d3.select(this);
  			var segment, index;
  
  			if (currentEl.attr("class") === pie.cssPrefix + "arc") {
  				segment = currentEl.select("path");
  			} else {
  				index = currentEl.attr("data-index");
  				segment = d3.select("#" + pie.cssPrefix + "segment" + index);
  			}
  
  			if (pie.options.effects.highlightSegmentOnMouseover) {
  				index = segment.attr("data-index");
  				var segColor = pie.options.colors[index];
  				segment.style("fill", helpers.getColorShade(segColor, pie.options.effects.highlightLuminosity));
  			}
  
  			if (pie.options.tooltips.enabled) {
  				index = segment.attr("data-index");
  				tt.showTooltip(pie, index);
  			}
  
  			var isExpanded = segment.attr("class") === pie.cssPrefix + "expanded";
  			segments.onSegmentEvent(pie, pie.options.callbacks.onMouseoverSegment, segment, isExpanded);
  		});
  
  		arc.on("mousemove", function() {
  			tt.moveTooltip(pie);
  		});
  
  		arc.on("mouseout", function() {
  			var currentEl = d3.select(this);
  			var segment, index;
  
  			if (currentEl.attr("class") === pie.cssPrefix + "arc") {
  				segment = currentEl.select("path");
  			} else {
  				index = currentEl.attr("data-index");
  				segment = d3.select("#" + pie.cssPrefix + "segment" + index);
  			}
  
  			if (pie.options.effects.highlightSegmentOnMouseover) {
  				index = segment.attr("data-index");
  				var color = pie.options.colors[index];
  				if (pie.options.misc.gradient.enabled) {
  					color = "url(#" + pie.cssPrefix + "grad" + index + ")";
  				}
  				segment.style("fill", color);
  			}
  
  			if (pie.options.tooltips.enabled) {
  				index = segment.attr("data-index");
  				tt.hideTooltip(pie, index);
  			}
  
  			var isExpanded = segment.attr("class") === pie.cssPrefix + "expanded";
  			segments.onSegmentEvent(pie, pie.options.callbacks.onMouseoutSegment, segment, isExpanded);
  		});
  	},
  
  	// helper function used to call the click, mouseover, mouseout segment callback functions
  	onSegmentEvent: function(pie, func, segment, isExpanded) {
  		if (!helpers.isFunction(func)) {
  			return;
  		}
  		var index = parseInt(segment.attr("data-index"), 10);
  		func({
  			segment: segment.node(),
  			index: index,
  			expanded: isExpanded,
  			data: pie.options.data.content[index]
  		});
  	},
  
  	openSegment: function(pie, segment) {
  		if (pie.isOpeningSegment) {
  			return;
  		}
  		pie.isOpeningSegment = true;
  
  		segments.maybeCloseOpenSegment();
  
  		d3.select(segment).transition()
  			.ease(segments.effectMap[pie.options.effects.pullOutSegmentOnClick.effect])
  			.duration(pie.options.effects.pullOutSegmentOnClick.speed)
  			.attr("transform", function(d, i) {
  				var c = pie.arc.centroid(d),
  					x = c[0],
  					y = c[1],
  					h = Math.sqrt(x*x + y*y),
  					pullOutSize = parseInt(pie.options.effects.pullOutSegmentOnClick.size, 10);
  
  				return "translate(" + ((x/h) * pullOutSize) + ',' + ((y/h) * pullOutSize) + ")";
  			})
  			.on("end", function(d, i) {
  				pie.currentlyOpenSegment = segment;
  				pie.isOpeningSegment = false;
  				d3.select(segment).attr("class", pie.cssPrefix + "expanded");
  			});
  	},
  
      maybeCloseOpenSegment: function() {
          if (d3.selectAll("." + pie.cssPrefix + "expanded").size() > 0) {
              segments.closeSegment(pie, d3.select("." + pie.cssPrefix + "expanded").node());
          }
  	},
  
  	closeSegment: function(pie, segment) {
  		d3.select(segment).transition()
  			.duration(400)
  			.attr("transform", "translate(0,0)")
  			.on("end", function(d, i) {
  				d3.select(segment).attr("class", "");
  				pie.currentlyOpenSegment = null;
  			});
  	},
  
  	getCentroid: function(el) {
  		var bbox = el.getBBox();
  		return {
  			x: bbox.x + bbox.width / 2,
  			y: bbox.y + bbox.height / 2
  		};
  	},
  
  	/**
  	 * General helper function to return a segment's angle, in various different ways.
  	 * @param index
  	 * @param opts optional object for fine-tuning exactly what you want.
  	 */
  	getSegmentAngle: function(index, data, totalSize, opts) {
  		var options = extend({
  			// if true, this returns the full angle from the origin. Otherwise it returns the single segment angle
  			compounded: true,
  
  			// optionally returns the midpoint of the angle instead of the full angle
  			midpoint: false
  		}, opts);
  
  		var currValue = data[index].value;
  		var fullValue;
  		if (options.compounded) {
  			fullValue = 0;
  
  			// get all values up to and including the specified index
  			for (var i=0; i<=index; i++) {
  				fullValue += data[i].value;
  			}
  		}
  
  		if (typeof fullValue === 'undefined') {
  			fullValue = currValue;
  		}
  
  		// now convert the full value to an angle
  		var angle = (fullValue / totalSize) * 360;
  
  		// lastly, if we want the midpoint, factor that sucker in
  		if (options.midpoint) {
  			var currAngle = (currValue / totalSize) * 360;
  			angle -= (currAngle / 2);
  		}
  
  		return angle;
  	}
  
  };
  
  	//// --------- text.js -----------
  var text = {
  	offscreenCoord: -10000,
  
  	addTitle: function(pie) {
  
  
  		var title = pie.svg.selectAll("." + pie.cssPrefix + "title")
  			.data([pie.options.header.title])
  			.enter()
  			.append("text")
  			.text(function(d) { return d.text; })
  			.attr("id", pie.cssPrefix + "title")
          	.attr("class", pie.cssPrefix + "title")
  			.attr("x", text.offscreenCoord)
  			.attr("y", text.offscreenCoord)
  			.attr("text-anchor", function() {
  				var location;
  				if (pie.options.header.location === "top-center" || pie.options.header.location === "pie-center") {
  					location = "middle";
  				} else {
  					location = "left";
  				}
  				return location;
  			})
  			.attr("fill", function(d) { return d.color; })
  			.style("font-size", function(d) { return d.fontSize + "px"; })
  			.style("font-family", function(d) { return d.font; });
  	},
  
  	positionTitle: function(pie) {
  		var textComponents = pie.textComponents;
  		var headerLocation = pie.options.header.location;
  		var canvasPadding = pie.options.misc.canvasPadding;
  		var canvasWidth = pie.options.size.canvasWidth;
  		var titleSubtitlePadding = pie.options.header.titleSubtitlePadding;
  
  		var x;
  		if (headerLocation === "top-left") {
  			x = canvasPadding.left;
  		} else {
  			x = ((canvasWidth - canvasPadding.right) / 2) + canvasPadding.left;
  		}
  
      // add whatever offset has been added by user
      x += pie.options.misc.pieCenterOffset.x;
  
  		var y = canvasPadding.top + textComponents.title.h;
  
  		if (headerLocation === "pie-center") {
  			y = pie.pieCenter.y;
  
  			// still not fully correct
  			if (textComponents.subtitle.exists) {
  				var totalTitleHeight = textComponents.title.h + titleSubtitlePadding + textComponents.subtitle.h;
  				y = y - (totalTitleHeight / 2) + textComponents.title.h;
  			} else {
  				y += (textComponents.title.h / 4);
  			}
  		}
  
  		pie.svg.select("#" + pie.cssPrefix + "title")
  			.attr("x", x)
  			.attr("y", y);
  	},
  
  	addSubtitle: function(pie) {
  		var headerLocation = pie.options.header.location;
  
  		pie.svg.selectAll("." + pie.cssPrefix + "subtitle")
  			.data([pie.options.header.subtitle])
  			.enter()
  			.append("text")
  			.text(function(d) { return d.text; })
  			.attr("x", text.offscreenCoord)
  			.attr("y", text.offscreenCoord)
  			.attr("id", pie.cssPrefix + "subtitle")
  			.attr("class", pie.cssPrefix + "subtitle")
  			.attr("text-anchor", function() {
  				var location;
  				if (headerLocation === "top-center" || headerLocation === "pie-center") {
  					location = "middle";
  				} else {
  					location = "left";
  				}
  				return location;
  			})
  			.attr("fill", function(d) { return d.color; })
  			.style("font-size", function(d) { return d.fontSize + "px"; })
  			.style("font-family", function(d) { return d.font; });
  	},
  
  	positionSubtitle: function(pie) {
  		var canvasPadding = pie.options.misc.canvasPadding;
  		var canvasWidth = pie.options.size.canvasWidth;
  
  		var x;
  		if (pie.options.header.location === "top-left") {
  			x = canvasPadding.left;
  		} else {
  			x = ((canvasWidth - canvasPadding.right) / 2) + canvasPadding.left;
  		}
  
      // add whatever offset has been added by user
      x += pie.options.misc.pieCenterOffset.x;
  
  		var y = text.getHeaderHeight(pie);
  		pie.svg.select("#" + pie.cssPrefix + "subtitle")
  			.attr("x", x)
  			.attr("y", y);
  	},
  
  	addFooter: function(pie) {
  		pie.svg.selectAll("." + pie.cssPrefix + "footer")
  			.data([pie.options.footer])
  			.enter()
  			.append("text")
  			.text(function(d) { return d.text; })
  			.attr("x", text.offscreenCoord)
  			.attr("y", text.offscreenCoord)
  			.attr("id", pie.cssPrefix + "footer")
  			.attr("class", pie.cssPrefix + "footer")
  			.attr("text-anchor", function() {
  				var location = "left";
  				if (pie.options.footer.location === "bottom-center") {
  					location = "middle";
  				} else if (pie.options.footer.location === "bottom-right") {
  					location = "left"; // on purpose. We have to change the x-coord to make it properly right-aligned
  				}
  				return location;
  			})
  			.attr("fill", function(d) { return d.color; })
  			.style("font-size", function(d) { return d.fontSize + "px"; })
  			.style("font-family", function(d) { return d.font; });
  	},
  
  	positionFooter: function(pie) {
  		var footerLocation = pie.options.footer.location;
  		var footerWidth = pie.textComponents.footer.w;
  		var canvasWidth = pie.options.size.canvasWidth;
  		var canvasHeight = pie.options.size.canvasHeight;
  		var canvasPadding = pie.options.misc.canvasPadding;
  
  		var x;
  		if (footerLocation === "bottom-left") {
  			x = canvasPadding.left;
  		} else if (footerLocation === "bottom-right") {
  			x = canvasWidth - footerWidth - canvasPadding.right;
  		} else {
  			x = canvasWidth / 2; // TODO - shouldn't this also take into account padding?
  		}
  
  		pie.svg.select("#" + pie.cssPrefix + "footer")
  			.attr("x", x)
  			.attr("y", canvasHeight - canvasPadding.bottom);
  	},
  
  	getHeaderHeight: function(pie) {
  		var h;
  		if (pie.textComponents.title.exists) {
  
  			// if the subtitle isn't defined, it'll be set to 0
  			var totalTitleHeight = pie.textComponents.title.h + pie.options.header.titleSubtitlePadding + pie.textComponents.subtitle.h;
  			if (pie.options.header.location === "pie-center") {
  				h = pie.pieCenter.y - (totalTitleHeight / 2) + totalTitleHeight;
  			} else {
  				h = totalTitleHeight + pie.options.misc.canvasPadding.top;
  			}
  		} else {
  			if (pie.options.header.location === "pie-center") {
  				var footerPlusPadding = pie.options.misc.canvasPadding.bottom + pie.textComponents.footer.h;
  				h = ((pie.options.size.canvasHeight - footerPlusPadding) / 2) + pie.options.misc.canvasPadding.top + (pie.textComponents.subtitle.h / 2);
  			} else {
  				h = pie.options.misc.canvasPadding.top + pie.textComponents.subtitle.h;
  			}
  		}
  		return h;
  	}
  };
  
  	//// --------- validate.js -----------
  var tt = {
      addTooltips: function(pie) {
  
      // group the label groups (label, percentage, value) into a single element for simpler positioning
      var tooltips = pie.svg.insert("g")
          .attr("class", pie.cssPrefix + "tooltips");
  
      tooltips.selectAll("." + pie.cssPrefix + "tooltip")
          .data(pie.options.data.content)
          .enter()
          .append("g")
          .attr("class", pie.cssPrefix + "tooltip")
          .attr("id", function(d, i) { return pie.cssPrefix + "tooltip" + i; })
          .style("opacity", 0)
          .append("rect")
          .attr("rx", pie.options.tooltips.styles.borderRadius)
          .attr("ry", pie.options.tooltips.styles.borderRadius)
          .attr("x", -pie.options.tooltips.styles.padding)
          .attr("opacity", pie.options.tooltips.styles.backgroundOpacity)
          .style("fill", pie.options.tooltips.styles.backgroundColor);
  
      tooltips.selectAll("." + pie.cssPrefix + "tooltip")
          .data(pie.options.data.content)
          .append("text")
          .attr("fill", function(d) { return pie.options.tooltips.styles.color; })
          .style("font-size", function(d) { return pie.options.tooltips.styles.fontSize; })
          .style("font-family", function(d) { return pie.options.tooltips.styles.font; })
          .text(function(d, i) {
              var caption = pie.options.tooltips.string;
              if (pie.options.tooltips.type === "caption") {
                  caption = d.caption;
              }
              return tt.replacePlaceholders(pie, caption, i, {
                  label: d.label,
                  value: d.value,
                  percentage: d.percentage
              });
          });
  
  		tooltips.selectAll("." + pie.cssPrefix + "tooltip rect")
  			.attr("width", function (d, i) {
                  var dims = helpers.getDimensions(pie.cssPrefix + "tooltip" + i);
                  return dims.w + (2 * pie.options.tooltips.styles.padding);
              })
              .attr("height", function (d, i) {
                  var dims = helpers.getDimensions(pie.cssPrefix + "tooltip" + i);
                  return dims.h + (2 * pie.options.tooltips.styles.padding);
              })
              .attr("y", function (d, i) {
                  var dims = helpers.getDimensions(pie.cssPrefix + "tooltip" + i);
                  return -(dims.h / 2) + 1;
              });
  	},
  
      showTooltip: function(pie, index) {
          var fadeInSpeed = pie.options.tooltips.styles.fadeInSpeed;
          if (tt.currentTooltip === index) {
              fadeInSpeed = 1;
          }
  
          tt.currentTooltip = index;
          d3.select("#" + pie.cssPrefix + "tooltip" + index)
              .transition()
              .duration(fadeInSpeed)
              .style("opacity", function() { return 1; });
  
          tt.moveTooltip(pie);
      },
  
      moveTooltip: function(pie) {
          d3.selectAll("#" + pie.cssPrefix + "tooltip" + tt.currentTooltip)
              .attr("transform", function(d) {
                  var mouseCoords = d3.mouse(this.parentNode);
                  var x = mouseCoords[0] + pie.options.tooltips.styles.padding + 2;
                  var y = mouseCoords[1] - (2 * pie.options.tooltips.styles.padding) - 2;
                      return "translate(" + x + "," + y + ")";
                  });
      },
  
      hideTooltip: function(pie, index) {
          d3.select("#" + pie.cssPrefix + "tooltip" + index)
              .style("opacity", function() { return 0; });
  
          // move the tooltip offscreen. This ensures that when the user next mouseovers the segment the hidden
          // element won't interfere
          d3.select("#" + pie.cssPrefix + "tooltip" + tt.currentTooltip)
              .attr("transform", function(d, i) {
                  // klutzy, but it accounts for tooltip padding which could push it onscreen
                  var x = pie.options.size.canvasWidth + 1000;
                  var y = pie.options.size.canvasHeight + 1000;
                  return "translate(" + x + "," + y + ")";
              });
      },
  
      replacePlaceholders: function(pie, str, index, replacements) {
  
          // if the user has defined a placeholderParser function, call it before doing the replacements
          if (helpers.isFunction(pie.options.tooltips.placeholderParser)) {
              pie.options.tooltips.placeholderParser(index, replacements);
          }
  
          var replacer = function()  {
              return function(match) {
                  var placeholder = arguments[1];
                  if (replacements.hasOwnProperty(placeholder)) {
                      return replacements[arguments[1]];
                  } else {
                      return arguments[0];
                  }
              };
          };
          return str.replace(/\{(\w+)\}/g, replacer(replacements));
      }
  };
  
  
  	// --------------------------------------------------------------------------------------------
  
  	// our constructor
  	var d3pie = function(element, options) {
  
  		// element can be an ID or DOM element
  		this.element = element;
  		if (typeof element === "string") {
  			var el = element.replace(/^#/, ""); // replace any jQuery-like ID hash char
  			this.element = document.getElementById(el);
  		}
  
  		var opts = {};
  		extend(true, opts, defaultSettings, options);
  		this.options = opts;
  
  		// if the user specified a custom CSS element prefix (ID, class), use it
  		if (this.options.misc.cssPrefix !== null) {
  			this.cssPrefix = this.options.misc.cssPrefix;
  		} else {
  			this.cssPrefix = "p" + _uniqueIDCounter + "_";
  			_uniqueIDCounter++;
  		}
  
  
  		// now run some validation on the user-defined info
  		if (!validate.initialCheck(this)) {
  			return;
  		}
  
  		// add a data-role to the DOM node to let anyone know that it contains a d3pie instance, and the d3pie version
  		d3.select(this.element).attr(_scriptName, _version);
  
  		// things that are done once
  		_setupData.call(this);
  		_init.call(this);
  	};
  
  	d3pie.prototype.recreate = function() {
  		// now run some validation on the user-defined info
  		if (!validate.initialCheck(this)) {
              return;
          }
  
  		_setupData.call(this);
  		_init.call(this);
  	};
  
  	d3pie.prototype.redraw = function() {
  		this.element.innerHTML = "";
  		_init.call(this);
  	};
  
  	d3pie.prototype.destroy = function() {
  		this.element.innerHTML = ""; // clear out the SVG
  		d3.select(this.element).attr(_scriptName, null); // remove the data attr
  	};
  
  	/**
  	 * Returns all pertinent info about the current open info. Returns null if nothing's open, or if one is, an object of
  	 * the following form:
  	 * 	{
  	 * 	  element: DOM NODE,
  	 * 	  index: N,
  	 * 	  data: {}
  	 * 	}
  	 */
  	d3pie.prototype.getOpenSegment = function() {
  		var segment = this.currentlyOpenSegment;
  		if (segment !== null && typeof segment !== "undefined") {
  			var index = parseInt(d3.select(segment).attr("data-index"), 10);
  			return {
  				element: segment,
  				index: index,
  				data: this.options.data.content[index]
  			};
  		} else {
  			return null;
  		}
  	};
  
  	d3pie.prototype.openSegment = function(index) {
  		index = parseInt(index, 10);
  		if (index < 0 || index > this.options.data.content.length-1) {
  			return;
  		}
  		segments.openSegment(this, d3.select("#" + this.cssPrefix + "segment" + index).node());
  	};
  
  	d3pie.prototype.closeSegment = function() {
          segments.maybeCloseOpenSegment();
  	};
  
  	// this let's the user dynamically update aspects of the pie chart without causing a complete redraw. It
  	// intelligently re-renders only the part of the pie that the user specifies. Some things cause a repaint, others
  	// just redraw the single element
  	d3pie.prototype.updateProp = function(propKey, value) {
  		switch (propKey) {
  			case "header.title.text":
  				var oldVal = helpers.processObj(this.options, propKey);
  				helpers.processObj(this.options, propKey, value);
  				d3.select("#" + this.cssPrefix + "title").html(value);
  				if ((oldVal === "" && value !== "") || (oldVal !== "" && value === "")) {
  					this.redraw();
  				}
  				break;
  
  			case "header.subtitle.text":
  				var oldValue = helpers.processObj(this.options, propKey);
  				helpers.processObj(this.options, propKey, value);
  				d3.select("#" + this.cssPrefix + "subtitle").html(value);
  				if ((oldValue === "" && value !== "") || (oldValue !== "" && value === "")) {
  					this.redraw();
  				}
  				break;
  
  			case "callbacks.onload":
  			case "callbacks.onMouseoverSegment":
  			case "callbacks.onMouseoutSegment":
  			case "callbacks.onClickSegment":
  			case "effects.pullOutSegmentOnClick.effect":
  			case "effects.pullOutSegmentOnClick.speed":
  			case "effects.pullOutSegmentOnClick.size":
  			case "effects.highlightSegmentOnMouseover":
  			case "effects.highlightLuminosity":
  				helpers.processObj(this.options, propKey, value);
  				break;
  
  			// everything else, attempt to update it & do a repaint
  			default:
  				helpers.processObj(this.options, propKey, value);
  
  				this.destroy();
  				this.recreate();
  				break;
  		}
  	};
  
  
  	// ------------------------------------------------------------------------------------------------
  
  	var _setupData = function () {
          this.options.data.content = math.sortPieData(this);
          if (this.options.data.smallSegmentGrouping.enabled) {
              this.options.data.content = helpers.applySmallSegmentGrouping(this.options.data.content, this.options.data.smallSegmentGrouping);
          }
  
  
          this.options.colors = helpers.initSegmentColors(this);
          this.totalSize      = math.getTotalPieSize(this.options.data.content);
  
          var dp = this.options.labels.percentage.decimalPlaces;
  
          // add in percentage data to content
          for (var i=0; i<this.options.data.content.length; i++) {
              this.options.data.content[i].percentage = _getPercentage(this.options.data.content[i].value, this.totalSize, dp);
          }
  
          // adjust the final item to ensure the percentage always adds up to precisely 100%. This is necessary
  		var totalPercentage = 0;
          for (var j=0; j<this.options.data.content.length; j++) {
          	if (j === this.options.data.content.length - 1) {
                  this.options.data.content[j].percentage = (100 - totalPercentage).toFixed(dp);
  			}
  			totalPercentage += parseFloat(this.options.data.content[j].percentage);
          }
  	};
  
  	var _init = function() {
  
  		// prep-work
  		this.svg = helpers.addSVGSpace(this);
  
  		// store info about the main text components as part of the d3pie object instance
  		this.textComponents = {
  			headerHeight: 0,
  			title: {
  				exists: this.options.header.title.text !== "",
  				h: 0,
  				w: 0
  			},
  			subtitle: {
  				exists: this.options.header.subtitle.text !== "",
  				h: 0,
  				w: 0
  			},
  			footer: {
  				exists: this.options.footer.text !== "",
  				h: 0,
  				w: 0
  			}
  		};
  
  		this.outerLabelGroupData = [];
  
  		// add the key text components offscreen (title, subtitle, footer). We need to know their widths/heights for later computation
  		if (this.textComponents.title.exists) {
  			text.addTitle(this);
  		}
  		if (this.textComponents.subtitle.exists) {
  			text.addSubtitle(this);
  		}
  		text.addFooter(this);
  
  		// the footer never moves. Put it in place now
  		var self = this;
  		helpers.whenIdExists(this.cssPrefix + "footer", function() {
  			text.positionFooter(self);
  			var d3 = helpers.getDimensions(self.cssPrefix + "footer");
  			self.textComponents.footer.h = d3.h;
  			self.textComponents.footer.w = d3.w;
  		});
  
  		// now create the pie chart and position everything accordingly
  		var reqEls = [];
  		if (this.textComponents.title.exists)    { reqEls.push(this.cssPrefix + "title"); }
  		if (this.textComponents.subtitle.exists) { reqEls.push(this.cssPrefix + "subtitle"); }
  		if (this.textComponents.footer.exists)   { reqEls.push(this.cssPrefix + "footer"); }
  
  		helpers.whenElementsExist(reqEls, function() {
  			if (self.textComponents.title.exists) {
  				var d1 = helpers.getDimensions(self.cssPrefix + "title");
  				self.textComponents.title.h = d1.h;
  				self.textComponents.title.w = d1.w;
  			}
  			if (self.textComponents.subtitle.exists) {
  				var d2 = helpers.getDimensions(self.cssPrefix + "subtitle");
  				self.textComponents.subtitle.h = d2.h;
  				self.textComponents.subtitle.w = d2.w;
  			}
  			// now compute the full header height
  			if (self.textComponents.title.exists || self.textComponents.subtitle.exists) {
  				var headerHeight = 0;
  				if (self.textComponents.title.exists) {
  					headerHeight += self.textComponents.title.h;
  					if (self.textComponents.subtitle.exists) {
  						headerHeight += self.options.header.titleSubtitlePadding;
  					}
  				}
  				if (self.textComponents.subtitle.exists) {
  					headerHeight += self.textComponents.subtitle.h;
  				}
  				self.textComponents.headerHeight = headerHeight;
  			}
  
  			// at this point, all main text component dimensions have been calculated
  			math.computePieRadius(self);
  
  			// this value is used all over the place for placing things and calculating locations. We figure it out ONCE
  			// and store it as part of the object
  			math.calculatePieCenter(self);
  
  			// position the title and subtitle
  			text.positionTitle(self);
  			text.positionSubtitle(self);
  
  			// now create the pie chart segments, and gradients if the user desired
  			if (self.options.misc.gradient.enabled) {
  				segments.addGradients(self);
  			}
  			segments.create(self); // also creates this.arc
  			labels.add(self, "inner", self.options.labels.inner.format);
  			labels.add(self, "outer", self.options.labels.outer.format);
  
  			// position the label elements relatively within their individual group (label, percentage, value)
  			labels.positionLabelElements(self, "inner", self.options.labels.inner.format);
  			labels.positionLabelElements(self, "outer", self.options.labels.outer.format);
  			labels.computeOuterLabelCoords(self);
  
  			// this is (and should be) dumb. It just places the outer groups at their calculated, collision-free positions
  			labels.positionLabelGroups(self, "outer");
  
  			// we use the label line positions for many other calculations, so ALWAYS compute them
  			labels.computeLabelLinePositions(self);
  
  			// only add them if they're actually enabled
  			if (self.options.labels.lines.enabled && self.options.labels.outer.format !== "none") {
  				labels.addLabelLines(self);
  			}
  
  			labels.positionLabelGroups(self, "inner");
  			labels.fadeInLabelsAndLines(self);
  
  			// add and position the tooltips
  			if (self.options.tooltips.enabled) {
  				tt.addTooltips(self);
  			}
  
  			segments.addSegmentEventHandlers(self);
  		});
  	};
  
  	var _getPercentage = function(value, total, decimalPlaces) {
  		var relativeAmount = value / total;
  		if (decimalPlaces <= 0) {
  			return Math.round(relativeAmount * 100);
  		} else {
  			return (relativeAmount * 100).toFixed(decimalPlaces);
  		}
  	};
  
      return d3pie;
  }));
  