// o---------------------------------------------------------------------------------o // | This file is part of the RGraph package - you can learn more at: | // | | // | https://www.rgraph.net/license.html | // | | // | RGraph is dual-licensed under the Open Source GPL license. That means that it's | // | free to use and there are no restrictions on what you can use RGraph for! | // | If the GPL license does not suit you however, then there's an inexpensive | // | commercial license option available. See the URL above for more details. | // o---------------------------------------------------------------------------------o RGraph = window.RGraph || {isrgraph:true,isRGraph:true,rgraph:true}; // // The pie chart constructor // RGraph.Pie = function (conf) { var id = conf.id, canvas = document.getElementById(id), data = conf.data; // Get the canvas and context objects this.id = id; this.canvas = canvas; this.context = this.canvas.getContext ? this.canvas.getContext("2d", {alpha: (typeof id === 'object' && id.alpha === false) ? false : true}) : null; this.canvas.__object__ = this; this.total = 0; this.subTotal = 0; this.angles = []; this.data = data; this.originalData = RGraph.arrayClone(data); this.properties = []; this.type = 'pie'; this.isRGraph = true; this.isrgraph = true; this.rgraph = true; this.coords = []; this.coords.key = []; this.coordsSticks = []; this.coordsText = []; this.uid = RGraph.createUID(); this.canvas.uid = this.canvas.uid ? this.canvas.uid : RGraph.createUID(); this.colorsParsed = false; this.original_colors = []; this.firstDraw = true; // After the first draw this will be false this.exploding = null; this.stopAnimationRequested = false;// Used to control the animations this.waveRadiusMultiplier = RGraph.arrayFill([], this.data.length, 1); // // Convert strings to numbers // this.data = RGraph.stringsToNumbers(this.data); this.properties = { centerxAdjust: 0, centeryAdjust: 0, colors: ['red', '#ccc', '#cfc', 'blue', 'pink', 'yellow', 'black', 'orange', 'cyan', 'purple', '#78CAEA', '#E284E9', 'white', 'blue', '#9E7BF6'], colorsStroke: 'white', linewidth: 3, labels: [], labelsFormattedDecimals: 0, labelsFormattedPoint: '.', labelsFormattedThousand: ',', labelsFormattedUnitsPre: '', labelsFormattedUnitsPost: '', labelsFont: null, labelsSize: null, labelsColor: null, labelsBold: null, labelsItalic: null, labelsRadiusOffset: 0, labelsSticks: false, labelsSticksLength: 7, labelsSticksColors: null, labelsSticksLinewidth: 1, labelsSticksHlength: 5, labelsList: true, labelsListLeftOffsetx: 0, labelsListLeftOffsety: 0, labelsListRightOffsetx: 0, labelsListRightOffsety: 0, labelsIngraph: null, labelsIngraphColor: null, labelsIngraphFont: null, labelsIngraphSize: null, labelsIngraphBold: null, labelsIngraphItalic: null, labelsIngraphBounding: true, labelsIngraphBoundingFill: 'rgba(255,255,255,0.85)', labelsIngraphBoundingStroke: 'rgba(0,0,0,0)', labelsIngraphSpecific: null, labelsIngraphSpecificFormattedDecimals: 0, labelsIngraphSpecificFormattedPoint: '.', labelsIngraphSpecificFormattedThousand: ',', labelsIngraphSpecificFormattedUnitsPre: '', labelsIngraphSpecificFormattedUnitsPost: '', labelsIngraphUnitsPre: '', labelsIngraphUnitsPost: '', labelsIngraphPoint: '.', labelsIngraphThousand: ',', labelsIngraphDecimals: 0, labelsIngraphRadius: null, labelsIngraphRadiusOffset: 0, labelsIngraphUndrawn: null, labelsIngraphUndrawnAsLabels: null, labelsIngraphUndrawnAlwaysShow: false, labelsCenter: null, labelsCenterSize: 26, labelsCenterFont: null, labelsCenterColor: null, labelsCenterItalic: null, labelsCenterBold: null, labelsCenterOffsetx: 0, labelsCenterOffsety: 0, labelsInside: null, labelsInsideColor: null, labelsInsideSize: null, labelsInsideFont: null, labelsInsideBold: null, labelsInsideItalic: null, labelsInsideDecimals: 0, labelsInsidePoint: '.', labelsInsideThousand: ',', labelsInsideUnitsPre: '', labelsInsideUnitsPost: '', labelsInsideOffsetr: 0, labelsInsideHalign: 'auto', labelsInsideBounding: false, labelsInsideBoundingFill: 'rgba(255,255,255,0.75)', labelsInsideBoundingStroke: 'transparent', labelsInsideSpecific: null, labelsInsideSpecificFormattedDecimals: 0, labelsInsideSpecificFormattedPoint: '.', labelsInsideSpecificFormattedThousand: ',', labelsInsideSpecificFormattedUnitsPre: '', labelsInsideSpecificFormattedUnitsPost: '', marginLeft: 35, marginRight: 35, marginTop: 35, marginBottom: 35, title: '', titleBold: null, titleFont: null, titleSize: null, titleColor: null, titleItalic: null, titleX: null, titleY: null, titleHalign: null, titleValign: null, titleOffsetx: 0, titleOffsety: 0, titleSubtitle: '', titleSubtitleSize: null, titleSubtitleColor: '#aaa', titleSubtitleFont: null, titleSubtitleBold: null, titleSubtitleItalic: null, titleSubtitleOffsetx: 0, titleSubtitleOffsety: 0, shadow: true, shadowColor: '#aaa', shadowOffsetx: 0, shadowOffsety: 0, shadowBlur: 15, textBold: false, textItalic: false, textSize: 12, textColor: 'black', textFont: 'Arial, Verdana, sans-serif', textAccessible: false, textAccessibleOverflow: 'visible', textAccessiblePointerevents: false, text: null, contextmenu: null, tooltips: [], tooltipsEvent: 'onclick', tooltipsEffect: 'slide', tooltipsCssClass: 'RGraph_tooltip', tooltipsCss: null, tooltipsHighlight: true, tooltipsPersistent: false, tooltipsFormattedThousand: ',', tooltipsFormattedPoint: '.', tooltipsFormattedDecimals: 0, tooltipsFormattedUnitsPre: '', tooltipsFormattedUnitsPost: '', tooltipsFormattedKeyColors: null, tooltipsFormattedKeyColorsShape: 'square', tooltipsFormattedKeyLabels: [], tooltipsFormattedListType: 'ul', tooltipsFormattedListItems: null, tooltipsFormattedTableHeaders: null, tooltipsFormattedTableData: null, tooltipsPointer: true, tooltipsPointerOffsetx: 0, tooltipsPointerOffsety: 0, tooltipsPositionStatic: true, tooltipsHotspotIgnore: null, highlightStyle: '2d', highlightStyleTwodFill: 'rgba(255,255,255,0.7)', highlightStyleTwodStroke: 'transparent', highlightStyleTwodLinewidth: 2, highlightStyleOutlineWidth: null, centerx: null, centery: null, radius: null, border: false, borderColor: 'rgba(255,255,255,0.5)', key: null, keyBackground: 'white', keyPosition: 'graph', keyHalign: 'right', keyValign: null, keyShadow: false, keyShadowColor: '#666', keyShadowBlur: 3, keyShadowOffsetx: 2, keyShadowOffsety: 2, keyPositionGutterBoxed: false, keyPositionX: null, keyPositionY: null, keyColorShape: 'square', keyRounded: true, keyLinewidth: 1, keyColors: null, keyInteractive: false, keyInteractiveHighlightChartLinewidth: 2, keyInteractiveHighlightChartStroke: 'black', keyInteractiveHighlightChartFill: 'rgba(255,255,255,0.7)', keyInteractiveHighlightLabel: 'rgba(255,0,0,0.2)', keyLabelsColor: null, keyLabelsFont: null, keyLabelsSize: null, keyLabelsBold: null, keyLabelsItalic: null, keyLabelsOffsetx: 0, keyLabelsOffsety: 0, keyFormattedDecimals: 0, keyFormattedPoint: '.', keyFormattedThousand: ',', keyFormattedUnitsPre: '', keyFormattedUnitsPost: '', keyFormattedValueSpecific: null, keyFormattedItemsCount: null, annotatable: false, annotatableColor: 'black', variant: 'pie', variantDonutWidth: null, variantThreedDepth: 20, exploded: [], effectRoundrobinMultiplier: 1, centerpin: null, centerpinFill: 'gray', centerpinStroke: 'white', origin: 0 - (Math.PI / 2), clearto: 'rgba(0,0,0,0)', events: true, clip: null } // // Add the reverse look-up table for property names // so that property names can be specified in any case. // this.properties_lowercase_map = []; for (var i in this.properties) { if (typeof i === 'string') { this.properties_lowercase_map[i.toLowerCase()] = i; } } // // Calculate the total // for (var i=0,len=data.length; i<len; i++) { this.total += data[i]; // This loop also creates the $xxx objects - this isn't related to // the code above but just saves doing another loop through the data this['$' + i] = {}; } // Easy access to properties and the path function var properties = this.properties; this.path = RGraph.pathObjectFunction; // // "Decorate" the object with the generic effects if the effects library has been included // if (RGraph.Effects && typeof RGraph.Effects.decorate === 'function') { RGraph.Effects.decorate(this); } // Add the responsive method. This method resides in the common file. this.responsive = RGraph.responsive; // // A generic setter // this.set = function (name) { var value = typeof arguments[1] === 'undefined' ? null : arguments[1]; // Go through all of the properties and make sure // that they're using the correct capitalisation if (typeof name === 'string') { name = this.properties_lowercase_map[name.toLowerCase()] || name; // Accommodate some BC if (name.toLowerCase() === 'labelsoffsetradius') { name = 'labelsRadiusOffset'; } if (name.toLowerCase() === 'labelsoffsetr') { name = 'labelsRadiusOffset'; } } // Set the colorsParsed flag to false if the colors // property is being set if ( name === 'colors' || name === 'keyColors' || name === 'colorsStroke' || name === 'highlightStroke' || name === 'highlightStyleTwodFill' || name === 'highlightStyleTwodStroke' || name === 'labelsIngraphBoundingFill' || name === 'labelsIngraphColor' ) { this.colorsParsed = false; } // the number of arguments is only one and it's an // object - parse it for configuration data and return. if (arguments.length === 1 && typeof arguments[0] === 'object') { for (i in arguments[0]) { if (typeof i === 'string') { this.set(i, arguments[0][i]); } } return this; } properties[name] = value; return this; }; // // A generic getter // this.get = function (name) { // Go through all of the properties and make sure // that they're using the correct capitalisation name = this.properties_lowercase_map[name.toLowerCase()] || name; return properties[name]; }; // // This draws the pie chart // this.draw = function () { // // Fire the onbeforedraw event // RGraph.fireCustomEvent(this, 'onbeforedraw'); // Translate half a pixel for antialiasing purposes - but only if it hasn't been // done already // // MUST be the first thing done! // if (!this.canvas.__rgraph_aa_translated__) { this.context.translate(0.5,0.5); this.canvas.__rgraph_aa_translated__ = true; } // NB: Colors are parsed further down so that the center X/Y can be used // // If there's only one datapiece then add another, // really small, value so that tooltips work correctly. // if (this.data.length === 1) { this.data.push(this.data / 10000); } // // Make the margins easy to access // this.marginLeft = properties.marginLeft; this.marginRight = properties.marginRight; this.marginTop = properties.marginTop; this.marginBottom = properties.marginBottom; this.radius = this.getRadius();// MUST be first this.centerx = (this.graph.width / 2) + this.marginLeft + properties.centerxAdjust; this.centery = (this.graph.height / 2) + this.marginTop + properties.centeryAdjust; this.subTotal = properties.origin; this.angles = []; this.coordsText = []; // // Allow specification of a custom radius & center X/Y // if (typeof properties.radius === 'number') this.radius = properties.radius; if (typeof properties.centerx === 'number') this.centerx = properties.centerx; if (typeof properties.centery === 'number') this.centery = properties.centery; // // Allow the centerx/centery/radius to be a plus/minus // if (typeof properties.radius === 'string' && properties.radius.match(/^\+|-\d+$/) ) this.radius += parseFloat(properties.radius); if (typeof properties.centerx === 'string' && properties.centerx.match(/^\+|-\d+$/) ) this.centerx += parseFloat(properties.centerx); if (typeof properties.centery === 'string' && properties.centery.match(/^\+|-\d+$/) ) this.centery += parseFloat(properties.centery); if (this.radius <= 0) { return; } // // Parse the colors for gradients. Its down here so that the center X/Y can be used // if (!this.colorsParsed) { this.parseColors(); // Don't want to do this again this.colorsParsed = true; } // // Install clipping // // MUST be the first thing that's done after the // beforedraw event // if (!RGraph.isNullish(this.properties.clip)) { RGraph.clipTo.start(this, this.properties.clip); } if (properties.variant.indexOf('3d') > 0) { return this.draw3d(); } // // Draw the title // this.drawTitle(); // // The total of the array of values // this.total = RGraph.arraySum(this.data); var tot = this.total; var data = this.data; for (var i=0,len=this.data.length; i<len; i++) { var angle = ((data[i] / tot) * RGraph.TWOPI); // Draw the segment this.drawSegment(angle,properties.colors[i],i == (len - 1), i); } RGraph.noShadow(this); // // Redraw the seperating lines // if (properties.linewidth > 0) { this.drawBorders(); } // // Now draw the segments again with shadow turned off. This is always performed, // not just if the shadow is on. // var len = this.angles.length; var r = this.radius; for (var action=0; action<2; action+=1) { for (var i=0; i<len; i++) { var r = this.radius * this.waveRadiusMultiplier[i]; this.context.beginPath(); var segment = this.angles[i]; if (action === 1) { this.context.strokeStyle = typeof properties.colorsStroke == 'object' ? properties.colorsStroke[i] : properties.colorsStroke; } properties.colors[i] ? this.context.fillStyle = properties.colors[i] : null; this.context.lineJoin = 'round'; this.context.arc( segment[2], segment[3], r, (segment[0]), (segment[1]), false ); if (properties.variant == 'donut') { this.context.arc( segment[2], segment[3], typeof properties.variantDonutWidth == 'number' ? r - properties.variantDonutWidth : r / 2, (segment[1]), (segment[0]), true ); } else { this.context.lineTo(segment[2], segment[3]); } this.context.closePath(); action === 0 ? this.context.fill() : this.context.stroke(); } } // // Draw label sticks // if (properties.labelsSticks) { this.drawSticks(); // Redraw the border going around the Pie chart if the stroke style is NOT white var strokeStyle = properties.colorsStroke; } // // Draw the labels // if (properties.labels) { this.drawLabels(); } // // Draw centerpin if requested // if (properties.centerpin) { this.drawCenterpin(); } // // Draw ingraph labels // if (properties.labelsIngraph) { this.drawInGraphLabels(); } // // Draw the center label if requested // if (typeof properties.labelsCenter === 'string') { this.drawCenterLabel(properties.labelsCenter); } // Draw the labelsInside labels. Mainly for donut // charts - but you can use them if you choose if (properties.labelsInside) { this.drawLabelsInside(); } // // Setup the context menu if required // if (properties.contextmenu) { RGraph.showContext(this); } // // If a border is pecified, draw it // if (properties.border) { this.context.beginPath(); this.context.lineWidth = 5; this.context.strokeStyle = properties.borderColor; this.context.arc( this.centerx, this.centery, this.radius - 2, 0, RGraph.TWOPI, 0 ); this.context.stroke(); } // // Draw the key if desired // if (properties.key && properties.key.length) { // Allow for vertical centering if (properties.keyValign === 'center') { // Calculate how big the key labels are var textConf = RGraph.getTextConf({ object: this, prefix: 'keyLabels' }); // Calculate the height of the key var height = this.properties.key.length * textConf.size * 1.5; // Use the height that has just been calculate to set // the new Y coordinate of the key this.set('keyPositionY', this.centery - (height / 2)); } RGraph.drawKey( this, properties.key, properties.colors ); } RGraph.noShadow(this); // // Add custom text thats specified // RGraph.addCustomText(this); // // This installs the event listeners // if (properties.events == true) { RGraph.installEventListeners(this); } // // End clipping // if (!RGraph.isNullish(this.properties.clip)) { RGraph.clipTo.end(); } // // Fire the onfirstdraw event // if (this.firstDraw) { this.firstDraw = false; RGraph.fireCustomEvent(this, 'onfirstdraw'); this.firstDrawFunc(); } // // Fire the RGraph draw event // RGraph.fireCustomEvent(this, 'ondraw'); // // Install any inline responsive configuration. This // should be last in the draw function - even after // the draw events. // RGraph.installInlineResponsive(this); return this; }; // // Used in chaining. Runs a function there and then - not waiting for // the events to fire (eg the onbeforedraw event) // // @param function func The function to execute // this.exec = function (func) { func(this); return this; }; // // Draws the title // this.drawTitle = function () { RGraph.drawTitle(this); }; // // Draws a single segment of the pie chart // // @param int degrees The number of degrees for this segment // this.drawSegment = function (radians, color, last, index) { var subTotal = this.subTotal; radians = radians * properties.effectRoundrobinMultiplier; this.context.beginPath(); color ? this.context.fillStyle = color : null; this.context.strokeStyle = properties.colorsStroke; this.context.lineWidth = 0; if (properties.shadow) { RGraph.setShadow( this, properties.shadowColor, properties.shadowOffsetx, properties.shadowOffsety, properties.shadowBlur ); } // // Exploded segments // if ( (typeof properties.exploded == 'object' && properties.exploded[index] > 0) || typeof properties.exploded == 'number') { var explosion = typeof properties.exploded == 'number' ? properties.exploded : properties.exploded[index]; var x = 0; var y = 0; var h = explosion; var t = subTotal + (radians / 2); var x = (Math.cos(t) * explosion); var y = (Math.sin(t) * explosion); this.context.moveTo(this.centerx + x, this.centery + y); } else { var x = 0; var y = 0; } var r = this.radius; r *= this.waveRadiusMultiplier[index]; // // Calculate the angles // var startAngle = subTotal; var endAngle = ((subTotal + radians)); this.context.arc(this.centerx + x, this.centery + y, r, startAngle, endAngle, 0); if (properties.variant == 'donut') { this.context.arc( this.centerx + x, this.centery + y, typeof properties.variantDonutWidth == 'number' ? r - properties.variantDonutWidth : r / 2, endAngle, startAngle, true ); } else { this.context.lineTo(this.centerx + x, this.centery + y); } this.context.closePath(); // Keep hold of the angles this.angles.push([ subTotal, subTotal + radians, this.centerx + x, this.centery + y ]); //this.context.stroke(); this.context.fill(); // // Calculate the segment angle // this.subTotal += radians; }; // // Draws the graphs labels // this.drawLabels = function () { if (properties.labels && properties.labels.length) { // // If the xaxisLabels option is a string then turn it // into an array. // if (typeof properties.labels === 'string') { properties.labels = RGraph.arrayPad({ array: [], length: this.data.length, value: properties.labels }); } for (var i=0; i<properties.labels.length; ++i) { // Only do substitution if it's a string or // a number if (RGraph.isTextual(properties.labels[i])) { properties.labels[i] = RGraph.labelSubstitution({ object: this, text: properties.labels[i], index: i, value: this.data[i], decimals: properties.labelsFormattedDecimals || 0, unitsPre: properties.labelsFormattedUnitsPre || '', unitsPost: properties.labelsFormattedUnitsPost || '', thousand: properties.labelsFormattedThousand || ',', point: properties.labelsFormattedPoint || '.' }); } } } // New way of spacing labels out if (properties.labels.length && properties.labelsList) { return this.drawLabelsList(); } var hAlignment = 'left', vAlignment = 'center', labels = properties.labels, context = this.context, font = properties.textFont, bold = properties.labelsBold, text_size = properties.textSize, cx = this.centerx, cy = this.centery, r = this.radius; // // Turn the shadow off // RGraph.noShadow(this); this.context.fillStyle = 'black'; this.context.beginPath(); // // Draw the labels // if (labels && labels.length) { var textConf = RGraph.getTextConf({ object: this, prefix: 'labels' }); for (i=0; i<this.angles.length; ++i) { var segment = this.angles[i]; if (typeof labels[i] != 'string' && typeof labels[i] != 'number') { continue; } // Move to the centre this.context.moveTo(cx,cy); var a = segment[0] + ((segment[1] - segment[0]) / 2), angle = ((segment[1] - segment[0]) / 2) + segment[0]; // // Handle the additional "explosion" offset // if (typeof properties.exploded === 'object' && properties.exploded[i] || typeof properties.exploded == 'number') { var t = ((segment[1] - segment[0]) / 2), seperation = typeof properties.exploded == 'number' ? properties.exploded : properties.exploded[i]; // Adjust the angles var explosion_offsetx = (Math.cos(angle) * seperation), explosion_offsety = (Math.sin(angle) * seperation); } else { var explosion_offsetx = 0, explosion_offsety = 0; } // // Allow for the label sticks // if (properties.labelsSticks) { explosion_offsetx += (Math.cos(angle) * (typeof properties.labelsSticksLength === 'object' ? properties.labelsSticksLength[i] : properties.labelsSticksLength) ); explosion_offsety += (Math.sin(angle) * (typeof properties.labelsSticksLength === 'object' ? properties.labelsSticksLength[i] : properties.labelsSticksLength) ); } // // Coords for the text // var x = cx + explosion_offsetx + ((r + 10 + properties.labelsRadiusOffset) * Math.cos(a)) + (properties.labelsSticks ? (a < RGraph.HALFPI || a > (RGraph.TWOPI + RGraph.HALFPI) ? 2 : -2) : 0), y = cy + explosion_offsety + (((r + 10 + properties.labelsRadiusOffset) * Math.sin(a))); // // If sticks are enabled use the endpoints that have been saved // if (this.coordsSticks && this.coordsSticks[i]) { var x = this.coordsSticks[i][4][0] + (x < cx ? -5 : 5), y = this.coordsSticks[i][4][1]; } // // Alignment // //vAlignment = y < cy ? 'center' : 'center'; vAlignment = 'center'; hAlignment = x < cx ? 'right' : 'left'; this.context.fillStyle = properties.textColor; //if ( typeof properties.labelsColors === 'object' && properties.labelsColors && properties.labelsColors[i]) { // this.context.fillStyle = properties.labelsColors[i]; //} RGraph.text({ object: this, font: textConf.font, size: textConf.size, color: textConf.color, bold: textConf.bold, italic: textConf.italic, x: x, y: y, text: labels[i], valign: vAlignment, halign: hAlignment, tag: 'labels', cssClass: RGraph.getLabelsCSSClassName({ object: this, name: 'labelsClass', index: i }) }); } this.context.fill(); } }; // // A newer way of spacing out labels // this.drawLabelsList = function () { var segment = this.angles[i], labels = properties.labels, labels_right = [], labels_left = [], left = [], right = [], centerx = this.centerx, centery = this.centery, radius = this.radius, offset = 50 // This may not be used - see the endpoint_outer var below // // Draw the right hand side labels // for (var i=0; i<this.angles.length; ++i) { // Null values do not get labels displayed. Also, // null or undefined labels aren't displayed either if (RGraph.isNullish(this.data[i])) { continue; } var angle = this.angles[i][0] + ((this.angles[i][1] - this.angles[i][0]) / 2), // Midpoint endpoint_inner = RGraph.getRadiusEndPoint(centerx, centery, angle, radius + 5), endpoint_outer = RGraph.getRadiusEndPoint(centerx, centery, angle, radius + 30), explosion = [ (typeof properties.exploded === 'number' ? properties.exploded : properties.exploded[i]), (Math.cos(angle) * (typeof properties.exploded === 'number' ? properties.exploded : properties.exploded[i])), (Math.sin(angle) * (typeof properties.exploded === 'number' ? properties.exploded : properties.exploded[i])) ] var textConf = RGraph.getTextConf({ object: this, prefix: 'labels' }); if (angle > (-1 * RGraph.HALFPI) && angle < RGraph.HALFPI) { labels_right.push([ i, angle, labels[i] ? labels[i] : '', endpoint_inner, endpoint_outer, textConf.color, RGraph.arrayClone(explosion) ]); } else { labels_left.push([ i, angle, labels[i] ? labels[i] : '', endpoint_inner, endpoint_outer, textConf.color, RGraph.arrayClone(explosion) ]); } } // // Draw the right hand side labels first // // Calculate how much space there is for each label var vspace_right = (this.canvas.height - properties.marginTop - properties.marginBottom) / labels_right.length; for (var i=0,y=(properties.marginTop + (vspace_right / 2)); i<labels_right.length; y+=vspace_right,i++) { if (labels_right[i][2]) { var x = this.centerx + this.radius + offset, idx = labels_right[i][0], explosionX = labels_right[i][6][0] ? labels_right[i][6][1] : 0, explosionY = labels_right[i][6][0] ? labels_right[i][6][2] : 0 var ret = RGraph.text({ object: this, font: textConf.font, size: textConf.size, color: textConf.color, bold: textConf.bold, italic: textConf.italic, x: x + explosionX + properties.labelsListRightOffsetx, y: y + explosionY + properties.labelsListRightOffsety, text: labels_right[i][2], valign: 'center', halign: 'left', tag: 'labels', color: labels_right[i][5], cssClass: RGraph.getLabelsCSSClassName({ object: this, name: 'labelsClass', index: i }) }); if (ret && ret.node) { ret.node.__index__ = labels_right[i][0]; } // This draws the stick this.path( 'lc round lw % b m % % qc % % % % s %', properties.labelsSticksLinewidth, labels_right[i][3][0] + explosionX,labels_right[i][3][1] + explosionY, labels_right[i][4][0] + explosionX,labels_right[i][4][1] + explosionY,ret.x - 5 ,ret.y + (ret.height / 2), labels_right[i][5] ); // Draw a circle at the end of the stick this.path( 'b a % % 2 0 6.2830 false, f %', ret.x - 5,ret.y + (ret.height / 2), labels_right[i][5] ); } } // // Draw the left hand side labels // // Calculate how much space there is for each label var vspace_left = (this.canvas.height - properties.marginTop - properties.marginBottom) / labels_left.length for (var i=(labels_left.length - 1),y=(properties.marginTop + (vspace_left / 2)); i>=0; y+=vspace_left,i--) { if (labels_left[i][2]) { var x = this.centerx - this.radius - offset, idx = labels_left[i][0], explosionX = labels_left[i][6][0] ? labels_left[i][6][1] : 0, explosionY = labels_left[i][6][0] ? labels_left[i][6][2] : 0 var ret = RGraph.text({ object: this, font: textConf.font, size: textConf.size, color: textConf.color, bold: textConf.bold, italic: textConf.italic, x: x + explosionX + properties.labelsListLeftOffsetx, y: y + explosionY + properties.labelsListLeftOffsety, text: labels_left[i][2], valign: 'center', halign: 'right', tag: 'labels', cssClass: RGraph.getLabelsCSSClassName({ object: this, name: 'labelsClass', index: i }) }); if (ret && ret.node) { ret.node.__index__ = labels_left[i][0]; } this.path( 'lw % b m % % qc % % % % s %', properties.labelsSticksLinewidth, labels_left[i][3][0] + explosionX,labels_left[i][3][1] + explosionY, labels_left[i][4][0] + explosionX,Math.round(labels_left[i][4][1] + explosionY),ret.x + 5 + ret.width,ret.y + (ret.height / 2), labels_left[i][5] ); // Draw a circle at the end of the stick this.path( 'b a % % 2 0 6.2830 false, f %', ret.x + 5 + ret.width,ret.y + (ret.height / 2), labels_left[i][5] ); } } }; // // Draw the labelsInside labels if they've been // specified // this.drawLabelsInside = function () { // Get the text configuration var textConf = RGraph.getTextConf({ object: this, prefix: 'labelsInside' }); // If the labelsInsideSpecific is a string (or a number) // then convert it to an array of that string repeated // a number of times (the same number as there are // items in the data) if (RGraph.isTextual(properties.labelsInsideSpecific)) { properties.labelsInsideSpecific = RGraph.arrayFill( [], this.data.length, properties.labelsInsideSpecific ); } for (let i=0; i<this.data.length; ++i) { var centerAngle = this.angles[i][0] + ((this.angles[i][1] - this.angles[i][0]) / 2); var p = RGraph.getRadiusEndPoint( this.centerx, this.centery, centerAngle, this.radius - properties.variantDonutWidth - 20 + properties.labelsInsideOffsetr + properties.exploded ); // Horizontal alignment if (p[0] >= this.centerx) { var halign = 'right'; } else { var halign = 'left'; } // If the labelsInsideSpecific property is set // then use that if (RGraph.isArray(properties.labelsInsideSpecific) && RGraph.isTextual(properties.labelsInsideSpecific[i])) { var label = String(properties.labelsInsideSpecific[i]); // // Allow for label substitution // label = RGraph.labelSubstitution({ object: this, text: label, index: i, value: this.data[i], decimals: properties.labelsInsideSpecificFormattedDecimals || 0, unitsPre: properties.labelsInsideSpecificFormattedUnitsPre || '', unitsPost: properties.labelsInsideSpecificFormattedUnitsPost || '', thousand: properties.labelsInsideSpecificFormattedThousand || ',', point: properties.labelsInsideSpecificFormattedPoint || '.' }); } else { var label = RGraph.numberFormat({ object: this, number: this.data[i].toFixed(properties.labelsInsideDecimals), unitspre: properties.labelsInsideUnitsPre, unitspost: properties.labelsInsideUnitsPost, point: properties.labelsInsidePoint, thousand: properties.labelsInsideThousand }); } RGraph.text({ object: this, x: p[0], y: p[1], text: label, size: textConf.size, font: textConf.font, color: textConf.color, bold: textConf.bold, italic: textConf.italic, halign: properties.labelsInsideHalign === 'center' ? 'center' : halign, valign: 'center', bounding: properties.labelsInsideBounding, boundingFill: properties.labelsInsideBoundingFill, boundingStroke: properties.labelsInsideBoundingStroke }); } } // // This function draws the pie chart sticks (for the labels) // this.drawSticks = function () { var offset = properties.linewidth / 2, exploded = properties.exploded, sticks = properties.labelsSticks, colors = properties.colors, cx = this.centerx, cy = this.centery, radius = this.radius, points = [], linewidth = properties.labelsSticksLinewidth for (var i=0,len=this.angles.length; i<len; ++i) { if (!this.properties.labels[i]) { continue; } var segment = this.angles[i]; // This allows the labelsSticks to be an array as well as a boolean if (typeof sticks === 'object' && !sticks[i]) { continue; } var radians = segment[1] - segment[0]; this.context.beginPath(); this.context.strokeStyle = typeof properties.labelsSticksColors === 'string' ? properties.labelsSticksColors : (!RGraph.isNullish(properties.labelsSticksColors) ? properties.labelsSticksColors[i] : 'gray'); this.context.lineWidth = linewidth; if (typeof properties.labelsSticksColor === 'string') { this.context.strokeStyle = properties.labelsSticksColor; } var midpoint = (segment[0] + (radians / 2)); if (typeof exploded === 'object' && exploded[i]) { var extra = exploded[i]; } else if (typeof exploded === 'number') { var extra = exploded; } else { var extra = 0; } // // Determine the stick length // var stickLength = typeof properties.labelsSticksLength === 'object' ? properties.labelsSticksLength[i] : properties.labelsSticksLength; points[0] = RGraph.getRadiusEndPoint(cx, cy, midpoint, radius + extra + offset); points[1] = RGraph.getRadiusEndPoint(cx, cy, midpoint, radius + stickLength + extra - 5); points[2] = RGraph.getRadiusEndPoint(cx, cy, midpoint, radius + stickLength + extra + properties.labelsRadiusOffset); points[3] = RGraph.getRadiusEndPoint(cx, cy, midpoint, radius + stickLength + extra + properties.labelsRadiusOffset); points[3][0] += (points[3][0] > cx ? 5 : -5); points[4] = [ points[2][0] + (points[2][0] > cx ? 5 + properties.labelsSticksHlength : -5 - properties.labelsSticksHlength), points[2][1] ]; this.context.moveTo(points[0][0], points[0][1]); this.context.quadraticCurveTo( points[2][0], points[2][1], points[4][0], points[4][1] ); this.context.stroke(); // // Save the stick end coords // this.coordsSticks[i] = [ points[0], points[1], points[2], points[3], points[4] ]; } }; // // The (now Pie chart specific) getSegment function // // @param object e The event object // this.getShape = function (e) { // The optional arg provides a way of allowing some accuracy (pixels) var accuracy = arguments[1] ? arguments[1] : 0; var canvas = this.canvas; var context = this.context; var mouseCoords = RGraph.getMouseXY(e); var mouseX = mouseCoords[0]; var mouseY = mouseCoords[1]; var r = this.radius; var angles = this.angles; for (var i=0,len=angles.length; i<len; ++i) { if (RGraph.tooltipsHotspotIgnore(this, i)) { continue; } // Draw the segment again so that it can be tested this.path( 'b ss rgba(0,0,0,0) a % % % % % false', angles[i][2],angles[i][3],this.radius,angles[i][0],angles[i][1] ); if (this.type === 'pie' && properties.variant.indexOf('donut') !== -1) { this.path( 'a % % % % % true', angles[i][2],angles[i][3],(typeof properties.variantDonutWidth == 'number' ? this.radius - properties.variantDonutWidth : this.radius / 2),angles[i][1],angles[i][0] ); } else { this.path( 'l % %', angles[i][2],angles[i][3] ); } this.path('c'); if ( this.context.isPointInPath(mouseX, mouseY) && (this.properties.clip ? RGraph.clipTo.test(this, mouseX, mouseY) : true) ) { if (angles[i][0] < 0) angles[i][0] += RGraph.TWOPI; if (angles[i][1] > RGraph.TWOPI) angles[i][1] -= RGraph.TWOPI; // // Get the tooltip for the returned shape // if (RGraph.parseTooltipText && properties.tooltips) { var tooltip = RGraph.parseTooltipText(properties.tooltips, i); } var ret = { object: this, x: angles[i][2], y: angles[i][3], radius: this.radius, angleStart: angles[i][0], angleEnd: angles[i][1], index: i, dataset: 0, sequentialIndex: i, label: properties.labels && typeof properties.labels[i] === 'string' ? properties.labels[i] : null, tooltip: typeof tooltip === 'string' ? tooltip : null }; return ret; } } return null; }; // // Draw the borders for the Pie chart // this.drawBorders = function () { if (properties.linewidth > 0) { this.context.lineWidth = properties.linewidth; this.context.strokeStyle = properties.colorsStroke; var r = this.radius; for (var i=0,len=this.angles.length; i<len; ++i) { var segment = this.angles[i]; this.context.beginPath(); this.context.arc(segment[2], segment[3], r, (segment[0]), (segment[0] + 0.001), 0); this.context.arc(segment[2], segment[3], properties.variant == 'donut' ? (typeof properties.variantDonutWidth == 'number' ? this.radius - properties.variantDonutWidth : r / 2): r, segment[0], segment[0] + 0.0001, 0); this.context.closePath(); this.context.stroke(); } } }; // // Returns the radius of the pie chart // // [06-02-2012] Maintained for compatibility ONLY. // this.getRadius = function () { this.graph = { width: this.canvas.width - properties.marginLeft - properties.marginRight, height: this.canvas.height - properties.marginTop - properties.marginBottom }; if (typeof properties.radius == 'number') { this.radius = properties.radius; } else { this.radius = Math.min(this.graph.width, this.graph.height) / 2; } // If the radius property is a string treat it // as an adjustment if (typeof properties.radius === 'string') { this.radius = this.radius + parseFloat(properties.radius); } return this.radius; }; // // A programmatic explode function // // @param object obj The chart object // @param number index The zero-indexed number of the segment // @param number size The size (in pixels) of the explosion // this.explodeSegment = function (index, size,callback = null) { if (typeof this.exploding === 'number' && this.exploding === index) { return; } //this.set('exploded', []); if (!properties.exploded) { properties.exploded = []; } // If exploded is a number - convert it to an array if (typeof properties.exploded === 'number') { var original_explode = properties.exploded; var exploded = properties.exploded; properties.exploded = []; for (var i=0,len=this.data.length; i<len; ++i) { properties.exploded[i] = exploded; } } properties.exploded[index] = typeof original_explode == 'number' ? original_explode : 0; this.exploding = index; var delay = RGraph.ISIE && !RGraph.ISIE10 ? 25 : 16.666; var obj = this; for (var o=0; o<size; ++o) { setTimeout( function () { properties.exploded[index] += 1; RGraph.clear(obj.canvas); RGraph.redrawCanvas(obj.canvas); }, o * delay); } var obj = this; setTimeout(function () { obj.exploding = null; if (RGraph.isFunction (callback)) { callback(obj); } }, size * delay); }; // // This function highlights a segment // // @param array segment The segment information that is returned by the pie.getSegment(e) function // this.highlight_segment = function (segment) { this.context.beginPath(); this.context.strokeStyle = properties.highlightStyleTwodStroke; this.context.fillStyle = properties.highlightStyleTwodFill; this.context.moveTo(segment[0], segment[1]); this.context.arc(segment[0], segment[1], segment[2], this.angles[segment[5]][0], this.angles[segment[5]][1], 0); this.context.lineTo(segment[0], segment[1]); this.context.closePath(); this.context.stroke(); this.context.fill(); }; // // Each object type has its own Highlight() function which highlights // the appropriate shape // // @param object shape The shape to highlight // this.highlight = function (shape) { if (properties.tooltipsHighlight) { if (typeof properties.highlightStyle === 'function') { (properties.highlightStyle)(shape); // // Inverted style of highlighting // } else if (properties.highlightStyle === 'invert') { // Loop through all of the segments for (var i=0; i<this.angles.length; ++i) { if (i !== shape.index) { this.path( 'b lw % m % % a % % % % % false c s % f %', properties.highlightStyleTwodLinewidth, this.angles[i][2], this.angles[i][3], this.angles[i][2], this.angles[i][3],shape.radius, this.angles[i][0], this.angles[i][1], properties.highlightStyleTwodStroke, properties.highlightStyleTwodFill ); } } //this.context.beginPath(); // this.context.strokeStyle = properties.highlightStyleTwodStroke; // this.context.fillStyle = properties.highlightStyleTwodFill; // if (properties.variant.indexOf('donut') > -1) { // this.context.arc(shape.x, shape.y, shape.radius, shape.angleStart, shape.angleEnd, false); // this.context.arc(shape.x, shape.y, typeof properties.variantDonutWidth == 'number' ? this.radius - properties.variantDonutWidth : shape.radius / 2, shape.angleEnd, shape.angleStart, true); // } else { // this.context.arc(shape.x, shape.y, shape.radius + 1, shape.angleStart, shape.angleEnd, false); // this.context.lineTo(shape.x, shape.y); // } //this.context.closePath(); //this.context.stroke(); //this.context.fill(); // // 3D style of highlighting // } else if (properties.highlightStyle == '3d') { this.context.lineWidth = 1; // This is the extent of the 2D effect. Bigger values will give the appearance of a larger "protusion" var extent = 2; // Draw a white-out where the segment is this.context.beginPath(); RGraph.noShadow(this); this.context.fillStyle = 'rgba(0,0,0,0)'; this.context.arc(shape.x, shape.y, shape.radius, shape.angleStart, shape.angleEnd, false); if (properties.variant == 'donut') { this.context.arc(shape.x, shape.y, shape.radius / 5, shape.angleEnd, shape.angleStart, true); } else { this.context.lineTo(shape.x, shape.y); } this.context.closePath(); this.context.fill(); // Draw the new segment this.context.beginPath(); this.context.shadowColor = '#666'; this.context.shadowBlur = 3; this.context.shadowOffsetX = 3; this.context.shadowOffsetY = 3; this.context.fillStyle = properties.colors[shape.index]; this.context.strokeStyle = properties.colorsStroke; this.context.arc(shape.x - extent, shape.y - extent, shape.radius, shape.angleStart, shape.angleEnd, false); if (properties.variant == 'donut') { this.context.arc(shape.x - extent, shape.y - extent, shape.radius / 2, shape.angleEnd, shape.angleStart, true) } else { this.context.lineTo(shape.x - extent, shape.y - extent); } this.context.closePath(); this.context.stroke(); this.context.fill(); // Turn off the shadow RGraph.noShadow(this); // // If a border is defined, redraw that // if (properties.border) { this.context.beginPath(); this.context.strokeStyle = properties.borderColor; this.context.lineWidth = 5; this.context.arc(shape.x - extent, shape.y - extent, shape.radius - 2, shape.angleStart, shape.angleEnd, false); this.context.stroke(); } // Outline style of highlighting } else if (properties.highlightStyle === 'outline') { var index = shape.index, coords = this.angles[index], color = this.get('colors')[index] width = this.radius / 12.5; // Allow custom setting of outline if (typeof properties.highlightStyleOutlineWidth === 'number') { width = properties.highlightStyleOutlineWidth; } this.path( 'ga 0.25 b a % % % % % false a % % % % % true c f % ga 1', coords[2], coords[3], this.radius + 2 + width, coords[0], coords[1], coords[2], coords[3], this.radius + 2, coords[1], coords[0], color ); // Default 2D style of highlighting } else { this.path( 'b ss % fs % lw %', properties.highlightStyleTwodStroke, properties.highlightStyleTwodFill, properties.highlightStyleTwodLinewidth ); if (properties.variant.indexOf('donut') > -1) { this.context.arc(shape.x, shape.y, shape.radius, shape.angleStart, shape.angleEnd, false); this.context.arc(shape.x, shape.y, typeof properties.variantDonutWidth == 'number' ? this.radius - properties.variantDonutWidth : shape.radius / 2, shape.angleEnd, shape.angleStart, true); } else { this.context.arc(shape.x, shape.y, shape.radius + 1, shape.angleStart, shape.angleEnd, false); this.context.lineTo(shape.x, shape.y); } this.context.closePath(); this.context.stroke(); this.context.fill(); } } }; // // The getObjectByXY() worker method. The Pie chart is able to use the // getShape() method - so it does. // this.getObjectByXY = function (e) { if (this.getShape(e)) { return this; } }; // // Draws the centerpin if requested // this.drawCenterpin = function () { if (typeof properties.centerpin === 'number' && properties.centerpin > 0) { var cx = this.centerx; var cy = this.centery; this.context.beginPath(); this.context.strokeStyle = properties.centerpinStroke ? properties.centerpinStroke : properties.colorsStroke; this.context.fillStyle = properties.centerpinFill ? properties.centerpinFill : properties.colorsStroke; this.context.moveTo(cx, cy); this.context.arc(cx, cy, properties.centerpin, 0, RGraph.TWOPI, false); this.context.stroke(); this.context.fill(); } }; // // This draws Ingraph labels // this.drawLabelsIngraph = this.drawInGraphLabels = function () { var context = this.context; var cx = this.centerx; var cy = this.centery; var radius = properties.labelsIngraphRadius; // If the labelsIngraphSpecific property is a string // make it an array and populate it if (typeof properties.labelsIngraphSpecific === 'string') { properties.labelsIngraphSpecific = RGraph.arrayFill( [], this.data.length, properties.labelsIngraphSpecific ); } // Reset this to an empty array this.set('labelsIngraphUndrawn', []); // Account for offsetting if (RGraph.isNumber(properties.labelsIngraphRadiusOffset)) { var radiusOffset = properties.labelsIngraphRadiusOffset; } else { var radiusOffset = 0; } // // Is the radius less than 2? If so then it's a // factor and not an exact point // if (radius <= 2 && radius > 0) { radiusFactor = radius; } else { radiusFactor = 0.5; } if (properties.variant === 'donut') { var r = this.radius * (0.5 + (radiusFactor * 0.5)); if (typeof properties.variantDonutWidth == 'number') { var r = (this.radius - properties.variantDonutWidth) + (properties.variantDonutWidth / 2); } } else { var r = this.radius * radiusFactor; } if (radius > 2) { r = radius; } for (var i=0,len=this.angles.length; i<len; ++i) { // This handles any explosion that the segment may have if (typeof properties.exploded == 'object' && typeof properties.exploded[i] == 'number') { var explosion = properties.exploded[i]; } else if (typeof properties.exploded == 'number') { var explosion = parseInt(properties.exploded); } else { var explosion = 0; } var angleStart = this.angles[i][0]; var angleEnd = this.angles[i][1]; var angleCenter = ((angleEnd - angleStart) / 2) + angleStart; var coords = RGraph.getRadiusEndPoint( this.centerx, this.centery, angleCenter, r + (explosion ? explosion : 0) + (properties.labelsIngraphRadiusOffset || 0) ); var x = coords[0]; var y = coords[1]; // Work out the text of the label: // // Use specific text if (properties.labelsIngraphSpecific && typeof properties.labelsIngraphSpecific[i] === 'string') { var text = RGraph.labelSubstitution({ object: this, text: properties.labelsIngraphSpecific[i], index: i, value: this.data[i], decimals: properties.labelsIngraphSpecificFormattedDecimals || 0, unitsPre: properties.labelsIngraphSpecificFormattedUnitsPre || '', unitsPost: properties.labelsIngraphSpecificFormattedUnitsPost || '', thousand: properties.labelsIngraphSpecificFormattedThousand || ',', point: properties.labelsIngraphSpecificFormattedPoint || '.' }); // Use the value } else if (RGraph.isNumber(this.data[i]) ) { var text = RGraph.numberFormat({ object: this, number: this.data[i].toFixed(properties.labelsIngraphDecimals), unitspre: properties.labelsIngraphUnitsPre, unitspost: properties.labelsIngraphUnitsPost, point: properties.labelsIngraphPoint, thousand: properties.labelsIngraphThousand }); } if (text) { this.context.beginPath(); var textConf = RGraph.getTextConf({ object: this, prefix: 'labelsIngraph' }); ///////////////////////////////////////////// // // // Determine if the text will fit into the // // segment // // // ///////////////////////////////////////////// var ret = RGraph.text({ object: this, font: textConf.font, size: textConf.size, color: 'transparent', // bold: textConf.bold, italic: textConf.italic, x: x, y: y, text: text, valign: 'center', halign: 'center', bounding: false }); this.context.stroke(); this.path( 'b lw 1 m % % a % % % % % false', this.centerx, this.centery, this.centerx, this.centery, this.radius + 10, this.angles[i][0], this.angles[i][1] ); // Account for this being a donut chart if (this.properties.variant.indexOf('donut') >= 0) { this.path( 'a % % % % % true', this.centerx, this.centery, this.radius - (this.properties.variantDonutWidth || this.radius / 2), this.angles[i][1], this.angles[i][0] ); } let [textX, textY, textW, textH] = [ret.x, ret.y, ret.width, ret.height]; if ( this.properties.labelsIngraphUndrawnAlwaysShow === false && ( !this.context.isPointInPath(textX, textY) || !this.context.isPointInPath(textX + textW, textY) || !this.context.isPointInPath(textX, textY + textH) || !this.context.isPointInPath(textX + textW, textY + textH) ) ) { // This clears any existing path this.context.beginPath(); var undrawn = { index: i, text: text }; this.get('labelsIngraphUndrawn').push(undrawn); // If undrawn ingraph labels are wanted to // be set as labels instead - add this text // to the Pie chart labels property if (properties.labelsIngraphUndrawn && properties.labelsIngraphUndrawnAsLabels) { if (!RGraph.isArray(this.properties.labels)) { this.properties.labels = []; } this.properties.labels[undrawn.index] = undrawn.text; if (!RGraph.Registry.get('pie-chart-ingraphlabels-redraw-function-added')) { var id = RGraph.addCustomEventListener(this, 'draw', function (obj) { // Now that we're in the // function that runs to // reenable the labelsingraph // labels - remove it RGraph.removeCustomEventListener( obj, RGraph.Registry.get('pie-chart-ingraphlabels-redraw-function-added') ); // Redraw the canvas - but only // once RGraph.runOnce('pie-chart-ingraphlabels-redraw-function', function () { RGraph.redraw(); }); }); RGraph.Registry.set('pie-chart-ingraphlabels-redraw-function-added', id); } } continue; } // This clears any existing path this.context.beginPath(); ////////////////////////////////////////////////////////////////// var ret = RGraph.text({ object: this, font: textConf.font, size: textConf.size, color: textConf.color, bold: textConf.bold, italic: textConf.italic, x: x, y: y, text: text, valign: 'center', halign: 'center', bounding: properties.labelsIngraphBounding, boundingFill: properties.labelsIngraphBoundingFill, boundingStroke: properties.labelsIngraphBoundingStroke, tag: 'labels.ingraph' }); this.context.stroke(); } } }; // // Draws the center label if required // this.drawCenterLabel = function (label) { var textConf = RGraph.getTextConf({ object: this, prefix: 'labelsCenter' }); RGraph.text({ object: this, font: textConf.font, size: textConf.size, color: textConf.color, bold: textConf.bold, italic: textConf.italic, x: this.centerx + properties.labelsCenterOffsetx, y: this.centery + properties.labelsCenterOffsety, halign: 'center', valign: 'center', text: label, bounding: true, boundingFill: 'rgba(255,255,255,0.7)', boundingStroke: 'rgba(0,0,0,0)', tag: 'labels.center' }); }; // // This returns the angle for a value based around the maximum number // // @param number value The value to get the angle for // this.getAngle = function (value) { if (value > this.total) { return null; } var angle = (value / this.total) * RGraph.TWOPI; // Handle the origin (it can br -HALFPI or 0) angle += properties.origin; return angle; }; // // This allows for easy specification of gradients // this.parseColors = function () { // Save the original colors so that they can be restored when the canvas is reset if (this.original_colors.length === 0) { this.original_colors.colors = RGraph.arrayClone(properties.colors); this.original_colors.keyColors = RGraph.arrayClone(properties.keyColors); this.original_colors.colorsStroke = RGraph.arrayClone(properties.colorsStroke); this.original_colors.highlightStroke = RGraph.arrayClone(properties.highlightStroke); this.original_colors.highlightStyleTwodFill = RGraph.arrayClone(properties.highlightStyleTwodFill); this.original_colors.highlightStyleTwodStroke = RGraph.arrayClone(properties.highlightStyleTwodStroke); this.original_colors.labelsIngraphBoundingFill = RGraph.arrayClone(properties.labelsIngraphBoundingFill); this.original_colors.labelsIngraphColor = RGraph.arrayClone(properties.labelsIngraphColor); } for (var i=0; i<properties.colors.length; ++i) { properties.colors[i] = this.parseSingleColorForGradient(properties.colors[i]); } var keyColors = properties.keyColors; if (keyColors) { for (var i=0; i<keyColors.length; ++i) { keyColors[i] = this.parseSingleColorForGradient(keyColors[i]); } } properties.colorsStroke = this.parseSingleColorForGradient(properties.colorsStroke); properties.highlightStroke = this.parseSingleColorForGradient(properties.highlightStroke); properties.highlightStyleTwodFill = this.parseSingleColorForGradient(properties.highlightStyleTwodFill); properties.highlightStyleTwodStroke = this.parseSingleColorForGradient(properties.highlightStyleTwodStroke); properties.labelsIngraphBoundingFill = this.parseSingleColorForGradient(properties.labelsIngraphBoundingFill); properties.labelsIngraphColor = this.parseSingleColorForGradient(properties.labelsIngraphColor); }; // // Use this function to reset the object to the post-constructor state. Eg reset colors if // need be etc // this.reset = function () { }; // // This parses a single color value // this.parseSingleColorForGradient = function (color) { if (!color || typeof color != 'string') { return color; } if (color.match(/^gradient\((.*)\)$/i)) { // Allow for JSON gradients if (color.match(/^gradient\(({.*})\)$/i)) { return RGraph.parseJSONGradient({object: this, def: RegExp.$1}); } var parts = RegExp.$1.split(':'); // If the chart is a donut - the first width should half the total radius if (properties.variant == 'donut') { var radius_start = typeof properties.variantDonutWidth == 'number' ? this.radius - properties.variantDonutWidth : this.radius / 2; } else { var radius_start = 0; } // Create the gradient var grad = this.context.createRadialGradient( this.centerx, this.centery, radius_start, this.centerx, this.centery, Math.min(this.canvas.width - properties.marginLeft - properties.marginRight, this.canvas.height - properties.marginTop - properties.marginBottom) / 2 ); var diff = 1 / (parts.length - 1); grad.addColorStop(0, RGraph.trim(parts[0])); for (var j=1; j<parts.length; ++j) { grad.addColorStop(j * diff, RGraph.trim(parts[j])); } } return grad ? grad : color; }; // // This function handles highlighting an entire data-series for the interactive // key // // @param int index The index of the data series to be highlighted // this.interactiveKeyHighlight = function (index) { if (this.angles && this.angles[index]) { var segment = this.angles[index]; var x = segment[2]; var y = segment[3]; var start = segment[0]; var end = segment[1]; this.context.strokeStyle = properties.keyInteractiveHighlightChartStroke; this.context.fillStyle = properties.keyInteractiveHighlightChartFill; this.context.lineWidth = properties.keyInteractiveHighlightChartLinewidth; this.context.lineJoin = 'bevel'; this.context.beginPath(); this.context.moveTo(x, y); this.context.arc(x, y, this.radius, start, end, false); this.context.closePath(); this.context.fill(); this.context.stroke(); } }; // // Using a function to add events makes it easier to facilitate method chaining // // @param string type The type of even to add // @param function func // this.on = function (type, func) { if (type.substr(0,2) !== 'on') { type = 'on' + type; } if (typeof this[type] !== 'function') { this[type] = func; } else { RGraph.addCustomEventListener(this, type, func); } return this; }; // // This function runs once only // (put at the end of the file (before any effects)) // this.firstDrawFunc = function () { }; // // Draw a 3D Pie/Donut chart // this.draw3d = function () { var scaleX = 1.5, depth = properties.variantThreedDepth, prop_shadow = properties.shadow, prop_labels = properties.labels, prop_labelsSticks = properties.labelsSticks, prop_title = properties.title; this.set({ labels: [], labelsSticks: false, strokestyle: 'rgba(0,0,0,0)', title: '' }); // // Change the variant so that the draw function doesn't // keep coming in here // this.set({ variant: this.get('variant').replace(/3d/, '') }); this.context.setTransform(scaleX, 0, 0, 1, (this.canvas.width * (scaleX) - this.canvas.width) * -0.5, 0); for (var i=depth; i>0; i-=1) { this.set({ centeryAdjust: i }); if (i === parseInt(depth / 2) ) { this.set({ labels: prop_labels, labelsSticks: prop_labelsSticks }); } if (i === 0) { this.set({ shadow: prop_shadow }); } if (i === 1) { this.set({ title: prop_title }); } this.draw(); // Turn off the shadow after the bottom pie/donut has // been drawn this.set({ shadow: false, title: '' }); // // If on the middle pie/donut turn the labels and sticks off // if (i <= parseInt(depth / 2) ) { this.set({ labels: [], labelsSticks: false }); } // // Make what we're drawng darker by going over // it in a semi-transparent dark color // if (i > 1) { if (properties.variant.indexOf('donut') !== -1) { for (var j=0; j<this.angles.length; ++j) { var x = this.angles[j][2]; var y = this.angles[j][3]; var r1 = this.radius; var r2 = this.radius / 2; var a1 = this.angles[j][0]; var a2 = this.angles[j][1]; this.path( 'b a % % % % % false a % % % % % true f rgba(0,0,0,0.15)', x, y, r1, a1, a2, x, y, r2, a2, a1 ); } // Draw the pie chart darkened segments } else { for (var j=0; j<this.angles.length; ++j) { var x = this.angles[j][2]; var y = this.angles[j][3]; var r1 = this.radius; var r2 = this.radius / 2; var a1 = this.angles[j][0]; var a2 = this.angles[j][1]; this.path( 'b m % % a % % % % % false f rgba(0,0,0,0.15)', x, y, x, y, r1 + 1, a1, a2 ); } } } } // // Reset the variant by adding the 3d back on // this.set({ variant: this.get('variant') + '3d', shadow: prop_shadow, labels: prop_labels, labelsSticks: prop_labelsSticks, title: prop_title }); // Necessary to allow method chaining return this; }; // // Pie chart explode effect // // Explodes the Pie chart - gradually incrementing the // size of the explode property // // @param object Options for the effect // @param function An optional callback function to call when the animation completes // this.explode = function () { // Cancel any stop request if one is pending this.cancelStopAnimation(); var obj = this; var opt = arguments[0] ? arguments[0] : {}; var callback = arguments[1] ? arguments[1] : function () {}; var frames = opt.frames ? opt.frames : 30; var frame = 0; var maxExplode = Number(typeof opt.radius === 'number' ? opt.radius : Math.max(this.canvas.width, this.canvas.height)); var currentExplode = Number(obj.get('exploded')) || 0; if (currentExplode === maxExplode) { currentExplode = 0; this.set('exploded', 0); } var step = (maxExplode - currentExplode) / frames; // Lose the labels this.set('labels', null); // exploded option var iterator = function () { if (obj.stopAnimationRequested) { // Reset the flag obj.stopAnimationRequested = false; return; } obj.set('exploded', currentExplode + (step * frame) ); RGraph.clear(obj.canvas); RGraph.redrawCanvas(obj.canvas); if (frame++ < frames) { RGraph.Effects.updateCanvas(iterator); } else { callback(obj); } } iterator(); return this; }; // // Pie chart grow effect // // Gradually increases the pie chart radius // // @param object OPTIONAL An object of options // @param function OPTIONAL A callback function // this.grow = function () { // Cancel any stop request if one is pending this.cancelStopAnimation(); var obj = this; var canvas = obj.canvas; var opt = arguments[0] ? arguments[0] : {}; var frames = opt.frames || 30; var frame = 0; var callback = arguments[1] ? arguments[1] : function () {}; var radius = obj.getRadius(); properties.radius = 0; var iterator = function () { if (obj.stopAnimationRequested) { // Reset the flag obj.stopAnimationRequested = false; return; } obj.set('radius', (frame / frames) * radius); RGraph.redrawCanvas(canvas); if (frame++ < frames) { RGraph.Effects.updateCanvas(iterator); } else { RGraph.redrawCanvas(obj.canvas); callback(obj); } }; iterator(); return this; }; // // Pie chart wave effect // // Grows the Pie chart segment by segment in a wave pattern // - gradually incrementing the size of the wave property // // @param object Options for the effect // @param function An optional callback function to call when the animation completes // this.wave = function () { // Cancel any stop request if one is pending this.cancelStopAnimation(); // If there's only one bar call the grow function instead if (this.data.length === 1) { return this.grow(arguments[0], arguments[1]); } var obj = this, opt = arguments[0] || {}; opt.frames = opt.frames || 60; opt.startFrames = []; opt.counters = []; var framespersegment = opt.frames / 2, frame = -1, callback = arguments[1] || function () {}, original = RGraph.arrayClone(this.originalData); // // turn off the labels option whilst animating // var originalLabels = this.get('labels'); this.set('labels', false); for (var i=0,len=obj.data.length; i<len; i+=1) { opt.startFrames[i] = ((opt.frames / 2) / (obj.data.length - 1)) * i; opt.counters[i] = 0; } // Initialise all of the wave multipliers to zero for (let i=0; i<this.data.length; ++i) { this.waveRadiusMultiplier[i] = 0; } RGraph.clear(obj.canvas); function iterator () { if (obj.stopAnimationRequested) { // Reset the flag obj.stopAnimationRequested = false; // Reset the data obj.data = RGraph.arrayClone(obj.originalData); return; } ++frame; for (let i=0,len=obj.data.length; i<len; i+=1) { if (frame > opt.startFrames[i]) { obj.waveRadiusMultiplier[i] = Math.min(1, (opt.counters[i] + 1) / framespersegment); opt.counters[i]++; } } if (frame >= opt.frames) { obj.set('labels', originalLabels); obj.waveRadiusMultiplier = RGraph.arrayFill([], obj.originalData.length, 1); RGraph.redrawCanvas(obj.canvas); callback(obj); } else { RGraph.redrawCanvas(obj.canvas); RGraph.Effects.updateCanvas(iterator); } } iterator(); return this; }; // // Pie chart RoundRobin effect // // This effect does two things: // 1. Gradually increases the size of each segment // 2. Gradually increases the size of the radius from 0 // // @param object OPTIONAL Options for the effect // @param function OPTIONAL A callback function // this.roundrobin = this.roundRobin = function () { // Cancel any stop request if one is pending this.cancelStopAnimation(); var obj = this, opt = arguments[0] || {}, callback = arguments[1] || function () {}, frame = 0, frames = opt.frames || 30, radius = obj.getRadius(), labels = obj.get('labels') obj.set('events', false); obj.set('labels', []); var iterator = function () { if (obj.stopAnimationRequested) { // Reset the flag obj.stopAnimationRequested = false; return; } obj.set( 'effectRoundrobinMultiplier', RGraph.Effects.getEasingMultiplier(frames, frame) ); RGraph.redrawCanvas(obj.canvas); if (frame < frames) { RGraph.Effects.updateCanvas(iterator); frame++; } else { obj.set({ events: true, labels: labels }); RGraph.redrawCanvas(obj.canvas); callback(obj); } }; iterator(); return this; }; // // Pie chart roundRobinSequential effect // // This function does similar to the above roundRobin // function but increases the segment sizes sequentially // instead of all at once. // // @param object OPTIONAL Options for the effect // @param function OPTIONAL A callback function // this.roundRobinSequential = function () { var args = RGraph.getArgs(arguments, 'options,callback'); // Cancel any stop request if one is pending this.cancelStopAnimation(); var obj = this, opt = args.options || {}, callback = args.callback || function () {}, frame = 0, frames = opt.frames || 60, radius = this.getRadius(), labels = this.get('labels') this.set('events', false); this.set('labels', []); var radius = Math.max( this.canvas.width, this.canvas.height ) * 2; var iterator = function () { if (obj.stopAnimationRequested) { // Reset the flag obj.stopAnimationRequested = false; return; } // Redraw the Pie/donut chart RGraph.redrawCanvas(obj.canvas); // Draw the cover over the canvas obj.path( 'b m % % a % % % % % false f white', obj.centerx, obj.centery, obj.centerx, obj.centery, radius, (0 - RGraph.HALFPI) + ((frame / frames) * RGraph.TWOPI), RGraph.TWOPI - RGraph.HALFPI ); if (frame < frames) { RGraph.Effects.updateCanvas(iterator); frame++; } else { obj.set({ events: true, labels: labels }); RGraph.redrawCanvas(obj.canvas); callback(obj); } }; iterator(); return this; }; // // Pie chart implode effect // // Implodes the Pie chart - gradually decreasing the size of the exploded property. It starts at the largest of // the canvas width./height // // @param object Optional options for the effect. You can pass in frames here - such as: // myPie.implode({frames: 60}; function () {alert('Done!');}) // @param function A callback function which is called when the effect is finished // this.implode = function () { // Cancel any stop request if one is pending this.cancelStopAnimation(); var obj = this, opt = arguments[0] || {}, callback = arguments[1] || function (){}, frames = opt.frames || 30, frame = 0, current_exploded = Number(this.get('exploded')); explodedMax = Math.max(this.canvas.width, this.canvas.height), explodedMax = Number(this.get('exploded')) || explodedMax; if (Number(this.get('exploded')) === explodedMax) { this.set('exploded', 0); current_exploded = 0; } function iterator () { if (obj.stopAnimationRequested) { // Reset the flag obj.stopAnimationRequested = false; return; } exploded = explodedMax - ((frame / frames) * explodedMax); // Set the new value obj.set('exploded', exploded); RGraph.clear(obj.canvas); RGraph.redrawCanvas(obj.canvas); if (frame++ < frames) { RGraph.Effects.updateCanvas(iterator); } else { RGraph.clear(obj.canvas); RGraph.redrawCanvas(obj.canvas); callback(obj); } } iterator(); return this; }; // // Couple of functions that allow you to control the // animation effect // this.stopAnimation = function () { this.stopAnimationRequested = true; }; this.cancelStopAnimation = function () { this.stopAnimationRequested = false; }; // // A worker function that handles Bar chart specific tooltip substitutions // this.tooltipSubstitutions = function (opt) { return { index: opt.index, dataset: 0, sequentialIndex: opt.index, value: this.data[opt.index], values: [this.data[opt.index]] }; }; // // A worker function that returns the correct color/label/value // // @param object specific The indexes that are applicable // @param number index The appropriate index // this.tooltipsFormattedCustom = function (specific, index, colors) { var color = colors[specific.index]; var label = ( (typeof properties.tooltipsFormattedKeyLabels === 'object' && typeof properties.tooltipsFormattedKeyLabels[specific.index] === 'string') ? properties.tooltipsFormattedKeyLabels[specific.index] : ''); return { color: color, label: label }; }; // // This allows for static tooltip positioning // this.positionTooltipStatic = function (args) { var obj = args.object, e = args.event, tooltip = args.tooltip, index = args.index, canvasXY = RGraph.getCanvasXY(obj.canvas) segment = this.angles[args.index], angle = ((segment[1] - segment[0]) / 2) + segment[0], multiplier = 0.5; // // Determine the correct radius to use when calculating the // coordinates of the tooltip // if (properties.variant.indexOf('donut') >= 0) { // Determine the radius if (RGraph.isNullish(properties.variantDonutWidth)) { var radius = (this.radius / 2) + (this.radius / 4); } else { var radius = (this.radius - properties.variantDonutWidth) + (properties.variantDonutWidth / 2); } } else { var radius = this.radius * multiplier; } var explosion = typeof properties.exploded == 'number' ? properties.exploded : properties.exploded[index]; var endpoint = RGraph.getRadiusEndPoint( this.centerx, this.centery, angle, radius + (explosion || 0) ); // Allow for the 3D stretching of the canvas if (properties.variant.indexOf('3d') > 0) { var width = properties.variantDonutWidth === 'number' ? properties.variantDonutWidth : this.radius / 2; endpoint[0] = (endpoint[0] - this.centerx) * 1.5 + this.centerx; } // Position the tooltip in the X direction args.tooltip.style.left = ( canvasXY[0] // The X coordinate of the canvas + endpoint[0] // The X coordinate of the shape on the chart - (tooltip.offsetWidth / 2) // Subtract half of the tooltip width + obj.properties.tooltipsOffsetx // Add any user defined offset ) + 'px'; args.tooltip.style.top = ( canvasXY[1] // The Y coordinate of the canvas + endpoint[1] // The Y coordinate of the shape on the chart - tooltip.offsetHeight // The height of the tooltip + obj.properties.tooltipsOffsety // Add any user defined offset - 10 // Account for the pointer ) + 'px'; }; // // This returns the relevant value for the formatted key // macro %{value}. THIS VALUE SHOULD NOT BE FORMATTED. // // @param number index The index in the dataset to get // the value for // this.getKeyValue = function (index) { if ( RGraph.isArray(this.properties.keyFormattedValueSpecific) && RGraph.isNumber(this.properties.keyFormattedValueSpecific[index])) { return this.properties.keyFormattedValueSpecific[index]; } else { return this.data[index]; } }; // // Returns how many data-points there should be when a string // based key property has been specified. For example, this: // // key: '%{property:_labels[%{index}]} %{value_formatted}' // // ...depending on how many bits of data ther is might get // turned into this: // // key: [ // '%{property:_labels[%{index}]} %{value_formatted}', // '%{property:_labels[%{index}]} %{value_formatted}', // '%{property:_labels[%{index}]} %{value_formatted}', // '%{property:_labels[%{index}]} %{value_formatted}', // '%{property:_labels[%{index}]} %{value_formatted}', // ] // // ... ie in that case there would be 4 data-points so the // template is repeated 4 times. // this.getKeyNumDatapoints = function () { return this.data.length; }; // // Now need to register all chart types. MUST be after the setters/getters are defined // RGraph.register(this); // // This is the 'end' of the constructor so if the first argument // contains configuration data - handle that. // RGraph.parseObjectStyleConfig(this, conf.options); };