// 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}; // // The bar chart constructor // RGraph.Bar = function (conf) { var id = conf.id, canvas = document.getElementById(id), data = conf.data; // Get the canvas and context objects this.id = id; this.canvas = canvas; this.context = this.canvas.getContext('2d'); this.canvas.__object__ = this; this.type = 'bar'; this.max = 0; this.stackedOrGrouped = false; this.isRGraph = true; this.isrgraph = true; this.rgraph = true; this.uid = RGraph.createUID(); this.canvas.uid = this.canvas.uid ? this.canvas.uid : RGraph.createUID(); this.colorsParsed = false; this.original_colors = []; this.cachedBackgroundCanvas = null; this.firstDraw = true; // After the first draw this will be false this.stopAnimationRequested = false;// Used to control the animations // Various config type stuff this.properties = { backgroundBarsCount: null, backgroundBarsColor1: 'rgba(0,0,0,0)', backgroundBarsColor2: 'rgba(0,0,0,0)', backgroundGrid: true, backgroundGridColor: '#ddd', backgroundGridLinewidth: 1, backgroundGridHsize: 20, backgroundGridVsize: 20, backgroundGridVlines: true, backgroundGridHlines: true, backgroundGridBorder: true, backgroundGridAutofit: true, backgroundGridAutofitAlign: true, backgroundGridHlinesCount: 5, backgroundGridDashed: false, backgroundGridDotted: false, backgroundGridDashArray: null, backgroundGridThreedYaxis: true, backgroundImage: null, backgroundImageStretch: true, backgroundImageX: null, backgroundImageY: null, backgroundImageW: null, backgroundImageH: null, backgroundImageAlign: null, backgroundColor: null, backgroundHbars: null, backgroundBorder: false, backgroundBorderLinewidth: 1, backgroundBorderColor: '#aaa', backgroundBorderDashed: false, backgroundBorderDotted: false, backgroundBorderDashArray: null, marginTop: 35, marginBottom: 35, marginLeft: 35, marginRight: 35, marginInner: 5, marginInnerGrouped: 1, labelsIngraph: null, labelsIngraphFont: null, labelsIngraphSize: null, labelsIngraphColor: null, labelsIngraphBold: null, labelsIngraphItalic: null, labelsIngraphOffsetx: 0, labelsIngraphOffsety: 0, labelsAbove: false, labelsAboveDecimals: 0, labelsAboveSize: null, labelsAboveColor: null, labelsAboveBold: null, labelsAboveItalic: null, labelsAboveFont: null, labelsAbovePoint: '.', labelsAboveThousand: ',', labelsAboveBackground:'transparent', labelsAboveAngle: null, labelsAboveOffset: null, labelsAboveOffsetx: 0, labelsAboveOffsety: 0, labelsAboveUnitsPre: '', labelsAboveUnitsPost:'', labelsAboveFormatter:null, yaxis: true, yaxisLinewidth: 1, yaxisColor: 'black', yaxisTickmarks: true, yaxisTickmarksCount: null, yaxisTickmarksLastTop: null, yaxisTickmarksLastBottom: null, yaxisTickmarksLength: 3, yaxisScale: true, yaxisScaleMin: 0, yaxisScaleMax: null, yaxisScaleUnitsPre: '', yaxisScaleUnitsPost: '', yaxisScaleDecimals: 0, yaxisScalePoint: '.', yaxisScaleThousand: ',', yaxisScaleRound: false, yaxisScaleFormatter: null, yaxisLabelsSpecific: null, yaxisLabelsCount: 5, yaxisLabelsOffsetx: 0, yaxisLabelsOffsety: 0, yaxisLabelsHalign: null, yaxisLabelsValign: null, yaxisLabelsFont: null, yaxisLabelsSize: null, yaxisLabelsColor: null, yaxisLabelsBold: null, yaxisLabelsItalic: null, yaxisLabelsPosition: 'edge', yaxisPosition: 'left', yaxisTitle: '', yaxisTitleAccessible: null, yaxisTitleBold: null, yaxisTitleSize: null, yaxisTitleFont: null, yaxisTitleColor: null, yaxisTitleItalic: null, yaxisTitlePos: null, yaxisTitleX: null, yaxisTitleY: null, yaxisTitleOffsetx: 0, yaxisTitleOffsety: 0, yaxisTitleHalign: null, yaxisTitleValign: null, yaxisTitleAccessible: null, xaxis: true, xaxisLinewidth: 1, xaxisColor: 'black', xaxisTickmarks: true, xaxisTickmarksLength: 3, xaxisTickmarksLastLeft: null, xaxisTickmarksLastRight: null, xaxisTickmarksCount: null, xaxisLabels: null, xaxisLabelsSize: null, xaxisLabelsFont: null, xaxisLabelsItalic: null, xaxisLabelsBold: null, xaxisLabelsColor: null, xaxisLabelsFormattedDecimals: 0, xaxisLabelsFormattedPoint: '.', xaxisLabelsFormattedThousand: ',', xaxisLabelsFormattedUnitsPre: '', xaxisLabelsFormattedUnitsPost: '', xaxisLabelsOffsetx: 0, xaxisLabelsOffsety: 0, xaxisLabelsHalign: null, xaxisLabelsValign: null, xaxisLabelsPosition: 'section', xaxisLabelsSpecificAlign: 'left', xaxisPosition: 'bottom', xaxisLabelsAngle: 0, xaxisTitle: '', xaxisTitleBold: null, xaxisTitleSize: null, xaxisTitleFont: null, xaxisTitleColor: null, xaxisTitleItalic: null, xaxisTitlePos: null, xaxisTitleOffsetx: 0, xaxisTitleOffsety: 0, xaxisTitleX: null, xaxisTitleY: null, xaxisTitleHalign: null, xaxisTitleValign: null, textItalic: false, textBold: false, textColor: 'black', textSize: 12, textFont: 'Arial, Verdana, sans-serif', textAccessible: false, textAccessibleOverflow: 'visible', textAccessiblePointerevents: false, text: null, title: '', titleX: null, titleY: null, titleHalign: null, titleValign: null, titleFont: null, titleSize: null, titleColor: null, titleBold: null, titleItalic: null, titleOffsetx: 0, titleOffsety: 0, titleSubtitle: '', titleSubtitleSize: null, titleSubtitleColor: '#aaa', titleSubtitleFont: null, titleSubtitleBold: null, titleSubtitleItalic: null, titleSubtitleOffsetx: 0, titleSubtitleOffsety: 0, colorsStroke: 'rgba(0,0,0,0)', colors: ['red','#0f0','blue','pink','orange','cyan','black','white','green','magenta'], colorsSequential: false, colorsReverse: false, grouping: 'grouped', variant: 'bar', variantSketchVerticals: true, variantThreedXaxis: true, variantThreedYaxis: true, variantThreedAngle: 0.1, variantThreedOffsetx: 10, variantThreedOffsety: 5, variantThreedXaxisColor: '#ddd', variantThreedYaxisColor: '#ddd', shadow: false, shadowColor: '#aaa', shadowOffsetx: 0, shadowOffsety: 0, shadowBlur: 15, tooltips: null, tooltipsEffect: 'slide', tooltipsCssClass: 'RGraph_tooltip', tooltipsCss: null, tooltipsEvent: 'onclick', tooltipsHighlight: true, tooltipsPersistent: false, tooltipsHotspotXonly: false, tooltipsHotspotIgnore: null, tooltipsFormattedThousand: ',', tooltipsFormattedPoint: '.', tooltipsFormattedDecimals: 0, tooltipsFormattedUnitsPre: '', tooltipsFormattedUnitsPost: '', tooltipsFormattedKeyColors: null, tooltipsFormattedKeyColorsShape: 'square', tooltipsFormattedKeyLabels: [], tooltipsFormattedListType: 'ul', tooltipsFormattedListItems: null, 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)', key: null, keyBackground: 'white', keyPosition: 'graph', keyShadow: false, keyShadowColor: '#666', keyShadowBlur: 3, keyShadowOffsetx: 2, keyShadowOffsety: 2, keyPositionMarginBoxed:false, keyPositionMarginHSpace: 0, keyPositionX: null, keyPositionY: null, keyInteractive: false, keyInteractiveHighlightChartLinewidth: 2, keyInteractiveHighlightChartStroke: 'black', keyInteractiveHighlightChartFill: 'rgba(255,255,255,0.7)', keyInteractiveHighlightLabel: 'rgba(255,0,0,0.2)', keyHalign: 'right', keyColorShape: 'square', keyRounded: true, keyLinewidth: 1, keyColors: null, keyLabelsColor: null, keyLabelsSize: null, keyLabelsFont: null, keyLabelsBold: null, keyLabelsItalic: null, keyLabelsOffsetx: 0, keyLabelsOffsety: 0, keyFormattedDecimals: 0, keyFormattedPoint: '.', keyFormattedThousand: ',', keyFormattedUnitsPre: '', keyFormattedUnitsPost: '', keyFormattedValueSpecific: null, keyFormattedItemsCount: null, contextmenu: null, crosshairs: false, crosshairsColor: '#333', crosshairsLinewidth: 1, crosshairsHline: true, crosshairsVline: true, crosshairsSnapToScale: false, linewidth: 1, annotatable: false, annotatableLinewidth: 1, annotatableColor: 'black', adjustable: false, adjustableOnly: null, beveled: false, errorbars: false, errorbarsColor: 'black', errorbarsCapped: true, errorbarsCappedWidth: 14, errorbarsLinewidth: 1, combinedEffect: null, combinedEffectOptions: null, combinedEffectCallback: null, corners: 'square', cornersRoundRadius: 10, cornersRoundLeft: true, cornersRoundRight: true, cornersRoundLeftRadius: null, cornersRoundRightRadius: null, responsive: null, clearto: 'rgba(0,0,0,0)' } // // 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; } } // Check for support if (!this.canvas) { alert('[BAR] No canvas support'); return; } // // Convert strings into numbers. Also converts undefined // elements to null // data = RGraph.stringsToNumbers(data); // // Determine whether the chart will contain stacked or // grouped bars // for (var i=0; i<data.length; ++i) { if (typeof data[i] === 'object' && !RGraph.isNullish(data[i])) { this.stackedOrGrouped = true; } } // // Create the dollar objects so that functions can be // added to them // var linear_data = RGraph.arrayLinearize(data); for (var i=0; i<linear_data.length; ++i) { this['$' + i] = {}; } // Store the data and set the orignal_data to it this.data = data; this.original_data = RGraph.arrayClone(data); // Used to store the coords of the bars this.coords = []; this.coords2 = []; this.coordsText = []; // // This linearises the data. Doing so can make it easier to pull // out the appropriate data from tooltips // this.data_arr = RGraph.arrayLinearize(this.data); // Easy access to properties and the path function var properties = this.properties; this.path = RGraph.pathObjectFunction; // // "Decorate" the object with the generic effects if the effects library has been included // if (RGraph.Effects && typeof RGraph.Effects.decorate === 'function') { RGraph.Effects.decorate(this); } // Add the responsive method. This method resides in the common file. this.responsive = RGraph.responsive; // // A setter // // @param name string The name of the property to set // @param value mixed The value of the property // this.set = function (name) { var value = typeof arguments[1] === 'undefined' ? null : arguments[1]; // Go through all of the properties and make sure // that they're using the correct capitalisation if (typeof name === 'string') { name = this.properties_lowercase_map[name.toLowerCase()] || name; } // Some BC for the bevelled property if (name === 'bevelled') { name = 'beveled'; } // Set the colorsParsed flag to false if the colors // property is being set if ( name === 'colors' || name === 'keyColors' || name === 'crosshairsColor' || name === 'highlightStroke' || name === 'highlightFill' || name === 'textColor' || name === 'backgroundBarsColor1' || name === 'backgroundBarsColor2' || name === 'backgroundGridColor' || name === 'backgroundColor' || name === 'colorsStroke' || name === 'axesColor' ) { this.colorsParsed = false; } // the number of arguments is only one and it's an // object - parse it for configuration data and return. if (arguments.length === 1 && typeof arguments[0] === 'object') { for (i in arguments[0]) { if (typeof i === 'string') { this.set(i, arguments[0][i]); } } return this; } properties[name] = value; 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 properties[name]; }; // // The function you call to draw the bar chart // this.draw = function () { // // Fire the onbeforedraw event // RGraph.fireCustomEvent(this, 'onbeforedraw'); // Translate half a pixel for antialiasing purposes - but only if it hasn't been // done already // // MUST be the first thing done after clipping is installed! // if (!this.canvas.__rgraph_aa_translated__) { this.context.translate(0.5,0.5); this.canvas.__rgraph_aa_translated__ = true; } // MUST be the second thing done! if (typeof properties.backgroundImage === 'string') { RGraph.drawBackgroundImage(this); } // // If the X axis is at the top then all the // data-points should be negative // if (properties.xaxisPosition === 'top') { for (var i=0; i<this.data.length; ++i) { if (typeof this.data[i] === 'object' && properties.grouping === 'grouped') { for (var j=0;j<this.data[i].length; ++j) { this.data[i][j] = Math.abs(this.data[i][j]) * -1; } } else if (typeof this.data[i] === 'number') { this.data[i] = Math.abs(this.data[i]) * -1; } } } // // If the chart is 3d then angle it // if (properties.variant === '3d') { if (properties.textAccessible) { // Nada } else { this.context.setTransform(1,properties.variantThreedAngle,0,1,0.5,0.5); } } // // Parse the colors. This allows for simple gradient // syntax // if (!this.colorsParsed) { this.parseColors(); // Don't want to do this again this.colorsParsed = true; } // // Make the margins easy ro access // this.marginLeft = properties.marginLeft; this.marginRight = properties.marginRight; this.marginTop = properties.marginTop; this.marginBottom = properties.marginBottom; // // Check for tooltips and alert the user that they're // not supported with pyramid charts // if ( (properties.variant == 'pyramid' || properties.variant == 'dot') && typeof properties.tooltips == 'object' && properties.tooltips && properties.tooltips.length > 0) { alert('[BAR] (' + this.id + ') Sorry, tooltips are not supported with dot or pyramid charts'); } // // Stop the coords arrays from growing uncontrollably // this.coords = []; this.coords2 = []; this.coordsText = []; // // Work out a few things. They need to be here because // they depend on things you can change before you // call Draw() but after you instantiate the object // this.max = 0; this.grapharea = this.canvas.height - this.marginTop - this.marginBottom; this.halfgrapharea = this.grapharea / 2; this.halfTextHeight = properties.textSize / 2; // // Work out the max value // if (properties.yaxisScaleMax) { this.scale2 = RGraph.getScale({object: this, options: { 'scale.max': properties.yaxisScaleMax, 'scale.strict': properties.yaxisScaleRound ? false : true, 'scale.min': properties.yaxisScaleMin, 'scale.thousand': properties.yaxisScaleThousand, 'scale.point': properties.yaxisScalePoint, 'scale.decimals': properties.yaxisScaleDecimals, 'scale.labels.count':properties.yaxisLabelsCount, 'scale.round': properties.yaxisScaleRound, 'scale.units.pre': properties.yaxisScaleUnitsPre, 'scale.units.post': properties.yaxisScaleUnitsPost, 'scale.formatter': properties.yaxisScaleFormatter }}); } else { // // If errorbars are given as a number then convert the nuumber to an // array. // var errorbars = properties.errorbars; if (typeof errorbars === 'number') { var value = errorbars; properties.errorbars = []; for (var i=0; i<this.data.length; ++i) { if (typeof this.data[i] === 'number') { properties.errorbars.push([value, null]); } else if (typeof this.data[i] === 'object' && !RGraph.isNullish(this.data[i])) { for (var j=0; j<this.data[i].length; ++j) { properties.errorbars.push([value, null]); } } } errorbars = properties.errorbars; } for (i=0; i<this.data.length; ++i) { if (typeof this.data[i] == 'object') { var value = properties.grouping === 'grouped' ? Number(RGraph.arrayMax(this.data[i], true)) : Number(RGraph.arraySum(this.data[i])); } else { var value = Number(this.data[i]); } this.max = Math.max(Math.abs(this.max), Math.abs(value) + Number( ( typeof properties.errorbars === 'object' && typeof properties.errorbars[i] === 'object' && !RGraph.isNullish(properties.errorbars[i]) && typeof properties.errorbars[i][0] === 'number' ) ? properties.errorbars[i][0] : 0 ) ); } this.scale2 = RGraph.getScale({object: this, options: { 'scale.max': this.max, 'scale.min': properties.yaxisScaleMin, 'scale.thousand': properties.yaxisScaleThousand, 'scale.point': properties.yaxisScalePoint, 'scale.decimals': properties.yaxisScaleDecimals, 'scale.labels.count':properties.yaxisLabelsCount, 'scale.round': properties.yaxisScaleRound, 'scale.units.pre': properties.yaxisScaleUnitsPre, 'scale.units.post': properties.yaxisScaleUnitsPost, 'scale.formatter': properties.yaxisScaleFormatter }}); this.max = this.scale2.max; } // // Install clipping // // MUST be the first thing that's done after the // beforedraw event // if (!RGraph.isNullish(this.properties.clip)) { RGraph.clipTo.start(this, this.properties.clip); } // Now draw the background on to the main canvas RGraph.Background.draw(this); //If it's a sketch chart variant, draw the axes first //if (properties.variant == 'sketch') { // this.drawAxes(); // this.drawbars(); //} else { this.drawbars(); this.drawAxes(); //} this.drawLabels(); // // Draw the bevel if required // if (properties.beveled || properties.beveled) { this.drawBevel(); } // Draw the key if necessary if (properties.key && properties.key.length) { RGraph.drawKey( this, properties.key, properties.colors ); } // // Setup the context menu if required // if (properties.contextmenu) { RGraph.showContext(this); } // // Draw errorbars // if (properties.errorbars) { this.drawErrorbars(); } // // Draw "in graph" labels // if (properties.labelsIngraph) { RGraph.drawInGraphLabels(this); } // // Add custom text thats specified // RGraph.addCustomText(this); // Draw any custom lines that have been defined RGraph.drawHorizontalLines(this); // // This installs the event listeners // RGraph.installEventListeners(this); // // End clipping // if (!RGraph.isNullish(this.properties.clip)) { RGraph.clipTo.end(); } // // Fire the onfirstdraw event // if (this.firstDraw) { this.firstDraw = false; RGraph.fireCustomEvent(this, 'onfirstdraw'); this.firstDrawFunc(); } // // Fire the RGraph draw event // RGraph.fireCustomEvent(this, 'ondraw'); // // Install any inline responsive configuration. This // should be last in the draw function - even after // the draw events. // RGraph.installInlineResponsive(this); 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; }; // // Draws the charts axes // this.drawAxes = function () { if (RGraph.ISSAFARI == -1) { this.context.lineCap = 'square'; } // // If the xaxisLabels option is a string then turn it // into an array. // if (properties.xaxisLabels && properties.xaxisLabels.length) { if (typeof properties.xaxisLabels === 'string') { properties.xaxisLabels = RGraph.arrayPad({ array: [], length: this.data.length, value: properties.xaxisLabels }); } // Label substitution // for (var i=0; i<properties.xaxisLabels.length; ++i) { if (RGraph.isString(properties.xaxisLabels[i])) { properties.xaxisLabels[i] = RGraph.labelSubstitution({ object: this, text: properties.xaxisLabels[i], index: i, value: this.data[i], decimals: properties.xaxisLabelsFormattedDecimals || 0, unitsPre: properties.xaxisLabelsFormattedUnitsPre || '', unitsPost: properties.xaxisLabelsFormattedUnitsPost || '', thousand: properties.xaxisLabelsFormattedThousand || ',', point: properties.xaxisLabelsFormattedPoint || '.' }); } } } // // The new common X axis drawing function RGraph.drawXAxis(this); // // The new common Y axis drawing function RGraph.drawYAxis(this); }; // // Draws the bars // this.drawbars = function () { this.context.lineWidth = properties.linewidth; this.context.strokeStyle = properties.colorsStroke; this.context.fillStyle = properties.colors[0]; var prevX = 0, prevY = 0, decimals = properties.yaxisScaleDecimals; // // if the chart is adjustable fix the scale so that it doesn't change. // if (properties.adjustable && !properties.yaxisScaleMax) { this.set('yaxisScaleMax', this.scale2.max); } // // Draw horizontal bars here // if (properties.backgroundHbars && properties.backgroundHbars.length > 0) { RGraph.drawBars(this); } var variant = properties.variant; // // Draw the 3D axes is necessary // if (variant === '3d') { RGraph.draw3DAxes(this); } // // Get the variant once, and draw the bars, be they regular, stacked or grouped // // Get these variables outside of the loop var xaxispos = properties.xaxisPosition, width = (this.canvas.width - this.marginLeft - this.marginRight ) / this.data.length, orig_height = height, hmargin = properties.marginInner, shadow = properties.shadow, shadowColor = properties.shadowColor, shadowBlur = properties.shadowBlur, shadowOffsetX = properties.shadowOffsetx, shadowOffsetY = properties.shadowOffsety, strokeStyle = properties.colorsStroke, colors = properties.colors, sequentialColorIndex = 0 var height; // // Loop through the data // for (i=0,len=this.data.length; i<len; i+=1) { // Work out the height //The width is up outside the loop if (RGraph.arraySum(this.data[i]) < 0) { var height = (RGraph.arraySum(this.data[i]) + this.scale2.min) / (this.scale2.max - this.scale2.min); } else { var height = (RGraph.arraySum(this.data[i]) - this.scale2.min) / (this.scale2.max - this.scale2.min); } height *= Math.abs(this.getYCoord(this.scale2.max) - this.getYCoord(this.scale2.min)); var x = (i * width) + this.marginLeft; var y = xaxispos == 'center' ? ((this.canvas.height - this.marginTop - this.marginBottom) / 2) + this.marginTop - height : this.canvas.height - height - this.marginBottom; // xaxispos is top if (xaxispos == 'top') { y = this.marginTop; } // Account for negative lengths - Some browsers don't like a negative value if (height < 0) { y += height; height = Math.abs(height); } // // Turn on the shadow if need be // if (shadow) { this.context.shadowColor = shadowColor; this.context.shadowBlur = shadowBlur; this.context.shadowOffsetX = shadowOffsetX; this.context.shadowOffsetY = shadowOffsetY; } // // Draw the bar // this.context.beginPath(); if (typeof this.data[i] == 'number') { // If the Y axis is offset change the bar start (the top of the bar) if (xaxispos === 'bottom' && properties.yaxisScaleMin < 0) { if (this.data[i] >= 0) { height = Math.abs(this.getYCoord(0) - this.getYCoord(this.data[i])); } else { y = this.getYCoord(0); height = Math.abs(this.getYCoord(0) - this.getYCoord(this.data[i])); } } var barWidth = width - (2 * hmargin); // // Check for a negative bar width // if (barWidth < 0) { alert('[RGRAPH] Warning: you have a negative bar width. This may be caused by the marginInner being too high or the width of the canvas not being sufficient.'); } // Set the fill color this.context.strokeStyle = strokeStyle; this.context.fillStyle = colors[0]; // // Sequential colors // if (properties.colorsSequential) { this.context.fillStyle = colors[i]; } if (variant == 'sketch') { this.context.lineCap = 'round'; var sketchOffset = 3; this.context.beginPath(); this.context.strokeStyle = colors[0]; // // Sequential colors // if (properties.colorsSequential) { this.context.strokeStyle = colors[i]; } // Left side this.context.moveTo(x + hmargin + 2, y + height - 2); this.context.lineTo(x + hmargin - 1, y - 4); // The top this.context.moveTo(x + hmargin - 3, y + -2 + (this.data[i] < 0 ? height : 0)); this.context.quadraticCurveTo( x + hmargin + ((width - hmargin - hmargin) / 4), y + 0 + (this.data[i] < 0 ? height : 0) + (this.data[i] > 0 ? 10 : -10), x + hmargin + width + -1 - hmargin - hmargin, y + 0 + (this.data[i] < 0 ? height : 0) ); // The right side this.context.moveTo(x + hmargin + width - 5 - hmargin - hmargin, y - 5); this.context.lineTo(x + hmargin + width - 3 - hmargin - hmargin, y + height - 3); // Draw the inner-bar verticals if (properties.variantSketchVerticals) { for (var r=0.2; r<=0.8; r+=0.2) { this.context.moveTo( x + hmargin + ((width - hmargin - hmargin) * r), y - 1 ); this.context.lineTo( x + hmargin + ((width - hmargin - hmargin) * r), y + height + (r == 0.2 ? 1 : -2) ); } } this.context.stroke(); // Regular bar } else if (variant == 'bar' || variant == '3d' || variant == 'glass' || variant == 'bevel') { if (variant == 'glass') { RGraph.roundedRect({ context: this.context, x: x + hmargin, y: y, width: barWidth, height: height, radius: RGraph.isNumber(properties.cornersRoundRadius) ? properties.cornersRoundRadius : 5, roundtl: this.data[i] > 0, roundtr: this.data[i] > 0, roundbl: this.data[i] < 0, roundbr: this.data[i] < 0 }); this.context.stroke(); this.context.fill(); } else { // On 9th April 2013 these two were swapped around so that the stroke happens SECOND so that any // shadow that is cast by the fill does not overwrite the stroke // Why is this here? //this.path( // 'b r % % % % f', // x + hmargin, y, barWidth, height //); // Turn the shadow off so that the stroke doesn't cast any "extra" shadow // that would show inside the bar // // 31/07/21 Removed as regular bar charts weren't showing shadows // //RGraph.noShadow(this); if (properties.corners === 'round') { this.context.beginPath(); this.context.lineCap = 'miter'; this.context.lineJoin = 'square'; (this.data[i] < 0 || properties.xaxisPosition === 'top') ? this.roundedCornersRectNegative(x + hmargin,y,barWidth,height) : this.roundedCornersRect(x + hmargin,y,barWidth,height); this.context.stroke(); this.context.fill(); } else { if (properties.xaxisPosition === 'top') { y += height; } this.context.beginPath(); this.context.lineJoin = 'miter'; this.context.lineCap = 'square'; this.context.rect(x + hmargin,y,barWidth,height); this.context.stroke(); this.context.fill(); } } // 3D effect if (variant == '3d') { var prevStrokeStyle = this.context.strokeStyle; var prevFillStyle = this.context.fillStyle; // Draw the top (if the value is positive - otherwise there's no point) if (this.data[i] >= 0) { this.context.beginPath(); this.context.moveTo(x + hmargin, y); this.context.lineTo(x + hmargin + properties.variantThreedOffsetx, y - properties.variantThreedOffsety); this.context.lineTo(x + hmargin + properties.variantThreedOffsetx + barWidth, y - properties.variantThreedOffsety); this.context.lineTo(x + hmargin + barWidth, y); this.context.closePath(); this.context.stroke(); this.context.fill(); } // Draw the right hand side this.context.beginPath(); this.context.moveTo(x + hmargin + barWidth, y); this.context.lineTo( x + hmargin + barWidth + properties.variantThreedOffsetx, this.data[i] < 0 && xaxispos === 'bottom' ? this.getYCoord(0) : ( this.data[i] < 0 && (y - properties.variantThreedOffsety) < (this.marginTop + this.halfgrapharea) ? (this.marginTop + this.halfgrapharea) : (y - properties.variantThreedOffsety)) ); this.context.lineTo( x + hmargin + barWidth + properties.variantThreedOffsetx, this.data[i] < 0 && (y - properties.variantThreedOffsety + height) < (this.marginTop + this.getYCoord(0)) ? this.getYCoord(this.data[i]) - properties.variantThreedOffsety : (this.data[i] > 0 ? y - properties.variantThreedOffsety + height : Math.min(y - properties.variantThreedOffsety + height, this.canvas.height - this.marginBottom) ) ); this.context.lineTo(x + hmargin + barWidth, y + height); this.context.closePath(); this.context.stroke(); this.context.fill(); // Draw the lighter top section if (this.data[i] > 0) { this.context.beginPath(); this.context.fillStyle = 'rgba(255,255,255,0.5)'; this.context.moveTo(x + hmargin, y); this.context.lineTo(x + hmargin + properties.variantThreedOffsetx, y - properties.variantThreedOffsety); this.context.lineTo(x + hmargin + properties.variantThreedOffsetx + barWidth, y - properties.variantThreedOffsety); this.context.lineTo(x + hmargin + barWidth, y); this.context.lineTo(x + hmargin, y); this.context.closePath(); this.context.stroke(); this.context.fill(); } // Draw the darker right side section this.context.beginPath(); this.context.fillStyle = 'rgba(0,0,0,0.4)'; // TL this.context.moveTo(x + hmargin + barWidth, y); // TR this.context.lineTo( x + hmargin + barWidth + properties.variantThreedOffsetx, this.data[i] < 0 && xaxispos === 'bottom' ? this.getYCoord(0) : (this.data[i] < 0 && (y - properties.variantThreedOffsety) < (this.marginTop + this.halfgrapharea) ? (this.marginTop + this.halfgrapharea) : y - properties.variantThreedOffsety) ); // BR this.context.lineTo( x + hmargin + barWidth + properties.variantThreedOffsetx, this.data[i] < 0 && (y - properties.variantThreedOffsety + height) < this.getYCoord(0) ? this.getYCoord(0) : this.data[i] > 0 ? y - properties.variantThreedOffsety + height : Math.min(y - properties.variantThreedOffsety + height, this.canvas.height - this.marginBottom) ); // BL this.context.lineTo(x + hmargin + barWidth, y + height); this.context.lineTo(x + hmargin + barWidth, y); this.context.closePath(); this.context.stroke(); this.context.fill(); this.context.strokeStyle = prevStrokeStyle; this.context.fillStyle = prevFillStyle; // Glass variant } else if (variant == 'glass') { var grad = this.context.createLinearGradient(x + hmargin,y,x + hmargin + (barWidth / 2),y); grad.addColorStop(0, 'rgba(255,255,255,0.9)'); grad.addColorStop(1, 'rgba(255,255,255,0.5)'); this.context.beginPath(); this.context.fillStyle = grad; this.context.roundRect( x + hmargin + 3, y + (this.data[i] > 0 ? 3 : 0), (barWidth / 2) - 2, height - 2, this.data[i] > 0 ? ([RGraph.isNumber(properties.cornersRoundRadius) ? properties.cornersRoundRadius - 3 : 5, 0,0,0]) : [0,0,0,RGraph.isNumber(properties.cornersRoundRadius) ? properties.cornersRoundRadius : 5] ); this.context.fill(); } // Dot chart } else if (variant == 'dot') { this.context.beginPath(); this.context.strokeStyle = this.properties.colors[0]; this.context.moveTo(x + (width / 2), y); this.context.lineTo(x + (width / 2), y + height); this.context.stroke(); this.context.beginPath(); this.context.fillStyle = this.properties.colors[i]; this.context.arc( x + (width / 2), y + (this.data[i] > 0 ? 0 : height), 2, 0, 6.28, 0 ); // Set the colour for the dots this.context.fillStyle = properties.colors[0]; // // Sequential colors // if (properties.colorsSequential) { this.context.fillStyle = colors[i]; } this.context.stroke(); this.context.fill(); // Unknown variant type } else { alert('[BAR] Warning! Unknown variant: ' + variant); } this.coords.push([x + hmargin, y, width - (2 * hmargin), height]); if (typeof this.coords2[i] == 'undefined') { this.coords2[i] = []; } this.coords2[i].push([x + hmargin, y, width - (2 * hmargin), height]); // // Stacked bar // } else if (this.data[i] && typeof this.data[i] == 'object' && properties.grouping == 'stacked') { if (this.scale2.min) { alert("[ERROR] Stacked Bar charts with a Y min are not supported"); } var barWidth = width - (2 * hmargin); var redrawCoords = [];// Necessary to draw if the shadow is enabled var startY = 0; var dataset = this.data[i]; // // Check for a negative bar width // if (barWidth < 0) { alert('[RGRAPH] Warning: you have a negative bar width. This may be caused by the marginInner being too high or the width of the canvas not being sufficient.'); } for (j=0; j<dataset.length; ++j) { // Stacked bar chart and X axis pos in the middle - poitless since negative values are not permitted if (xaxispos == 'center') { alert("[BAR] It's pointless having the X axis position at the center on a stacked bar chart."); return; } // Negative values not permitted for the stacked chart if (this.data[i][j] < 0) { alert('[BAR] Negative values are not permitted with a stacked bar chart. Try a grouped one instead.'); return; } // // Set the fill and stroke colors // this.context.strokeStyle = strokeStyle this.context.fillStyle = colors[j]; if (properties.colorsReverse) { this.context.fillStyle = colors[this.data[i].length - j - 1]; } if (properties.colorsSequential && colors[sequentialColorIndex]) { this.context.fillStyle = colors[sequentialColorIndex++]; } else if (properties.colorsSequential) { this.context.fillStyle = colors[sequentialColorIndex - 1]; } var height = (dataset[j] / this.scale2.max) * (this.canvas.height - this.marginTop - this.marginBottom ); // If the X axis pos is in the center, we need to half the height if (xaxispos == 'center') { height /= 2; } var totalHeight = (RGraph.arraySum(dataset) / this.scale2.max) * (this.canvas.height - hmargin - this.marginTop - this.marginBottom); // // Store the coords for tooltips // this.coords.push([x + hmargin, y, width - (2 * hmargin), height]); if (typeof this.coords2[i] == 'undefined') { this.coords2[i] = []; } this.coords2[i].push([x + hmargin, y, width - (2 * hmargin), height]); if (height > 0) { // TODO Handle xaxisPosition=top here if (j === 0 && properties.corners === 'round' && properties.xaxisPosition === 'bottom') { this.context.beginPath(); this.context.lineCap = 'miter'; this.context.lineJoin = 'square'; this.roundedCornersRect(x + hmargin, y, width - (2 * hmargin), height); this.context.stroke(); this.context.fill(); } else if (j === (dataset.length - 1) && properties.corners === 'round' && properties.xaxisPosition === 'top') { this.context.beginPath(); this.context.lineCap = 'miter'; this.context.lineJoin = 'square'; this.roundedCornersRectNegative(x + hmargin, y, width - (2 * hmargin), height); this.context.stroke(); this.context.fill(); } else { this.path( 'b lj % lc % r % % % % s % f %', 'miter','square', x + hmargin, y, width - (2 * hmargin), height, this.context.strokeStyle, this.context.fillStyle ); } } if (j == 0) { var startY = y; var startX = x; } // // Store the redraw coords if the shadow is enabled // if (shadow) { redrawCoords.push([x + hmargin, y, width - (2 * hmargin), height, this.context.fillStyle]); } // // Stacked 3D effect // if (variant == '3d') { var prevFillStyle = this.context.fillStyle; var prevStrokeStyle = this.context.strokeStyle; // Draw the top side if (j == 0) { this.context.beginPath(); this.context.moveTo(startX + hmargin, y); this.context.lineTo(startX + properties.variantThreedOffsetx + hmargin, y - properties.variantThreedOffsety); this.context.lineTo(startX + properties.variantThreedOffsetx + barWidth + hmargin, y - properties.variantThreedOffsety); this.context.lineTo(startX + barWidth + hmargin, y); this.context.closePath(); this.context.fill(); this.context.stroke(); } // Draw the side section this.context.beginPath(); this.context.moveTo(startX + barWidth + hmargin, y); this.context.lineTo(startX + barWidth + hmargin + properties.variantThreedOffsetx, y - properties.variantThreedOffsety); this.context.lineTo(startX + barWidth + hmargin + properties.variantThreedOffsetx, y - properties.variantThreedOffsety + height); this.context.lineTo(startX + barWidth + hmargin , y + height); this.context.closePath(); this.context.fill(); this.context.stroke(); // Draw the lighter top side if (j == 0) { this.context.fillStyle = 'rgba(255,255,255,0.5)'; this.context.beginPath(); this.context.moveTo(startX + hmargin, y); this.context.lineTo(startX + properties.variantThreedOffsetx + hmargin, y - properties.variantThreedOffsety); this.context.lineTo(startX + properties.variantThreedOffsetx + barWidth + hmargin, y - properties.variantThreedOffsety); this.context.lineTo(startX + barWidth + hmargin, y); this.context.closePath(); this.context.fill(); this.context.stroke(); } // Draw the darker side section this.context.fillStyle = 'rgba(0,0,0,0.4)'; this.context.beginPath(); this.context.moveTo(startX + barWidth + hmargin, y); this.context.lineTo(startX + barWidth + hmargin + properties.variantThreedOffsetx, y - properties.variantThreedOffsety); this.context.lineTo(startX + barWidth + hmargin + properties.variantThreedOffsetx, y - properties.variantThreedOffsety + height); this.context.lineTo(startX + barWidth + hmargin , y + height); this.context.closePath(); this.context.fill(); this.context.stroke(); this.context.strokeStyle = prevStrokeStyle; this.context.fillStyle = prevFillStyle; } y += height; } // // Redraw the bars if the shadow is enabled due to hem being drawn from the bottom up, and the // shadow spilling over to higher up bars // if (shadow) { RGraph.noShadow(this); for (k=0; k<redrawCoords.length; ++k) { this.context.strokeStyle = strokeStyle; this.context.fillStyle = redrawCoords[k][4]; this.context.strokeRect(redrawCoords[k][0], redrawCoords[k][1], redrawCoords[k][2], redrawCoords[k][3]); this.context.fillRect(redrawCoords[k][0], redrawCoords[k][1], redrawCoords[k][2], redrawCoords[k][3]); this.context.stroke(); this.context.fill(); } // Reset the redraw coords to be empty redrawCoords = []; } // // Grouped bar // } else if (this.data[i] && typeof this.data[i] == 'object' && properties.grouping == 'grouped') { var redrawCoords = []; this.context.lineWidth = properties.linewidth; for (j=0; j<this.data[i].length; ++j) { // Set the fill and stroke colors this.context.strokeStyle = strokeStyle; this.context.fillStyle = colors[j]; // // Sequential colors // if (properties.colorsSequential && colors[sequentialColorIndex]) { this.context.fillStyle = colors[sequentialColorIndex++]; } else if (properties.colorsSequential) { this.context.fillStyle = colors[sequentialColorIndex - 1]; } var individualBarWidth = (width - (2 * hmargin)) / this.data[i].length; var height = ((this.data[i][j] + (this.data[i][j] < 0 ? this.scale2.min : (-1 * this.scale2.min) )) / (this.scale2.max - this.scale2.min) ) * (this.canvas.height - this.marginTop - this.marginBottom ); var groupedMargin = properties.marginInnerGrouped; var startX = x + hmargin + (j * individualBarWidth); // // Check for a negative bar width // if (individualBarWidth < 0) { alert('[RGRAPH] Warning: you have a negative bar width. This may be caused by the marginInner being too high or the width of the canvas not being sufficient.'); } // If the X axis pos is in the center, we need to half the height if (xaxispos == 'center') { height /= 2; } // // Determine the start positioning for the bar // if (xaxispos == 'top') { var startY = this.marginTop; var height = Math.abs(height); } else if (xaxispos == 'center') { var startY = this.marginTop + (this.grapharea / 2) - height; } else { var startY = this.getYCoord(0);//this.canvas.height - this.marginBottom - height; var height = Math.abs(Math.abs(this.getYCoord(this.data[i][j])) - this.getYCoord(0)); if (this.data[i][j] >= 0) { startY -= height; } } if (properties.corners === 'round') { this.context.beginPath(); this.context.lineCap = 'miter'; this.context.lineJoin = 'square'; (this.data[i][j] < 0) ? this.roundedCornersRectNegative(startX + groupedMargin, startY, individualBarWidth - (2 * groupedMargin), height) : this.roundedCornersRect(startX + groupedMargin, startY, individualBarWidth - (2 * groupedMargin), height); this.context.stroke(); this.context.fill(); } else { this.context.beginPath(); this.context.lineJoin = 'miter'; this.context.lineCap = 'square'; this.context.rect(startX + groupedMargin, startY, individualBarWidth - (2 * groupedMargin), height); this.context.stroke(); this.context.fill(); } y += height; // // Grouped 3D effect // if (variant == '3d') { var prevFillStyle = this.context.fillStyle; var prevStrokeStyle = this.context.strokeStyle; var hmarginGrouped = properties.marginInnerGrouped; // Draw the top side if (this.data[i][j] >= 0) { this.context.beginPath(); this.context.moveTo(startX + hmarginGrouped, startY); this.context.lineTo(startX + hmarginGrouped + properties.variantThreedOffsetx, startY - properties.variantThreedOffsety); this.context.lineTo(startX + properties.variantThreedOffsetx + individualBarWidth - hmarginGrouped, startY - properties.variantThreedOffsety); this.context.lineTo(startX + individualBarWidth - hmarginGrouped, startY); this.context.closePath(); this.context.fill(); this.context.stroke(); } // Draw the side section this.context.beginPath(); this.context.moveTo( startX + individualBarWidth - hmarginGrouped - 1, startY ); this.context.lineTo( startX + individualBarWidth - hmarginGrouped + properties.variantThreedOffsetx, this.data[i][j] < 0 ? (this.getYCoord(0) + Math.abs(height) - properties.variantThreedOffsety) - (properties.xaxisPosition === 'center' ? 0 : Math.abs(height) - this.properties.variantThreedOffsety) : this.getYCoord(0) - height - properties.variantThreedOffsety ); this.context.lineTo( startX + individualBarWidth - hmarginGrouped + properties.variantThreedOffsetx, this.data[i][j] < 0 && (startY + height - properties.variantThreedOffsety) < this.getYCoord(0) ? (this.getYCoord(0)) : (startY + height - properties.variantThreedOffsety) ); this.context.lineTo(startX + individualBarWidth - hmarginGrouped - 1, startY + height); this.context.closePath(); this.context.fill(); this.context.stroke(); // Draw the lighter top side - but only if the current value is positive if (this.data[i][j] >= 0) { this.context.fillStyle = 'rgba(255,255,255,0.5)'; this.context.beginPath(); // BL this.context.moveTo(startX + hmarginGrouped, startY); // BR this.context.lineTo(startX + hmarginGrouped + properties.variantThreedOffsetx, startY - properties.variantThreedOffsety); // TR this.context.lineTo(startX + properties.variantThreedOffsetx + individualBarWidth - hmarginGrouped, startY - properties.variantThreedOffsety); // TL this.context.lineTo(startX + individualBarWidth - hmarginGrouped, startY); this.context.closePath(); this.context.fill(); this.context.stroke(); } // Draw the darker side section this.context.fillStyle = 'rgba(0,0,0,0.4)'; this.context.beginPath(); this.context.moveTo( startX + individualBarWidth - hmarginGrouped, startY ); this.context.lineTo( startX + individualBarWidth + properties.variantThreedOffsetx - hmarginGrouped, this.data[i][j] < 0 ? (this.getYCoord(0) + Math.abs(height) - properties.variantThreedOffsety) - (properties.xaxisPosition === 'center' ? 0 : Math.abs(height) - this.properties.variantThreedOffsety) : this.getYCoord(0) - height - properties.variantThreedOffsety ); this.context.lineTo( startX + individualBarWidth + properties.variantThreedOffsetx - hmarginGrouped, +this.data[i][j] < 0 && (startY + height - 5) < this.getYCoord(0) ? ((height > this.properties.variantThreedOffsety) ? this.getYCoord(0) + height - this.properties.variantThreedOffsety : this.getYCoord(0)) : (startY + height - properties.variantThreedOffsety) ); // TL corner this.context.lineTo(startX + individualBarWidth - hmarginGrouped, startY + height); this.context.closePath(); this.context.fill(); this.context.stroke(); this.context.strokeStyle = prevStrokeStyle; this.context.fillStyle = prevFillStyle; } if (height < 0) { height = Math.abs(height); startY = startY - height; } this.coords.push([startX + groupedMargin, startY, individualBarWidth - (2 * groupedMargin), height]); if (typeof this.coords2[i] == 'undefined') { this.coords2[i] = []; } this.coords2[i].push([startX + groupedMargin, startY, individualBarWidth - (2 * groupedMargin), height]); // Facilitate shadows going to the left if (properties.shadow) { redrawCoords.push([ startX + groupedMargin, startY, individualBarWidth - (2 * groupedMargin), height, this.context.fillStyle ]); } } // // Redraw the bar if shadows are going to the left // if (redrawCoords.length) { RGraph.noShadow(this); this.context.lineWidth = properties.linewidth; this.context.beginPath(); for (var j=0; j<redrawCoords.length; ++j) { this.context.fillStyle = redrawCoords[j][4]; this.context.strokeStyle = properties.colorsStroke; this.context.fillRect(redrawCoords[j][0], redrawCoords[j][1], redrawCoords[j][2], redrawCoords[j][3]); this.context.strokeRect(redrawCoords[j][0], redrawCoords[j][1], redrawCoords[j][2], redrawCoords[j][3]); } this.context.fill(); this.context.stroke(); redrawCoords = []; } } else { this.coords.push([]); } this.context.closePath(); } // If 3D, redraw the right hand Y axis if (properties.variant === '3d' && properties.yaxisPosition === 'right' && properties.yaxis) { RGraph.draw3DYAxis(this); } // // Turn off any shadow // RGraph.noShadow(this); }; // // Draws the labels for the graph. As of version 5.2 this // no longer draws the X axis labels // this.drawLabels = function () { // // Draw above labels - nothing else todo here now that there // are common drawXAxis() and drawYAxis() functions. // this.drawAboveLabels(); }; // // Not used by the class during creating the graph, but is used by event handlers // to get the coordinates (if any) of the selected bar // // @param object e The event object // @param object OPTIONAL You can pass in the bar object instead of the // function using "this" // this.getShape = function (e) { // This facilitates you being able to pass in the bar object as a parameter instead of // the function getting it from itself var obj = arguments[1] ? arguments[1] : this; var mouseXY = RGraph.getMouseXY(e), mouseX = mouseXY[0], mouseY = mouseXY[1], canvas = obj.canvas, context = obj.context, coords = obj.coords for (var i=0,len=coords.length; i<len; i+=1) { if (this.coords[i].length == 0) { continue; } if (RGraph.tooltipsHotspotIgnore(this, i)) { continue; } var left = coords[i][0], top = coords[i][1], width = coords[i][2], height = coords[i][3]; // Old way of testing //if (mouseX >= left && mouseX <= (left + width) && mouseY >= top && mouseY <= (top + height)) { // Recreate the path/rectangle so that it can be tested // ** DO NOT STROKE OR FILL IT ** if (properties.tooltipsHotspotXonly) { this.path( 'b r % % % %', left, this.marginTop, width, this.canvas.height - this.marginBottom ); } else { var indexes = RGraph.sequentialIndexToGrouped(i, this.data); // Use the rounded rect function if the chart is stacked and the index is 0 if (properties.grouping === 'stacked' && properties.corners === 'round' && indexes[1] === 0) { this.context.beginPath(); this.roundedCornersRect(left, top, width, height); } else { this.path( 'b r % % % %', left, top, width, height ); } } if ( this.context.isPointInPath(mouseX, mouseY) && (this.properties.clip ? RGraph.clipTo.test(this, mouseX, mouseY) : true) ) { if (properties.tooltips && RGraph.parseTooltipText) { var tooltip = RGraph.parseTooltipText(properties.tooltips, i); } // Work out the dataset var dataset = 0, idx = i while (idx >= (typeof obj.data[dataset] === 'object' && obj.data[dataset] ? obj.data[dataset].length : 1)) { if (typeof obj.data[dataset] === 'number') { idx -= 1; } else if (obj.data[dataset]) { // Accounts for null being an object idx -= obj.data[dataset].length; } else { idx -= 1; } dataset++; } if (typeof obj.data[dataset] == 'number') { idx = 0; } return { object: this, x: left, y: top, width: width, height: height, tooltip: typeof tooltip === 'string' ? tooltip : null, label: properties.xaxisLabels && typeof properties.xaxisLabels[dataset] === 'string' ? properties.xaxisLabels[dataset] : null, dataset: dataset, index: idx, sequentialIndex: i }; } } return null; }; // // This retrives the bar based on the X coordinate only. // // @param object e The event object // @param object OPTIONAL You can pass in the bar object instead of the // function using "this" // this.getShapeByX = function (e) { var canvas = e.target; var mouseCoords = RGraph.getMouseXY(e); // This facilitates you being able to pass in the bar object as a parameter instead of // the function getting it from itself var obj = arguments[1] ? arguments[1] : this; // // Loop through the bars determining if the mouse is over a bar // for (var i=0,len=obj.coords.length; i<len; i++) { if (obj.coords[i].length == 0) { continue; } var mouseX = mouseCoords[0]; var mouseY = mouseCoords[1]; var left = obj.coords[i][0]; var top = obj.coords[i][1]; var width = obj.coords[i][2]; var height = obj.coords[i][3]; if (mouseX >= left && mouseX <= (left + width)) { if (properties.tooltips) { var tooltip = RGraph.parseTooltipText ? RGraph.parseTooltipText(properties.tooltips, i) : properties.tooltips[i]; } var indexes = RGraph.sequentialIndexToGrouped(i, this.data); return { object: obj, x: left, y: top, width: width, height: height, dataset: indexes[0], index: indexes[1], sequentialIndex: i, tooltip: typeof tooltip === 'string' ? tooltip : null }; } } return null; }; // // When you click on the chart, this method can return the Y value at that point. It works for any point on the // chart (that is inside the margins) - not just points within the Bars. // // EITHER: // // @param object arg The event object // // OR: // // @param object arg A two element array containing the X and Y coordinates // this.getValue = function (arg) { if (arg.length == 2) { var mouseX = arg[0]; var mouseY = arg[1]; } else { var mouseCoords = RGraph.getMouseXY(arg); var mouseX = mouseCoords[0]; var mouseY = mouseCoords[1]; } if ( mouseY < properties.marginTop || mouseY > (this.canvas.height - properties.marginBottom) || mouseX < properties.marginLeft || mouseX > (this.canvas.width - properties.marginRight) ) { return null; } if (properties.xaxisPosition == 'center') { var value = (((this.grapharea / 2) - (mouseY - properties.marginTop)) / this.grapharea) * (this.scale2.max - this.scale2.min) value *= 2; if (value >= 0) { value += this.scale2.min; } else { value -= this.scale2.min; } } else if (properties.xaxisPosition == 'top') { var value = ((this.grapharea - (mouseY - properties.marginTop)) / this.grapharea) * (this.scale2.max - this.scale2.min) value = this.scale2.max - value; value = Math.abs(value) * -1; } else { var value = ((this.grapharea - (mouseY - properties.marginTop)) / this.grapharea) * (this.scale2.max - this.scale2.min) value += this.scale2.min; } return value; }; // // This function can be used when the canvas is clicked on (or similar - depending on the event) // to retrieve the relevant Y coordinate for a particular value. // // @param int value The value to get the Y coordinate for // this.getYCoord = function (value) { if (value > this.scale2.max) { return null; } var y, xaxispos = properties.xaxisPosition; if (xaxispos == 'top') { // Account for negative numbers if (value < 0) { value = Math.abs(value); } y = ((value - this.scale2.min) / (this.scale2.max - this.scale2.min)) * this.grapharea; y = y + this.marginTop } else if (xaxispos == 'center') { y = ((value - this.scale2.min) / (this.scale2.max - this.scale2.min)) * (this.grapharea / 2); y = (this.grapharea / 2) - y; y += this.marginTop; } else { if (value < this.scale2.min) { value = this.scale2.min; } y = ((value - this.scale2.min) / (this.scale2.max - this.scale2.min)); y *= (this.canvas.height - this.marginTop - this.marginBottom); y = this.canvas.height - this.marginBottom - y; } return y; }; // // Each object type has its own Highlight() function which highlights the appropriate shape // // @param object shape The shape to highlight // this.highlight = function (shape) { if (typeof properties.highlightStyle === 'function') { (properties.highlightStyle)(shape); // Highlight all of the rects except this one - essentially // an inverted highlight } else if (typeof properties.highlightStyle === 'string' && properties.highlightStyle === 'invert') { for (var i=0; i<this.coords.length; ++i) { if (i !== shape.sequentialIndex) { this.path( 'b r % % % % s % f %', this.coords[i][0],this.coords[i][1],this.coords[i][2],this.coords[i][3], properties.highlightStroke, properties.highlightFill ); } } // Redraw the X axis so the highlight doesn't // appear over the X axis. But not the X axis // labels or the title. This is new in // September 2024. RGraph.drawXAxis(this, { labels: false, title: false }); } else { if (properties.grouping === 'stacked' && shape.index === 0 && properties.xaxisPosition === 'bottom') { this.context.beginPath(); this.context.strokeStyle = properties.highlightStroke; this.context.fillStyle = properties.highlightFill; if (properties.corners === 'round') { this.roundedCornersRect(shape.x,shape.y,shape.width,shape.height); } else { this.context.rect(shape.x, shape.y, shape.width, shape.height); } this.context.stroke(); this.context.fill(); } else { // Add the new highlight RGraph.Highlight.rect(this, shape); // Redraw the X axis so the highlight doesn't // appear over the X axis. But not the X axis // labels or the title. This is new in // September 2024. RGraph.drawXAxis(this, { labels: false, title: false }); } } }; // // The getObjectByXY() worker method // this.getObjectByXY = function (e) { var mouseXY = RGraph.getMouseXY(e); var shape = this.getShape(e); // Adjust the mouse Y coordinate for when the bar chart is // a 3D variant if the textAccessible wrapper DIV is not here if (properties.variant === '3d' && !properties.textAccessible) { var adjustment = properties.variantThreedAngle * mouseXY[0]; mouseXY[1] -= adjustment; } if ( mouseXY[0] >= properties.marginLeft && mouseXY[0] <= (this.canvas.width - properties.marginRight) && mouseXY[1] >= properties.marginTop && mouseXY[1] <= (this.canvas.height - properties.marginBottom) ) { return this; } }; // // This method handles the adjusting calculation for when the mouse is moved // // @param object e The event object // this.adjusting_mousemove = function (e) { // // Handle adjusting for the Bar // if (properties.adjustable && RGraph.Registry.get('adjusting') && RGraph.Registry.get('adjusting').uid == this.uid) { // Rounding the value to the given number of decimals make the chart step var value = Number(this.getValue(e)); var shape = RGraph.Registry.get('adjusting.shape') if (shape) { RGraph.Registry.set('adjusting.shape', shape); if (this.stackedOrGrouped && properties.grouping == 'grouped') { var indexes = RGraph.sequentialIndexToGrouped(shape.sequentialIndex, this.data); if (typeof this.data[indexes[0]] == 'number') { this.data[indexes[0]] = Number(value); } else if (!RGraph.isNullish(this.data[indexes[0]])) { this.data[indexes[0]][indexes[1]] = Number(value); } } else if (typeof this.data[shape.sequentialIndex] == 'number') { this.data[shape.sequentialIndex] = Number(value); } RGraph.redrawCanvas(e.target); RGraph.fireCustomEvent(this, 'onadjust'); } } }; // // This allows for easy specification of gradients // this.parseColors = function () { // Save the original colors so that they can be restored when the canvas is reset if (this.original_colors.length === 0) { this.original_colors.colors = RGraph.arrayClone(properties.colors); this.original_colors.keyColors = RGraph.arrayClone(properties.keyColors); this.original_colors.crosshairsColor = properties.crosshairsColor; this.original_colors.highlightStroke = properties.highlightStroke; this.original_colors.highlightFill = properties.highlightFill; this.original_colors.textColor = properties.textColor; this.original_colors.backgroundBarsColor1 = properties.backgroundBarsColor1; this.original_colors.backgroundBarsColor2 = properties.backgroundBarsColor2; this.original_colors.backgroundGridColor = properties.backgroundGridColor; this.original_colors.backgroundColor = properties.backgroundColor; this.original_colors.colorsStroke = properties.colorsStroke; this.original_colors.axesColor = properties.axesColor; } // colors var colors = properties.colors; if (colors) { for (var i=0; i<colors.length; ++i) { colors[i] = this.parseSingleColorForGradient(colors[i]); } } // keyColors var colors = properties.keyColors; if (colors) { for (var i=0; i<colors.length; ++i) { colors[i] = this.parseSingleColorForGradient(colors[i]); } } properties.crosshairsColor = this.parseSingleColorForGradient(properties.crosshairsColor); properties.highlightStroke = this.parseSingleColorForGradient(properties.highlightStroke); properties.highlightFill = this.parseSingleColorForGradient(properties.highlightFill); properties.textColor = this.parseSingleColorForGradient(properties.textColor); properties.backgroundBarsColor1 = this.parseSingleColorForGradient(properties.backgroundBarsColor1); properties.backgroundBarsColor2 = this.parseSingleColorForGradient(properties.backgroundBarsColor2); properties.backgroundGridColor = this.parseSingleColorForGradient(properties.backgroundGridColor); properties.backgroundColor = this.parseSingleColorForGradient(properties.backgroundColor); properties.colorStroke = this.parseSingleColorForGradient(properties.colorStroke); properties.axesColor = this.parseSingleColorForGradient(properties.axesColor); }; // // Use this function to reset the object to the post-constructor state. Eg reset colors if // need be etc // this.reset = function () { }; // // This parses a single color value. This method can also parse the new // JSON gradient syntax. // // @param string The color to parse // this.parseSingleColorForGradient = function (color) { if (!color || typeof color != 'string') { return color; } if (color.match(/^gradient\((.*)\)$/i)) { // Allow for JSON gradients if (color.match(/^gradient\(({.*})\)$/i)) { return RGraph.parseJSONGradient({object: this, def: RegExp.$1}); } var parts = RegExp.$1.split(':'); // Create the gradient var grad = this.context.createLinearGradient(0,this.canvas.height - properties.marginBottom, 0, properties.marginTop); var diff = 1 / (parts.length - 1); grad.addColorStop(0, RGraph.trim(parts[0])); for (var j=1,len=parts.length; j<len; ++j) { grad.addColorStop(j * diff, RGraph.trim(parts[j])); } } return grad ? grad : color; }; this.drawBevel = function () { var coords = this.coords, coords2 = this.coords2; if (properties.grouping == 'stacked') { for (var i=0; i<coords2.length; ++i) { if (coords2[i] && coords2[i][0] && coords2[i][0][0]) { var x = coords2[i][0][0]; var y = coords2[i][0][1]; var w = coords2[i][0][2]; var arr = []; for (var j=0; j<coords2[i].length; ++j) { arr.push(coords2[i][j][3]); } var h = RGraph.arraySum(arr); this.context.save(); this.context.strokeStyle = 'black'; // Clip to the rect this.context.beginPath(); this.context.rect(x, y, w, h); this.context.clip(); // Add the shadow this.context.shadowColor = 'black'; this.context.shadowOffsetX = 0; this.context.shadowOffsetY = 0; this.context.shadowBlur = 20; this.context.beginPath(); this.context.rect(x - 3, y - 3, w + 6, h + 100); this.context.lineWidth = 5; this.context.stroke(); this.context.restore(); } } } else { for (var i=0; i<coords.length; ++i) { if (coords[i]) { var x = coords[i][0]; var y = coords[i][1]; var w = coords[i][2]; var h = coords[i][3]; var xaxispos = properties.xaxisPosition; var xaxis_ycoord = ((this.canvas.height - this.marginTop - this.marginBottom) / 2) + this.marginTop; this.context.save(); this.context.strokeStyle = 'black'; // Clip to the rect this.context.beginPath(); this.context.rect(x, y, w, h); this.context.clip(); // Add the shadow this.context.shadowColor = 'black'; this.context.shadowOffsetX = 0; this.context.shadowOffsetY = 0; this.context.shadowBlur = 20; if (xaxispos == 'top' || (xaxispos == 'center' && (y + h) > xaxis_ycoord)) { y = y - 100; h = h + 100; } else { y = y; h = h + 100; } this.context.beginPath(); this.context.rect(x - 3, y - 3, w + 6, h + 6); this.context.lineWidth = 5; this.context.stroke(); this.context.restore(); } } } }; // // This function handles highlighting an entire data-series for the interactive // key // // @param int index The index of the data series to be highlighted // this.interactiveKeyHighlight = function (index) { var obj = this; this.coords2.forEach(function (value, idx, arr) { if (typeof value[index] == 'object' && value[index]) { var x = value[index][0] - 0.5, y = value[index][1] - 0.5, w = value[index][2] + 1, h = value[index][3] + 1; obj.context.fillStyle = properties.keyInteractiveHighlightChartFill; obj.context.strokeStyle = properties.keyInteractiveHighlightChartStroke; obj.context.lineWidth = properties.keyInteractiveHighlightChartLinewidth; obj.context.strokeRect(x, y, w, h); obj.context.fillRect(x, y, w, h); } }); }; // // 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; } if (typeof this[type] !== 'function') { this[type] = func; } else { RGraph.addCustomEventListener(this, type, func); } return this; }; // Draws the above labels this.drawLabelsAbove = this.drawAboveLabels = function () { var labels = properties.labelsAbove, specific = properties.labelsAboveSpecific, bold = typeof properties.labelsAboveBold === 'boolean' ? properties.labelsAboveBold : properties.textBold, italic = typeof properties.labelsAboveItalic === 'boolean' ? properties.labelsAboveItalic : properties.textItalic, color = properties.labelsAboveColor || properties.textColor, font = properties.labelsAboveFont || properties.textFont, size = typeof properties.labelsAboveSize === 'number' ? properties.labelsAboveSize : properties.textSize, background = properties.labelsAboveBackground, decimals = properties.labelsAboveDecimals, angle = -1 * properties.labelsAboveAngle, unitsPre = properties.labelsAboveUnitsPre, unitsPost = properties.labelsAboveUnitsPost, point = properties.labelsAbovePoint, thousand = properties.labelsAboveThousand, formatter = properties.labelsAboveFormatter, coords = this.coords, coords2 = this.coords2, data = this.data, ldata = RGraph.arrayLinearize(this.data), offsetx = properties.labelsAboveOffsetx, offsety = properties.labelsAboveOffsety, text_italic = properties.textItalic, text_bold = properties.textBold, text_color = properties.textColor, text_font = properties.textFont, text_size = properties.textSize, grouping = properties.grouping; // BC if (typeof properties.labelsAboveOffset === 'number') { offsety = properties.labelsAboveOffset; } var textConf = RGraph.getTextConf({ object: this, prefix: 'labelsAbove' }); // Turn off any shadow RGraph.noShadow(this); // Color this.context.fillStyle = textConf.color; // This bit draws the text labels that appear above the bars if requested if (labels && grouping === 'grouped') { for (var i=0,len=data.length,sequentialIndex=0; i<len; i+=1) { // Alignment for regular, positive bars if (typeof data[i] === 'number' && data[i] >= 0) { var angle = angle; var halign = (angle ? 'left' : 'center'); var valign = angle !== 0 ? 'center' : 'bottom'; RGraph.text({ object: this, font: textConf.font, size: textConf.size, color: textConf.color, bold: textConf.bold, italic: textConf.italic, x: coords2[i][0][0] + (coords2[i][0][2] / 2) + offsetx, y: coords2[i][0][1] - offsety - 3, text: specific ? (specific[sequentialIndex] || '') : RGraph.numberFormat({ object: this, number: Number(typeof data[i] === 'object' ? data[i][0] : data[i]).toFixed(decimals), value: Number(typeof data[i] === 'object' ? data[i][0] : data[i]).toFixed(decimals), unitspre: unitsPre, unitspost: unitsPost, point: point, thousand: thousand, formatter: formatter, dataset: 0, index: i }), halign: halign, valign: valign, angle: angle, marker: false, bounding: true, 'bounding.fill': background, 'bounding.stroke': 'rgba(0,0,0,0)', tag: 'labels.above' }); sequentialIndex++; // Alignment for regular, negative bars } else if (typeof data[i] === 'number' && data[i] < 0) { var angle = angle; var halign = angle ? 'right' : 'center'; var valign = angle !== 0 ? 'center' : 'top'; RGraph.text({ object: this, font: textConf.font, size: textConf.size, color: textConf.color, bold: textConf.bold, italic: textConf.italic, x: coords2[i][0][0] + (coords2[i][0][2] / 2) + offsetx, y: coords2[i][0][1] + coords2[i][0][3] + offsety + 5, text: specific ? (specific[sequentialIndex] || '') : RGraph.numberFormat({ object: this, number: Number(typeof data[i] === 'object' ? data[i][0] : data[i]).toFixed(decimals), value: Number(typeof data[i] === 'object' ? data[i][0] : data[i]).toFixed(decimals), unitspre: unitsPre, unitspost: unitsPost, point: point, thousand: thousand, formatter: formatter, dataset: 0, index: i, }), halign: halign, valign: valign, angle: angle, bounding: true, 'bounding.fill':background, 'bounding.stroke':'rgba(0,0,0,0)', marker: false, tag: 'labels.above' }); sequentialIndex++; // Alignment for grouped bars } else if (typeof data[i] === 'object') { for (var j=0,len2=data[i].length; j<len2; j+=1) { var angle = angle; var halign = data[i][j] < 0 ? 'right' : 'left'; halign = angle === 0 ? 'center' : halign; var valign = data[i][j] < 0 ? 'top' : 'bottom'; valign = angle != 0 ? 'center' : valign; RGraph.text({ object: this, font: textConf.font, size: textConf.size, color: textConf.color, bold: textConf.bold, italic: textConf.italic, x: coords2[i][j][0] + (coords2[i][j][2] / 2) + offsetx, y: properties.xaxisPosition === 'top' ? coords2[i][j][1] + coords2[i][j][3] + 5 : coords2[i][j][1] + (data[i][j] < 0 ? coords2[i][j][3] + offsety + 5: -offsety), text: specific ? (specific[sequentialIndex] || '') : RGraph.numberFormat({ object: this, number: Number(data[i][j]).toFixed(decimals), value: Number(data[i][j]).toFixed(decimals), unitspre: unitsPre, unitspost: unitsPost, point: point, thousand: thousand, formatter: formatter, dataset: i, index: j }), halign: halign, valign: properties.xaxisPosition === 'top' ? 'top' : valign, angle: angle, bounding: true, 'bounding.fill': background, 'bounding.stroke': 'rgba(0,0,0,0)', marker: false, tag: 'labels.above' }); sequentialIndex++; } } } // // STACKED bars // } else if (labels && grouping === 'stacked') { for (var i=0,len=data.length,sequentialIndex=0; i<len; i+=1) { if (typeof data[i] === 'object') { var angle = angle; var halign = angle != 0 ? 'left' : 'center'; var valign = angle != 0 ? 'center' : 'bottom'; RGraph.text({ object: this, font: textConf.font, size: textConf.size, color: textConf.color, bold: textConf.bold, italic: textConf.italic, x: coords2[i][0][0] + (coords2[i][0][2] / 2) + offsetx, y: coords2[i][0][1] + (data[i][0] < 0 ? coords2[i][0][3] : 0) + (properties.xaxisPosition === 'top' ? (coords2[i].reduce( (acc, curr) => acc += curr[3], 0) + 3 + offsety) : (-3 - offsety) ), text: specific ? (specific[sequentialIndex] || '') : RGraph.numberFormat({ object: this, number: Number(RGraph.arraySum(data[i])).toFixed(decimals), value: Number(RGraph.arraySum(data[i])).toFixed(decimals), unitspre: unitsPre, unitspost: unitsPost, point: point, thousand: thousand, formatter: formatter, dataset: i }), halign: halign, valign: properties.xaxisPosition === 'top' ? 'top' : valign, angle: angle, bounding: true, 'bounding.fill':background, 'bounding.stroke': 'rgba(0,0,0,0)', marker: false, tag: 'labels.above' }); sequentialIndex += data[i].length; // // Regular numbers but in a stacked grouping // } else { var angle = angle; var halign = angle != 0 ? 'left' : 'center'; var valign = angle != 0 ? 'center' : 'bottom'; RGraph.text({ object: this, font: textConf.font, size: textConf.size, color: textConf.color, bold: textConf.bold, italic: textConf.italic, x: coords2[i][0][0] + (coords2[i][0][2] / 2) + offsetx, y: coords2[i][0][1] + (data[i][0] < 0 ? coords2[i][0][3] : 0) + (properties.xaxisPosition === 'top' ? coords2[i][0][3] + offsety + 3 : 0 - offsety - 3), text: specific ? (specific[sequentialIndex] || '') : RGraph.numberFormat({ object: this, number: Number(data[i]).toFixed(decimals), value: Number(data[i]).toFixed(decimals), unitspre: unitsPre, unitspost: unitsPost, point: point, thousand: thousand, formatter: formatter, dataset: i }), halign: halign, valign: properties.xaxisPosition === 'top' ? 'top' : valign, angle: angle, bounding: true, 'bounding.fill': background, 'bounding.stroke': 'rgba(0,0,0,0)', marker: false, tag: 'labels.above' }); sequentialIndex++; } } } }; // // This function runs once only // this.firstDrawFunc = function () { }; // // (new) Bar chart Wave effect. This is a rewrite that should be smoother // because it just uses a single loop and not setTimeout // // @param object OPTIONAL An object map of options. You specify 'frames' here to give the number of frames in the effect // @param function OPTIONAL A function that will be called when the effect is complete // this.wave = function () { // Cancel any stop request if one is pending this.cancelStopAnimation(); // Reset the data to the original this.data = RGraph.arrayClone(this.original_data); // If there's only one bar call the grow function instead if (this.data.length === 1) { return this.grow(arguments[0], arguments[1]); } var obj = this, opt = arguments[0] || {}, labelsAbove = this.get('labelsAbove'); opt.frames = opt.frames || 60; opt.startFrames = []; opt.counters = []; var framesperbar = opt.frames / 3, frame = -1, callback = arguments[1] || function () {}, original = RGraph.arrayClone(this.original_data); // // turn off the labelsAbove option whilst animating // this.set('labelsAbove', false); for (var i=0,len=obj.data.length; i<len; i+=1) { opt.startFrames[i] = ((opt.frames / 2) / (obj.data.length - 1)) * i; if (typeof obj.data[i] === 'object' && obj.data[i]) { opt.counters[i] = []; for (var j=0; j<obj.data[i].length; j++) { opt.counters[i][j] = 0; } } else { opt.counters[i] = 0; } } // // This stops the chart from jumping // obj.draw(); obj.set('yaxisScaleMax', obj.scale2.max); RGraph.clear(obj.canvas); function iterator () { if (obj.stopAnimationRequested) { // Reset the flag obj.stopAnimationRequested = false; // Reset the data obj.data = RGraph.arrayClone(obj.original_data); return; } ++frame; for (let i=0,len=obj.data.length; i<len; i+=1) { if (frame > opt.startFrames[i]) { if (typeof obj.data[i] === 'number') { obj.data[i] = Math.min( Math.abs(original[i]), Math.abs(original[i] * ( (opt.counters[i]++) / framesperbar)) ); // Make the number negative if the original was if (original[i] < 0) { obj.data[i] *= -1; } } else if (!RGraph.isNullish(obj.data[i])) { for (let j=0,len2=obj.data[i].length; j<len2; j+=1) { obj.data[i][j] = Math.min( Math.abs(original[i][j]), Math.abs(original[i][j] * ( (opt.counters[i][j]++) / framesperbar)) ); // Make the number negative if the original was if (original[i][j] < 0) { obj.data[i][j] *= -1; } } } } else { obj.data[i] = typeof obj.data[i] === 'object' && obj.data[i] ? RGraph.arrayPad([], obj.data[i].length, 0) : (RGraph.isNullish(obj.data[i]) ? null : 0); } } if (frame >= opt.frames) { if (labelsAbove) { obj.set('labelsAbove', true); RGraph.redraw(); } callback(obj); } else { RGraph.redrawCanvas(obj.canvas); RGraph.Effects.updateCanvas(iterator); } } iterator(); return this; }; // // Color Wave effect. This fades in color sequentially like the wave effect // makes the bars grow. // // @param object OPTIONAL An object map of options. You specify 'frames' // here to give the number of frames in the effect // @param function OPTIONAL A function that will be called when the effect // is complete // this.colorwave = this.colorWave = function () { // Cancel any stop request if one is pending this.cancelStopAnimation(); // Reset the data to the original this.data = RGraph.arrayClone(this.original_data); var obj = this, opt = arguments[0] || {}; opt.frames = opt.frames || 60; opt.startFrames = []; opt.counters = []; colors = obj.properties.colors; // If just one color is specified and colorsSequential is not, then // pad the colors array out if (colors.length <= obj.data.length) { obj.set('colorsSequential', true); colors = RGraph.arrayPad(colors, obj.data.length, colors[colors.length - 1]); } var framesperbar = opt.frames / 2, frame = -1, callback = arguments[1] || function () {}, originalColors = RGraph.arrayClone(obj.properties.colors); for (var i=0,len=originalColors.length; i<len; i+=1) { opt.startFrames[i] = ((opt.frames / 2) / (originalColors.length - 1)) * i; opt.counters[i] = 0; } function iterator () { ++frame; for (var i=0,len=colors.length; i<len; i+=1) { if (frame > opt.startFrames[i] && colors[i].match(/^rgba?\(([0-9 ]+),([0-9 ]+),([0-9 ]+)(,([ 0-9.]+)?)\)/)) { // DO NOT USE SPACES! colors[i] = 'rgba({1},{2},{3},{4})'.format( RegExp.$1, RegExp.$2, RegExp.$3, (frame - opt.startFrames[i]) / framesperbar ); } else { colors[i] = colors[i].replace(/,[0-9. ]+\)/, ',0)'); } } if (frame >= opt.frames) { callback(obj); } else { RGraph.redrawCanvas(obj.canvas); RGraph.Effects.updateCanvas(iterator); } } iterator(); return this; }; // // Grow // // The Bar chart Grow effect gradually increases the values of the bars // // @param object An object of options - eg: {frames: 30} // @param function A function to call when the effect is complete // this.grow = function () { // Cancel any stop request if one is pending this.cancelStopAnimation(); // Reset the data to the original this.data = RGraph.arrayClone(this.original_data); // Callback var opt = arguments[0] || {}, frames = opt.frames || 30, frame = 0, callback = arguments[1] || function () {}, obj = this, labelsAbove = this.get('labelsAbove'); // Go through the data and change string arguments of the format +/-[0-9] // to absolute numbers if (RGraph.isArray(opt.data)) { var ymax = 0; for (var i=0; i<opt.data.length; ++i) { if (typeof opt.data[i] === 'object') { for (var j=0; j<opt.data[i].length; ++j) { if (typeof opt.data[i][j] === 'string'&& opt.data[i][j].match(/(\+|\-)([0-9]+)/)) { if (RegExp.$1 === '+') { opt.data[i][j] = this.original_data[i][j] + parseInt(RegExp.$2); } else { opt.data[i][j] = this.original_data[i][j] - parseInt(RegExp.$2); } } ymax = Math.max(ymax, opt.data[i][j]); } } else if (typeof opt.data[i] === 'string' && opt.data[i].match(/(\+|\-)([0-9]+)/)) { if (RegExp.$1 === '+') { opt.data[i] = this.original_data[i] + parseInt(RegExp.$2); } else { opt.data[i] = this.original_data[i] - parseInt(RegExp.$2); } ymax = Math.max(ymax, opt.data[i]); } else { ymax = Math.max(ymax, opt.data[i]); } } var scale = RGraph.getScale({object: this, options: {'scale.max':ymax}}); if (typeof properties.yaxisScaleMax !== 'number') { this.set('yaxisScaleMax', scale.max); } } // // turn off the labelsAbove option whilst animating // this.set('labelsAbove', false); // Stop the scale from changing by setting yaxisScaleMax (if it's not already set) if (properties.yaxisScaleMax == null) { var ymax = 0; for (var i=0; i<this.data.length; ++i) { if (RGraph.isArray(this.data[i]) && properties.grouping === 'stacked') { ymax = Math.max(ymax, Math.abs(RGraph.arraySum(this.data[i]))); } else if (RGraph.isArray(this.data[i]) && properties.grouping === 'grouped') { for (var j=0,group=[]; j<this.data[i].length; j++) { group.push(Math.abs(this.data[i][j])); } ymax = Math.max(ymax, Math.abs(RGraph.arrayMax(group))); } else { ymax = Math.max(ymax, Math.abs(this.data[i])); } } var scale = RGraph.getScale({object: this, options: {'scale.max':ymax}}); this.set('yaxisScaleMax', scale.max); } // You can give a ymax to the grow function if (typeof opt.ymax === 'number') { this.set('yaxisScaleMax', opt.ymax); } var iterator = function () { if (obj.stopAnimationRequested) { // Reset the flag obj.stopAnimationRequested = false; // Reset the data obj.data = RGraph.arrayClone(obj.original_data); return; } var easingMultiplier = RGraph.Effects.getEasingMultiplier(frames, frame); // Alter the Bar chart data depending on the frame for (var j=0,len=obj.original_data.length; j<len; ++j) { if (typeof obj.data[j] === 'object' && !RGraph.isNullish(obj.data[j])) { for (var k=0,len2=obj.data[j].length; k<len2; ++k) { if (obj.firstDraw || !opt.data) { obj.data[j][k] = easingMultiplier * obj.original_data[j][k]; } else if (opt.data && opt.data.length === obj.original_data.length) { var diff = opt.data[j][k] - obj.original_data[j][k]; obj.data[j][k] = (easingMultiplier * diff) + obj.original_data[j][k]; } } } else { if (obj.firstDraw || !opt.data) { obj.data[j] = easingMultiplier * obj.original_data[j]; } else if (opt.data && opt.data.length === obj.original_data.length) { var diff = opt.data[j] - obj.original_data[j]; obj.data[j] = (easingMultiplier * diff) + obj.original_data[j]; } } } //RGraph.clear(obj.canvas); RGraph.redrawCanvas(obj.canvas); if (frame < frames) { frame += 1; RGraph.Effects.updateCanvas(iterator); // Call the callback function } else { // Do some housekeeping if new data was specified thats done in // the constructor - but needs to be redone because new data // has been specified if (RGraph.isArray(opt.data)) { var linear_data = RGraph.arrayLinearize(data); for (var i=0; i<linear_data.length; ++i) { if (!obj['$' + i]) { obj['$' + i] = {}; } } } obj.data = data; obj.original_data = RGraph.arrayClone(data); if (labelsAbove) { obj.set('labelsAbove', true); RGraph.redraw(); } callback(obj); } }; iterator(); return this; }; // // Couple of functions that allow you to control the // Bipolar animation effects // this.stopAnimation = function () { this.stopAnimationRequested = true; }; this.cancelStopAnimation = function () { this.stopAnimationRequested = false; }; // // Draws error-bars for the Bar and Line charts // this.drawErrorbars = function () { var coords = this.coords, color = properties.errorbarsColor || 'black', default_halfwidth = Math.min(properties.errorbarsCappedWidth, coords[0][2]) / 2, x = 0, errorbars = properties.errorbars, length = 0; // If not capped set the width of the cqap to zero if (!properties.errorbarsCapped) { properties.errorbarsCappedWidth = 0; halfwidth = 0; } // Set the linewidth this.context.lineWidth = properties.errorbarsLinewidth; for (var i=0; i<coords.length; ++i) { var barX = coords[i][0], barY = coords[i][1], barW = coords[i][2], barH = coords[i][3]; // Get the grouped version of the index var groupedIndexes = RGraph.sequentialIndexToGrouped(i, this.data); // Determine if this is if (typeof this.data[groupedIndexes[0]] === 'object' && !RGraph.isNullish(this.data[groupedIndexes[0]])) { var isGrouped = true, group = groupedIndexes[0], subgroup = groupedIndexes[1]; } // Default to black color = properties.errorbarsColor || 'black'; // Set the perbar linewidth if the fourth option in the array // is specified if (errorbars[i] && typeof errorbars[i][3] === 'number') { this.context.lineWidth = errorbars[i][3]; } // Set the halfwidth var halfwidth = (errorbars[i]&& typeof errorbars[i][4] === 'number') ? errorbars[i][4] / 2 : default_halfwidth; if (!properties.errorbarsCapped) { halfwidth = 0; } // Calulate the pixel size if (typeof errorbars[i] === 'number') { length = Math.abs(this.getYCoord(errorbars[i]) - this.getYCoord(0)); if (length) { this.path( 'b % % l % % l % % l % % s %', barX + (barW / 2), (typeof this.data[i] === 'number' && this.data[i] < 0 || (isGrouped && this.data[group][subgroup] < 0) ) ? barY + barH : barY, barX + (barW / 2), (typeof this.data[i] === 'number' && this.data[i] < 0 || (isGrouped && this.data[group][subgroup] < 0)) ? barY + barH + length : barY - length, barX + (barW / 2) - halfwidth, (typeof this.data[i] === 'number' && this.data[i] < 0 || (isGrouped && this.data[group][subgroup] < 0)) ? Math.round(barY + barH + length) : Math.round(barY - length), barX + (barW / 2) + halfwidth, (typeof this.data[i] === 'number' && this.data[i] < 0 || (isGrouped && this.data[group][subgroup] < 0)) ? Math.round(barY + barH + length) : Math.round(barY - length), color ); } } else if (typeof errorbars[i] === 'object' && !RGraph.isNullish(errorbars[i])) { var positiveLength = Math.abs(this.getYCoord(errorbars[i][0]) - this.getYCoord(0)); // Color if (typeof errorbars[i][1] === 'string') { color = errorbars[i][1]; } else if (typeof errorbars[i][2] === 'string') { color = errorbars[i][2]; } // Cap width halfwidth = typeof errorbars[i][4] === 'number' ? errorbars[i][4] / 2 : default_halfwidth; if (!properties.errorbarsCapped) { halfwidth = 0; } if (!RGraph.isNullish(errorbars[i][0])) { this.path( 'b m % % l % % l % % l % % s %', barX + (barW / 2), barY + (this.data[i] < 0 ? barH : 0) + ((isGrouped && this.data[group][subgroup] < 0) ? barH : 0), barX + (barW / 2), barY - positiveLength + (this.data[i] < 0 ? barH : 0)+ ((isGrouped && this.data[group][subgroup] < 0) ? barH : 0), barX + (barW / 2) - halfwidth, Math.round(barY - positiveLength) + (this.data[i] < 0 ? barH : 0) + ((isGrouped && this.data[group][subgroup] < 0) ? barH : 0), barX + (barW / 2) + halfwidth, Math.round(barY - positiveLength) + (this.data[i] < 0 ? barH : 0) + ((isGrouped && this.data[group][subgroup] < 0) ? barH : 0), color ); } if (typeof errorbars[i][1] === 'number') { var negativeLength = Math.abs(this.getYCoord(errorbars[i][1]) - this.getYCoord(0)); this.path( 'b m % % l % % l % % l % % s %', barX + (barW / 2), barY + (this.data[i] < 0 ? barH : 0)+ ((isGrouped && this.data[group][subgroup] < 0) ? barH : 0), barX + (barW / 2), barY + negativeLength + (this.data[i] < 0 ? barH : 0)+ ((isGrouped && this.data[group][subgroup] < 0) ? barH : 0), barX + (barW / 2) - halfwidth, Math.round(coords[i][1] + negativeLength) + (this.data[i] < 0 ? barH : 0)+ ((isGrouped && this.data[group][subgroup] < 0) ? barH : 0), barX + (barW / 2) + halfwidth, Math.round(barY + negativeLength) + (this.data[i] < 0 ? barH : 0)+ ((isGrouped && this.data[group][subgroup] < 0) ? barH : 0), color ); } } // Reset the perbar linewidth to the default if the fourth option // in the array was specified specified if (errorbars[i] && typeof errorbars[i][3] === 'number') { this.context.lineWidth = properties.errorbarsLinewidth; } } }; // // A per-object to test whether a particular bar is adjustable or not // // @param shape The shape object // this.isAdjustable = function (shape) { if (RGraph.isNullish(properties.adjustableOnly) || !RGraph.isArray(properties.adjustableOnly)) { return true; } if (RGraph.isArray(properties.adjustableOnly) && properties.adjustableOnly[shape.sequentialIndex]) { return true; } return false; }; // // A worker function that handles Bar chart specific tooltip substitutions // this.tooltipSubstitutions = function (opt) { var indexes = RGraph.sequentialIndexToGrouped(opt.index, this.data); var values = this.data[indexes[0]]; if (typeof values === 'number') { values = [values]; } return { index: indexes[1], dataset: indexes[0], sequentialIndex: opt.index, value: this.data_arr[opt.index], values: values }; }; // // 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) { var label; if (this.stackedOrGrouped) { label = (!RGraph.isNullish(properties.tooltipsFormattedKeyLabels) && typeof properties.tooltipsFormattedKeyLabels === 'object' && properties.tooltipsFormattedKeyLabels[index]) ? properties.tooltipsFormattedKeyLabels[index] : ''; } else { label = ( !RGraph.isNullish(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, canvasXY = RGraph.getCanvasXY(obj.canvas) coords = this.coords[args.index]; // Position the tooltip in the X direction args.tooltip.style.left = ( canvasXY[0] // The X coordinate of the canvas + coords[0] // The X coordinate of the bar on the chart - (tooltip.offsetWidth / 2) // Subtract half of the tooltip width + (coords[2] / 2) // Add half of the bar width + obj.properties.tooltipsOffsetx // Add any user defined offset ) + 'px'; args.tooltip.style.top = ( canvasXY[1] // The Y coordinate of the canvas + coords[1] // The Y coordinate of the bar on the chart - tooltip.offsetHeight // The height of the tooltip - 10 // An arbitrary amount + obj.properties.tooltipsOffsety // Add any user defined offset ) + 'px'; // If the chart is a 3D version the tooltip Y position needs this // adjustment if (properties.variant === '3d') { var left = coords[0]; var top = coords[1]; var angle = properties.variantThreedAngle; var adjustment = Math.tan(angle) * left; args.tooltip.style.top = parseInt(args.tooltip.style.top) + adjustment - 5 + 'px'; } // If the bar is a negative one, add half the height to the Y coord if (this.data_arr[index] < 0) { args.tooltip.style.top = parseFloat(args.tooltip.style.top) + (coords[3] / 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 = 5 + 'px'; //} }; // // This adds a roundedRect(x, y, width, height, radius) function to the drawing context. // The radius argument dictates by how much the corners are rounded. // // @param number x The X coordinate // @param number y The Y coordinate // @param number width The width of the rectangle // @param number height The height of the rectangle // @param number radius The radius of the corners. Bigger values mean more rounded corners // this.roundedCornersRect = function (x, y, width, height) { var radiusLeft = null; var radiusRight = null; // LHS radius if (RGraph.isNumber(properties.cornersRoundLeftRadius)) { radiusLeft = properties.cornersRoundLeftRadius; } else { radiusLeft = Math.min(width / 2, height / 2, properties.cornersRoundRadius); } // RHS radius if (RGraph.isNumber(properties.cornersRoundRightRadius)) { radiusRight = properties.cornersRoundRightRadius; } else { radiusRight = Math.min(width / 2, height / 2, properties.cornersRoundRadius); } if ( (radiusLeft + radiusRight) > width) { // Calculate the left and right radiuses and assign // to temporary variables var a = width / (radiusLeft + radiusRight) * radiusLeft; var b = width / (radiusLeft + radiusRight) * radiusRight; // Reassign the values to the correct variables radiusLeft = a; radiusRight = b; } // Save the existing state of the canvas so that it can be restored later this.context.save(); // Translate to the given X/Y coordinates this.context.translate(x, y); // Move to the center of the top horizontal line this.context.moveTo(width / 2,0); // Draw the rounded corners. The connecting lines in between them are drawn automatically this.context.arcTo(width,0,width,height,Math.min(height / 2, properties.cornersRoundRight ? radiusRight : 0)); this.context.arcTo(width, height, 0, height, 0); this.context.arcTo(0, height, 0, 0, 0); this.context.arcTo(0, 0, radiusLeft, 0, Math.min(height / 2, this.properties.cornersRoundLeft ? radiusLeft : 0)); // Draw a line back to the start coordinates this.context.lineTo(width / 2,0); // Restore the state of the canvas to as it was before the save() this.context.restore(); }; // // This adds a roundedRectNegative(x, y, width, height, radius) function to the drawing context. // The radius argument dictates by how much the corners are rounded. // This function handles negative bars whereas the above // function handles positive ones. // // @param number x The X coordinate // @param number y The Y coordinate // @param number width The width of the rectangle // @param number height The height of the rectangle // @param number radius The radius of the corners. Bigger values mean more rounded corners // this.roundedCornersRectNegative = function (x, y, width, height) { if (height < 0) { height = Math.abs(height); y -= height; } var radiusLeft = null; var radiusRight = null; // LHS radius if (RGraph.isNumber(properties.cornersRoundLeftRadius)) { radiusLeft = properties.cornersRoundLeftRadius; } else { radiusLeft = Math.min(width / 2, height / 2, properties.cornersRoundRadius);; } // RHS radius if (RGraph.isNumber(properties.cornersRoundRightRadius)) { radiusRight = properties.cornersRoundRightRadius; } else { radiusRight = Math.min(width / 2, height / 2, properties.cornersRoundRadius);; } if ( (radiusLeft + radiusRight) > width) { // Calculate the left and right radiuses and assign // to temporary variables var a = width / (radiusLeft + radiusRight) * radiusLeft; var b = width / (radiusLeft + radiusRight) * radiusRight; // Reassign the values to the correct variables radiusLeft = a; radiusRight = b; } // Save the existing state of the canvas so that it can be restored later this.context.save(); // Translate to the given X/Y coordinates this.context.translate(x, y); // Move to the center of the top horizontal line this.context.moveTo(width / 2,0); // Draw the rounded corners. The connecting lines in // between them are drawn automatically this.context.arcTo(width,0,width,height, 0); this.context.arcTo(width, height, 0, height, Math.min(height / 2, properties.cornersRoundRight ? radiusRight : 0)); this.context.arcTo(0, height, 0, 0, Math.min(height / 2, properties.cornersRoundLeft ? radiusLeft : 0)); this.context.arcTo(0, 0, width, 0, 0); // Draw a line back to the start coordinates this.context.lineTo(width / 2,0); // Restore the state of the canvas to as it was before the save() this.context.restore(); }; // // This function is NOT currently used - it installs an // appropriate clipping region for the lower half of 3D // Bar charts when the X axis is in the middle. It's // used like this: // // this.context.save(); // this.context.install3DAxisClip(); // ... // this.context.restore(); // this.install3DAxisNegativeClip = function () { this.path( 'b m % % l % % l % % l % % l % % l % % c cl', this.marginLeft, this.getYCoord(0), this.marginLeft, this.canvas.height - this.marginBottom, this.canvas.width - this.marginRight, this.canvas.height - this.marginBottom, this.canvas.width - this.marginRight + this.properties.variantThreedOffsetx, this.canvas.height - this.marginBottom - this.properties.variantThreedOffsety, this.canvas.width - this.marginRight + this.properties.variantThreedOffsetx, this.getYCoord(0) - this.properties.variantThreedOffsety, this.canvas.width - this.marginRight, this.getYCoord(0) ); }; // // This returns the relevant value for the formatted key // macro %{value}. THIS VALUE SHOULD NOT BE FORMATTED. // // @param number index The index in the dataset to get // the value for // this.getKeyValue = function (index) { if (RGraph.isArray(this.properties.keyFormattedValueSpecific) && RGraph.isNumber(this.properties.keyFormattedValueSpecific[index])) { return this.properties.keyFormattedValueSpecific[index]; } else { var total = 0; for (let i=0; i<this.data.length; ++i) { if (RGraph.isArray(this.data[i])) { total += this.data[i][index]; } } return total; } }; // // Returns how many data-points there should be when a string // based key property has been specified. For example, this: // // key: '%{property:_labels[%{index}]} %{value_formatted}' // // ...depending on how many bits of data ther is might get // turned into this: // // key: [ // '%{property:_labels[%{index}]} %{value_formatted}', // '%{property:_labels[%{index}]} %{value_formatted}', // '%{property:_labels[%{index}]} %{value_formatted}', // '%{property:_labels[%{index}]} %{value_formatted}', // '%{property:_labels[%{index}]} %{value_formatted}', // ] // // ... ie in that case there would be 4 data-points so the // template is repeated 4 times. // this.getKeyNumDatapoints = function () { var num = 0; for (let i=0; i<this.data.length; ++i) { if (RGraph.isArray(this.data[i])) { num = Math.max( num, this.data[i].length ); } } return num; }; // // 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 in the // RGraph.clipTo.start() function. // // @param string clip The clip string as supplied by the // user in the chart configuration // this.clipToScaleWorker = function (clip) { // The Regular expression is actually done by the // calling RGraph.clipTo.start() function in the core // library if (RegExp.$1 === 'min') from = this.scale2.min; else from = Number(RegExp.$1); if (RegExp.$2 === 'max') to = this.scale2.max; else to = Number(RegExp.$2); var width = this.canvas.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; } this.path( 'sa b r % % % % cl', x, y, width, height ); }; // // This function handles TESTING clipping to scale values. // Because each chart handles scales differently, a worker // function is needed instead of it all being done centrally // in the RGraph.clipTo.start() function. // // @param string clip The clip string as supplied by the // user in the chart configuration // this.clipToScaleTestWorker = function (clip) { // The Regular expression is actually done by the // calling RGraph.clipTo.start() function in the core // library if (RegExp.$1 === 'min') from = this.scale2.min; else from = Number(RegExp.$1); if (RegExp.$2 === 'max') to = this.scale2.max; else to = Number(RegExp.$2); var width = this.canvas.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; } this.path( 'b r % % % %', x, y, width, height ); }; // // Register the object // RGraph.register(this); // // This is the 'end' of the constructor so if the first argument // contains configuration dsta - handle that. // RGraph.parseObjectStyleConfig(this, conf.options); }; // // This is the combined bar and Line class which makes creating bar/line combo charts a little bit easier // RGraph.CombinedChart = function () { // // Create a default empty array for the objects // this.objects = []; var objects = []; if (RGraph.isArray(arguments[0])) { objects = arguments[0]; } else { for (var i=0; i<arguments.length; i+=1) { objects[i] = arguments[i]; } } for (var i=0; i<objects.length; ++i) { this.objects[i] = objects[i]; // // Set the Line chart margins to match the Bar chart margins // this.objects[i].set({ marginLeft: this.objects[0].get('marginLeft'), // Needs to use the dot form to skirt an IE9 bug marginRight: this.objects[0].get('marginRight'), // Needs to use the dot form to skirt an IE9 bug marginTop: this.objects[0].get('marginTop'), // Needs to use the dot form to skirt an IE9 bug marginBottom: this.objects[0].get('marginBottom') // Needs to use the dot form to skirt an IE9 bug }); if (this.objects[i].type == 'line') { var obj = this.objects[i]; // // Set the line chart marginInner // obj.set('marginInner', ((this.objects[0].canvas.width - this.objects[0].get('marginRight') - this.objects[0].get('marginLeft')) / this.objects[0].data.length) / 2 ); // // No labels, axes or grid on the Line chart // obj.set('yaxis', false); obj.set('xaxis', false); obj.set('backgroundGrid', false); obj.set('yaxisScale', false); } // // Resizing // if (this.objects[i].get('resizable')) { var resizable_object = obj; } } // // Resizing // if (resizable_object) { // // This recalculates the Line chart marginInner when the chart is resized // function myOnresizebeforedraw (obj) { var marginLeft = obj.get('marginLeft'); var marginRight = obj.get('marginRight'); obj.set('marginInner', (obj.canvas.width - marginLeft - marginRight) / (obj.original_data[0].length * 2)); } RGraph.addCustomEventListener( resizable_object, 'onresizebeforedraw', myOnresizebeforedraw ); } return this; }; // // The Add method can be used to add methods to the CombinedChart object. // RGraph.CombinedChart.prototype.add = function (obj) { this.objects.push(obj); return this; }; // // The Draw method goes through all of the objects drawing them (sequentially) // RGraph.CombinedChart.prototype.draw = function () { if (RGraph.isArray(this.objects)) { for (var i=0; i<this.objects.length; ++i) { if (this.objects[i].properties['combinedEffect']) { // The options must be given as a string because of the // RGraph configuration system var options = this.objects[i].properties['combinedEffectOptions'] ? eval('(' + this.objects[i].properties['combinedEffectOptions'] + ')') : null, callback = this.objects[i].properties['combinedEffectCallback'], func = this.objects[i].properties['combinedEffect']; (this.objects[i][func])(options, callback); } else { this.objects[i].draw(); } } } return this; }; // // Provides an easy way to get a segmented Bar chart. // See the Bar chart documentation page for more details. // RGraph.SegmentedBar = function (conf) { this.config = conf; this.properties = {}; // Process the data this.config.data.forEach(function (v, k, arr) { arr[k] = RGraph.arrayPad([], v, 1); }); // Set the options on the segmentedBar object for (i in this.config.options) { if (typeof i === 'string') { this.properties[i] = this.config.options[i]; } } // Prevent any 'this' madness var seg = this; this.background = new RGraph.Bar({ id: conf.id[0], data: RGraph.arrayPad([], conf.data.length), options: { backgroundGridHlinesCount: seg.properties.segmentsCount, colors: ['rgba(0,0,0,0)'], xaxis: false, yaxis: false, yaxisLabels: false, xaxis: false, yaxis: false, yaxisScale: false } }); this.foreground = new RGraph.Bar({ id: conf.id[1], data: conf.data, options: { grouping: 'stacked', xaxis: false, yaxis: false, backgroundGrid: false, yaxisLabelsOffsetx: -3, yaxisLabelsOffsety: 20 } }).on('draw', function (obj) { var lw = seg.properties.segmentsLinewidth; var count = seg.properties.segmentsCount; for (var i=0; i<=count; ++i) { obj.path( 'cr 0 % 1000 %', obj.getYCoord(i / count * seg.foreground.scale2.max) - (lw / 2), lw); } }); // Set the Y label Offset this.foreground.set({ yaxisLabelsOffsety: (this.foreground.canvas.height - this.foreground.properties.marginTop - this.foreground.properties.marginBottom) / seg.properties.segmentsCount / 2 }); // Draw the Bar chart this.draw = function () { this.background.draw(); this.foreground.draw(); return this; } // Animate the Bar chart (only the foreground chart though) this.grow = function () { this.background.draw(); this.foreground.grow(arguments[0], arguments[1]); return this; } // Animate the Bar chart (only the foreground chart though) this.wave = function () { this.background.draw(); this.foreground.wave(arguments[0], arguments[1]); return this; } // This allows you to add responsive configuration to the chart by // passing the configuration throough to the underlying objects. this.responsive = function (conf) { this.foreground.responsive(conf); this.background.responsive(conf); return this; } }