// o---------------------------------------------------------------------------------o // | This file is part of the RGraph package - you can learn more at: | // | | // | https://www.rgraph.net/license.html | // | | // | RGraph is dual-licensed under the Open Source GPL license. That means that it's | // | free to use and there are no restrictions on what you can use RGraph for! | // | If the GPL license does not suit you however, then there's an inexpensive | // | commercial license option available. See the URL above for more details. | // o---------------------------------------------------------------------------------o RGraph = window.RGraph || {isrgraph:true,isRGraph: true,rgraph:true}; // // The bar chart constructor // RGraph.Bar = function (conf) { var id = conf.id, canvas = document.getElementById(id), data = conf.data; // Get the canvas and context objects this.id = id; this.canvas = canvas; this.context = this.canvas.getContext('2d'); this.canvas.__object__ = this; this.type = 'bar'; this.max = 0; this.stackedOrGrouped = false; this.isRGraph = true; this.isrgraph = true; this.rgraph = true; this.uid = RGraph.createUID(); this.canvas.uid = this.canvas.uid ? this.canvas.uid : RGraph.createUID(); this.colorsParsed = false; this.original_colors = []; this.cachedBackgroundCanvas = null; this.firstDraw = true; // After the first draw this will be false this.stopAnimationRequested = false;// Used to control the animations // Various config type stuff this.properties = { backgroundBarsCount: null, backgroundBarsColor1: 'rgba(0,0,0,0)', backgroundBarsColor2: 'rgba(0,0,0,0)', backgroundGrid: true, backgroundGridColor: '#ddd', backgroundGridLinewidth: 1, backgroundGridHsize: 20, backgroundGridVsize: 20, backgroundGridVlines: true, backgroundGridHlines: true, backgroundGridBorder: true, backgroundGridAutofit: true, backgroundGridAutofitAlign: true, backgroundGridHlinesCount: 5, backgroundGridDashed: false, backgroundGridDotted: false, backgroundGridDashArray: null, backgroundGridThreedYaxis: true, backgroundImage: null, backgroundImageStretch: true, backgroundImageX: null, backgroundImageY: null, backgroundImageW: null, backgroundImageH: null, backgroundImageAlign: null, backgroundColor: null, backgroundHbars: null, backgroundBorder: false, backgroundBorderLinewidth: 1, backgroundBorderColor: '#aaa', backgroundBorderDashed: false, backgroundBorderDotted: false, backgroundBorderDashArray: null, marginTop: 35, marginBottom: 35, marginLeft: 35, marginRight: 35, marginInner: 5, marginInnerGrouped: 1, labelsIngraph: null, labelsIngraphFont: null, labelsIngraphSize: null, labelsIngraphColor: null, labelsIngraphBold: null, labelsIngraphItalic: null, labelsIngraphOffsetx: 0, labelsIngraphOffsety: 0, labelsAbove: false, labelsAboveDecimals: 0, labelsAboveSize: null, labelsAboveColor: null, labelsAboveBold: null, labelsAboveItalic: null, labelsAboveFont: null, labelsAbovePoint: '.', labelsAboveThousand: ',', labelsAboveBackground:'transparent', labelsAboveAngle: null, labelsAboveOffset: null, labelsAboveOffsetx: 0, labelsAboveOffsety: 0, labelsAboveUnitsPre: '', labelsAboveUnitsPost:'', labelsAboveFormatter:null, yaxis: true, yaxisLinewidth: 1, yaxisColor: 'black', yaxisTickmarks: true, yaxisTickmarksCount: null, yaxisTickmarksLastTop: null, yaxisTickmarksLastBottom: null, yaxisTickmarksLength: 3, yaxisScale: true, yaxisScaleMin: 0, yaxisScaleMax: null, yaxisScaleUnitsPre: '', yaxisScaleUnitsPost: '', yaxisScaleDecimals: 0, yaxisScalePoint: '.', yaxisScaleThousand: ',', yaxisScaleRound: false, yaxisScaleFormatter: null, yaxisLabelsSpecific: null, yaxisLabelsCount: 5, yaxisLabelsOffsetx: 0, yaxisLabelsOffsety: 0, yaxisLabelsHalign: null, yaxisLabelsValign: null, yaxisLabelsFont: null, yaxisLabelsSize: null, yaxisLabelsColor: null, yaxisLabelsBold: null, yaxisLabelsItalic: null, yaxisLabelsPosition: 'edge', yaxisPosition: 'left', yaxisTitle: '', yaxisTitleAccessible: null, yaxisTitleBold: null, yaxisTitleSize: null, yaxisTitleFont: null, yaxisTitleColor: null, yaxisTitleItalic: null, yaxisTitlePos: null, yaxisTitleX: null, yaxisTitleY: null, yaxisTitleOffsetx: 0, yaxisTitleOffsety: 0, yaxisTitleHalign: null, yaxisTitleValign: null, yaxisTitleAccessible: null, xaxis: true, xaxisLinewidth: 1, xaxisColor: 'black', xaxisTickmarks: true, xaxisTickmarksLength: 3, xaxisTickmarksLastLeft: null, xaxisTickmarksLastRight: null, xaxisTickmarksCount: null, xaxisLabels: null, xaxisLabelsSize: null, xaxisLabelsFont: null, xaxisLabelsItalic: null, xaxisLabelsBold: null, xaxisLabelsColor: null, xaxisLabelsFormattedDecimals: 0, xaxisLabelsFormattedPoint: '.', xaxisLabelsFormattedThousand: ',', xaxisLabelsFormattedUnitsPre: '', xaxisLabelsFormattedUnitsPost: '', xaxisLabelsOffsetx: 0, xaxisLabelsOffsety: 0, xaxisLabelsHalign: null, xaxisLabelsValign: null, xaxisLabelsPosition: 'section', xaxisLabelsSpecificAlign: 'left', xaxisPosition: 'bottom', xaxisLabelsAngle: 0, xaxisTitle: '', xaxisTitleBold: null, xaxisTitleSize: null, xaxisTitleFont: null, xaxisTitleColor: null, xaxisTitleItalic: null, xaxisTitlePos: null, xaxisTitleOffsetx: 0, xaxisTitleOffsety: 0, xaxisTitleX: null, xaxisTitleY: null, xaxisTitleHalign: null, xaxisTitleValign: null, textItalic: false, textBold: false, textColor: 'black', textSize: 12, textFont: 'Arial, Verdana, sans-serif', textAccessible: false, textAccessibleOverflow: 'visible', textAccessiblePointerevents: false, text: null, title: '', titleX: null, titleY: null, titleHalign: null, titleValign: null, titleFont: null, titleSize: null, titleColor: null, titleBold: null, titleItalic: null, titleOffsetx: 0, titleOffsety: 0, titleSubtitle: '', titleSubtitleSize: null, titleSubtitleColor: '#aaa', titleSubtitleFont: null, titleSubtitleBold: null, titleSubtitleItalic: null, titleSubtitleOffsetx: 0, titleSubtitleOffsety: 0, colorsStroke: 'rgba(0,0,0,0)', colors: ['red','#0f0','blue','pink','orange','cyan','black','white','green','magenta'], colorsSequential: false, colorsReverse: false, grouping: 'grouped', variant: 'bar', variantSketchVerticals: true, variantThreedXaxis: true, variantThreedYaxis: true, variantThreedAngle: 0.1, variantThreedOffsetx: 10, variantThreedOffsety: 5, variantThreedXaxisColor: '#ddd', variantThreedYaxisColor: '#ddd', shadow: false, shadowColor: '#aaa', shadowOffsetx: 0, shadowOffsety: 0, shadowBlur: 15, tooltips: null, tooltipsEffect: 'slide', tooltipsCssClass: 'RGraph_tooltip', tooltipsCss: null, tooltipsEvent: 'onclick', tooltipsHighlight: true, tooltipsHotspotXonly: false, tooltipsHotspotIgnore: null, tooltipsFormattedThousand: ',', tooltipsFormattedPoint: '.', tooltipsFormattedDecimals: 0, tooltipsFormattedUnitsPre: '', tooltipsFormattedUnitsPost: '', tooltipsFormattedKeyColors: null, tooltipsFormattedKeyColorsShape: 'square', tooltipsFormattedKeyLabels: [], tooltipsFormattedListType: 'ul', tooltipsFormattedListItems: null, tooltipsFormattedTableHeaders: null, tooltipsFormattedTableData: null, tooltipsPointer: true, tooltipsPointerOffsetx: 0, tooltipsPointerOffsety: 0, tooltipsPositionStatic: true, highlightStroke: 'rgba(0,0,0,0)', highlightFill: 'rgba(255,255,255,0.7)', key: null, keyBackground: 'white', keyPosition: 'graph', keyShadow: false, keyShadowColor: '#666', keyShadowBlur: 3, keyShadowOffsetx: 2, keyShadowOffsety: 2, keyPositionMarginBoxed:false, keyPositionMarginHSpace: 0, keyPositionX: null, keyPositionY: null, keyInteractive: false, keyInteractiveHighlightChartLinewidth: 2, keyInteractiveHighlightChartStroke: 'black', keyInteractiveHighlightChartFill: 'rgba(255,255,255,0.7)', keyInteractiveHighlightLabel: 'rgba(255,0,0,0.2)', keyHalign: 'right', keyColorShape: 'square', keyRounded: true, keyLinewidth: 1, keyColors: null, keyLabelsColor: null, keyLabelsSize: null, keyLabelsFont: null, keyLabelsBold: null, keyLabelsItalic: null, keyLabelsOffsetx: 0, keyLabelsOffsety: 0, keyFormattedDecimals: 0, keyFormattedPoint: '.', keyFormattedThousand: ',', keyFormattedUnitsPre: '', keyFormattedUnitsPost: '', keyFormattedValueSpecific: null, keyFormattedItemsCount: null, contextmenu: null, crosshairs: false, crosshairsColor: '#333', crosshairsLinewidth: 1, crosshairsHline: true, crosshairsVline: true, crosshairsSnapToScale: false, linewidth: 1, annotatable: false, annotatableLinewidth: 1, annotatableColor: 'black', adjustable: false, adjustableOnly: null, beveled: false, errorbars: false, errorbarsColor: 'black', errorbarsCapped: true, errorbarsCappedWidth: 14, errorbarsLinewidth: 1, combinedEffect: null, combinedEffectOptions: null, combinedEffectCallback: null, corners: 'square', cornersRoundRadius: 10, cornersRoundLeft: true, cornersRoundRight: true, cornersRoundLeftRadius: null, cornersRoundRightRadius: null, responsive: null, clearto: 'rgba(0,0,0,0)' } // // Add the reverse look-up table for property names // so that property names can be specified in any case. // this.properties_lowercase_map = []; for (var i in this.properties) { if (typeof i === 'string') { this.properties_lowercase_map[i.toLowerCase()] = i; } } // Check for support if (!this.canvas) { alert('[BAR] No canvas support'); return; } // // Convert strings into numbers. Also converts undefined // elements to null // data = RGraph.stringsToNumbers(data); // // Determine whether the chart will contain stacked or // grouped bars // for (var i=0; i 0) { alert('[BAR] (' + this.id + ') Sorry, tooltips are not supported with dot or pyramid charts'); } // // Stop the coords arrays from growing uncontrollably // this.coords = []; this.coords2 = []; this.coordsText = []; // // Work out a few things. They need to be here because // they depend on things you can change before you // call Draw() but after you instantiate the object // this.max = 0; this.grapharea = this.canvas.height - this.marginTop - this.marginBottom; this.halfgrapharea = this.grapharea / 2; this.halfTextHeight = properties.textSize / 2; // // Work out the max value // if (properties.yaxisScaleMax) { this.scale2 = RGraph.getScale({object: this, options: { 'scale.max': properties.yaxisScaleMax, 'scale.strict': properties.yaxisScaleRound ? false : true, 'scale.min': properties.yaxisScaleMin, 'scale.thousand': properties.yaxisScaleThousand, 'scale.point': properties.yaxisScalePoint, 'scale.decimals': properties.yaxisScaleDecimals, 'scale.labels.count':properties.yaxisLabelsCount, 'scale.round': properties.yaxisScaleRound, 'scale.units.pre': properties.yaxisScaleUnitsPre, 'scale.units.post': properties.yaxisScaleUnitsPost, 'scale.formatter': properties.yaxisScaleFormatter }}); } else { // // If errorbars are given as a number then convert the nuumber to an // array. // var errorbars = properties.errorbars; if (typeof errorbars === 'number') { var value = errorbars; properties.errorbars = []; for (var i=0; i 0) { RGraph.drawBars(this); } var variant = properties.variant; // // Draw the 3D axes is necessary // if (variant === '3d') { RGraph.draw3DAxes(this); } // // Get the variant once, and draw the bars, be they regular, stacked or grouped // // Get these variables outside of the loop var xaxispos = properties.xaxisPosition, width = (this.canvas.width - this.marginLeft - this.marginRight ) / this.data.length, orig_height = height, hmargin = properties.marginInner, shadow = properties.shadow, shadowColor = properties.shadowColor, shadowBlur = properties.shadowBlur, shadowOffsetX = properties.shadowOffsetx, shadowOffsetY = properties.shadowOffsety, strokeStyle = properties.colorsStroke, colors = properties.colors, sequentialColorIndex = 0 var height; // // Loop through the data // for (i=0,len=this.data.length; i= 0) { height = Math.abs(this.getYCoord(0) - this.getYCoord(this.data[i])); } else { y = this.getYCoord(0); height = Math.abs(this.getYCoord(0) - this.getYCoord(this.data[i])); } } var barWidth = width - (2 * hmargin); // // Check for a negative bar width // if (barWidth < 0) { alert('[RGRAPH] Warning: you have a negative bar width. This may be caused by the marginInner being too high or the width of the canvas not being sufficient.'); } // Set the fill color this.context.strokeStyle = strokeStyle; this.context.fillStyle = colors[0]; // // Sequential colors // if (properties.colorsSequential) { this.context.fillStyle = colors[i]; } if (variant == 'sketch') { this.context.lineCap = 'round'; var sketchOffset = 3; this.context.beginPath(); this.context.strokeStyle = colors[0]; // // Sequential colors // if (properties.colorsSequential) { this.context.strokeStyle = colors[i]; } // Left side this.context.moveTo(x + hmargin + 2, y + height - 2); this.context.lineTo(x + hmargin - 1, y - 4); // The top this.context.moveTo(x + hmargin - 3, y + -2 + (this.data[i] < 0 ? height : 0)); this.context.quadraticCurveTo( x + hmargin + ((width - hmargin - hmargin) / 4), y + 0 + (this.data[i] < 0 ? height : 0) + (this.data[i] > 0 ? 10 : -10), x + hmargin + width + -1 - hmargin - hmargin, y + 0 + (this.data[i] < 0 ? height : 0) ); // The right side this.context.moveTo(x + hmargin + width - 5 - hmargin - hmargin, y - 5); this.context.lineTo(x + hmargin + width - 3 - hmargin - hmargin, y + height - 3); // Draw the inner-bar verticals if (properties.variantSketchVerticals) { for (var r=0.2; r<=0.8; r+=0.2) { this.context.moveTo( x + hmargin + ((width - hmargin - hmargin) * r), y - 1 ); this.context.lineTo( x + hmargin + ((width - hmargin - hmargin) * r), y + height + (r == 0.2 ? 1 : -2) ); } } this.context.stroke(); // Regular bar } else if (variant == 'bar' || variant == '3d' || variant == 'glass' || variant == 'bevel') { if (variant == 'glass') { RGraph.roundedRect({ context: this.context, x: x + hmargin, y: y, width: barWidth, height: height, radius: RGraph.isNumber(properties.cornersRoundRadius) ? properties.cornersRoundRadius : 5, roundtl: this.data[i] > 0, roundtr: this.data[i] > 0, roundbl: this.data[i] < 0, roundbr: this.data[i] < 0 }); this.context.stroke(); this.context.fill(); } else { // On 9th April 2013 these two were swapped around so that the stroke happens SECOND so that any // shadow that is cast by the fill does not overwrite the stroke // Why is this here? //this.path( // 'b r % % % % f', // x + hmargin, y, barWidth, height //); // Turn the shadow off so that the stroke doesn't cast any "extra" shadow // that would show inside the bar // // 31/07/21 Removed as regular bar charts weren't showing shadows // //RGraph.noShadow(this); if (properties.corners === 'round') { this.context.beginPath(); this.context.lineCap = 'miter'; this.context.lineJoin = 'square'; (this.data[i] < 0 || properties.xaxisPosition === 'top') ? this.roundedCornersRectNegative(x + hmargin,y,barWidth,height) : this.roundedCornersRect(x + hmargin,y,barWidth,height); this.context.stroke(); this.context.fill(); } else { if (properties.xaxisPosition === 'top') { y += height; } this.context.beginPath(); this.context.lineJoin = 'miter'; this.context.lineCap = 'square'; this.context.rect(x + hmargin,y,barWidth,height); this.context.stroke(); this.context.fill(); } } // 3D effect if (variant == '3d') { var prevStrokeStyle = this.context.strokeStyle; var prevFillStyle = this.context.fillStyle; // Draw the top (if the value is positive - otherwise there's no point) if (this.data[i] >= 0) { this.context.beginPath(); this.context.moveTo(x + hmargin, y); this.context.lineTo(x + hmargin + properties.variantThreedOffsetx, y - properties.variantThreedOffsety); this.context.lineTo(x + hmargin + properties.variantThreedOffsetx + barWidth, y - properties.variantThreedOffsety); this.context.lineTo(x + hmargin + barWidth, y); this.context.closePath(); this.context.stroke(); this.context.fill(); } // Draw the right hand side this.context.beginPath(); this.context.moveTo(x + hmargin + barWidth, y); this.context.lineTo( x + hmargin + barWidth + properties.variantThreedOffsetx, this.data[i] < 0 && xaxispos === 'bottom' ? this.getYCoord(0) : ( this.data[i] < 0 && (y - properties.variantThreedOffsety) < (this.marginTop + this.halfgrapharea) ? (this.marginTop + this.halfgrapharea) : (y - properties.variantThreedOffsety)) ); this.context.lineTo( x + hmargin + barWidth + properties.variantThreedOffsetx, this.data[i] < 0 && (y - properties.variantThreedOffsety + height) < (this.marginTop + this.getYCoord(0)) ? this.getYCoord(this.data[i]) - properties.variantThreedOffsety : (this.data[i] > 0 ? y - properties.variantThreedOffsety + height : Math.min(y - properties.variantThreedOffsety + height, this.canvas.height - this.marginBottom) ) ); this.context.lineTo(x + hmargin + barWidth, y + height); this.context.closePath(); this.context.stroke(); this.context.fill(); // Draw the lighter top section if (this.data[i] > 0) { this.context.beginPath(); this.context.fillStyle = 'rgba(255,255,255,0.5)'; this.context.moveTo(x + hmargin, y); this.context.lineTo(x + hmargin + properties.variantThreedOffsetx, y - properties.variantThreedOffsety); this.context.lineTo(x + hmargin + properties.variantThreedOffsetx + barWidth, y - properties.variantThreedOffsety); this.context.lineTo(x + hmargin + barWidth, y); this.context.lineTo(x + hmargin, y); this.context.closePath(); this.context.stroke(); this.context.fill(); } // Draw the darker right side section this.context.beginPath(); this.context.fillStyle = 'rgba(0,0,0,0.4)'; // TL this.context.moveTo(x + hmargin + barWidth, y); // TR this.context.lineTo( x + hmargin + barWidth + properties.variantThreedOffsetx, this.data[i] < 0 && xaxispos === 'bottom' ? this.getYCoord(0) : (this.data[i] < 0 && (y - properties.variantThreedOffsety) < (this.marginTop + this.halfgrapharea) ? (this.marginTop + this.halfgrapharea) : y - properties.variantThreedOffsety) ); // BR this.context.lineTo( x + hmargin + barWidth + properties.variantThreedOffsetx, this.data[i] < 0 && (y - properties.variantThreedOffsety + height) < this.getYCoord(0) ? this.getYCoord(0) : this.data[i] > 0 ? y - properties.variantThreedOffsety + height : Math.min(y - properties.variantThreedOffsety + height, this.canvas.height - this.marginBottom) ); // BL this.context.lineTo(x + hmargin + barWidth, y + height); this.context.lineTo(x + hmargin + barWidth, y); this.context.closePath(); this.context.stroke(); this.context.fill(); this.context.strokeStyle = prevStrokeStyle; this.context.fillStyle = prevFillStyle; // Glass variant } else if (variant == 'glass') { var grad = this.context.createLinearGradient(x + hmargin,y,x + hmargin + (barWidth / 2),y); grad.addColorStop(0, 'rgba(255,255,255,0.9)'); grad.addColorStop(1, 'rgba(255,255,255,0.5)'); this.context.beginPath(); this.context.fillStyle = grad; this.context.roundRect( x + hmargin + 3, y + (this.data[i] > 0 ? 3 : 0), (barWidth / 2) - 2, height - 2, this.data[i] > 0 ? ([RGraph.isNumber(properties.cornersRoundRadius) ? properties.cornersRoundRadius - 3 : 5, 0,0,0]) : [0,0,0,RGraph.isNumber(properties.cornersRoundRadius) ? properties.cornersRoundRadius : 5] ); this.context.fill(); } // Dot chart } else if (variant == 'dot') { this.context.beginPath(); this.context.strokeStyle = this.properties.colors[0]; this.context.moveTo(x + (width / 2), y); this.context.lineTo(x + (width / 2), y + height); this.context.stroke(); this.context.beginPath(); this.context.fillStyle = this.properties.colors[i]; this.context.arc( x + (width / 2), y + (this.data[i] > 0 ? 0 : height), 2, 0, 6.28, 0 ); // Set the colour for the dots this.context.fillStyle = properties.colors[0]; // // Sequential colors // if (properties.colorsSequential) { this.context.fillStyle = colors[i]; } this.context.stroke(); this.context.fill(); // Unknown variant type } else { alert('[BAR] Warning! Unknown variant: ' + variant); } this.coords.push([x + hmargin, y, width - (2 * hmargin), height]); if (typeof this.coords2[i] == 'undefined') { this.coords2[i] = []; } this.coords2[i].push([x + hmargin, y, width - (2 * hmargin), height]); // // Stacked bar // } else if (this.data[i] && typeof this.data[i] == 'object' && properties.grouping == 'stacked') { if (this.scale2.min) { alert("[ERROR] Stacked Bar charts with a Y min are not supported"); } var barWidth = width - (2 * hmargin); var redrawCoords = [];// Necessary to draw if the shadow is enabled var startY = 0; var dataset = this.data[i]; // // Check for a negative bar width // if (barWidth < 0) { alert('[RGRAPH] Warning: you have a negative bar width. This may be caused by the marginInner being too high or the width of the canvas not being sufficient.'); } for (j=0; j 0) { // TODO Handle xaxisPosition=top here if (j === 0 && properties.corners === 'round' && properties.xaxisPosition === 'bottom') { this.context.beginPath(); this.context.lineCap = 'miter'; this.context.lineJoin = 'square'; this.roundedCornersRect(x + hmargin, y, width - (2 * hmargin), height); this.context.stroke(); this.context.fill(); } else if (j === (dataset.length - 1) && properties.corners === 'round' && properties.xaxisPosition === 'top') { this.context.beginPath(); this.context.lineCap = 'miter'; this.context.lineJoin = 'square'; this.roundedCornersRectNegative(x + hmargin, y, width - (2 * hmargin), height); this.context.stroke(); this.context.fill(); } else { this.path( 'b lj % lc % r % % % % s % f %', 'miter','square', x + hmargin, y, width - (2 * hmargin), height, this.context.strokeStyle, this.context.fillStyle ); } } if (j == 0) { var startY = y; var startX = x; } // // Store the redraw coords if the shadow is enabled // if (shadow) { redrawCoords.push([x + hmargin, y, width - (2 * hmargin), height, this.context.fillStyle]); } // // Stacked 3D effect // if (variant == '3d') { var prevFillStyle = this.context.fillStyle; var prevStrokeStyle = this.context.strokeStyle; // Draw the top side if (j == 0) { this.context.beginPath(); this.context.moveTo(startX + hmargin, y); this.context.lineTo(startX + properties.variantThreedOffsetx + hmargin, y - properties.variantThreedOffsety); this.context.lineTo(startX + properties.variantThreedOffsetx + barWidth + hmargin, y - properties.variantThreedOffsety); this.context.lineTo(startX + barWidth + hmargin, y); this.context.closePath(); this.context.fill(); this.context.stroke(); } // Draw the side section this.context.beginPath(); this.context.moveTo(startX + barWidth + hmargin, y); this.context.lineTo(startX + barWidth + hmargin + properties.variantThreedOffsetx, y - properties.variantThreedOffsety); this.context.lineTo(startX + barWidth + hmargin + properties.variantThreedOffsetx, y - properties.variantThreedOffsety + height); this.context.lineTo(startX + barWidth + hmargin , y + height); this.context.closePath(); this.context.fill(); this.context.stroke(); // Draw the lighter top side if (j == 0) { this.context.fillStyle = 'rgba(255,255,255,0.5)'; this.context.beginPath(); this.context.moveTo(startX + hmargin, y); this.context.lineTo(startX + properties.variantThreedOffsetx + hmargin, y - properties.variantThreedOffsety); this.context.lineTo(startX + properties.variantThreedOffsetx + barWidth + hmargin, y - properties.variantThreedOffsety); this.context.lineTo(startX + barWidth + hmargin, y); this.context.closePath(); this.context.fill(); this.context.stroke(); } // Draw the darker side section this.context.fillStyle = 'rgba(0,0,0,0.4)'; this.context.beginPath(); this.context.moveTo(startX + barWidth + hmargin, y); this.context.lineTo(startX + barWidth + hmargin + properties.variantThreedOffsetx, y - properties.variantThreedOffsety); this.context.lineTo(startX + barWidth + hmargin + properties.variantThreedOffsetx, y - properties.variantThreedOffsety + height); this.context.lineTo(startX + barWidth + hmargin , y + height); this.context.closePath(); this.context.fill(); this.context.stroke(); this.context.strokeStyle = prevStrokeStyle; this.context.fillStyle = prevFillStyle; } y += height; } // // Redraw the bars if the shadow is enabled due to hem being drawn from the bottom up, and the // shadow spilling over to higher up bars // if (shadow) { RGraph.noShadow(this); for (k=0; k= 0) { startY -= height; } } if (properties.corners === 'round') { this.context.beginPath(); this.context.lineCap = 'miter'; this.context.lineJoin = 'square'; (this.data[i][j] < 0) ? this.roundedCornersRectNegative(startX + groupedMargin, startY, individualBarWidth - (2 * groupedMargin), height) : this.roundedCornersRect(startX + groupedMargin, startY, individualBarWidth - (2 * groupedMargin), height); this.context.stroke(); this.context.fill(); } else { this.context.beginPath(); this.context.lineJoin = 'miter'; this.context.lineCap = 'square'; this.context.rect(startX + groupedMargin, startY, individualBarWidth - (2 * groupedMargin), height); this.context.stroke(); this.context.fill(); } y += height; // // Grouped 3D effect // if (variant == '3d') { var prevFillStyle = this.context.fillStyle; var prevStrokeStyle = this.context.strokeStyle; var hmarginGrouped = properties.marginInnerGrouped; // Draw the top side if (this.data[i][j] >= 0) { this.context.beginPath(); this.context.moveTo(startX + hmarginGrouped, startY); this.context.lineTo(startX + hmarginGrouped + properties.variantThreedOffsetx, startY - properties.variantThreedOffsety); this.context.lineTo(startX + properties.variantThreedOffsetx + individualBarWidth - hmarginGrouped, startY - properties.variantThreedOffsety); this.context.lineTo(startX + individualBarWidth - hmarginGrouped, startY); this.context.closePath(); this.context.fill(); this.context.stroke(); } // Draw the side section this.context.beginPath(); this.context.moveTo( startX + individualBarWidth - hmarginGrouped - 1, startY ); this.context.lineTo( startX + individualBarWidth - hmarginGrouped + properties.variantThreedOffsetx, this.data[i][j] < 0 ? (this.getYCoord(0) + Math.abs(height) - properties.variantThreedOffsety) - (properties.xaxisPosition === 'center' ? 0 : Math.abs(height) - this.properties.variantThreedOffsety) : this.getYCoord(0) - height - properties.variantThreedOffsety ); this.context.lineTo( startX + individualBarWidth - hmarginGrouped + properties.variantThreedOffsetx, this.data[i][j] < 0 && (startY + height - properties.variantThreedOffsety) < this.getYCoord(0) ? (this.getYCoord(0)) : (startY + height - properties.variantThreedOffsety) ); this.context.lineTo(startX + individualBarWidth - hmarginGrouped - 1, startY + height); this.context.closePath(); this.context.fill(); this.context.stroke(); // Draw the lighter top side - but only if the current value is positive if (this.data[i][j] >= 0) { this.context.fillStyle = 'rgba(255,255,255,0.5)'; this.context.beginPath(); // BL this.context.moveTo(startX + hmarginGrouped, startY); // BR this.context.lineTo(startX + hmarginGrouped + properties.variantThreedOffsetx, startY - properties.variantThreedOffsety); // TR this.context.lineTo(startX + properties.variantThreedOffsetx + individualBarWidth - hmarginGrouped, startY - properties.variantThreedOffsety); // TL this.context.lineTo(startX + individualBarWidth - hmarginGrouped, startY); this.context.closePath(); this.context.fill(); this.context.stroke(); } // Draw the darker side section this.context.fillStyle = 'rgba(0,0,0,0.4)'; this.context.beginPath(); this.context.moveTo( startX + individualBarWidth - hmarginGrouped, startY ); this.context.lineTo( startX + individualBarWidth + properties.variantThreedOffsetx - hmarginGrouped, this.data[i][j] < 0 ? (this.getYCoord(0) + Math.abs(height) - properties.variantThreedOffsety) - (properties.xaxisPosition === 'center' ? 0 : Math.abs(height) - this.properties.variantThreedOffsety) : this.getYCoord(0) - height - properties.variantThreedOffsety ); this.context.lineTo( startX + individualBarWidth + properties.variantThreedOffsetx - hmarginGrouped, +this.data[i][j] < 0 && (startY + height - 5) < this.getYCoord(0) ? ((height > this.properties.variantThreedOffsety) ? this.getYCoord(0) + height - this.properties.variantThreedOffsety : this.getYCoord(0)) : (startY + height - properties.variantThreedOffsety) ); // TL corner this.context.lineTo(startX + individualBarWidth - hmarginGrouped, startY + height); this.context.closePath(); this.context.fill(); this.context.stroke(); this.context.strokeStyle = prevStrokeStyle; this.context.fillStyle = prevFillStyle; } if (height < 0) { height = Math.abs(height); startY = startY - height; } this.coords.push([startX + groupedMargin, startY, individualBarWidth - (2 * groupedMargin), height]); if (typeof this.coords2[i] == 'undefined') { this.coords2[i] = []; } this.coords2[i].push([startX + groupedMargin, startY, individualBarWidth - (2 * groupedMargin), height]); // Facilitate shadows going to the left if (properties.shadow) { redrawCoords.push([ startX + groupedMargin, startY, individualBarWidth - (2 * groupedMargin), height, this.context.fillStyle ]); } } // // Redraw the bar if shadows are going to the left // if (redrawCoords.length) { RGraph.noShadow(this); this.context.lineWidth = properties.linewidth; this.context.beginPath(); for (var j=0; j= left && mouseX <= (left + width) && mouseY >= top && mouseY <= (top + height)) { // Recreate the path/rectangle so that it can be tested // ** DO NOT STROKE OR FILL IT ** if (properties.tooltipsHotspotXonly) { this.path( 'b r % % % %', left, this.marginTop, width, this.canvas.height - this.marginBottom ); } else { var indexes = RGraph.sequentialIndexToGrouped(i, this.data); // Use the rounded rect function if the chart is stacked and the index is 0 if (properties.grouping === 'stacked' && properties.corners === 'round' && indexes[1] === 0) { this.context.beginPath(); this.roundedCornersRect(left, top, width, height); } else { this.path( 'b r % % % %', left, top, width, height ); } } if ( this.context.isPointInPath(mouseX, mouseY) && (this.properties.clip ? RGraph.clipTo.test(this, mouseX, mouseY) : true) ) { if (properties.tooltips && RGraph.parseTooltipText) { var tooltip = RGraph.parseTooltipText(properties.tooltips, i); } // Work out the dataset var dataset = 0, idx = i while (idx >= (typeof obj.data[dataset] === 'object' && obj.data[dataset] ? obj.data[dataset].length : 1)) { if (typeof obj.data[dataset] === 'number') { idx -= 1; } else if (obj.data[dataset]) { // Accounts for null being an object idx -= obj.data[dataset].length; } else { idx -= 1; } dataset++; } if (typeof obj.data[dataset] == 'number') { idx = 0; } return { object: this, x: left, y: top, width: width, height: height, tooltip: typeof tooltip === 'string' ? tooltip : null, label: properties.xaxisLabels && typeof properties.xaxisLabels[dataset] === 'string' ? properties.xaxisLabels[dataset] : null, dataset: dataset, index: idx, sequentialIndex: i }; } } return null; }; // // This retrives the bar based on the X coordinate only. // // @param object e The event object // @param object OPTIONAL You can pass in the bar object instead of the // function using "this" // this.getShapeByX = function (e) { var canvas = e.target; var mouseCoords = RGraph.getMouseXY(e); // This facilitates you being able to pass in the bar object as a parameter instead of // the function getting it from itself var obj = arguments[1] ? arguments[1] : this; // // Loop through the bars determining if the mouse is over a bar // for (var i=0,len=obj.coords.length; i= left && mouseX <= (left + width)) { if (properties.tooltips) { var tooltip = RGraph.parseTooltipText ? RGraph.parseTooltipText(properties.tooltips, i) : properties.tooltips[i]; } var indexes = RGraph.sequentialIndexToGrouped(i, this.data); return { object: obj, x: left, y: top, width: width, height: height, dataset: indexes[0], index: indexes[1], sequentialIndex: i, tooltip: typeof tooltip === 'string' ? tooltip : null }; } } return null; }; // // When you click on the chart, this method can return the Y value at that point. It works for any point on the // chart (that is inside the margins) - not just points within the Bars. // // EITHER: // // @param object arg The event object // // OR: // // @param object arg A two element array containing the X and Y coordinates // this.getValue = function (arg) { if (arg.length == 2) { var mouseX = arg[0]; var mouseY = arg[1]; } else { var mouseCoords = RGraph.getMouseXY(arg); var mouseX = mouseCoords[0]; var mouseY = mouseCoords[1]; } if ( mouseY < properties.marginTop || mouseY > (this.canvas.height - properties.marginBottom) || mouseX < properties.marginLeft || mouseX > (this.canvas.width - properties.marginRight) ) { return null; } if (properties.xaxisPosition == 'center') { var value = (((this.grapharea / 2) - (mouseY - properties.marginTop)) / this.grapharea) * (this.scale2.max - this.scale2.min) value *= 2; if (value >= 0) { value += this.scale2.min; } else { value -= this.scale2.min; } } else if (properties.xaxisPosition == 'top') { var value = ((this.grapharea - (mouseY - properties.marginTop)) / this.grapharea) * (this.scale2.max - this.scale2.min) value = this.scale2.max - value; value = Math.abs(value) * -1; } else { var value = ((this.grapharea - (mouseY - properties.marginTop)) / this.grapharea) * (this.scale2.max - this.scale2.min) value += this.scale2.min; } return value; }; // // This function can be used when the canvas is clicked on (or similar - depending on the event) // to retrieve the relevant Y coordinate for a particular value. // // @param int value The value to get the Y coordinate for // this.getYCoord = function (value) { if (value > this.scale2.max) { return null; } var y, xaxispos = properties.xaxisPosition; if (xaxispos == 'top') { // Account for negative numbers if (value < 0) { value = Math.abs(value); } y = ((value - this.scale2.min) / (this.scale2.max - this.scale2.min)) * this.grapharea; y = y + this.marginTop } else if (xaxispos == 'center') { y = ((value - this.scale2.min) / (this.scale2.max - this.scale2.min)) * (this.grapharea / 2); y = (this.grapharea / 2) - y; y += this.marginTop; } else { if (value < this.scale2.min) { value = this.scale2.min; } y = ((value - this.scale2.min) / (this.scale2.max - this.scale2.min)); y *= (this.canvas.height - this.marginTop - this.marginBottom); y = this.canvas.height - this.marginBottom - y; } return y; }; // // Each object type has its own Highlight() function which highlights the appropriate shape // // @param object shape The shape to highlight // this.highlight = function (shape) { if (typeof properties.highlightStyle === 'function') { (properties.highlightStyle)(shape); // Highlight all of the rects except this one - essentially an inverted highlight } else if (typeof properties.highlightStyle === 'string' && properties.highlightStyle === 'invert') { for (var i=0; i= properties.marginLeft && mouseXY[0] <= (this.canvas.width - properties.marginRight) && mouseXY[1] >= properties.marginTop && mouseXY[1] <= (this.canvas.height - properties.marginBottom) ) { return this; } }; // // This method handles the adjusting calculation for when the mouse is moved // // @param object e The event object // this.adjusting_mousemove = function (e) { // // Handle adjusting for the Bar // if (properties.adjustable && RGraph.Registry.get('adjusting') && RGraph.Registry.get('adjusting').uid == this.uid) { // Rounding the value to the given number of decimals make the chart step var value = Number(this.getValue(e)); var shape = RGraph.Registry.get('adjusting.shape') if (shape) { RGraph.Registry.set('adjusting.shape', shape); if (this.stackedOrGrouped && properties.grouping == 'grouped') { var indexes = RGraph.sequentialIndexToGrouped(shape.sequentialIndex, this.data); if (typeof this.data[indexes[0]] == 'number') { this.data[indexes[0]] = Number(value); } else if (!RGraph.isNull(this.data[indexes[0]])) { this.data[indexes[0]][indexes[1]] = Number(value); } } else if (typeof this.data[shape.sequentialIndex] == 'number') { this.data[shape.sequentialIndex] = Number(value); } RGraph.redrawCanvas(e.target); RGraph.fireCustomEvent(this, 'onadjust'); } } }; // // This allows for easy specification of gradients // this.parseColors = function () { // Save the original colors so that they can be restored when the canvas is reset if (this.original_colors.length === 0) { this.original_colors.colors = RGraph.arrayClone(properties.colors); this.original_colors.keyColors = RGraph.arrayClone(properties.keyColors); this.original_colors.crosshairsColor = properties.crosshairsColor; this.original_colors.highlightStroke = properties.highlightStroke; this.original_colors.highlightFill = properties.highlightFill; this.original_colors.textColor = properties.textColor; this.original_colors.backgroundBarsColor1 = properties.backgroundBarsColor1; this.original_colors.backgroundBarsColor2 = properties.backgroundBarsColor2; this.original_colors.backgroundGridColor = properties.backgroundGridColor; this.original_colors.backgroundColor = properties.backgroundColor; this.original_colors.colorsStroke = properties.colorsStroke; this.original_colors.axesColor = properties.axesColor; } // colors var colors = properties.colors; if (colors) { for (var i=0; i xaxis_ycoord)) { y = y - 100; h = h + 100; } else { y = y; h = h + 100; } this.context.beginPath(); this.context.rect(x - 3, y - 3, w + 6, h + 6); this.context.lineWidth = 5; this.context.stroke(); this.context.restore(); } } } }; // // This function handles highlighting an entire data-series for the interactive // key // // @param int index The index of the data series to be highlighted // this.interactiveKeyHighlight = function (index) { var obj = this; this.coords2.forEach(function (value, idx, arr) { if (typeof value[index] == 'object' && value[index]) { var x = value[index][0] - 0.5, y = value[index][1] - 0.5, w = value[index][2] + 1, h = value[index][3] + 1; obj.context.fillStyle = properties.keyInteractiveHighlightChartFill; obj.context.strokeStyle = properties.keyInteractiveHighlightChartStroke; obj.context.lineWidth = properties.keyInteractiveHighlightChartLinewidth; obj.context.strokeRect(x, y, w, h); obj.context.fillRect(x, y, w, h); } }); }; // // Using a function to add events makes it easier to facilitate method chaining // // @param string type The type of even to add // @param function func // this.on = function (type, func) { if (type.substr(0,2) !== 'on') { type = 'on' + type; } if (typeof this[type] !== 'function') { this[type] = func; } else { RGraph.addCustomEventListener(this, type, func); } return this; }; // Draws the above labels this.drawLabelsAbove = this.drawAboveLabels = function () { var labels = properties.labelsAbove, specific = properties.labelsAboveSpecific, bold = typeof properties.labelsAboveBold === 'boolean' ? properties.labelsAboveBold : properties.textBold, italic = typeof properties.labelsAboveItalic === 'boolean' ? properties.labelsAboveItalic : properties.textItalic, color = properties.labelsAboveColor || properties.textColor, font = properties.labelsAboveFont || properties.textFont, size = typeof properties.labelsAboveSize === 'number' ? properties.labelsAboveSize : properties.textSize, background = properties.labelsAboveBackground, decimals = properties.labelsAboveDecimals, angle = -1 * properties.labelsAboveAngle, unitsPre = properties.labelsAboveUnitsPre, unitsPost = properties.labelsAboveUnitsPost, point = properties.labelsAbovePoint, thousand = properties.labelsAboveThousand, formatter = properties.labelsAboveFormatter, coords = this.coords, coords2 = this.coords2, data = this.data, ldata = RGraph.arrayLinearize(this.data), offsetx = properties.labelsAboveOffsetx, offsety = properties.labelsAboveOffsety, text_italic = properties.textItalic, text_bold = properties.textBold, text_color = properties.textColor, text_font = properties.textFont, text_size = properties.textSize, grouping = properties.grouping; // BC if (typeof properties.labelsAboveOffset === 'number') { offsety = properties.labelsAboveOffset; } var textConf = RGraph.getTextConf({ object: this, prefix: 'labelsAbove' }); // Turn off any shadow RGraph.noShadow(this); // Color this.context.fillStyle = textConf.color; // This bit draws the text labels that appear above the bars if requested if (labels && grouping === 'grouped') { for (var i=0,len=data.length,sequentialIndex=0; i= 0) { var angle = angle; var halign = (angle ? 'left' : 'center'); var valign = angle !== 0 ? 'center' : 'bottom'; RGraph.text({ object: this, font: textConf.font, size: textConf.size, color: textConf.color, bold: textConf.bold, italic: textConf.italic, x: coords2[i][0][0] + (coords2[i][0][2] / 2) + offsetx, y: coords2[i][0][1] - offsety - 3, text: specific ? (specific[sequentialIndex] || '') : RGraph.numberFormat({ object: this, number: Number(typeof data[i] === 'object' ? data[i][0] : data[i]).toFixed(decimals), value: Number(typeof data[i] === 'object' ? data[i][0] : data[i]).toFixed(decimals), unitspre: unitsPre, unitspost: unitsPost, point: point, thousand: thousand, formatter: formatter, dataset: 0, index: i }), halign: halign, valign: valign, angle: angle, marker: false, bounding: true, 'bounding.fill': background, 'bounding.stroke': 'rgba(0,0,0,0)', tag: 'labels.above' }); sequentialIndex++; // Alignment for regular, negative bars } else if (typeof data[i] === 'number' && data[i] < 0) { var angle = angle; var halign = angle ? 'right' : 'center'; var valign = angle !== 0 ? 'center' : 'top'; RGraph.text({ object: this, font: textConf.font, size: textConf.size, color: textConf.color, bold: textConf.bold, italic: textConf.italic, x: coords2[i][0][0] + (coords2[i][0][2] / 2) + offsetx, y: coords2[i][0][1] + coords2[i][0][3] + offsety + 5, text: specific ? (specific[sequentialIndex] || '') : RGraph.numberFormat({ object: this, number: Number(typeof data[i] === 'object' ? data[i][0] : data[i]).toFixed(decimals), value: Number(typeof data[i] === 'object' ? data[i][0] : data[i]).toFixed(decimals), unitspre: unitsPre, unitspost: unitsPost, point: point, thousand: thousand, formatter: formatter, dataset: 0, index: i, }), halign: halign, valign: valign, angle: angle, bounding: true, 'bounding.fill':background, 'bounding.stroke':'rgba(0,0,0,0)', marker: false, tag: 'labels.above' }); sequentialIndex++; // Alignment for grouped bars } else if (typeof data[i] === 'object') { for (var j=0,len2=data[i].length; j acc += curr[3], 0) + 3 + offsety) : (-3 - offsety) ), text: specific ? (specific[sequentialIndex] || '') : RGraph.numberFormat({ object: this, number: Number(RGraph.arraySum(data[i])).toFixed(decimals), value: Number(RGraph.arraySum(data[i])).toFixed(decimals), unitspre: unitsPre, unitspost: unitsPost, point: point, thousand: thousand, formatter: formatter, dataset: i }), halign: halign, valign: properties.xaxisPosition === 'top' ? 'top' : valign, angle: angle, bounding: true, 'bounding.fill':background, 'bounding.stroke': 'rgba(0,0,0,0)', marker: false, tag: 'labels.above' }); sequentialIndex += data[i].length; // // Regular numbers but in a stacked grouping // } else { var angle = angle; var halign = angle != 0 ? 'left' : 'center'; var valign = angle != 0 ? 'center' : 'bottom'; RGraph.text({ object: this, font: textConf.font, size: textConf.size, color: textConf.color, bold: textConf.bold, italic: textConf.italic, x: coords2[i][0][0] + (coords2[i][0][2] / 2) + offsetx, y: coords2[i][0][1] + (data[i][0] < 0 ? coords2[i][0][3] : 0) + (properties.xaxisPosition === 'top' ? coords2[i][0][3] + offsety + 3 : 0 - offsety - 3), text: specific ? (specific[sequentialIndex] || '') : RGraph.numberFormat({ object: this, number: Number(data[i]).toFixed(decimals), value: Number(data[i]).toFixed(decimals), unitspre: unitsPre, unitspost: unitsPost, point: point, thousand: thousand, formatter: formatter, dataset: i }), halign: halign, valign: properties.xaxisPosition === 'top' ? 'top' : valign, angle: angle, bounding: true, 'bounding.fill': background, 'bounding.stroke': 'rgba(0,0,0,0)', marker: false, tag: 'labels.above' }); sequentialIndex++; } } } }; // // This function runs once only // this.firstDrawFunc = function () { }; // // (new) Bar chart Wave effect. This is a rewrite that should be smoother // because it just uses a single loop and not setTimeout // // @param object OPTIONAL An object map of options. You specify 'frames' here to give the number of frames in the effect // @param function OPTIONAL A function that will be called when the effect is complete // this.wave = function () { // Cancel any stop request if one is pending this.cancelStopAnimation(); // Reset the data to the original this.data = RGraph.arrayClone(this.original_data); // If there's only one bar call the grow function instead if (this.data.length === 1) { return this.grow(arguments[0], arguments[1]); } var obj = this, opt = arguments[0] || {}, labelsAbove = this.get('labelsAbove'); opt.frames = opt.frames || 60; opt.startFrames = []; opt.counters = []; var framesperbar = opt.frames / 3, frame = -1, callback = arguments[1] || function () {}, original = RGraph.arrayClone(this.original_data); // // turn off the labelsAbove option whilst animating // this.set('labelsAbove', false); for (var i=0,len=obj.data.length; i opt.startFrames[i]) { if (typeof obj.data[i] === 'number') { obj.data[i] = Math.min( Math.abs(original[i]), Math.abs(original[i] * ( (opt.counters[i]++) / framesperbar)) ); // Make the number negative if the original was if (original[i] < 0) { obj.data[i] *= -1; } } else if (!RGraph.isNull(obj.data[i])) { for (let j=0,len2=obj.data[i].length; j= opt.frames) { if (labelsAbove) { obj.set('labelsAbove', true); RGraph.redraw(); } callback(obj); } else { RGraph.redrawCanvas(obj.canvas); RGraph.Effects.updateCanvas(iterator); } } iterator(); return this; }; // // Color Wave effect. This fades in color sequentially like the wave effect // makes the bars grow. // // @param object OPTIONAL An object map of options. You specify 'frames' // here to give the number of frames in the effect // @param function OPTIONAL A function that will be called when the effect // is complete // this.colorwave = this.colorWave = function () { // Cancel any stop request if one is pending this.cancelStopAnimation(); // Reset the data to the original this.data = RGraph.arrayClone(this.original_data); var obj = this, opt = arguments[0] || {}; opt.frames = opt.frames || 60; opt.startFrames = []; opt.counters = []; colors = obj.properties.colors; // If just one color is specified and colorsSequential is not, then // pad the colors array out if (colors.length <= obj.data.length) { obj.set('colorsSequential', true); colors = RGraph.arrayPad(colors, obj.data.length, colors[colors.length - 1]); } var framesperbar = opt.frames / 2, frame = -1, callback = arguments[1] || function () {}, originalColors = RGraph.arrayClone(obj.properties.colors); for (var i=0,len=originalColors.length; i opt.startFrames[i] && colors[i].match(/^rgba?\(([0-9 ]+),([0-9 ]+),([0-9 ]+)(,([ 0-9.]+)?)\)/)) { // DO NOT USE SPACES! colors[i] = 'rgba({1},{2},{3},{4})'.format( RegExp.$1, RegExp.$2, RegExp.$3, (frame - opt.startFrames[i]) / framesperbar ); } else { colors[i] = colors[i].replace(/,[0-9. ]+\)/, ',0)'); } } if (frame >= opt.frames) { callback(obj); } else { RGraph.redrawCanvas(obj.canvas); RGraph.Effects.updateCanvas(iterator); } } iterator(); return this; }; // // Grow // // The Bar chart Grow effect gradually increases the values of the bars // // @param object An object of options - eg: {frames: 30} // @param function A function to call when the effect is complete // this.grow = function () { // Cancel any stop request if one is pending this.cancelStopAnimation(); // Reset the data to the original this.data = RGraph.arrayClone(this.original_data); // Callback var opt = arguments[0] || {}, frames = opt.frames || 30, frame = 0, callback = arguments[1] || function () {}, obj = this, labelsAbove = this.get('labelsAbove'); // Go through the data and change string arguments of the format +/-[0-9] // to absolute numbers if (RGraph.isArray(opt.data)) { var ymax = 0; for (var i=0; i width) { // Calculate the left and right radiuses and assign // to temporary variables var a = width / (radiusLeft + radiusRight) * radiusLeft; var b = width / (radiusLeft + radiusRight) * radiusRight; // Reassign the values to the correct variables radiusLeft = a; radiusRight = b; } // Save the existing state of the canvas so that it can be restored later this.context.save(); // Translate to the given X/Y coordinates this.context.translate(x, y); // Move to the center of the top horizontal line this.context.moveTo(width / 2,0); // Draw the rounded corners. The connecting lines in between them are drawn automatically this.context.arcTo(width,0,width,height,Math.min(height / 2, properties.cornersRoundRight ? radiusRight : 0)); this.context.arcTo(width, height, 0, height, 0); this.context.arcTo(0, height, 0, 0, 0); this.context.arcTo(0, 0, radiusLeft, 0, Math.min(height / 2, this.properties.cornersRoundLeft ? radiusLeft : 0)); // Draw a line back to the start coordinates this.context.lineTo(width / 2,0); // Restore the state of the canvas to as it was before the save() this.context.restore(); }; // // This adds a roundedRectNegative(x, y, width, height, radius) function to the drawing context. // The radius argument dictates by how much the corners are rounded. // This function handles negative bars whereas the above // function handles positive ones. // // @param number x The X coordinate // @param number y The Y coordinate // @param number width The width of the rectangle // @param number height The height of the rectangle // @param number radius The radius of the corners. Bigger values mean more rounded corners // this.roundedCornersRectNegative = function (x, y, width, height) { if (height < 0) { height = Math.abs(height); y -= height; } var radiusLeft = null; var radiusRight = null; // LHS radius if (RGraph.isNumber(properties.cornersRoundLeftRadius)) { radiusLeft = properties.cornersRoundLeftRadius; } else { radiusLeft = Math.min(width / 2, height / 2, properties.cornersRoundRadius);; } // RHS radius if (RGraph.isNumber(properties.cornersRoundRightRadius)) { radiusRight = properties.cornersRoundRightRadius; } else { radiusRight = Math.min(width / 2, height / 2, properties.cornersRoundRadius);; } if ( (radiusLeft + radiusRight) > width) { // Calculate the left and right radiuses and assign // to temporary variables var a = width / (radiusLeft + radiusRight) * radiusLeft; var b = width / (radiusLeft + radiusRight) * radiusRight; // Reassign the values to the correct variables radiusLeft = a; radiusRight = b; } // Save the existing state of the canvas so that it can be restored later this.context.save(); // Translate to the given X/Y coordinates this.context.translate(x, y); // Move to the center of the top horizontal line this.context.moveTo(width / 2,0); // Draw the rounded corners. The connecting lines in // between them are drawn automatically this.context.arcTo(width,0,width,height, 0); this.context.arcTo(width, height, 0, height, Math.min(height / 2, properties.cornersRoundRight ? radiusRight : 0)); this.context.arcTo(0, height, 0, 0, Math.min(height / 2, properties.cornersRoundLeft ? radiusLeft : 0)); this.context.arcTo(0, 0, width, 0, 0); // Draw a line back to the start coordinates this.context.lineTo(width / 2,0); // Restore the state of the canvas to as it was before the save() this.context.restore(); }; // // This function is NOT currently used - it installs an // appropriate clipping region for the lower half of 3D // Bar charts when the X axis is in the middle. It's // used like this: // // this.context.save(); // this.context.install3DAxisClip(); // ... // this.context.restore(); // this.install3DAxisNegativeClip = function () { this.path( 'b m % % l % % l % % l % % l % % l % % c cl', this.marginLeft, this.getYCoord(0), this.marginLeft, this.canvas.height - this.marginBottom, this.canvas.width - this.marginRight, this.canvas.height - this.marginBottom, this.canvas.width - this.marginRight + this.properties.variantThreedOffsetx, this.canvas.height - this.marginBottom - this.properties.variantThreedOffsety, this.canvas.width - this.marginRight + this.properties.variantThreedOffsetx, this.getYCoord(0) - this.properties.variantThreedOffsety, this.canvas.width - this.marginRight, this.getYCoord(0) ); }; // // This returns the relevant value for the formatted key // macro %{value}. THIS VALUE SHOULD NOT BE FORMATTED. // // @param number index The index in the dataset to get // the value for // this.getKeyValue = function (index) { if (RGraph.isArray(this.properties.keyFormattedValueSpecific) && RGraph.isNumber(this.properties.keyFormattedValueSpecific[index])) { return this.properties.keyFormattedValueSpecific[index]; } else { var total = 0; for (let i=0; i