| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416 | 
							- 'use strict';
 
- module.exports = function(Chart) {
 
- 	var helpers = Chart.helpers;
 
- 	var globalDefaults = Chart.defaults.global;
 
- 	var defaultConfig = {
 
- 		display: true,
 
- 		// Boolean - Whether to animate scaling the chart from the centre
 
- 		animate: true,
 
- 		lineArc: false,
 
- 		position: 'chartArea',
 
- 		angleLines: {
 
- 			display: true,
 
- 			color: 'rgba(0, 0, 0, 0.1)',
 
- 			lineWidth: 1
 
- 		},
 
- 		// label settings
 
- 		ticks: {
 
- 			// Boolean - Show a backdrop to the scale label
 
- 			showLabelBackdrop: true,
 
- 			// String - The colour of the label backdrop
 
- 			backdropColor: 'rgba(255,255,255,0.75)',
 
- 			// Number - The backdrop padding above & below the label in pixels
 
- 			backdropPaddingY: 2,
 
- 			// Number - The backdrop padding to the side of the label in pixels
 
- 			backdropPaddingX: 2,
 
- 			callback: Chart.Ticks.formatters.linear
 
- 		},
 
- 		pointLabels: {
 
- 			// Number - Point label font size in pixels
 
- 			fontSize: 10,
 
- 			// Function - Used to convert point labels
 
- 			callback: function(label) {
 
- 				return label;
 
- 			}
 
- 		}
 
- 	};
 
- 	var LinearRadialScale = Chart.LinearScaleBase.extend({
 
- 		getValueCount: function() {
 
- 			return this.chart.data.labels.length;
 
- 		},
 
- 		setDimensions: function() {
 
- 			var me = this;
 
- 			var opts = me.options;
 
- 			var tickOpts = opts.ticks;
 
- 			// Set the unconstrained dimension before label rotation
 
- 			me.width = me.maxWidth;
 
- 			me.height = me.maxHeight;
 
- 			me.xCenter = Math.round(me.width / 2);
 
- 			me.yCenter = Math.round(me.height / 2);
 
- 			var minSize = helpers.min([me.height, me.width]);
 
- 			var tickFontSize = helpers.getValueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize);
 
- 			me.drawingArea = opts.display ? (minSize / 2) - (tickFontSize / 2 + tickOpts.backdropPaddingY) : (minSize / 2);
 
- 		},
 
- 		determineDataLimits: function() {
 
- 			var me = this;
 
- 			var chart = me.chart;
 
- 			me.min = null;
 
- 			me.max = null;
 
- 			helpers.each(chart.data.datasets, function(dataset, datasetIndex) {
 
- 				if (chart.isDatasetVisible(datasetIndex)) {
 
- 					var meta = chart.getDatasetMeta(datasetIndex);
 
- 					helpers.each(dataset.data, function(rawValue, index) {
 
- 						var value = +me.getRightValue(rawValue);
 
- 						if (isNaN(value) || meta.data[index].hidden) {
 
- 							return;
 
- 						}
 
- 						if (me.min === null) {
 
- 							me.min = value;
 
- 						} else if (value < me.min) {
 
- 							me.min = value;
 
- 						}
 
- 						if (me.max === null) {
 
- 							me.max = value;
 
- 						} else if (value > me.max) {
 
- 							me.max = value;
 
- 						}
 
- 					});
 
- 				}
 
- 			});
 
- 			// Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero
 
- 			me.handleTickRangeOptions();
 
- 		},
 
- 		getTickLimit: function() {
 
- 			var tickOpts = this.options.ticks;
 
- 			var tickFontSize = helpers.getValueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize);
 
- 			return Math.min(tickOpts.maxTicksLimit ? tickOpts.maxTicksLimit : 11, Math.ceil(this.drawingArea / (1.5 * tickFontSize)));
 
- 		},
 
- 		convertTicksToLabels: function() {
 
- 			var me = this;
 
- 			Chart.LinearScaleBase.prototype.convertTicksToLabels.call(me);
 
- 			// Point labels
 
- 			me.pointLabels = me.chart.data.labels.map(me.options.pointLabels.callback, me);
 
- 		},
 
- 		getLabelForIndex: function(index, datasetIndex) {
 
- 			return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]);
 
- 		},
 
- 		fit: function() {
 
- 			/*
 
- 			 * Right, this is really confusing and there is a lot of maths going on here
 
- 			 * The gist of the problem is here: https://gist.github.com/nnnick/696cc9c55f4b0beb8fe9
 
- 			 *
 
- 			 * Reaction: https://dl.dropboxusercontent.com/u/34601363/toomuchscience.gif
 
- 			 *
 
- 			 * Solution:
 
- 			 *
 
- 			 * We assume the radius of the polygon is half the size of the canvas at first
 
- 			 * at each index we check if the text overlaps.
 
- 			 *
 
- 			 * Where it does, we store that angle and that index.
 
- 			 *
 
- 			 * After finding the largest index and angle we calculate how much we need to remove
 
- 			 * from the shape radius to move the point inwards by that x.
 
- 			 *
 
- 			 * We average the left and right distances to get the maximum shape radius that can fit in the box
 
- 			 * along with labels.
 
- 			 *
 
- 			 * Once we have that, we can find the centre point for the chart, by taking the x text protrusion
 
- 			 * on each side, removing that from the size, halving it and adding the left x protrusion width.
 
- 			 *
 
- 			 * This will mean we have a shape fitted to the canvas, as large as it can be with the labels
 
- 			 * and position it in the most space efficient manner
 
- 			 *
 
- 			 * https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif
 
- 			 */
 
- 			var pointLabels = this.options.pointLabels;
 
- 			var pointLabelFontSize = helpers.getValueOrDefault(pointLabels.fontSize, globalDefaults.defaultFontSize);
 
- 			var pointLabeFontStyle = helpers.getValueOrDefault(pointLabels.fontStyle, globalDefaults.defaultFontStyle);
 
- 			var pointLabeFontFamily = helpers.getValueOrDefault(pointLabels.fontFamily, globalDefaults.defaultFontFamily);
 
- 			var pointLabeFont = helpers.fontString(pointLabelFontSize, pointLabeFontStyle, pointLabeFontFamily);
 
- 			// Get maximum radius of the polygon. Either half the height (minus the text width) or half the width.
 
- 			// Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points
 
- 			var largestPossibleRadius = helpers.min([(this.height / 2 - pointLabelFontSize - 5), this.width / 2]),
 
- 				pointPosition,
 
- 				i,
 
- 				textWidth,
 
- 				halfTextWidth,
 
- 				furthestRight = this.width,
 
- 				furthestRightIndex,
 
- 				furthestRightAngle,
 
- 				furthestLeft = 0,
 
- 				furthestLeftIndex,
 
- 				furthestLeftAngle,
 
- 				xProtrusionLeft,
 
- 				xProtrusionRight,
 
- 				radiusReductionRight,
 
- 				radiusReductionLeft;
 
- 			this.ctx.font = pointLabeFont;
 
- 			for (i = 0; i < this.getValueCount(); i++) {
 
- 				// 5px to space the text slightly out - similar to what we do in the draw function.
 
- 				pointPosition = this.getPointPosition(i, largestPossibleRadius);
 
- 				textWidth = this.ctx.measureText(this.pointLabels[i] ? this.pointLabels[i] : '').width + 5;
 
- 				// Add quarter circle to make degree 0 mean top of circle
 
- 				var angleRadians = this.getIndexAngle(i) + (Math.PI / 2);
 
- 				var angle = (angleRadians * 360 / (2 * Math.PI)) % 360;
 
- 				if (angle === 0 || angle === 180) {
 
- 					// At angle 0 and 180, we're at exactly the top/bottom
 
- 					// of the radar chart, so text will be aligned centrally, so we'll half it and compare
 
- 					// w/left and right text sizes
 
- 					halfTextWidth = textWidth / 2;
 
- 					if (pointPosition.x + halfTextWidth > furthestRight) {
 
- 						furthestRight = pointPosition.x + halfTextWidth;
 
- 						furthestRightIndex = i;
 
- 					}
 
- 					if (pointPosition.x - halfTextWidth < furthestLeft) {
 
- 						furthestLeft = pointPosition.x - halfTextWidth;
 
- 						furthestLeftIndex = i;
 
- 					}
 
- 				} else if (angle < 180) {
 
- 					// Less than half the values means we'll left align the text
 
- 					if (pointPosition.x + textWidth > furthestRight) {
 
- 						furthestRight = pointPosition.x + textWidth;
 
- 						furthestRightIndex = i;
 
- 					}
 
- 				// More than half the values means we'll right align the text
 
- 				} else if (pointPosition.x - textWidth < furthestLeft) {
 
- 					furthestLeft = pointPosition.x - textWidth;
 
- 					furthestLeftIndex = i;
 
- 				}
 
- 			}
 
- 			xProtrusionLeft = furthestLeft;
 
- 			xProtrusionRight = Math.ceil(furthestRight - this.width);
 
- 			furthestRightAngle = this.getIndexAngle(furthestRightIndex);
 
- 			furthestLeftAngle = this.getIndexAngle(furthestLeftIndex);
 
- 			radiusReductionRight = xProtrusionRight / Math.sin(furthestRightAngle + Math.PI / 2);
 
- 			radiusReductionLeft = xProtrusionLeft / Math.sin(furthestLeftAngle + Math.PI / 2);
 
- 			// Ensure we actually need to reduce the size of the chart
 
- 			radiusReductionRight = (helpers.isNumber(radiusReductionRight)) ? radiusReductionRight : 0;
 
- 			radiusReductionLeft = (helpers.isNumber(radiusReductionLeft)) ? radiusReductionLeft : 0;
 
- 			this.drawingArea = Math.round(largestPossibleRadius - (radiusReductionLeft + radiusReductionRight) / 2);
 
- 			this.setCenterPoint(radiusReductionLeft, radiusReductionRight);
 
- 		},
 
- 		setCenterPoint: function(leftMovement, rightMovement) {
 
- 			var me = this;
 
- 			var maxRight = me.width - rightMovement - me.drawingArea,
 
- 				maxLeft = leftMovement + me.drawingArea;
 
- 			me.xCenter = Math.round(((maxLeft + maxRight) / 2) + me.left);
 
- 			// Always vertically in the centre as the text height doesn't change
 
- 			me.yCenter = Math.round((me.height / 2) + me.top);
 
- 		},
 
- 		getIndexAngle: function(index) {
 
- 			var angleMultiplier = (Math.PI * 2) / this.getValueCount();
 
- 			var startAngle = this.chart.options && this.chart.options.startAngle ?
 
- 				this.chart.options.startAngle :
 
- 				0;
 
- 			var startAngleRadians = startAngle * Math.PI * 2 / 360;
 
- 			// Start from the top instead of right, so remove a quarter of the circle
 
- 			return index * angleMultiplier - (Math.PI / 2) + startAngleRadians;
 
- 		},
 
- 		getDistanceFromCenterForValue: function(value) {
 
- 			var me = this;
 
- 			if (value === null) {
 
- 				return 0; // null always in center
 
- 			}
 
- 			// Take into account half font size + the yPadding of the top value
 
- 			var scalingFactor = me.drawingArea / (me.max - me.min);
 
- 			if (me.options.reverse) {
 
- 				return (me.max - value) * scalingFactor;
 
- 			}
 
- 			return (value - me.min) * scalingFactor;
 
- 		},
 
- 		getPointPosition: function(index, distanceFromCenter) {
 
- 			var me = this;
 
- 			var thisAngle = me.getIndexAngle(index);
 
- 			return {
 
- 				x: Math.round(Math.cos(thisAngle) * distanceFromCenter) + me.xCenter,
 
- 				y: Math.round(Math.sin(thisAngle) * distanceFromCenter) + me.yCenter
 
- 			};
 
- 		},
 
- 		getPointPositionForValue: function(index, value) {
 
- 			return this.getPointPosition(index, this.getDistanceFromCenterForValue(value));
 
- 		},
 
- 		getBasePosition: function() {
 
- 			var me = this;
 
- 			var min = me.min;
 
- 			var max = me.max;
 
- 			return me.getPointPositionForValue(0,
 
- 				me.beginAtZero? 0:
 
- 				min < 0 && max < 0? max :
 
- 				min > 0 && max > 0? min :
 
- 				0);
 
- 		},
 
- 		draw: function() {
 
- 			var me = this;
 
- 			var opts = me.options;
 
- 			var gridLineOpts = opts.gridLines;
 
- 			var tickOpts = opts.ticks;
 
- 			var angleLineOpts = opts.angleLines;
 
- 			var pointLabelOpts = opts.pointLabels;
 
- 			var getValueOrDefault = helpers.getValueOrDefault;
 
- 			if (opts.display) {
 
- 				var ctx = me.ctx;
 
- 				// Tick Font
 
- 				var tickFontSize = getValueOrDefault(tickOpts.fontSize, globalDefaults.defaultFontSize);
 
- 				var tickFontStyle = getValueOrDefault(tickOpts.fontStyle, globalDefaults.defaultFontStyle);
 
- 				var tickFontFamily = getValueOrDefault(tickOpts.fontFamily, globalDefaults.defaultFontFamily);
 
- 				var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily);
 
- 				helpers.each(me.ticks, function(label, index) {
 
- 					// Don't draw a centre value (if it is minimum)
 
- 					if (index > 0 || opts.reverse) {
 
- 						var yCenterOffset = me.getDistanceFromCenterForValue(me.ticksAsNumbers[index]);
 
- 						var yHeight = me.yCenter - yCenterOffset;
 
- 						// Draw circular lines around the scale
 
- 						if (gridLineOpts.display && index !== 0) {
 
- 							ctx.strokeStyle = helpers.getValueAtIndexOrDefault(gridLineOpts.color, index - 1);
 
- 							ctx.lineWidth = helpers.getValueAtIndexOrDefault(gridLineOpts.lineWidth, index - 1);
 
- 							if (opts.lineArc) {
 
- 								// Draw circular arcs between the points
 
- 								ctx.beginPath();
 
- 								ctx.arc(me.xCenter, me.yCenter, yCenterOffset, 0, Math.PI * 2);
 
- 								ctx.closePath();
 
- 								ctx.stroke();
 
- 							} else {
 
- 								// Draw straight lines connecting each index
 
- 								ctx.beginPath();
 
- 								for (var i = 0; i < me.getValueCount(); i++) {
 
- 									var pointPosition = me.getPointPosition(i, yCenterOffset);
 
- 									if (i === 0) {
 
- 										ctx.moveTo(pointPosition.x, pointPosition.y);
 
- 									} else {
 
- 										ctx.lineTo(pointPosition.x, pointPosition.y);
 
- 									}
 
- 								}
 
- 								ctx.closePath();
 
- 								ctx.stroke();
 
- 							}
 
- 						}
 
- 						if (tickOpts.display) {
 
- 							var tickFontColor = getValueOrDefault(tickOpts.fontColor, globalDefaults.defaultFontColor);
 
- 							ctx.font = tickLabelFont;
 
- 							if (tickOpts.showLabelBackdrop) {
 
- 								var labelWidth = ctx.measureText(label).width;
 
- 								ctx.fillStyle = tickOpts.backdropColor;
 
- 								ctx.fillRect(
 
- 									me.xCenter - labelWidth / 2 - tickOpts.backdropPaddingX,
 
- 									yHeight - tickFontSize / 2 - tickOpts.backdropPaddingY,
 
- 									labelWidth + tickOpts.backdropPaddingX * 2,
 
- 									tickFontSize + tickOpts.backdropPaddingY * 2
 
- 								);
 
- 							}
 
- 							ctx.textAlign = 'center';
 
- 							ctx.textBaseline = 'middle';
 
- 							ctx.fillStyle = tickFontColor;
 
- 							ctx.fillText(label, me.xCenter, yHeight);
 
- 						}
 
- 					}
 
- 				});
 
- 				if (!opts.lineArc) {
 
- 					ctx.lineWidth = angleLineOpts.lineWidth;
 
- 					ctx.strokeStyle = angleLineOpts.color;
 
- 					var outerDistance = me.getDistanceFromCenterForValue(opts.reverse ? me.min : me.max);
 
- 					// Point Label Font
 
- 					var pointLabelFontSize = getValueOrDefault(pointLabelOpts.fontSize, globalDefaults.defaultFontSize);
 
- 					var pointLabeFontStyle = getValueOrDefault(pointLabelOpts.fontStyle, globalDefaults.defaultFontStyle);
 
- 					var pointLabeFontFamily = getValueOrDefault(pointLabelOpts.fontFamily, globalDefaults.defaultFontFamily);
 
- 					var pointLabeFont = helpers.fontString(pointLabelFontSize, pointLabeFontStyle, pointLabeFontFamily);
 
- 					for (var i = me.getValueCount() - 1; i >= 0; i--) {
 
- 						if (angleLineOpts.display) {
 
- 							var outerPosition = me.getPointPosition(i, outerDistance);
 
- 							ctx.beginPath();
 
- 							ctx.moveTo(me.xCenter, me.yCenter);
 
- 							ctx.lineTo(outerPosition.x, outerPosition.y);
 
- 							ctx.stroke();
 
- 							ctx.closePath();
 
- 						}
 
- 						// Extra 3px out for some label spacing
 
- 						var pointLabelPosition = me.getPointPosition(i, outerDistance + 5);
 
- 						// Keep this in loop since we may support array properties here
 
- 						var pointLabelFontColor = getValueOrDefault(pointLabelOpts.fontColor, globalDefaults.defaultFontColor);
 
- 						ctx.font = pointLabeFont;
 
- 						ctx.fillStyle = pointLabelFontColor;
 
- 						var pointLabels = me.pointLabels;
 
- 						// Add quarter circle to make degree 0 mean top of circle
 
- 						var angleRadians = this.getIndexAngle(i) + (Math.PI / 2);
 
- 						var angle = (angleRadians * 360 / (2 * Math.PI)) % 360;
 
- 						if (angle === 0 || angle === 180) {
 
- 							ctx.textAlign = 'center';
 
- 						} else if (angle < 180) {
 
- 							ctx.textAlign = 'left';
 
- 						} else {
 
- 							ctx.textAlign = 'right';
 
- 						}
 
- 						// Set the correct text baseline based on outer positioning
 
- 						if (angle === 90 || angle === 270) {
 
- 							ctx.textBaseline = 'middle';
 
- 						} else if (angle > 270 || angle < 90) {
 
- 							ctx.textBaseline = 'bottom';
 
- 						} else {
 
- 							ctx.textBaseline = 'top';
 
- 						}
 
- 						ctx.fillText(pointLabels[i] ? pointLabels[i] : '', pointLabelPosition.x, pointLabelPosition.y);
 
- 					}
 
- 				}
 
- 			}
 
- 		}
 
- 	});
 
- 	Chart.scaleService.registerScaleType('radialLinear', LinearRadialScale, defaultConfig);
 
- };
 
 
  |