// 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 line chart constructor // RGraph.Gauge = function (conf) { var id = conf.id, canvas = document.getElementById(id), min = conf.min, max = conf.max, value = conf.value; 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.type = 'gauge'; this.min = RGraph.stringsToNumbers(min); this.max = RGraph.stringsToNumbers(max); this.value = RGraph.stringsToNumbers(value); this.isRGraph = true; this.isrgraph = true; this.rgraph = true; this.currentValue = null; this.uid = RGraph.createUID(); this.canvas.uid = this.canvas.uid ? this.canvas.uid : RGraph.createUID(); this.colorsParsed = false; this.coordsText = []; this.original_colors = []; this.firstDraw = true; // After the first draw this will be false this.stopAnimationRequested = false;// Used to control the animations // // Range checking. // // As of 4.63, it now saves the original value // this.valueOriginal = this.value; if (typeof this.value === 'object') { for (var i=0; i this.max) this.value[i] = max; if (this.value[i] < this.min) this.value[i] = min; } } else { if (this.value > this.max) this.value = max; if (this.value < this.min) this.value = min; } // Coniguration properties this.properties = { anglesStart: null, anglesEnd: null, centerx: null, centery: null, radius: null, marginLeft: 35, marginRight: 35, marginTop: 35, marginBottom: 35, borderWidth: 10, textFont: 'Arial, Verdana, sans-serif', textSize: 12, textColor: '#666', textBold: false, textItalic: false, textAccessible: false, textAccessibleOverflow: 'visible', textAccessiblePointerevents: false, text: null, titleTop: '', titleTopFont: null, titleTopSize: 16, titleTopColor: 'black', titleTopBold: true, titleTopItalic: null, titleTopPos: null, titleTopOffsetx: 0, titleTopOffsety: 0, titleTopSubtitle: null, titleTopSubtitleFont: null, titleTopSubtitleSize: null, titleTopSubtitleColor: '#666', titleTopSubtitleBold: null, titleTopSubtitleItalic: null, titleTopSubtitlePos: null, titleTopSubtitleOffsetx: 0, titleTopSubtitleOffsety: 0, titleBottom: '', titleBottomFont: null, titleBottomSize: null, titleBottomColor: null, titleBottomBold: null, titleBottomItalic: null, titleBottomPos: null, titleBottomOffsetx: 0, titleBottomOffsety: 0, backgroundColor: 'white', backgroundGradient: false, scaleDecimals: 0, scalePoint: '.', scaleThousand: ',', scaleUnitsPre: '', scaleUnitsPost: '', scalePoint: '.', scaleThousand: ',', labelsCount: 5, labelsCentered: false, labelsOffsetRadius: 0, labelsOffsetAngle: 0, labelsSpecific: null, labelsSpecificFormattedDecimals: 0, labelsSpecificFormattedPoint: '.', labelsSpecificFormattedThousand: ',', labelsSpecificFormattedUnitsPre: '', labelsSpecificFormattedUnitsPost: '', labelsOffsetx: 0, labelsOffsety: 0, labelsFont: null, labelsSize: null, labelsColor: null, labelsBold: null, labelsItalic: null, labelsValue: false, labelsValueYPos: 0.5, labelsValueUnitsPre: '', labelsValueUnitsPost: '', labelsValueBounding: true, labelsValueBoundingFill: 'white', labelsValueBoundingStroke: 'black', labelsValueFont: null, labelsValueSize: null, labelsValueColor: null, labelsValueItalic: null, labelsValueBold: null, labelsValueDecimals: null, labelsValuePoint: null, labelsValueThousand: null, colorsRedStart: 0.9 * this.max, colorsRedColor: '#DC3912', colorsRedWidth: 10, colorsYellowColor: '#FF9900', colorsYellowWidth: 10, colorsGreenEnd: 0.7 * this.max, colorsGreenColor: 'rgba(0,0,0,0)', colorsGreenWidth: 10, colorsRanges: null, needleSize: null, needleTail: false, needleColors: ['#D5604D', 'red', 'green', 'yellow'], needleType: 'triangle', needleWidth: 7, borderOuter: '#ccc', borderInner: '#f1f1f1', borderOutline: 'black', borderGradient: false, centerpinColor: 'blue', centerpinRadius: null, tickmarksSmall: 25, tickmarksSmallColor: 'black', tickmarksMedium: 0, tickmarksMediumColor: 'black', tickmarksLarge: 5, tickmarksLargeColor: 'black', adjustable: false, annotatable: false, annotatableLinewidth: 1, annotatableColor: 'black', shadow: true, shadowColor: 'gray', shadowOffsetx: 0, shadowOffsety: 0, shadowBlur: 15, 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; } } // Easy access to properties and the path function var properties = this.properties; this.path = RGraph.pathObjectFunction; // // "Decorate" the object with the generic effects if the effects library has been included // if (RGraph.Effects && typeof RGraph.Effects.decorate === 'function') { RGraph.Effects.decorate(this); } // Add the responsive method. This method resides in the common file. this.responsive = RGraph.responsive; // // An all encompassing accessor // // @param string name The name of the property // @param mixed value The value of the property // this.set = function (name) { var value = typeof arguments[1] === 'undefined' ? null : arguments[1]; // Go through all of the properties and make sure // that they're using the correct capitalisation if (typeof name === 'string') { name = this.properties_lowercase_map[name.toLowerCase()] || name; } // Set the colorsParsed flag to false if the colors // property is being set if ( name === 'colors' || name === 'backgroundColor' || name === 'colorsRedColor' || name === 'colorsYellowColor' || name === 'colorsGreenColor' || name === 'borderInner' || name === 'borderOuter' || name === 'colorsRanges' || name === 'needleColors' ) { this.colorsParsed = false; } // the number of arguments is only one and it's an // object - parse it for configuration data and return. if (arguments.length === 1 && typeof arguments[0] === 'object') { for (i in arguments[0]) { if (typeof i === 'string') { this.set(i, arguments[0][i]); } } return this; } properties[name] = value; return this; }; // // An all encompassing accessor // // @param string name The name of the property // this.get = function (name) { // Go through all of the properties and make sure // that they're using the correct capitalisation name = this.properties_lowercase_map[name.toLowerCase()] || name; return properties[name]; }; // // The function you call to draw the line chart // // @param bool An optional bool used internally to ditinguish whether the // line chart is being called by the bar chart // this.draw = function () { // // Fire the onbeforedraw event // RGraph.fireCustomEvent(this, 'onbeforedraw'); // Translate half a pixel for antialiasing purposes - but only if it hasn't been // done already // // MUST be the first thing done! // if (!this.canvas.__rgraph_aa_translated__) { this.context.translate(0.5,0.5); this.canvas.__rgraph_aa_translated__ = true; } // // Store the value (for animation primarily // this.currentValue = this.value; // // Make the margins easy to access // this.marginLeft = properties.marginLeft; this.marginRight = properties.marginRight; this.marginTop = properties.marginTop; this.marginBottom = properties.marginBottom; this.centerx = ((this.canvas.width - this.marginLeft - this.marginRight) / 2) + this.marginLeft; this.centery = ((this.canvas.height - this.marginTop - this.marginBottom) / 2) + this.marginTop; this.radius = Math.min( ((this.canvas.width - this.marginLeft - this.marginRight) / 2), ((this.canvas.height - this.marginTop - this.marginBottom) / 2) ); this.startAngle = properties.anglesStart ? properties.anglesStart : (RGraph.HALFPI / 3) + RGraph.HALFPI; this.endAngle = properties.anglesEnd ? properties.anglesEnd : RGraph.TWOPI + RGraph.HALFPI - (RGraph.HALFPI / 3); // // Reset this so it doesn't keep growing // this.coordsText = []; // // You can now override the positioning and radius if you so wish. // if (typeof properties.centerx === 'number') this.centerx = properties.centerx; if (typeof properties.centery === 'number') this.centery = properties.centery; if (typeof properties.radius === 'number') this.radius = properties.radius; // // Allow the centerx/centery/radius to be a plus/minus // if (typeof properties.radius === 'string' && properties.radius.match(/^\+|-\d+$/) ) this.radius += parseFloat(properties.radius); if (typeof properties.centerx === 'string' && properties.centerx.match(/^\+|-\d+$/) ) this.centerx += parseFloat(properties.centerx); if (typeof properties.centery === 'string' && properties.centery.match(/^\+|-\d+$/) ) this.centery += parseFloat(properties.centery); // // Parse the colors. This allows for simple gradient syntax // if (!this.colorsParsed) { this.parseColors(); // Don't want to do this again this.colorsParsed = true; } // This has to be in the constructor this.centerpinRadius = 0.16 * this.radius; if (typeof properties.centerpinRadius == 'number') { this.centerpinRadius = properties.centerpinRadius; } // // Install clipping // // MUST be the first thing that's done after the // beforedraw event // if (!RGraph.isNull(this.properties.clip)) { RGraph.clipTo.start(this, this.properties.clip); } // // Setup the context menu if required // if (properties.contextmenu) { RGraph.showContext(this); } // DRAW THE CHART HERE this.drawBackGround(); this.drawGradient(); this.drawColorBands(); this.drawSmallTickmarks(); this.drawMediumTickmarks(); this.drawLargeTickmarks(); this.drawLabels(); this.drawTopTitle(); this.drawBottomTitle(); if (typeof this.value == 'object') { for (var i=0; i this.startAngle && a< this.endAngle) { this.context.arc(this.centerx, this.centery, this.radius - properties.borderWidth - 10, a, a + 0.00001, 0); this.context.arc(this.centerx, this.centery, this.radius - properties.borderWidth - 10 - 6, a, a + 0.00001, 0); } this.context.stroke(); } } }; // // This function draws the large, bold tickmarks // this.drawLargeTickmarks = function () { var numTicks = properties.tickmarksLarge; this.context.lineWidth = 3; this.context.lineCap = 'round'; for (var i=0; i<=numTicks; ++i) { this.context.beginPath(); this.context.strokeStyle = properties.tickmarksLargeColor; var a = (((this.endAngle - this.startAngle) / numTicks) * i) + this.startAngle; this.context.arc(this.centerx, this.centery, this.radius - properties.borderWidth - 10, a, a + 0.00001, 0); this.context.arc(this.centerx, this.centery, this.radius - properties.borderWidth - 10 - 10, a, a + 0.00001, 0); this.context.stroke(); } }; // // This function draws the centerpin // this.drawCenterpin = function () { var offset = 6; var grad = this.context.createRadialGradient(this.centerx + offset, this.centery - offset, 0, this.centerx + offset, this.centery - offset, 25); grad.addColorStop(0, '#ddf'); grad.addColorStop(1, properties.centerpinColor); this.context.beginPath(); this.context.fillStyle = grad; this.context.arc(this.centerx, this.centery, this.centerpinRadius, 0, RGraph.TWOPI, 0); this.context.fill(); }; // // This function draws the labels // this.drawLabels = function () { this.context.fillStyle = properties.textColor; var font = properties.textFont, size = properties.textSize, num = properties.labelsSpecific ? (properties.labelsSpecific.length - 1) : properties.labelsCount, offsetx = properties.labelsOffsetx, offsety = properties.labelsOffsety, offseta = properties.labelsOffsetAngle; var textConf = RGraph.getTextConf({ object: this, prefix: 'labels' }); // // String based labels // if (typeof properties.labelsSpecific === 'string') { // Reset the number of labels to show num = properties.labelsCount - 1; if (properties.labelsSpecific && properties.labelsSpecific.length) { // // If the labelsSpecific option is a string then turn it // into an array. if (typeof properties.labelsSpecific === 'string' ) { properties.labelsSpecific = RGraph.arrayPad({ array: [], length: properties.labelsCount, value: properties.labelsSpecific }); } // // Label substitution // for (var i=0; i this.centerx ? 'right' : 'left'; var vAlign = y > this.centery ? 'bottom' : 'top'; // This handles the label alignment when the label is on a PI/HALFPI boundary if (a == RGraph.HALFPI) { vAlign = 'center'; } else if (a == RGraph.PI) { hAlign = 'center'; } else if (a == (RGraph.HALFPI + RGraph.PI) ) { vAlign = 'center'; } // // Can now force center alignment // if (properties.labelsCentered) { hAlign = 'center'; vAlign = 'center'; } var value = (((this.max - this.min) * (i / num)) + this.min); RGraph.text({ object: this, font: textConf.font, size: textConf.size, color: textConf.color, bold: textConf.bold, italic: textConf.italic, x: x + offsetx, y: y + offsety, text: properties.labelsSpecific ? properties.labelsSpecific[i] : RGraph.numberFormat({ object: this, number: value.toFixed(properties.scaleDecimals), unitspre: properties.scaleUnitsPre, unitspost: properties.scaleUnitsPost, point: properties.scalePoint, thousand: properties.scaleThousand }), halign: hAlign, valign: vAlign, tag: properties.labelsSpecific ? 'labels.specific' : 'labels', cssClass: properties.labelsSpecific ? RGraph.getLabelsCSSClassName({ object: this, name: 'labelsClass', index: i }) : '' }); } } this.context.fill(); // // Draw the textual value if requested // if (properties.labelsValue) { var x = this.centerx, y = this.centery + (properties.labelsValueYPos * this.radius), units_pre = typeof properties.labelsValueUnitsPre == 'string' ? properties.labelsValueUnitsPre : properties.scaleUnitsPre, units_post = typeof properties.labelsValueUnitsPost == 'string' ? properties.labelsValueUnitsPost : properties.scaleUnitsPost, bounding = properties.labelsValueBounding, boundingFill = properties.labelsValueBoundingFill, boundingStroke = properties.labelsValueBoundingStroke, decimals = typeof properties.labelsValueDecimals === 'number' ? properties.labelsValueDecimals : properties.scaleDecimals, point = typeof properties.labelsValuePoint === 'string' ? properties.labelsValuePoint : properties.scalePoint, thousand = typeof properties.labelsValueThousand === 'string' ? properties.labelsValueThousand : properties.scaleThousand; var textConf = RGraph.getTextConf({ object: this, prefix: 'labelsValue' }); if (typeof this.value === 'number') { var value = parseFloat(properties.valueTextActual ? this.valueOriginal : this.value); var text = RGraph.numberFormat({ object: this, number: value.toFixed(decimals), unitspre: units_pre, unitspost: units_post, point: point, thousand: thousand }); } else { var text = []; for (var i=0; i= 0 && angle <= RGraph.HALFPI) { angle += RGraph.TWOPI; } var value = ((angle - this.startAngle) / (this.endAngle - this.startAngle)) * (this.max - this.min); value = value + this.min; if (value < this.min) { value = this.min } if (value > this.max) { value = this.max } return value; }; // // The getObjectByXY() worker method. Don't call this call: // // RGraph.ObjectRegistry.getObjectByXY(e) // // @param object e The event object // this.getObjectByXY = function (e) { var mouseXY = RGraph.getMouseXY(e); if ( mouseXY[0] > (this.centerx - this.radius) && mouseXY[0] < (this.centerx + this.radius) && mouseXY[1] > (this.centery - this.radius) && mouseXY[1] < (this.centery + this.radius) && RGraph.getHypLength(this.centerx, this.centery, mouseXY[0], mouseXY[1]) <= this.radius ) { return this; } }; // // This draws the gradient that goes around the Gauge chart // this.drawGradient = function () { if (properties.borderGradient) { this.context.beginPath(); var grad = this.context.createRadialGradient(this.centerx, this.centery, this.radius, this.centerx, this.centery, this.radius - 15); grad.addColorStop(0, 'gray'); grad.addColorStop(1, 'white'); this.context.fillStyle = grad; this.context.arc(this.centerx, this.centery, this.radius, 0, RGraph.TWOPI, false) this.context.arc(this.centerx, this.centery, this.radius - 15, RGraph.TWOPI,0, true) this.context.fill(); } }; // // 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) { this.value = this.getValue(e); //RGraph.clear(this.canvas); RGraph.redrawCanvas(this.canvas); RGraph.fireCustomEvent(this, 'onadjust'); } }; // // This method returns an appropriate angle for the given value (in RADIANS) // // @param number value The value to get the angle for // this.getAngle = function (value) { // Higher than max if (value > this.max || value < this.min) { return null; } //var value = ((angle - this.startAngle) / (this.endAngle - this.startAngle)) * (this.max - this.min); //value = value + this.min; var angle = (((value - this.min) / (this.max - this.min)) * (this.endAngle - this.startAngle)) + this.startAngle; return angle; }; // // This allows for easy specification of gradients. Could optimise this not to repeatedly call parseSingleColors() // 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.backgroundColor = RGraph.arrayClone(properties.backgroundColor); this.original_colors.colorsRedColor = RGraph.arrayClone(properties.colorsRedColor); this.original_colors.colorsYellowColor = RGraph.arrayClone(properties.colorsYellowColor); this.original_colors.colorsGreenColor = RGraph.arrayClone(properties.colorsGreenColor); this.original_colors.borderInner = RGraph.arrayClone(properties.borderInner); this.original_colors.borderOuter = RGraph.arrayClone(properties.borderOuter); this.original_colors.colorsRanges = RGraph.arrayClone(properties.colorsRanges); this.original_colors.needleColors = RGraph.arrayClone(properties.needleColors); } properties.backgroundColor = this.parseSingleColorForGradient(properties.backgroundColor); properties.colorsRedColor = this.parseSingleColorForGradient(properties.colorsRedColor); properties.colorsYellowColor = this.parseSingleColorForGradient(properties.colorsYellowColor); properties.colorsGreenColor = this.parseSingleColorForGradient(properties.colorsGreenColor); properties.borderInner = this.parseSingleColorForGradient(properties.borderInner); properties.borderOuter = this.parseSingleColorForGradient(properties.borderOuter); // Parse the colorRanges value if (properties.colorsRanges) { var ranges = properties.colorsRanges; for (var i=0; i obj.max) obj.value = obj.max; if (obj.value < obj.min) obj.value = obj.min; //RGraph.clear(obj.canvas); RGraph.redrawCanvas(obj.canvas); if (frame++ < frames) { RGraph.Effects.updateCanvas(iterator); } else { callback(obj); } }; iterator(); // // Multiple pointers // } else { if (this.currentValue == null) { this.currentValue = []; for (var i=0; i obj.max) obj.value[i] = obj.max; if (obj.value[i] < obj.min) obj.value[i] = obj.min; } //RGraph.clear(obj.canvas); RGraph.redrawCanvas(obj.canvas); if (frame < frames) { RGraph.Effects.updateCanvas(iterator); } else { callback(obj); } }; 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; }; // // This function handles clipping to scale values. Because // each chart handles scales differently, a worker function // is needed instead of it all being done centrally in the // RGraph.clipTo.start() function. // // @param string clip The clip string as supplied by the // user in the chart configuration // this.clipToScaleWorker = function (clip) { // The Regular expression is actually done by the // calling RGraph.clipTo.start() function in the core // library if (RegExp.$1 === 'min') from = this.min; else from = Number(RegExp.$1); if (RegExp.$2 === 'max') to = this.max; else to = Number(RegExp.$2); var r1 = this.getAngle(from), r2 = this.getAngle(to); // Change the angle if the number is "max" if (RegExp.$2 === 'max') { r2 = RGraph.TWOPI + RGraph.HALFPI; } // Change the angle if the number is "min" if (RegExp.$1 === 'min') { r1 = RGraph.HALFPI; } this.path( 'sa b m % % a % % % % % false c cl', this.centerx, this.centery, this.centerx, this.centery, Math.max(this.canvas.width, this.canvas.height), r1, r2 ); }; // // Register the object // RGraph.register(this); // // This is the 'end' of the constructor so if the first argument // contains configuration data - handle that. // RGraph.parseObjectStyleConfig(this, conf.options); };