// 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.HBar = 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]; }; // Convert strings to numbers conf.data = RGraph.SVG.stringsToNumbers(conf.data); this.type = 'hbar'; 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.width = Number(this.svg.getAttribute('width')); this.height = Number(this.svg.getAttribute('height')); this.data = conf.data; this.coords = []; this.coords2 = []; this.coordsSpline = []; this.stackedBackfaces = []; this.colorsParsed = false; this.originalColors = {}; this.gradientCounter = 1; this.isTrace = false; // Used for the vertical line trace effect this.firstDraw = true; // After the first draw this will be false // Add this object to the ObjectRegistry RGraph.SVG.OR.add(this); this.container.style.display = 'inline-block'; this.properties = { marginLeft: 100, marginRight: 35, marginRightAuto: null, marginTop: 35, marginBottom: 35, marginLeftAuto: true, 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)', marginInner: 3, marginInnerGrouped: 2, marginInnerTop: 0, marginInnerBottom: 0, xaxis: true, xaxisLinewidth: 1, xaxisTickmarks: true, xaxisTickmarksLength: 5, xaxisColor: 'black', xaxisLabels: [], xaxisLabelsOffsetx: 0, xaxisLabelsOffsety: 0, xaxisLabelsCount: 5, xaxisScale: true, xaxisScaleUnitsPre: '', xaxisScaleUnitsPost: '', xaxisScaleStrict: false, xaxisScaleDecimals: 0, xaxisScaleThousand: '.', xaxisScaleThousand: ',', xaxisScaleRound: false, xaxisScaleMax: null, xaxisScaleMin: 0, xaxisScaleFormatter: null, xaxisLabelsPositionEdgeTickmarksCount: null, xaxisLabelsColor: null, xaxisLabelsBold: null, xaxisLabelsItalic: null, xaxisLabelsFont: null, xaxisLabelsSize: null, xaxisTitle: '', xaxisTitleBold: null, xaxisTitleSize: null, xaxisTitleFont: null, xaxisTitleColor: null, xaxisTitleItalic: null, xaxisTitleOffsetx: 0, xaxisTitleOffsety: 0, xaxisTitleX: null, xaxisTitleY: null, xaxisTitleHalign: null, xaxisTitleValign: null, yaxis: true, yaxisLinewidth: 1, yaxisTickmarks: true, yaxisTickmarksLength: 3, yaxisTickmarksCount: 5, yaxisLabels: [], yaxisLabelsPosition: 'section', yaxisLabelsOffsetx: 0, yaxisLabelsOffsety: 0, yaxisScale: false, yaxisLabelsPositionSectionTickmarksCount: null, yaxisColor: 'black', yaxisLabelsFont: null, yaxisLabelsSize: null, yaxisLabelsColor: null, yaxisLabelsBold: null, yaxisLabelsItalic: null, yaxisPosition: 'left', yaxisLabelsFormattedDecimals: 0, yaxisLabelsFormattedUnitsPre: '', yaxisLabelsFormattedUnitsPost: '', yaxisLabelsFormattedThousand: ',', yaxisLabelsFormattedPoint: '.', yaxisTitle: '', yaxisTitleBold: null, yaxisTitleSize: null, yaxisTitleFont: null, yaxisTitleColor: null, yaxisTitleItalic: null, yaxisTitleOffsetx: 0, yaxisTitleOffsety: 0, yaxisTitleX: null, yaxisTitleY: null, yaxisTitleHalign: null, yaxisTitleValign: null, textColor: 'black', textFont: 'Arial, Verdana, sans-serif', textSize: 12, textBold: false, textItalic: false, text: 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: null, labelsAboveValign: 'center', labelsAboveSpecific: null, labelsInbar: false, labelsInbarHalign: 'center', labelsInbarValign: 'center', labelsInbarFont: null, labelsInbarSize: null, labelsInbarBold: null, labelsInbarItalic: null, labelsInbarColor: null, labelsInbarBackground: null, labelsInbarBackgroundPadding: 0, labelsInbarUnitsPre: null, labelsInbarUnitsPost: null, labelsInbarPoint: null, labelsInbarThousand: null, labelsInbarFormatter: null, labelsInbarDecimals: null, labelsInbarOffsetx: 0, labelsInbarOffsety: 0, labelsInbarSpecific: 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, titleSubtitleColor: '#aaa', titleSubtitleSize: null, titleSubtitleFont: null, titleSubtitleBold: null, titleSubtitleItalic: null, shadow: false, shadowOffsetx: 2, shadowOffsety: 2, shadowBlur: 2, shadowColor: 'rgba(0,0,0,0.25)', key: null, keyColors: null, keyOffsetx: 0, keyOffsety: 0, keyLabelsOffsetx: 0, keyLabelsOffsety: -1, keyLabelsSize: null, keyLabelsBold: null, keyLabelsItalic: null, keyLabelsColor: null, keyLabelsFont: null, line: false, lineLinejoin: 'round', lineLinecap: 'round', lineLinewidth: 2, lineTickmarksLinewidth: 2, lineTickmarksStyle: null, lineTickmarksSize: 5, lineColor: 'black', lineShadow: true, lineShadowColor: '#666', lineShadowBlur: 2, lineShadowOffsetx: 2, lineShadowOffsety: 2, lineSpline: false, lineTickmarksDrawNonNull: false, 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; var properties = this.properties; // // The draw method draws the Bar chart // this.draw = function () { // Fire the beforedraw event RGraph.SVG.fireCustomEvent(this, 'onbeforedraw'); // // Do the yaxis label substitution. This has to be called // before the left (or right) margin size is calculated. // this.yaxisLabelSubstitution(); // Should be the first(ish) 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')); this.coords = []; this.coords2 = []; // Create the defs tag if necessary RGraph.SVG.createDefs(this); // // Handle the marginLeft autosizing // if (properties.marginLeftAuto) { var textConf = RGraph.SVG.getTextConf({ object: this, prefix: 'yaxisLabels' }); for (var i=0,len=properties.yaxisLabels.length,maxLength=0; i 0 && this.scale.max > 0) ? this.scale.min : 0 ) - (this.data[i] < 0 ? width : 0), y = properties.marginTop + properties.marginInnerTop + properties.marginInner + (outerSegment * i); // Allow for the Y axis to be positioned on the right hand side if (properties.yaxisPosition === 'right' && this.scale.min >= 0) { x = this.getXCoord(this.data[i]); } if (properties.yaxisPosition === 'right' && this.scale.min < 0) { x = this.getXCoord(0); } // If theres a min set but both the min and max are below // zero the bars should be aligned to the right hand // side if (this.scale.min < 0 && this.scale.max < 0) { x = this.width - properties.marginRight - width; } // Adjust for a negative value if (this.mirrorScale && properties.yaxisPosition === 'right') { if (this.data[i] > 0) { x = this.getXCoord(0) - width; } else { x = this.getXCoord(0); } } // If the X axis is right, move the bar left if (this.data[i] > 0 && properties.yaxisPosition === 'right') { x = this.getXCoord(0) - width; } var rect = RGraph.SVG.create({ svg: this.svg, parent: this.svgAllGroup, type: 'rect', 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, height: height, 'stroke-width': properties.linewidth, 'data-tooltip': (!RGraph.SVG.isNull(properties.tooltips) && properties.tooltips.length) ? properties.tooltips[i] : '', 'data-index': i, 'data-original-x': x, 'data-original-y': y, 'data-original-width': width, 'data-original-height': height, 'data-sequential-index': sequentialIndex, 'data-value': this.data[i], filter: properties.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')) }); 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 toooltips if necessary if (!RGraph.SVG.isNull(properties.tooltips) && (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 charts // } else if (RGraph.SVG.isArray(this.data[i]) && properties.grouping === 'grouped') { var outerSegment = ( (this.graphHeight - properties.marginInnerTop - properties.marginInnerBottom) / this.data.length), innerSegment = outerSegment - (2 * properties.marginInner); // Loop through the group for (var j=0; j 0 && this.scale.max > this.scale.min) { var x1 = this.getXCoord(this.data[i][j]); var x2 = this.getXCoord(this.scale.min); x = this.getXCoord(this.scale.min); width = x1 - x2; } ////////////////////////////////////////////////////////////////////////////// // Allow for the Y axis to be positioned on the right hand side if (properties.yaxisPosition === 'right' && this.scale.min === 0) { x = this.getXCoord(this.data[i][j]); } // Allow for the Y axis to be positioned on the right hand side // with a scale of (for example) -5 -> 20 if ( properties.yaxisPosition === 'right' && this.scale.min < 0 && this.scale.max >= 0) { if (this.data[i][j] < 0) { x = this.getXCoord(0); } else { x = this.getXCoord(this.data[i][j]); } } // Fixes an odd bug //if (this.mirrorScale && properties.yaxisPosition === 'right') { // if (this.data[i][j] > 0) { // x -= width; // } else { // x += width; // } //} // // Determine the fill color // var fill; if (properties.colorsSequential) { if (properties.colors[sequentialIndex]) { fill = properties.colors[sequentialIndex]; } } else { if (properties.colors[j]) { fill = properties.colors[j]; } else { fill = properties.colors[properties.colors.length - 1]; } } var rect = RGraph.SVG.create({ svg: this.svg, type: 'rect', parent: this.svgAllGroup, attr: { stroke: properties['colorsStroke'], fill: fill, x: x, y: y, width: width, height: height, 'stroke-width': properties.linewidth, 'data-index': i, 'data-original-x': x, 'data-original-y': y, 'data-original-width': width, 'data-original-height': height, '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)' : '' } }); 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 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) { var indexes = RGraph.SVG.sequentialIndexToGrouped(seq, obj.data); rect.addEventListener(properties.tooltipsEvent.replace(/^on/, ''), function (e) { obj.removeHighlight(); // 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') { // This is each bars "segment" of the chart var section = ( (this.graphHeight - properties.marginInnerTop - properties.marginInnerBottom) / this.data.length); // Initialise the X coordinate var x = this.getXCoord(0); // Loop through the stack for (var j=0; j this.scale.max) { return null; } if (value < this.scale.min) { return null; } var x = ((value - this.scale.min) / (this.scale.max - this.scale.min)); x *= this.graphWidth; if (properties.yaxisPosition === 'right') { x = this.width - properties.marginRight - x; } else { x += properties.marginLeft; } return x; }; // // This function can be used to retrieve the relevant X coordinate for a // particular value. // // @param int value The value to get the X coordinate for // this.getWidth = function (value) { if (this.scale.max <= 0 && this.scale.min < this.scale.max) { var x1 = this.getXCoord(this.scale.max); var x2 = this.getXCoord(value); } else if (this.scale.min > 0 && this.scale.max > this.scale.min) { var x1 = this.getXCoord(this.scale.min); var x2 = this.getXCoord(value); } else { var x1 = this.getXCoord(0); var x2 = this.getXCoord(value); } return Math.abs(x1 - x2); }; //Math.abs(((this.data[i] - this.scale.min) / (this.max - this.scale.min)) * this.graphWidth) // // 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, type: 'rect', parent: this.svgAllGroup, 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') { // highlight.addEventListener('mouseout', function (e) // { // highlight.parentNode.removeChild(highlight); // 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), lineColor: RGraph.SVG.arrayClone(properties.lineColor), lineFilledColor: RGraph.SVG.arrayClone(properties.lineFilledColor) } } // colors var colors = properties.colors; if (colors) { for (var i=0; i= 0) ? (parseFloat(this.coords[i].element.getAttribute('x')) + 7 + properties.labelsAboveOffsetx) : parseFloat(this.coords[i].element.getAttribute('x') - 7 - properties.labelsAboveOffsetx), y = parseFloat(this.coords[i].element.getAttribute('y')) + parseFloat(this.coords[i].element.getAttribute('height') / 2) + properties.labelsAboveOffsety, width = dimensions[0], height = dimensions[1], halign = (value >= 0) ? 'left': 'right'; // Corner case if (properties.yaxisPosition === 'left' && properties.grouping === 'grouped') { x = parseFloat(this.coords[i].element.getAttribute('x')) + parseFloat(this.coords[i].element.getAttribute('width')) + 7 + properties.labelsAboveOffsetx } // ADjust the values if the Y axis is on the RHS if (properties.yaxisPosition === 'right') { x = (value >= 0) ? (parseFloat(this.coords[i].element.getAttribute('x')) - 7 - properties.labelsAboveOffsetx) : parseFloat(this.coords[i].element.getAttribute('x') + 7 + properties.labelsAboveOffsetx), halign = (value >= 0) ? 'right': 'left'; // Special case for an oddity } else if (RGraph.SVG.isArray(this.data[indexes[0]]) && properties.grouping === 'stacked' && properties.yaxisPosition === 'left') { x += this.coords2[indexes[0]][indexes[1]].width; } // Another corner case if ( properties.yaxisPosition === 'right' && properties.grouping === 'grouped' && properties.xaxisScaleMax > 0 && properties.xaxisScaleMin < 0 ) { var value = this.coords[i].element.getAttribute('data-value'); if (value < 0) { x = this.getXCoord(value) + 7; } else { x = this.getXCoord(value) - 7; } } // Another corner case if ( properties.yaxisPosition === 'left' && properties.grouping === 'grouped' && properties.xaxisScaleMax > 0 && properties.xaxisScaleMin < 0 ) { var value = this.coords[i].element.getAttribute('data-value'); if (value < 0) { x = this.getXCoord(value) - 7; } else { x = this.getXCoord(value) + 7; } } // Account for the labels going off the edge of the SVG tag (whilst the Y axis // is on the left) if (properties.yaxisPosition === 'right') { if (x - width < properties.marginLeft && value > 0) { halign = 'left'; x = properties.marginLeft + 5; properties.labelsAboveBackground = properties.labelsAboveBackground || 'rgba(255,255,255,0.95)'; } } else { if (x + width > this.width && value > 0) { halign = 'right'; x = this.width - 5; properties.labelsAboveBackground = properties.labelsAboveBackground || 'rgba(255,255,255,0.95)'; } } // Another oddity - when there's regular data but the grouping // is set to stacked and the Y axis is on the left if (properties.grouping === 'stacked' && typeof this.data[indexes[0]] === 'number' && properties.yaxisPosition === 'left') { x += parseInt(this.coords[i].element.getAttribute('width')); } // Horizontal alignment if (typeof properties.labelsAboveHalign === 'string') { halign = properties.labelsAboveHalign; } var text = RGraph.SVG.text({ object: this, parent: this.svgAllGroup, tag: 'labels.above', text: str, x: x, y: y, halign: halign, 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 }); } } }; // // Draws the labelsInbar // this.drawLabelsInbar = function () { // Go through the above labels if (properties.labelsInbar) { // Default alignment var valign = properties.labelsInbarValign, halign = properties.labelsInbarHalign; // Get the text configuration for the labels var textConf = RGraph.SVG.getTextConf({ object: this, prefix: 'labelsInbar' }); var data = RGraph.SVG.arrayLinearize(this.data); for (var i=0; i 0 ? obj.getXCoord(0) - (properties.yaxisPosition === 'right' ? width : 0) : (properties.xaxisScaleMin < 0 && properties.xaxisScaleMax > 0 ? (properties.yaxisPosition === 'right' ? obj.getXCoord(0) : obj.getXCoord(0) - width) : obj.getXCoord(0)) ); } else if (typeof data[i] === 'object') { var accumulativeWidth = 0; for (var j=0,len2=data[i].length; j 1 ? opt.delay : 200); RGraph.SVG.FX.update(iterate); } else if (opt.callback) { RGraph.SVG.redraw(); (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 // // ************************************************************** // *** In order to deal with stacked charts, this function is *** // *** complicated - probably significantly more so than it *** // *** needs to be. As such it most definitely needs *** // *** refactoring *** // ************************************************************** // this.wave = function () { var stackedAccumulativeWidth = 0; // 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 () {}, width; for (var i=0,len=this.coords.length; i opt.startFrames[i]) { var originalWidth = obj.coords[i].element.getAttribute('data-original-width'), value = parseFloat(obj.coords[i].element.getAttribute('data-value')), seq = i; indexes = RGraph.SVG.sequentialIndexToGrouped(i, obj.data); if (indexes[0] !== group) { group = indexes[0]; } obj.coords[i].element.setAttribute( 'width', width = Math.min( ((frame - opt.startFrames[i]) / framesperbar) * originalWidth, originalWidth ) ); stackedAccumulativeWidth += width; if (properties.yaxisPosition === 'right') { if (properties.grouping === 'stacked') { if (indexes[1] === 0) { obj.coords[i].element.setAttribute('x',obj.width - properties.marginRight - width); var previousX = obj.coords[i].element.getAttribute('x'); } else { obj.coords[i].element.setAttribute( 'x', previousX - width ); } } else { obj.coords[i].element.setAttribute( 'x', value >=0 ? obj.getXCoord(0) - width : obj.getXCoord(0) ); } } else { obj.coords[i].element.setAttribute( 'x', value >=0 ? obj.getXCoord(0) : obj.getXCoord(0) - width ); } if (properties.grouping === 'stacked' && RGraph.SVG.isArray(obj.data[indexes[0]])) { // Are these two needed any more? // //var seq = obj.coords[i].element.getAttribute('data-sequential-index'); //var indexes = RGraph.SVG.sequentialIndexToGrouped(seq, obj.data); //////////////////////////////////// if (properties.yaxisPosition === 'left' && indexes[1] > 0) { obj.coords[i].element.setAttribute( 'x', parseInt(obj.coords[i - 1].element.getAttribute('x')) + parseInt(obj.coords[i - 1].element.getAttribute('width')) ); } // Not really related to the code above, reuse this if() // condition to set the width of the backface //obj.stackedBackfaces[indexes[0]].setAttribute('width', width); for (var j=0,cumulativeWidth=0; j= 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'; 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 - 10 // An arbitrary amount ) + 'px'; }; // // Draw a line on the chart - just each of the X points // (ie the top of the bars) connected by a line. This // does mean you can set the colors property to transparent // and you have a vertical line. // this.drawLine = function () { if (this.properties.lineShadow) { RGraph.SVG.setShadow({ object: this, id: 'lineDropShadow_' + this.uid, offsetx: this.properties.lineShadowOffsetx, offsety: this.properties.lineShadowOffsety, blur: this.properties.lineShadowBlur, color: this.properties.lineShadowColor }); } if (this.properties.lineSpline) { // Set this so that we can refer to the object var obj = this; var c = RGraph.SVG.arrayClone(this.coords); // Get the coordinates for the spline line // coordinates var coordinates = Spline(c, {return: true}); // Draw a line back to the X axis (whichever side // that may be) if (this.properties.lineFilled) { // Copy the coordinates array so that the coordinates of // the axis can bw added to it var fillAreaCoordinates = RGraph.SVG.arrayClone(coordinates); // Add the coordinates that take the fill back // to the Y axis if (this.properties.yaxisPosition === 'right') { fillAreaCoordinates.push([this.width - this.properties.marginRight, coordinates[coordinates.length - 1][1]]); fillAreaCoordinates.push([this.width - this.properties.marginRight, coordinates[0][1]]); } else { fillAreaCoordinates.push([this.properties.marginLeft, coordinates[coordinates.length - 1][1]]); fillAreaCoordinates.push([this.properties.marginLeft, coordinates[0][1]]); } RGraph.SVG.create({ svg: obj.svg, parent: obj.svgAllGroup, type: 'path', attr: { fill: obj.properties.lineFilledColor, stroke: 'transparent', d: RGraph.SVG.create.pathString(fillAreaCoordinates), 'clip-path': obj.isTrace ? 'url(#trace-effect-clip)' : '' } }); } RGraph.SVG.create({ svg: obj.svg, parent: obj.svgAllGroup, type: 'path', attr: { fill: "transparent", stroke: obj.properties.lineColor, 'stroke-width': obj.properties.lineLinewidth, 'stroke-linejoin': 'round', 'stroke-linecap': 'round', d: RGraph.SVG.create.pathString(coordinates), filter: obj.properties.lineShadow ? 'url(#lineDropShadow_' + obj.uid + ')' : '', 'clip-path': obj.isTrace ? 'url(#trace-effect-clip)' : '' } }); } else { // TODO Need to skip null values at the start // of the array here var d = ''; // Move to the first coordinate if (RGraph.SVG.isNumber(this.data[0])) { if (this.properties.yaxisPosition === 'right') { d = 'M {1} {2} '.format( this.coords[0].x, this.coords[0].y + (this.coords[0].height / 2) ); } else { var v = Number(this.coords[0].element.getAttribute('data-value')); d = 'M {1} {2} '.format( this.coords[0].x + (v < 0 ? 0 : this.coords[0].width), this.coords[0].y + (this.coords[0].height / 2) ); } } // Draw a line to subsequent points unless // that point is null, in which case move // to it instead for (let i=0; i= 0 && isEndTick) || obj.properties.lineTickmarksStyle.indexOf('end') === -1 ) { var x = v.x; var y = v.y + (v.height / 2); // Adjust the tickmark if the // Y-axis position is on the left // and the value is positive if ( obj.properties.yaxisPosition === 'left' && Number(v.element.getAttribute('data-value')) > 0 ) { x += v.width; } // Adjust the tickmark if the // Y-axis position is on the right // and the value is negative if ( obj.properties.yaxisPosition === 'right' && Number(v.element.getAttribute('data-value')) < 0 ) { x += v.width; } RGraph.SVG.create({ svg: obj.svg, type: 'circle', parent: obj.svgAllGroup, attr: { cx: x, cy: y, r: obj.properties.lineTickmarksSize, fill: obj.properties.lineColor, filter: obj.properties.lineShadow ? 'url(#lineDropShadow_' + obj.uid + ')' : '', 'clip-path': obj.isTrace ? 'url(#trace-effect-clip)' : '' } }); // Draw the center part of the circle tickmark if (obj.properties.lineTickmarksStyle.indexOf('filled') < 0) { RGraph.SVG.create({ svg: obj.svg, type: 'circle', parent: obj.svgAllGroup, attr: { cx: x, cy: y, r: RGraph.SVG.isNumber(obj.properties.lineTickmarksLinewidth) ? (obj.properties.lineTickmarksSize - obj.properties.lineTickmarksLinewidth) : (obj.properties.lineTickmarksSize - 3), fill: 'white', 'clip-path':obj.isTrace ? 'url(#trace-effect-clip)' : '' } }); } } break; case 'square': case 'rect': case 'filledsquare': case 'filledrect': case 'filledendsquare': case 'filledendrect': case 'endsquare': case 'endrect': if ( (obj.properties.lineTickmarksStyle.indexOf('end') >= 0 && isEndTick) || obj.properties.lineTickmarksStyle.indexOf('end') === -1 ) { var x = v.x - obj.properties.lineTickmarksSize + (RGraph.SVG.isNull(obj.properties.lineTickmarksLinewidth) ? 3 : obj.properties.lineTickmarksLinewidth); var y = v.y + (v.height / 2) - obj.properties.lineTickmarksSize; // Adjust the tickmark if the // Y-axis position is on the left // and the value is positive if ( obj.properties.yaxisPosition === 'left' && Number(v.element.getAttribute('data-value')) > 0 ) { x += v.width; } // Adjust the tickmark if the // Y-axis position is on the right // and the value is negative if ( obj.properties.yaxisPosition === 'right' && Number(v.element.getAttribute('data-value')) < 0 ) { x += v.width; } RGraph.SVG.create({ svg: obj.svg, type: 'rect', parent: obj.svgAllGroup, attr: { x: x, y: y, width: obj.properties.lineTickmarksSize * 2, height: obj.properties.lineTickmarksSize * 2, fill: obj.properties.lineColor, filter: obj.properties.lineShadow ? 'url(#lineDropShadow_' + obj.uid + ')' : '', 'clip-path': obj.isTrace ? 'url(#trace-effect-clip)' : '' } }); // Draw the center part of the rect tickmark if (obj.properties.lineTickmarksStyle.indexOf('filled') < 0) { RGraph.SVG.create({ svg: obj.svg, type: 'rect', parent: obj.svgAllGroup, attr: { x: x + (obj.properties.lineTickmarksLinewidth), y: y + (obj.properties.lineTickmarksLinewidth), width: RGraph.SVG.isNumber(obj.properties.lineTickmarksLinewidth) ? (obj.properties.lineTickmarksSize * 2) - (2 * obj.properties.lineTickmarksLinewidth) : (obj.properties.lineTickmarksSize * 2) - 3 - 3, height: RGraph.SVG.isNumber(obj.properties.lineTickmarksLinewidth) ? (obj.properties.lineTickmarksSize * 2) - (2 * obj.properties.lineTickmarksLinewidth) : (obj.properties.lineTickmarksSize * 2) - 3 - 3, fill: 'white', 'clip-path': obj.isTrace ? 'url(#trace-effect-clip)' : '' } }); } } break; } } }); // // This function draws a spline using the HBar coords // // @param array coords The coordinates // function Spline (coords, opt = {}) { var coordsSpline = [[]]; var yCoords = [], interval = (obj.height - obj.properties.marginTop - obj.properties.marginBottom) / coords.length; path = []; // // The drawSpline function needs an array of JUST // the X values - so put the coords into the correct // format // for (var i=0; i 0) { last[0] += obj.coords[obj.coords.length - 1].width; } // Adjust the position of the last point if (obj.properties.yaxisPosition === 'right' && obj.coords[j - 1].element.getAttribute('data-value') > 0) { last[0] -= obj.coords[obj.coords.length - 1].width; } path.push([ last[0], last[1] ]); if (typeof index === 'number') { coordsSpline[0].push([ last[0], last[1] ]); } var str = ''; path.forEach(function (v,k,arr) { var command = (k === 0 ? 'M' : 'L'); str += ' {1} {2} {3}'.format(command, v[0], v[1]); }); // // Create the path which depicts the spline // if (opt.return) { // The 'path' variable appears to contain all // of the coordinates. return path; } else { RGraph.SVG.create({ svg: obj.svg, parent: obj.svgAllGroup, type: 'path', attr: { fill: "transparent", stroke: obj.properties.lineColor, 'stroke-width': obj.properties.lineLinewidth, 'stroke-linejoin': 'round', 'stroke-linecap': 'round', d: str, filter: obj.properties.lineShadow ? 'url(#lineDropShadow_' + obj.uid + ')' : '', 'clip-path': obj.isTrace ? 'url(#trace-effect-clip)' : '' } }); } function Spline (t, P0, P1, P2, P3) { return 0.5 * ((2 * P1) + ((0-P0) + P2) * t + ((2*P0 - (5*P1) + (4*P2) - P3) * (t*t) + ((0-P0) + (3*P1)- (3*P2) + P3) * (t*t*t))); } } }; // // A trace effect for the line // // @param object Options to give to the effect // @param function A function to call when the effect has completed // this.trace = function () { var opt = arguments[0] || {}, frame = 1, frames = opt.frames || 60, obj = this; this.isTrace = true; this.draw(); // Create the clip area var clippath = RGraph.SVG.create({ svg: this.svg, type: 'clipPath', parent: this.svg.defs, attr: { id: 'trace-effect-clip' } }); var clippathrect = RGraph.SVG.create({ svg: this.svg, type: 'rect', parent: clippath, attr: { x: 0, y: 0, width: this.width, height: 0 } }); var iterator = function () { var height = (frame++) / frames * obj.height; clippathrect.setAttribute("height", height); if (frame <= frames) { RGraph.SVG.FX.update(iterator); } else { // Remove the clippath clippath.parentNode.removeChild(clippath); if (opt.callback) { (opt.callback)(obj); } } }; iterator(); return this; }; // // 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 height = this.height, x1 = this.getXCoord(from), x2 = this.getXCoord(to), width = Math.abs(x2 - x1), y = 0, x = Math.min(x1, x2); // Increase the width if the maximum value is "max" if (RegExp.$2 === 'max') { width += this.properties.marginRight; } // Increase the height if the minimum value is "min" if (RegExp.$1 === 'min') { x = 0; width += this.properties.marginLeft; } 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);