// 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.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, 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, 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 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=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.isNull(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.isNull(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 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.isNull(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.isNull(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.isNull(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.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 (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.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= 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.isNull(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=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 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' }); } } } }; // // Grow // // The HBar chart Grow effect gradually increases the values of the bars // // @param object OPTIONAL Options for the effect. You can pass frames here // @param function OPTIONAL A callback function // //this.grow = function () //{ // var obj = this, // opt = arguments[0] || {}, // frames = opt.frames || 30, // frame = 0, // callback = arguments[1] || function () {}, // labelsAbove = properties.labelsAbove; // // this.set('labelsAbove', false); // // Save the data // obj.original_data = RGraph.arrayClone(obj.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 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 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.isNull(obj.data[i])) { for (var j=0,len2=obj.data[i].length; j 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.isNull(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 ( typeof orig_cornersRoundRadius === 'number' || typeof orig_cornersRoundTopRadius === 'number' || typeof orig_cornersRoundBottomRadius === 'number' ) { obj.animate({ frames: 90, cornersRoundRadius: orig_cornersRoundRadius, cornersRoundTopRadius: orig_cornersRoundTopRadius, cornersRoundBottomRadius: orig_cornersRoundBottomRadius }); } 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.isNull(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.isNull(properties.tooltipsFormattedKeyLabels) && typeof properties.tooltipsFormattedKeyLabels === 'object' && properties.tooltipsFormattedKeyLabels[index]) ? properties.tooltipsFormattedKeyLabels[index] : ''; } else { var label = ( !RGraph.isNull(properties.tooltipsFormattedKeyLabels) && typeof properties.tooltipsFormattedKeyLabels === 'object' && properties.tooltipsFormattedKeyLabels[specific.index] ) ? properties.tooltipsFormattedKeyLabels[specific.index] : ''; } return { label: label }; }; // // This allows for static tooltip positioning // this.positionTooltipStatic = function (args) { var obj = args.object, e = args.event, tooltip = args.tooltip, index = args.index, canvasXY = RGraph.getCanvasXY(obj.canvas) coords = this.coords[args.index]; // Position the tooltip in the X direction args.tooltip.style.left = ( canvasXY[0] // The X coordinate of the canvas + coords[0] // The X coordinate of the 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 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.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 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.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 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 { 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