// 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 odometer constructor. Pass it the ID of the canvas tag, the start value of the odo, // the end value, and the value that the pointer should point to. // RGraph.Odometer = 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 = 'odo'; this.isRGraph = true; this.isrgraph = true; this.rgraph = true; this.min = RGraph.stringsToNumbers(min); this.max = RGraph.stringsToNumbers(max); this.value = RGraph.stringsToNumbers(value); 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 this.properties = { backgroundBorder: 'black', backgroundColor: '#eee', backgroundLinesColor: '#ddd', centerx: null, centery: null, radius: null, labelsMargin: 35, labelsFont: null, labelsSize: null, labelsColor: null, labelsBold: null, labelsItalic: null, labelsValue: false, labelsValueDecimals: 0, labelsValuePoint: '.', labelsValueThousand: ',', labelsValueUnitsPre: '', labelsValueUnitsPost: '', labelsValueFont: null, labelsValueSize: null, labelsValueColor: null, labelsValueBold: null, labelsValueItalic: null, labelsValueOffsetx: 0, labelsValueOffsety: 0, needleColor: 'black', needleLength: null, needleWidth: 2, needleHead: true, needleTail: true, needleType: 'pointer', needleTriangleBorder: '#aaa', textSize: 12, textColor: 'black', textFont: 'Arial, Verdana, sans-serif', textBold: false, textItalic: false, textAccessible: false, textAccessibleOverflow: 'visible', textAccessiblePointerevents: false, text: null, colorsGreenMax: max * 0.75, colorsGreenColor: 'Gradient(white:#0c0)', colorsYellowColor: 'Gradient(white:#ff0)', colorsRedMin: max * 0.9, colorsRedColor: 'Gradient(white:#f00)', marginLeft: 35, marginRight: 35, marginTop: 35, marginBottom: 35, title: '', titleFont: null, titleBold: null, titleItalic: 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, contextmenu: null, linewidth: 1, shadowInner: false, shadowInnerColor: 'black', shadowInnerOffsetx: 3, shadowInnerOffsety: 3, shadowInnerBlur: 6, shadowOuter: false, shadowOuterColor: 'black', shadowOuterOffsetx: 3, shadowOuterOffsety: 3, shadowOuterBlur: 6, annotatable: false, annotatableColor: 'black', scaleDecimals: 0, scalePoint: '.', scaleThousand: ',', scaleUnitsPre: '', scaleUnitsPost: '', scaleZerostart: false, resizable: false, resizableHandleAdjust: [0,0], resizableHandleBackground: null, border: true, borderColor1: '#BEBCB0', borderColor2: '#F0EFEA', borderColor3: '#BEBCB0', tickmarks: false, tickmarksHighlighted: true, tickmarksLargeColor: '#999', labels: null, key: null, keyBackground: 'white', keyPosition: 'graph', keyShadow: false, keyShadowColor: '#666', keyShadowBlur: 3, keyShadowOffsetx: 2, keyShadowOffsety: 2, keyPositionMarginBoxed: false, keyPositionMarginHSpace: 0, keyPositionX: null, keyPositionY: null, keyHalign: 'right', keyColorShape: 'square', keyRounded: true, keyColors: null, keyLabelsSize: null, keyLabelsFont: null, keyLabelsColor: null, keyLabelsBold: null, keyLabelsItalic: null, keyLabelsOffsetx: 0, keyLabelsOffsety: 0, keyFormattedDecimals: 0, keyFormattedPoint: '.', keyFormattedThousand: ',', keyFormattedUnitsPre: '', keyFormattedUnitsPost: '', keyFormattedValueSpecific: null, keyFormattedItemsCount: null, adjustable: false, 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; // // A peudo setter // 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 === 'colorsRedColor' || name === 'colorsYellowColor' || name === 'colorsGreenColor' ) { this.colorsParsed = false; } // the number of arguments is only one and it's an // object - parse it for configuration data and return. if (arguments.length === 1 && typeof arguments[0] === 'object') { for (i in arguments[0]) { if (typeof i === 'string') { this.set(i, arguments[0][i]); } } return this; } properties[name] = value; return this; }; // // A getter // // @param name string The name of the property to get // this.get = function (name) { // Go through all of the properties and make sure // that they're using the correct capitalisation name = this.properties_lowercase_map[name.toLowerCase()] || name; return properties[name]; }; // // Draws the odometer // 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; } // // Set the current value // this.currentValue = this.value; // // No longer allow values outside the range of the Odo // if (this.value > this.max) { this.value = this.max; } if (this.value < this.min) { this.value = this.min; } // // Make the margins easy ro access // this.marginLeft = properties.marginLeft; this.marginRight = properties.marginRight; this.marginTop = properties.marginTop; this.marginBottom = properties.marginBottom; // Work out a few things this.radius = Math.min( (this.canvas.width - this.marginLeft - this.marginRight) / 2, (this.canvas.height - this.marginTop - this.marginBottom) / 2 ) - (properties.border ? 25 : 0); this.diameter = 2 * this.radius; 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.range = this.max - this.min; this.coordsText = []; // // Move the centerx if the key is defined // if (properties.key && properties.key.length > 0 && this.canvas.width > this.canvas.height) this.centerx = 5 + this.radius; if (typeof properties.centerx === 'number') this.centerx = properties.centerx; if (typeof properties.centery === 'number') this.centery = properties.centery; // // 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); // // Allow custom setting of the radius // if (typeof properties.radius === 'number') { this.radius = properties.radius; if (properties.border) { this.radius -= 25; } } // // Parse the colors for gradients. Its down here so that the center X/Y can be used // if (!this.colorsParsed) { this.parseColors(); // Don't want to do this again this.colorsParsed = true; } // // Install clipping // if (!RGraph.isNullish(this.properties.clip)) { RGraph.clipTo.start(this, this.properties.clip); } this.context.lineWidth = properties.linewidth; // Draw the background this.drawBackground(); // And lastly, draw the labels this.drawLabels(); // Draw the needle if (RGraph.isArray(this.value)) { for (let i=0; i<this.value.length; ++i) { this.drawNeedle({ value: this.value[i], color: RGraph.isArray(properties.needleColor) ? properties.needleColor[i] : properties.needleColor, length: RGraph.isArray(properties.needleLength) ? properties.needleLength[i] : properties.needleLength, type: RGraph.isArray(properties.needleType) ? properties.needleType[i] : properties.needleType, linewidth: RGraph.isArray(properties.needleWidth) ? properties.needleWidth[i] : properties.needleWidth, head: RGraph.isArray(properties.needleHead) ? properties.needleHead[i] : properties.needleHead, tail: RGraph.isArray(properties.needleTail) ? properties.needleTail[i] : properties.needleTail, triangleBorder: RGraph.isArray(properties.needleTriangleBorder) ? properties.needleTriangleBorder[i] : properties.needleTriangleBorder }); } } else { this.drawNeedle({ value: this.value, color: RGraph.isArray(properties.needleColor) ? properties.needleColor[0] : properties.needleColor, length: RGraph.isArray(properties.needleLength) ? properties.needleLength[0] : properties.needleLength, type: RGraph.isArray(properties.needleColor) ? properties.needleType[0] : properties.needleType, linewidth: RGraph.isArray(properties.needleWidth) ? properties.needleWidth[0] : properties.needleWidth, head: RGraph.isArray(properties.needleHead) ? properties.needleHead[0] : properties.needleHead, tail: RGraph.isArray(properties.needleTail) ? properties.needleTail[0] : properties.needleTail, triangleBorder: RGraph.isArray(properties.needleTriangleBorder) ? properties.needleTriangleBorder[0] : properties.needleTriangleBorder }); } // // Draw the key if requested // if (properties.key && properties.key.length > 0) { // Build a colors array out of the needle colors var colors = RGraph.isArray(properties.needleColor) ? properties.needleColor : [properties.needleColor]; RGraph.drawKey(this, properties.key, colors ); } // // Setup the context menu if required // if (properties.contextmenu) { RGraph.showContext(this); } // // Add custom text thats specified // RGraph.addCustomText(this); // // This installs the event listeners // RGraph.installEventListeners(this); // // End clipping // if (!RGraph.isNullish(this.properties.clip)) { RGraph.clipTo.end(); } // // Fire the onfirstdraw event // if (this.firstDraw) { this.firstDraw = false; RGraph.fireCustomEvent(this, 'onfirstdraw'); this.firstDrawFunc(); } // // Fire the RGraph draw event // RGraph.fireCustomEvent(this, 'ondraw'); // // Install any inline responsive configuration. This // should be last in the draw function - even after // the draw events. // RGraph.installInlineResponsive(this); return this; }; // // Used in chaining. Runs a function there and then - not waiting for // the events to fire (eg the onbeforedraw event) // // @param function func The function to execute // this.exec = function (func) { func(this); return this; }; // // Draws the background // this.drawBackground = function () { this.context.beginPath(); // // Turn on the shadow if need be // if (properties.shadowOuter) { RGraph.setShadow( this, properties.shadowOuterColor, properties.shadowOuterOffsetx, properties.shadowOuterOffsety, properties.shadowOuterBlur ); } var backgroundColor = properties.backgroundColor; // Draw the grey border this.context.fillStyle = backgroundColor; this.context.arc( this.centerx, this.centery, this.radius + (properties.border ? 20 : 0), 0.0001, RGraph.TWOPI, false ); this.context.fill(); // // Turn off the shadow // RGraph.noShadow(this); // Draw a circle this.context.strokeStyle = '#666'; this.context.arc(this.centerx, this.centery, this.radius, 0, RGraph.TWOPI, false); // Now draw a big white circle to make the lines appear as tick marks // This is solely for Chrome this.context.fillStyle = backgroundColor; this.context.arc(this.centerx, this.centery, this.radius, 0, RGraph.TWOPI, false); this.context.fill(); // // Draw more tickmarks // if (properties.tickmarks) { this.context.beginPath(); this.context.strokeStyle = '#bbb'; for (var i=0; i<=360; i+=3) { this.context.arc(this.centerx, this.centery, this.radius, 0, i / 57.3, false); this.context.lineTo(this.centerx, this.centery); } this.context.stroke(); } this.context.beginPath(); this.context.lineWidth = 1; this.context.strokeStyle = 'black'; // Now draw a big white circle to make the lines appear as tick marks this.context.fillStyle = backgroundColor; this.context.strokeStyle = backgroundColor; this.context.arc(this.centerx, this.centery, this.radius - 5, 0, RGraph.TWOPI, false); this.context.fill(); this.context.stroke(); // Gray lines at 18 degree intervals this.context.beginPath(); this.context.strokeStyle = properties.backgroundLinesColor; for (var i=0; i<360; i+=18) { this.context.arc(this.centerx, this.centery, this.radius, 0, RGraph.toRadians(i), false); this.context.lineTo(this.centerx, this.centery); } this.context.stroke(); // Redraw the outer circle this.context.beginPath(); this.context.strokeStyle = properties.backgroundBorder; this.context.arc(this.centerx, this.centery, this.radius, 0, RGraph.TWOPI, false); this.context.stroke(); // // Now draw the center bits shadow if need be // if (properties.shadowInner) { this.context.beginPath(); RGraph.setShadow( this, properties.shadowInnerColor, properties.shadowInnerOffsetx, properties.shadowInnerOffsety, properties.shadowInnerBlur ); this.context.arc(this.centerx, this.centery, this.radius - properties.labelsMargin, 0, RGraph.TWOPI, 0); this.context.fill(); this.context.stroke(); // // Turn off the shadow // RGraph.noShadow(this); } // // Draw the green area // var greengrad = properties.colorsGreenColor; // Draw the "tick highlight" if (properties.tickmarksHighlighted) { this.context.beginPath(); this.context.lineWidth = 5; this.context.strokeStyle = greengrad; this.context.arc(this.centerx, this.centery, this.radius - 2.5, -1 * RGraph.HALFPI, (((properties.colorsGreenMax - this.min)/ (this.max - this.min)) * RGraph.TWOPI) - RGraph.HALFPI, 0); this.context.stroke(); this.context.lineWidth = 1; } this.context.beginPath(); this.context.fillStyle = greengrad; this.context.arc( this.centerx, this.centery, this.radius - properties.labelsMargin, 0 - RGraph.HALFPI, (((properties.colorsGreenMax - this.min)/ (this.max - this.min)) * RGraph.TWOPI) - RGraph.HALFPI, false ); this.context.lineTo(this.centerx, this.centery); this.context.closePath(); this.context.fill(); // // Draw the yellow area // var yellowgrad = properties.colorsYellowColor; // Draw the "tick highlight" if (properties.tickmarksHighlighted) { this.context.beginPath(); this.context.lineWidth = 5; this.context.strokeStyle = yellowgrad; this.context.arc(this.centerx, this.centery, this.radius - 2.5, ( ((properties.colorsGreenMax - this.min) / (this.max - this.min)) * RGraph.TWOPI) - RGraph.HALFPI, (((properties.colorsRedMin - this.min) / (this.max - this.min)) * RGraph.TWOPI) - RGraph.HALFPI, 0); this.context.stroke(); this.context.lineWidth = 1; } this.context.beginPath(); this.context.fillStyle = yellowgrad; this.context.arc( this.centerx, this.centery, this.radius - properties.labelsMargin, ( ((properties.colorsGreenMax - this.min) / (this.max - this.min)) * RGraph.TWOPI) - RGraph.HALFPI, ( ((properties.colorsRedMin - this.min) / (this.max - this.min)) * RGraph.TWOPI) - RGraph.HALFPI, false ); this.context.lineTo(this.centerx, this.centery); this.context.closePath(); this.context.fill(); // // Draw the red area // var redgrad = properties.colorsRedColor; // Draw the "tick highlight" if (properties.tickmarksHighlighted) { this.context.beginPath(); this.context.lineWidth = 5; this.context.strokeStyle = redgrad; this.context.arc(this.centerx, this.centery, this.radius - 2.5,(((properties.colorsRedMin - this.min) / (this.max - this.min)) * RGraph.TWOPI) - RGraph.HALFPI,RGraph.TWOPI - RGraph.HALFPI,0); this.context.stroke(); this.context.lineWidth = 1; } this.context.beginPath(); this.context.fillStyle = redgrad; this.context.strokeStyle = redgrad; this.context.arc( this.centerx, this.centery, this.radius - properties.labelsMargin, (((properties.colorsRedMin - this.min) / (this.max - this.min)) * RGraph.TWOPI) - RGraph.HALFPI, RGraph.TWOPI - RGraph.HALFPI, false ); this.context.lineTo(this.centerx, this.centery); this.context.closePath(); this.context.fill(); // // Draw the thick border // if (properties.border) { var grad = this.context.createRadialGradient(this.centerx, this.centery, this.radius, this.centerx, this.centery, this.radius + 20); grad.addColorStop(0, properties.borderColor1); grad.addColorStop(0.5, properties.borderColor2); grad.addColorStop(1, properties.borderColor3); this.context.beginPath(); this.context.fillStyle = grad; this.context.strokeStyle = 'rgba(0,0,0,0)' this.context.lineWidth = 0.001; this.context.arc(this.centerx, this.centery, this.radius + 20, 0, RGraph.TWOPI, 0); this.context.arc(this.centerx, this.centery, this.radius - 2, RGraph.TWOPI, 0, 1); this.context.fill(); } // Put the linewidth back to what it was this.context.lineWidth = properties.linewidth; // // Draw the title if specified // if (properties.title) { RGraph.drawTitle(this); } // Draw the big tick marks if (!properties.tickmarksHighlighted) { for (var i=18; i<=360; i+=36) { this.context.beginPath(); this.context.strokeStyle = properties.tickmarksLargeColor; this.context.lineWidth = 2; this.context.arc(this.centerx, this.centery, this.radius - 1, RGraph.toRadians(i), RGraph.toRadians(i+0.01), false); this.context.arc(this.centerx, this.centery, this.radius - 7, RGraph.toRadians(i), RGraph.toRadians(i+0.01), false); this.context.stroke(); } } }; // // Draws the needle of the odometer // // @param object opt An object map of the following // properties: // o value // o head // o width // o length // o type // o color // o triangleborder // this.drawNeedle = function (opt) { // The optional length of the needle opt.length = opt.length ? opt.length : this.radius - properties.labelsMargin - 10; // First draw a grey background circle at the center of // the Odo this.context.fillStyle = '#999'; this.context.beginPath(); this.context.moveTo(this.centerx, this.centery); this.context.arc( this.centerx, this.centery, 10, 0, RGraph.TWOPI, false ); this.context.closePath(); this.context.fill(); this.context.fill(); this.context.fillStyle = opt.color this.context.strokeStyle = '#666'; // Draw the centre bit this.context.beginPath(); this.context.moveTo(this.centerx, this.centery); this.context.arc(this.centerx, this.centery, 8, 0, RGraph.TWOPI, false); this.context.fill(); this.context.closePath(); this.context.stroke(); this.context.fill(); if (opt.type === 'pointer') { this.context.strokeStyle = opt.color; this.context.lineWidth = opt.linewidth; this.context.lineCap = 'round'; this.context.lineJoin = 'round'; // Draw the needle this.context.beginPath(); // The trailing bit on the opposite side of the dial this.context.beginPath(); this.context.moveTo( this.centerx, this.centery ); if (opt.tail) { this.context.arc( this.centerx, this.centery, 20, (opt.value / this.range) * RGraph.TWOPI + RGraph.HALFPI, (opt.value / this.range) * RGraph.TWOPI + RGraph.HALFPI, false ); } // Draw the long bit on the opposite side this.context.arc( this.centerx, this.centery, opt.length - 20, (((opt.value / this.range) * 360) - 90) / (180 / RGraph.PI), (((opt.value / this.range) * 360) - 90 + 0.1 ) / (180 / RGraph.PI), false ); this.context.closePath(); //this.context.stroke(); //this.context.fill(); } else if (opt.type === 'triangle') { this.context.lineWidth = 0.01; this.context.lineEnd = 'square'; this.context.lineJoin = 'miter'; // // This draws the version of the pointer that becomes the border // this.context.beginPath(); this.context.fillStyle = opt.triangleBorder; this.context.arc(this.centerx, this.centery, 11, (((opt.value / this.range) * 360)) / 57.3, ((((opt.value / this.range) * 360)) + 0.01) / 57.3, 0); this.context.arc(this.centerx, this.centery, 11, (((opt.value / this.range) * 360) + 180) / 57.3, ((((opt.value / this.range) * 360) + 180) + 0.01)/ 57.3, 0); this.context.arc(this.centerx, this.centery, opt.length - 5, (((opt.value / this.range) * 360) - 90) / 57.3, ((((opt.value / this.range) * 360) - 90) / 57.3) + 0.01, 0); this.context.closePath(); this.context.fill(); this.context.beginPath(); this.context.arc( this.centerx, this.centery, 15, 0, RGraph.TWOPI, false ); this.context.closePath(); this.context.fill(); // This draws the pointer this.path( 'b a % % % % % false a % % % % % false', this.centerx, this.centery, 7, (((opt.value / this.range) * 360)) / 57.3, ((((opt.value / this.range) * 360)) + 0.01) / 57.3, this.centerx, this.centery, 7, (((opt.value / this.range) * 360) + 180) / 57.3, ((((opt.value / this.range) * 360) + 180) + 0.01)/ 57.3 ); this.context.arc(this.centerx, this.centery, opt.length - 13, (((opt.value / this.range) * 360) - 90) / 57.3, ((((opt.value / this.range) * 360) - 90) / 57.3) + 0.01, 0); this.context.closePath(); this.context.strokeStyle = 'black'; this.context.fillStyle = opt.color; this.context.stroke(); this.context.fill(); } this.context.stroke(); this.context.fill(); // Draw the mini center circle this.context.beginPath(); this.context.fillStyle = opt.color; this.context.arc( this.centerx, this.centery, opt.type === 'pointer' ? 7 : 12, 0.01, RGraph.TWOPI, false ); this.context.fill(); // This draws the arrow at the end of the line if (opt.head && opt.type === 'pointer') { this.context.lineWidth = 1; this.context.fillStyle = opt.color; // round, bevel, miter this.context.lineJoin = 'miter'; this.context.lineCap = 'butt'; this.context.beginPath(); this.context.arc( this.centerx, this.centery, opt.length - 5, RGraph.toRadians(((opt.value / this.range) * 360) - 90), RGraph.toRadians(((opt.value / this.range) * 360) - 90 + 0.1), false ); this.context.arc( this.centerx, this.centery, opt.length - 20, RGraph.toRadians( ((opt.value / this.range) * 360) - (opt.length < 60 ? 80 : 85) ), RGraph.toRadians( ((opt.value / this.range) * 360) - (opt.length < 60 ? 100 : 95) ), true ); this.context.closePath(); this.context.fill(); //this.context.stroke(); } // // Draw a white circle at the centre // this.context.beginPath(); this.context.fillStyle = 'gray'; this.context.moveTo(this.centerx, this.centery); this.context.arc( this.centerx, this.centery, 2, 0, 6.2795, false ); this.context.closePath(); this.context.fill(); }; // // Draws the labels for the Odo // this.drawLabels = function () { var centerx = this.centerx, centery = this.centery, r = this.radius - (properties.labelsMargin / 2) - 5, start = this.min, end = this.max, decimals = properties.scaleDecimals, point = properties.scalePoint, thousand = properties.scaleThousand, labels = properties.labels, units_pre = properties.scaleUnitsPre, units_post = properties.scaleUnitsPost; this.context.beginPath(); this.context.fillStyle = properties.textColor; var textConf = RGraph.getTextConf({ object: this, prefix: 'labels' }); // // If labels are specified, use those // if (labels) { for (var i=0; i<labels.length; ++i) { RGraph.text({ object: this, font: textConf.font, size: textConf.size, color: textConf.color, bold: textConf.bold, italic: textConf.italic, x: centerx + (Math.cos(((i / labels.length) * RGraph.TWOPI) - RGraph.HALFPI) * (this.radius - (properties.labelsMargin / 2) ) ), // Sin A = Opp / Hyp y: centery + (Math.sin(((i / labels.length) * RGraph.TWOPI) - RGraph.HALFPI) * (this.radius - (properties.labelsMargin / 2) ) ), // Cos A = Adj / Hyp text: String(labels[i]), valign: 'center', halign: 'center', tag: 'labels' }); } // // If not, use the maximum value // } else { this.scale2 = RGraph.getScale({object: this, options: { 'scale.max': this.max, 'scale.strict': true, 'scale.min': this.min, 'scale.thousand': properties.scaleThousand, 'scale.point': properties.scalePoint, 'scale.decimals': properties.scaleDecimals, 'scale.labels.count': 10, 'scale.round': false, 'scale.units.pre': properties.scaleUnitsPre, 'scale.units.post': properties.scaleUnitsPost }}); RGraph.text({object:this,font: textConf.font, size: textConf.size, color: textConf.color, bold: textConf.bold, italic: textConf.italic,x:centerx + (0.588 * r ),y:centery - (0.809 * r ),text:RGraph.numberFormat({object: this, number: (((end - start) * (1/10)) + start).toFixed(decimals), unitspre: units_pre, unitspost: units_post,point: point,thousand: thousand}),halign:'center',valign:'center',angle:36,tag: 'scale'}); RGraph.text({object:this,font: textConf.font, size: textConf.size, color: textConf.color, bold: textConf.bold, italic: textConf.italic,x:centerx + (0.951 * r ),y:centery - (0.309 * r),text:RGraph.numberFormat({object: this, number: (((end - start) * (2/10)) + start).toFixed(decimals), unitspre:units_pre, unitspost: units_post,point: point,thousand: thousand}),halign:'center',valign:'center',angle:72,tag: 'scale'}); RGraph.text({object:this,font: textConf.font, size: textConf.size, color: textConf.color, bold: textConf.bold, italic: textConf.italic,x:centerx + (0.949 * r),y:centery + (0.31 * r),text:RGraph.numberFormat({object: this, number: (((end - start) * (3/10)) + start).toFixed(decimals), unitspre: units_pre, unitspost: units_post,point: point,thousand: thousand}),halign:'center',valign:'center',angle:108,tag: 'scale'}); RGraph.text({object:this,font: textConf.font, size: textConf.size, color: textConf.color, bold: textConf.bold, italic: textConf.italic,x:centerx + (0.588 * r ),y:centery + (0.809 * r ),text:RGraph.numberFormat({object: this, number: (((end - start) * (4/10)) + start).toFixed(decimals), unitspre: units_pre, unitspost: units_post,point: point,thousand: thousand}),halign:'center',valign:'center',angle:144,tag: 'scale'}); RGraph.text({object:this,font: textConf.font, size: textConf.size, color: textConf.color, bold: textConf.bold, italic: textConf.italic,x:centerx,y:centery + r,text:RGraph.numberFormat({object: this, number: (((end - start) * (5/10)) + start).toFixed(decimals),unitspre: units_pre, unitspost: units_post,point: point,thousand: thousand}),halign:'center',valign:'center',angle:180,tag: 'scale'}); RGraph.text({object:this,font: textConf.font, size: textConf.size, color: textConf.color, bold: textConf.bold, italic: textConf.italic,x:centerx - (0.588 * r ),y:centery + (0.809 * r ),text:RGraph.numberFormat({object: this, number: (((end - start) * (6/10)) + start).toFixed(decimals), unitspre: units_pre, unitspost: units_post,point: point,thousand: thousand}),halign:'center',valign:'center',angle:216,tag: 'scale'}); RGraph.text({object:this,font: textConf.font, size: textConf.size, color: textConf.color, bold: textConf.bold, italic: textConf.italic,x:centerx - (0.949 * r),y:centery + (0.300 * r),text:RGraph.numberFormat({object: this, number: (((end - start) * (7/10)) + start).toFixed(decimals), unitspre: units_pre, unitspost: units_post,point: point,thousand: thousand}),halign:'center',valign:'center',angle:252,tag: 'scale'}); RGraph.text({object:this,font: textConf.font, size: textConf.size, color: textConf.color, bold: textConf.bold, italic: textConf.italic,x:centerx - (0.951 * r),y:centery - (0.309 * r),text:RGraph.numberFormat({object: this, number: (((end - start) * (8/10)) + start).toFixed(decimals), unitspre: units_pre, unitspost: units_post,point: point,thousand: thousand}),halign:'center',valign:'center',angle:288,tag: 'scale'}); RGraph.text({object:this,font: textConf.font, size: textConf.size, color: textConf.color, bold: textConf.bold, italic: textConf.italic,x:centerx - (0.588 * r ),y:centery - (0.809 * r ),text:RGraph.numberFormat({object: this, number: (((end - start) * (9/10)) + start).toFixed(decimals), unitspre: units_pre, unitspost: units_post,point: point,thousand: thousand}),halign:'center',valign:'center',angle:324,tag: 'scale'}); RGraph.text({object:this,font: textConf.font, size: textConf.size, color: textConf.color, bold: textConf.bold, italic: textConf.italic,x:centerx,y:centery - r,text: RGraph.numberFormat({object: this, number: (((end - start) * (10/10)) + start).toFixed(decimals), unitspre: units_pre, unitspost: units_post,point: point,thousand: thousand}),halign:'center',valign:'center',tag: 'scale'}); } this.context.fill(); // // Draw the text label below the center point // if (properties.labelsValue) { this.context.strokeStyle = 'black'; var textConf = RGraph.getTextConf({ object: this, prefix: 'labelsValue' }); RGraph.text({ object: this, font: textConf.font, size: textConf.size, color: textConf.color, bold: textConf.bold, italic: textConf.italic, x: centerx + properties.labelsValueOffsetx, y: centery + textConf.size + 15 + properties.labelsValueOffsety, text: RGraph.numberFormat({ object: this, number: this.value.toFixed(this.value === 0 ? 0 : properties.labelsValueDecimals), unitspre: properties.labelsValueUnitsPre, unitspost: properties.labelsValueUnitsPost, point: properties.labelsValuePoint, thousand: properties.labelsValueThousand }), halign: 'center', valign: 'center', bounding: true, boundingFill: 'rgba(255,255,255,0.7)', boundingStroke: 'rgba(0,0,0,0)', tag: 'value.text' }); } }; // // A placeholder function // // @param object The event object // this.getShape = function (e) {}; // // This function returns the pertinent value at the point of click // // @param object The event object // this.getValue = function (e) { var mouseXY = RGraph.getMouseXY(e) var angle = RGraph.getAngleByXY(this.centerx, this.centery, mouseXY[0], mouseXY[1]); angle += RGraph.HALFPI; if (mouseXY[0] >= this.centerx && mouseXY[1] <= this.centery) { angle -= RGraph.TWOPI; } var value = ((angle / RGraph.TWOPI) * (this.max - this.min)) + this.min; 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); var radius = RGraph.getHypLength(this.centerx, this.centery, mouseXY[0], mouseXY[1]); 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) && radius <= this.radius ) { 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) { this.value = this.getValue(e); RGraph.clear(this.canvas); RGraph.redrawCanvas(this.canvas); RGraph.fireCustomEvent(this, 'onadjust'); } }; // // This method returns the appropriate angle for a value // // @param number value The value // this.getAngle = function (value) { // Higher than max or lower than min if (value > this.max || value < this.min) { return null; } var angle = (((value - this.min) / (this.max - this.min)) * RGraph.TWOPI); angle -= RGraph.HALFPI; return angle; }; // // 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.colorsGreenColor = RGraph.arrayClone(properties.colorsGreenColor); this.original_colors.colorsYellowColor = RGraph.arrayClone(properties.colorsYellowColor); this.original_colors.colorsRedColor = RGraph.arrayClone(properties.colorsRedColor); } // Parse the basic colors properties.colorsGreenColor = this.parseSingleColorForGradient(properties.colorsGreenColor); properties.colorsYellowColor = this.parseSingleColorForGradient(properties.colorsYellowColor); properties.colorsRedColor = this.parseSingleColorForGradient(properties.colorsRedColor); }; // // Use this function to reset the object to the post-constructor state. Eg reset colors if // need be etc // this.reset = function () { }; // // This parses a single color value // this.parseSingleColorForGradient = function (color) { if (!color || typeof color != 'string') { return color; } if (color.match(/^gradient\((.*)\)$/i)) { // Allow for JSON gradients if (color.match(/^gradient\(({.*})\)$/i)) { return RGraph.parseJSONGradient({object: this, def: RegExp.$1}); } var parts = RegExp.$1.split(':'); // Create the gradient var grad = this.context.createRadialGradient(this.centerx, this.centery, 0, this.centerx, this.centery, this.radius); var diff = 1 / (parts.length - 1); grad.addColorStop(0, RGraph.trim(parts[0])); for (var j=1; j<parts.length; ++j) { grad.addColorStop(j * diff, RGraph.trim(parts[j])); } } return grad ? grad : color; }; // // Using a function to add events makes it easier to facilitate method chaining // // @param string type The type of even to add // @param function func // this.on = function (type, func) { if (type.substr(0,2) !== 'on') { type = 'on' + type; } if (typeof this[type] !== 'function') { this[type] = func; } else { RGraph.addCustomEventListener(this, type, func); } return this; }; // // This function runs once only // (put at the end of the file (before any effects)) // this.firstDrawFunc = function () { }; // // Odo Grow // // This effect gradually increases the represented value // // @param An object of effect properties - eg: {frames: 30} // @param function An optional callback function // this.grow = function () { // Cancel any stop request if one is pending this.cancelStopAnimation(); var obj = this; var opt = arguments[0] || {}; var frames = opt.frames || 30; var frame = 0; var current = this.currentValue || 0; var origValue = Number(obj.currentValue); var newValue = this.value; var diff = newValue - origValue; var step = (diff / frames); var callback = arguments[1] || function () {}; function iterator () { if (obj.stopAnimationRequested) { // Reset the flag obj.stopAnimationRequested = false; return; } obj.value = origValue + (frame * step); 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 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 { if (index === 0) { return this.value; } else { return this.properties.needleExtra[index - 1][0]; } } }; // // 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 len = 1; // Add the needleExtra count len += this.properties.needleExtra.length; return len; }; // // 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 a1 = this.getAngle(from), a2 = this.getAngle(to); // Change the radius if the number is "min" if (RegExp.$1 === 'min') { a1 = this.getAngle(this.min); } // Change the radius if the number is "max" if (RegExp.$2 === 'max') { a2 = this.getAngle(this.max); } 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), a1, a2 ); }; // // 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); };