793 行
		
	
	
	
		
			22 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			793 行
		
	
	
	
		
			22 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
|  | /*! | ||
|  |  * @energiency/chartjs-plugin-piechart-outlabels v1.3.1 | ||
|  |  * http://www.chartjs.org
 | ||
|  |  * (c) 2017-2022 @energiency/chartjs-plugin-piechart-outlabels contributors | ||
|  |  * Released under the MIT license | ||
|  |  */ | ||
|  | (function (global, factory) { | ||
|  | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('chart.js'), require('chart.js/helpers')) : | ||
|  | typeof define === 'function' && define.amd ? define(['chart.js', 'chart.js/helpers'], factory) : | ||
|  | (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.ChartPieChartOutlabels = factory(global.Chart, global.Chart.helpers)); | ||
|  | })(this, (function (chart_js, helpers) { 'use strict'; | ||
|  | 
 | ||
|  | /** | ||
|  |  * @module Options | ||
|  |  */ | ||
|  | 
 | ||
|  | var customDefaults = { | ||
|  | 
 | ||
|  |   PLUGIN_KEY: '$outlabels', | ||
|  | 
 | ||
|  |   /** | ||
|  | 	 * The color used to draw the background of the label rect. | ||
|  | 	 * @member {String|Array|Function|null} | ||
|  | 	 * @default null (adaptive background) | ||
|  | 	 */ | ||
|  |   backgroundColor: function(context) { | ||
|  |     return context.dataset.backgroundColor; | ||
|  |   }, | ||
|  | 
 | ||
|  |   /** | ||
|  | 	 * The color used to draw the border of the label rect. | ||
|  | 	 * @member {String|Array|Function|null} | ||
|  | 	 * @default null (adaptive border color) | ||
|  | 	 */ | ||
|  |   borderColor: function(context) { | ||
|  |     return context.dataset.backgroundColor; | ||
|  |   }, | ||
|  | 
 | ||
|  |   /** | ||
|  | 	 * The color used to draw the line between label and arc of the chart. | ||
|  | 	 * @member {String|Array|Function|null} | ||
|  | 	 * @default null (adaptive line color) | ||
|  | 	 */ | ||
|  |   lineColor: function(context) { | ||
|  |     return context.dataset.backgroundColor; | ||
|  |   }, | ||
|  | 
 | ||
|  |   /** | ||
|  | 	 * The border radius used to add rounded corners to the label rect. | ||
|  | 	 * @member {Number|Array|Function} | ||
|  | 	 * @default 0 (not rounded) | ||
|  | 	 */ | ||
|  |   borderRadius: 0, | ||
|  | 
 | ||
|  |   /** | ||
|  | 	 * The border width of the surrounding frame. | ||
|  | 	 * @member {Number|Array|Function} | ||
|  | 	 * @default 0 (no border) | ||
|  | 	 */ | ||
|  |   borderWidth: 0, | ||
|  | 
 | ||
|  |   /** | ||
|  | 	 * The width (thickness) of the line between label and chart arc. | ||
|  | 	 * @member {Number|Array|Function} | ||
|  | 	 * @default 2 | ||
|  | 	 */ | ||
|  |   lineWidth: 2, | ||
|  | 
 | ||
|  |   /** | ||
|  | 	 * The color used to draw the label text. | ||
|  | 	 * @member {String|Array|Function} | ||
|  | 	 * @default white | ||
|  | 	 */ | ||
|  |   color: 'white', | ||
|  | 
 | ||
|  |   /** | ||
|  | 	 * Whether to display labels global (boolean) or per data (function) | ||
|  | 	 * @member {Boolean|Array|Function} | ||
|  | 	 * @default true | ||
|  | 	 */ | ||
|  |   display: true, | ||
|  | 
 | ||
|  |   /** | ||
|  | 	 * The font options used to draw the label text. | ||
|  | 	 * @member {Object|Array|Function} | ||
|  | 	 * @prop {Boolean} font.family - defaults to Chart.defaults.global.defaultFontFamily | ||
|  | 	 * @prop {Boolean} font.size - defaults to Chart.defaults.global.defaultFontSize | ||
|  | 	 * @prop {Boolean} font.style - defaults to Chart.defaults.global.defaultFontStyle | ||
|  | 	 * @prop {Boolean} font.weight - defaults to 'normal' | ||
|  | 	 * @prop {Boolean} font.maxSize - defaults to undefined (unlimited) | ||
|  | 	 * @prop {Boolean} font.minSize - defaults to undefined (unlimited) | ||
|  | 	 * @prop {Boolean} font.resizable - defaults to true | ||
|  | 	 * @default Chart.defaults.global.defaultFont.* | ||
|  | 	 */ | ||
|  |   font: { | ||
|  |     family: undefined, | ||
|  |     size: undefined, | ||
|  |     style: undefined, | ||
|  |     weight: null, | ||
|  |     maxSize: null, | ||
|  |     minSize: null, | ||
|  |     resizable: true, | ||
|  |   }, | ||
|  | 
 | ||
|  |   /** | ||
|  | 	 * The line height (in pixel) to use for multi-lines labels. | ||
|  | 	 * @member {Number|Array|Function|undefined} | ||
|  | 	 * @default 1.2 | ||
|  | 	 */ | ||
|  |   lineHeight: 1.2, | ||
|  | 
 | ||
|  | 
 | ||
|  |   /** | ||
|  | 	 * The padding (in pixels) to apply between the text and the surrounding frame. | ||
|  | 	 * @member {Number|Object|Array|Function} | ||
|  | 	 * @prop {Number} padding.top - Space above the text. | ||
|  | 	 * @prop {Number} padding.right - Space on the right of the text. | ||
|  | 	 * @prop {Number} padding.bottom - Space below the text. | ||
|  | 	 * @prop {Number} padding.left - Space on the left of the text. | ||
|  | 	 * @default 4 (all values) | ||
|  | 	 */ | ||
|  |   padding: { | ||
|  |     top: 2, | ||
|  |     right: 2, | ||
|  |     bottom: 2, | ||
|  |     left: 2 | ||
|  |   }, | ||
|  | 
 | ||
|  |   /** | ||
|  | 	 * Text alignment for multi-lines labels ('left'|'right'|'start'|'center'|'end'). | ||
|  | 	 * @member {String|Array|Function} | ||
|  | 	 * @default 'center' | ||
|  | 	 */ | ||
|  |   textAlign: 'center', | ||
|  | 
 | ||
|  |   /** | ||
|  | 	 * The radius of distance where the label will be drawn | ||
|  | 	 * @member {Number|Array|Function|undefined} | ||
|  | 	 * @default 30 | ||
|  | 	 */ | ||
|  |   stretch: 12, | ||
|  | 
 | ||
|  |   /** | ||
|  | 	 * The length of the horizontal part of line between label and chart arc. | ||
|  | 	 * @member {Number} | ||
|  | 	 * @default 30 | ||
|  | 	 */ | ||
|  |   horizontalStrechPad: 12, | ||
|  | 
 | ||
|  |   /** | ||
|  | 	 * The text of the label. | ||
|  | 	 * @member {String} | ||
|  | 	 * @default '%l %p' (label name and value percentage) | ||
|  | 	 */ | ||
|  |   text: '%l %p', | ||
|  | 
 | ||
|  |   /** | ||
|  | 	 * The max level of zoom (out) for pie/doughnut chart in percent. | ||
|  | 	 * @member {Number} | ||
|  | 	 * @default 50 (%) | ||
|  | 	 */ | ||
|  |   maxZoomOutPercentage: 50, | ||
|  | 
 | ||
|  |   /** | ||
|  | 	 * The count of numbers after the point separator for float values of percent property | ||
|  | 	 * @member {Number} | ||
|  | 	 * @default 1 | ||
|  | 	 */ | ||
|  |   percentPrecision: 1, | ||
|  | 
 | ||
|  |   /** | ||
|  | 	 * The count of numbers after the point separator for float values of value property | ||
|  | 	 * @member {Number} | ||
|  | 	 * @default 3 | ||
|  | 	 */ | ||
|  |   valuePrecision: 3 | ||
|  | }; | ||
|  | 
 | ||
|  | var positioners = { | ||
|  |   center: function(arc, stretch) { | ||
|  |     var angle = (arc.startAngle + arc.endAngle) / 2; | ||
|  |     var cosA = Math.cos(angle); | ||
|  |     var sinA = Math.sin(angle); | ||
|  |     var d = arc.outerRadius; | ||
|  | 
 | ||
|  |     var stretchedD = d + stretch; | ||
|  |     return { | ||
|  |       x: arc.x + cosA * stretchedD, | ||
|  |       y: arc.y + sinA * stretchedD, | ||
|  |       d: stretchedD, | ||
|  |       arc: arc, | ||
|  |       anchor: { | ||
|  |         x: arc.x + cosA * d, | ||
|  |         y: arc.y + sinA * d, | ||
|  |       }, | ||
|  |       copy: { | ||
|  |         x: arc.x + cosA * stretchedD, | ||
|  |         y: arc.y + sinA * stretchedD | ||
|  |       } | ||
|  |     }; | ||
|  |   }, | ||
|  | 
 | ||
|  |   moveFromAnchor: function(center, dist) { | ||
|  |     var arc = center.arc; | ||
|  |     var d = center.d; | ||
|  |     var angle = (arc.startAngle + arc.endAngle) / 2; | ||
|  |     var cosA = Math.cos(angle); | ||
|  |     var sinA = Math.sin(angle); | ||
|  | 
 | ||
|  |     d += dist; | ||
|  | 
 | ||
|  |     return { | ||
|  |       x: arc.x + cosA * d, | ||
|  |       y: arc.y + sinA * d, | ||
|  |       d: d, | ||
|  |       arc: arc, | ||
|  |       anchor: center.anchor, | ||
|  |       copy: { | ||
|  |         x: arc.x + cosA * d, | ||
|  |         y: arc.y + sinA * d | ||
|  |       } | ||
|  |     }; | ||
|  |   } | ||
|  | }; | ||
|  | 
 | ||
|  | function toFontString(font) { | ||
|  |   if (!font || helpers.isNullOrUndef(font.size) || helpers.isNullOrUndef(font.family)) { | ||
|  |     return null; | ||
|  |   } | ||
|  | 
 | ||
|  |   return (font.style ? font.style + ' ' : '') | ||
|  |     + (font.weight ? font.weight + ' ' : '') | ||
|  |     + font.size + 'px ' | ||
|  |     + font.family; | ||
|  | } | ||
|  | 
 | ||
|  | function textSize(ctx, lines, font) { | ||
|  |   var items = [].concat(lines); | ||
|  |   var ilen = items.length; | ||
|  |   var prev = ctx.font; | ||
|  |   var width = 0; | ||
|  |   var i; | ||
|  | 
 | ||
|  |   ctx.font = font.string; | ||
|  | 
 | ||
|  |   for (i = 0; i < ilen; ++i) { | ||
|  |     width = Math.max(ctx.measureText(items[i]).width, width); | ||
|  |   } | ||
|  | 
 | ||
|  |   ctx.font = prev; | ||
|  | 
 | ||
|  |   return { | ||
|  |     height: ilen * font.lineHeight, | ||
|  |     width: width | ||
|  |   }; | ||
|  | } | ||
|  | 
 | ||
|  | function adaptTextSizeToHeight(height, minimum, maximum) { | ||
|  |   var size = (height / 100) * 2.5; | ||
|  |   if (minimum && size < minimum) { | ||
|  |     return minimum; | ||
|  |   } | ||
|  |   if (maximum && size > maximum) { | ||
|  |     return maximum; | ||
|  |   } | ||
|  |   return size; | ||
|  | } | ||
|  | 
 | ||
|  | function parseFont(value, height) { | ||
|  |   var size = helpers.valueOrDefault(value.size, chart_js.Chart.defaults.defaultFontSize); | ||
|  | 
 | ||
|  |   if (value.resizable) { | ||
|  |     size = adaptTextSizeToHeight(height, value.minSize, value.maxSize); | ||
|  |   } | ||
|  | 
 | ||
|  |   var font = { | ||
|  |     family: helpers.valueOrDefault(value.family, chart_js.Chart.defaults.defaultFontFamily), | ||
|  |     lineHeight: helpers.toLineHeight(value.lineHeight, size), | ||
|  |     size: size, | ||
|  |     style: helpers.valueOrDefault(value.style, chart_js.Chart.defaults.defaultFontStyle), | ||
|  |     weight: helpers.valueOrDefault(value.weight, null), | ||
|  |     string: '' | ||
|  |   }; | ||
|  | 
 | ||
|  |   font.string = toFontString(font); | ||
|  |   return font; | ||
|  | } | ||
|  | 
 | ||
|  | var PLUGIN_KEY$1 = customDefaults.PLUGIN_KEY; | ||
|  | 
 | ||
|  | var classes = { | ||
|  |   OutLabel: function(chart, index, ctx, config, context) { | ||
|  |     // Check whether the label should be displayed
 | ||
|  |     if (!helpers.resolve([config.display, true], context, index)) { | ||
|  |       throw new Error('Label display property is set to false.'); | ||
|  |     } | ||
|  | 
 | ||
|  |     // Init text
 | ||
|  |     var value = context.dataset.data[index]; | ||
|  |     var label = context.labels[index]; | ||
|  |     var text = helpers.resolve([config.text, customDefaults.text], context, index); | ||
|  |     /* Replace label marker */ | ||
|  |     text = text.replace(/%l/gi, label); | ||
|  | 
 | ||
|  |     /* Replace value marker with possible precision value */ | ||
|  |     (text.match(/%v\.?(\d*)/gi) || []).map(function(val) { | ||
|  |       var prec = val.replace(/%v\./gi, ''); | ||
|  |       if (prec.length) { | ||
|  |         return +prec; | ||
|  |       } | ||
|  |       return config.valuePrecision || customDefaults.valuePrecision; | ||
|  |     }).forEach(function(val) { | ||
|  |       text = text.replace(/%v\.?(\d*)/i, value.toFixed(val)); | ||
|  |     }); | ||
|  | 
 | ||
|  |     /* Replace percent marker with possible precision value */ | ||
|  |     (text.match(/%p\.?(\d*)/gi) || []).map(function(val) { | ||
|  |       var prec = val.replace(/%p\./gi, ''); | ||
|  |       if (prec.length) { | ||
|  |         return +prec; | ||
|  |       } | ||
|  |       return config.percentPrecision || customDefaults.percentPrecision; | ||
|  |     }).forEach(function(val) { | ||
|  |       text = text.replace(/%p\.?(\d*)/i, (context.percent * 100).toFixed(val) + '%'); | ||
|  |     }); | ||
|  | 
 | ||
|  |     // Count lines
 | ||
|  |     var lines = text.match(/[^\r\n]+/g) || []; | ||
|  | 
 | ||
|  |     // Remove unnecessary spaces
 | ||
|  |     for (var i = 0; i < lines.length; ++i) { | ||
|  |       lines[i] = lines[i].trim(); | ||
|  |     } | ||
|  | 
 | ||
|  |     /* ===================== CONSTRUCTOR ==================== */ | ||
|  |     this.init = function(text, lines) { | ||
|  |       // If everything ok -> begin initializing
 | ||
|  |       this.encodedText = config.text; | ||
|  |       this.text = text; | ||
|  |       this.lines = lines; | ||
|  |       this.label = label; | ||
|  |       this.value = value; | ||
|  |       this.ctx = ctx; | ||
|  | 
 | ||
|  |       // Init style
 | ||
|  |       this.style = { | ||
|  |         backgroundColor: helpers.resolve([config.backgroundColor, customDefaults.backgroundColor, 'black'], context, index), | ||
|  |         borderColor: helpers.resolve([config.borderColor, customDefaults.borderColor, 'black'], context, index), | ||
|  |         borderRadius: helpers.resolve([config.borderRadius, 0], context, index), | ||
|  |         borderWidth: helpers.resolve([config.borderWidth, 0], context, index), | ||
|  |         lineWidth: helpers.resolve([config.lineWidth, 2], context, index), | ||
|  |         lineColor: helpers.resolve([config.lineColor, customDefaults.lineColor, 'black'], context, index), | ||
|  |         color: helpers.resolve([config.color, 'white'], context, index), | ||
|  |         font: parseFont(helpers.resolve([config.font, {resizable: true}]), ctx.canvas.style.height.slice(0, -2)), | ||
|  |         padding: helpers.toPadding(helpers.resolve([config.padding, 0], context, index)), | ||
|  |         textAlign: helpers.resolve([config.textAlign, 'left'], context, index), | ||
|  |       }; | ||
|  | 
 | ||
|  |       this.stretch = helpers.resolve([config.stretch, customDefaults.stretch], context, index); | ||
|  |       this.horizontalStrechPad = helpers.resolve([config.horizontalStrechPad, customDefaults.horizontalStrechPad], context, index); | ||
|  |       this.size = textSize(ctx, this.lines, this.style.font); | ||
|  | 
 | ||
|  |       this.offsetStep = this.size.width / 20; | ||
|  |       this.offset = { | ||
|  |         x: 0, | ||
|  |         y: 0 | ||
|  |       }; | ||
|  |       this.predictedOffset = this.offset; | ||
|  | 
 | ||
|  |       /*var angle = -((el._model.startAngle + el._model.endAngle) / 2) / (Math.PI); | ||
|  | 			var val = Math.abs(angle - Math.trunc(angle)); | ||
|  | 
 | ||
|  | 			if (val > 0.45 && val < 0.55) { | ||
|  | 				this.predictedOffset.x = 0; | ||
|  | 			} else if (angle <= 0.45 && angle >= -0.45) { | ||
|  | 				this.predictedOffset.x = this.size.width / 2; | ||
|  | 			} else if (angle >= -1.45 && angle <= -0.55) { | ||
|  | 				this.predictedOffset.x = -this.size.width / 2; | ||
|  | 			}*/ | ||
|  |     }; | ||
|  | 
 | ||
|  |     this.init(text, lines); | ||
|  | 
 | ||
|  |     /* COMPUTING RECTS PART */ | ||
|  |     this.computeLabelRect = function() { | ||
|  |       var width = this.textRect.width + 2 * this.style.borderWidth + this.style.padding.left + this.style.padding.right; | ||
|  |       var height = this.textRect.height + 2 * this.style.borderWidth + this.style.padding.top + this.style.padding.bottom; | ||
|  | 
 | ||
|  |       var x = this.textRect.x - this.style.borderWidth; | ||
|  |       var y = this.textRect.y - this.style.borderWidth; | ||
|  | 
 | ||
|  |       return { | ||
|  |         x: x, | ||
|  |         y: y, | ||
|  |         width: width, | ||
|  |         height: height, | ||
|  |         isLeft: this.textRect.isLeft, | ||
|  |         isTop: this.textRect.isTop | ||
|  |       }; | ||
|  |     }; | ||
|  | 
 | ||
|  |     this.computeTextRect = function() { | ||
|  |       const isLeft = this.center.x - this.center.anchor.x < 0; | ||
|  |       const isTop = this.center.y - this.center.anchor.y < 0; | ||
|  |       const shift = isLeft ? -(this.horizontalStrechPad + this.size.width) : this.horizontalStrechPad; | ||
|  |       return { | ||
|  |         x: this.center.x - this.style.padding.left + shift, | ||
|  |         y: this.center.y - (this.size.height / 2), | ||
|  |         width: this.size.width, | ||
|  |         height: this.size.height, | ||
|  |         isLeft, | ||
|  |         isTop | ||
|  |       }; | ||
|  |     }; | ||
|  | 
 | ||
|  |     this.roundedRect = function (ctx, x, y, width, height, radius) { | ||
|  |       var TL, TR, BR, BL; | ||
|  |      | ||
|  |       // Minimum horizontal value: half of the width
 | ||
|  |       var mH = r => Math.min(r, width / 2); | ||
|  |       // Minimum vertical value: half of the width
 | ||
|  |       var mV = r => Math.min(r, height / 2); | ||
|  |      | ||
|  |       if (radius instanceof Array) { | ||
|  |         // [TL, TR, BR, BL]
 | ||
|  |         TL = radius[0]; | ||
|  |         TR = radius[1]; | ||
|  |         BR = radius[2]; | ||
|  |         BL = radius[3]; | ||
|  |       } else { | ||
|  |         TL = TR = BR = BL = radius; | ||
|  |       } | ||
|  |      | ||
|  |       ctx.beginPath(); | ||
|  |      | ||
|  |       ctx.moveTo(x + mH(TL), y); | ||
|  |       ctx.lineTo(x + width - mH(TR), y); | ||
|  |       ctx.quadraticCurveTo(x + width, y, x + width, y + mV(TR)); | ||
|  |       ctx.lineTo(x + width, y + height - mV(BR)); | ||
|  |       ctx.quadraticCurveTo(x + width, y + height, x + width - mH(BR), y + height); | ||
|  |       ctx.lineTo(x + mH(BL), y + height); | ||
|  |       ctx.quadraticCurveTo(x, y + height, x, y + height - mV(BL)); | ||
|  |       ctx.lineTo(x, y + mV(TL)); | ||
|  |       ctx.quadraticCurveTo(x, y, x + mH(TL), y); | ||
|  |      | ||
|  |       ctx.closePath(); | ||
|  |     } | ||
|  | 
 | ||
|  |     this.getPoints = function() { | ||
|  |       return [ | ||
|  |         { | ||
|  |           x: this.labelRect.x, | ||
|  |           y: this.labelRect.y | ||
|  |         }, | ||
|  |         { | ||
|  |           x: this.labelRect.x + this.labelRect.width, | ||
|  |           y: this.labelRect.y | ||
|  |         }, | ||
|  |         { | ||
|  |           x: this.labelRect.x + this.labelRect.width, | ||
|  |           y: this.labelRect.y + this.labelRect.height | ||
|  |         }, | ||
|  |         { | ||
|  |           x: this.labelRect.x, | ||
|  |           y: this.labelRect.y + this.labelRect.height | ||
|  |         } | ||
|  |       ]; | ||
|  |     }; | ||
|  | 
 | ||
|  |     this.containsPoint = function(point) { | ||
|  |       let offset = 5; | ||
|  | 
 | ||
|  |       return	this.labelRect.x - offset <= point.x && point.x <= this.labelRect.x + this.labelRect.width + offset | ||
|  | 							&& | ||
|  | 						this.labelRect.y - offset <= point.y && point.y <= this.labelRect.y + this.labelRect.height + offset; | ||
|  |     }; | ||
|  | 
 | ||
|  | 
 | ||
|  |     /* ======================= DRAWING ======================= */ | ||
|  |     // Draw label text
 | ||
|  |     this.drawText = function() { | ||
|  |       var align = this.style.textAlign; | ||
|  |       var font = this.style.font; | ||
|  |       var lh = font.lineHeight; | ||
|  |       var color = this.style.color; | ||
|  |       var ilen = this.lines.length; | ||
|  |       var x, y, idx; | ||
|  | 
 | ||
|  |       if (!ilen || !color) { | ||
|  |         return; | ||
|  |       } | ||
|  | 
 | ||
|  |       x = this.textRect.x; | ||
|  |       y = this.textRect.y + lh / 2; | ||
|  | 
 | ||
|  |       if (align === 'center') { | ||
|  |         x += this.textRect.width / 2; | ||
|  |       } else if (align === 'end' || align === 'right') { | ||
|  |         x += this.textRect.width; | ||
|  |       } | ||
|  | 
 | ||
|  |       this.ctx.font = this.style.font.string; | ||
|  |       this.ctx.fillStyle = color; | ||
|  |       this.ctx.textAlign = align; | ||
|  |       this.ctx.textBaseline = 'middle'; | ||
|  | 
 | ||
|  |       for (idx = 0; idx < ilen; ++idx) { | ||
|  |         this.ctx.fillText( | ||
|  |           this.lines[idx], | ||
|  |           Math.round(x) + this.style.padding.left, | ||
|  |           Math.round(y), | ||
|  |           Math.round(this.textRect.width) | ||
|  |         ); | ||
|  |         y += lh; | ||
|  |       } | ||
|  |     }; | ||
|  | 
 | ||
|  |     this.ccw = function(A, B, C) { | ||
|  |       return (C.y - A.y) * (B.x - A.x) > (B.y - A.y) * (C.x - A.x); | ||
|  |     }; | ||
|  | 
 | ||
|  |     this.intersects = function(A, B, C, D) { | ||
|  |       return this.ccw(A, C, D) !== this.ccw(B, C, D) && this.ccw(A, B, C) !== this.ccw(A, B, D); | ||
|  |     }; | ||
|  | 
 | ||
|  |     // Draw label box
 | ||
|  | 		this.drawLabel = function() { | ||
|  | 			ctx.beginPath(); | ||
|  | 			this.roundedRect( | ||
|  | 				this.ctx, | ||
|  | 				Math.round(this.labelRect.x), | ||
|  | 				Math.round(this.labelRect.y), | ||
|  | 				Math.round(this.labelRect.width), | ||
|  | 				Math.round(this.labelRect.height), | ||
|  | 				this.style.borderRadius | ||
|  | 			); | ||
|  | 			this.ctx.closePath(); | ||
|  | 
 | ||
|  | 			if (this.style.backgroundColor) { | ||
|  | 				this.ctx.fillStyle = this.style.backgroundColor || 'black'; | ||
|  | 				this.ctx.fill(); | ||
|  | 			} | ||
|  | 
 | ||
|  | 			if (this.style.borderColor && this.style.borderWidth) { | ||
|  | 				this.ctx.strokeStyle = this.style.borderColor; | ||
|  | 				this.ctx.lineWidth = this.style.borderWidth; | ||
|  | 				this.ctx.lineJoin = 'miter'; | ||
|  | 				this.ctx.stroke(); | ||
|  | 			} | ||
|  | 		}; | ||
|  | 
 | ||
|  |     this.drawLine = function() { | ||
|  |       if (!this.lines.length) { | ||
|  |         return; | ||
|  |       } | ||
|  |       this.ctx.save(); | ||
|  | 
 | ||
|  |       this.ctx.strokeStyle = this.style.lineColor; | ||
|  |       this.ctx.lineWidth = this.style.lineWidth; | ||
|  |       this.ctx.lineJoin = 'miter'; | ||
|  |       this.ctx.beginPath(); | ||
|  |       this.ctx.moveTo(this.center.anchor.x, this.center.anchor.y); | ||
|  |       this.ctx.lineTo(this.center.copy.x, this.center.copy.y); | ||
|  |       this.ctx.stroke(); | ||
|  | 
 | ||
|  |       this.ctx.beginPath(); | ||
|  |       this.ctx.moveTo(this.center.copy.x, this.center.copy.y); | ||
|  |       const xOffset = this.textRect.width + this.style.padding.width; | ||
|  |       const intersect = this.intersects(this.textRect, { | ||
|  |         x: this.textRect.x + this.textRect.width, | ||
|  |         y: this.textRect.y + this.textRect.height, | ||
|  |       }, this.center.copy, { | ||
|  |         x: this.textRect.x, | ||
|  |         y: this.textRect.y + this.textRect.height / 2 | ||
|  |       }); | ||
|  |       this.ctx.lineTo(this.textRect.x + (intersect ? xOffset : 0), this.textRect.y + this.textRect.height / 2); | ||
|  |       this.ctx.stroke(); | ||
|  |       this.ctx.restore(); | ||
|  |     }; | ||
|  | 
 | ||
|  |     this.draw = function() { | ||
|  |       if (chart.getDataVisibility(index)) { | ||
|  |         this.drawLabel(); | ||
|  |         this.drawText(); | ||
|  |         this.drawLine(); | ||
|  |       } | ||
|  |     }; | ||
|  | 
 | ||
|  | 
 | ||
|  | 
 | ||
|  | 
 | ||
|  |     // eslint-disable-next-line max-statements
 | ||
|  |     this.update = function(view, elements, max) { | ||
|  |       this.center = positioners.center(view, this.stretch); | ||
|  |       this.moveLabelToOffset(); | ||
|  | 
 | ||
|  | 			this.center.x += this.offset.x; | ||
|  | 			this.center.y += this.offset.y; | ||
|  | 
 | ||
|  |       var valid = false; | ||
|  | 
 | ||
|  |       while (!valid) { | ||
|  |         this.textRect = this.computeTextRect(); | ||
|  |         this.labelRect = this.computeLabelRect(); | ||
|  | 
 | ||
|  |         var rectPoints = this.getPoints(); | ||
|  | 
 | ||
|  |         valid = true; | ||
|  | 
 | ||
|  |         for (var e = 0; e < max; ++e) { | ||
|  |           var element = elements[e][PLUGIN_KEY$1]; | ||
|  |           if (!element || !chart.getDataVisibility(index)) { | ||
|  |             continue; | ||
|  |           } | ||
|  | 
 | ||
|  |           var elPoints = element.getPoints(); | ||
|  | 
 | ||
|  |           for (var p = 0; p < rectPoints.length; ++p) { | ||
|  |             if (element.containsPoint(rectPoints[p])) { | ||
|  |               valid = false; | ||
|  |               break; | ||
|  |             } | ||
|  | 
 | ||
|  |             if (this.containsPoint(elPoints[p])) { | ||
|  |               valid = false; | ||
|  |               break; | ||
|  |             } | ||
|  |           } | ||
|  |         } | ||
|  | 
 | ||
|  |         if (!valid) { | ||
|  |           this.center = positioners.moveFromAnchor(this.center, 1); | ||
|  |           this.center.x += this.offset.x; | ||
|  | 					this.center.y += this.offset.y; | ||
|  |         } | ||
|  |       } | ||
|  |     }; | ||
|  | 
 | ||
|  |     this.moveLabelToOffset = function() { | ||
|  |       if (this.predictedOffset.x <= 0 && this.offset.x > this.predictedOffset.x) { | ||
|  |         this.offset.x -= this.offsetStep; | ||
|  |         if (this.offset.x <= this.predictedOffset.x) { | ||
|  |           this.offset.x = this.predictedOffset.x; | ||
|  |         } | ||
|  |       } else if (this.predictedOffset.x >= 0 && this.offset.x < this.predictedOffset.x) { | ||
|  |         this.offset.x += this.offsetStep; | ||
|  |         if (this.offset.x >= this.predictedOffset.x) { | ||
|  |           this.offset.x = this.predictedOffset.x; | ||
|  |         } | ||
|  |       } | ||
|  |     }; | ||
|  | 
 | ||
|  |   } | ||
|  | }; | ||
|  | 
 | ||
|  | 
 | ||
|  | 
 | ||
|  | chart_js.defaults.plugins.outlabels = customDefaults; | ||
|  | 
 | ||
|  | 
 | ||
|  | var PLUGIN_KEY = customDefaults.PLUGIN_KEY; | ||
|  | 
 | ||
|  | function configure(dataset, options) { | ||
|  |   var override = dataset.outlabels; | ||
|  |   var config = {}; | ||
|  | 
 | ||
|  |   if (override === false) { | ||
|  |     return null; | ||
|  |   } | ||
|  |   if (override === true) { | ||
|  |     override = {}; | ||
|  |   } | ||
|  | 
 | ||
|  |   return Object.assign({}, config, options, override); | ||
|  | 
 | ||
|  | } | ||
|  | 
 | ||
|  | var plugin = { | ||
|  |   id: 'outlabels', | ||
|  |   resize: function(chart) { | ||
|  |     chart.sizeChanged = true; | ||
|  |   }, | ||
|  |   afterUpdate: (chart) => { | ||
|  |     const ctrl = chart._metasets[0].controller; | ||
|  |     var meta = ctrl.getMeta(); | ||
|  | 
 | ||
|  |     var elements = meta.data || []; | ||
|  |     const rect = { | ||
|  |       x1: Infinity, | ||
|  |       x2: 0, | ||
|  |       y1: Infinity, | ||
|  |       y2: 0 | ||
|  |     }; | ||
|  |     elements.forEach((el, index) => { | ||
|  |       const outlabelPlugin = el[PLUGIN_KEY]; | ||
|  |       if (!outlabelPlugin) { | ||
|  |         return; | ||
|  |       } | ||
|  | 
 | ||
|  |       outlabelPlugin.update(el, elements, index); | ||
|  |       const x = outlabelPlugin.labelRect.x + (!outlabelPlugin.labelRect.isLeft ? 0 : outlabelPlugin.labelRect.width); | ||
|  |       const y = outlabelPlugin.labelRect.y + (outlabelPlugin.labelRect.isTop ? 0 : outlabelPlugin.labelRect.height); | ||
|  |       if (x < rect.x1) { | ||
|  |         rect.x1 = x; | ||
|  |       } | ||
|  |       if (x > rect.x2) { | ||
|  |         rect.x2 = x; | ||
|  |       } | ||
|  |       if (y < rect.y1) { | ||
|  |         rect.y1 = y; | ||
|  |       } | ||
|  |       if (y > rect.y2) { | ||
|  |         rect.y2 = y; | ||
|  |       } | ||
|  |     }); | ||
|  | 
 | ||
|  |     var max = chart.options.maxZoomOutPercentage || customDefaults.maxZoomOutPercentage; | ||
|  |     const maxDeltas = [ | ||
|  |       chart.chartArea.left - rect.x1, | ||
|  |       chart.chartArea.top - rect.y1, | ||
|  |       rect.x2 - chart.chartArea.right, | ||
|  |       rect.y2 - chart.chartArea.bottom | ||
|  |     ]; | ||
|  |     const diff = Math.max(...maxDeltas.filter(x => x > 0), 0); | ||
|  |     const percent = diff * 100 / ctrl.outerRadius; | ||
|  |     ctrl.outerRadius -= percent < max ? diff : max * 100 / ctrl.outerRadius; | ||
|  |     ctrl.innerRadius = ctrl.outerRadius / 2; | ||
|  | 
 | ||
|  |     ctrl.updateElements(meta.data, 0, meta.data.length, 'resize'); | ||
|  |   }, | ||
|  |   afterDatasetUpdate: function(chart, args, options) { | ||
|  |     var labels = chart.config.data.labels; | ||
|  |     var dataset = chart.data.datasets[args.index]; | ||
|  |     var config = configure(dataset, options); | ||
|  |     var display = config && config.display; | ||
|  |     var elements = args.meta.data || []; | ||
|  |     var ctx = chart.ctx; | ||
|  |     var el, label, percent, newLabel, context, i; | ||
|  |     ctx.save(); | ||
|  | 
 | ||
|  |     for (i = 0; i < elements.length; ++i) { | ||
|  |       el = elements[i]; | ||
|  |       label = el[PLUGIN_KEY]; | ||
|  |       percent = dataset.data[i] / args.meta.total; | ||
|  |       newLabel = null; | ||
|  | 
 | ||
|  |       if (display && el && !el.hidden) { | ||
|  |         try { | ||
|  |           context = { | ||
|  |             chart: chart, | ||
|  |             dataIndex: i, | ||
|  |             dataset: dataset, | ||
|  |             labels: labels, | ||
|  |             datasetIndex: args.index, | ||
|  |             percent: percent | ||
|  |           }; | ||
|  |           newLabel = new classes.OutLabel(chart, i, ctx, config, context); | ||
|  |         } catch (e) { | ||
|  |           newLabel = null; | ||
|  |         } | ||
|  |       } | ||
|  | 
 | ||
|  |       if ( | ||
|  |         label && newLabel && !chart.sizeChanged && | ||
|  |     (label.label === newLabel.label) && (label.encodedText === newLabel.encodedText) | ||
|  |       ) { | ||
|  |         newLabel.offset = label.offset; | ||
|  |       } | ||
|  |       el[PLUGIN_KEY] = newLabel; | ||
|  |     } | ||
|  | 
 | ||
|  |     ctx.restore(); | ||
|  |     chart.sizeChanged = false; | ||
|  |   }, | ||
|  |   afterDatasetDraw: function(chart, args) { | ||
|  |     var elements = args.meta.data || []; | ||
|  |     var ctx = chart.ctx; | ||
|  | 
 | ||
|  |     elements.forEach((el, index) => { | ||
|  |       const outlabelPlugin = el[PLUGIN_KEY]; | ||
|  |       if (!outlabelPlugin) { | ||
|  |         return; | ||
|  |       } | ||
|  |       outlabelPlugin.update(el, elements, index); | ||
|  |       outlabelPlugin.draw(ctx); | ||
|  |     }); | ||
|  |   }, | ||
|  | }; | ||
|  | 
 | ||
|  | return plugin; | ||
|  | 
 | ||
|  | })); |