// 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 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 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 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 (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 (-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=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 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 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 -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 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; i0; 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