// version: 2017-11-25 /** * o--------------------------------------------------------------------------------o * | This file is part of the RGraph package - you can learn more at: | * | | * | http://www.rgraph.net | * | | * | RGraph is licensed under the Open Source MIT license. That means that it's | * | totally free to use and there are no restrictions on what you can do with it! | * o--------------------------------------------------------------------------------o */ RGraph = window.RGraph || {isRGraph: true}; /** * The pie chart constructor * * @param data array The data to be represented on the Pie chart */ RGraph.Pie = function (conf) { /** * Allow for object config style */ if ( typeof conf === 'object' && typeof conf.data === 'object' && typeof conf.id === 'string') { var id = conf.id, canvas = document.getElementById(id), data = conf.data, parseConfObjectForOptions = true; // Set this so the config is parsed (at the end of the constructor } else { var id = conf, canvas = document.getElementById(id), data = arguments[1]; } // 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.properties = []; this.type = 'pie'; this.isRGraph = 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; // // Go through the data and convert strings to numbers // for (var i=0; i<this.data.length; ++i) { if (typeof this.data[i] === 'string') { this.data[i] = parseFloat(this.data[i]); } } this.properties = { 'chart.centerx.adjust': 0, 'chart.centery.adjust': 0, 'chart.colors': ['red', '#ccc', '#cfc', 'blue', 'pink', 'yellow', 'black', 'orange', 'cyan', 'purple', '#78CAEA', '#E284E9', 'white', 'blue', '#9E7BF6'], 'chart.strokestyle': 'white', 'chart.linewidth': 3, 'chart.labels': [], 'chart.labels.bold': false, 'chart.labels.sticks': false, 'chart.labels.sticks.length': 7, 'chart.labels.sticks.colors': null, 'chart.labels.sticks.usecolors': false, 'chart.labels.sticks.linewidth': 1, 'chart.labels.sticks.hlength': 5, 'chart.labels.sticks.list': false, 'chart.labels.ingraph': null, 'chart.labels.ingraph.color': null, 'chart.labels.ingraph.font': null, 'chart.labels.ingraph.size': null, 'chart.labels.ingraph.bounding':true, 'chart.labels.ingraph.bounding.fill':'white', 'chart.labels.ingraph.specific':null, 'chart.labels.ingraph.units.pre':'', 'chart.labels.ingraph.units.post':'', 'chart.labels.ingraph.radius': null, 'chart.labels.center': null, 'chart.labels.center.size': 26, 'chart.labels.center.font': 'Segoe UI, Arial, Verdana, sans-serif', 'chart.labels.center.color': 'black', 'chart.labels.center.italic': false, 'chart.labels.center.bold': false, 'chart.labels.center.units.pre': '', 'chart.labels.center.units.post': '', 'chart.gutter.left': 25, 'chart.gutter.right': 25, 'chart.gutter.top': 25, 'chart.gutter.bottom': 25, 'chart.title': '', 'chart.title.background': null, 'chart.title.hpos': null, 'chart.title.vpos': 0.5, 'chart.title.bold': true, 'chart.title.font': null, 'chart.title.x': null, 'chart.title.y': null, 'chart.title.halign': null, 'chart.title.valign': null, 'chart.shadow': true, 'chart.shadow.color': '#aaa', 'chart.shadow.offsetx': 0, 'chart.shadow.offsety': 0, 'chart.shadow.blur': 15, 'chart.text.size': 12, 'chart.text.color': 'black', 'chart.text.font': 'Segoe UI, Arial, Verdana, sans-serif', 'chart.text.accessible': true, 'chart.text.accessible.overflow': 'visible', 'chart.text.accessible.pointerevents': true, 'chart.contextmenu': null, 'chart.tooltips': null, 'chart.tooltips.event': 'onclick', 'chart.tooltips.effect': 'fade', 'chart.tooltips.css.class': 'RGraph_tooltip', 'chart.tooltips.highlight': true, 'chart.highlight.style': '2d', 'chart.highlight.style.twod.fill': 'rgba(255,255,255,0.7)', 'chart.highlight.style.twod.stroke': 'rgba(255,255,255,0.7)', 'chart.highlight.style.outline.width': null, 'chart.centerx': null, 'chart.centery': null, 'chart.radius': null, 'chart.border': false, 'chart.border.color': 'rgba(255,255,255,0.5)', 'chart.key': null, 'chart.key.background': 'white', 'chart.key.position': 'graph', 'chart.key.halign': 'right', 'chart.key.shadow': false, 'chart.key.shadow.color': '#666', 'chart.key.shadow.blur': 3, 'chart.key.shadow.offsetx': 2, 'chart.key.shadow.offsety': 2, 'chart.key.position.gutter.boxed': false, 'chart.key.position.x': null, 'chart.key.position.y': null, 'chart.key.color.shape': 'square', 'chart.key.rounded': true, 'chart.key.linewidth': 1, 'chart.key.colors': null, 'chart.key.interactive': false, 'chart.key.interactive.highlight.chart.stroke': 'black', 'chart.key.interactive.highlight.chart.fill': 'rgba(255,255,255,0.7)', 'chart.key.interactive.highlight.label': 'rgba(255,0,0,0.2)', 'chart.key.text.color': 'black', 'chart.annotatable': false, 'chart.annotate.color': 'black', 'chart.zoom.factor': 1.5, 'chart.zoom.fade.in': true, 'chart.zoom.fade.out': true, 'chart.zoom.hdir': 'right', 'chart.zoom.vdir': 'down', 'chart.zoom.frames': 25, 'chart.zoom.delay': 16.666, 'chart.zoom.shadow': true, 'chart.zoom.background': true, 'chart.zoom.action': 'zoom', 'chart.resizable': false, 'chart.resize.handle.adjust': [0,0], 'chart.resize.handle.background': null, 'chart.variant': 'pie', 'chart.variant.donut.width': null, 'chart.variant.threed.depth': 20, 'chart.exploded': [], 'chart.effect.roundrobin.multiplier': 1, 'chart.events.click': null, 'chart.events.mousemove': null, 'chart.centerpin': null, 'chart.centerpin.fill': 'gray', 'chart.centerpin.stroke': 'white', 'chart.origin': 0 - (Math.PI / 2), 'chart.events': true, 'chart.labels.colors': [], 'chart.clearto': 'rgba(0,0,0,0)' } /** * 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] = {}; } /** * Translate half a pixel for antialiasing purposes - but only if it hasn't beeen * done already */ if (!this.canvas.__rgraph_aa_translated__) { this.context.translate(0.5,0.5); this.canvas.__rgraph_aa_translated__ = true; } // Short variable names var RG = RGraph, ca = this.canvas, co = ca.getContext('2d'), prop = this.properties, pa2 = RG.path2, win = window, doc = document, ma = Math /** * "Decorate" the object with the generic effects if the effects library has been included */ if (RG.Effects && typeof RG.Effects.decorate === 'function') { RG.Effects.decorate(this); } /** * A generic setter */ this.set = this.Set = function (name) { var value = typeof arguments[1] === 'undefined' ? null : arguments[1]; /** * the number of arguments is only one and it's an * object - parse it for configuration data and return. */ if (arguments.length === 1 && typeof name === 'object') { RG.parseObjectStyleConfig(this, name); return this; } /** * This should be done first - prepend the property name with "chart." if necessary */ if (name.substr(0,6) != 'chart.') { name = 'chart.' + name; } // Convert uppercase letters to dot+lower case letter while(name.match(/([A-Z])/)) { name = name.replace(/([A-Z])/, '.' + RegExp.$1.toLowerCase()); } if (name == 'chart.highlight.style.twod.color') { name = 'chart.highlight.style.twod.fill'; } if (name == 'chart.labels.spaced') { name = 'chart.labels.sticks.list'; } prop[name] = value; return this; }; /** * A generic getter */ this.get = this.Get = function (name) { /** * This should be done first - prepend the property name with "chart." if necessary */ if (name.substr(0,6) != 'chart.') { name = 'chart.' + name; } // Convert uppercase letters to dot+lower case letter while(name.match(/([A-Z])/)) { name = name.replace(/([A-Z])/, '.' + RegExp.$1.toLowerCase()); } if (name == 'chart.highlight.style.twod.color') { name = 'chart.highlight.style.twod.fill'; } return prop[name]; }; /** * This draws the pie chart */ this.draw = this.Draw = function () { /** * Fire the onbeforedraw event */ RG.FireCustomEvent(this, 'onbeforedraw'); // NB: Colors are parsed further down so that the center X/Y can be used /** * This is new in May 2011 and facilitates indiviual gutter settings, * eg chart.gutter.left */ this.gutterLeft = prop['chart.gutter.left']; this.gutterRight = prop['chart.gutter.right']; this.gutterTop = prop['chart.gutter.top']; this.gutterBottom = prop['chart.gutter.bottom']; this.radius = this.getRadius();// MUST be first this.centerx = (this.graph.width / 2) + this.gutterLeft + prop['chart.centerx.adjust']; this.centery = (this.graph.height / 2) + this.gutterTop + prop['chart.centery.adjust']; this.subTotal = this.properties['chart.origin']; this.angles = []; this.coordsText = []; /** * Allow specification of a custom radius & center X/Y */ if (typeof prop['chart.radius'] === 'number') this.radius = prop['chart.radius']; if (typeof prop['chart.centerx'] === 'number') this.centerx = prop['chart.centerx']; if (typeof prop['chart.centery'] === 'number') this.centery = prop['chart.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; } /** * This sets the label colors. Doing it here saves lots of if() conditions in the draw method */ if (prop['chart.labels.colors'].length < prop['chart.labels'].length) { while (prop['chart.labels.colors'].length < prop['chart.labels'].length) { prop['chart.labels.colors'].push(prop['chart.labels.colors'][prop['chart.labels.colors'].length - 1]); } } else { if (typeof prop['chart.labels.colors'] === 'undefined') { prop['chart.labels.colors'] = []; } while (prop['chart.labels.colors'].length < prop['chart.labels'].length) { prop['chart.labels.colors'].push(prop['chart.text.color']); } } if (prop['chart.variant'].indexOf('3d') > 0) { return this.draw3d(); } /** * Draw the title */ RG.DrawTitle( this, prop['chart.title'], (ca.height / 2) - this.radius - 5, this.centerx, prop['chart.title.size'] ? prop['chart.title.size'] : prop['chart.text.size'] + 2 ); /** * Draw the shadow if required * * ??? */ //if (prop['chart.shadow'] && false) { // // var offsetx = doc.all ? prop['chart.shadow.offsetx'] : 0; // var offsety = doc.all ? prop['chart.shadow.offsety'] : 0; // // co.beginPath(); // co.fillStyle = prop['chart.shadow.color']; // // co.shadowColor = prop['chart.shadow.color']; // co.shadowBlur = prop['chart.shadow.blur']; // co.shadowOffsetX = prop['chart.shadow.offsetx']; // co.shadowOffsetY = prop['chart.shadow.offsety']; // // co.arc(this.centerx + offsetx, this.centery + offsety, this.radius, 0, TWOPI, 0); // // co.fill(); // // // Now turn off the shadow // RG.NoShadow(this); //} /** * The total of the array of values */ this.total = RG.array_sum(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) * RG.TWOPI); // Draw the segment this.DrawSegment(angle,prop['chart.colors'][i],i == (len - 1), i); } RG.NoShadow(this); /** * Redraw the seperating lines */ if (prop['chart.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++) { co.beginPath(); var segment = this.angles[i]; if (action === 1) { co.strokeStyle = typeof(prop['chart.strokestyle']) == 'object' ? prop['chart.strokestyle'][i] : prop['chart.strokestyle']; } prop['chart.colors'][i] ? co.fillStyle = prop['chart.colors'][i] : null; co.lineJoin = 'round'; co.arc(segment[2], segment[3], r, (segment[0]), (segment[1]), false); if (prop['chart.variant'] == 'donut') { co.arc( segment[2], segment[3], typeof(prop['chart.variant.donut.width']) == 'number' ? r - prop['chart.variant.donut.width'] : r / 2, (segment[1]), (segment[0]), true ); } else { co.lineTo(segment[2], segment[3]); } co.closePath(); action === 0 ? co.fill() : co.stroke(); } } /** * Draw label sticks */ if (prop['chart.labels.sticks']) { this.DrawSticks(); // Redraw the border going around the Pie chart if the stroke style is NOT white var strokeStyle = prop['chart.strokestyle']; } /** * Draw the labels */ if (prop['chart.labels']) { this.DrawLabels(); } /** * Draw centerpin if requested */ if (prop['chart.centerpin']) { this.DrawCenterpin(); } /** * Draw ingraph labels */ if (prop['chart.labels.ingraph']) { this.DrawInGraphLabels(); } /** * Draw the center label if requested */ if (!RG.isNull(prop['chart.labels.center'])) { this.drawCenterLabel(prop['chart.labels.center']); } /** * Setup the context menu if required */ if (prop['chart.contextmenu']) { RG.ShowContext(this); } /** * If a border is pecified, draw it */ if (prop['chart.border']) { co.beginPath(); co.lineWidth = 5; co.strokeStyle = prop['chart.border.color']; co.arc(this.centerx, this.centery, this.radius - 2, 0, RG.TWOPI, 0); co.stroke(); } /** * Draw the kay if desired */ if (prop['chart.key'] && prop['chart.key'].length) { RG.DrawKey(this, prop['chart.key'], prop['chart.colors']); } RG.NoShadow(this); /** * This function enables resizing */ if (prop['chart.resizable']) { RG.AllowResizing(this); } /** * This installs the event listeners */ if (prop['chart.events'] == true) { RG.InstallEventListeners(this); } /** * Fire the onfirstdraw event */ if (this.firstDraw) { this.firstDraw = false; RG.fireCustomEvent(this, 'onfirstdraw'); this.firstDrawFunc(); } /** * Fire the RGraph ondraw event */ RG.FireCustomEvent(this, 'ondraw'); 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 a single segment of the pie chart * * @param int degrees The number of degrees for this segment */ this.drawSegment = this.DrawSegment = function (radians, color, last, index) { // IE7/8/ExCanvas fix (when there's only one segment the Pie chart doesn't display if (RGraph.ISOLD && radians == RG.TWOPI) { radians -= 0.0001; } else if (RGraph.ISOLD && radians == 0) { radians = 0.001; } var subTotal = this.subTotal; radians = radians * prop['chart.effect.roundrobin.multiplier']; co.beginPath(); color ? co.fillStyle = color : null; co.strokeStyle = prop['chart.strokestyle']; co.lineWidth = 0; if (prop['chart.shadow']) { RG.setShadow( this, prop['chart.shadow.color'], prop['chart.shadow.offsetx'], prop['chart.shadow.offsety'], prop['chart.shadow.blur'] ); } /** * Exploded segments */ if ( (typeof(prop['chart.exploded']) == 'object' && prop['chart.exploded'][index] > 0) || typeof(prop['chart.exploded']) == 'number') { var explosion = typeof(prop['chart.exploded']) == 'number' ? prop['chart.exploded'] : prop['chart.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); var r = this.radius; co.moveTo(this.centerx + x, this.centery + y); } else { var x = 0; var y = 0; var r = this.radius; } /** * Calculate the angles */ var startAngle = subTotal; var endAngle = ((subTotal + radians)); co.arc(this.centerx + x, this.centery + y, r, startAngle, endAngle, 0); if (prop['chart.variant'] == 'donut') { co.arc(this.centerx + x, this.centery + y, typeof(prop['chart.variant.donut.width']) == 'number' ? r - prop['chart.variant.donut.width'] : r / 2, endAngle, startAngle, true); } else { co.lineTo(this.centerx + x, this.centery + y); } co.closePath(); // Keep hold of the angles this.angles.push([subTotal, subTotal + radians, this.centerx + x, this.centery + y]); //co.stroke(); co.fill(); /** * Calculate the segment angle */ this.subTotal += radians; }; /** * Draws the graphs labels */ this.drawLabels = this.DrawLabels = function () { // New way of spacing labels out if (prop['chart.labels'].length && prop['chart.labels.sticks.list']) { return this.drawLabelsList(); } var hAlignment = 'left', vAlignment = 'center', labels = prop['chart.labels'], context = co, font = prop['chart.text.font'], bold = prop['chart.labels.bold'], text_size = prop['chart.text.size'], cx = this.centerx, cy = this.centery, r = this.radius; /** * Turn the shadow off */ RG.noShadow(this); co.fillStyle = 'black'; co.beginPath(); /** * Draw the labels */ if (labels && labels.length) { 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 co.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 prop['chart.exploded'] === 'object' && prop['chart.exploded'][i] || typeof prop['chart.exploded'] == 'number') { var t = ((segment[1] - segment[0]) / 2), seperation = typeof(prop['chart.exploded']) == 'number' ? prop['chart.exploded'] : prop['chart.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 (prop['chart.labels.sticks']) { explosion_offsetx += (ma.cos(angle) * (typeof prop['chart.labels.sticks.length'] === 'object' ? prop['chart.labels.sticks.length'][i] : prop['chart.labels.sticks.length']) ); explosion_offsety += (ma.sin(angle) * (typeof prop['chart.labels.sticks.length'] === 'object' ? prop['chart.labels.sticks.length'][i] : prop['chart.labels.sticks.length']) ); } /** * Coords for the text */ var x = cx + explosion_offsetx + ((r + 10)* Math.cos(a)) + (prop['chart.labels.sticks'] ? (a < RG.HALFPI || a > (RG.TWOPI + RG.HALFPI) ? 2 : -2) : 0), y = cy + explosion_offsety + (((r + 10) * 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'; co.fillStyle = prop['chart.text.color']; if ( typeof prop['chart.labels.colors'] === 'object' && prop['chart.labels.colors'] && prop['chart.labels.colors'][i]) { co.fillStyle = prop['chart.labels.colors'][i]; } RG.text2(this, { font: font, size: text_size, x: x, y: y, text: labels[i], valign: vAlignment, halign: hAlignment, tag: 'labels', bold: bold, color: prop['chart.labels.sticks.usecolors'] ? prop['chart.colors'][i] : 'black' }); } co.fill(); } }; // // A new way of spacing out labels // this.drawLabelsList = function () { var segment = this.angles[i], labels = prop['chart.labels'], labels_right = [], labels_left = [], text_font = prop['chart.text.font'], text_size = prop['chart.text.size'], text_color = prop['chart.text.color'], left = [], right = [], centerx = this.centerx, centery = this.centery, radius = this.radius, offset = 50 // // Draw the right hand side labels first // for (var i=0; i<this.angles.length; ++i) { var angle = this.angles[i][0] + ((this.angles[i][1] - this.angles[i][0]) / 2), // Midpoint endpoint_inner = RG.getRadiusEndPoint(centerx, centery, angle, radius + 5), endpoint_outer = RG.getRadiusEndPoint(centerx, centery, angle, radius + 10), explosion = [ (typeof prop['chart.exploded'] === 'number' ? prop['chart.exploded'] : prop['chart.exploded'][i]), (ma.cos(angle) * (typeof prop['chart.exploded'] === 'number' ? prop['chart.exploded'] : prop['chart.exploded'][i])), (ma.sin(angle) * (typeof prop['chart.exploded'] === 'number' ? prop['chart.exploded'] : prop['chart.exploded'][i])) ] // // Work out the color // if ( typeof prop['chart.labels.sticks.colors'] === 'object' && prop['chart.labels.sticks.colors'] && prop['chart.labels.sticks.colors'][i] ) { var color = prop['chart.labels.sticks.colors'][i]; } else if ( prop['chart.labels.sticks.usecolors'] && prop['chart.colors'][i] ) { var color = prop['chart.colors'][i]; } else { var color = prop['chart.text.color']; } if (angle > (-1 * RG.HALFPI) && angle < RG.HALFPI) { labels_right.push([ i, angle, labels[i] ? labels[i] : '', endpoint_inner, endpoint_outer, color, RG.arrayClone(explosion) ]); } else { labels_left.push([ i, angle, labels[i] ? labels[i] : '', endpoint_inner, endpoint_outer, color, RG.arrayClone(explosion) ]); } } // // Draw the right hand side labels first // // Calculate how much space there is for each label var vspace_right = (ca.height - prop['chart.gutter.top'] - prop['chart.gutter.bottom']) / labels_right.length for (var i=0,y=(prop['chart.gutter.top'] + (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 = RG.text2(this, { font: text_font, size: text_size, x: x + explosionX, y: y + explosionY, text: labels_right[i][2], valign: 'center', halign: 'left', tag: 'labels', color: labels_right[i][5] }); if (ret && ret.node) { ret.node.__index__ = labels_right[i][0]; } pa2(co, 'lc round lw % b m % % l % % l % % l % % s %', prop['chart.labels.sticks.linewidth'], labels_right[i][3][0] + explosionX, labels_right[i][3][1] + explosionY, labels_right[i][4][0] + explosionX, labels_right[i][4][1] + explosionY, this.centerx + this.radius + 25 + explosionX, ma.round(labels_right[i][4][1] + explosionY), 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 = (ca.height - prop['chart.gutter.top'] - prop['chart.gutter.bottom']) / labels_left.length for (var i=(labels_left.length - 1),y=(prop['chart.gutter.top'] + (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 = RG.text2(this, { font: text_font, size: text_size, x: x + explosionX, y: y + explosionY, text: labels_left[i][2], valign: 'center', halign: 'right', tag: 'labels', color: labels_left[i][5] }); if (ret && ret.node) { ret.node.__index__ = labels_left[i][0]; } pa2(co, 'lw % b m % % l % % l % % l % % s %', prop['chart.labels.sticks.linewidth'], labels_left[i][3][0] + explosionX, labels_left[i][3][1] + explosionY, labels_left[i][4][0] + explosionX, labels_left[i][4][1] + explosionY, this.centerx - this.radius - 25 + explosionX, ma.round(labels_left[i][4][1] + explosionY), ret.x + 5 + ret.width, ret.y + (ret.height / 2), labels_left[i][5] ); } } }; /** * This function draws the pie chart sticks (for the labels) */ this.drawSticks = this.DrawSticks = function () { var offset = prop['chart.linewidth'] / 2, exploded = prop['chart.exploded'], sticks = prop['chart.labels.sticks'], colors = prop['chart.colors'], cx = this.centerx, cy = this.centery, radius = this.radius, points = [], linewidth = prop['chart.labels.sticks.linewidth'] for (var i=0,len=this.angles.length; i<len; ++i) { var segment = this.angles[i]; // This allows the chart.labels.sticks to be an array as well as a boolean if (typeof sticks === 'object' && !sticks[i]) { continue; } var radians = segment[1] - segment[0]; co.beginPath(); co.strokeStyle = typeof prop['chart.labels.sticks.colors'] === 'string' ? prop['chart.labels.sticks.colors'] : (!RG.isNull(prop['chart.labels.sticks.colors']) ? prop['chart.labels.sticks.colors'][i] : 'gray'); co.lineWidth = linewidth; if (typeof prop['chart.labels.sticks.color'] === 'string') { co.strokeStyle = prop['chart.labels.sticks.color']; } // // Allow for labelsSticksUseColors // if (prop['chart.labels.sticks.usecolors']) { co.strokeStyle = prop['chart.colors'][i]; } 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 prop['chart.labels.sticks.length'] === 'object' ? prop['chart.labels.sticks.length'][i] : prop['chart.labels.sticks.length']; points[0] = RG.getRadiusEndPoint(cx, cy, midpoint, radius + extra + offset); points[1] = RG.getRadiusEndPoint(cx, cy, midpoint, radius + stickLength + extra - 5); points[2] = RG.getRadiusEndPoint(cx, cy, midpoint, radius + stickLength + extra); points[3] = RG.getRadiusEndPoint(cx, cy, midpoint, radius + stickLength + extra); points[3][0] += (points[3][0] > cx ? 5 : -5); points[4] = [ points[2][0] + (points[2][0] > cx ? 5 + prop['chart.labels.sticks.hlength'] : -5 - prop['chart.labels.sticks.hlength']), points[2][1] ]; co.moveTo(points[0][0], points[0][1]); co.quadraticCurveTo(points[2][0], points[2][1], points[4][0], points[4][1]); co.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 = this.getSegment = function (e) { RG.FixEventObject(e); // The optional arg provides a way of allowing some accuracy (pixels) var accuracy = arguments[1] ? arguments[1] : 0; var canvas = ca; var context = co; var mouseCoords = RG.getMouseXY(e); var mouseX = mouseCoords[0]; var mouseY = mouseCoords[1]; var r = this.radius; var angles = this.angles; var ret = []; for (var i=0,len=angles.length; i<len; ++i) { // DRAW THE SEGMENT AGAIN SO IT CAN BE TESTED ////////////////////////// co.beginPath(); co.strokeStyle = 'rgba(0,0,0,0)'; co.arc(angles[i][2], angles[i][3], this.radius, angles[i][0], angles[i][1], false); if (this.type == 'pie' && prop['chart.variant'] == 'donut') { co.arc(angles[i][2], angles[i][3], (typeof(prop['chart.variant.donut.width']) == 'number' ? this.radius - prop['chart.variant.donut.width'] : this.radius / 2), angles[i][1], angles[i][0], true); } else { co.lineTo(angles[i][2], angles[i][3]); } co.closePath(); if (!co.isPointInPath(mouseX, mouseY)) { continue; } //////////////////////////////////////////////////////////////////////// ret[0] = angles[i][2]; ret[1] = angles[i][3]; ret[2] = this.radius; ret[3] = angles[i][0] - RG.TWOPI; ret[4] = angles[i][1]; ret[5] = i; if (ret[3] < 0) ret[3] += RG.TWOPI; if (ret[4] > RG.TWOPI) ret[4] -= RG.TWOPI; /** * Add the tooltip to the returned shape */ var tooltip = RG.parseTooltipText ? RG.parseTooltipText(prop['chart.tooltips'], ret[5]) : null; /** * Now return textual keys as well as numerics */ ret['object'] = this; ret['x'] = ret[0]; ret['y'] = ret[1]; ret['radius'] = ret[2]; ret['angle.start'] = ret[3]; ret['angle.end'] = ret[4]; ret['index'] = ret[5]; ret['tooltip'] = tooltip; return ret; } return null; }; this.drawBorders = this.DrawBorders = function () { if (prop['chart.linewidth'] > 0) { co.lineWidth = prop['chart.linewidth']; co.strokeStyle = prop['chart.strokestyle']; var r = this.radius; for (var i=0,len=this.angles.length; i<len; ++i) { var segment = this.angles[i]; co.beginPath(); co.arc(segment[2], segment[3], r, (segment[0]), (segment[0] + 0.001), 0); co.arc(segment[2], segment[3], prop['chart.variant'] == 'donut' ? (typeof(prop['chart.variant.donut.width']) == 'number' ? this.radius - prop['chart.variant.donut.width'] : r / 2): r, segment[0], segment[0] + 0.0001, 0); co.closePath(); co.stroke(); } } }; /** * Returns the radius of the pie chart * * [06-02-2012] Maintained for compatibility ONLY. */ this.getRadius = function () { this.graph = {width: ca.width - prop['chart.gutter.left'] - prop['chart.gutter.right'], height: ca.height - prop['chart.gutter.top'] - prop['chart.gutter.bottom']} if (typeof(prop['chart.radius']) == 'number') { this.radius = prop['chart.radius']; } else { this.radius = Math.min(this.graph.width, this.graph.height) / 2; } 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 = this.Explode = function (index, size) { if (typeof this.exploding === 'number' && this.exploding === index) { return; } //this.Set('chart.exploded', []); if (!prop['chart.exploded']) { prop['chart.exploded'] = []; } // If chart.exploded is a number - convert it to an array if (typeof(prop['chart.exploded']) == 'number') { var original_explode = prop['chart.exploded']; var exploded = prop['chart.exploded']; prop['chart.exploded'] = []; for (var i=0,len=this.data.length; i<len; ++i) { prop['chart.exploded'][i] = exploded; } } prop['chart.exploded'][index] = typeof(original_explode) == 'number' ? original_explode : 0; this.exploding = index; var delay = RG.ISIE && !RG.ISIE10 ? 25 : 16.666; for (var o=0; o<size; ++o) { setTimeout( function () { prop['chart.exploded'][index] += 1; RG.Clear(ca); RG.RedrawCanvas(ca); }, o * delay); } var obj = this; setTimeout(function () { obj.exploding = null; }, 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) { co.beginPath(); co.strokeStyle = prop['chart.highlight.style.twod.stroke']; co.fillStyle = prop['chart.highlight.style.twod.fill']; co.moveTo(segment[0], segment[1]); co.arc(segment[0], segment[1], segment[2], this.angles[segment[5]][0], this.angles[segment[5]][1], 0); co.lineTo(segment[0], segment[1]); co.closePath(); co.stroke(); co.fill(); }; /** * Each object type has its own Highlight() function which highlights * the appropriate shape * * @param object shape The shape to highlight */ this.highlight = this.Highlight = function (shape) { if (prop['chart.tooltips.highlight']) { if (typeof prop['chart.highlight.style'] === 'function') { (prop['chart.highlight.style'])(shape); /** * 3D style of highlighting */ } else if (prop['chart.highlight.style'] == '3d') { co.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 co.beginPath(); RG.NoShadow(this); co.fillStyle = 'rgba(0,0,0,0)'; co.arc(shape['x'], shape['y'], shape['radius'], shape['angle.start'], shape['angle.end'], false); if (prop['chart.variant'] == 'donut') { co.arc(shape['x'], shape['y'], shape['radius'] / 5, shape['angle.end'], shape['angle.start'], true); } else { co.lineTo(shape['x'], shape['y']); } co.closePath(); co.fill(); // Draw the new segment co.beginPath(); co.shadowColor = '#666'; co.shadowBlur = 3; co.shadowOffsetX = 3; co.shadowOffsetY = 3; co.fillStyle = prop['chart.colors'][shape['index']]; co.strokeStyle = prop['chart.strokestyle']; co.arc(shape['x'] - extent, shape['y'] - extent, shape['radius'], shape['angle.start'], shape['angle.end'], false); if (prop['chart.variant'] == 'donut') { co.arc(shape['x'] - extent, shape['y'] - extent, shape['radius'] / 2, shape['angle.end'], shape['angle.start'], true) } else { co.lineTo(shape['x'] - extent, shape['y'] - extent); } co.closePath(); co.stroke(); co.fill(); // Turn off the shadow RG.NoShadow(this); /** * If a border is defined, redraw that */ if (prop['chart.border']) { co.beginPath(); co.strokeStyle = prop['chart.border.color']; co.lineWidth = 5; co.arc(shape['x'] - extent, shape['y'] - extent, shape['radius'] - 2, shape['angle.start'], shape['angle.end'], false); co.stroke(); } // Outline style of highlighting } else if (prop['chart.highlight.style'] === 'outline') { var tooltip = RG.Registry.get('chart.tooltip'), index = tooltip.__index__, coords = this.angles[index], color = this.get('colors')[index] width = this.radius / 12.5; // Allow custom setting of outline if (typeof prop['chart.highlight.style.outline.width'] === 'number') { width = prop['chart.highlight.style.outline.width']; } RGraph.path2( co, '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 { co.beginPath(); co.strokeStyle = prop['chart.highlight.style.twod.stroke']; co.fillStyle = prop['chart.highlight.style.twod.fill']; if (prop['chart.variant'].indexOf('donut') > -1) { co.arc(shape['x'], shape['y'], shape['radius'], shape['angle.start'], shape['angle.end'], false); co.arc(shape['x'], shape['y'], typeof(prop['chart.variant.donut.width']) == 'number' ? this.radius - prop['chart.variant.donut.width'] : shape['radius'] / 2, shape['angle.end'], shape['angle.start'], true); } else { co.arc(shape['x'], shape['y'], shape['radius'] + 1, shape['angle.start'], shape['angle.end'], false); co.lineTo(shape['x'], shape['y']); } co.closePath(); co.stroke(); co.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 = this.DrawCenterpin = function () { if (typeof(prop['chart.centerpin']) == 'number' && prop['chart.centerpin'] > 0) { var cx = this.centerx; var cy = this.centery; co.beginPath(); co.strokeStyle = prop['chart.centerpin.stroke'] ? prop['chart.centerpin.stroke'] : prop['chart.strokestyle']; co.fillStyle = prop['chart.centerpin.fill'] ? prop['chart.centerpin.fill'] : prop['chart.strokestyle']; co.moveTo(cx, cy); co.arc(cx, cy, prop['chart.centerpin'], 0, RG.TWOPI, false); co.stroke(); co.fill(); } }; /** * This function positions a tooltip when it is displayed * * @param obj object The chart object * @param int x The X coordinate specified for the tooltip * @param int y The Y coordinate specified for the tooltip * @param objec tooltip The tooltips DIV element * this.positionTooltip = function (obj, x, y, tooltip, idx) { var coordX = obj.angles[idx][2]; var coordY = obj.angles[idx][3]; var mouseXY = RG.getMouseXY(window.event); var angleStart = obj.angles[idx][0]; var angleEnd = obj.angles[idx][1]; var angleCenter = ((angleEnd - angleStart) / 2) + angleStart; var canvasXY = RGraph.getCanvasXY(obj.canvas); var gutterLeft = prop['chart.gutter.left']; var gutterTop = prop['chart.gutter.top']; var width = tooltip.offsetWidth; var height = tooltip.offsetHeight; var x = canvasXY[0] + this.angles[idx][2] + (Math.cos(angleCenter) * (prop['chart.variant'] == 'donut' && typeof(prop['chart.variant.donut.width']) == 'number' ? ((this.radius - prop['chart.variant.donut.width']) + (prop['chart.variant.donut.width'] / 2)) : (this.radius * 0.5))); var y = canvasXY[1] + this.angles[idx][3] + (Math.sin(angleCenter) * (prop['chart.variant'] == 'donut' && typeof(prop['chart.variant.donut.width']) == 'number' ? ((this.radius - prop['chart.variant.donut.width']) + (prop['chart.variant.donut.width'] / 2)) : (this.radius * 0.5))); // By default any overflow is hidden tooltip.style.overflow = ''; // Set the top position tooltip.style.left = 0; tooltip.style.top = window.event.pageY - height - 5 + 'px'; // Reposition the tooltip if at the edges: // LEFT edge if (canvasXY[0] + mouseXY[0] - (width / 2) < 0) { tooltip.style.left = canvasXY[0] + mouseXY[0] - (width * 0.1) + 'px'; // RIGHT edge } else if (canvasXY[0] + mouseXY[0] + (width / 2) > doc.body.offsetWidth) { tooltip.style.left = canvasXY[0] + mouseXY[0] - (width * 0.9) + 'px'; // Default positioning - CENTERED } else { tooltip.style.left = canvasXY[0] + mouseXY[0] - (width / 2) + 'px'; } };*/ /** * This draws Ingraph labels */ this.drawInGraphLabels = this.DrawInGraphLabels = function () { var context = co; var cx = this.centerx; var cy = this.centery; var radius = prop['chart.labels.ingraph.radius']; // // Is the radius less than 2? If so then it's a factor and not n exact point // if (radius <= 2 && radius > 0) { radiusFactor = radius; } else { radiusFactor = 0.5; } if (prop['chart.variant'] == 'donut') { var r = this.radius * (0.5 + (radiusFactor * 0.5)); if (typeof(prop['chart.variant.donut.width']) == 'number') { var r = (this.radius - prop['chart.variant.donut.width']) + (prop['chart.variant.donut.width'] / 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(prop['chart.exploded']) == 'object' && typeof(prop['chart.exploded'][i]) == 'number') { var explosion = prop['chart.exploded'][i]; } else if (typeof(prop['chart.exploded']) == 'number') { var explosion = parseInt(prop['chart.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 = RG.getRadiusEndPoint( this.centerx, this.centery, angleCenter, r + (explosion ? explosion : 0) ); var x = coords[0]; var y = coords[1]; var text = prop['chart.labels.ingraph.specific'] && typeof(prop['chart.labels.ingraph.specific'][i]) == 'string' ? prop['chart.labels.ingraph.specific'][i] : RG.number_format(this, this.data[i], prop['chart.labels.ingraph.units.pre'] , prop['chart.labels.ingraph.units.post']); if (text) { co.beginPath(); var font = typeof prop['chart.labels.ingraph.font'] === 'string' ? prop['chart.labels.ingraph.font'] : prop['chart.text.font']; var size = typeof prop['chart.labels.ingraph.size'] === 'number' ? prop['chart.labels.ingraph.size'] : prop['chart.text.size'] + 2; // // Set the colors // co.fillStyle = prop['chart.labels.ingraph.color'] ? prop['chart.labels.ingraph.color'] : 'black'; RG.Text2(this, { 'font':font, 'size':size, 'x':x, 'y':y, 'text':text, 'valign':'center', 'halign':'center', 'bounding': prop['chart.labels.ingraph.bounding'], 'bounding.fill': prop['chart.labels.ingraph.bounding.fill'], 'tag':'labels.ingraph' }); co.stroke(); } } }; // // Draws the center label if required // this.drawCenterLabel = function (label) { var font = prop['chart.labels.center.font'], size = prop['chart.labels.center.size'], color = prop['chart.labels.center.color'], unitsPre = prop['chart.labels.center.units.pre'], unitsPost = prop['chart.labels.center.units.post'], bold = prop['chart.labels.center.bold'], italic = prop['chart.labels.center.italic']; RG.text2(this, { color: color, bold: bold, italic: italic, font: font, size: size, x: this.centerx, y: this.centery, halign: 'center', valign: 'center', text: RG.numberFormat(this, label,unitsPre, unitsPost) }); } /** * 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) * RG.TWOPI; // Handle the origin (it can br -HALFPI or 0) angle += prop['chart.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['chart.colors'] = RG.arrayClone(prop['chart.colors']); this.original_colors['chart.key.colors'] = RG.arrayClone(prop['chart.key.colors']); this.original_colors['chart.strokestyle'] = RG.arrayClone(prop['chart.strokestyle']); this.original_colors['chart.highlight.stroke'] = RG.arrayClone(prop['chart.highlight.stroke']); this.original_colors['chart.highlight.style.twod.fill'] = RG.arrayClone(prop['chart.highlight.style.twod.fill']); this.original_colors['chart.highlight.style.twod.stroke'] = RG.arrayClone(prop['chart.highlight.style.twod.stroke']); this.original_colors['chart.ingraph.bounding.fill'] = RG.arrayClone(prop['chart.ingraph.bounding.fill']); this.original_colors['chart.ingraph.color'] = RG.arrayClone(prop['chart.ingraph.color']); } for (var i=0; i<prop['chart.colors'].length; ++i) { prop['chart.colors'][i] = this.parseSingleColorForGradient(prop['chart.colors'][i]); } var keyColors = prop['chart.key.colors']; if (keyColors) { for (var i=0; i<keyColors.length; ++i) { keyColors[i] = this.parseSingleColorForGradient(keyColors[i]); } } prop['chart.strokestyle'] = this.parseSingleColorForGradient(prop['chart.strokestyle']); prop['chart.highlight.stroke'] = this.parseSingleColorForGradient(prop['chart.highlight.stroke']); prop['chart.highlight.style.twod.fill'] = this.parseSingleColorForGradient(prop['chart.highlight.style.twod.fill']); prop['chart.highlight.style.twod.stroke'] = this.parseSingleColorForGradient(prop['chart.highlight.style.twod.stroke']); prop['chart.labels.ingraph.bounding.fill'] = this.parseSingleColorForGradient(prop['chart.labels.ingraph.bounding.fill']); prop['chart.labels.ingraph.color'] = this.parseSingleColorForGradient(prop['chart.labels.ingraph.color']); }; /** * 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)) { var parts = RegExp.$1.split(':'); // If the chart is a donut - the first width should half the total radius if (prop['chart.variant'] == 'donut') { var radius_start = typeof(prop['chart.variant.donut.width']) == 'number' ? this.radius - prop['chart.variant.donut.width'] : this.radius / 2; } else { var radius_start = 0; } // Create the gradient var grad = co.createRadialGradient(this.centerx, this.centery, radius_start, this.centerx, this.centery, Math.min(ca.width - prop['chart.gutter.left'] - prop['chart.gutter.right'], ca.height - prop['chart.gutter.top'] - prop['chart.gutter.bottom']) / 2); var diff = 1 / (parts.length - 1); grad.addColorStop(0, RG.trim(parts[0])); for (var j=1; j<parts.length; ++j) { grad.addColorStop(j * diff, RG.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]; co.strokeStyle = prop['chart.key.interactive.highlight.chart.stroke']; co.fillStyle = prop['chart.key.interactive.highlight.chart.fill']; co.lineWidth = 2; co.lineJoin = 'bevel'; co.beginPath(); co.moveTo(x, y); co.arc(x, y, this.radius, start, end, false); co.closePath(); co.fill(); co.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 { RG.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 = prop['chart.variant.threed.depth'], prop_shadow = prop['chart.shadow'], prop_labels = prop['chart.labels'], prop_labelsSticks = prop['chart.labels.sticks'] this.set({ labels: [], labelsSticks: false, strokestyle: 'rgba(0,0,0,0)' }); // // 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, (ca.width * (scaleX) - ca.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 }); } this.draw(); // Turn off the shadow after the bottom pie/donut has // been drawn this.set('shadow', false); // // 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 (prop['chart.variant'].indexOf('donut') !== -1) { for (var j=0; j<this.angles.length; ++j) { pa2(co,[ 'b', 'a', this.angles[j][2], this.angles[j][3], this.radius + 1, this.angles[j][0], this.angles[j][1] * prop['chart.effect.roundrobin.multiplier'], false, 'a', this.angles[j][2], this.angles[j][3], this.radius / 2, this.angles[j][1] * prop['chart.effect.roundrobin.multiplier'], this.angles[j][0], true, 'f', 'rgba(0,0,0,0.15)' ]); } // Draw the pie chart darkened segments } else { for (var j=0; j<this.angles.length; ++j) { pa2(co,[ 'b', 'm', this.angles[j][2], this.angles[j][3], 'a', this.angles[j][2], this.angles[j][3], this.radius + 1, this.angles[j][0], this.angles[j][1] * prop['chart.effect.roundrobin.multiplier'], false, 'c', 'f', 'rgba(0,0,0,0.15)' ]); } } } } // // 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 }); // Necessary to allow method chaining return this; }; /** * Pie chart explode * * Explodes the Pie chart - gradually incrementing the size of the chart.explode property * * @param object Options for the effect * @param function An optional callback function to call when the animation completes */ this.explode = function () { 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 : ma.max(ca.width, ca.height)); var currentExplode = Number(obj.get('exploded')) || 0; var step = (maxExplode - currentExplode) / frames; // chart.exploded var iterator = function () { 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 * * Gradually increases the pie chart radius * * @param object OPTIONAL An object of options * @param function OPTIONAL A callback function */ this.grow = function () { 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(); prop['chart.radius'] = 0; var iterator = function () { obj.set('chart.radius', (frame / frames) * radius); RG.redrawCanvas(ca); if (frame++ < frames) { RG.Effects.updateCanvas(iterator); } else { RG.redrawCanvas(obj.canvas); callback(obj); } }; iterator(); return this; }; /** * RoundRobin * * 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 () { 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('chart.events', false); obj.Set('chart.labels', []); var iterator = function () { obj.set( 'effect.roundrobin.multiplier', RG.Effects.getEasingMultiplier(frames, frame) ); RGraph.redrawCanvas(ca); if (frame++ < frames) { RGraph.Effects.updateCanvas(iterator); } else { obj.set({ events: true, labels: labels }); RG.redrawCanvas(obj.canvas); callback(obj); } }; iterator(); return this; }; RG.att(ca); /** * Pie chart implode * * Implodes the Pie chart - gradually decreasing the size of the chart.explode 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 () { var obj = this, opt = arguments[0] || {}, callback = arguments[1] || function (){}, frames = opt.frames || 30, frame = 0, explodedMax = ma.max(ca.width, ca.height), exploded = explodedMax; function iterator () { exploded = explodedMax - ((frame / frames) * explodedMax); // Set the new value obj.Set('exploded', exploded); RG.clear(ca); RG.redrawCanvas(ca); if (frame++ < frames) { RG.Effects.updateCanvas(iterator); } else { RG.clear(obj.canvas); RG.redrawCanvas(obj.canvas); callback(obj); } } iterator(); return this; }; /** * Now need to register all chart types. MUST be after the setters/getters are defined */ RG.register(this); /** * This is the 'end' of the constructor so if the first argument * contains configuration data - handle that. */ if (parseConfObjectForOptions) { RG.parseObjectStyleConfig(this, conf.options); } };