// 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}; RGraph.SVG = RGraph.SVG || {}; // Module pattern (function (win, doc, undefined) { var RG = RGraph, ua = navigator.userAgent, ma = Math, win = window, doc = document; RG.SVG.Bipolar = function (conf) { // // A setter that the constructor uses (at the end) // to set all of the properties // // @param string name The name of the property to set // @param string value The value to set the property to // this.set = function (name, value) { if (arguments.length === 1 && typeof name === 'object') { for (i in arguments[0]) { if (typeof i === 'string') { var ret = RG.SVG.commonSetter({ object: this, name: i, value: arguments[0][i] }); name = ret.name; value = ret.value; this.set(name, value); } } } else { var ret = RG.SVG.commonSetter({ object: this, name: name, value: value }); name = ret.name; value = ret.value; this.properties[name] = value; // If setting the colors, update the originalColors // property too if (name === 'colors') { this.originalColors = RG.SVG.arrayClone(value); this.colorsParsed = false; } } return this; }; this.id = conf.id; this.uid = RG.SVG.createUID(); this.container = document.getElementById(this.id); this.layers = {}; // MUST be before the SVG tag is created! this.svg = RG.SVG.createSVG({object: this,container: this.container}); this.isRGraph = true; this.data = [conf.left, conf.right]; this.left = conf.left; this.right = conf.right; this.type = 'bipolar'; this.coords = []; this.coordsLeft = []; this.coordsRight = []; this.coords2 = []; this.coords2Left = []; this.coords2Right = []; this.stackedBackfacesLeft = []; this.stackedBackfacesRight = []; this.originalColors = {}; this.gradientCounter = 1; this.sequentialIndex = 0; // Used for tooltips // Add this object to the ObjectRegistry RG.SVG.OR.add(this); this.container.style.display = 'inline-block'; this.properties = { gutterLeft: 35, gutterRight: 35, gutterTop: 35, gutterBottom: 35, gutterCenter: null, backgroundColor: null, backgroundGrid: true, backgroundGridColor: '#ddd', backgroundGridLinewidth: 1, backgroundGridHlines: true, backgroundGridHlinesCount: null, backgroundGridVlines: true, backgroundGridVlinesCount: null, backgroundGridBorder: true, backgroundGridDashed: false, backgroundGridDotted: false, backgroundGridDashArray: null, xaxis: true, xaxisLinewidth: 1, xaxisTickmarks: true, xaxisTickmarksLength: 5, xaxisLabelsCount: 5, xaxisLabelsPositionEdgeTickmarksCount: 5, xaxisColor: 'black', xaxisLabelsOffsetx: 0, xaxisLabelsOffsety: 0, xaxisUnitsPre: '', xaxisUnitsPost: '', xaxisStrict: false, xaxisDecimals: 0, xaxisPoint: '.', xaxisThousand: ',', xaxisRound: false, xaxisMax: null, xaxisMin: 0, xaxisFormatter: null, xaxisTextFont: null, xaxisTextSize: null, xaxisTextBold: null, xaxisTextItalic: null, xaxisTextColor: null, yaxis: true, yaxisTickmarks: true, yaxisTickmarksLength: 5, yaxisColor: 'black', yaxisScale: false, yaxisLabels: null, yaxisLabelsOffsetx: 0, yaxisLabelsOffsety: 0, yaxisTextFont: null, yaxisTextSize: null, yaxisTextBold: null, yaxisTextItalic: null, yaxisTextColor: null, // 20 colors. If you need more you need to set the colors property colors: [ 'red', '#0f0', '#00f', '#ff0', '#0ff', '#0f0','pink','orange','gray','black', 'red', '#0f0', '#00f', '#ff0', '#0ff', '#0f0','pink','orange','gray','black' ], colorsSequential: false, strokestyle: 'rgba(0,0,0,0)', vmargin: 3, vmarginGrouped: 2, labelsAbove: false, labelsAboveFont: null, labelsAboveSize: null, labelsAboveBold: null, labelsAboveItalic: null, labelsAboveColor: null, labelsAboveBackground: null, labelsAboveBackgroundPadding: 0, labelsAboveUnitsPre: null, labelsAboveUnitsPost: null, labelsAbovePoint: null, labelsAboveThousand: null, labelsAboveFormatter: null, labelsAboveDecimals: null, labelsAboveOffsetx: 0, labelsAboveOffsety: 0, labelsAboveSpecific: null, textColor: 'black', textFont: 'sans-serif', textSize: 12, textBold: false, textItalic: false, linewidth: 1, grouping: 'grouped', tooltips: null, tooltipsOverride: null, tooltipsEffect: 'fade', tooltipsCssClass: 'RGraph_tooltip', tooltipsEvent: 'click', highlightStroke: 'rgba(0,0,0,0)', highlightFill: 'rgba(255,255,255,0.7)', highlightLinewidth: 1, title: '', titleSize: 16, titleX: null, titleY: null, titleHalign: 'center', titleValign: null, titleColor: 'black', titleFont: null, titleBold: false, titleItalic: false, titleSubtitle: null, titleSubtitleSize: 10, titleSubtitleX: null, titleSubtitleY: null, titleSubtitleHalign: 'center', titleSubtitleValign: null, titleSubtitleColor: '#aaa', titleSubtitleFont: null, titleSubtitleBold: false, titleSubtitleItalic: false, shadow: false, shadowOffsetx: 2, shadowOffsety: 2, shadowBlur: 2, shadowOpacity: 0.25, key: null, keyColors: null, keyOffsetx: 0, keyOffsety: 0, keyTextOffsetx: 0, keyTextOffsety: -1, keyTextSize: null, keyTextBold: null, keyTextItalic: null, keyTextFont: null }; // // Copy the global object properties to this instance // RG.SVG.getGlobals(this); /** * "Decorate" the object with the generic effects if the effects library has been included */ if (RG.SVG.FX && typeof RG.SVG.FX.decorate === 'function') { RG.SVG.FX.decorate(this); } var prop = this.properties; // // The draw method draws the Bar chart // this.draw = function () { // Fire the beforedraw event RG.SVG.fireCustomEvent(this, 'onbeforedraw'); // Should the first thing that's done inthe.draw() function // except for the onbeforedraw event this.width = Number(this.svg.getAttribute('width')); this.height = Number(this.svg.getAttribute('height')); // Create the defs tag if necessary RG.SVG.createDefs(this); // // Autosize the center gutter to allow for big labels // if (typeof prop.gutterCenter !== 'number') { prop.gutterCenter = this.getGutterCenter(); } // Reset the coords arrays this.coords = []; this.coordsLeft = []; this.coordsRight = []; this.coords2 = []; this.coords2Left = []; this.coords2Right = []; this.graphWidth = (this.width - prop.gutterLeft - prop.gutterRight - prop.gutterCenter) / 2; this.graphHeight = this.height - prop.gutterTop - prop.gutterBottom; /** * Parse the colors. This allows for simple gradient syntax */ // Parse the colors for gradients RG.SVG.resetColorsToOriginalValues({object:this}); this.parseColors(); // Go through the data and work out the maximum value var values = []; for (var i=0; i<2; ++i) { for (var j=0,max=0; j<this.data[i].length; ++j) { if (typeof this.data[i][j] === 'number') { values.push(this.data[i][j]); } else if (RG.SVG.isArray(this.data[i][j]) && prop.grouping === 'grouped') { values.push(RG.SVG.arrayMax(this.data[i][j])); } else if (RG.SVG.isArray(this.data[i][j]) && prop.grouping === 'stacked') { values.push(RG.SVG.arraySum(this.data[i][j])); } } } var max = RG.SVG.arrayMax(values); // A custom, user-specified maximum value if (typeof prop.xaxisMax === 'number') { max = prop.xaxisMax; } // // Generate an appropiate scale // this.scale = RG.SVG.getScale({ object: this, numlabels: prop.xaxisLabelsCount, unitsPre: prop.xaxisUnitsPre, unitsPost: prop.xaxisUnitsPost, max: max, min: prop.xaxisMin, point: prop.xaxisPoint, round: prop.xaxisRound, thousand: prop.xaxisThousand, decimals: prop.xaxisDecimals, strict: typeof prop.xaxisMax === 'number', formatter: prop.xaxisFormatter }); // Now the scale has been generated adopt its max value this.max = this.scale.max; this.min = this.scale.min; prop.yaxisMax = this.scale.max; prop.yaxisMin = this.scale.min; // Draw the background first this.drawBackground(this); // Draw the title this.drawTitle(); // Draw the bars this.drawBars(); // Draw the axes over the bars this.drawAxes(); // Draw the labels for both of the the axes this.drawLabels() // Draw the labelsAbove labels this.drawLabelsAbove(); // Draw the key if (typeof prop.key !== null && RG.SVG.drawKey) { RG.SVG.drawKey(this); } else if (!RG.SVG.isNull(prop.key)) { alert('The drawKey() function does not exist - have you forgotten to include the key library?'); } // Fire the draw event RG.SVG.fireCustomEvent(this, 'ondraw'); return this; }; // // Draws the background // this.drawBackground = function () { // Save the original gutter properties var originalGutterRight = prop.gutterRight, originalGutterLeft = prop.gutterLeft; // Draw the LEFT background prop.gutterRight = this.width - (prop.gutterLeft + this.graphWidth); if (RG.SVG.isNull(prop.backgroundGridHlinesCount)) { var resetToNull = true; prop.backgroundGridHlinesCount = this.left.length; } // Set the LEFT background image properties var properties = ['','Aspect','Opacity','Stretch','X','Y','W','H',]; for (i in properties ) { if (typeof properties[i] === 'string') { prop['backgroundImage' + properties[i]] = prop['backgroundImageLeft' + properties[i]]; } } RG.SVG.drawBackground(this); if (resetToNull) { prop.backgroundGridHlinesCount = null; } // Draw the RIGHT background prop.gutterRight = originalGutterRight; prop.gutterLeft = this.width - (prop.gutterRight + this.graphWidth); if (RG.SVG.isNull(prop.backgroundGridHlinesCount)) { prop.backgroundGridHlinesCount = this.right.length; } // Set the RIGHT background image properties var properties = ['','Aspect','Opacity','Stretch','X','Y','W','H',]; for (i in properties ) { if (typeof properties[i] === 'string') { prop['backgroundImage' + properties[i]] = prop['backgroundImageRight' + properties[i]]; } } // Draw the background RG.SVG.drawBackground(this); // Reset the gutter properties to the original values prop.gutterLeft = originalGutterLeft; prop.gutterRight = originalGutterRight; }; // // Draws the axes // this.drawAxes = function () { // Draw the LEFT X axes if (prop.xaxis) { RG.SVG.create({ svg: this.svg, type: 'path', parent: this.svg.all, attr: { d: 'M {1} {2} L {3} {4}'.format( prop.gutterLeft, this.height - prop.gutterBottom, prop.gutterLeft + this.graphWidth, this.height - prop.gutterBottom ), 'stroke-width': prop.xaxisLinewidth, stroke: prop.xaxisColor, fill: 'rgba(0,0,0,0)', 'shape-rendering': 'crispEdges' } }); // Draw the right X axis RG.SVG.create({ svg: this.svg, type: 'path', parent: this.svg.all, attr: { d: 'M {1} {2} L {3} {4}'.format( this.width - prop.gutterRight, this.height - prop.gutterBottom, this.width - prop.gutterRight - this.graphWidth, this.height - prop.gutterBottom ), 'stroke-width': prop.xaxisLinewidth, stroke: prop.xaxisColor, fill: 'rgba(0,0,0,0)', 'shape-rendering': 'crispEdges' } }); // // Draw tickmarks if necessary // if (prop.xaxisTickmarks) { var startY = this.height - prop.gutterBottom, endY = this.height - prop.gutterBottom + prop.xaxisTickmarksLength; // Draw the LEFT sides tickmarks for (var i=0; i<prop.xaxisLabelsPositionEdgeTickmarksCount; ++i) { var x = prop.gutterLeft + (i * (this.graphWidth / prop.xaxisLabelsPositionEdgeTickmarksCount)); RG.SVG.create({ svg: this.svg, parent: this.svg.all, type: 'path', attr: { d: 'M{1} {2} L{3} {4}'.format( x + 0.001, startY, x, endY ), stroke: prop.xaxisColor, 'stroke-width': prop.xaxisLinewidth, 'shape-rendering': "crispEdges" } }); } // Draw an extra LEFT tick if no Y axis is being shown if (!prop.yaxis) { var x = prop.gutterLeft + this.graphWidth; RG.SVG.create({ svg: this.svg, parent: this.svg.all, type: 'path', attr: { d: 'M{1} {2} L{3} {4}'.format( x + 0.001, startY, x, endY ), stroke: prop.xaxisColor, 'stroke-width': prop.xaxisLinewidth, 'shape-rendering': "crispEdges" } }); } // Draw the RIGHT sides tickmarks for (var i=0; i<prop.xaxisLabelsPositionEdgeTickmarksCount; ++i) { var x = prop.gutterLeft + prop.gutterCenter + this.graphWidth + ((i+1) * (this.graphWidth / prop.xaxisLabelsPositionEdgeTickmarksCount)); RG.SVG.create({ svg: this.svg, parent: this.svg.all, type: 'path', attr: { d: 'M{1} {2} L{3} {4}'.format( x + 0.001, startY, x, endY ), stroke: prop.xaxisColor, 'stroke-width': prop.xaxisLinewidth, 'shape-rendering': "crispEdges" } }); } // Draw an extra RIGHT tick if no Y axis is being shown if (!prop.yaxis) { var x = prop.gutterLeft + this.graphWidth + prop.gutterCenter; RG.SVG.create({ svg: this.svg, parent: this.svg.all, type: 'path', attr: { d: 'M{1} {2} L{3} {4}'.format( x + 0.001, startY, x, endY ), stroke: prop.xaxisColor, 'stroke-width': prop.xaxisLinewidth, 'shape-rendering': "crispEdges" } }); } } } // Draw the LEFT vertical axes if (prop.yaxis) { RG.SVG.create({ svg: this.svg, type: 'path', parent: this.svg.all, attr: { d: 'M {1} {2} L {3} {4}'.format( prop.gutterLeft + this.graphWidth, this.height - prop.gutterBottom, prop.gutterLeft + this.graphWidth, prop.gutterTop ), 'stroke-width': prop.yaxisLinewidth, stroke: prop.yaxisColor, fill: 'rgba(0,0,0,0)', 'shape-rendering': 'crispEdges', 'stroke-linecap': 'square' } }); // Draw the RIGHT vertical axis RG.SVG.create({ svg: this.svg, type: 'path', parent: this.svg.all, attr: { d: 'M {1} {2} L {3} {4}'.format( prop.gutterLeft + this.graphWidth + prop.gutterCenter, this.height - prop.gutterBottom, prop.gutterLeft + this.graphWidth + prop.gutterCenter, prop.gutterTop ), 'stroke-width': prop.yaxisLinewidth, stroke: prop.yaxisColor, fill: 'rgba(0,0,0,0)', 'shape-rendering': 'crispEdges', 'stroke-linecap': 'square' } }); // // Draw Y axis tickmarks if necessary // if (prop.yaxisTickmarks) { var startX = prop.gutterLeft + this.graphWidth, endX = prop.gutterLeft + this.graphWidth + prop.yaxisTickmarksLength, numticks = this.left.length; // Draw the left sides tickmarks for (var i=0; i<numticks; ++i) { var y = prop.gutterTop + (i * (this.graphHeight / numticks)); RG.SVG.create({ svg: this.svg, parent: this.svg.all, type: 'path', attr: { d: 'M{1} {2} L{3} {4}'.format( startX + 0.001, y, endX, y ), stroke: prop.yaxisColor, 'stroke-width': prop.yaxisLinewidth, 'shape-rendering': "crispEdges" } }); } // Draw an extra LEFT tickmark if the X axis is not being shown if (!prop.xaxis) { var y = prop.gutterTop + this.graphHeight; RG.SVG.create({ svg: this.svg, parent: this.svg.all, type: 'path', attr: { d: 'M{1} {2} L{3} {4}'.format( startX + 0.001, y, endX, y ), stroke: prop.yaxisColor, 'stroke-width': prop.yaxisLinewidth, 'shape-rendering': "crispEdges" } }); } var startX = prop.gutterLeft + this.graphWidth + prop.gutterCenter, endX = prop.gutterLeft + this.graphWidth + prop.gutterCenter - prop.yaxisTickmarksLength, numticks = this.right.length; for (var i=0; i<numticks; ++i) { var y = prop.gutterTop + (i * (this.graphHeight / numticks)); RG.SVG.create({ svg: this.svg, parent: this.svg.all, type: 'path', attr: { d: 'M{1} {2} L{3} {4}'.format( startX + 0.001, y, endX, y ), stroke: prop.yaxisColor, 'stroke-width': prop.yaxisLinewidth, 'shape-rendering': "crispEdges" } }); } // Draw an extra RIGHT tickmark if the X axis is not being shown if (!prop.xaxis) { var y = prop.gutterTop + this.graphHeight; RG.SVG.create({ svg: this.svg, parent: this.svg.all, type: 'path', attr: { d: 'M{1} {2} L{3} {4}'.format( startX + 0.001, y, endX, y ), stroke: prop.yaxisColor, 'stroke-width': prop.yaxisLinewidth, 'shape-rendering': "crispEdges" } }); } } } }; // // Draws the labels // this.drawLabels = function () { // // Draw the Y axis labels // var numlabels = prop.yaxisLabels ? prop.yaxisLabels.length : 5 for (var i=0; i<numlabels; ++i) { var segment = this.graphHeight / numlabels, y = prop.gutterTop + (segment * i) + (segment / 2) + prop.yaxisLabelsOffsety, x = prop.gutterLeft + this.graphWidth + (prop.gutterCenter / 2) + prop.yaxisLabelsOffsetx; var text = RG.SVG.text({ object: this, parent: this.svg.all, text: prop.yaxisLabels && prop.yaxisLabels[i] ? prop.yaxisLabels[i] : '', x: x, y: y, halign: 'center', valign: 'center', tag: 'labels.yaxis', font: prop.yaxisTextFont || prop.textFont, size: prop.yaxisTextSize || (typeof prop.textSize === 'number' ? prop.textSize + 'pt' : prop.textSize), bold: !RG.SVG.isNull(prop.yaxisTextBold) ? prop.yaxisTextBold : prop.textBold, italic: !RG.SVG.isNull(prop.yaxisTextItalic) ? prop.yaxisTextItalic : prop.textItalic, color: prop.yaxisTextColor || prop.textColor }); } // // Draw the X axis scale for the LEFT side // var segment = this.graphWidth / prop.xaxisLabelsCount; for (var i=0; i<this.scale.labels.length; ++i) { RG.SVG.text({ object: this, parent: this.svg.all, text: this.scale.labels[i], x: prop.gutterLeft + this.graphWidth - (segment * (i+1)) + prop.xaxisLabelsOffsetx, y: this.height - prop.gutterBottom + 10 + prop.xaxisLabelsOffsety, halign: 'center', valign: 'top', tag: 'labels.xaxis', font: prop.xaxisTextFont || prop.textFont, size: prop.xaxisTextSize || (typeof prop.textSize === 'number' ? prop.textSize + 'pt' : prop.textSize), bold: typeof prop.xaxisTextBold === 'boolean' ? prop.xaxisTextBold : prop.textBold, italic: typeof prop.xaxisTextItalic === 'boolean' ? prop.xaxisTextItalic : prop.textItalic, color: prop.xaxisTextColor || prop.textColor }); } // // Add the minimum label for the LEST side // var y = this.height - prop.gutterBottom + 10, str = (prop.xaxisUnitsPre + prop.xaxisMin.toFixed(prop.xaxisDecimals).replace(/\./, prop.xaxisPoint) + prop.xaxisUnitsPost); var text = RG.SVG.text({ object: this, parent: this.svg.all, text: str, x: prop.gutterLeft + this.graphWidth + prop.xaxisLabelsOffsetx, y: y + prop.xaxisLabelsOffsety, halign: 'center', valign: 'top', tag: 'labels.xaxis', font: prop.xaxisTextFont || prop.textFont, size: prop.xaxisTextSize || (typeof prop.textSize === 'number' ? prop.textSize + 'pt' : prop.textSize), bold: typeof prop.xaxisTextBold === 'boolean' ? prop.xaxisTextBold : prop.textBold, italic: typeof prop.xaxisTextItalic === 'boolean' ? prop.xaxisTextItalic : prop.textItalic, color: prop.xaxisTextColor || prop.textColor }); // // Draw the X axis scale for the RIGHT side // for (var i=0; i<this.scale.labels.length; ++i) { RG.SVG.text({ object: this, parent: this.svg.all, text: this.scale.labels[i], x: prop.gutterLeft + this.graphWidth + prop.gutterCenter + (segment * (i + 1)) + prop.xaxisLabelsOffsetx, y: this.height - prop.gutterBottom + 10 + prop.xaxisLabelsOffsety, halign: 'center', valign: 'top', tag: 'labels.xaxis', font: prop.xaxisTextFont || prop.textFont, size: prop.xaxisTextSize || (typeof prop.textSize === 'number' ? prop.textSize + 'pt' : prop.textSize), bold: typeof prop.xaxisTextBold === 'boolean' ? prop.xaxisTextBold : prop.textBold, italic: typeof prop.xaxisTextItalic === 'boolean' ? prop.xaxisTextItalic : prop.textItalic, color: prop.xaxisTextColor || prop.textColor }); } // // Add the minimum label for the RIGHT side // var text = RG.SVG.text({ object: this, parent: this.svg.all, text: prop.xaxisUnitsPre + prop.xaxisMin.toFixed(prop.xaxisDecimals).replace(/\./, prop.xaxisPoint) + prop.xaxisUnitsPost, x: prop.gutterLeft + this.graphWidth + prop.gutterCenter + prop.xaxisLabelsOffsetx, y: this.height - prop.gutterBottom + 10 + prop.xaxisLabelsOffsety, halign: 'center', valign: 'top', tag: 'labels.xaxis', font: prop.xaxisTextFont || prop.textFont, size: prop.xaxisTextSize || (typeof prop.textSize === 'number' ? prop.textSize + 'pt' : prop.textSize), bold: typeof prop.xaxisTextBold === 'boolean' ? prop.xaxisTextBold : prop.textBold, italic: typeof prop.xaxisTextItalic === 'boolean' ? prop.xaxisTextItalic : prop.textItalic, color: prop.xaxisTextColor || prop.textColor }); }; // // Draws the bars // this.drawBars = function () { if (prop.shadow) { RG.SVG.setShadow({ object: this, offsetx: prop.shadowOffsetx, offsety: prop.shadowOffsety, blur: prop.shadowBlur, opacity: prop.shadowOpacity, id: 'dropShadow' }); } // Go thru the LEFT data and draw the bars for (var i=0; i<this.left.length; ++i) { // LEFT REGULAR NUMBER if (typeof this.left[i] === 'number') { var color = prop.colors[this.sequentialIndex], tooltip = RG.SVG.isNull(prop.tooltips) ? null : prop.tooltips[this.sequentialIndex], y = prop.gutterTop + ((this.graphHeight / this.left.length) * i) + prop.vmargin, width = this.getWidth(this.left[i]), x = prop.gutterLeft + this.graphWidth - width, height = (this.graphHeight / this.left.length) - prop.vmargin - prop.vmargin; var rect = RG.SVG.create({ svg: this.svg, parent: this.svg.all, type: 'rect', attr: { x: x, y: y, width: width, height: height, fill: prop.colorsSequential ? prop.colors[this.sequentialIndex] : prop.colors[0], stroke: prop.strokestyle, 'stroke-width': prop.linewidth, 'shape-rendering': 'crispEdges', 'data-original-x': x, 'data-original-y': y, 'data-original-width': width, 'data-original-height': height, 'data-tooltop': (tooltip || ''), 'data-index': i, 'data-sequential-index': this.sequentialIndex, 'data-value': this.left[i], filter: prop.shadow ? 'url(#dropShadow)' : '' } }); this.coords.push({ object: this, element: rect, x: parseFloat(rect.getAttribute('x')), y: parseFloat(rect.getAttribute('y')), width: parseFloat(rect.getAttribute('width')), height: parseFloat(rect.getAttribute('height')) }); this.coordsLeft.push({ object: this, element: rect, x: parseFloat(rect.getAttribute('x')), y: parseFloat(rect.getAttribute('y')), width: parseFloat(rect.getAttribute('width')), height: parseFloat(rect.getAttribute('height')) }); this.installTooltipsEventListeners({ rect: rect, index: i, sequentialIndex: this.sequentialIndex }); this.sequentialIndex++; // LEFT STACKED } else if (RG.SVG.isArray(this.left[i]) && prop.grouping === 'stacked') { var accWidth = 0; for (var j=0; j<this.left[i].length; ++j) { var color = prop.colors[this.sequentialIndex], tooltip = RG.SVG.isNull(prop.tooltips) ? null : prop.tooltips[this.sequentialIndex], y = prop.gutterTop + ((this.graphHeight / this.left.length) * i) + prop.vmargin, width = this.getWidth(this.left[i][j]), accWidth = accWidth + width, x = prop.gutterLeft + this.graphWidth - accWidth, height = (this.graphHeight / this.left.length) - prop.vmargin - prop.vmargin; // If this is the first iteration of the loop and a shadow // is requested draw a rect here to create it. if (j === 0 && prop.shadow) { var shadowBackfaceX = prop.gutterLeft + this.graphWidth - this.getWidth(RG.SVG.arraySum(this.left[i])), shadowBackfaceWidth = this.getWidth(RG.SVG.arraySum(this.left[i])); var rect = RG.SVG.create({ svg: this.svg, parent: this.svg.all, type: 'rect', attr: { fill: '#eee', x: shadowBackfaceX, y: y, width: shadowBackfaceWidth, height: height, 'stroke-width': 0, 'data-index': i, filter: 'url(#dropShadow)' } }); this.stackedBackfacesLeft[i] = rect; } var rect = RG.SVG.create({ svg: this.svg, parent: this.svg.all, type: 'rect', attr: { x: x, y: y, width: width, height: height, fill: prop.colorsSequential ? prop.colors[this.sequentialIndex] : prop.colors[j], stroke: prop.strokestyle, 'stroke-width': prop.linewidth, 'shape-rendering': 'crispEdges', 'data-original-x': x, 'data-original-y': y, 'data-original-width': width, 'data-original-height': height, 'data-tooltop': (tooltip || ''), 'data-index': i, 'data-subindex': j, 'data-sequential-index': this.sequentialIndex, 'data-value': this.left[i][j] } }); this.coords.push({ object: this, element: rect, x: parseFloat(rect.getAttribute('x')), y: parseFloat(rect.getAttribute('y')), width: parseFloat(rect.getAttribute('width')), height: parseFloat(rect.getAttribute('height')) }); this.coordsLeft.push({ object: this, element: rect, x: parseFloat(rect.getAttribute('x')), y: parseFloat(rect.getAttribute('y')), width: parseFloat(rect.getAttribute('width')), height: parseFloat(rect.getAttribute('height')) }); if (!this.coords2[i]) { this.coords2[i] = []; } if (!this.coords2Left[i]) { this.coords2Left[i] = []; } this.coords2[i].push({ object: this, element: rect, x: parseFloat(rect.getAttribute('x')), y: parseFloat(rect.getAttribute('y')), width: parseFloat(rect.getAttribute('width')), height: parseFloat(rect.getAttribute('height')) }); this.coords2Left[i].push({ object: this, element: rect, x: parseFloat(rect.getAttribute('x')), y: parseFloat(rect.getAttribute('y')), width: parseFloat(rect.getAttribute('width')), height: parseFloat(rect.getAttribute('height')) }); this.installTooltipsEventListeners({ rect: rect, index: i, sequentialIndex: this.sequentialIndex }); this.sequentialIndex++; } // LEFT GROUPED } else if (RG.SVG.isArray(this.left[i]) && prop.grouping === 'grouped') { for (var j=0; j<this.left[i].length; ++j) { var color = prop.colors[this.sequentialIndex], tooltip = RG.SVG.isNull(prop.tooltips) ? null : prop.tooltips[this.sequentialIndex], height = ((this.graphHeight / this.left.length) - prop.vmargin - prop.vmargin - (prop.vmarginGrouped * (this.left[i].length - 1))) / this.left[i].length, y = prop.gutterTop + ((this.graphHeight / this.left.length) * i) + prop.vmargin + (height * j) + (j * prop.vmarginGrouped), width = this.getWidth(this.left[i][j]), x = prop.gutterLeft + this.graphWidth - width; var rect = RG.SVG.create({ svg: this.svg, parent: this.svg.all, type: 'rect', attr: { x: x, y: y, width: width, height: height, fill: prop.colorsSequential ? prop.colors[this.sequentialIndex] : prop.colors[j], stroke: prop.strokestyle, 'stroke-width': prop.linewidth, 'shape-rendering': 'crispEdges', 'data-original-x': x, 'data-original-y': y, 'data-original-width': width, 'data-original-height': height, 'data-tooltop': (tooltip || ''), 'data-index': i, 'data-subindex': j, 'data-sequential-index': this.sequentialIndex, 'data-value': this.left[i][j], filter: prop.shadow ? 'url(#dropShadow)' : '' } }); this.coords.push({ object: this, element: rect, x: parseFloat(rect.getAttribute('x')), y: parseFloat(rect.getAttribute('y')), width: parseFloat(rect.getAttribute('width')), height: parseFloat(rect.getAttribute('height')) }); this.coordsLeft.push({ object: this, element: rect, x: parseFloat(rect.getAttribute('x')), y: parseFloat(rect.getAttribute('y')), width: parseFloat(rect.getAttribute('width')), height: parseFloat(rect.getAttribute('height')) }); if (!this.coords2[i]) { this.coords2[i] = []; } if (!this.coords2Left[i]) { this.coords2Left[i] = []; } this.coords2[i].push({ object: this, element: rect, x: parseFloat(rect.getAttribute('x')), y: parseFloat(rect.getAttribute('y')), width: parseFloat(rect.getAttribute('width')), height: parseFloat(rect.getAttribute('height')) }); this.coords2Left[i].push({ object: this, element: rect, x: parseFloat(rect.getAttribute('x')), y: parseFloat(rect.getAttribute('y')), width: parseFloat(rect.getAttribute('width')), height: parseFloat(rect.getAttribute('height')) }); this.installTooltipsEventListeners({ rect: rect, index: i, sequentialIndex: this.sequentialIndex }); this.sequentialIndex++; } } } // Go thru the RIGHT data and draw the bars for (var i=0; i<this.right.length; ++i) { // RIGHT REGULAR if (typeof this.right[i] === 'number') { var color = prop.colors[this.sequentialIndex], tooltip = RG.SVG.isNull(prop.tooltips) ? null : prop.tooltips[this.sequentialIndex], y = prop.gutterTop + ((this.graphHeight / this.right.length) * i) + prop.vmargin, width = this.getWidth(this.right[i]), x = prop.gutterLeft + this.graphWidth + prop.gutterCenter, height = (this.graphHeight / this.right.length) - prop.vmargin - prop.vmargin; var rect = RG.SVG.create({ svg: this.svg, parent: this.svg.all, type: 'rect', attr: { x: x, y: y, width: width, height: height, fill: prop.colorsSequential ? prop.colors[this.sequentialIndex] : prop.colors[0], stroke: prop.strokestyle, 'stroke-width': prop.linewidth, 'shape-rendering': 'crispEdges', 'data-original-x': x, 'data-original-y': y, 'data-original-width': width, 'data-original-height': height, 'data-tooltop': (tooltip || ''), 'data-index': i, 'data-sequential-index': this.sequentialIndex, 'data-value': this.right[i], filter: prop.shadow ? 'url(#dropShadow)' : '' } }); this.coords.push({ object: this, element: rect, x: parseFloat(rect.getAttribute('x')), y: parseFloat(rect.getAttribute('y')), width: parseFloat(rect.getAttribute('width')), height: parseFloat(rect.getAttribute('height')) }); this.coordsRight.push({ object: this, element: rect, x: parseFloat(rect.getAttribute('x')), y: parseFloat(rect.getAttribute('y')), width: parseFloat(rect.getAttribute('width')), height: parseFloat(rect.getAttribute('height')) }); this.installTooltipsEventListeners({ rect: rect, index: i, sequentialIndex: this.sequentialIndex }); this.sequentialIndex++; // RIGHT STACKED } else if (RG.SVG.isArray(this.right[i]) && prop.grouping === 'stacked') { var accWidth = 0; for (var j=0; j<this.right[i].length; ++j) { var color = prop.colors[this.sequentialIndex], tooltip = RG.SVG.isNull(prop.tooltips) ? null : prop.tooltips[this.sequentialIndex], y = prop.gutterTop + ((this.graphHeight / this.right.length) * i) + prop.vmargin, width = this.getWidth(this.right[i][j]), x = prop.gutterLeft + this.graphWidth + prop.gutterCenter + accWidth, accWidth = accWidth + width, height = (this.graphHeight / this.left.length) - prop.vmargin - prop.vmargin; // If this is the first iteration of the loop and a shadow // is requested draw a rect here to create it. if (j === 0 && prop.shadow) { var shadowBackfaceX = prop.gutterLeft + this.graphWidth + prop.gutterCenter, shadowBackfaceWidth = this.getWidth(RG.SVG.arraySum(this.right[i])); var rect = RG.SVG.create({ svg: this.svg, parent: this.svg.all, type: 'rect', attr: { fill: '#eee', x: shadowBackfaceX, y: y, width: shadowBackfaceWidth, height: height, 'stroke-width': 0, 'data-index': i, filter: 'url(#dropShadow)' } }); this.stackedBackfacesRight[i] = rect; } var rect = RG.SVG.create({ svg: this.svg, parent: this.svg.all, type: 'rect', attr: { x: x, y: y, width: width, height: height, fill: prop.colorsSequential ? prop.colors[this.sequentialIndex] : prop.colors[j], stroke: prop.strokestyle, 'stroke-width': prop.linewidth, 'shape-rendering': 'crispEdges', 'data-original-x': x, 'data-original-y': y, 'data-original-width': width, 'data-original-height': height, 'data-tooltop': (tooltip || ''), 'data-index': i, 'data-subindex': j, 'data-sequential-index': this.sequentialIndex, 'data-value': this.right[i][j] } }); this.coords.push({ object: this, element: rect, x: parseFloat(rect.getAttribute('x')), y: parseFloat(rect.getAttribute('y')), width: parseFloat(rect.getAttribute('width')), height: parseFloat(rect.getAttribute('height')) }); this.coordsRight.push({ object: this, element: rect, x: parseFloat(rect.getAttribute('x')), y: parseFloat(rect.getAttribute('y')), width: parseFloat(rect.getAttribute('width')), height: parseFloat(rect.getAttribute('height')) }); if (!this.coords2[i + this.left.length]) { this.coords2[i + this.left.length] = []; } if (!this.coords2Right[i]) { this.coords2Right[i] = []; } this.coords2[i + this.left.length].push({ object: this, element: rect, x: parseFloat(rect.getAttribute('x')), y: parseFloat(rect.getAttribute('y')), width: parseFloat(rect.getAttribute('width')), height: parseFloat(rect.getAttribute('height')) }); this.coords2Right[i].push({ object: this, element: rect, x: parseFloat(rect.getAttribute('x')), y: parseFloat(rect.getAttribute('y')), width: parseFloat(rect.getAttribute('width')), height: parseFloat(rect.getAttribute('height')) }); this.installTooltipsEventListeners({ rect: rect, index: i, sequentialIndex: this.sequentialIndex }); this.sequentialIndex++; } // RIGHT GROUPED } else if (RG.SVG.isArray(this.right[i]) && prop.grouping === 'grouped') { for (var j=0; j<this.right[i].length; ++j) { var color = prop.colors[this.sequentialIndex], tooltip = RG.SVG.isNull(prop.tooltips) ? null : prop.tooltips[this.sequentialIndex], height = ((this.graphHeight / this.right.length) - prop.vmargin - prop.vmargin - (prop.vmarginGrouped * (this.right[i].length - 1))) / this.right[i].length, y = prop.gutterTop + ((this.graphHeight / this.right.length) * i) + prop.vmargin + (height * j) + (j * prop.vmarginGrouped), width = this.getWidth(this.right[i][j]), x = prop.gutterLeft + this.graphWidth + prop.gutterCenter; var rect = RG.SVG.create({ svg: this.svg, parent: this.svg.all, type: 'rect', attr: { x: x, y: y, width: width, height: height, fill: prop.colorsSequential ? prop.colors[this.sequentialIndex] : prop.colors[j], stroke: prop.strokestyle, 'stroke-width': prop.linewidth, 'shape-rendering': 'crispEdges', 'data-original-x': x, 'data-original-y': y, 'data-original-width': width, 'data-original-height': height, 'data-tooltop': (tooltip || ''), 'data-index': i, 'data-subindex': j, 'data-sequential-index': this.sequentialIndex, 'data-value': this.right[i][j], filter: prop.shadow ? 'url(#dropShadow)' : '' } }); this.coords.push({ object: this, element: rect, x: parseFloat(rect.getAttribute('x')), y: parseFloat(rect.getAttribute('y')), width: parseFloat(rect.getAttribute('width')), height: parseFloat(rect.getAttribute('height')) }); this.coordsRight.push({ object: this, element: rect, x: parseFloat(rect.getAttribute('x')), y: parseFloat(rect.getAttribute('y')), width: parseFloat(rect.getAttribute('width')), height: parseFloat(rect.getAttribute('height')) }); if (!this.coords2[i + this.left.length]) { this.coords2[i + this.left.length] = []; } if (!this.coords2Right[i]) { this.coords2Right[i] = []; } this.coords2[i + this.left.length].push({ object: this, element: rect, x: parseFloat(rect.getAttribute('x')), y: parseFloat(rect.getAttribute('y')), width: parseFloat(rect.getAttribute('width')), height: parseFloat(rect.getAttribute('height')) }); this.coords2Right[i].push({ object: this, element: rect, x: parseFloat(rect.getAttribute('x')), y: parseFloat(rect.getAttribute('y')), width: parseFloat(rect.getAttribute('width')), height: parseFloat(rect.getAttribute('height')) }); this.installTooltipsEventListeners({ rect: rect, index: i, sequentialIndex: this.sequentialIndex }); this.sequentialIndex++; } } } }; // // Installs the tooltips event lissteners. This is called from the // above function. // // @param object opt The various arguments to the function // this.installTooltipsEventListeners = function (opt) { var obj = this; // Add the tooltip events if (!RG.SVG.isNull(prop.tooltips) && prop.tooltips[this.sequentialIndex]) { // // Add tooltip event listeners // (function (idx, seq) { opt.rect.addEventListener(prop.tooltipsEvent.replace(/^on/, ''), function (e) { obj.removeHighlight(); // Show the tooltip RG.SVG.tooltip({ object: obj, index: idx, group: null, sequentialIndex: seq, text: prop.tooltips[seq], event: e }); // Highlight the rect that has been clicked on obj.highlight(e.target); }, false); opt.rect.addEventListener('mousemove', function (e) { e.target.style.cursor = 'pointer' }, false); })(opt.index, opt.sequentialIndex); } }; /** * * * @param int value The value to get the width for. */ this.getWidth = function (value) { var x1 = this.getLeftXCoord(0), x2 = this.getLeftXCoord(value); if (RG.SVG.isNull(x1) || RG.SVG.isNull(x2)) { return null; } return x1 - x2; }; /** * This function is similar to the above but instead * of a width it gets a relevant coord for a value * on the LEFT side * * @param int value The value to get the coordinate for. */ this.getLeftXCoord = function (value) { var width; if (value > this.scale.max) { return null; } if (value < this.scale.min) { return null; } width = ((value - this.scale.min) / (this.scale.max - this.scale.min)); width *= this.graphWidth; // Calculate the X coord var x = prop.gutterLeft + this.graphWidth - width; return x; }; /** * This function gets an X coordinate for the RIGHT * side. * * @param int value The value to get the coordinate for. */ this.getRightXCoord = function (value) { var width; if (value > this.scale.max) { return null; } if (value < this.scale.min) { return null; } width = ((value - this.scale.min) / (this.scale.max - this.scale.min)); width *= this.graphWidth; // Calculate the X coord var x = prop.gutterLeft + this.graphWidth + prop.gutterCenter + width; return x; }; /** * This function can be used to highlight a bar on the chart * * @param object rect The rectangle to highlight */ this.highlight = function (rect) { var x = parseInt(rect.getAttribute('x')), y = parseInt(rect.getAttribute('y')), width = parseInt(rect.getAttribute('width')), height = parseInt(rect.getAttribute('height')); var highlight = RG.SVG.create({ svg: this.svg, parent: this.svg.all, type: 'rect', attr: { stroke: prop.highlightStroke, fill: prop.highlightFill, x: x - 1, y: y - 1, width: width + 2, height: height + 2 }, style: { pointerEvents: 'none' } }); // Store the highlight rect in the rebistry so // it can be cleared later RG.SVG.REG.set('highlight', highlight); }; /** * This allows for easy specification of gradients */ this.parseColors = function () { // Save the original colors so that they can be restored when // the canvas is cleared if (!Object.keys(this.originalColors).length) { this.originalColors = { colors: RG.SVG.arrayClone(prop.colors), backgroundGridColor: RG.SVG.arrayClone(prop.backgroundGridColor), highlightFill: RG.SVG.arrayClone(prop.highlightFill), backgroundColor: RG.SVG.arrayClone(prop.backgroundColor) } } // colors var colors = prop.colors; if (colors) { for (var i=0; i<colors.length; ++i) { colors[i] = RG.SVG.parseColorLinear({ object: this, color: colors[i], direction: 'horizontal' }); } } prop.backgroundGridColor = RG.SVG.parseColorLinear({object: this, color: prop.backgroundGridColor,direction:'horizontal'}); prop.highlightFill = RG.SVG.parseColorLinear({object: this, color: prop.highlightFill,direction:'horizontal'}); prop.backgroundColor = RG.SVG.parseColorLinear({object: this, color: prop.backgroundColor,direction:'horizontal'}); }; // // Draws the labelsAbove // this.drawLabelsAbove = function () { // Go through the above labels if (prop.labelsAbove) { //var data_seq = RG.SVG.arrayLinearize(this.data), // seq = 0, // stacked_total = 0; for (var dataset=0,seq=0; dataset<this.data.length; ++dataset,++seq) { for (var i=0; i<this.data[dataset].length; ++i,++seq) { var value = this.data[dataset][i], halign = dataset === 0 ? 'right' : 'left', valign = 'center', hoffset = dataset === 0 ? -10 : 10; // REGULAR CHART if (typeof value === 'number') { var x = parseInt(this.coords[seq].element.getAttribute('x')) + hoffset + prop.labelsAboveOffsetx, height = parseInt(this.coords[seq].element.getAttribute('height')), y = parseInt(this.coords[seq].element.getAttribute('y')) + (height / 2) + prop.labelsAboveOffsety, width = parseInt(this.coords[seq].element.getAttribute('width')); // If the dataset is the RHS (which would equal ) // then set the alignment appropriately if (dataset === 1) { x += width; } var str = RG.SVG.numberFormat({ object: this, num: value.toFixed(prop.labelsAboveDecimals), prepend: typeof prop.labelsAboveUnitsPre === 'string' ? prop.labelsAboveUnitsPre : null, append: typeof prop.labelsAboveUnitsPost === 'string' ? prop.labelsAboveUnitsPost : null, point: typeof prop.labelsAbovePoint === 'string' ? prop.labelsAbovePoint : null, thousand: typeof prop.labelsAboveThousand === 'string' ? prop.labelsAboveThousand : null, formatter: typeof prop.labelsAboveFormatter === 'function' ? prop.labelsAboveFormatter : null }); // Facilitate labelsAboveSpecific if (prop.labelsAboveSpecific && prop.labelsAboveSpecific.length && (typeof prop.labelsAboveSpecific[seq] === 'string' || typeof prop.labelsAboveSpecific[seq] === 'number') ) { str = parseStr(prop.labelsAboveSpecific[seq]); } else if ( prop.labelsAboveSpecific && prop.labelsAboveSpecific.length && typeof prop.labelsAboveSpecific[seq] !== 'string' && typeof prop.labelsAboveSpecific[seq] !== 'number') { continue; } // Add the text to the SVG RG.SVG.text({ object: this, parent: this.svg.all, text: str, x: x, y: y, halign: halign, valign: valign, tag: 'labels.above', font: prop.labelsAboveFont || prop.textFont, size: prop.labelsAboveSize || prop.textSize, bold: typeof prop.labelsAboveBold === 'boolean' ? prop.labelsAboveBold : prop.textBold, italic: typeof prop.labelsAboveItalic === 'boolean' ? prop.labelsAboveItalic : prop.textItalic, color: prop.labelsAboveColor || prop.textColor, background: prop.labelsAboveBackground, padding: prop.labelsAboveBackgroundPadding }); // STACKED CHART } else if (typeof value === 'object' && prop.grouping === 'stacked') { for (var k=0,sum=0,width=0; k<this.coords2[i].length; ++k) { sum += parseFloat(this.coords2[i][k].element.getAttribute('data-value')); } var len =this.coords2[i].length; if (dataset === 0) { var x = parseFloat(this.coords2[i][len - 1].x) + hoffset, height = parseFloat(this.coords2[i][len - 1].height), y = parseFloat(this.coords2[i][0].y) + (height / 2); } else { var x = parseFloat(this.coords2[this.data[0].length + i][0].x) + hoffset + prop.labelsAboveOffsetx, height = parseFloat(this.coords2[i][len - 1].height), y = parseFloat(this.coords2[i][0].y) + (height / 2) + prop.labelsAboveOffsety; // Work out the total width by summing all the individual widths for (var j=0; j<this.coords2Right[i].length; ++j) { x += this.coords2Right[i][j].width; } } var str = RG.SVG.numberFormat({ object: this, num: sum.toFixed(prop.labelsAboveDecimals), prepend: typeof prop.labelsAboveUnitsPre === 'string' ? prop.labelsAboveUnitsPre : null, append: typeof prop.labelsAboveUnitsPost === 'string' ? prop.labelsAboveUnitsPost : null, point: typeof prop.labelsAbovePoint === 'string' ? prop.labelsAbovePoint : null, thousand: typeof prop.labelsAboveThousand === 'string' ? prop.labelsAboveThousand : null, formatter: typeof prop.labelsAboveFormatter === 'function' ? prop.labelsAboveFormatter : null }); // Facilitate labelsAboveSpecific if (prop.labelsAboveSpecific && prop.labelsAboveSpecific.length && (typeof prop.labelsAboveSpecific[seq] === 'string' || typeof prop.labelsAboveSpecific[seq] === 'number') ) { str = parseStr(prop.labelsAboveSpecific[seq]); } else if ( prop.labelsAboveSpecific && prop.labelsAboveSpecific.length && typeof prop.labelsAboveSpecific[seq] !== 'string' && typeof prop.labelsAboveSpecific[seq] !== 'number') { continue; } // Add the text to the SVG RG.SVG.text({ object: this, parent: this.svg.all, text: str, x: x, y: y, halign: halign, valign: valign, tag: 'labels.above', font: prop.labelsAboveFont || prop.textFont, size: prop.labelsAboveSize || prop.textSize, bold: typeof prop.labelsAboveBold === 'boolean' ? prop.labelsAboveBold : prop.textBold, italic: typeof prop.labelsAboveItalic === 'boolean' ? prop.labelsAboveItalic : prop.textItalic, color: prop.labelsAboveColor || prop.textColor, background: prop.labelsAboveBackground, padding: prop.labelsAboveBackgroundPadding }); // GROUPED CHART } else if (typeof value === 'object' && prop.grouping === 'grouped') { for (var k=0; k<value.length; ++k) { val = value[k]; var x = parseInt(this.coords[seq].element.getAttribute('x')) + hoffset + prop.labelsAboveOffsetx, height = parseInt(this.coords[seq].element.getAttribute('height')), y = parseInt(this.coords[seq].element.getAttribute('y')) + (height / 2) + prop.labelsAboveOffsety, width = parseInt(this.coords[seq].element.getAttribute('width')); // If the dataset is the RHS (which would equal ) // then set the alignment appropriately if (dataset === 1) { x += width; } var str = RG.SVG.numberFormat({ object: this, num: parseFloat(val).toFixed(prop.labelsAboveDecimals), prepend: typeof prop.labelsAboveUnitsPre === 'string' ? prop.labelsAboveUnitsPre : null, append: typeof prop.labelsAboveUnitsPost === 'string' ? prop.labelsAboveUnitsPost : null, point: typeof prop.labelsAbovePoint === 'string' ? prop.labelsAbovePoint : null, thousand: typeof prop.labelsAboveThousand === 'string' ? prop.labelsAboveThousand : null, formatter: typeof prop.labelsAboveFormatter === 'function' ? prop.labelsAboveFormatter : null }); // Facilitate labelsAboveSpecific if (prop.labelsAboveSpecific && prop.labelsAboveSpecific.length && (typeof prop.labelsAboveSpecific[seq] === 'string' || typeof prop.labelsAboveSpecific[seq] === 'number') ) { str = parseStr(prop.labelsAboveSpecific[seq]); } else if ( prop.labelsAboveSpecific && prop.labelsAboveSpecific.length && typeof prop.labelsAboveSpecific[seq] !== 'string' && typeof prop.labelsAboveSpecific[seq] !== 'number') { continue; } // Add the text to the SVG RG.SVG.text({ object: this, parent: this.svg.all, text: str, x: x, y: y, halign: halign, valign: valign, tag: 'labels.above', font: prop.labelsAboveFont || prop.textFont, size: prop.labelsAboveSize || prop.textSize, bold: typeof prop.labelsAboveBold === 'boolean' ? prop.labelsAboveBold : prop.textBold, italic: typeof prop.labelsAboveItalic === 'boolean' ? prop.labelsAboveItalic : prop.textItalic, color: prop.labelsAboveColor || prop.textColor, background: prop.labelsAboveBackground, padding: prop.labelsAboveBackgroundPadding }); seq++; } seq--; } } --seq; } } }; /** * 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; } RG.SVG.addCustomEventListener(this, type, func); 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; }; // // Remove highlight from the chart (tooltips) // this.removeHighlight = function () { var highlight = RG.SVG.REG.get('highlight'); if (highlight && highlight.parentNode) { highlight.parentNode.removeChild(highlight); } RG.SVG.REG.set('highlight', null); }; // // Calulate the center gutter size // this.getGutterCenter = function () { var bold = typeof prop.yaxisTextBold === 'boolean' ? prop.yaxisTextBold : prop.textBold, font = typeof prop.yaxisTextFont === 'string' ? prop.yaxisTextFont : prop.textFont, size = typeof prop.yaxisTextSize === 'number' ? prop.yaxisTextSize : prop.textSize, width = 0; // Loop through the labels measuring them if (prop.yaxisLabels) { for (var i=0,len=prop.yaxisLabels.length; i<len; ++i) { width = ma.max(width, RG.SVG.measureText({ text: prop.yaxisLabels[i], bold: bold, font: font, size: size })[0]); } } else { var width = 50; } return width + 15; }; // // Draw the title // this.drawTitle = function () { if (RG.SVG.isNull(prop.titleX)) { prop.titleX = ((this.width - prop.gutterLeft - prop.gutterRight) / 2) + prop.gutterLeft; } RG.SVG.drawTitle(this); }; // // The Bar chart grow effect // this.grow = function () { var opt = arguments[0] || {}, frames = opt.frames || 30, frame = 0, obj = this, left = RG.SVG.arrayClone(this.left), right = RG.SVG.arrayClone(this.right), seq = 0; this.draw(); var iterate = function () { // LOOP THROUGH THE LEFT DATA for (var i=0,seq=0,len=obj.coordsLeft.length; i<len; ++i, ++seq) { var multiplier = (frame / frames) * RG.SVG.FX.getEasingMultiplier(frames, frame) * RG.SVG.FX.getEasingMultiplier(frames, frame); // The main loop through the data // LEFT REGULAR if (typeof left[i] === 'number') { width = ma.abs(obj.getLeftXCoord(left[i]) - obj.getLeftXCoord(0)); left[i] = obj.left[i] * multiplier; // Set the new height on the rect obj.coordsLeft[i].element.setAttribute( 'width', width ); // Set the correct Y coord on the object obj.coords[seq].element.setAttribute( 'x', obj.getLeftXCoord(0) - width ); // LEFT STACKED } else if (typeof left[i] === 'object' && prop.grouping === 'stacked') { var accumulativeWidth = 0; for (var j=0,len2=left[i].length; j<len2; ++j, ++seq) { width = ma.abs(obj.getLeftXCoord(left[i][j]) - obj.getLeftXCoord(0)); left[i][j] = obj.left[i][j] * multiplier; obj.coords[seq].element.setAttribute( 'width', width ); obj.coords[seq].element.setAttribute( 'x', obj.getLeftXCoord(0) - width - accumulativeWidth ); accumulativeWidth += (prop.grouping === 'stacked' ? width : 0); } // // Set the width and X coord of the backfaces // if (obj.stackedBackfacesLeft[i]) { obj.stackedBackfacesLeft[i].setAttribute( 'width', accumulativeWidth ); obj.stackedBackfacesLeft[i].setAttribute( 'x', obj.getLeftXCoord(0) - accumulativeWidth ); } // Decrease seq by one so that it's not incremented twice --seq; // LEFT GROUPED } else if (typeof left[i] === 'object' && prop.grouping === 'grouped') { // Loop thru the group for (var j=0,len2=left[i].length; j<len2; ++j, ++seq) { width = ma.abs(obj.getLeftXCoord(left[i][j]) - obj.getLeftXCoord(0)); left[i][j] = obj.left[i][j] * multiplier; obj.coords[seq].element.setAttribute( 'width', width ); obj.coords[seq].element.setAttribute( 'x', obj.getLeftXCoord(0) - width ); } // Decrease seq by one so that it's not incremented twice --seq; } } // LOOP THROUGH THE RIGHT DATA for (var i=0,seq=0,len=obj.coordsRight.length; i<len; ++i, ++seq) { var multiplier = (frame / frames) * RG.SVG.FX.getEasingMultiplier(frames, frame) * RG.SVG.FX.getEasingMultiplier(frames, frame); // The main loop through the data // RIGHT REGULAR if (typeof right[i] === 'number') { width = ma.abs(obj.getRightXCoord(right[i]) - obj.getRightXCoord(0)); right[i] = obj.right[i] * multiplier; // Set the new height on the rect obj.coordsRight[i].element.setAttribute( 'width', width ); // Set the correct Y coord on the object obj.coordsRight[seq].element.setAttribute( 'x', obj.getRightXCoord(0) ); // RIGHT STACKED } else if (typeof right[i] === 'object' && prop.grouping === 'stacked') { var accumulativeWidth = 0; for (var j=0,len2=right[i].length; j<len2; ++j, ++seq) { width = ma.abs(obj.getRightXCoord(right[i][j]) - obj.getRightXCoord(0)); right[i][j] = obj.right[i][j] * multiplier; obj.coordsRight[seq].element.setAttribute( 'width', width ); obj.coordsRight[seq].element.setAttribute( 'x', obj.getRightXCoord(0) + accumulativeWidth ); accumulativeWidth += width; } // // Set the width and X coord of the backfaces // if (obj.stackedBackfacesRight[i]) { obj.stackedBackfacesRight[i].setAttribute( 'width', accumulativeWidth ); obj.stackedBackfacesRight[i].setAttribute( 'x', obj.getRightXCoord(0) ); } // Decrease seq by one so that it's not incremented twice --seq; // RIGHT GROUPED } else if (typeof right[i] === 'object' && prop.grouping === 'grouped') { // Loop thru the group for (var j=0,len2=right[i].length; j<len2; ++j, ++seq) { width = ma.abs(obj.getRightXCoord(right[i][j]) - obj.getRightXCoord(0)); right[i][j] = obj.right[i][j] * multiplier; obj.coordsRight[seq].element.setAttribute( 'width', width ); obj.coordsRight[seq].element.setAttribute( 'x', obj.getRightXCoord(0) ); } // Decrease seq by one so that it's not incremented twice --seq; } } if (frame++ <= frames) { RG.SVG.FX.update(iterate); } else if (opt.callback) { (opt.callback)(obj); } }; iterate(); return this; }; /** * HBar chart Wave effect. * * @param object OPTIONAL An object map of options. You specify 'frames' * here to give the number of frames in the effect * and also callback to specify a callback function * thats called at the end of the effect */ this.wave = function () { var obj = this, opt = arguments[0] || {}, frames = opt.frames || 120, startFrames_left = [], startFrames_right = [], counters_left = [], counters_right = []; var framesperbar = frames / 3, frame_left = -1, frame_right = -1, callback = arguments[1] || function () {}, original_left = RG.SVG.arrayClone(this.left), original_right = RG.SVG.arrayClone(this.right); for (var i=0,len=this.left.length,seq=0; i<len; i+=1,++seq) { startFrames_left[seq] = ((frames / 3) / (RG.SVG.arrayLinearize(this.left).length - 1)) * i; startFrames_right[seq] = ((frames / 3) / (RG.SVG.arrayLinearize(this.right).length - 1)) * i; counters_left[seq] = 0; counters_right[seq] = 0; if (RG.SVG.isArray(this.left[i])) { for (var j=0; j<this.left[i].length; ++j,seq++) { startFrames_left[seq] = ((frames / 3) / (RG.SVG.arrayLinearize(this.left).length - 1)) * seq; startFrames_right[seq] = ((frames / 3) / (RG.SVG.arrayLinearize(this.right).length - 1)) * seq; counters_left[seq] = 0; counters_right[seq] = 0; } --seq; } } // This stops the chart from jumping this.draw(); // Zero all of the data and set all of the rect widths to zero for (var i=0,len=this.left.length; i<len; i+=1) { if (typeof this.left[i] === 'number') { this.left[i] = 0; this.right[i] = 0; this.coordsLeft[i].element.setAttribute('width', 0); this.coordsRight[i].element.setAttribute('width', 0); } else if (typeof this.left[i] === 'object' && !RG.SVG.isNull(this.left[i])) { for (var j=0; j<this.left[i].length; ++j) { this.left[i][j] = 0; this.right[i][j] = 0; this.coords2Left[i][j].element.setAttribute('width', 0); this.coords2Right[i][j].element.setAttribute('width', 0); } } } // // Iterate over the left side // function iteratorLeft () { ++frame_left; for (var i=0,len=obj.left.length,seq=0; i<len; i+=1,seq+=1) { if (frame_left >= startFrames_left[seq]) { var isNull = RG.SVG.isNull(obj.left[i]); // Regular bars if (typeof obj.left[i] === 'number') { obj.left[i] = ma.min( ma.abs(original_left[i]), ma.abs(original_left[i] * ( (counters_left[i]++) / framesperbar)) ); var rect_left = obj.coords[i].element; rect_left.setAttribute( 'width', parseFloat(rect_left.getAttribute('data-original-width')) * (obj.left[i] / rect_left.getAttribute('data-value')) ); rect_left.setAttribute( 'x', obj.properties.gutterLeft + obj.graphWidth - (parseFloat(rect_left.getAttribute('data-original-width')) * (obj.left[i] / rect_left.getAttribute('data-value'))) ); // Stacked or grouped bars } else if (RG.SVG.isArray(obj.left[i])) { for (var j=0,accWidth=0; j<obj.left[i].length; ++j,++seq) { obj.left[i][j] = ma.min( ma.abs(original_left[i][j]), ma.abs(original_left[i][j] * ( (counters_left[seq]++) / framesperbar)) ); var rect_left = obj.coords[seq].element; rect_left.setAttribute( 'width', parseFloat(rect_left.getAttribute('data-original-width')) * (obj.left[i][j] / rect_left.getAttribute('data-value')) ); rect_left.setAttribute( 'x', obj.properties.gutterLeft + obj.graphWidth - (parseFloat(rect_left.getAttribute('data-original-width')) * (obj.left[i][j] / rect_left.getAttribute('data-value'))) - accWidth ); // Only update this for stacked charts if (obj.properties.grouping === 'stacked') { accWidth += parseFloat(rect_left.getAttribute('width')); } } seq--; } if (isNull) { obj.left[i] = null; } } else { obj.left[i] = typeof obj.left[i] === 'object' && obj.left[i] ? RG.SVG.arrayPad([], obj.left[i].length, 0) : (RG.SVG.isNull(obj.left[i]) ? null : 0); } } // No callback here - only called by the right function if (frame_left <= frames) { RG.SVG.FX.update(iteratorLeft); } } // // Iterate over the left side // function iteratorRight () { ++frame_right; for (var i=0,len=obj.right.length,seq=0; i<len; i+=1,seq+=1) { if (frame_right >= startFrames_right[seq]) { var isNull = RG.SVG.isNull(obj.right[i]); // Regular bars if (typeof obj.right[i] === 'number') { obj.right[i] = ma.min( ma.abs(original_right[i]), ma.abs(original_right[i] * ( (counters_right[i]++) / framesperbar)) ); var rect_right = obj.coords[i + obj.left.length].element; rect_right.setAttribute( 'width', parseFloat(rect_right.getAttribute('data-original-width')) * (obj.right[i] / rect_right.getAttribute('data-value')) ); rect_right.setAttribute( 'x', obj.properties.gutterLeft + obj.graphWidth + prop.gutterCenter ); // Stacked or grouped bars } else if (RG.SVG.isArray(obj.right[i])) { for (var j=0,accWidth=0; j<obj.right[i].length; ++j,++seq) { obj.right[i][j] = ma.min( ma.abs(original_right[i][j]), ma.abs(original_right[i][j] * ( (counters_right[seq]++) / framesperbar)) ); var rect_right = obj.coordsRight[seq].element; rect_right.setAttribute( 'width', parseFloat(rect_right.getAttribute('data-original-width')) * (obj.right[i][j] / rect_right.getAttribute('data-value')) ); rect_right.setAttribute( 'x', obj.properties.gutterLeft + obj.graphWidth + prop.gutterCenter + accWidth ); // Only update this for stacked charts if (obj.properties.grouping === 'stacked') { accWidth += parseFloat(rect_right.getAttribute('width')); } } seq--; } if (isNull) { obj.right[i] = null; } } else { obj.right[i] = typeof obj.right[i] === 'object' && obj.right[i] ? RG.SVG.arrayPad([], obj.right[i].length, 0) : (RG.SVG.isNull(obj.right[i]) ? null : 0); } } // Call the callback if necessary if (frame_right <= frames) { RG.SVG.FX.update(iteratorRight); } else { // Fini - call the callback } } iteratorLeft(); iteratorRight(); return this; }; // // Set the options that the user has provided // for (i in conf.options) { if (typeof i === 'string') { this.set(i, conf.options[i]); } } }; return this; // End module pattern })(window, document);