// 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 horizontal bar chart constructor. The horizontal bar is a minor variant // on the bar chart. If you have big labels, this may be useful as there is usually // more space available for them. // RGraph.HBar = function (conf) { // // Allow for object config style // var id = conf.id, canvas = document.getElementById(id), data = conf.data; this.id = id; this.canvas = canvas; this.context = this.canvas.getContext ? this.canvas.getContext("2d", {alpha: (typeof id === 'object' && id.alpha === false) ? false : true}) : null; this.canvas.__object__ = this; this.data = data; this.type = 'hbar'; 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.coords = []; this.coords2 = []; this.coordsText = []; this.coordsLines = []; this.coordsSpline = []; this.original_colors = []; this.firstDraw = true; // After the first draw this will be false this.stopAnimationRequested = false;// Used to control the animations this.yaxisLabelsSize = 0; // Used later when the margin is auto calculated this.yaxisTitleSize = 0; // Used later when the margin is auto calculated this.max = 0; this.stackedOrGrouped = false; // Default properties this.properties = { marginLeft: 75, marginLeftAuto: true, marginRight: 35, marginTop: 35, marginBottom: 35, marginInner: 2, marginInnerGrouped: 2, backgroundBarsCount: null, backgroundBarsColor1: 'rgba(0,0,0,0)', backgroundBarsColor2: 'rgba(0,0,0,0)', backgroundGrid: true, backgroundGridColor: '#ddd', backgroundGridLinewidth: 1, backgroundGridHsize: 25, backgroundGridVsize: 25, backgroundGridHlines: true, backgroundGridVlines: true, backgroundGridBorder: true, backgroundGridAutofit: true, backgroundGridAutofitAlign:true, backgroundGridHlinesCount: null, backgroundGridVlinesCount: 5, backgroundGridDashed: false, backgroundGridDotted: false, backgroundColor: null, backgroundBorder: false, backgroundBorderLinewidth: 1, backgroundBorderColor: '#aaa', backgroundBorderDashed: false, backgroundBorderDotted: false, backgroundBorderDashArray: null, linewidth: 1, title: '', titleBold: null, titleItalic: null, titleFont: null, titleSize: null, titleColor: null, titleX: null, titleY: null, titleHalign: null, titleValign: null, titleOffsetx: 0, titleOffsety: 0, titleSubtitle: '', titleSubtitleSize: null, titleSubtitleColor: '#aaa', titleSubtitleFont: null, titleSubtitleBold: null, titleSubtitleItalic: null, titleSubtitleOffsetx: 0, titleSubtitleOffsety: 0, textSize: 12, textColor: 'black', textFont: 'Arial, Verdana, sans-serif', textBold: false, textItalic: false, textAngle: 0, textAccessible: false, textAccessibleOverflow: 'visible', textAccessiblePointerevents: false, text: null, colors: ['red', 'blue', 'green', 'pink', 'yellow', 'cyan', 'navy', 'gray', 'black'], colorsSequential: false, colorsStroke: 'rgba(0,0,0,0)', xaxis: true, xaxisLinewidth: 1, xaxisColor: 'black', xaxisPosition: 'bottom', xaxisTickmarks: true, xaxisTickmarksLength: 3, xaxisTickmarksLastLeft: null, xaxisTickmarksLastRight: null, xaxisTickmarksCount: null, xaxisLabels: true, xaxisLabelsCount: 5, xaxisLabelsBold: null, xaxisLabelsItalic: null, xaxisLabelsFont: null, xaxisLabelsSize: null, xaxisLabelsColor: null, xaxisLabelsSpecific: null, xaxisLabelsAngle: 0, xaxisLabelsOffsetx: 0, xaxisLabelsOffsety: 0, xaxisLabelsHalign: null, xaxisLabelsValign: null, xaxisLabelsPosition: 'edge', xaxisLabelsSpecificAlign:'left', xaxisScale: true, xaxisScaleUnitsPre: '', xaxisScaleUnitsPost: '', xaxisScaleMin: 0, xaxisScaleMax: 0, xaxisScalePoint: '.', xaxisScaleThousand: ',', xaxisScaleDecimals: null, xaxisScaleZerostart: true, xaxisTitle: '', xaxisTitleBold: null, xaxisTitleItalic: null, xaxisTitleSize: null, xaxisTitleFont: null, xaxisTitleColor: null, xaxisTitleX: null, xaxisTitleY: null, xaxisTitleOffsetx: null, xaxisTitleOffsety: null, xaxisTitlePos: null, xaxisTitleHalign: null, xaxisTitleValign: null, yaxis: true, yaxisLinewidth: 1, yaxisColor: 'black', yaxisTickmarks: true, yaxisTickmarksCount: null, yaxisTickmarksLastTop: null, yaxisTickmarksLastBottom: null, yaxisTickmarksLength: 3, yaxisScale: false, yaxisLabels: null, yaxisLabelsCount: null, // Not used by the HBar yaxisLabelsOffsetx: 0, yaxisLabelsOffsety: 0, yaxisLabelsHalign: null, yaxisLabelsValign: null, yaxisLabelsFont: null, yaxisLabelsSize: null, yaxisLabelsColor: null, yaxisLabelsBold: null, yaxisLabelsItalic: null, yaxisLabelsPosition: 'section', yaxisLabelsFormattedDecimals: 0, yaxisLabelsFormattedPoint: '.', yaxisLabelsFormattedThousand: ',', yaxisLabelsFormattedUnitsPre: '', yaxisLabelsFormattedUnitsPost: '', yaxisPosition: 'left', yaxisTitle: 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, labelsAbove: false, labelsAboveDecimals: 0, labelsAboveSpecific: null, labelsAboveUnitsPre: '', labelsAboveUnitsPost: '', labelsAboveColor: null, labelsAboveFont: null, labelsAboveSize: null, labelsAboveBold: null, labelsAboveItalic: null, labelsAboveOffsetx: 0, labelsAboveOffsety: 0, labelsAboveBackground: 'transparent', labelsInbar: false, labelsInbarHalign: 'center', labelsInbarValign: 'center', labelsInbarFont: null, labelsInbarSize: null, labelsInbarBold: null, labelsInbarItalic: null, labelsInbarColor: null, labelsInbarBackground: null, labelsInbarBackgroundPadding: 0, labelsInbarUnitsPre: null, labelsInbarUnitsPost: null, labelsInbarPoint: null, labelsInbarThousand: null, labelsInbarFormatter: null, labelsInbarDecimals: null, labelsInbarOffsetx: 0, labelsInbarOffsety: 0, labelsInbarSpecific: null, labelsInbarFormatter: null, contextmenu: null, key: null, keyBackground: 'white', keyPosition: 'graph', keyHalign: 'right', keyShadow: false, keyShadowColor: '#666', keyShadowBlur: 3, keyShadowOffsetx: 2, keyShadowOffsety: 2, keyPositionMarginBoxed: false, keyPositionMarginHSpace: 0, keyPositionX: null, keyPositionY: null, keyColorShape: 'square', keyRounded: true, keyLinewidth: 1, keyColors: null, keyInteractive: false, keyInteractiveHighlightChartLinewidth: 2, keyInteractiveHighlightChartStroke: 'black', keyInteractiveHighlightChartFill: 'rgba(255,255,255,0.7)', keyInteractiveHighlightLabel: 'rgba(255,0,0,0.2)', keyLabelsColor: null, keyLabelsFont: null, keyLabelsSize: null, keyLabelsBold: null, keyLabelsItalic: null, keyLabelsOffsetx: 0, keyLabelsOffsety: 0, keyFormattedDecimals: 0, keyFormattedPoint: '.', keyFormattedThousand: ',', keyFormattedUnitsPre: '', keyFormattedUnitsPost: '', keyFormattedValueSpecific: null, keyFormattedItemsCount: null, unitsIngraph: false, shadow: false, shadowColor: '#666', shadowBlur: 3, shadowOffsetx: 3, shadowOffsety: 3, grouping: 'grouped', tooltips: null, tooltipsEvent: 'onclick', tooltipsEffect: 'slide', tooltipsCssClass: 'RGraph_tooltip', tooltipsCss: null, tooltipsHighlight: true, tooltipsPersistent: false, 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, tooltipsHotspotYonly: false, tooltipsHotspotIgnore: null, tooltipsHotspotShape: 'rect', highlightFill: 'rgba(255,255,255,0.7)', highlightStroke: 'rgba(0,0,0,0)', highlightStyle: null, annotatable: false, annotatableColor: 'black', annotatableLinewidth: 1, redraw: true, variant: 'hbar', variantThreedAngle: 0.1, variantThreedOffsetx: 10, variantThreedOffsety: 5, variantThreedXaxis: true, variantThreedYaxis: true, variantThreedXaxisColor: '#ddd', variantThreedYaxisColor: '#ddd', adjustable: false, adjustableOnly: null, crosshairs: false, crosshairsColor: '#333', crosshairsLinewidth: 1, crosshairsHline: true, crosshairsVline: true, crosshairsSnapToScale: false, corners: 'square', cornersRoundRadius: 10, cornersRoundTop: true, cornersRoundBottom: true, cornersRoundTopRadius: null, cornersRoundBottomRadius: null, line: false, lineColor: 'black', lineLinejoin: 'round', lineLinecap: 'round', lineLinewidth: 1, lineShadow: true, lineShadowColor: '#666', lineShadowBlur: 2, lineShadowOffsetx: 2, lineShadowOffsety: 2, lineSpline: false, lineTickmarksStyle: null, lineTickmarksSize: 5, lineTickmarksDrawNull: false, lineTickmarksDrawNonNull: false, lineFilled: false, lineFilledColor: null, animationTraceClip: 1, 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('[HBAR] No canvas support'); return; } // // Allow the data to be given as a string // this.data = RGraph.stringsToNumbers(this.data); // This loop is used to check for stacked or grouped charts and now // also to convert strings to numbers. And now also undefined values // (29/07/2016 for (i=0,len=this.data.length; i<len; ++i) { if (typeof this.data[i] === 'object' && !RGraph.isNullish(this.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,len=linear_data.length; i<len; ++i) { this['$' + i] = {}; } // // Create the linear data array // 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; } // Set the colorsParsed flag to false if the colors // property is being set if ( name === 'colors' || name === 'backgroundGridColor' || name === 'backgroundColor' || name === 'backgroundBarsColor1' || name === 'backgroundBarsColor2' || name === 'textColor' || name === 'yaxisLabelsColor' || name === 'colorsStroke' || name === 'axesColor' || name === 'highlightFill' || name === 'highlightStroke' || name === 'annotatableColor' ) { 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; } // Fix labelsInBar* name name = name.replace('labelsInBar','labelsInbar'); // Property name change if (name === 'labelsInbarBgcolor') { name = 'labelsInbarBackground'; } 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'); // Reset this so that it doesn't grow uncontrollably this.yaxisTitleSize = 0; // Calculate the size of the labels regardless of anything else if ( properties.yaxisLabels) { var labels = properties.yaxisLabels, marginName = properties.yaxisPosition === 'right' ? 'marginRight' : 'marginLeft'; var textConf = RGraph.getTextConf({ object: this, prefix: 'yaxisLabels' }); for (var i=0,len=0; i<labels.length; i+=1) { var length = RGraph.measureText( labels[i], textConf.bold, textConf.font, textConf.size )[0] || 0; this.yaxisLabelsSize = Math.max(len, length); len = this.yaxisLabelsSize; } // Is a title Specified? If so accommodate that if ( properties.yaxisTitle) { var textConf = RGraph.getTextConf({ object: this, prefix: 'yaxisTitle' }); var titleSize = RGraph.measureText( properties.yaxisTitle, textConf.bold, textConf.font, textConf.size ) || []; this.yaxisTitleSize += titleSize[1]; properties[marginName] += this.yaxisTitleSize; } } // // Accomodate autosizing the left/right margin // if (properties.marginLeftAuto) { var name = properties.yaxisPosition === 'right' ? 'marginRight' : 'marginLeft'; this.set( name, this.yaxisLabelsSize + this.yaxisTitleSize + 10 ); } // Translate half a pixel for antialiasing purposes - but only if it hasn't been // done already // if (!this.canvas.__rgraph_aa_translated__) { this.context.translate(0.5,0.5); this.canvas.__rgraph_aa_translated__ = true; } // // Do the yaxis label substitution. This has to be called // before the left (or right) margin size is calculated. // this.yaxisLabelSubstitution(); // // Check that the bHBar isn't stacked with adjusting enabled // if (properties.adjustable && properties.grouping === 'stacked') { alert('[RGRAPH] The HBar does not support stacked charts with adjusting'); } // // Set the correct number of horizontal grid lines if // it hasn't been set already // if (RGraph.isNullish(properties.backgroundGridHlines.count)) { this.set('backgroundGridHlinesCount', this.data.length); } // // 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); } // Enlarge the margin if its 25 if (properties.marginBottom === 25) { this.set('marginBottom', 80); } } // // Parse the colors. This allows for simple gradient syntax // if (!this.colorsParsed) { this.parseColors(); // Don't want to do this again this.colorsParsed = true; } // *** marginLeftAuto calculation was here *** // // Make the margins easy to access // this.marginLeft = properties.marginLeft; this.marginRight = properties.marginRight; this.marginTop = properties.marginTop; this.marginBottom = properties.marginBottom; // // Stop the coords array from growing uncontrollably // this.coords = []; this.coords2 = []; this.coordsText = []; this.max = 0; // // Check for xaxisScaleMin in stacked charts // if ( properties.xaxisScaleMin > 0 && properties.grouping === 'stacked') { alert('[HBAR] Using xaxisScaleMin is not supported with stacked charts, resetting xaxisScaleMin to zero'); this.set('xaxisScaleMin', 0); } // // 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.graphwidth = this.canvas.width - this.marginLeft - this.marginRight; this.graphheight = this.canvas.height - this.marginTop - this.marginBottom; this.halfgrapharea = this.grapharea / 2; this.halfTextHeight = properties.textSize / 2; this.halfway = Math.round((this.graphwidth / 2) + this.marginLeft); ////////////////////// // SCALE GENERATION // ////////////////////// // // Work out the max value // if ( properties.xaxisScaleMax) { this.scale2 = RGraph.getScale({object: this, options: { 'scale.max': properties.xaxisScaleMax, 'scale.min': properties.xaxisScaleMin, 'scale.decimals': Number( properties.xaxisScaleDecimals), 'scale.point': properties.xaxisScalePoint, 'scale.thousand': properties.xaxisScaleThousand, 'scale.round': properties.xaxisScaleRound, 'scale.units.pre': properties.xaxisScaleUnitsPre, 'scale.units.post': properties.xaxisScaleUnitsPost, 'scale.labels.count': properties.xaxisLabelsCount, 'scale.strict': true }}); this.max = this.scale2.max; } else { var grouping = properties.grouping; for (i=0; i<this.data.length; ++i) { if (typeof this.data[i] == 'object') { var value = grouping == 'grouped' ? Number(RGraph.arrayMax(this.data[i], true)) : Number(RGraph.arraySum(this.data[i])); } else { var value = Number(Math.abs(this.data[i])); } this.max = Math.max(Math.abs(this.max), Math.abs(value)); } this.scale2 = RGraph.getScale({object: this, options: { 'scale.max': this.max, 'scale.min': properties.xaxisScaleMin, 'scale.decimals': Number( properties.xaxisScaleDecimals), 'scale.point': properties.xaxisScalePoint, 'scale.thousand': properties.xaxisScaleThousand, 'scale.round': properties.xaxisScaleRound, 'scale.units.pre': properties.xaxisScaleUnitsPre, 'scale.units.post': properties.xaxisScaleUnitsPost, 'scale.labels.count': properties.xaxisLabelsCount }}); this.max = this.scale2.max; this.min = this.scale2.min; } if ( properties.xaxisScaleDecimals == null && Number(this.max) == 1) { this.set('xaxisScaleDecimals', 1); } // // 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); } // Progressively Draw the chart RGraph.Background.draw(this); this.drawbars(); // // Fix negative width coordinates. ie If the width is // negative move the X coordinate and change the negative // width to positive // for (let i=0; i<this.coords.length; ++i) { if (this.coords[i][2] < 0) { this.coords[i][0] += this.coords[i][2]; this.coords[i][2] = Math.abs(this.coords[i][2]); } } this.drawAxes(); this.drawLabels(); // Draw the labelsInbar this.drawLabelsInbar(); // Draw the key if necessary if (properties.key && properties.key.length) { RGraph.drawKey(this, properties.key, properties.colors); } // // Draw a line on the chart if necessary // if (this.properties.line) { this.drawLine(); } // // Setup the context menu if required // if (properties.contextmenu) { RGraph.showContext(this); } // // Draw "in graph" labels // RGraph.drawInGraphLabels(this); // // Add custom text thats specified // RGraph.addCustomText(this); // // Add vertical lines // this.drawVerticalLines(); // // 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; }; // // This draws the axes // this.drawAxes = function () { // Draw the X axis RGraph.drawXAxis(this); // Draw the Y axis RGraph.drawYAxis(this); }; // // Do the label substitution. This is called from the top of the // draw function // this.yaxisLabelSubstitution = function () { if (properties.yaxisLabels && properties.yaxisLabels.length) { // // If the yaxisLabels option is a string then turn it // into an array. // if (typeof properties.yaxisLabels === 'string') { properties.yaxisLabels = RGraph.arrayPad({ array: [], length: this.data.length, value: properties.yaxisLabels }); } // // Label substitution // for (var i=0; i<properties.yaxisLabels.length; ++i) { properties.yaxisLabels[i] = RGraph.labelSubstitution({ object: this, text: properties.yaxisLabels[i], index: i, value: this.data[i], decimals: properties.yaxisLabelsFormattedDecimals || 0, unitsPre: properties.yaxisLabelsFormattedUnitsPre || '', unitsPost: properties.yaxisLabelsFormattedUnitsPost || '', thousand: properties.yaxisLabelsFormattedThousand || ',', point: properties.yaxisLabelsFormattedPoint || '.' }); } } }; // // This draws the labels for the graph // this.drawLabels = function () { // Labels are now drawn by the RGraph.drawYaxis() function }; // // This function draws the bars. It also draw 3D axes as the axes drawing bit // is don AFTER the bars are drawn // this.drawbars = function () { this.context.lineWidth = properties.linewidth; this.context.strokeStyle = properties.colorsStroke; this.context.fillStyle = properties.colors[0]; var prevX = 0, prevY = 0; /////////////////////////////// // SCALE GENERATION WAS HERE // /////////////////////////////// // // This is here to facilitate sequential colors // var colorIdx = 0; // // For grouped bars we need to calculate the number of bars // this.numbars = RGraph.arrayLinearize(this.data).length; // // if the chart is adjustable fix the scale so that it doesn't change. // // It's here (after the scale generation) so that the max value can be // set to the maximum scale value) // if (properties.adjustable && ! properties.xaxisScaleMax) { this.set('xaxisScaleMax', this.scale2.max); } // Draw the 3d axes if necessary if (properties.variant === '3d') { RGraph.draw3DAxes(this); } // // The bars are drawn HERE // var graphwidth = (this.canvas.width - this.marginLeft - this.marginRight); var halfwidth = graphwidth / 2; for (i=(len=this.data.length-1); i>=0; --i) { // Work out the width and height var width = Math.abs((this.data[i] / this.max) * graphwidth); var height = this.graphheight / this.data.length; var orig_height = height; var x = this.getXCoord(0); var y = this.marginTop + (i * height); var vmargin = properties.marginInner; // // Edge case: When X axis min is greater than 0 // eg min=1 and max=2.5 // if (properties.xaxisScaleMin > 0 && properties.xaxisScaleMax > properties.xaxisScaleMin) { x = this.getXCoord(properties.xaxisScaleMin); } // Account for the Y axis being on the right hand side if ( properties.yaxisPosition === 'right') { x = this.canvas.width - this.marginRight - Math.abs(width); } // Account for negative lengths - Some browsers (eg Chrome) don't like a negative value if (width < 0) { x -= width; width = Math.abs(width); } // // Turn on the shadow if need be // if (properties.shadow) { this.context.shadowColor = properties.shadowColor; this.context.shadowBlur = properties.shadowBlur; this.context.shadowOffsetX = properties.shadowOffsetx; this.context.shadowOffsetY = properties.shadowOffsety; } // // Draw the bar // this.context.beginPath(); // Standard (non-grouped and non-stacked) bars here if (typeof this.data[i] === 'number' || RGraph.isNullish(this.data[i])) { var barHeight = height - (2 * vmargin), barWidth = ((this.data[i] - properties.xaxisScaleMin) / (this.max - properties.xaxisScaleMin)) * this.graphwidth, barX = x; // Accommodate an offset Y axis if (this.scale2.min < 0 && this.scale2.max > 0 && properties.yaxisPosition === 'left') { barWidth = (this.data[i] / (this.max - properties.xaxisScaleMin)) * this.graphwidth; } // Account for Y axis pos if ( properties.yaxisPosition == 'center') { barWidth /= 2; barX += halfwidth; if (this.data[i] < 0) { barWidth = (Math.abs(this.data[i]) - properties.xaxisScaleMin) / (this.max - properties.xaxisScaleMin); barWidth = barWidth * (this.graphwidth / 2); barX = ((this.graphwidth / 2) + this.marginLeft) - barWidth; } else if (this.data[i] > 0) { barX = (this.graphwidth / 2) + this.marginLeft; } } else if ( properties.yaxisPosition == 'right') { barWidth = Math.abs(barWidth); barX = this.canvas.width - this.marginRight - barWidth; } // Set the fill color this.context.strokeStyle = properties.colorsStroke; this.context.fillStyle = properties.colors[0]; // Sequential colors ++colorIdx; if (properties.colorsSequential && typeof colorIdx === 'number') { if (properties.colors[this.numbars - colorIdx]) { this.context.fillStyle = properties.colors[this.numbars - colorIdx]; } else { this.context.fillStyle = properties.colors[properties.colors.length - 1]; } } if (properties.corners === 'round') { this.context.rectOld = this.context.rect; this.context.rect = this.roundedCornersRect; } this.context.beginPath(); this.context.lineJoin = 'miter'; this.context.lineCap = 'square'; // Draw the rounded corners rect positive or negative if (properties.corners === 'square' || (this.data[i] > 0 && this.properties.yaxisPosition !== 'right') ) { this.context.rect( barX, this.marginTop + (i * height) + properties.marginInner, barWidth, barHeight ); } else { this.roundedCornersRectNegative( barX, this.marginTop + (i * height) + properties.marginInner, barWidth, barHeight ); } this.context.stroke(); this.context.fill(); // Put the rect function back to what it was if (properties.corners === 'round' ) { this.context.rect = this.context.rectOld; this.context.rectOld = null; } // This skirts an annoying "extra fill bug" // by getting rid of the last path which //was drawm - which is usually the last //bar to be drawn (the bars are drawn //from bottom to top). Woo. this.path('b'); this.coords.push([ barX, y + vmargin, barWidth, height - (2 * vmargin), this.context.fillStyle, this.data[i], true ]); // Draw the 3D effect using the coords that have just been stored if (properties.variant === '3d' && typeof this.data[i] == 'number') { var prevStrokeStyle = this.context.strokeStyle, prevFillStyle = this.context.fillStyle; // // Turn off the shadow for the 3D bits // RGraph.noShadow(this); // DRAW THE 3D BITS HERE var barX = barX, barY = y + vmargin, barW = barWidth, barH = height - (2 * vmargin), offsetX = properties.variantThreedOffsetx, offsetY = properties.variantThreedOffsety, value = this.data[i]; this.path( 'b m % % l % % l % % l % % c s % f % f rgba(255,255,255,0.6)', barX, barY, barX + offsetX - ( properties.yaxisPosition == 'left' && value < 0 ? offsetX : 0), barY - offsetY, barX + barW + offsetX - ( properties.yaxisPosition == 'center' && value < 0 ? offsetX : 0), barY - offsetY, barX + barW, barY, this.context.strokeStyle,this.context.fillStyle ); if ( properties.yaxisPosition !== 'right' && !( properties.yaxisPosition === 'center' && value < 0) && value >= 0 && !RGraph.isNullish(value) ) { this.path( 'b fs % m % % l % % l % % l % % c s % f % f rgba(0,0,0,0.25)', prevFillStyle, barX + barW, barY, barX + barW + offsetX, barY - offsetY, barX + barW + offsetX, barY - offsetY + barH, barX + barW, barY + barH, this.context.strokeStyle,prevFillStyle ); } } // // Stacked bar chart // } else if (typeof this.data[i] == 'object' && properties.grouping === 'stacked') { if ( properties.yaxisPosition == 'center') { alert('[HBAR] You can\'t have a stacked chart with the Y axis in the center, change it to grouped'); } else if ( properties.yaxisPosition == 'right') { var x = this.canvas.width - this.marginRight } var barHeight = height - (2 * vmargin); if (typeof this.coords2[i] == 'undefined') { this.coords2[i] = []; } for (j=0; j<this.data[i].length; ++j) { // The previous 3D segments would have turned the shadow off - so turn it back on if (properties.shadow && properties.variant === '3d') { this.context.shadowColor = properties.shadowColor; this.context.shadowBlur = properties.shadowBlur; this.context.shadowOffsetX = properties.shadowOffsetx; this.context.shadowOffsetY = properties.shadowOffsety; } // // Ensure the number is positive //(even though having the X axis on the right implies a //negative value) // if (!RGraph.isNullish(this.data[i][j])) this.data[i][j] = Math.abs(this.data[i][j]); var last = (j === (this.data[i].length - 1) ); // Set the fill/stroke colors this.context.strokeStyle = properties.colorsStroke; // Sequential colors ++colorIdx; if (properties.colorsSequential && typeof colorIdx === 'number') { if (properties.colors[this.numbars - colorIdx]) { this.context.fillStyle = properties.colors[this.numbars - colorIdx]; } else { this.context.fillStyle = properties.colors[properties.colors.length - 1]; } } else if (properties.colors[j]) { this.context.fillStyle = properties.colors[j]; } var width = (((this.data[i][j]) / (this.max))) * this.graphwidth; var totalWidth = (RGraph.arraySum(this.data[i]) / this.max) * this.graphwidth; if ( properties.yaxisPosition === 'right') { x -= width; } if (properties.corners === 'round' && j === (this.data[i].length - 1) ) { this.context.rectOld = this.context.rect; this.context.rect = this.roundedCornersRect; } this.context.beginPath(); this.context.lineJoin = 'miter'; this.context.lineCap = 'square'; // Draw the rounded corners rect positive or negative if (properties.corners === 'square' || (j < (this.data[i].length - 1) && this.data[i][j] > 0) ) { this.context.rect(x, this.marginTop + properties.marginInner + (this.graphheight / this.data.length) * i, width, height - (2 * vmargin) ); } else { if (this.properties.yaxisPosition === 'left') { this.context.rect(x, this.marginTop + properties.marginInner + (this.graphheight / this.data.length) * i, width, height - (2 * vmargin) ); } else { this.roundedCornersRectNegative(x, this.marginTop + properties.marginInner + (this.graphheight / this.data.length) * i, width, height - (2 * vmargin) ); } } this.context.stroke(); this.context.fill(); // This avoids a "double fill"bug by resetting // the path this.context.beginPath(); // Put the rect function back to what it was if (properties.corners === 'round' && j === (this.data[i].length - 1) ) { this.context.rect = this.context.rectOld; this.context.rectOld = null; } //this.context.strokeRect(x, this.marginTop + properties.marginInner + (this.graphheight / this.data.length) * i, width, height - (2 * vmargin) ); //this.context.fillRect(x, this.marginTop + properties.marginInner + (this.graphheight / this.data.length) * i, width, height - (2 * vmargin) ); // // Store the coords for tooltips // // The last property of this array is a boolean which tells you whether the value is the last or not this.coords.push([ x, y + vmargin, width, height - (2 * vmargin), this.context.fillStyle, RGraph.arraySum(this.data[i]), j == (this.data[i].length - 1) ]); this.coords2[i].push([ x, y + vmargin, width, height - (2 * vmargin), this.context.fillStyle, RGraph.arraySum(this.data[i]), j == (this.data[i].length - 1) ]); // 3D effect if (properties.variant === '3d') { // // Turn off the shadow for the 3D bits // RGraph.noShadow(this); var prevStrokeStyle = this.context.strokeStyle, prevFillStyle = this.context.fillStyle; // DRAW THE 3D BITS HERE var barX = x, barY = y + vmargin, barW = width, barH = height - (2 * vmargin), offsetX = properties.variantThreedOffsetx, offsetY = properties.variantThreedOffsety, value = this.data[i][j]; if (!RGraph.isNullish(value)) { this.path( 'b m % % l % % l % % l % % c s % f % f rgba(255,255,255,0.6)', barX, barY, barX + offsetX, barY - offsetY, barX + barW + offsetX, barY - offsetY, barX + barW, barY, this.context.strokeStyle,this.context.fillStyle ); } if ( properties.yaxisPosition !== 'right' && !( properties.yaxisPosition === 'center' && value < 0) && !RGraph.isNullish(value) ) { this.path( 'fs % b m % % l % % l % % l % % c s % f % f rgba(0,0,0,0.25)', prevFillStyle, barX + barW, barY, barX + barW + offsetX, barY - offsetY, barX + barW + offsetX, barY - offsetY + barH, barX + barW, barY + barH, this.context.strokeStyle,prevFillStyle ); } this.context.beginPath(); this.context.strokeStyle = prevStrokeStyle; this.context.fillStyle = prevFillStyle; } if ( properties.yaxisPosition !== 'right') { x += width; } } // // A grouped bar chart // } else if (typeof this.data[i] == 'object' && properties.grouping == 'grouped') { var vmarginGrouped = properties.marginInnerGrouped; var individualBarHeight = ((height - (2 * vmargin) - ((this.data[i].length - 1) * vmarginGrouped)) / this.data[i].length) if (typeof this.coords2[i] == 'undefined') { this.coords2[i] = []; } for (j=(this.data[i].length - 1); j>=0; --j) { // // Turn on the shadow if need be // if (properties.shadow) { RGraph.setShadow( this, properties.shadowColor, properties.shadowOffsetx, properties.shadowOffsety, properties.shadowBlur ); } // Set the fill/stroke colors this.context.strokeStyle = properties.colorsStroke; // Sequential colors ++colorIdx; if (properties.colorsSequential && typeof colorIdx === 'number') { if (properties.colors[this.numbars - colorIdx]) { this.context.fillStyle = properties.colors[this.numbars - colorIdx]; } else { this.context.fillStyle = properties.colors[properties.colors.length - 1]; } } else if (properties.colors[j]) { this.context.fillStyle = properties.colors[j]; } var startY = this.marginTop + (height * i) + (individualBarHeight * j) + vmargin + (vmarginGrouped * j); if (properties.xaxisScaleMin > 0 && properties.xaxisScaleMax > properties.xaxisScaleMin) { var width = ((this.data[i][j] - properties.xaxisScaleMin) / (this.max - properties.xaxisScaleMin)) * (this.canvas.width - this.marginLeft - this.marginRight ); var startX = this.getXCoord((properties.xaxisScaleMin > 0 && properties.xaxisScaleMax > properties.xaxisScaleMin) ? properties.xaxisScaleMin : 0);//this.marginLeft; } else { var width = (this.data[i][j] / (this.max - properties.xaxisScaleMin)) * (this.canvas.width - this.marginLeft - this.marginRight); var startX = this.getXCoord(0); } // Account for the Y axis being in the middle if ( properties.yaxisPosition == 'center') { width /= 2; // Account for the Y axis being on the right } else if ( properties.yaxisPosition == 'right') { width = Math.abs(width); startX = this.canvas.width - this.marginRight - Math.abs(width); } if (width < 0) { startX += width; width *= -1; } if (properties.corners === 'round') { this.context.rectOld = this.context.rect; this.context.rect = this.roundedCornersRect; } this.context.beginPath(); this.context.lineJoin = 'miter'; this.context.lineCap = 'square'; // Draw the rounded corners rect positive or negative if (properties.corners === 'square' || (this.properties.yaxisPosition === 'left' || this.properties.yaxisPosition === 'center')) { if (properties.corners === 'square' || this.data[i][j] > 0) { this.context.rect(startX, startY, width, individualBarHeight); } else { this.roundedCornersRectNegative(startX, startY, width, individualBarHeight); } } else { if (this.data[i][j] > 0 && properties.corners === 'round') { this.roundedCornersRectNegative(startX, startY, width, individualBarHeight); } else { this.context.rect(startX + width, startY, width, individualBarHeight); } } this.context.stroke(); this.context.fill(); // Put the rect function back to what it was if (properties.corners === 'round') { this.context.rect = this.context.rectOld; this.context.rectOld = null; } this.coords.push([ startX, startY, width, individualBarHeight, this.context.fillStyle, this.data[i][j], true ]); this.coords2[i].push([ startX, startY, width, individualBarHeight, this.context.fillStyle, this.data[i][j], true ]); // 3D effect if (properties.variant === '3d') { // // Turn off the shadow for the 3D bits // RGraph.noShadow(this); var prevStrokeStyle = this.context.strokeStyle, prevFillStyle = this.context.fillStyle; // DRAW THE 3D BITS HERE var barX = startX, barY = startY, barW = width, barH = individualBarHeight, offsetX = properties.variantThreedOffsetx, offsetY = properties.variantThreedOffsety, value = this.data[i][j]; this.path( 'b m % % l % % l % % l % % c s % f % f rgba(255,255,255,0.6)', barX, barY, barX + offsetX, barY - offsetY, barX + barW + offsetX - (value < 0 ? offsetX : 0), barY - offsetY, barX + barW, barY, this.context.strokeStyle,this.context.fillStyle ); if ( properties.yaxisPosition !== 'right' && !( properties.yaxisPosition === 'center' && value < 0) && value >= 0 && !RGraph.isNullish(value) ) { this.path( 'fs % b m % % l % % l % % l % % c s % f % f rgba(0,0,0,0.25)', prevFillStyle, barX + barW, barY, barX + barW + offsetX, barY - offsetY, barX + barW + offsetX, barY - offsetY + barH, barX + barW, barY + barH, this.context.strokeStyle,prevFillStyle ); } this.context.beginPath(); this.context.strokeStyle = prevStrokeStyle; this.context.fillStyle = prevFillStyle; } } startY += vmargin; // This skirts an annoying "extra fill bug" // by getting rid of the last path which // was drawn - which is usually the last // bar to be drawn (the bars are drawn // from bottom to top). Woo. this.path('b'); } this.context.closePath(); } this.context.stroke(); this.context.fill(); // Sunday 30th April 2023: // Why is this necessary? It causes the title // (if it's big enough) to be cut off so it's // been commented out. // // Under certain circumstances we can cover the shadow // overspill with a white rectangle // //if ( properties.yaxisPosition === 'right') { // this.path( // 'cr % % % %', // this.canvas.width - this.marginRight + properties.variantThreedOffsetx,'0',this.marginRight,this.canvas.height // ); //} // Draw the 3d axes AGAIN if the Y axis is on the right if ( properties.yaxisPosition === 'right' && properties.variant === '3d' ) { RGraph.draw3DYAxis(this); } // // Now the bars are stroke()ed, turn off the shadow // RGraph.noShadow(this); // // Reverse the coords arrays as the bars are drawn from the borrom up now // this.coords = RGraph.arrayReverse(this.coords); if (properties.grouping === 'grouped') { for (var i=0; i<this.coords2.length; ++i) { this.coords2[i] = RGraph.arrayReverse(this.coords2[i]); } } this.redrawBars(); }; // // This function goes over the bars after they been drawn, so that upwards shadows are underneath the bars // this.redrawBars = function () { if (!properties.redraw) { return; } var coords = this.coords; var font = properties.textFont, size = properties.textSize, color = properties.textColor; RGraph.noShadow(this); this.context.strokeStyle = properties.colorsStroke; for (var i=0; i<coords.length; ++i) { if (properties.shadow) { this.path( 'b lw % r % % % % s % f %', properties.linewidth, coords[i][0],coords[i][1],coords[i][2],coords[i][3], properties.colorsStroke,coords[i][4] ); } // TODO Maybe put a function here to check the // various conditions as to whether a labelsAbove // label should be shown or not // Draw labels "above" the bar var halign = 'left'; if ( properties.labelsAbove && coords[i][6] //&& !RGraph.isNullish(this.data[i]) ) { var border = (coords[i][0] + coords[i][2] + 7 + this.context.measureText(properties.labelsAboveUnitsPre + this.coords[i][5] + properties.labelsAboveUnitsPost).width) > this.canvas.width ? true : false, text = RGraph.numberFormat({ object: this, number: (this.coords[i][5]).toFixed(properties.labelsAboveDecimals), unitspre: properties.labelsAboveUnitsPre, unitspost: properties.labelsAboveUnitsPost, point: properties.labelsAbovePoint, thousand: properties.labelsAboveThousand }); RGraph.noShadow(this); // Check for specific labels if (typeof properties.labelsAboveSpecific === 'object' && properties.labelsAboveSpecific && properties.labelsAboveSpecific[i]) { text = properties.labelsAboveSpecific[i]; } var x = coords[i][0] + coords[i][2] + 5; var y = coords[i][1] + (coords[i][3] / 2); if ( properties.yaxisPosition === 'right') { x = coords[i][0] - 5; halign = 'right'; } else if ( properties.yaxisPosition === 'center' && this.data_arr[i] < 0) { x = coords[i][0] - 5; halign = 'right'; } var textConf = RGraph.getTextConf({ object: this, prefix: 'labelsAbove' }); RGraph.text({ object: this, font: textConf.font, size: textConf.size, color: textConf.color, bold: textConf.bold, italic: textConf.italic, x: x + properties.labelsAboveOffsetx, y: y + properties.labelsAboveOffsety, text: text, valign: 'center', halign: halign, bounding: (properties.labelsAboveBackground !== 'transparent'), boundingFill: properties.labelsAboveBackground, boundingStroke: 'transparent', tag: 'labels.above' }); } } }; // // This function can be used to get the appropriate bar information (if any) // // @param e Event object // @return Appriate bar information (if any) // this.getShape = function (e) { var mouseXY = RGraph.getMouseXY(e); // // Loop through the bars determining if the mouse is over a bar // for (var i=0,len=this.coords.length; i<len; i++) { if (RGraph.tooltipsHotspotIgnore(this, i)) { continue; } var mouseX = mouseXY[0], // In relation to the canvas mouseY = mouseXY[1], // In relation to the canvas left = this.coords[i][0], top = this.coords[i][1], width = this.coords[i][2], height = this.coords[i][3], idx = i, indexes = RGraph.sequentialIndexToGrouped(i, this.data); // // If the corners are rounded then change the rect function // if (properties.corners === 'round') { var isLast = indexes[1] === 0; if (properties.grouping === 'stacked' && isLast) { this.context.rectOld = this.context.rect; this.context.rect = this.roundedCornersRect; var revert = true; } else if (properties.grouping === 'stacked') { // Do nothing for the main body bits of // stacked bars } else { this.context.rectOld = this.context.rect; this.context.rect = this.roundedCornersRect; var revert = true; } } // Recreate the path/rectangle so that it can be // tested // ** DO NOT STROKE OR FILL IT ** // if (properties.tooltipsHotspotYonly) { if (this.properties.tooltipsHotspotShape === 'point') { this.path( 'b a % % 7 0 6.29 ', left + width ); } else { this.path( 'b r % % % % ', this.marginLeft, top, this.canvas.width - this.marginRight - this.marginLeft, height ); } } else { if (this.properties.tooltipsHotspotShape === 'point') { if (this.data_arr[idx] > 0 ) { // Positive this.path( 'b a % % 7 0 6.29 false ', left + (this.properties.yaxisPosition === 'right' ? 0 : width), top + (height / 2) ); } else { // Negative this.path( 'b a % % 7 0 6.29 false', left, top + (height / 2) ); } } else { this.path( 'b r % % % %', left,top,width,height ); } } // // Put the rect function back to what it was // if (revert) { this.context.rect = this.context.rectOld; this.context.rectOld = null; revert = null; } if ( this.context.isPointInPath(mouseX, mouseY) && (this.properties.clip ? RGraph.clipTo.test(this, mouseX, mouseY) : true) ) { if (RGraph.parseTooltipText) { var tooltip = RGraph.parseTooltipText(properties.tooltips, i); } var indexes = RGraph.sequentialIndexToGrouped(idx, this.data); var group = indexes[0]; var index = indexes[1]; return { object: this, x: left, y: top, width: width, height: height, sequentialIndex: idx, dataset: group, index: index, label: properties.yaxisLabels && typeof properties.yaxisLabels[group] === 'string' ? properties.yaxisLabels[group] : null, tooltip: typeof tooltip === 'string' ? tooltip : null }; } } }; // // When you click on the chart, this method can return the X value at that point. It works for any point on the // chart (that is inside the margins) - not just points within the Bars. // // @param object e The event object // 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 < this.marginTop || mouseY > (this.canvas.height - this.marginBottom) || mouseX < this.marginLeft || mouseX > (this.canvas.width - this.marginRight) ) { return null; } if ( properties.yaxisPosition == 'center') { var value = ((mouseX - this.marginLeft) / (this.graphwidth / 2)) * (this.max - properties.xaxisScaleMin); value = value - this.max // Special case if xmin is defined if ( properties.xaxisScaleMin > 0) { value = ((mouseX - this.marginLeft - (this.graphwidth / 2)) / (this.graphwidth / 2)) * (this.max - properties.xaxisScaleMin); value += properties.xaxisScaleMin; if (mouseX < (this.marginLeft + (this.graphwidth / 2))) { value -= (2 * properties.xaxisScaleMin); } } // TODO This needs fixing } else if ( properties.yaxisPosition == 'right') { var value = ((mouseX - this.marginLeft) / this.graphwidth) * (this.scale2.max - properties.xaxisScaleMin); value = this.scale2.max - value; } else { var value = ((mouseX - this.marginLeft) / this.graphwidth) * (this.scale2.max - properties.xaxisScaleMin); value += properties.xaxisScaleMin; } return value; }; // // Each object type has its own Highlight() function which highlights the appropriate shape // // @param object shape The shape to highlight // this.highlight = function (shape) { // highlightStyle is a function - user defined highlighting 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] - 0.5, this.coords[i][1] - 0.5, this.coords[i][2] + 1, this.coords[i][3] + 1, properties.highlightStroke, properties.highlightFill ); } // Redraw the Y axis so the highlight doesn't // appear over the Y axis. But not the Y axis // labels or the title. This is new in // September 2024. RGraph.drawYAxis(this, { labels: false, title: false }); } // Circular highlighting (for vertical lines) } else if (this.properties.tooltipsHotspotShape === 'point') { var index = shape.sequentialIndex; this.path( 'b a % % % 0 6.29 false s % f %', this.coords[index][0] + this.coords[index][2], this.coords[index][1] + (this.coords[index][3] / 2), this.properties.lineTickmarksSize, this.properties.highlightStroke, this.properties.highlightFill ); // Standard higlight } else { RGraph.Highlight.rect(this, shape); // Redraw the Y axis so the highlight doesn't // appear over the Y axis. But not the Y axis // labels or the title. This is new in // September 2024. RGraph.drawYAxis(this, { labels: false, title: false }); } }; // // The getObjectByXY() worker method. Don't call this call: // // RGraph.ObjectRegistry.getObjectByXY(e) // // @param object e The event object // this.getObjectByXY = function (e) { var mouseXY = RGraph.getMouseXY(e); // Adjust the mouse Y coordinate for when the bar chart is // a 3D variant if (properties.variant === '3d' && !properties.textAccessible) { var adjustment = properties.variantThreedAngle * mouseXY[0]; mouseXY[1] -= adjustment; } if ( mouseXY[0] >= this.marginLeft && mouseXY[0] <= (this.canvas.width - this.marginRight) && mouseXY[1] >= this.marginTop && mouseXY[1] <= (this.canvas.height - this.marginBottom) ) { return this; } }; // // Returns the appropriate X coord for the given value // // @param number value The value to get the coord for // this.getXCoord = function (value, outofbounds = false) { if ( properties.yaxisPosition == 'center') { // Range checking if (outofbounds === false && value > this.max || value < (-1 * this.max)) { return null; } var width = (this.canvas.width - properties.marginLeft - properties.marginRight) / 2; var coord = (((value - properties.xaxisScaleMin) / (this.max - properties.xaxisScaleMin)) * width) + width; coord = properties.marginLeft + coord; } else { // Range checking if (outofbounds === false && value > this.max || value < 0) { return null; } var width = this.canvas.width - properties.marginLeft - properties.marginRight; var coord = ((value - properties.xaxisScaleMin) / (this.max - properties.xaxisScaleMin)) * width; coord = properties.marginLeft + coord; } return coord; }; // // // 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.backgroundGridColor = RGraph.arrayClone(properties.backgroundGridColor); this.original_colors.backgroundColor = RGraph.arrayClone(properties.backgroundColor); this.original_colors.backgroundBarsColor1 = RGraph.arrayClone(properties.backgroundBarsColor1); this.original_colors.backgroundBarsColor2 = RGraph.arrayClone(properties.backgroundBarsColor2); this.original_colors.textColor = RGraph.arrayClone(properties.textColor); this.original_colors.yaxisLabelsColor = RGraph.arrayClone(properties.yaxisLabelsColor); this.original_colors.colorsStroke = RGraph.arrayClone(properties.colorsStroke); this.original_colors.axesColor = RGraph.arrayClone(properties.axesColor); this.original_colors.highlightFill = RGraph.arrayClone(properties.highlightFill); this.original_colors.highlightStroke = RGraph.arrayClone(properties.highlightStroke); this.original_colors.annotatableColor = RGraph.arrayClone(properties.annotatableColor); this.original_colors.lineFilledColor = RGraph.arrayClone(properties.lineFilledColor); } var colors = properties.colors; for (var i=0; i<colors.length; ++i) { colors[i] = this.parseSingleColorForGradient(colors[i]); } properties.backgroundGridColor = this.parseSingleColorForGradient(properties.backgroundGridColor); properties.backgroundColor = this.parseSingleColorForGradient(properties.backgroundColor); properties.backgroundBarsColor1 = this.parseSingleColorForGradient(properties.backgroundBarsColor1); properties.backgroundBarsColor2 = this.parseSingleColorForGradient(properties.backgroundBarsColor2); properties.textColor = this.parseSingleColorForGradient(properties.textColor); properties.yaxisLabelsColor = this.parseSingleColorForGradient(properties.yaxisLabelsColor); properties.colorsStroke = this.parseSingleColorForGradient(properties.colorsStroke); properties.axesColor = this.parseSingleColorForGradient(properties.axesColor); properties.highlightFill = this.parseSingleColorForGradient(properties.highlightFill); properties.highlightStroke = this.parseSingleColorForGradient(properties.highlightStroke); properties.annotatableColor = this.parseSingleColorForGradient(properties.annotatableColor); properties.lineFilledColor = this.parseSingleColorForGradient(properties.lineFilledColor); }; // // 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.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(':'); if ( properties.yaxisPosition === 'right') { parts = RGraph.arrayReverse(parts); } // Create the gradient var grad = this.context.createLinearGradient(properties.marginLeft,0,this.canvas.width - properties.marginRight,0); var diff = 1 / (parts.length - 1); grad.addColorStop(0, RGraph.trim(parts[0])); for (var j=1; j<parts.length; ++j) { grad.addColorStop(j * diff, RGraph.trim(parts[j])); } } return grad ? grad : color; }; // // 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) { var coords = obj.coords2[idx][index], pre_linewidth = obj.context.lineWidth; obj.path( 'b lw % r % % % % f % s % lw %', properties.keyInteractiveHighlightChartLinewidth, coords[0]- 0.5, coords[1] - 0.5, coords[2] + 1, coords[3] + 1, properties.keyInteractiveHighlightChartFill, properties.keyInteractiveHighlightChartStroke, pre_linewidth ); }); }; // // 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; }; // // This function runs once only // (put at the end of the file (before any effects)) // this.firstDrawFunc = function () { }; // // 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.getShapeByY = function (e) { var mouseXY = 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 = mouseXY[0], mouseY = mouseXY[1], left = obj.coords[i][0], top = obj.coords[i][1], width = obj.coords[i][2], height = obj.coords[i][3]; if (mouseY >= top && mouseY <= (top + height)) { var indexes = RGraph.sequentialIndexToGrouped(i, this.data); var group = indexes[0]; var index = indexes[1]; if (properties.tooltips) { var tooltip = RGraph.parseTooltipText ? RGraph.parseTooltipText(properties.tooltips, i) : properties.tooltips[i]; } return { object: obj, x: left, y: top, width: width, height: height, dataset: group, index: index, sequentialIndex: i, tooltip: tooltip || null }; } } return null; }; // // 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)), 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.dataset] == 'number') { this.data[shape.dataset] = Number(value); } RGraph.redrawCanvas(e.target); RGraph.fireCustomEvent(this, 'onadjust'); } } }; // // Draws the labelsInbar // this.drawLabelsInbar = function () { // Go through the above labels if (properties.labelsInbar) { // Default alignment var valign = properties.labelsInbarValign, halign = properties.labelsInbarHalign; // Get the text configuration for the labels var textConf = RGraph.getTextConf({ object: this, prefix: 'labelsInbar' }); // Linearize the data using a custom function because the coords are // stored in the wrong order var linearize = function (data) { var ret = []; for (var i=0; i<data.length; ++i) { if (typeof data[i] === 'number') { ret.push(data[i]); } else if (typeof data[i] === 'object' && !RGraph.isNullish(data[i])) { for (var j=data[i].length-1; j>=0; j--) { ret.push(data[i][j]); } } } return ret; }; // Linearize the data using the custom linearize function if stacked, // if not stacked use the API function if (properties.grouping === 'stacked') { data = linearize(this.data); } else { data = RGraph.arrayLinearize(this.data); } for (var i=0; i<data.length; ++i) { if (RGraph.isNumber(data[i]) && !isNaN(data[i]) && this.coords[i][2] > 0) { var value = data[i].toFixed(properties.labelsInbarDecimals); var indexes = RGraph.sequentialIndexToGrouped(i, this.data); var str = RGraph.numberFormat({ object: this, number: Number(value).toFixed(properties.labelsInbarDecimals), unitspre: properties.labelsInbarUnitsPre, unitspost: properties.labelsInbarUnitsPost, point: properties.labelsInbarPoint, thousand: properties.labelsInbarThousand, formatter: properties.labelsInbarFormatter }); var dimensions = RGraph.measureText({ text: str, bold: textConf.bold, font: textConf.font, size: textConf.size }); var x = this.coords[i][0] + (this.coords[i][2] / 2) + properties.labelsInbarOffsetx, y = this.coords[i][1] + (this.coords[i][3] / 2) + properties.labelsInbarOffsety, width = dimensions[0], height = dimensions[1]; // // Horizontal alignment // if (properties.labelsInbarHalign === 'left') { halign = 'left'; x = this.coords[i][0] + 5 + properties.labelsInbarOffsetx; } else if (properties.labelsInbarHalign === 'right') { halign = 'right'; x = this.coords[i][0] + this.coords[i][2] - 5 + properties.labelsInbarOffsetx; } // // Vertical alignment // if (properties.labelsInbarValign === 'bottom') { valign = 'bottom'; y = this.coords[i][1] - 5 + this.coords[i][3] + properties.labelsInbarOffsety; } else if (properties.labelsInbarValign === 'top') { valign = 'top'; y = this.coords[i][1] + 5 + properties.labelsInbarOffsety; } // Specific label given if (RGraph.isArray(properties.labelsInbarSpecific) && (RGraph.isString(properties.labelsInbarSpecific[i]) || RGraph.isNumber(properties.labelsInbarSpecific[i]))) { str = properties.labelsInbarSpecific[i]; } RGraph.text({ object: this, font: textConf.font, size: textConf.size, color: textConf.color, bold: textConf.bold, italic: textConf.italic, x: x, y: y, text: str, valign: valign, halign: halign, tag: 'labels.above', bounding: RGraph.isString(properties.labelsInbarBackground), boundingFill: properties.labelsInbarBackground, boundingStroke: 'transparent' }); } } } }; // // The HBar 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(); // Callback var opt = arguments[0] || {}, frames = opt.frames || 120, frame = 0, callback = arguments[1] || function () {}, obj = this, labelsAbove = this.get('labelsAbove') this.original_data = RGraph.arrayClone(this.data); // Stop the scale from changing by setting xaxisScalemax (if it's not already set) if ( properties.xaxisScaleMax == 0) { var xmax = 0; for (var i=0; i<obj.data.length; ++i) { if (RGraph.isArray(obj.data[i]) && properties.grouping == 'stacked') { xmax = Math.max(xmax, RGraph.arraySum(obj.data[i])); } else if (RGraph.isArray(obj.data[i]) && properties.grouping == 'grouped') { xmax = Math.max(xmax, RGraph.arrayMax(obj.data[i])); } else { xmax = Math.max(xmax, Math.abs(RGraph.arrayMax(obj.data[i]))); } } var scale2 = RGraph.getScale({object: obj, options: {'scale.max':xmax, 'scale.round': obj.properties.xaxisScaleRound}}); obj.set('xaxisScaleMax', scale2.max); } // Go through the data and change string arguments of // the format +/-[0-9] to absolute numbers if (RGraph.isArray(opt.data)) { var xmax = 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); } } xmax = Math.max(xmax, 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] + parseFloat(RegExp.$2); } else { opt.data[i] = this.original_data[i] - parseFloat(RegExp.$2); } xmax = Math.max(xmax, opt.data[i]); } else { xmax = Math.max(xmax, opt.data[i]); } } var scale = RGraph.getScale({object: this, options: {'scale.max':xmax}}); if (RGraph.isNullish(this.get('xaxisScaleMax'))) { this.set('xaxisScaleMax', scale.max); } } // // turn off the labelsAbove option whilst animating // this.set('labelsAbove', false); // Stop the scale from changing by setting xaxisScaleMax (if it's not already set) if (RGraph.isNullish( properties.xaxisScaleMax)) { var xmax = 0; for (var i=0; i<obj.data.length; ++i) { if (RGraph.isArray(this.data[i]) && properties.grouping === 'stacked') { xmax = Math.max(xmax, 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])); } xmax = Math.max(xmax, Math.abs(RGraph.arrayMax(group))); } else { xmax = Math.max(xmax, Math.abs(this.data[i])); } } var scale = RGraph.getScale({object: this, options: {'scale.max':xmax}}); this.set('xaxisScaleMax', scale.max); } // You can give an xmax to the grow function if (typeof opt.xmax === 'number') { obj.set('xaxisScaleMax', opt.xmax); } // // Need a copy of the original border radiuses // if (typeof obj.properties.cornersRoundTopRadius === 'number') {orig_cornersRoundTopRadius = obj.properties.cornersRoundTopRadius;} if (typeof obj.properties.cornersRoundBottomRadius === 'number') {orig_cornersRoundBottomRadius = obj.properties.cornersRoundBottomRadius;} var iterator = function () { if (obj.stopAnimationRequested) { // Reset the flag obj.stopAnimationRequested = false; return; } var easingMultiplier = RGraph.Effects.getEasingMultiplier(frames, frame); if (typeof obj.properties.cornersRoundTopRadius === 'number') { obj.properties.cornersRoundTopRadius = easingMultiplier * orig_cornersRoundTopRadius; } if (typeof obj.properties.cornersRoundBottomRadius === 'number') { obj.properties.cornersRoundBottomRadius = easingMultiplier * orig_cornersRoundBottomRadius; } // 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) { if (properties.xaxisScaleMin > 0 && properties.xaxisScaleMax > properties.xaxisScaleMin) { obj.data[j][k] = (easingMultiplier * (obj.original_data[j][k] - properties.xaxisScaleMin)) + properties.xaxisScaleMin; } else { 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) { if (properties.xaxisScaleMin > 0 && properties.xaxisScaleMax > properties.xaxisScaleMin) { obj.data[j] = (easingMultiplier * (obj.original_data[j] - properties.xaxisScaleMin)) + properties.xaxisScaleMin; } else { 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; }; // // HBar 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(); var obj = this, opt = arguments[0] || {}; opt.frames = opt.frames || 120; opt.startFrames = []; opt.counters = []; var framesperbar = opt.frames / 3, frame = -1, callback = arguments[1] || function () {}, original = RGraph.arrayClone(obj.data), labelsAbove = properties.labelsAbove; this.isWave = true; // // If corners are set to be rounded disable them // whilst growing the bars // var orig_cornersRoundRadius = this.properties.cornersRoundRadius; var orig_cornersRoundTopRadius = this.properties.cornersRoundTopRadius; var orig_cornersRoundBottomRadius = this.properties.cornersRoundBottomRadius; this.properties.cornersRoundRadius = null; this.properties.cornersRoundTopRadius = null; this.properties.cornersRoundBottomRadius = null; 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('xaxisScaleMax', obj.scale2.max); RGraph.clear(obj.canvas); function iterator () { if (obj.stopAnimationRequested) { // Reset the flag obj.stopAnimationRequested = false; return; } ++frame; for (var 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))); opt.counters[i] += 1; // Make the number negative if the original was if (original[i] < 0) { obj.data[i] *= -1; } } else if (!RGraph.isNullish(obj.data[i])) { for (var j=0,len2=obj.data[i].length; j<len2; j+=1) { if (properties.xaxisScaleMin > 0 && properties.xaxisScaleMax > properties.xaxisScaleMin) { obj.data[i][j] = Math.min( Math.abs(original[i][j]), Math.abs(((original[i][j] - properties.xaxisScaleMin) * ( opt.counters[i][j] / framesperbar)) + properties.xaxisScaleMin) ); opt.counters[i][j] += 1; } else { obj.data[i][j] = Math.min( Math.abs(original[i][j]), Math.abs(original[i][j] * (opt.counters[i][j] / framesperbar)) ); // Having this here seems to skirt a // minification bug opt.counters[i][j] += 1; } // 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, (properties.xaxisScaleMin > 0 ? properties.xaxisScaleMin : 0)) : (RGraph.isNullish(obj.data[i]) ? null : (properties.xaxisScaleMin > 0 ? properties.xaxisScaleMin : 0)); } } if (frame >= opt.frames) { if (labelsAbove) { obj.set('labelsAbove', true); RGraph.redrawCanvas(obj.canvas); } // // Animate the corners to their desired amount // if it has been requested // if ( obj.animate && ( typeof orig_cornersRoundRadius === 'number' || typeof orig_cornersRoundTopRadius === 'number' || typeof orig_cornersRoundBottomRadius === 'number' ) ) { obj.animate({ frames: 15, cornersRoundRadius: orig_cornersRoundRadius, cornersRoundTopRadius: orig_cornersRoundTopRadius, cornersRoundBottomRadius: orig_cornersRoundBottomRadius }); } else { obj.properties.cornersRoundRadius = orig_cornersRoundRadius; obj.properties.cornersRoundTopRadius = orig_cornersRoundTopRadius; obj.properties.cornersRoundBottomRadius = orig_cornersRoundBottomRadius; RGraph.redrawCanvas(obj.canvas); } this.isWave = null; callback(obj); } else { RGraph.redrawCanvas(obj.canvas); RGraph.Effects.updateCanvas(iterator); } } iterator(); return this; }; // // Couple of functions that allow you to control the // animation effect // this.stopAnimation = function () { this.stopAnimationRequested = true; }; this.cancelStopAnimation = function () { this.stopAnimationRequested = false; }; // // Determines whether the given shape is adjustable or not // // @param object The shape that pertains to the relevant bar // this.isAdjustable = function (shape) { if (RGraph.isNullish(properties.adjustableOnly)) { return true; } if (RGraph.isArray(properties.adjustableOnly) && properties.adjustableOnly[shape.sequentialIndex]) { return true; } return false; }; // // 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 radiusTop = null; var radiusBottom = null; // Top radius if (RGraph.isNumber(properties.cornersRoundTopRadius)) { radiusTop = properties.cornersRoundTopRadius; } else { radiusTop = Math.min(width / 2, height / 2, properties.cornersRoundRadius); } // Bottom radius if (RGraph.isNumber(properties.cornersRoundBottomRadius)) { radiusBottom = properties.cornersRoundBottomRadius; } else { radiusBottom = Math.min(width / 2, height / 2, properties.cornersRoundRadius); } if ( (radiusTop + radiusBottom) > height) { // Calculate the top and bottom radiii and assign // to temporary variables var a = height / (radiusTop + radiusBottom) * radiusTop; var b = height / (radiusTop + radiusBottom) * radiusBottom; // Reassign the values to the correct variables radiusTop = a; radiusBottom = b; } // Because the function is added to the context prototype // the 'this' variable is actually the context // Save the existing state of the canvas so that it can be restored later this.save(); // Translate to the given X/Y coordinates this.translate(x, y); // Move to the center of the top horizontal line this.moveTo(width - radiusTop,0); // Draw the rounded corners. The connecting lines in between them are drawn automatically this.arcTo(width,0, width,0 + radiusTop, properties.cornersRoundTop ? radiusTop : 0); this.arcTo(width, height, width - radiusBottom, height, properties.cornersRoundBottom ? radiusBottom : 0); this.lineTo(0,height); this.lineTo(0, 0); // Draw a line back to the start coordinates this.closePath(); // Restore the state of the canvas to as it was before the save() this.restore(); }; // // This draws a rectangle with rounded corners [for negative // values] // // @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) { var radiusTop = null; var radiusBottom = null; // Top radius if (properties.cornersRoundTop) { if (RGraph.isNumber(properties.cornersRoundTopRadius)) { radiusTop = properties.cornersRoundTopRadius; } else { radiusTop = Math.min(width / 2, height / 2, properties.cornersRoundRadius); } } else { radiusTop = 0; } // Bottom radius if (properties.cornersRoundBottom) { if (RGraph.isNumber(properties.cornersRoundBottomRadius)) { radiusBottom = properties.cornersRoundBottomRadius; } else { radiusBottom = Math.min(width / 2, height / 2, properties.cornersRoundRadius); } } else { radiusBottom = 0; } if ( (radiusTop + radiusBottom) > height) { // Calculate the top and bottom radiii and assign // to temporary variables var a = height / (radiusTop + radiusBottom) * radiusTop; var b = height / (radiusTop + radiusBottom) * radiusBottom; // Reassign the values to the correct variables radiusTop = a; radiusBottom = b; } // Because the function is added to the context prototype // the 'this' variable is actually the context // 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,0); // Draw the rounded corners. The connecting lines in between them are drawn automatically this.context.lineTo(width,height); this.context.lineTo(0 + radiusBottom, height); this.context.arcTo(0,height, 0,height - radiusBottom, properties.cornersRoundBottom ? radiusBottom : 0); this.context.arcTo(0, 0, 0 + radiusTop,0, properties.cornersRoundTop ? radiusTop : 0); // Draw a line back to the start coordinates this.context.closePath(); // Restore the state of the canvas to as it was before the save() this.context.restore(); }; // // A worker function that handles Bar chart specific tooltip substitutions // this.tooltipSubstitutions = function (opt) { var indexes = RGraph.sequentialIndexToGrouped(opt.index, this.data); if (typeof this.data[indexes[0]] === 'object') { var values = this.data[indexes[0]]; } else { var values = [this.data[indexes[0]]]; } var value = this.data_arr[opt.index]; var index = indexes[1]; var seq = opt.index; // Skirt an indexes bug if (typeof this.data[indexes[0]] === 'object' && properties.grouping === 'stacked') { value = this.data[indexes[0]][this.data[indexes[0]].length - 1 - indexes[1]]; } // // Return the values to the user // return { index: index, dataset: indexes[0], sequentialIndex: seq, value: value, 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) { if (this.stackedOrGrouped) { var label = (!RGraph.isNullish(properties.tooltipsFormattedKeyLabels) && typeof properties.tooltipsFormattedKeyLabels === 'object' && properties.tooltipsFormattedKeyLabels[index]) ? properties.tooltipsFormattedKeyLabels[index] : ''; } else { var 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 when the hotspot is a point. // This is commonly used for vertical lines. // if (this.properties.tooltipsHotspotShape === 'point') { // 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 point on the chart + (this.properties.yaxisPosition === 'right' ? 0 : coords[2]) // Add the width of the bar if the yaxisPosition is 'left' - (tooltip.offsetWidth / 2) // Subtract half of the tooltip width + obj.properties.tooltipsOffsetx // Add any user defined offset ) + 'px'; args.tooltip.style.top = ( canvasXY[1] // The Y coordinate of the canvas + coords[1] + (coords[3] / 2) // The Y coordinate of the bar on the chart plus half of the height - tooltip.offsetHeight // The height of the tooltip - 12 // An arbitrary amount + obj.properties.tooltipsOffsety // Add any user defined offset - this.properties.lineTickmarksSize // Take off tickmarksize ) + 'px'; // // Position the tooltip based on a rect. This is used // for regular HBar charts. // } else { // 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 point on the chart + (coords[2] / 2) // Add half of the width of the bar - (tooltip.offsetWidth / 2) // Subtract half of the tooltip 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 = parseInt(args.tooltip.style.left); var top = coords[1]; var angle = properties.variantThreedAngle; var adjustment = Math.tan(angle) * left; args.tooltip.style.top = parseInt(args.tooltip.style.top) + adjustment + 'px'; } }; // // 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 { return this.data[index]; } }; // // 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; }; // // Draw a line on the chart - just each of the X points // (ie the top of the bars) connected by a line. This // does mean you can set the colors property to transparent // and you have a vertical line. // this.drawLine = function () { // Set the clipping region so that the trace //effect works RGraph.clipTo.start(this, [ 0,0, this.canvas.width,this.canvas.height * this.properties.animationTraceClip ]); var lineCoords = []; if (this.properties.lineSpline) { if (this.properties.lineShadow) { RGraph.setShadow({ object: this, prefix: 'lineShadow' }); } // Set the line options: // lineJoin // lineCap // lineWidth this.path( 'lj % lc % lw %', this.properties.lineLinejoin, this.properties.lineLinecap, this.properties.lineLinewidth ); // Set this so that we can refer to the object var obj = this; var c = RGraph.arrayClone(this.coords); // Call the Spline() function and have it return the // coordinates instead of drawing the line. This way // we can draw the line and fill (if necessary) it // instead of just the line being drawn. var coordinates = Spline(c, {return: true}); // 23rd March 2025 - Taken out due to a spurious // line being drawn. // // Add the coordinates of the end of the last // bar to the array of coord that we've got //coordinates[0].push([ // this.coords[this.coords.length - 1][0] + (properties.yaxisPosition === 'right' ? 0 : this.coords[this.coords.length - 1][2]), // this.coords[this.coords.length - 1][1] + (this.coords[this.coords.length - 1][3] / 2) //]); // // If the chart is to be filled - do that // if (this.properties.lineFilled) { // // Start the path for the fill and also set the shadow color // to transparent so that no shadow is cast by the fill. It's // turned back to what has been requested further down after // the fill has been drawn. // this.path( 'b lw 1 sc transparent m % % l % %', properties.yaxisPosition === 'right' ? this.canvas.width - properties.marginRight : properties.marginLeft, coordinates[0][coordinates[0].length - 1][1], properties.yaxisPosition === 'right' ? this.canvas.width - properties.marginRight : properties.marginLeft, coordinates[0][0][1] ); RGraph.pathLine({ context: this.context, coords: coordinates[0], moveto: false }); this.path( 'c f %', properties.lineFilledColor ? properties.lineFilledColor : properties.lineColor ); // Set the shadow color back to whatever has been requested // because it was turned off just above so there's no shadow // cast by the fill that has just been drawn. this.context.shadowColor = this.properties.lineShadowColor; } // // Now call the RGraph.drawLine() common function // to draw the spline. This function draws the // spline onto the canvas. // RGraph.drawLine({ context: this.context, coords: coordinates[0], stroke: this.properties.lineColor, linewidth: this.properties.lineLinewidth }); // // Store the coordinates that were generated // for the spline // this.coordsSpline = []; this.coordsSpline[0] = RGraph.arrayClone(coordinates[0]); // // Now generate the coordinates of the points of the // line from the coordinates of the bars. // this.coordsLines = [[]]; for (let i=0; i<this.coords.length; ++i) { this.coordsLines[0].push([ this.coords[i][0] + this.coords[i][2], this.coords[i][1] + (this.coords[i][3] / 2), ]); } } else { // Move to the first point if (this.properties.yaxisPosition === 'right') { lineCoords.push([ 'm', this.coords[0][0], this.coords[0][1] + (this.coords[0][3] / 2) ]); } else if (this.properties.yaxisPosition === 'center') { if (this.data[0] > 0) { lineCoords.push([ 'm', this.coords[0][0] + this.coords[0][2], this.coords[0][1] + (this.coords[0][3] / 2) ]); } else { lineCoords.push([ 'm', this.coords[0][0], this.coords[0][1] + (this.coords[0][3] / 2) ]); } } else { lineCoords.push([ 'm', this.data_arr[0] < 0 ? this.coords[0][0] : this.coords[0][0] + this.coords[0][2], this.coords[0][1] + (this.coords[0][3] / 2) ]); } // Draw a line to subsequent points unless // that point is null, in which case move // to it instead for (let i=1; i<this.coords.length; ++i) { if (RGraph.isNullish(this.data[i]) || RGraph.isNullish(this.data[i - 1])) { var action = 'm'; } else { var action = 'l' } if (this.properties.yaxisPosition === 'right') { lineCoords.push([ action, this.coords[i][0], this.coords[i][1] + (this.coords[i][3] / 2) ]); } else if (this.properties.yaxisPosition === 'center') { if (this.data_arr[i] > 0) { lineCoords.push([ action, this.coords[i][0] + this.coords[i][2], this.coords[i][1] + (this.coords[i][3] / 2) ]); } else { lineCoords.push([ action, this.coords[i][0], this.coords[i][1] + (this.coords[i][3] / 2) ]); } } else { lineCoords.push([ action, this.coords[i][0] + (this.data_arr[i] < 0 ? 0: this.coords[i][2]), this.coords[i][1] + (this.coords[i][3] / 2) ]); } } // // Draw the fill if it's been requested // if (this.properties.lineFilled) { for (var i=0; i<lineCoords.length; ++i) { if (i === 0) { this.path( 'b % % %', lineCoords[i][0], lineCoords[i][1], lineCoords[i][2] ); } else { this.path( '% % %', lineCoords[i][0], lineCoords[i][1], lineCoords[i][2] ); } } // Draw a line to the axis (be it on the left or // on the right) this.path( 'l % % l % % c sx 0 sy 0 sb 0 sc transparent f %', properties.yaxisPosition === 'right' ? this.canvas.width - properties.marginRight : properties.marginLeft, lineCoords[lineCoords.length - 1][2], properties.yaxisPosition === 'right' ? this.canvas.width - properties.marginRight : properties.marginLeft, lineCoords[0][2], !RGraph.isNullish(this.properties.lineFilledColor) ? this.properties.lineFilledColor : this.properties.lineColor ); } // // Add the coords to the obj.coordsLine variable // this.coordsLines = [[]]; for (let i=0; i<lineCoords.length; ++i) { this.coordsLines[0].push([ lineCoords[1], lineCoords[2] ]); } if (this.properties.lineShadow) { RGraph.setShadow({ object: this, prefix: 'lineShadow' }); } // Set the line options: // lineJoin // lineCap // lineWidth this.path( 'lj % lc % lw %', this.properties.lineLinejoin, this.properties.lineLinecap, this.properties.lineLinewidth ); // // === PLOT THE COORDINATES ===================== // // // Plot the coords that have just been calculated // for (var i=0; i<lineCoords.length; ++i) { if (i === 0) { this.path( 'b % % %', lineCoords[i][0], lineCoords[i][1], lineCoords[i][2] ); } else { this.path( '% % %', lineCoords[i][0], lineCoords[i][1], lineCoords[i][2] ); } } this.path('s ' + this.properties.lineColor); } // // === TICKMARKS =================== // // TODO Add more styles of tickmarks // var obj = this; this.coords.forEach(function (v, k, arr) { if (typeof obj.properties.lineTickmarksStyle === 'string') { var isEndTick = (k === 0 || k === (arr.length - 1)); var isNull = RGraph.isNullish(obj.data[k]); var prevIsNull = RGraph.isNullish(obj.data[k - 1]); var nextIsNull = RGraph.isNullish(obj.data[k + 1]); var isLast = k === (arr.length - 1); // // Does this tickmark need to be drawn? // if (isNull && !obj.properties.lineTickmarksDrawNull) return; if (!isNull && prevIsNull && nextIsNull && !obj.properties.lineTickmarksDrawNonNull) return; // Determine the X and Y coordinates for the // tickmark var x, y; if (obj.properties.yaxisPosition === 'right') { var x = v[0], y = v[1] + (v[3]/2); } else if (obj.properties.yaxisPosition === 'center') { if (obj.data[k] > 0) { var x = v[0] + v[2], y = v[1] + (v[3]/2); } else { var x = v[0], y = v[1] + (v[3]/2); } } else { if (obj.data[k] < 0) { var x = v[0], y = v[1] + (v[3]/2); } else { var x = v[0] + v[2], y = v[1] + (v[3]/2); } } // // Draw the various styles of tickmark // switch (obj.properties.lineTickmarksStyle) { case 'circle': case 'filledcircle': case 'filledendcircle': case 'endcircle': if ( (obj.properties.lineTickmarksStyle.indexOf('end') >= 0 && isEndTick) || obj.properties.lineTickmarksStyle.indexOf('end') === -1 ) { obj.path( 'b a % % % 0 6.29 false s % f %', x, y, obj.properties.lineTickmarksSize, obj.properties.lineColor, obj.properties.lineTickmarksStyle.indexOf('filled') >= 0 ? obj.properties.lineColor : 'white' ); } break; case 'square': case 'rect': case 'filledsquare': case 'filledrect': case 'filledendsquare': case 'filledendrect': case 'endsquare': case 'endrect': if ( (obj.properties.lineTickmarksStyle.indexOf('end') >= 0 && isEndTick) || obj.properties.lineTickmarksStyle.indexOf('end') === -1 ) { obj.path( 'b r % % % % s % f %', x - obj.properties.lineTickmarksSize, y - obj.properties.lineTickmarksSize, obj.properties.lineTickmarksSize * 2, obj.properties.lineTickmarksSize * 2, obj.properties.lineColor, obj.properties.lineTickmarksStyle.indexOf('filled') >= 0 ? obj.properties.lineColor : 'white' ); } break; } } }); // Turn the shadow off RGraph.noShadow(this); // End the trace animation clipping RGraph.clipTo.end(); // // This function draws a spline using the HBar coords // // @param array coords The coordinates // function Spline (coords, opt = {}) { var context = obj.context; //obj.coordsSpline[0] = []; var coordsSpline = [[]]; var yCoords = [], interval = (obj.canvas.height - obj.properties.marginTop - obj.properties.marginBottom) / coords.length; obj.context.beginPath(); obj.context.strokeStyle = obj.properties.lineColor; // // The drawSpline function needs an array of JUST // the X values - so put the coords into the correct // format // for (var i=0; i<coords.length;++i) { coords[i] = Number(coords[i][0]) + (obj.properties.yaxisPosition === 'right' || obj.data_arr[i] < 0 ? 0 : Number(coords[i][2]) ); } // // Get the Points array in the format we want - // first value should be null along with the lst // value // var P = [coords[0]]; for (var i=0; i<coords.length; ++i) { P.push(coords[i]); } P.push( coords[coords.length - 1] + (coords[coords.length - 1] - coords[coords.length - 2]) ); // This is is necessary to center the points // within each bar/section var halfsection = ((obj.canvas.height - obj.properties.marginTop - obj.properties.marginBottom) / obj.data.length) / 2 for (var j=1; j<P.length-2; ++j) { for (var t=0; t<=10; ++t) { var xCoord = Spline( t/10, P[j-1], P[j], P[j+1], P[j+2] ); yCoords.push( ((j-1) * interval) + (t * (interval / 10)) + obj.properties.marginTop + halfsection ); obj.context.lineTo( xCoord, yCoords[yCoords.length - 1] ); coordsSpline[0].push([ xCoord, yCoords[yCoords.length - 1] ]); } } // Draw the last section var last = [ obj.coords[obj.coords.length - 1][0] + (obj.properties.yaxisPosition === 'right' ? 0 : obj.coords[obj.coords.length - 1][2]), obj.coords[obj.coords.length - 1][1] + (obj.coords[obj.coords.length - 1][3] / 2) ]; obj.context.lineTo( last[0], last[1] ); if (typeof index === 'number') { coordsSpline[0].push([ last[0], last[1] ]); } if (opt.return === true) { return coordsSpline; } else { obj.coordsSpline = RGraph.arrayClone(coordsSpline); obj.context.stroke(); } function Spline (t, P0, P1, P2, P3) { return 0.5 * ((2 * P1) + ((0-P0) + P2) * t + ((2*P0 - (5*P1) + (4*P2) - P3) * (t*t) + ((0-P0) + (3*P1)- (3*P2) + P3) * (t*t*t))); } } }; // // Trace (for the line only) // // This is a new version of the Trace effect which no longer requires jQuery and is more compatible // with other effects (eg Expand). This new effect is considerably simpler and less code. // // @param object Options for the effect. Currently only "frames" is available. // @param int A function that is called when the ffect is complete // this.trace = function () { // Cancel any stop request if one is pending this.cancelStopAnimation(); var obj = this, opt = arguments[0] || {}, frames = opt.frames || 60, frame = 0, callback = arguments[1] || function () {}; obj.set('animationTraceClip', opt.reverse ? 1 : 0); // Disable the labelsAbove option if (obj.properties.labelsAbove) { obj.set('labelsAbove', false); var enableLabelsAbove = true; } function iterator () { if (obj.stopAnimationRequested) { // Reset the flag obj.stopAnimationRequested = false; return; } RGraph.clear(obj.canvas); RGraph.redrawCanvas(obj.canvas); if (frame++ < frames) { obj.set('animationTraceClip', opt.reverse ? (1 - (frame / frames)) : (frame / frames)); RGraph.Effects.updateCanvas(iterator); } else { if (enableLabelsAbove) { setTimeout(function () { obj.set('labelsAbove', true); RGraph.redraw(); }, 500); } callback(obj); } } iterator(); return this; }; // // This function handles clipping to scale values. Because // each chart handles scales differently, a worker function // is needed instead of it all being done centrally 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 height = this.canvas.height, x1 = this.getXCoord(from, true), x2 = this.getXCoord(to, true), width = Math.abs(x2 - x1), x = Math.min(x1, x2), y = 0; // Increase the height if the maximum value is "max" if (RegExp.$2 === 'max') { width += this.properties.marginRight; } // Increase the height if the minimum value is "min" if (RegExp.$1 === 'min') { x = 0; width = x2; } //$a([x, y, width, height]); 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 height = this.canvas.height, x1 = this.getXCoord(from, true), x2 = this.getXCoord(to, true), width = Math.abs(x2 - x1), x = Math.min(x1, x2), y = 0; // Increase the height if the maximum value is "max" if (RegExp.$2 === 'max') { width += this.properties.marginRight; } // Increase the height if the minimum value is "min" if (RegExp.$1 === 'min') { x = 0; width = x2; } this.path( 'b r % % % %', x, y, width, height ); }; // // This function allows the drawing of custom lines // this.drawVerticalLines = function () { var lines = this.properties.verticalLines, avg,x,y,label,halign,valign,hmargin = 5,vmargin = 10, position,textFont,textSize,textColor,textBold,textItalic, data,linewidth; if (lines) { // // Set some defaults for the configuration of // each line // var defaults = { dotted: false, dashed: true, color: '#666', // Same as labelColor property below linewidth: 1, label: 'Average (%{value})', labelPosition: 'top', labelColor: '#666', // Same as color property above labelValueDecimals: 0, labelOffsetx: 0, labelOffsety: 0, labelHalign: 'center', labelValign: 'bottom' }; // Loop through each line to be drawn for (var i=0; i<this.properties.verticalLines.length; ++i) { var conf = lines[i], textFont = conf.labelFont || this.properties.textFont, textColor = conf.labelColor || defaults.labelColor, textSize = conf.labelSize || this.properties.textSize, textBold = conf.labelBold ? conf.labelBold : this.properties.textBold, textItalic = conf.labelItalic ? conf.labelItalic : this.properties.textItalic; if (typeof conf.value === 'number') { x = this.getXCoord(conf.value); } else { avg = RGraph.arraySum(this.data_arr) / this.data_arr.length; x = this.getXCoord(avg); } // // Dotted or dashed lines // linedash = '[1,1]'; if (conf.dotted === true) { linedash = '[1,3]'; } if (conf.dashed === true || (typeof conf.dashed === 'undefined' && RGraph.isUndefined(conf.dotted)) ) { linedash = '[6,6]'; } // // Draw the line // this.path( 'lw % ld % b m % % l % % s %', typeof conf.linewidth === 'number' ? conf.linewidth : defaults.linewidth, linedash, x, this.properties.marginTop, x, this.canvas.height - this.properties.marginBottom, conf.color || defaults.color ); textX = x; textY = conf.labelPosition === 'bottom' ? this.canvas.height - this.properties.marginBottom + vmargin : this.properties.marginTop - vmargin; // // Draw the label // //Calc textx and texty here halign = (conf.labelHalign || defaults.labelHalign); valign = (conf.labelValign || defaults.labelValign); if (RGraph.isNumber(conf.labelX)) textX = conf.labelX; if (RGraph.isNumber(conf.labelY)) textY = conf.labelY; if (!conf.labelValign && conf.labelPosition === 'bottom') { valign = 'top'; } // Default pos for the label conf.labelPosition = conf.labelPosition || defaults.labelPosition; labelPosition = conf.labelPosition.trim(); // Account for linewidth linewidth = typeof conf.linewidth === 'number' ? conf.linewidth : defaults.linewidth; // // Determine the value to show // if (RGraph.isNumber(conf.value)) { var num = Number(conf.value).toFixed(conf.labelValueDecimals); } else { num = avg; num = num.toFixed(conf.labelValueDecimals); } num = RGraph.numberFormat({ object: this, number: num, unitspre: conf.labelValueUnitsPre, unitspost: conf.labelValueUnitsPost, thousand: conf.labelValueThousand, point: conf.labelValuePoint, formatter: conf.labelValueFormatter }); // // Draw the label // RGraph.text({ object: this, text: (RGraph.isString(conf.label) ? conf.label : defaults.label).replace('%{value}', num), x: RGraph.isNumber(conf.labelX) ? conf.labelX : (textX + (conf.labelOffsetx || 0)), y: RGraph.isNumber(conf.labelY) ? conf.labelY : (textY + (conf.labelOffsety || 0)), valign: valign, halign: halign, size: textSize, font: textFont, color: textColor, italic: textItalic, bold: textBold, bounding: true, boundingFill: 'rgba(255,255,255,0.75)', boundingStroke: 'transparent' }); } } }; // // Charts are now always registered // RGraph.register(this); // // This is the 'end' of the constructor so if the first argument // contains configuration data - handle that. // RGraph.parseObjectStyleConfig(this, conf.options); };