// version: 2014-08-16 /** * o--------------------------------------------------------------------------------o * | This file is part of the RGraph package - you can learn more at: | * | | * | http://www.rgraph.net | * | | * | This package is licensed under the Creative Commons BY-NC license. That means | * | that for non-commercial purposes it's free to use and for business use there's | * | a 99 GBP per-company fee to pay. You can read the full license here: | * | | * | http://www.rgraph.net/license | * o--------------------------------------------------------------------------------o */ RGraph = window.RGraph || {isRGraph: true}; /** * The traditional radar chart constructor * * @param string id The ID of the canvas * @param array data An array of data to represent */ RGraph.Radar = function (conf) { /** * Allow for object config style */ if ( typeof conf === 'object' && typeof conf.data === 'object' && typeof conf.id === 'string') { var parseConfObjectForOptions = true; // Set this so the config is parsed (at the end of the constructor) // Turn conf.data into a multi-d array if it's not already if (typeof conf.data[0] === 'number') { conf.data = [conf.data]; } } else { var conf = {id: conf, data: []}; // Arguments style: var foo = new RGraph.Radar('cvs', [1,2,3], [1,2,3], [1,2,3]); if (typeof arguments[1] === 'object' && typeof arguments[1][0] === 'number') { for (var i=1; i 0) { // This goes back to the start coords of this particular dataset co.lineTo(coords_dataset[0][0], coords_dataset[0][1]); //Now move down to the end point of the previous dataset co.moveTo(last_coords[0][0], last_coords[0][1]); for (var i=coords_dataset.length - 1; i>=0; --i) { co.lineTo(last_coords[i][0], last_coords[i][1]); } } // This is used by the next iteration of the loop var last_coords = coords_dataset; co.closePath(); co.stroke(); co.fill(); } // Reset the globalAlpha if (typeof(alpha) == 'number') { co.globalAlpha = oldAlpha; } }; /** * Gets the coordinates for a particular mark * * @param number i The index of the data (ie which one it is) * @return array A two element array of the coordinates */ this.getCoordinates = this.GetCoordinates = function (dataset, index) { // The number of data points var len = this.data[dataset].length; // The magnitude of the data (NOT the x/y coords) var mag = (this.data[dataset][index] / this.max) * this.radius; /** * Get the angle */ var angle = (RG.TWOPI / len) * index; // In radians angle -= RG.HALFPI; /** * Work out the X/Y coordinates */ var x = Math.cos(angle) * mag; var y = Math.sin(angle) * mag; /** * Put the coordinate in the right quadrant */ x = this.centerx + x; y = this.centery + y; return [x,y]; }; /** * This function adds the labels to the chart */ this.drawLabels = this.DrawLabels = function () { var labels = prop['chart.labels']; if (labels && labels.length > 0) { co.lineWidth = 1; co.strokeStyle = 'gray'; co.fillStyle = prop['chart.text.color']; var bgFill = prop['chart.labels.background.fill']; var bold = prop['chart.labels.bold']; var bgBoxed = prop['chart.labels.boxed']; var offset = prop['chart.labels.offset']; var font = prop['chart.text.font']; var size = prop['chart.text.size']; var radius = this.radius; for (var i=0; i -1) { for (var i=0; i -1) { for (var i=0; i -1) { for (var i=0; i -1) { for (var i=0; i -1) RG.Text2(this, {'tag': 'labels.specific', 'bold':reversed_bold[i],'font':font,'size':size,'x':this.centerx,'y':this.centery - this.radius + ((this.radius / labels.length) * i),'text':reversed_labels[i],'valign':'center','halign':'center','bounding':reversed_boxed[i],'boundingFill':'white'}); if (axes.indexOf('s') > -1) RG.Text2(this, {'tag': 'labels.specific', 'bold':bold[i],'font':font,'size':size,'x':this.centerx,'y':this.centery + ((this.radius / labels.length) * (i+1)),'text':labels[i],'valign':'center','halign':'center','bounding':boxed[i],'boundingFill':'white'}); if (axes.indexOf('w') > -1) RG.Text2(this, {'tag': 'labels.specific', 'bold':reversed_bold[i],'font':font,'size':size,'x':this.centerx - this.radius + ((this.radius / labels.length) * i),'y':this.centery,'text':reversed_labels[i],'valign':'center','halign':'center','bounding':reversed_boxed[i],'boundingFill':'white'}); if (axes.indexOf('e') > -1) RG.Text2(this, {'tag': 'labels.specific', 'bold':bold[i],'font':font,'size':size,'x':this.centerx + ((this.radius / labels.length) * (i+1)),'y':this.centery,'text':labels[i],'valign':'center','halign':'center','bounding':boxed[i],'boundingFill':'white'}); } }; /** * This method eases getting the focussed point (if any) * * @param event e The event object */ this.getShape = this.getPoint = function (e) { for (var i=0; i (x - 5) && mouseY > (y - 5) && mouseY < (y + 5) ) { var tooltip = RG.parseTooltipText(prop['chart.tooltips'], index); return {0: this, 'object': this, 1: x, 'x': x, 2: y, 'y': y, 3: null, 'dataset': null, 4: index, 'index': i, 'tooltip': tooltip } } } }; /** * 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) { // Add the new highlight RG.Highlight.Point(this, shape); }; /** * The getObjectByXY() worker method. Don't call this call: * * RGraph.ObjectRegistry.getObjectByXY(e) * * @param object e The event object */ this.getObjectByXY = function (e) { var mouseXY = RG.getMouseXY(e); if ( mouseXY[0] > (this.centerx - this.radius) && mouseXY[0] < (this.centerx + this.radius) && mouseXY[1] > (this.centery - this.radius) && mouseXY[1] < (this.centery + this.radius) ) { return this; } }; /** * 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 dataset = tooltip.__dataset__; var index = tooltip.__index__; var coordX = this.coords[index][0]; var coordY = this.coords[index][1]; var canvasXY = RG.getCanvasXY(obj.canvas); var gutterLeft = this.gutterLeft; var gutterTop = this.gutterTop; var width = tooltip.offsetWidth; // Set the top position tooltip.style.left = 0; tooltip.style.top = parseInt(tooltip.style.top) - 9 + 'px'; // By default any overflow is hidden tooltip.style.overflow = ''; // The arrow var img = new Image(); img.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABEAAAAFCAYAAACjKgd3AAAARUlEQVQYV2NkQAN79+797+RkhC4M5+/bd47B2dmZEVkBCgcmgcsgbAaA9GA1BCSBbhAuA/AagmwQPgMIGgIzCD0M0AMMAEFVIAa6UQgcAAAAAElFTkSuQmCC'; img.style.position = 'absolute'; img.id = '__rgraph_tooltip_pointer__'; img.style.top = (tooltip.offsetHeight - 2) + 'px'; tooltip.appendChild(img); // Reposition the tooltip if at the edges: // LEFT edge if ((canvasXY[0] + coordX - (width / 2)) < 10) { tooltip.style.left = (canvasXY[0] + coordX - (width * 0.1)) + 'px'; img.style.left = ((width * 0.1) - 8.5) + 'px'; // RIGHT edge } else if ((canvasXY[0] + coordX + (width / 2)) > doc.body.offsetWidth) { tooltip.style.left = canvasXY[0] + coordX - (width * 0.9) + 'px'; img.style.left = ((width * 0.9) - 8.5) + 'px'; // Default positioning - CENTERED } else { tooltip.style.left = (canvasXY[0] + coordX - (width * 0.5)) + 'px'; img.style.left = ((width * 0.5) - 8.5) + 'px'; } }; /** * This draws highlights on the points */ this.drawHighlights = this.DrawHighlights = function () { if (prop['chart.highlights']) { var sequentialIdx = 0; var dataset = 0; var index = 0; var radius = prop['chart.highlights.radius']; for (var dataset=0; dataset this.max) { return null; } // Radar doesn't support minimum value var radius = (value / this.max) * this.radius; return radius; }; /** * This function returns the angle (in radians) for a particular index. * * @param number numitems The total number of items * @param number index The zero index number of the item to get the angle for */ this.getAngle = function (numitems, index) { var angle = (RG.TWOPI / numitems) * index; angle -= RG.HALFPI; 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.array_clone(prop['chart.colors']); this.original_colors['chart.key.colors'] = RG.array_clone(prop['chart.key.colors']); this.original_colors['chart.title.color'] = RG.array_clone(prop['chart.title.color']); this.original_colors['chart.text.color'] = RG.array_clone(prop['chart.text.color']); this.original_colors['chart.highlight.stroke'] = RG.array_clone(prop['chart.highlight.stroke']); this.original_colors['chart.highlight.fill'] = RG.array_clone(prop['chart.highlight.fill']); this.original_colors['chart.circle.fill'] = RG.array_clone(prop['chart.circle.fill']); this.original_colors['chart.circle.stroke'] = RG.array_clone(prop['chart.circle.stroke']); } for (var i=0; i=0; --dataset) { // Draw the path again so that it can be checked co.beginPath(); co.moveTo(obj.coords2[dataset][0][0], obj.coords2[dataset][0][1]); for (var j=0; j 0) { co.lineTo(obj.coords2[dataset - 1][0][0], obj.coords2[dataset - 1][0][1]); for (var j=(obj.coords2[dataset - 1].length - 1); j>=0; --j) { co.lineTo(obj.coords2[dataset - 1][j][0], obj.coords2[dataset - 1][j][1]); } } co.closePath(); if (co.isPointInPath(mouseXY[0], mouseXY[1])) { var inPath = true; break; } } // Call the events if (inPath) { var fillTooltips = prop['chart.fill.tooltips']; /** * Click event */ if (e.type == 'click') { if (prop['chart.fill.click']) { prop['chart.fill.click'](e, dataset); } if (prop['chart.fill.tooltips'] && prop['chart.fill.tooltips'][dataset]) { obj.DatasetTooltip(e, dataset); } } /** * Mousemove event */ if (e.type == 'mousemove') { if (prop['chart.fill.mousemove']) { prop['chart.fill.mousemove'](e, dataset); } if (!RG.is_null(fillTooltips)) { e.target.style.cursor = 'pointer'; } if (prop['chart.fill.tooltips'] && prop['chart.fill.tooltips'][dataset]) { e.target.style.cursor = 'pointer'; } } e.stopPropagation(); } else if (e.type == 'mousemove') { ca.style.cursor = 'default'; } }; /** * Add the click listener */ if (prop['chart.fill.click'] || !RG.is_null(prop['chart.fill.tooltips'])) { ca.addEventListener('click', func, false); } /** * Add the mousemove listener */ if (prop['chart.fill.mousemove'] || !RG.is_null(prop['chart.fill.tooltips'])) { ca.addEventListener('mousemove', func, false); } }; /** * This highlights a specific dataset on the chart * * @param number dataset The index of the dataset (which starts at zero) */ this.highlightDataset = this.HighlightDataset = function (dataset) { co.beginPath(); for (var j=0; j 0) { co.lineTo(this.coords2[dataset - 1][0][0], this.coords2[dataset - 1][0][1]); for (var j=(this.coords2[dataset - 1].length - 1); j>=0; --j) { co.lineTo(this.coords2[dataset - 1][j][0], this.coords2[dataset - 1][j][1]); } } co.strokeStyle = prop['chart.fill.highlight.stroke']; co.fillStyle = prop['chart.fill.highlight.fill']; co.stroke(); co.fill(); }; /** * Shows a tooltip for a dataset (a "fill" tooltip), You can pecify these * with chart.fill.tooltips */ this.datasetTooltip = this.DatasetTooltip = function (e, dataset) { // Highlight the dataset this.HighlightDataset(dataset); // Use the First datapoints coords for the Y position of the tooltip NOTE The X position is changed in the // obj.positionTooltip() method so set the index to be the first one var text = prop['chart.fill.tooltips'][dataset]; var x = 0; var y = this.coords2[dataset][0][1] + RG.getCanvasXY(ca)[1]; // Show a tooltip RG.Tooltip(this, text, x, y, 0, e); }; /** * 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) { var coords = this.coords2[index]; if (coords) { var pre_linewidth = co.lineWidth; var pre_linecap = co.lineCap; // ------------------------------------------ // co.lineWidth = prop['chart.linewidth'] + 10; co.lineCap = 'round'; co.strokeStyle = prop['chart.key.interactive.highlight.chart.stroke']; co.beginPath(); for (var i=0,len=coords.length; i