// o---------------------------------------------------------------------------------o // | This file is part of the RGraph package - you can learn more at: | // | | // | https://www.rgraph.net/license.html | // | | // | RGraph is dual-licensed under the Open Source GPL license. That means that it's | // | free to use and there are no restrictions on what you can use RGraph for! | // | If the GPL license does not suit you however, then there's an inexpensive | // | commercial license option available. See the URL above for more details. | // o---------------------------------------------------------------------------------o RGraph = window.RGraph || {isrgraph:true,isRGraph:true,rgraph:true}; RGraph.SVG = RGraph.SVG || {}; // Module pattern (function (win, doc, undefined) { RGraph.SVG.Bar = 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') { name = ret.name; value = ret.value; this.set(name, value); } } } else { // Go through all of the properties and make sure // that they're using the correct capitalisation name = this.properties_lowercase_map[name.toLowerCase()] || name; var ret = RGraph.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 = RGraph.SVG.arrayClone(value); this.colorsParsed = false; } } return this; }; // // A getter. // // @param name string The name of the property to get // this.get = function (name) { // Go through all of the properties and make sure // that they're using the correct capitalisation name = this.properties_lowercase_map[name.toLowerCase()] || name; return this.properties[name]; }; this.type = 'bar'; this.id = conf.id; this.uid = RGraph.SVG.createUID(); this.container = document.getElementById(this.id); this.layers = {}; // MUST be before the SVG tag is created! this.svg = RGraph.SVG.createSVG({object: this,container: this.container}); this.svgAllGroup = RGraph.SVG.createAllGroup(this); this.clipid = null; // Used to clip the canvas this.isRGraph = true; this.isrgraph = true; this.rgraph = true; this.data = conf.data; this.coords = []; this.coords2 = []; this.stackedBackfaces = []; this.originalColors = {}; this.gradientCounter = 1; this.firstDraw = true; // After the first draw this will be false // Convert strings to numbers this.data = RGraph.SVG.stringsToNumbers(this.data); // Add this object to the ObjectRegistry RGraph.SVG.OR.add(this); this.container.style.display = 'inline-block'; this.properties = { marginLeft: 35, marginRight: 35, marginTop: 35, marginBottom: 35, variant: null, variant3dOffsetx: 10, variant3dOffsety: 5, backgroundColor: null, backgroundImage: null, backgroundImageAspect: 'none', backgroundImageStretch: true, backgroundImageOpacity: null, backgroundImageX: null, backgroundImageY: null, backgroundImageW: null, backgroundImageH: null, backgroundGrid: true, backgroundGridColor: '#ddd', backgroundGridLinewidth: 1, backgroundGridHlines: true, backgroundGridHlinesCount: null, backgroundGridVlines: true, backgroundGridVlinesCount: null, backgroundGridBorder: true, backgroundGridDashed: false, backgroundGridDotted: false, backgroundGridDashArray: 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, colorsStroke: 'rgba(0,0,0,0)', errorbars: null, marginInner: 3, marginInnerGrouped: 2, marginInnerLeft: 0, marginInnerRight: 0, yaxis: true, yaxisLinewidth: 1, yaxisTickmarks: true, yaxisTickmarksLength: 3, yaxisColor: 'black', yaxisScale: true, yaxisLabels: null, yaxisLabelsFont: null, yaxisLabelsSize: null, yaxisLabelsColor: null, yaxisLabelsBold: null, yaxisLabelsItalic: null, yaxisLabelsOffsetx: 0, yaxisLabelsOffsety: 0, yaxisLabelsCount: 5, yaxisScaleUnitsPre: '', yaxisScaleUnitsPost: '', yaxisScaleStrict: false, yaxisScaleDecimals: 0, yaxisScalePoint: '.', yaxisScaleThousand: ',', yaxisScaleRound: false, yaxisScaleMax: null, yaxisScaleMin: 0, yaxisScaleFormatter: null, yaxisTitle: '', yaxisTitleBold: null, yaxisTitleSize: null, yaxisTitleFont: null, yaxisTitleColor: null, yaxisTitleItalic: null, yaxisTitleOffsetx: 0, yaxisTitleOffsety: 0, yaxisTitleX: null, yaxisTitleY: null, yaxisTitleHalign: null, yaxisTitleValign: null, xaxis: true, xaxisLinewidth: 1, xaxisTickmarks: true, xaxisTickmarksLength: 5, xaxisLabels: null, xaxisLabelsFont: null, xaxisLabelsSize: null, xaxisLabelsColor: null, xaxisLabelsBold: null, xaxisLabelsItalic: null, xaxisLabelsPosition: 'section', xaxisLabelsPositionSectionTickmarksCount: null, xaxisLabelsOffsetx: 0, xaxisLabelsOffsety: 0, xaxisLabelsFormattedDecimals: 0, xaxisLabelsFormattedPoint: '.', xaxisLabelsFormattedThousand: ',', xaxisLabelsFormattedUnitsPre: '', xaxisLabelsFormattedUnitsPost: '', xaxisColor: 'black', xaxisTitle: '', xaxisTitleBold: null, xaxisTitleSize: null, xaxisTitleFont: null, xaxisTitleColor: null, xaxisTitleItalic: null, xaxisTitleOffsetx: 0, xaxisTitleOffsety: 0, xaxisTitleX: null, xaxisTitleY: null, xaxisTitleHalign: null, xaxisTitleValign: null, 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, labelsAboveHalign: 'center', labelsAboveValign: 'bottom', labelsAboveSpecific: null, textColor: 'black', textFont: 'Arial, Verdana, sans-serif', textSize: 12, textBold: false, textItalic: false, text: null, linewidth: 1, grouping: 'grouped', tooltips: null, tooltipsOverride: null, tooltipsEffect: 'fade', tooltipsCssClass: 'RGraph_tooltip', tooltipsCss: null, tooltipsEvent: 'click', tooltipsFormattedThousand: ',', tooltipsFormattedPoint: '.', tooltipsFormattedDecimals: 0, tooltipsFormattedUnitsPre: '', tooltipsFormattedUnitsPost: '', tooltipsFormattedKeyColors: null, tooltipsFormattedKeyColorsShape: 'square', tooltipsFormattedKeyLabels: [], tooltipsFormattedTableHeaders: null, tooltipsFormattedTableData: null, tooltipsPointer: true, tooltipsPointerOffsetx: 0, tooltipsPointerOffsety: 0, tooltipsPositionStatic: true, highlightStroke: 'rgba(0,0,0,0)', highlightFill: 'rgba(255,255,255,0.7)', highlightLinewidth: 1, title: '', titleX: null, titleY: null, titleHalign: 'center', //titleValign: null, titleSize: null, titleColor: null, titleFont: null, titleBold: null, titleItalic: null, titleSubtitle: null, titleSubtitleSize: null, titleSubtitleColor: '#aaa', titleSubtitleFont: null, titleSubtitleBold: null, titleSubtitleItalic: null, shadow: false, shadowOffsetx: 2, shadowOffsety: 2, shadowBlur: 2, shadowColor: 'rgba(0,0,0,0.25)', errorbars: null, errorbarsColor: 'black', errorbarsLinewidth: 1, errorbarsCapwidth: 10, key: null, keyColors: null, keyOffsetx: 0, keyOffsety: 0, keyLabelsOffsetx: 0, keyLabelsOffsety: -1, keyLabelsColor: null, keyLabelsSize: null, keyLabelsBold: null, keyLabelsItalic: null, keyLabelsFont: null, clip: null }; // // Add the reverse look-up table for property names // so that property names can be specified in any case. // this.properties_lowercase_map = []; for (var i in this.properties) { if (typeof i === 'string') { this.properties_lowercase_map[i.toLowerCase()] = i; } } // // Copy the global object properties to this instance // RGraph.SVG.getGlobals(this); // // "Decorate" the object with the generic effects if the effects library has been included // if (RGraph.SVG.FX && typeof RGraph.SVG.FX.decorate === 'function') { RGraph.SVG.FX.decorate(this); } // Add the responsive function to the object this.responsive = RGraph.SVG.responsive; // A shortcut var properties = this.properties; // // The draw method draws the Bar chart // this.draw = function () { // Fire the beforedraw event RGraph.SVG.fireCustomEvent(this, 'onbeforedraw'); // Should the first thing that's done in the.draw() // function except for the onbeforedraw event and the // installation of clipping. this.width = Number(this.svg.getAttribute('width')); this.height = Number(this.svg.getAttribute('height')); // Zero these if the 3D effect is not wanted if (properties.variant !== '3d') { properties.variant3dOffsetx = 0; properties.variant3dOffsety = 0; } else { // Set the skew transform on the all group if necessary this.svgAllGroup.setAttribute('transform', 'skewY(5)'); } // Create the defs tag if necessary RGraph.SVG.createDefs(this); // Reset the coords array this.coords = []; this.coords2 = []; this.graphWidth = this.width - properties.marginLeft - properties.marginRight; this.graphHeight = this.height - properties.marginTop - properties.marginBottom; // Make the data sequential first this.data_seq = RGraph.SVG.arrayLinearize(this.data); // This allows the errorbars to be a variety of formats and convert // them all into an array of objects which have the min and max // properties set if (properties.errorbars) { // Go through the error bars and convert numbers to objects for (var i=0; i // element // RGraph.SVG.Bar.adjusting_mousemove_chart_update_function = function (opt) { if (!opt || !opt.object) { return; } var index = opt.object.getIndexByX(opt.event); var x = opt.event.offsetX; var value = opt.object.getValue(window.event); var xaxisY = opt.object.height - opt.object.get('marginBottom'); var barHeight = opt.object.getHeight(value); if (RGraph.SVG.isNumber(index)) { // // Set the new Cooordinates in the // coords array // //opt.object.coords[index].element.setAttribute('y', xaxisY - barHeight); //opt.object.coords[index].element.setAttribute('height', Math.abs(barHeight)); // // Update the y coord and height values held // in the coords array // //opt.object.coords[index].y = xaxisY - barHeight; //opt.object.coords[index].height = barHeight; // // Update the data-value attribute // //opt.object.coords[index].element.setAttribute('data-value', value); // // Update the objects data // var data_index = parseInt(opt.object.coords[index].element.getAttribute('data-index')); var data_subindex = parseInt(opt.object.coords[index].element.getAttribute('data-subindex')); // Grouped chart if (RGraph.SVG.isNumber(data_subindex) && data_subindex >= 0) { opt.object.data[data_index][data_subindex] = value; // Regular chart } else { opt.object.data[data_index] = value; } opt.object.coords[index].element.setAttribute('data-value',value); } }; RGraph.SVG.runOnce('svg-bar-adjusting-svg-mousedown-listener-' + this.uid, function () { obj.container.addEventListener('mousedown', function (e) { var index = obj.getIndexByX(e); if (obj.coords && obj.coords[index]) { obj.adjusting_mousedown = { object: obj, element: obj.coords[index].element, index: index, event: e }; // Add this so that people can get the // details of the adjusting if they need // to obj.adjusting_mousedown_last = obj.adjusting_mousedown; // Fire the adjustbegin event RGraph.SVG.fireCustomEvent(obj, 'adjustbegin'); } RGraph.SVG.Bar.adjusting_mousemove_chart_update_function(obj.adjusting_mousedown); // // Redraw the SVG // var tmp = obj.adjusting_mousedown; RGraph.SVG.redraw(obj.svg); obj.adjusting_mousedown = tmp; }, false); }); RGraph.SVG.runOnce('svg-bar-adjusting-svg-mousemove-listener-' + this.uid, function () { obj.container.addEventListener('mousemove', function (e) { if (obj.adjusting_mousedown) { RGraph.SVG.Bar.adjusting_mousemove_chart_update_function(obj.adjusting_mousedown); var tmp = obj.adjusting_mousedown; obj.adjusting_mousedown = null; RGraph.SVG.redraw(obj.svg); obj.adjusting_mousedown = tmp; obj.adjusting_mousedown_last = { object: obj, element: obj.coords[tmp.index].element, event: e }; // Fire the adjust event RGraph.SVG.fireCustomEvent(obj, 'adjust'); } }, false); }); RGraph.SVG.runOnce('svg-bar-adjusting-window-mouseup-listener-' + this.id, function () { window.addEventListener('mouseup', function (e) { for (let i=0,objects=RGraph.SVG.OR.objects; i= 0 && this.scale.max > 0) { y = this.getYCoord(this.scale.min) - height; } else if (this.scale.min < 0 && this.scale.max > 0) { height = (Math.abs(this.data[i]) / (this.scale.max - this.scale.min)) * this.graphHeight; y = this.getYCoord(0) - height; if (this.data[i] < 0) { y = this.getYCoord(0); } } else if (this.scale.min < 0 && this.scale.max <= 0) { height = (Math.abs(this.data[i]) - Math.abs(this.scale.max)) / (Math.abs(this.scale.min) - Math.abs(this.scale.max)) * this.graphHeight; y = properties.marginTop; } var rect = RGraph.SVG.create({ svg: this.svg, type: 'rect', parent: properties.variant === '3d' && this.data[i] < 0 ? this.threed_xaxis_group : this.svgAllGroup, attr: { stroke: properties.colorsStroke, fill: properties.colorsSequential ? (properties.colors[sequentialIndex] ? properties.colors[sequentialIndex] : properties.colors[properties.colors.length - 1]) : properties.colors[0], x: x, y: y, width: width < 0 ? 0 : width, height: height, 'stroke-width': properties.linewidth, 'data-original-x': x, 'data-original-y': y, 'data-original-width': width, 'data-original-height': height, 'data-tooltip': (!RGraph.SVG.isNull(properties.tooltips) && properties.tooltips.length) ? properties.tooltips[i] : '', 'data-index': i, 'data-sequential-index': sequentialIndex, 'data-value': this.data[i], filter: properties.shadow ? 'url(#dropShadow)' : '' } }); // Draw the errorbar if required this.drawErrorbar({ object: this, element: rect, index: i, value: this.data[i], type: 'normal' }); 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')) }); if (!this.coords2[0]) { this.coords2[0] = []; } this.coords2[0].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')) }); // // Add the 3D faces if required // if (properties.variant === '3d') { this.drawTop3dFace({rect: rect, value: this.data[i]}); this.drawSide3dFace({rect: rect, value: this.data[i]}); } // Add the tooltip data- attribute if ( !RGraph.SVG.isNull(properties.tooltips) && (!RGraph.SVG.isNull(properties.tooltips[sequentialIndex]) || typeof properties.tooltips === 'string') ) { var obj = this; // // Add tooltip event listeners // (function (idx, seq) { rect.addEventListener(properties.tooltipsEvent.replace(/^on/, ''), function (e) { obj.removeHighlight(); // Show the tooltip RGraph.SVG.tooltip({ object: obj, index: idx, group: null, sequentialIndex: seq, text: typeof properties.tooltips === 'string' ? properties.tooltips : properties.tooltips[seq], event: e }); // Highlight the rect that has been clicked on obj.highlight(e.target); }, false); rect.addEventListener('mousemove', function (e) { e.target.style.cursor = 'pointer' }, false); })(i, sequentialIndex); } // // GROUPED BARS // } else if (RGraph.SVG.isArray(this.data[i]) && properties.grouping === 'grouped') { var outerSegment = ( (this.graphWidth - properties.marginInnerLeft - properties.marginInnerRight) / this.data.length), innerSegment = outerSegment - (2 * properties.marginInner); // Loop through the group for (var j=0; j 10 if (this.scale.min === 0 && this.scale.max > this.scale.min) { var height = ((this.data[i][j] - this.scale.min) / (this.scale.max - this.scale.min)) * this.graphHeight, y = this.getYCoord(0) - height; // eg -5 -> -15 } else if (this.scale.max <= 0 && this.scale.min < this.scale.max) { var height = ((this.data[i][j] - this.scale.max) / (this.scale.max - this.scale.min)) * this.graphHeight, y = this.getYCoord(this.scale.max); height = Math.abs(height); // eg 10 -> -10 } else if (this.scale.max > 0 && this.scale.min < 0) { var height = (Math.abs(this.data[i][j]) / (this.scale.max - this.scale.min)) * this.graphHeight, y = this.data[i][j] < 0 ? this.getYCoord(0) : this.getYCoord(this.data[i][j]); // eg 5 -> 10 } else if (this.scale.min > 0 && this.scale.max > this.scale.min) { var height = (Math.abs(this.data[i][j] - this.scale.min) / (this.scale.max - this.scale.min)) * this.graphHeight, y = this.getYCoord(this.scale.min) - height; } // Add the rect tag var rect = RGraph.SVG.create({ svg: this.svg, parent: properties.variant === '3d' && this.data[i][j] < 0 ? this.threed_xaxis_group : this.svgAllGroup, type: 'rect', attr: { stroke: properties.colorsStroke, fill: (properties.colorsSequential && properties.colors[sequentialIndex]) ? properties.colors[sequentialIndex] : properties.colors[j], x: x, y: y, width: width, height: height, 'stroke-width': properties.linewidth, 'data-original-x': x, 'data-original-y': y, 'data-original-width': width, 'data-original-height': height, 'data-index': i, 'data-subindex': j, 'data-sequential-index': sequentialIndex, 'data-tooltip': (!RGraph.SVG.isNull(properties.tooltips) && properties.tooltips.length) ? properties.tooltips[sequentialIndex] : '', 'data-value': this.data[i][j], filter: properties.shadow ? 'url(#dropShadow)' : '' } }); // Draw the errorbar if required this.drawErrorbar({ object: this, element: rect, index: sequentialIndex, value: this.data[i][j], type: 'grouped' }); 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')) }); if (!this.coords2[i]) { this.coords2[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')) }); // // Add the 3D faces if required // if (properties.variant === '3d') { this.drawTop3dFace({rect: rect, value: this.data[i][j]}); this.drawSide3dFace({rect: rect, value: this.data[i][j]}); } // Add the tooltip data- attribute if ( !RGraph.SVG.isNull(properties.tooltips) && (properties.tooltips[sequentialIndex] || typeof properties.tooltips === 'string') ) { var obj = this; // // Add tooltip event listeners // (function (idx, seq) { obj.removeHighlight(); var indexes = RGraph.SVG.sequentialIndexToGrouped(seq, obj.data); rect.addEventListener(properties.tooltipsEvent.replace(/^on/, ''), function (e) { // Show the tooltip RGraph.SVG.tooltip({ object: obj, group: idx, index: indexes[1], sequentialIndex: seq, text: typeof properties.tooltips === 'string' ? properties.tooltips : properties.tooltips[seq], event: e }); // Highlight the rect that has been clicked on obj.highlight(e.target); }, false); rect.addEventListener('mousemove', function (e) { e.target.style.cursor = 'pointer' }, false); })(i, sequentialIndex); } } --sequentialIndex; // // STACKED CHARTS // } else if (RGraph.SVG.isArray(this.data[i]) && properties.grouping === 'stacked') { var section = ( (this.graphWidth - properties.marginInnerLeft - properties.marginInnerRight) / this.data.length); // Intialise the Y coordinate to the bottom gutter var y = this.getYCoord(0); // Loop through the stack for (var j=0; j this.scale.max) { return null; } var y, xaxispos = properties.xaxispos; if (value < this.scale.min) { return null; } y = ((value - this.scale.min) / (this.scale.max - this.scale.min)); y *= (this.height - properties.marginTop - properties.marginBottom); y = this.height - properties.marginBottom - y; return y; }; // // This function can be used to retrieve the relevant index // based on the given X coordinate // // @param int x The X coordinate // this.getIndexByX = function (e) { var x = e.offsetX; // Go thru the coords looking for an element wich // fits the X coord (if any). for (let i=0; i= this.coords[i].x && x <= (this.coords[i].x + this.coords[i].width) ) { return i; } } return null; }; // // This function can be used to retrieve the value for the // given coordinate. // // @param int e The event object // this.getValue = function (e) { var graphHeight = this.height - this.properties.marginTop - this.properties.marginBottom; if (this.mirrorScale) { var coordY = (graphHeight / 2) - (e.offsetY - this.properties.marginTop); var value = ((this.scale.max - this.min) / graphHeight) * coordY; } else if (properties.yaxisScaleMax === 0 && properties.yaxisScaleMin < 0) { var coordY = graphHeight - (e.offsetY - this.properties.marginTop); var value = ((this.scale.max - this.min) / graphHeight) * coordY; var value = value - Math.abs(this.scale.min); } else { var coordY = graphHeight - (e.offsetY - this.properties.marginTop); var value = (((this.scale.max - this.scale.min) / graphHeight) * coordY) + this.scale.min; } // Constrain the value to the maximum and minimum if (value > this.scale.max) value = this.scale.max; if (value < this.scale.min) value = this.scale.min; return value; }; // // This function can be used to retrieve the height of a // bar for a given value. // // @param int value The value to get the height for // this.getHeight = function (value) { var height = (this.graphHeight / (this.scale.max - this.scale.min)) * value; return height; }; // // 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 = parseFloat(rect.getAttribute('x')) - 0.5, y = parseFloat(rect.getAttribute('y')) - 0.5, width = parseFloat(rect.getAttribute('width')) + 1, height = parseFloat(rect.getAttribute('height')) + 1; var highlight = RGraph.SVG.create({ svg: this.svg, parent: this.svgAllGroup, type: 'rect', attr: { stroke: properties.highlightStroke, fill: properties.highlightFill, x: x, y: y, width: width, height: height, 'stroke-width': properties.highlightLinewidth }, style: { pointerEvents: 'none' } }); if (properties.tooltipsEvent === 'mousemove') { //var obj = this; //highlight.addEventListener('mouseout', function (e) //{ // obj.removeHighlight(); // RGraph.SVG.hideTooltip(); // RGraph.SVG.REG.set('highlight', null); //}, false); } // Store the highlight rect in the rebistry so // it can be cleared later RGraph.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: RGraph.SVG.arrayClone(properties.colors), backgroundGridColor: RGraph.SVG.arrayClone(properties.backgroundGridColor), highlightFill: RGraph.SVG.arrayClone(properties.highlightFill), backgroundColor: RGraph.SVG.arrayClone(properties.backgroundColor) } } // colors var colors = properties.colors; if (colors) { for (var i=0; i= 0) { var y = parseFloat(this.coords[i].element.getAttribute('y')) - 7 + properties.labelsAboveOffsety; var valign = properties.labelsAboveValign; } else { var y = parseFloat(this.coords[i].element.getAttribute('y')) + parseFloat(this.coords[i].element.getAttribute('height')) + 7 - properties.labelsAboveOffsety; var valign = properties.labelsAboveValign === 'top' ? 'bottom' : 'top'; } var textConf = RGraph.SVG.getTextConf({ object: this, prefix: 'labelsAbove' }); RGraph.SVG.text({ object: this, parent: this.svgAllGroup, tag: 'labels.above', text: str, x: x, y: y, halign: properties.labelsAboveHalign, valign: valign, font: textConf.font, size: textConf.size, bold: textConf.bold, italic: textConf.italic, color: textConf.color, background: properties.labelsAboveBackground || null, padding: properties.labelsAboveBackgroundPadding || 0 }); } } }; // // 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; } RGraph.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 = RGraph.SVG.REG.get('highlight'); //if (highlight && highlight.parentNode) { // highlight.parentNode.removeChild(highlight); //} //RGraph.SVG.REG.set('highlight', null); RGraph.SVG.removeHighlight(); }; // // Draws the top of 3D bars // this.drawTop3dFace = function (opt) { var rect = opt.rect, arr = [parseInt(rect.getAttribute('fill')), 'rgba(255,255,255,0.7)'], x = parseInt(rect.getAttribute('x')), y = parseInt(rect.getAttribute('y')), w = parseInt(rect.getAttribute('width')), h = parseInt(rect.getAttribute('height')), value = parseFloat(rect.getAttribute('data-value')); rect.rgraph_3d_top_face = []; for (var i=0; i<2; ++i) { var color = (i === 0 ? rect.getAttribute('fill') : 'rgba(255,255,255,0.7)'); var face = RGraph.SVG.create({ svg: this.svg, type: 'path', parent: properties.variant === '3d' && opt.value < 0 ? this.threed_xaxis_group : this.svgAllGroup, attr: { stroke: properties.colorsStroke, fill: color, 'stroke-width': properties.linewidth, d: 'M {1} {2} L {3} {4} L {5} {6} L {7} {8}'.format( x, y, x + properties.variant3dOffsetx, y - properties.variant3dOffsety, x + w + properties.variant3dOffsetx, y - properties.variant3dOffsety, x + w, y ) } }); // Store a reference to the rect on the front face of the bar rect.rgraph_3d_top_face[i] = face } }; // // Draws the top of 3D bars // this.drawSide3dFace = function (opt) { var rect = opt.rect, arr = [parseInt(rect.getAttribute('fill')), 'rgba(0,0,0,0.3)'], x = parseInt(rect.getAttribute('x')), y = parseInt(rect.getAttribute('y')), w = parseInt(rect.getAttribute('width')), h = parseInt(rect.getAttribute('height')); rect.rgraph_3d_side_face = []; for (var i=0; i<2; ++i) { var color = (i === 0 ? rect.getAttribute('fill') : 'rgba(0,0,0,0.3)'); var face = RGraph.SVG.create({ svg: this.svg, type: 'path', parent: properties.variant === '3d' && opt.value < 0 ? this.threed_xaxis_group : this.svgAllGroup, attr: { stroke: properties.colorsStroke, fill: color, 'stroke-width': properties.linewidth, d: 'M {1} {2} L {3} {4} L {5} {6} L {7} {8}'.format( x + w, y, x + w + properties.variant3dOffsetx, y - properties.variant3dOffsety, x + w + properties.variant3dOffsetx, y + h - properties.variant3dOffsety, x + w, y + h ) } }); // Store a reference to the rect on the front face of the bar rect.rgraph_3d_side_face[i] = face } }; // This function is used to draw the errorbar. Its in the common // file because it's used by multiple chart libraries this.drawErrorbar = function (opt) { var index = opt.index, datapoint = opt.value, linewidth = RGraph.SVG.getErrorbarsLinewidth({object: this, index: index}), color = RGraph.SVG.getErrorbarsColor({object: this, index: index}), capwidth = RGraph.SVG.getErrorbarsCapWidth({object: this, index: index}), element = opt.element, type = opt.type; // Get the error bar value var max = RGraph.SVG.getErrorbarsMaxValue({ object: this, index: index }); // Get the error bar value var min = RGraph.SVG.getErrorbarsMinValue({ object: this, index: index }); if (!max && !min) { return; } // Accounts for stacked bars if (type === 'stacked') { datapoint = RGraph.SVG.arraySum(this.data[index]); } if (datapoint >= 0) { var x1 = parseFloat(element.getAttribute('x')) + (parseFloat(element.getAttribute('width')) / 2); // Draw the UPPER vertical line var errorbarLine = RGraph.SVG.create({ svg: this.svg, type: 'line', parent: this.svgAllGroup, attr: { x1: x1, y1: parseFloat(element.getAttribute('y')), x2: x1, y2: this.getYCoord(parseFloat(datapoint + max)), stroke: color, 'stroke-width': linewidth } }); // Draw the cap to the UPPER line var errorbarCap = RGraph.SVG.create({ svg: this.svg, type: 'line', parent: this.svgAllGroup, attr: { x1: parseFloat(errorbarLine.getAttribute('x1')) - (capwidth / 2), y1: errorbarLine.getAttribute('y2'), x2: parseFloat(errorbarLine.getAttribute('x1')) + (capwidth / 2), y2: errorbarLine.getAttribute('y2'), stroke: color, 'stroke-width': linewidth } }); // Draw the minimum errorbar if necessary if (typeof min === 'number') { var errorbarLine = RGraph.SVG.create({ svg: this.svg, type: 'line', parent: this.svgAllGroup, attr: { x1: x1, y1: parseFloat(element.getAttribute('y')), x2: x1, y2: this.getYCoord(parseFloat(datapoint - min)), stroke: color, 'stroke-width': linewidth } }); // Draw the cap to the UPPER line var errorbarCap = RGraph.SVG.create({ svg: this.svg, type: 'line', parent: this.svgAllGroup, attr: { x1: parseFloat(errorbarLine.getAttribute('x1')) - (capwidth / 2), y1: errorbarLine.getAttribute('y2'), x2: parseFloat(errorbarLine.getAttribute('x1')) + (capwidth / 2), y2: errorbarLine.getAttribute('y2'), stroke: color, 'stroke-width': linewidth } }); } } else if (datapoint < 0) { var x1 = parseFloat(element.getAttribute('x')) + (parseFloat(element.getAttribute('width')) / 2), y1 = parseFloat(element.getAttribute('y')) + parseFloat(element.getAttribute('height')), y2 = this.getYCoord(parseFloat(datapoint - Math.abs(max) )) // Draw the vertical line var errorbarLine = RGraph.SVG.create({ svg: this.svg, type: 'line', parent: this.svgAllGroup, attr: { x1: x1, y1: y1, x2: x1, y2: y2, stroke: color, 'stroke-width': linewidth } }); // Draw the cap to the vertical line var errorbarCap = RGraph.SVG.create({ svg: this.svg, type: 'line', parent: this.svgAllGroup, attr: { x1: parseFloat(errorbarLine.getAttribute('x1')) - (capwidth / 2), y1: errorbarLine.getAttribute('y2'), x2: parseFloat(errorbarLine.getAttribute('x1')) + (capwidth / 2), y2: errorbarLine.getAttribute('y2'), stroke: color, 'stroke-width': linewidth } }); // Draw the minimum errorbar if necessary if (typeof min === 'number') { var x1 = parseFloat(element.getAttribute('x')) + (parseFloat(element.getAttribute('width')) / 2); var errorbarLine = RGraph.SVG.create({ svg: this.svg, type: 'line', parent: this.svgAllGroup, attr: { x1: x1, y1: this.getYCoord(parseFloat(datapoint + min)), x2: x1, y2: this.getYCoord(parseFloat(datapoint)), stroke: color, 'stroke-width': linewidth } }); // Draw the cap to the UPPER line var errorbarCap = RGraph.SVG.create({ svg: this.svg, type: 'line', parent: this.svgAllGroup, attr: { x1: parseFloat(errorbarLine.getAttribute('x1')) - (capwidth / 2), y1: errorbarLine.getAttribute('y1'), x2: parseFloat(errorbarLine.getAttribute('x1')) + (capwidth / 2), y2: errorbarLine.getAttribute('y1'), stroke: color, 'stroke-width': linewidth } }); } } }; // // The Bar chart grow effect // this.grow = function () { var opt = arguments[0] || {}, frames = opt.frames || 30, frame = 0, obj = this, data = [], height = null, seq = 0; // // Copy the data // data = RGraph.SVG.arrayClone(this.data); this.draw(); var iterate = function () { for (var i=0,seq=0,len=obj.coords.length; i 1 ? opt.delay : 200); RGraph.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 () { // First draw the chart this.draw(); var obj = this, opt = arguments[0] || {}; opt.frames = opt.frames || 60; opt.startFrames = []; opt.counters = []; var framesperbar = opt.frames / 3, frame = -1, callback = opt.callback || function () {}; for (var i=0,len=this.coords.length; i opt.startFrames[i]) { var originalHeight = el.getAttribute('data-original-height'), height, value = parseFloat(el.getAttribute('data-value')); var height = Math.min( ((frame - opt.startFrames[i]) / framesperbar) * originalHeight, originalHeight ); el.setAttribute( 'height', height < 0 ? 0 : height ); el.setAttribute( 'y', value >=0 ? obj.getYCoord(0) - height : obj.getYCoord(0) ); // This updates the size of the 3D sides to the bar if (properties.variant === '3d') { // Remove the 3D sides to the bar var parent = el.rgraph_3d_side_face[0].parentNode; if (parent) parent.removeChild(el.rgraph_3d_side_face[0]); if (parent) parent.removeChild(el.rgraph_3d_side_face[1]); var parent = el.rgraph_3d_top_face[0].parentNode; if (parent) parent.removeChild(el.rgraph_3d_top_face[0]); if (parent) parent.removeChild(el.rgraph_3d_top_face[1]); // Now remove and immediately re-add the front face of // the bar - this is so that the front face appears // above the other sides if (el.parentNode) { var parent = el.parentNode; var node = parent.removeChild(el); parent.appendChild(node); } } if (properties.grouping === 'stacked') { var seq = el.getAttribute('data-sequential-index'); var indexes = RGraph.SVG.sequentialIndexToGrouped(seq, obj.data); if (indexes[1] > 0) { el.setAttribute( 'y', parseFloat(obj.coords[i - 1].element.getAttribute('y')) - height + 1 ); } } if (properties.variant === '3d') { // Add the 3D sides to the bar (again) obj.drawSide3dFace({ rect: el, value: el.getAttribute('data-value') }); // Draw the top side of the 3D bar if (properties.grouping === 'grouped' || (properties.grouping === 'stacked' && (indexes[1] + 1) === obj.data[indexes[0]].length) ) { obj.drawTop3dFace({ rect: el, value: el.getAttribute('data-value') }); } } } } if (frame >= opt.frames) { callback(obj); } else { RGraph.SVG.FX.update(iterator); } } iterator(); return this; }; // // A worker function that handles Bar chart specific tooltip substitutions // this.tooltipSubstitutions = function (opt) { var indexes = RGraph.SVG.sequentialIndexToGrouped(opt.index, this.data); return { index: indexes[1], dataset: indexes[0], sequentialIndex: opt.index, value: typeof this.data[indexes[0]] === 'number' ? this.data[indexes[0]] : this.data[indexes[0]][indexes[1]], values: typeof this.data[indexes[0]] === 'number' ? [this.data[indexes[0]]] : this.data[indexes[0]] }; }; // // A worker function that returns the correct color/label/value // // @param object specific The indexes that are applicable // @param number index The appropriate index // this.tooltipsFormattedCustom = function (specific, index) { if (typeof this.data[0] === 'object') { var label = (!RGraph.SVG.isNull(properties.tooltipsFormattedKeyLabels) && typeof properties.tooltipsFormattedKeyLabels === 'object' && properties.tooltipsFormattedKeyLabels[index]) ? properties.tooltipsFormattedKeyLabels[index] : ''; } else { var label = ( !RGraph.SVG.isNull(properties.tooltipsFormattedKeyLabels) && typeof properties.tooltipsFormattedKeyLabels === 'object' && properties.tooltipsFormattedKeyLabels[specific.index]) ? properties.tooltipsFormattedKeyLabels[specific.index] : ''; } return { label: label }; }; // // This allows for static tooltip positioning // this.positionTooltipStatic = function (args) { var obj = args.object, e = args.event, tooltip = args.tooltip, index = args.index, svgXY = RGraph.SVG.getSVGXY(obj.svg), coords = this.coords[args.index]; // Position the tooltip in the X direction args.tooltip.style.left = ( svgXY[0] // The X coordinate of the canvas + coords.x // The X coordinate of the bar on the chart - (tooltip.offsetWidth / 2) // Subtract half of the tooltip width + (coords.width / 2) // Add half of the bar width ) + 'px'; // If the chart is a 3D version the tooltip Y position needs this // adjustment var adjustment = 0; if (properties.variant === '3d') { var left = coords.x; var top = coords.y; var angle = 5 / (180 / Math.PI); var adjustment = Math.tan(angle) * left; } args.tooltip.style.top = ( svgXY[1] // The Y coordinate of the canvas + coords.y // The Y coordinate of the bar on the chart - tooltip.offsetHeight // The height of the tooltip - 15 // An arbitrary amount + adjustment // Account for the 3D ) + 'px'; // If the bar is a negative one, add half the height to the Y coord var data_arr = RGraph.SVG.arrayLinearize(this.data); if (data_arr[index] < 0) { args.tooltip.style.top = parseFloat(args.tooltip.style.top) + (coords.height / 2) + 'px'; } // If the top of the tooltip is off the top of the page // then move the tooltip down if(parseFloat(args.tooltip.style.top) < 0) { args.tooltip.style.top = parseFloat(args.tooltip.style.top) + 20 + 'px'; } }; // // This function handles clipping to scale values. Because // each chart handles scales differently, a worker function // is needed instead of it all being done centrally. // // @param object clipPath The node // this.clipToScaleWorker = function (clipPath) { // The Regular expression is actually done by the // calling RGraph.clipTo.start() function in the core // library if (RegExp.$1 === 'min') from = this.min; else from = Number(RegExp.$1); if (RegExp.$2 === 'max') to = this.max; else to = Number(RegExp.$2); var width = this.width, y1 = this.getYCoord(from), y2 = this.getYCoord(to), height = Math.abs(y2 - y1), x = 0, y = Math.min(y1, y2); // Increase the height if the maximum value is "max" if (RegExp.$2 === 'max') { y = 0; height += this.properties.marginTop; } // Increase the height if the minimum value is "min" if (RegExp.$1 === 'min') { height += this.properties.marginBottom; } RGraph.SVG.create({ svg: this.svg, type: 'rect', parent: clipPath, attr: { x: x, y: y, width: width, height: height } }); // Now set the clip-path attribute on the first // Line charts all-elements group this.svgAllGroup.setAttribute( 'clip-path', 'url(#' + clipPath.id + ')' ); }; // // 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);