// 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 window.RGraph = window.RGraph || {isrgraph:true,isRGraph: true,rgraph:true}; // Module pattern (function (win, doc, undefined) { // A short name variable var ua = navigator.userAgent; // // Initialise the various objects // RGraph.Highlight = {}; RGraph.Registry = {}; RGraph.Registry.store = []; RGraph.Registry.store['event.handlers'] = []; RGraph.Registry.store['__rgraph_event_listeners__'] = []; // Used in the new system for tooltips RGraph.Registry.store['rgraph-runonce-functions'] = []; // Used in the runonce system RGraph.Background = {}; RGraph.background = {}; RGraph.objects = []; RGraph.Resizing = {}; RGraph.events = []; RGraph.cursor = []; RGraph.Effects = RGraph.Effects || {}; RGraph.cache = []; RGraph.GET = {}; RGraph.GET.__parts__ = null; RGraph.ObjectRegistry = {}; RGraph.ObjectRegistry.objects = {}; RGraph.ObjectRegistry.objects.byUID = []; RGraph.ObjectRegistry.objects.byCanvasID = []; RGraph.OR = RGraph.ObjectRegistry; // // Some "constants". The ua variable is navigator.userAgent (definedabove) // RGraph.PI = Math.PI; RGraph.HALFPI = RGraph.PI / 2; RGraph.TWOPI = RGraph.PI * 2; RGraph.ISFF = ua.indexOf('Firefox') != -1; RGraph.ISOPERA = ua.indexOf('Opera') != -1; RGraph.ISCHROME = ua.indexOf('Chrome') != -1; RGraph.ISSAFARI = ua.indexOf('Safari') != -1 && !RGraph.ISCHROME; RGraph.ISWEBKIT = ua.indexOf('WebKit') != -1; RGraph.ISIE = ua.indexOf('Trident') > 0 || navigator.userAgent.indexOf('MSIE') > 0; RGraph.ISIE6 = ua.indexOf('MSIE 6') > 0; RGraph.ISIE7 = ua.indexOf('MSIE 7') > 0; RGraph.ISIE8 = ua.indexOf('MSIE 8') > 0; RGraph.ISIE9 = ua.indexOf('MSIE 9') > 0; RGraph.ISIE10 = ua.indexOf('MSIE 10') > 0; RGraph.ISOLD = RGraph.ISIE6 || RGraph.ISIE7 || RGraph.ISIE8; // MUST be here RGraph.ISIE11UP = ua.indexOf('MSIE') == -1 && ua.indexOf('Trident') > 0; RGraph.ISIE10UP = RGraph.ISIE10 || RGraph.ISIE11UP; RGraph.ISIE9UP = RGraph.ISIE9 || RGraph.ISIE10UP; // Some commonly used bits of info RGraph.MONTHS_SHORT = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']; RGraph.MONTHS_LONG = ['January','February','March','April','May','June','July','August','September','October','November','December']; RGraph.DAYS_SHORT = ['Mon','Tue','Wed','Thu','Fri','Sat','Sun']; RGraph.DAYS_LONG = ['Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday']; RGraph.HOURS24 = ['00:00','01:00','02:00','03:00','04:00','05:00','06:00','07:00','08:00','09:00','10:00','11:00','12:00','13:00','14:00','15:00','16:00','17:00','18:00','19:00','20:00','21:00','22:00','23:00']; RGraph.HOURS12 = ['00:00','01:00','02:00','03:00','04:00','05:00','06:00','07:00','08:00','09:00','10:00','11:00']; // // Returns five values which are used as a nice scale // // 11/12/2018 // ========== // This funtction doesn't appear to be being used // any more - could remove it. // // @param max int The maximum value of the graph // @param obj object The graph object // @return array An appropriate scale // // RGraph.getScale = function (max, obj) // { // var prefix = obj.type === 'hbar' ? 'xaxis' : 'yaxis'; // // // // // Special case for 0 // // // if (max == 0) { // return ['0.2', '0.4', '0.6', '0.8', '1.0']; // } // // var original_max = max; // // // // // Manually do decimals // // // if (max <= 1) { // if (max > 0.5) { // return [0.2,0.4,0.6,0.8, Number(1).toFixed(1)]; // // } else if (max >= 0.1) { // return obj.get(prefix + 'ScaleRound') ? [0.2,0.4,0.6,0.8,1] : [0.1,0.2,0.3,0.4,0.5]; // // } else { // // var tmp = max; // var exp = 0; // // while (tmp < 1.01) { // exp += 1; // tmp *= 10; // } // // var ret = ['2e-' + exp, '4e-' + exp, '6e-' + exp, '8e-' + exp, '10e-' + exp]; // // // if (max <= ('5e-' + exp)) { // ret = ['1e-' + exp, '2e-' + exp, '3e-' + exp, '4e-' + exp, '5e-' + exp]; // } // // return ret; // } // } // // // Take off any decimals // if (String(max).indexOf('.') > 0) { // max = String(max).replace(/\.\d+$/, ''); // } // // var interval = Math.pow(10, Number(String(Number(max)).length - 1)); // var topValue = interval; // // while (topValue < max) { // topValue += (interval / 2); // } // // // Handles cases where the max is (for example) 50.5 // if (Number(original_max) > Number(topValue)) { // topValue += (interval / 2); // } // // // Custom if the max is greater than 5 and less than 10 // if (max < 10) { // topValue = (Number(original_max) <= 5 ? 5 : 10); // } // // // // // Added 02/11/2010 to create "nicer" scales // // // if (obj && typeof obj.get(prefix + 'ScaleRound') == 'boolean' && obj.get(prefix + 'ScaleRound')) { // topValue = 10 * interval; // } // // return [topValue * 0.2, topValue * 0.4, topValue * 0.6, topValue * 0.8, topValue]; // }; // // This function allows both object based arguments to functions // and also regular arguments as well. // // You can call it from inside a function like this: // // args = RGraph.getArgs(arguments, 'object,id,foo,bar'); // // So you're passing it the arguments object and a comma seperated list of names // for the arguments. // // @param array args The arguments object that you get when inside a function // @param string names A comma seperated list of desired names for the arguments // eg: 'object,color,size' // RGraph.getArgs = function (args, names) { var ret = {}; var count = 0; names = names.trim().split(/ *, */); if ( args && args[0] && args.length === 1 && typeof args[0][names[0]] !== 'undefined') { for (var i=0; i arr[i]) { i--; break; } } scale.max = arr[i]; scale.labels = []; scale.values = []; for (var j=0; j Number(topValue)) { topValue += (interval / 2); } // Custom if the max is greater than 5 and less than 10 if (max <= 10) { topValue = (Number(original_max) <= 5 ? 5 : 10); } // Added 02/11/2010 to create "nicer" scales if (args.object && typeof round == 'boolean' && round) { topValue = 10 * interval; } scale.max = topValue; // Now generate the scale. Temporarily set the objects scaleDecimal and scalePoint to those // that we've been given as the number_format functuion looks at those instead of using // arguments. var tmp_point = properties[prefix + 'ScalePoint']; var tmp_thousand = properties[prefix + 'ScaleThousand']; args.object.set(prefix + 'scaleThousand', thousand); args.object.set(prefix + 'scalePoint', point); for (var i=0; i= 0) { ret = new Array(); for (i in obj) { ret[i] = RGraph.arrayClone(obj[i]); } } else if (obj.constructor.toString().indexOf('Object') >= 0) { ret = new Object(); for (i in obj) { ret[i] = RGraph.arrayClone(obj[i]); } } break; case 'function': ret = obj; break; } } return ret; }; // // With this variation of the clone function you can do // this: // arr1 = [1,2,3,4,5,6]; // arr2 = arr1.clone(); // Object.prototype.clone = function () { return RGraph.arrayClone(this, arguments[0]); }; // // An alias of the above function // RGraph.clone = RGraph.arrayClone; // // Returns the maximum numeric value which is in an array. This function IS NOT // recursive // // @param object args An object consisting of an array property which is the array to get // the max value of. // // OR // // @param array arr The array (can also be a number, in which case it's returned as-is) // @param int Whether to ignore signs (ie negative/positive) // @return int The maximum value in the array // RGraph.arrayMax = function () { var args = RGraph.getArgs(arguments, 'array,ignore'); var max = null; if (typeof args.array === 'number') { return args.array; } if (RGraph.isNull(args.array)) { return 0; } for (var i=0,len=args.array.length; i=0; i-=1) { newarr.push(args.array[i]); } return newarr; }; // // Returns the absolute value of a number. You can also pass in an // array and it will run the abs() function on each element. It // operates recursively so sub-arrays are also traversed. // // @param args object An object consisting of: // o value // OR // // @param array arr The number or array to work on // RGraph.abs = function () { var args = RGraph.getArgs(arguments, 'value'); if (typeof args.value === 'string') { args.value = parseFloat(args.value) || 0; } if (typeof args.value === 'number') { return Math.abs(args.value); } if (typeof args.value === 'object') { for (i in args.value) { if ( typeof args.value[i] === 'string' || typeof args.value[i] === 'number' || typeof args.value[i] === 'object') { args.value[i] = RGraph.abs(args.value[i]); } } return args.value; } return 0; }; // // Clears the canvas by setting the width. You can specify a colour if you wish. // // @param args object An object consisting of: // o canvas // o color // OR // // // @param object canvas The canvas to clear // @param mixed Usually a color string to use to clear the canvas // with - could also be a gradient object // RGraph.clear = RGraph.Clear = function (args) { var args = RGraph.getArgs(arguments, 'canvas,color'); var obj = args.canvas.__object__; var context = args.canvas.getContext('2d'); var color = args.color || (obj && obj.get('clearto')); if (!args.canvas) { return; } RGraph.fireCustomEvent(obj, 'onbeforeclear'); // // Set the CSS display: to none for DOM text // if (RGraph.text.domNodeCache && RGraph.text.domNodeCache[args.canvas.id]) { for (var i in RGraph.text.domNodeCache[args.canvas.id]) { var el = RGraph.text.domNodeCache[args.canvas.id][i]; if (el && el.style) { el.style.display = 'none'; } } } // // Can now clear the canvas back to fully transparent // if ( !color || (color && color === 'rgba(0,0,0,0)' || color === 'transparent') ) { context.clearRect( -100, -100, args.canvas.width + 200, args.canvas.height + 200 ); // Reset the globalCompositeOperation context.globalCompositeOperation = 'source-over'; } else if (color) { obj.path( 'fs % fr -100 -100 % %', color, args.canvas.width + 200, args.canvas.height + 200 ); } else { obj.path( 'fs % fr -100 -100 % %', obj.get('clearto'), args.canvas.width + 200, args.canvas.height + 200 ); } //if (RGraph.clearAnnotations) { //RGraph.clearAnnotations(canvas.id); //} // // This removes any background image that may be present // if (RGraph.Registry.get('background.image.' + args.canvas.id)) { var img = RGraph.Registry.get('background.image.' + args.canvas.id); img.style.position = 'absolute'; img.style.left = '-10000px'; img.style.top = '-10000px'; } // // This hides the tooltip that is showing IF it has the same canvas ID as // that which is being cleared // if (RGraph.Registry.get('tooltip') && obj && !obj.get('tooltipsNohideonclear')) { RGraph.hideTooltip(args.canvas); } // // Set the cursor to default // args.canvas.style.cursor = 'default'; RGraph.fireCustomEvent(obj, 'onclear'); }; // // Draws the title of the graph // // @param object args An object consisting of the arguments to the function // o object // // OR // // @param object canvas The canvas object // RGraph.drawTitle = function () { var args = RGraph.getArgs(arguments, 'object'), obj = args.object, halign = 'center', valign = 'center', x = ((obj.canvas.width - obj.properties.marginLeft - obj.properties.marginRight) / 2) + obj.properties.marginLeft, y = null, textConf = RGraph.getTextConf({ object: args.object, prefix: 'title' }); obj.context.beginPath(); obj.context.fillStyle = textConf.color; // // If not set then set the size of the text 4 pt higher than // the textSize setting. Also set the title to be bold if the //bold property isn't set // if (!RGraph.isNumber(obj.properties.titleSize)) { textConf.size += 4; } if (!RGraph.isBoolean(obj.properties.titleBold)) { textConf.bold = true; } // Determine the Y coordinate y = obj.properties.marginTop - textConf.size - 5; if (obj.properties.xaxisPosition === 'top') { y = obj.canvas.height - obj.properties.marginBottom + textConf.size + 5; } // // Vertically center the text if the key is not present // or if it's not positioned in the margin // if (obj.properties.key && obj.properties.key.length && obj.properties.keyPosition && obj.properties.keyPosition !== 'margin') { var valign = 'center'; } else if (obj.properties.key && obj.properties.key.length && obj.properties.keyPosition && obj.properties.keyPosition === 'margin') { var valign = 'bottom'; // Measure the size of the key text var keyTextDim = RGraph.measureText({ bold: obj.properties.keyLabelsBold, italic: obj.properties.keyLabelsItalic, size: obj.properties.keyLabelsSize, font: obj.properties.keyLabelsFont, text: 'Mg' }); y -= keyTextDim[1]; } else { var valign = 'center'; } // // Now, the titleX and titleY settings override (if set) the above if (RGraph.isNumber(obj.properties.titleX)) x = obj.properties.titleX; if (RGraph.isNumber(obj.properties.titleY)) y = obj.properties.titleY; // the titleX and titleY properties can be strings - in // which case the added to the calculated coordinate if (RGraph.isString(obj.properties.titleX)) x += parseFloat(obj.properties.titleX); if (RGraph.isString(obj.properties.titleY)) y += parseFloat(obj.properties.titleY); // Similar to the above - the titleOffsetx and titleOffsety // are more explicit properties for moving the title if (RGraph.isNumber(obj.properties.titleOffsetx)) x += obj.properties.titleOffsetx; if (RGraph.isNumber(obj.properties.titleOffsety)) y += obj.properties.titleOffsety; // Set the default vertical alignment for the title if (RGraph.isString(obj.properties.titleSubtitle) && obj.properties.titleSubtitle.length > 0) { valign = 'bottom'; } else { valign = 'center'; } // // Allow the user to override the horizontal alignment // if (RGraph.isString(obj.properties.titleHalign)) { halign = obj.properties.titleHalign; } // // Allow the user to override the vertical alignment // if (RGraph.isString(obj.properties.titleValign)) { valign = obj.properties.titleValign; } // Set the color var oldColor = obj.context.fillStyle; obj.context.fillStyle = textConf.color ? textConf.color : 'black'; // Draw the title var ret = RGraph.text({ object: obj, font: textConf.font, size: textConf.size, color: textConf.color, bold: textConf.bold, italic: textConf.italic, x: x, y: y, text: obj.properties.title, valign: valign, halign: halign, tag: 'title', marker: false }); // Draw the subtitle if (typeof obj.properties.titleSubtitle === 'string' && obj.properties.titleSubtitle.length) { // Get the size of the title // Necessary any more ? var titleSize = textConf.size; // Set the default subtitle size if it's null if (RGraph.isNull(obj.properties.titleSubtitleSize)) { obj.properties.titleSubtitleSize = textConf.size - 4; } var textConf = RGraph.getTextConf({ object: obj, prefix: 'titleSubtitle' }); // Draw the subtitle var ret = RGraph.text({ object: obj, font: textConf.font, size: textConf.size, color: textConf.color, bold: textConf.bold, italic: textConf.italic, x: x + obj.properties.titleSubtitleOffsetx, y: y + obj.properties.titleSubtitleOffsety, text: obj.properties.titleSubtitle, valign: 'top', halign: halign, tag: 'subtitle', marker: false }); } // Reset the fill colour obj.context.fillStyle = oldColor; }; // // Gets the mouse X/Y coordinates relative to the canvas // // @param args object An object consisting of: // o event // OR // // @param object e The event object. As such this method should be used in an event listener. // RGraph.getMouseXY = function () { var args = RGraph.getArgs(arguments, 'event'); // This is necessary for IE9 if (!args.event.target) { return; } var el = args.event.target, canvas = el, caStyle = canvas.style, offsetX = 0, offsetY = 0, x, y, borderLeft = parseInt(caStyle.borderLeftWidth) || 0, borderTop = parseInt(caStyle.borderTopWidth) || 0, paddingLeft = parseInt(caStyle.paddingLeft) || 0, paddingTop = parseInt(caStyle.paddingTop) || 0, additionalX = borderLeft + paddingLeft, additionalY = borderTop + paddingTop; if (typeof args.event.offsetX === 'number' && typeof args.event.offsetY === 'number') { if (!RGraph.ISIE && !RGraph.ISOPERA) { x = args.event.offsetX - borderLeft - paddingLeft; y = args.event.offsetY - borderTop - paddingTop; } else if (RGraph.ISIE) { x = args.event.offsetX - paddingLeft; y = args.event.offsetY - paddingTop; } else { x = args.event.offsetX; y = args.event.offsetY; } } else { if (typeof el.offsetParent !== 'undefined') { do { offsetX += el.offsetLeft; offsetY += el.offsetTop; } while ((el = el.offsetParent)); } x = args.event.pageX - offsetX - additionalX; y = args.event.pageY - offsetY - additionalY; x -= (2 * (parseInt(document.body.style.borderLeftWidth) || 0)); y -= (2 * (parseInt(document.body.style.borderTopWidth) || 0)); //x += (parseInt(caStyle.borderLeftWidth) || 0); //y += (parseInt(caStyle.borderTopWidth) || 0); } // We return a javascript array with x and y defined return [x, y]; }; // // This function returns a two element array of the canvas x/y position in // relation to the page // // @param args object An object consisting of: // o canvas // OR // // @param object canvas // RGraph.getCanvasXY = function () { var args = RGraph.getArgs(arguments, 'canvas'); // If the getBoundingClientRect function is available - use that // if (args.canvas.getBoundingClientRect) { var rect = args.canvas.getBoundingClientRect(); // Add the the current scrollTop and scrollLeft becuase the getBoundingClientRect() // method is relative to the viewport - not the document var scrollLeft = window.pageXOffset || document.documentElement.scrollLeft, scrollTop = window.pageYOffset || document.documentElement.scrollTop; return [rect.x + scrollLeft, rect.y + scrollTop]; } var x = 0; var y = 0; var el = args.canvas; // !!! do { x += el.offsetLeft; y += el.offsetTop; // ACCOUNT FOR TABLES IN wEBkIT if (el.tagName.toLowerCase() == 'table' && (RGraph.ISCHROME || RGraph.ISSAFARI)) { x += parseInt(el.border) || 0; y += parseInt(el.border) || 0; } el = el.offsetParent; } while (el && el.tagName.toLowerCase() != 'body'); var paddingLeft = args.canvas.style.paddingLeft ? parseInt(args.canvas.style.paddingLeft) : 0; var paddingTop = args.canvas.style.paddingTop ? parseInt(args.canvas.style.paddingTop) : 0; var borderLeft = args.canvas.style.borderLeftWidth ? parseInt(args.canvas.style.borderLeftWidth) : 0; var borderTop = args.canvas.style.borderTopWidth ? parseInt(args.canvas.style.borderTopWidth) : 0; if (navigator.userAgent.indexOf('Firefox') > 0) { x += parseInt(document.body.style.borderLeftWidth) || 0; y += parseInt(document.body.style.borderTopWidth) || 0; } return [x + paddingLeft + borderLeft, y + paddingTop + borderTop]; }; // // This function determines whther a canvas is fixed (CSS positioning) or not. If not it returns // false. If it is then the element that is fixed is returned (it may be a parent of the canvas). // // @param args object An object consisting of: // o canvas // OR // // @return Either false or the fixed positioned element // RGraph.isFixed = function () { var args = RGraph.getArgs(arguments, 'canvas'); var i = 0; while (args.canvas && args.canvas.tagName.toLowerCase() != 'body' && i < 99) { if (args.canvas.style.position == 'fixed') { return args.canvas; } args.canvas = args.canvas.offsetParent; } return false; }; // // Registers a graph object (used when the canvas is redrawn) // // @param args object An object consisting of: // o object // OR // // @param object obj The object to be registered // RGraph.register = function () { var args = RGraph.getArgs(arguments, 'object'); // Allow the registration of functions if (typeof args.object === 'function') { var func = args.object; // Register a shell object var temp = function () { this.id = null; this.isFunction = true; this.canvas = {id: null}; this.getObjectByXY = function (){return false;}; this.get = function (){}; this.set = function (){}; this.draw = function (){func();}; }; args.object = new temp(); } // Checking this property ensures the object is only // registered once if (!args.object.get('noregister') && args.object.get('register') !== false) { RGraph.ObjectRegistry.add(args.object); args.object.set('register', false); } }; // // Causes all registered objects to be redrawn // // @param args object An object consisting of: // o color // OR // // @param string An optional color to use to clear the canvas // RGraph.redraw = function () { var args = RGraph.getArgs(arguments, 'color'); var objectRegistry = RGraph.ObjectRegistry.objects.byCanvasID; // if the argument is a canvas object (ie not a color string) then // call .redrawCanvas instead if ( typeof args.color === 'object' && args.color && typeof args.color.toString === 'function' && typeof args.color.toString().indexOf === 'function' && args.color.toString().indexOf('HTMLCanvasElement') > -1) { var opt = {canvas: args.color}; // Has a color been given as well? if (arguments[1]) { opt.color = arguments[1]; } return RGraph.redrawCanvas(opt); } // Get all of the canvas tags on the page var tags = document.getElementsByTagName('canvas'); for (var i=0,len=tags.length; i 0 ? obj.data[0].length - 1 : 0 ); } } else if (obj.type === 'waterfall') { obj.set( 'backgroundGridVlinesCount', obj.data.length + (properties.total ? 1 : 0) ); // Align the vertical lines for the bar } else if (obj.type === 'bar') { // 13/12/2018 // // Updated to be the same as the number of data points // obj.set('backgroundGridVlinesCount', obj.data.length); // Align the vertical lines for the Scatter } else if (obj.type === 'scatter') { if (typeof properties.backgroundGridVlinesCount !== 'number') { // Set the number of grid lines to the same // as the number of labels if (RGraph.isArray(properties.xaxisLabels) && properties.xaxisLabels.length) { obj.set('backgroundGridVlinesCount', properties.xaxisLabels.length); // No labels - set the number of grid lines // to 10 } else { obj.set('backgroundGridVlinesCount', 10); } } // Gantt } else if (obj.type === 'gantt') { if (typeof obj.get('backgroundGridVlinesCount') === 'number') { // Nothing to do here } else { obj.set('backgroundGridVlinesCount', properties.xaxisScaleMax); } obj.set('backgroundGridHlinesCount', obj.data.length); // HBar } else if (obj.type === 'hbar' && RGraph.isNull(properties.backgroundGridHlinesCount) ) { obj.set('backgroundGridHlinesCount', obj.data.length); } } var vsize = ((cacheCanvas.width - marginLeft - marginRight)) / properties.backgroundGridVlinesCount; var hsize = (cacheCanvas.height - marginTop - marginBottom) / properties.backgroundGridHlinesCount; obj.set('backgroundGridVsize', vsize); obj.set('backgroundGridHsize', hsize); } obj.context.beginPath(); cacheContext.lineWidth = properties.backgroundGridLinewidth ? properties.backgroundGridLinewidth : 1; cacheContext.strokeStyle = properties.backgroundGridColor; // Dashed background grid if (properties.backgroundGridDashed && typeof cacheContext.setLineDash == 'function') { cacheContext.setLineDash([3,5]); } // Dotted background grid if (properties.backgroundGridDotted && typeof cacheContext.setLineDash == 'function') { cacheContext.setLineDash([1,3]); } // Custom linedash if (RGraph.isArray(properties.backgroundGridDashArray)) { cacheContext.setLineDash(properties.backgroundGridDashArray); } obj.context.beginPath(); // Draw the horizontal lines if (properties.backgroundGridHlines) { height = (cacheCanvas.height - marginBottom) var hsize = properties.backgroundGridHsize; for (y=marginTop; y<=(height+1); y+=hsize) { cacheContext.moveTo(marginLeft, Math.round(y)); cacheContext.lineTo(args.object.canvas.width - marginRight, Math.round(y)); if ( args.object.properties.variant === '3d' && args.object.type === 'bar' && args.object.properties.backgroundGridThreedYaxis) { cacheContext.moveTo(marginLeft, Math.round(y)); cacheContext.lineTo( marginLeft - args.object.properties.variantThreedOffsetx, Math.round(y) + args.object.properties.variantThreedOffsety ); } } } // Draw the vertical lines if (properties.backgroundGridVlines) { var width = (cacheCanvas.width - marginRight); var vsize = properties.backgroundGridVsize; for (var x=marginLeft; Math.round(x)<=width; x+=vsize) { cacheContext.moveTo(Math.round(x), marginTop); cacheContext.lineTo(Math.round(x), obj.canvas.height - marginBottom); } } if (properties.backgroundGridBorder) { // Make sure a rectangle, the same colour as the grid goes around the graph cacheContext.strokeStyle = properties.backgroundGridColor; cacheContext.strokeRect(Math.round(marginLeft), Math.round(marginTop), obj.canvas.width - marginLeft - marginRight, obj.canvas.height - marginTop - marginBottom); } } cacheContext.stroke(); // Ensure the grid is drawn before continuing cacheContext.beginPath(); cacheContext.closePath(); } // Now a cached draw in newer browsers RGraph.cachedDraw( args.object, args.object.uid + '_background', func ); // If it's a bar and 3D variant, translate if (variant == '3d') { args.object.context.restore(); } // Reset the line dash if (typeof args.object.context.setLineDash == 'function') { //args.object.context.setLineDash([1, 0]); // Old args.object.context.setLineDash([]); // New - should be faster } args.object.context.stroke(); // // Draw the backgroundBorder if requested // if (properties.backgroundBorder) { var color = RGraph.isString(properties.backgroundBorderColor) ? properties.backgroundBorderColor : '#aaa'; var linewidth = RGraph.isNumber(properties.backgroundBorderLinewidth) ? properties.backgroundBorderLinewidth : 1; // Dashed background border if (properties.backgroundBorderDashed && typeof args.object.context.setLineDash == 'function') { args.object.context.setLineDash([3,5]); } // Dotted background grid if (properties.backgroundBorderDotted && typeof args.object.context.setLineDash == 'function') { args.object.context.setLineDash([1,3]); } // Custom linedash if (RGraph.isArray(properties.backgroundBorderDashArray)) { args.object.context.setLineDash(properties.backgroundBorderDashArray); } args.object.path( 'b lc square lw % r % % % % s %', linewidth, args.object.properties.marginLeft, args.object.properties.marginTop, args.object.canvas.width - args.object.properties.marginLeft - args.object.properties.marginRight, args.object.canvas.height - args.object.properties.marginTop - args.object.properties.marginBottom, color ); // Reset the linedash //args.object.context.setLineDash([1,0]); // Old args.object.context.setLineDash([]);// New - should be faster } // Draw the title if one is set if ( typeof args.object.properties.title === 'string') { RGraph.drawTitle(args.object); } // Fire the background event RGraph.fireCustomEvent(args.object, 'background'); }; // // Formats a number with thousand separators so it's easier to read // // @param args object An object consisting of: // o object // o number // o unitspre // o unitspost // o point // o thousand // o formatter // OR // // THESE ARE OLDER ARGS: // // @param object obj The chart object // @param integer num The number to format // @param string The (optional) string to prepend to the string // @param string The (optional) string to append to the string // @return string The formatted number // RGraph.numberFormat = function (args) { var i; var prepend = args.unitspre ? String(args.unitspre) : ''; var append = args.unitspost ? String(args.unitspost) : ''; var output = ''; var decimal = ''; var decimal_seperator = typeof args.point === 'string' ? args.point : '.'; var thousand_seperator = typeof args.thousand === 'string' ? args.thousand : ','; RegExp.$1 = ''; var i,j; if (typeof args.formatter === 'function') { return (args.formatter)(args); } // Ignore the preformatted version of "1e-2" if (String(args.number).indexOf('e') > 0) { return String(prepend + String(args.number) + append); } // We need then number as a string args.number = String(args.number); // Take off the decimal part - we re-append it later if (args.number.indexOf('.') > 0) { var tmp = args.number; args.number = args.number.replace(/\.(.*)/, ''); // The front part of the number decimal = tmp.replace(/(.*)\.(.*)/, '$2'); // The decimal part of the number } // Thousand separator //var separator = arguments[1] ? String(arguments[1]) : ','; var seperator = thousand_seperator; // Work backwards adding the thousand separators // // ** i is a local variable at this poin ** var foundPoint; for (i=(args.number.length - 1),j=0; i>=0; j++,i--) { var character = args.number.charAt(i); if ( j % 3 == 0 && j != 0) { output += seperator; } // // Build the output // output += character; } // // Now need to reverse the string // var rev = output; output = ''; for (i=(rev.length - 1); i>=0; i--) { output += rev.charAt(i); } // Tidy up //output = output.replace(/^-,/, '-'); if (output.indexOf('-' + args.thousand) == 0) { output = '-' + output.substr(('-' + args.thousand).length); } // Reappend the decimal if (decimal.length) { output = output + decimal_seperator + decimal; decimal = ''; RegExp.$1 = ''; } // Minor bugette if (output.charAt(0) == '-') { output = output.replace(/-/, ''); prepend = '-' + prepend; } // Get rid of leading commas output = output.replace(/^,+/,''); return prepend + output + append; }; // // Draws horizontal coloured bars on something like the bar, line or scatter // // @param args object An object consisting of: // o object // RGraph.drawBars = function () { var args = RGraph.getArgs(arguments, 'object'), properties = args.object.properties, hbars = properties.backgroundHbars; if (hbars === null) { return; } // // Draws a horizontal bar // args.object.context.beginPath(); for (var i=0,len=hbars.length; i args.object.scale2.max) start = args.object.scale2.max; if (RGraph.isNull(length)) length = args.object.scale2.max - start; if (start + length > args.object.scale2.max) length = args.object.scale2.max - start; if (start + length < (-1 * args.object.scale2.max) ) length = (-1 * args.object.scale2.max) - start; if (properties.xaxisPosition == 'center' && start == args.object.scale2.max && length < (args.object.scale2.max * -2)) { length = args.object.scale2.max * -2; } // // Draw the bar // var x = properties.marginLeft; var y = args.object.getYCoord(start); var w = args.object.canvas.width - properties.marginLeft - properties.marginRight; var h = args.object.getYCoord(start + length) - y; // Accommodate Opera :-/ if (RGraph.ISOPERA != -1 && properties.xaxisPosition == 'center' && h < 0) { h *= -1; y = y - h; } // // Account for X axis at the top // if (properties.xaxisPosition == 'top') { y = args.object.canvas.height - y; h *= -1; } args.object.context.fillStyle = color; args.object.context.fillRect(x, y, w, h); } // // // If the X axis is at the bottom, and a negative max is given, warn the user // if (args.object.get('xaxisPosition') == 'bottom' && (hbars[i][0] < 0 || (hbars[i][1] + hbars[i][1] < 0)) ) { // alert('[' + args.object.type.toUpperCase() + ' (ID: ' + args.object.id + ') BACKGROUND HBARS] You have a negative value in one of your background hbars values, whilst the X axis is in the center'); // } // // var ystart = (args.object.grapharea - (((hbars[i][0] - args.object.scale2.min) / (args.object.scale2.max - args.object.scale2.min)) * args.object.grapharea)); // //var height = (Math.min(hbars[i][1], args.object.max - hbars[i][0]) / (args.object.scale2.max - args.object.scale2.min)) * args.object.grapharea; // var height = args.object.getYCoord(hbars[i][0]) - args.object.getYCoord(hbars[i][1]); // // // Account for the X axis being in the center // if (args.object.get('xaxisPosition') == 'center') { // ystart /= 2; // //height /= 2; // } // // ystart += args.object.get('marginTop') // // var x = args.object.get('marginLeft'); // var y = ystart - height; // var w = args.object.canvas.width - args.object.get('marginLeft') - args.object.get('marginRight'); // var h = height; // // // Accommodate Opera :-/ // if (navigator.userAgent.indexOf('Opera') != -1 && args.object.get('xaxisPosition') == 'center' && h < 0) { // h *= -1; // y = y - h; // } // // // // // Account for X axis at the top // // // //if (args.object.get('xaxisPosition') == 'top') { // // y = args.object.canvas.height - y; // // h *= -1; // //} // // //args.object.context.fillStyle = hbars[i][2]; // //args.object.context.fillRect(x, y, w, h); // //} }; // // Draws in-graph labels. // // @param args object An object consisting of: // o object // OR // // @param object obj The graph object // RGraph.drawInGraphLabels = function () { var args = RGraph.getArgs(arguments, 'object'); var properties = args.object.properties, labels = properties.labelsIngraph, labels_processed = []; // Defaults var fgcolor = 'black', bgcolor = 'white', direction = 1; if (!labels) { return; } // Get the text configuration var textConf = RGraph.getTextConf({ object: args.object, prefix: 'labelsIngraph' }); // // Preprocess the labels array. Numbers are expanded // for (var i=0,len=labels.length; i 0) { for (var i=0,len=labels_processed.length; i 0) { var x = ((args.object.type == 'bar' ? coords[0] + (coords[2] / 2) : coords[0])) + (properties.labelsIngraphOffsetx || 0); var y = (args.object.type == 'bar' ? coords[1] + (coords[3] / 2) : coords[1]) + (properties.labelsIngraphOffsety || 0); var length = typeof labels_processed[i][4] === 'number' ? labels_processed[i][4] : 25; args.object.context.beginPath(); args.object.context.fillStyle = 'black'; args.object.context.strokeStyle = 'black'; if (args.object.type === 'bar') { // // X axis at the top // if (args.object.get('xaxisPosition') == 'top') { length *= -1; } if (properties.variant == 'dot') { args.object.context.moveTo(Math.round(x), args.object.coords[i][1] - 5); args.object.context.lineTo(Math.round(x), args.object.coords[i][1] - 5 - length); var text_x = Math.round(x); var text_y = args.object.coords[i][1] - 5 - length; } else if (properties.variant == 'arrow') { args.object.context.moveTo(Math.round(x), args.object.coords[i][1] - 5); args.object.context.lineTo(Math.round(x), args.object.coords[i][1] - 5 - length); var text_x = Math.round(x); var text_y = args.object.coords[i][1] - 5 - length; } else { args.object.context.arc(Math.round(x), y, 2.5, 0, 6.28, 0); args.object.context.moveTo(Math.round(x), y); args.object.context.lineTo(Math.round(x), y - length); var text_x = Math.round(x); var text_y = y - length; } args.object.context.stroke(); args.object.context.fill(); } else { if ( typeof labels_processed[i] == 'object' && typeof labels_processed[i][3] == 'number' && labels_processed[i][3] == -1 ) { // Draw an up arrow drawUpArrow(x, y) var valign = 'top'; var text_x = x; var text_y = y + 5 + length; } else { var text_x = x; var text_y = y - 5 - length; if (text_y < 5 && (typeof labels_processed[i] === 'string' || typeof labels_processed[i][3] === 'undefined')) { text_y = y + 5 + length; var valign = 'top'; } if (valign === 'top') { /// Draw an down arrow drawUpArrow(x, y); } else { /// Draw an up arrow drawDownArrow(x, y); } } args.object.context.fill(); } args.object.context.beginPath(); // Foreground color if ((typeof labels_processed[i] === 'object' && typeof labels_processed[i][1] === 'string')) { args.object.context.fillStyle = labels_processed[i][1]; } else { args.object.context.fillStyle = properties.labelsIngraphColor; } RGraph.text({ object: args.object, font: textConf.font, size: textConf.size, color: args.object.context.fillStyle || textConf.color, bold: textConf.bold, italic: textConf.italic, x: text_x, y: text_y + (args.object.properties.textAccessible ? 2 : 0), text: (typeof labels_processed[i] === 'object' && typeof labels_processed[i][0] === 'string') ? labels_processed[i][0] : labels_processed[i], valign: valign || 'bottom', halign: 'center', bounding: true, 'bounding.fill': (typeof labels_processed[i] === 'object' && typeof labels_processed[i][2] === 'string') ? labels_processed[i][2] : 'white', tag: 'labels ingraph' }); args.object.context.fill(); } // Draws a down arrow function drawUpArrow (x, y) { args.object.context.moveTo(Math.round(x), y + 5); args.object.context.lineTo(Math.round(x), y + 5 + length); args.object.context.stroke(); args.object.context.beginPath(); // This draws the arrow args.object.context.moveTo(Math.round(x), y + 5); args.object.context.lineTo(Math.round(x) - 3, y + 10); args.object.context.lineTo(Math.round(x) + 3, y + 10); args.object.context.closePath(); } // Draw an up arrow function drawDownArrow (x, y) { args.object.context.moveTo(Math.round(x), y - 5); args.object.context.lineTo(Math.round(x), y - 5 - length); args.object.context.stroke(); args.object.context.beginPath(); // This draws the arrow args.object.context.moveTo(Math.round(x), y - 5); args.object.context.lineTo(Math.round(x) - 3, y - 10); args.object.context.lineTo(Math.round(x) + 3, y - 10); args.object.context.closePath(); } valign = undefined; } } } }; // // This function hides the crosshairs coordinates. This function // has no arguments // RGraph.hideCrosshairCoords = function () { var div = RGraph.Registry.get('coordinates.coords.div'); if ( div && div.style.opacity == 1 && div.__object__.get('crosshairsCoordsFadeout') ) { var style = RGraph.Registry.get('coordinates.coords.div').style; setTimeout(function() {style.opacity = 0.9;}, 25); setTimeout(function() {style.opacity = 0.8;}, 50); setTimeout(function() {style.opacity = 0.7;}, 75); setTimeout(function() {style.opacity = 0.6;}, 100); setTimeout(function() {style.opacity = 0.5;}, 125); setTimeout(function() {style.opacity = 0.4;}, 150); setTimeout(function() {style.opacity = 0.3;}, 175); setTimeout(function() {style.opacity = 0.2;}, 200); setTimeout(function() {style.opacity = 0.1;}, 225); setTimeout(function() {style.opacity = 0;}, 250); setTimeout(function() {style.display = 'none';}, 275); } }; // // Draws the3D axes/background // // @param args object An object consisting of: // o object // OR // // @param object obj The chart object // RGraph.draw3DAxes = function () { var args = RGraph.getArgs(arguments, 'object'); var properties = args.object.properties; var marginLeft = args.object.marginLeft, marginRight = args.object.marginRight, marginTop = args.object.marginTop, marginBottom = args.object.marginBottom, xaxispos = properties.xaxisPosition, graphArea = args.object.canvas.height - marginTop - marginBottom, halfGraphArea = graphArea / 2, offsetx = properties.variantThreedOffsetx, offsety = properties.variantThreedOffsety, xaxis = properties.variantThreedXaxis, yaxis = properties.variantThreedYaxis // // Draw the 3D Y axis // if (yaxis) { RGraph.draw3DYAxis(args.object); } // X axis if (xaxis) { if (xaxispos === 'center') { args.object.path( 'b m % % l % % l % % l % % c s #aaa f %', marginLeft,marginTop + halfGraphArea, marginLeft + offsetx,marginTop + halfGraphArea - offsety, args.object.canvas.width - marginRight + offsetx,marginTop + halfGraphArea - offsety, args.object.canvas.width - marginRight,marginTop + halfGraphArea, properties.variantThreedXaxisColor ); } else { if (args.object.type === 'hbar') { var xaxisYCoord = args.object.canvas.height - args.object.properties.marginBottom; } else { var xaxisYCoord = args.object.getYCoord(0); } args.object.path( 'm % % l % % l % % l % % c s #aaa f %', marginLeft,xaxisYCoord, marginLeft + offsetx,xaxisYCoord - offsety, args.object.canvas.width - marginRight + offsetx,xaxisYCoord - offsety, args.object.canvas.width - marginRight,xaxisYCoord, properties.variantThreedXaxisColor ); } } }; // // Draws the3D Y axis/background // // @param args object An object consisting of: // o object // OR // // @param object obj The chart object // RGraph.draw3DYAxis = function (args) { var args = RGraph.getArgs(arguments, 'object'); var properties = args.object.properties; var marginLeft = args.object.marginLeft, marginRight = args.object.marginRight, marginTop = args.object.marginTop, marginBottom = args.object.marginBottom, xaxispos = properties.xaxisPosition, graphArea = args.object.canvas.height - marginTop - marginBottom, halfGraphArea = graphArea / 2, offsetx = properties.variantThreedOffsetx, offsety = properties.variantThreedOffsety, yaxisFill = properties.variantThreedYaxisColor || '#ddd'; // Y axis // Commented out the if condition because of drawing oddities //if (!properties.noaxes && !properties.noyaxis) { if ( (args.object.type === 'hbar' || args.object.type === 'bar') && properties.yaxisPosition === 'center') { var x = ((args.object.canvas.width - marginLeft - marginRight) / 2) + marginLeft; } else if ((args.object.type === 'hbar' || args.object.type === 'bar') && properties.yaxisPosition === 'right') { var x = args.object.canvas.width - marginRight; } else { var x = marginLeft; } args.object.path( 'b m % % l % % l % % l % % s #aaa f % b', // Don't know the b at the end is needed x,marginTop, x + offsetx, marginTop - offsety, x + offsetx, args.object.canvas.height - marginBottom - offsety, x,args.object.canvas.height - marginBottom, properties.variantThreedYaxisColor ); //} }; // // Draws a filled rectangle with curvy corners // // @param args object An object consisting of: // o context // o x // o y // o w // o h // o roundtl // o roundtr // o roundbl // o roundbr // OR // // @param context object The context // @param x number The X coordinate (top left of the square) // @param y number The Y coordinate (top left of the square) // @param w number The width of the rectangle // @param h number The height of the rectangle // @param number The radius of the curved corners // @param boolean Whether the top left corner is curvy // @param boolean Whether the top right corner is curvy // @param boolean Whether the bottom right corner is curvy // @param boolean Whether the bottom left corner is curvy // RGraph.roundedRect = function () { var args = RGraph.getArgs(arguments, 'context,x,y,width,height,radius,roundtl,roundtr,roundbl,roundbr'); // The corner radius var r = args.radius ? args.radius : 3; // Change the radius based on the smallest width or height r = Math.min( Math.min(args.width, args.height) / 2, args.radius ); // The corners var corner_tl = (args.roundtl === false) ? false : true, corner_tr = (args.roundtr === false) ? false : true, corner_bl = (args.roundbl === false) ? false : true, corner_br = (args.roundbr === false) ? false : true; args.context.beginPath(); args.context.moveTo(args.x, args.y + r); // Top left corner if (corner_tl) { args.context.arc(args.x + r, args.y + r, r, RGraph.PI, RGraph.PI + RGraph.HALFPI, false); } else { args.context.lineTo(args.x, args.y); args.context.lineTo(args.x + r, args.y); } // Top right corner if (corner_tr) { args.context.arc(args.x + args.width - r, args.y + r, r, RGraph.PI + RGraph.HALFPI, 0, false); } else { args.context.lineTo(args.x + args.width, args.y); args.context.lineTo(args.x + args.width, args.y + r); } // Bottom right corner if (corner_br) { args.context.arc(args.x + args.width - r, args.y + args.height - r, r, 0, RGraph.HALFPI, false); } else { args.context.lineTo(args.x + args.width, args.y + args.height); args.context.lineTo(args.x + args.width - r, args.y + args.height); } // Bottom left corner if (corner_bl) { args.context.arc(args.x + r, args.y - r + args.height, r, RGraph.HALFPI, RGraph.PI, false); } else { args.context.lineTo(args.x, args.y + args.height); args.context.lineTo(args.x, args.y + args.height - r); } args.context.closePath(); }; // // Hides the zoomed canvas. // // UPDATE 14th Oct 2019 // Zoom has been removed for some time now so this is now commented out // //RGraph.hideZoomedCanvas = //RGraph.HideZoomedCanvas = function () //{ // var interval = 10; // var frames = 15; // // if (typeof RGraph.zoom_image === 'object') { // var obj = RGraph.zoom_image.obj; // var properties = obj.properties; // } else { // return; // } // // if (properties.zoomFadeOut) { // for (var i=frames,j=1; i>=0; --i, ++j) { // if (typeof RGraph.zoom_image === 'object') { // setTimeout("RGraph.zoom_image.style.opacity = " + String(i / 10), j * interval); // } // } // // if (typeof RGraph.zoom_background === 'object') { // setTimeout("RGraph.zoom_background.style.opacity = " + String(i / frames), j * interval); // } // } // // if (typeof RGraph.zoom_image === 'object') { // setTimeout("RGraph.zoom_image.style.display = 'none'", properties.zoomFadeOut ? (frames * interval) + 10 : 0); // } // // if (typeof RGraph.zoom_background === 'object') { // setTimeout("RGraph.zoom_background.style.display = 'none'", properties.zoomFadeOut ? (frames * interval) + 10 : 0); // } //}; // // Adds an event handler // // @param args object An object consisting of: // o object // o name // o func // OR // // @param object obj The graph object // @param string event The name of the event, eg ontooltip // @param object func The callback function // RGraph.addCustomEventListener = function () { var args = RGraph.getArgs(arguments, 'object,name,func'); // Initialise the events array if necessary if (typeof RGraph.events[args.object.uid] === 'undefined') { RGraph.events[args.object.uid] = []; } // Prepend "on" if necessary if (args.name.substr(0, 2) !== 'on') { args.name = 'on' + args.name; } RGraph.events[args.object.uid].push([ args.object, args.name, args.func ]); return RGraph.events[args.object.uid].length - 1; }; // // Used to fire one of the RGraph custom events // // @param args object An object consisting of: // o object // o name // OR // // @param object obj The graph object that fires the event // @param string event The name of the event to fire // RGraph.fireCustomEvent = function () { var args = RGraph.getArgs(arguments, 'object,name'); // Prepend the name with "on" if necessary if (args.name.substr(0,2) !== 'on') { args.name = 'on' + args.name; } if (args.object && args.object.isrgraph) { // This allows the eventsMouseout property to work // (for some reason...) // // 25/10/19 - Taken out // //if (args.name.match(/(on)?mouseout/) && typeof args.object.properties.eventsMouseout === 'function') { // (args.object.properties.eventsMouseout)(args.object); //} // DOM1 style of adding custom events if (args.object[args.name]) { (args.object[args.name])(args.object); } var uid = args.object.uid; if ( typeof uid === 'string' && typeof RGraph.events === 'object' && typeof RGraph.events[uid] === 'object' && RGraph.events[uid].length > 0) { for(var j=0; j=0; --i) { var obj = objects[i].getObjectByXY(args.event); if (obj) { return obj; } } }; // // Retrieves the relevant objects based on the X/Y position. // NOTE This function returns an array of objects // // @param args object An object consisting of: // o event // OR // // @param object e The event object // @return An array of pertinent objects. Note the there may be only one object // RGraph.ObjectRegistry.getObjectsByXY = function () { var args = RGraph.getArgs(arguments, 'event'); var canvas = args.event.target, ret = [], objects = RGraph.ObjectRegistry.getObjectsByCanvasID(canvas.id); // Retrieve objects "front to back" for (var i=(objects.length - 1); i>=0; --i) { var obj = objects[i].getObjectByXY(args.event); if (obj) { ret.push(obj); } } return ret; }; // // Retrieves the object with the corresponding UID // // @param args object An object consisting of: // o uid // OR // // @param string uid The UID to get the relevant object for // RGraph.ObjectRegistry.get = RGraph.ObjectRegistry.getObjectByUID = function () { var args = RGraph.getArgs(arguments, 'uid'); var objects = RGraph.ObjectRegistry.objects.byUID; for (var i=0,len=objects.length; i= args.cx && args.y >= args.cy) { angle += RGraph.TWOPI; } else if (args.x >= args.cx && args.y < args.cy) { angle = (RGraph.HALFPI - angle) + (RGraph.PI + RGraph.HALFPI); } else if (args.x < args.cx && args.y < args.cy) { angle += RGraph.PI; } else { angle = RGraph.PI - angle; } // // Upper and lower limit checking // if (angle > RGraph.TWOPI) { angle -= RGraph.TWOPI; } return angle; }; // // This function returns the distance between two points. In effect the // radius of an imaginary circle that is centered on x1 and y1. The name // of this function is derived from the word "Hypoteneuse", which in // trigonmetry is the longest side of a triangle // // @param args object An object consisting of: // o x1 // o y1 // o x2 // o y2 // OR // // @param number x1 The original X coordinate // @param number y1 The original Y coordinate // @param number x2 The target X coordinate // @param number y2 The target Y coordinate // RGraph.getHypLength = function () { var args = RGraph.getArgs(arguments, 'x1,y1,x2,y2'); return Math.sqrt(((args.x2 - args.x1) * (args.x2 - args.x1)) + ((args.y2 - args.y1) * (args.y2 - args.y1))); }; // // This function gets the end point (X/Y coordinates) of a given radius. // You pass it the center X/Y, the angle and the radius and this function // will return the endpoint X/Y coordinates. // // @param args object An object consisting of: // o cx // o cy // o angle // o radius // OR // // @param number cx The center X coord // @param number cy The center Y coord // @param number angle The angle that the "line" is at from the cx/cy coords // @param number radius The length of the radius // // RGraph.getRadiusEndPoint = function () { var args = RGraph.getArgs(arguments, 'cx,cy,angle,radius'); var x = args.cx + (Math.cos(args.angle) * args.radius); var y = args.cy + (Math.sin(args.angle) * args.radius); return [x, y]; }; // // This installs all of the event listeners // // @param args object An object consisting of: // o object // OR // // @param object object The chart object // RGraph.installEventListeners = function () { var args = RGraph.getArgs(arguments, 'object'); var properties = args.object.properties; // // If this function exists, then the dynamic file has been included. // if (RGraph.installCanvasClickListener) { RGraph.installWindowMousedownListener(args.object); RGraph.installWindowMouseupListener(args.object); RGraph.installCanvasMousemoveListener(args.object); RGraph.installCanvasMouseupListener(args.object); RGraph.installCanvasMousedownListener(args.object); RGraph.installCanvasClickListener(args.object); } else if ( RGraph.hasTooltips(args.object) || properties.adjustable || properties.annotatable || properties.contextmenu || properties.keyInteractive || typeof args.object.onclick === 'function' || typeof args.object.onmousemove === 'function' || typeof args.object.onmouseout === 'function' || typeof args.object.onmouseover === 'function' ) { alert('[RGRAPH] You appear to have used dynamic features but not included the file: RGraph.common.dynamic.js'); } }; // // Loosly mimicks the PHP function print_r(); // // @param args object An object consisting of: // o object - You only really need to give this argument // o alert // o indent // o count // RGraph.pr = function (obj) { var args = RGraph.getArgs(arguments, 'object,alert,indent,counter'); var indent = (args.indent ? args.indent : ' '); var str = ''; var counter = typeof args.counter == 'number' ? args.counter : 0; if (counter >= 3) { return ''; } switch (typeof obj) { case 'string': str += args.object + ' (' + (typeof args.object) + ', ' + args.object.length + ')'; break; case 'number': str += args.object + ' (' + (typeof args.object) + ')'; break; case 'boolean': str += args.object + ' (' + (typeof args.object) + ')'; break; case 'function': str += 'function () {}'; break; case 'undefined': str += 'undefined'; break; case 'null': str += 'null'; break; case 'object': // In case of null if (RGraph.isNull(args.object)) { str += 'null'; } else { str += 'Object {' + '\n' for (var j in obj) { str += indent + ' ' + j + ' => ' + RGraph.pr(args.object[j], true, indent + ' ', counter + 1) + '\n'; } str += indent + '}'; } break; default: str += 'Unknown type: ' + typeof args.object + ''; break; } // // Finished, now either return if we're in a recursed call, or alert() // if we're not. // if (!args.alert) { alert(str); } return str; }; // // Produces a dashed line // // @param args object An object consisting of: // o context // o x1 // o y1 // o x2 // o y2 // o size // OR // // @param object context The 2D context // @param number x1 The start X coordinate // @param number y1 The start Y coordinate // @param number x2 The end X coordinate // @param number y2 The end Y coordinate // @param number The size of the dashes // RGraph.dashedLine = function() { var args = RGraph.getArgs(arguments, 'context,x1,y1,x2,y2,size'), dx = args.x2 - args.x1, dy = args.y2 - args.y1, num = Math.floor(Math.sqrt((dx * dx) + (dy * dy)) / (args.size || 3)), xLen = dx / num, yLen = dy / num, count = 0; do { if (count % 2 == 0 && count > 0) { args.context.lineTo(args.x1, args.y1); } else { args.context.moveTo(args.x1, args.y1); } args.x1 += xLen; args.y1 += yLen; count++; } while(count <= num); }; // // Makes an AJAX call. It calls the given callback (a function) when ready // // @param args object An object consisting of: // o url // o callback // OR // // @param string url The URL to retrieve // @param function callback A function that is called when the response is ready, // there's an example below called "myCallback". // RGraph.AJAX = function () { var args = RGraph.getArgs(arguments, 'url,callback'); // Mozilla, Safari, ... if (window.XMLHttpRequest) { var httpRequest = new XMLHttpRequest(); // MSIE } else if (window.ActiveXObject) { var httpRequest = new ActiveXObject("Microsoft.XMLHTTP"); } httpRequest.onreadystatechange = function () { if (this.readyState == 4 && this.status == 200) { this.__user_callback__ = args.callback; this.__user_callback__(this.responseText); } } httpRequest.open('GET', args.url, true); // Set a Cache-Control header if (httpRequest && httpRequest.setRequestHeader) { httpRequest.setRequestHeader('Cache-Control', 'no-cache'); } httpRequest.send(); }; // // Makes an AJAX POST request. It calls the given callback (a function) when ready // // @param args object An object consisting of: // o url // o data // o callback // OR // // @param string url The URL to retrieve // @param object data The POST data // @param function callback A function that is called when the response is ready, there's an example below // called "myCallback". // // DO NOT REMOVE THIS ALIAS! RGraph.AJAX.post = RGraph.AJAX.POST = function () { var args = RGraph.getArgs(arguments, 'url,data,callback'); // Used when building the POST string var crumbs = []; // Mozilla, Safari, ... if (window.XMLHttpRequest) { var httpRequest = new XMLHttpRequest(); // MSIE } else if (window.ActiveXObject) { var httpRequest = new ActiveXObject("Microsoft.XMLHTTP"); } httpRequest.onreadystatechange = function () { if (this.readyState == 4 && this.status == 200) { this.__user_callback__ = args.callback; this.__user_callback__(this.responseText); } } httpRequest.open('POST', args.url, true); httpRequest.setRequestHeader("Content-type","application/x-www-form-urlencoded"); for (i in args.data) { if (typeof i == 'string') { crumbs.push(i + '=' + encodeURIComponent(args.data[i])); } } httpRequest.send(crumbs.join('&')); }; // // Uses the above function but calls the call back passing a number as its argument // // @param args object An object consisting of: // o url // o callback // OR // // @param url string The URL to fetch // @param callback function Your callback function (which is passed the number as an argument) // RGraph.AJAX.getNumber = function () { var args = RGraph.getArgs(arguments, 'url,callback'); RGraph.AJAX(args.url, function () { var num = parseFloat(this.responseText); args.callback(num); }); }; // // Uses the above function but calls the call back passing a string as its argument // // @param args object An object consisting of: // o url // o callback // OR // // @param url string The URL to fetch // @param callback function Your callback function (which is passed the string as an argument) // RGraph.AJAX.getString = function () { var args = RGraph.getArgs(arguments, 'url,callback'); RGraph.AJAX(args.url, function () { var str = String(this.responseText); args.callback(str); }); }; // // Uses the above function but calls the call back passing JSON (ie a JavaScript object ) as its argument // // @param args object An object consisting of: // o url // o callback // OR // // @param url string The URL to fetch // @param callback function Your callback function (which is passed the JSON object as an argument) // RGraph.AJAX.getJSON = function () { var args = RGraph.getArgs(arguments, 'url,callback'); RGraph.AJAX(args.url, function () { var json = eval('(' + this.responseText + ')'); args.callback(json); }); }; // // Uses the above RGraph.AJAX function but calls the call back passing an array as its argument. // Useful if you're retrieving CSV data // // @param args object An object consisting of: // o url // o callback // o sparator (optional) // OR // // @param url string The URL to fetch // @param callback function Your callback function (which is passed the CSV/array as an argument) // @param string An OPTIONAL separator character // RGraph.AJAX.getCSV = function () { var args = RGraph.getArgs(arguments, 'url,callback,separator'); var separator = args.separator ? args.separator : ','; RGraph.AJAX(args.url, function () { var regexp = new RegExp(separator); var arr = this.responseText.split(regexp); // Convert the strings to numbers for (var i=0,len=arr.length;i')); div.style.fontFamily = args.font; div.style.fontWeight = args.bold ? 'bold' : 'normal'; div.style.fontSize = (args.size || 12) + 'pt'; //document.body.removeChild(div); RGraph.measuretext_cache[str] = [div.offsetWidth, div.offsetHeight]; return [div.offsetWidth, div.offsetHeight]; }; // // New text function. Accepts two arguments: // o opt - An object/hash/map of properties. This can consist of: // object The RGraph chart object (THIS OR BELOW OPTION IS REQUIRED) // context The canvases context. This can be given in // place of the above (THIS OR ABOVE OPTION IS REQUIRED) // x The X coordinate (REQUIRED) // y The Y coordinate (REQUIRED) // text The text to show (REQUIRED) // font The font to use // size The size of the text (in pt) // italic Whether the text should be italic or not // bold Whether the text should be bold or not // marker Whether to show a marker that indicates the X/Y coordinates // valign The vertical alignment // halign The horizontal alignment // bounding Whether to draw a bounding box for the text // boundingStroke The strokeStyle of the bounding box // boundingFill The fillStyle of the bounding box // accessible If false this will cause the text to be // rendered as native canvas text. DOM text otherwise // RGraph.text = function (args) { // Allow for the use of a single argument or two // 1. First handle two arguments if (arguments[0] && arguments[1] && (typeof arguments[1].text === 'string' || typeof arguments[1].text === 'number' ) ) { var obj = arguments[0], args = arguments[1]; // 2. The alternative is a single option } else { var obj = args.object; // // The text arg must be a string or a number // if (typeof args.text === 'number') { args.text = String(args.text); } } // Get the defaults for the text function from RGraph.text.defaults object for (var i in RGraph.text.defaults) { if (typeof i === 'string' && typeof args[i] === 'undefined') { args[i] = RGraph.text.defaults[i]; } } // Use DOM nodes to get better quality text. This option is BETA quality // code and most likely and will not work if you use 3D or if you use // your own transformations. function domtext () { // // Check the font property to see if it contains the italic keyword, // and if it does then take it out and set the italic property // if (String(args.size).toLowerCase().indexOf('italic') !== -1) { args.size = args.size.replace(/ *italic +/, ''); args.italic = true; } // Used for caching the DOM node var cacheKey = Math.abs(parseInt(args.x)) + '_' + Math.abs(parseInt(args.y)) + '_' + String(args.text).replace(/[^a-zA-Z0-9]+/g, '_') + '_' + obj.canvas.id; // Wrap the canvas in a DIV if (!obj.canvas.rgraph_domtext_wrapper) { var wrapper = document.createElement('div'); wrapper.id = obj.canvas.id + '_rgraph_domtext_wrapper'; wrapper.className = 'rgraph_domtext_wrapper'; // The wrapper can be configured to hide or show the // overflow with the textAccessibleOverflow option wrapper.style.overflow = obj.properties.textAccessibleOverflow != false && obj.properties.textAccessibleOverflow != 'hidden' ? 'visible' : 'hidden'; wrapper.style.width = obj.canvas.offsetWidth + 'px'; wrapper.style.height = obj.canvas.offsetHeight + 'px'; wrapper.style.cssFloat = obj.canvas.style.cssFloat; wrapper.style.display = obj.canvas.style.display || 'inline-block'; wrapper.style.position = obj.canvas.style.position || 'relative'; wrapper.style.left = obj.canvas.style.left; wrapper.style.right = obj.canvas.style.right; wrapper.style.top = obj.canvas.style.top; wrapper.style.bottom = obj.canvas.style.bottom; wrapper.style.width = obj.canvas.width + 'px'; wrapper.style.height = obj.canvas.height + 'px'; wrapper.style.lineHeight = 'initial'; obj.canvas.style.position = 'absolute'; obj.canvas.style.left = 0; obj.canvas.style.top = 0; obj.canvas.style.display = 'inline'; obj.canvas.style.cssFloat = 'none'; // This now (10/12/2022) skews ro the same // angle as the variantThreedAngle property if ((obj.type === 'bar' || obj.type === 'bipolar' || obj.type === 'hbar') && obj.properties.variant === '3d') { wrapper.style.transform = 'skewY(' + obj.properties.variantThreedAngle + 'rad)'; } obj.canvas.parentNode.insertBefore(wrapper, obj.canvas); // Remove the canvas from the DOM and put it in the wrapper obj.canvas.parentNode.removeChild(obj.canvas); wrapper.appendChild(obj.canvas); obj.canvas.rgraph_domtext_wrapper = wrapper; // TODO Add a subwrapper here } else { wrapper = obj.canvas.rgraph_domtext_wrapper; } var defaults = { size: 12, font: 'Arial', italic: 'normal', bold: 'normal', valign: 'bottom', halign: 'left', marker: true, color: context.fillStyle, bounding: { enabled: false, fill: 'rgba(255,255,255,0.7)', stroke: '#666', linewidth: 1 } } // Transform \n to the string [[RETURN]] which is then replaced // further down args.text = String(args.text).replace(/\r?\n/g, '[[RETURN]]'); // Create the node cache array that nodes // already created are stored in if (typeof RGraph.text.domNodeCache === 'undefined') { RGraph.text.domNodeCache = new Array(); } if (typeof RGraph.text.domNodeCache[obj.id] === 'undefined') { RGraph.text.domNodeCache[obj.id] = new Array(); } // Create the dimension cache array that node // dimensions are stored in if (typeof RGraph.text.domNodeDimensionCache === 'undefined') { RGraph.text.domNodeDimensionCache = new Array(); } if (typeof RGraph.text.domNodeDimensionCache[obj.id] === 'undefined') { RGraph.text.domNodeDimensionCache[obj.id] = new Array(); } // Create the DOM node if (!RGraph.text.domNodeCache[obj.id] || !RGraph.text.domNodeCache[obj.id][cacheKey]) { var span = document.createElement('span'); span.style.position = 'absolute'; span.style.display = 'inline'; span.className = ' rgraph_accessible_text' + ' rgraph_accessible_text_' + obj.id + ' rgraph_accessible_text_' + (args.tag || '').replace(/\./, '_') + ' rgraph_accessible_text_' + obj.type + ' ' + (args.cssClass || ''); // This is here to accommodate 3D charts // span.style.left = (args.x * (parseInt(obj.canvas.offsetWidth) / parseInt(obj.canvas.width))) + 'px'; span.style.top = (args.y * (parseInt(obj.canvas.offsetHeight) / parseInt(obj.canvas.height))) + 'px'; // This could be used for none-3d charts // //span.style.left = args.x + 'px'; //span.style.top = args.y + 'px'; span.style.color = args.color || defaults.color; span.style.fontFamily = args.font || defaults.font; span.style.fontWeight = args.bold ? 'bold' : defaults.bold; span.style.fontStyle = args.italic ? 'italic' : defaults.italic; span.style.fontSize = (args.size || defaults.size) + 'pt'; // Also see line-height setting a few lines down span.style.whiteSpace = 'nowrap'; span.style.lineHeight = RGraph.ISIE ? 'normal' : 'initial'; // Also see font-size setting a few lines up span.tag = args.tag; span.setAttribute('data-tag', args.tag); // CSS angled text. This should be conasidered BETA quality code at the moment, // but it seems to be OK. You may need to use the labelsOffsety when using this // option. if (typeof args.angle === 'number' && args.angle !== 0) { var coords = RGraph.measureText( args.text, args.bold, args.font, args.size ); //span.style.left = parseFloat(span.style.left) - coords[0] + 'px'; var hOrigin, vOrigin; if (args.halign === 'center') {hOrigin = '50%';} else if (args.halign === 'right') {hOrigin = '100%';} else {hOrigin = '0%';} if (args.valign === 'center') {vOrigin = '50%';} else if (args.valign === 'top') {vOrigin = '0%';} else {vOrigin = '100%';} span.style.transformOrigin = '{1} {2}'.format( hOrigin, vOrigin ); span.style.transform = 'rotate(' + args.angle + 'deg)'; } // Shadow span.style.textShadow = '{1}px {2}px {3}px {4}'.format( context.shadowOffsetX, context.shadowOffsetY, context.shadowBlur, context.shadowColor ); if (args.bounding) { span.style.border = '1px solid ' + (args['bounding.stroke'] || defaults.bounding.stroke); span.style.backgroundColor = args['bounding.fill'] || defaults.bounding.fill; span.style.borderWidth = typeof args['bounding.linewidth'] === 'number' ? args['bounding.linewidth'] : defaults.bounding.linewidth; } // Pointer events if ( (typeof obj.properties.textAccessiblePointerevents === 'undefined' || obj.properties.textAccessiblePointerevents) && obj.properties.textAccessiblePointerevents !== 'none' ) { span.style.pointerEvents = 'auto'; } else { span.style.pointerEvents = 'none'; } span.style.padding = args.bounding ? '2px' : null; // Changed to 2px on 16th January 2019 span.__text__ = args.text span.insertAdjacentHTML( 'afterbegin', args.text.replace('&', '&') .replace('<', '<') .replace('>', '>') .replace(/\[\[RETURN\]\]/g, '
') ); //span.innerHTML = args.text.replace('&', '&') // .replace('<', '<') // .replace('>', '>'); // Now replace the string [[RETURN]] with a
//span.innerHTML = span.innerHTML.replace(/\[\[RETURN\]\]/g, '
'); wrapper.appendChild(span); // Alignment defaults args.halign = args.halign || 'left'; args.valign = args.valign || 'bottom'; // Horizontal alignment if (args.halign === 'right') { span.style.left = parseFloat(span.style.left) - span.offsetWidth + 'px'; span.style.textAlign = 'right'; } else if (args.halign === 'center') { span.style.left = parseFloat(span.style.left) - (span.offsetWidth / 2) + 'px'; span.style.textAlign = 'center'; } // Vertical alignment if (args.valign === 'top') { // Nothing to do here } else if (args.valign === 'center') { span.style.top = parseFloat(span.style.top) - (span.offsetHeight / 2) + 'px'; } else { span.style.top = parseFloat(span.style.top) - span.offsetHeight + 'px'; } var offsetWidth = parseFloat(span.offsetWidth), offsetHeight = parseFloat(span.offsetHeight), top = parseFloat(span.style.top), left = parseFloat(span.style.left); RGraph.text.domNodeCache[obj.id][cacheKey] = span; RGraph.text.domNodeDimensionCache[obj.id][cacheKey] = { left: left, top: top, width: offsetWidth, height: offsetHeight }; span.id = cacheKey; } else { span = RGraph.text.domNodeCache[obj.id][cacheKey]; span.style.display = 'inline'; var offsetWidth = RGraph.text.domNodeDimensionCache[obj.id][cacheKey].width, offsetHeight = RGraph.text.domNodeDimensionCache[obj.id][cacheKey].height, top = RGraph.text.domNodeDimensionCache[obj.id][cacheKey].top, left = RGraph.text.domNodeDimensionCache[obj.id][cacheKey].left; } // If requested, draw a marker to indicate the coords if (args.marker) { obj.path( 'b m % % l % % m % % l % % s', args.x - 5, args.y, args.x + 5, args.y, args.x, args.y - 5, args.x, args.y + 5 ); } // // If its a drawing API text object then allow // for events and tooltips // if (obj.type === 'drawing.text') { // Mousemove //if (obj.properties.eventsMousemove) { // span.addEventListener('mousemove', function (e) {(obj.properties.eventsMousemove)(e, obj);}, false); //} // Click //if (obj.properties.eventsClick) { // span.addEventListener('click', function (e) {(obj.properties.eventsClick)(e, obj);}, false); //} // Tooltips if (obj.properties.tooltips) { span.addEventListener( obj.properties.tooltipsEvent.indexOf('mousemove') !== -1 ? 'mousemove' : 'click', function (e) { if ( !RGraph.Registry.get('tooltip') || RGraph.Registry.get('tooltip').__index__ !== 0 || RGraph.Registry.get('tooltip').__object__.uid != obj.uid ) { RGraph.hideTooltip(); RGraph.redraw(); RGraph.tooltip(obj, obj.properties.tooltips[0], args.x, args.y, 0, e); } }, false ); } } // Build the return value var ret = {}; ret.x = left; ret.y = top; ret.width = offsetWidth; ret.height = offsetHeight; ret.object = obj; ret.text = args.text; ret.tag = args.tag; // The reset() function clears the domNodeCache //// // @param object OPTIONAL You can pass in the canvas to limit the // clearing to that canvas. RGraph.text.domNodeCache.reset = function () { // Limit the clearing to a single canvas tag if (arguments[0]) { if (typeof arguments[0] === 'string') { var canvas = document.getElementById(arguments[0]) } else { var canvas = arguments[0]; } var nodes = RGraph.text.domNodeCache[canvas.id]; for (j in nodes) { var node = RGraph.text.domNodeCache[canvas.id][j]; if (node && node.parentNode) { node.parentNode.removeChild(node); } } RGraph.text.domNodeCache[canvas.id] = []; RGraph.text.domNodeDimensionCache[canvas.id] = []; // Clear all DOM text from all tags } else { for (i in RGraph.text.domNodeCache) { for (j in RGraph.text.domNodeCache[i]) { if (RGraph.text.domNodeCache[i][j] && RGraph.text.domNodeCache[i][j].parentNode) { RGraph.text.domNodeCache[i][j].parentNode.removeChild(RGraph.text.domNodeCache[i][j]); } } } RGraph.text.domNodeCache = []; RGraph.text.domNodeDimensionCache = []; } }; // // Helps you get hold of the SPAN tag nodes that hold the text on the chart // RGraph.text.find = function (args) { var span, nodes = []; if (args.object && args.object.isrgraph) { var id = args.object.id; } else if (args.id) { var id = typeof args.id === 'string' ? args.id : args.object.id; args.object = document.getElementById(id).__object__; } else { alert('[RGRAPH] You Must give either an object or an ID to the RGraph.text.find() function'); return false; } for (i in RGraph.text.domNodeCache[id]) { span = RGraph.text.domNodeCache[id][i]; // A full tag is given if (typeof args.tag === 'string' && args.tag === span.tag) { nodes.push(span); continue; } // A regex is given as the tag if (typeof args.tag === 'object' && args.tag.constructor.toString().indexOf('RegExp')) { var regexp = new RegExp(args.tag); if (regexp.test(span.tag)) { nodes.push(span); continue; } } // A full text is given if (typeof args.text === 'string' && args.text === span.__text__) { nodes.push(span); continue; } // Regex for the text is given // A regex is given as the tag if (typeof args.text === 'object' && args.text.constructor.toString().indexOf('RegExp')) { var regexp = new RegExp(args.text); if (regexp.test(span.__text__)) { nodes.push(span); continue; } } } // If a callback has been specified then call it whilst // passing it the text if (typeof args.callback === 'function') { (args.callback)({nodes: nodes, object:args.object}); } return nodes; }; // // Add the SPAN tag to the return value // ret.node = span; // // Save and then return the details of the text (but oly // if it's an RGraph object that was given) // if (obj && obj.isrgraph && obj.coordsText) { obj.coordsText.push(ret); } return ret; } // // An RGraph object can be given, or a string or the 2D rendering context // The coords are placed on the obj.coordsText variable ONLY if it's an RGraph object. The function // still returns the cooords though in all cases. // if (obj && obj.isrgraph) { var obj = obj; var canvas = obj.canvas; var context = obj.context; } else if (typeof obj == 'string') { var canvas = document.getElementById(obj); var context = canvas.getContext('2d'); var obj = canvas.__object__; } else if (typeof obj.getContext === 'function') { var canvas = obj; var context = canvas.getContext('2d'); var obj = canvas.__object__; } else if (obj.toString().indexOf('CanvasRenderingContext2D') != -1 || RGraph.ISIE8 && obj.moveTo) { var context = obj; var canvas = obj.canvas; var obj = canvas.__object__; // IE7/8 } else if (RGraph.ISOLD && obj.fillText) { var context = obj; var canvas = obj.canvas; var obj = canvas.__object__; } // // Changed the name of boundingFill/boundingStroke - this allows you to still use those names // if (typeof args.boundingFill === 'string') args['bounding.fill'] = args.boundingFill; if (typeof args.boundingStroke === 'string') args['bounding.stroke'] = args.boundingStroke; if (typeof args.boundingLinewidth === 'number') args['bounding.linewidth'] = args.boundingLinewidth; // // If textConfPrefix is set then get the style configuration // if (typeof args.textConfPrefix === 'string' && args.textConfPrefix.length) { var textConf = RGraph.getTextConf({ object: obj, prefix: args.textConfPrefix }); args.font = textConf.font; args.size = textConf.size; args.color = textConf.color; args.bold = textConf.bold; args.italic = textConf.italic; } if (typeof args.accessible === 'undefined') { if (obj && obj.properties.textAccessible) { return domtext(); } } else if (typeof args.accessible === 'boolean' && args.accessible) { return domtext(); } var x = args.x, y = args.y, originalX = x, originalY = y, text = args.text, text_multiline = typeof text === 'string' ? text.split(/\r?\n/g) : '', numlines = text_multiline.length, font = args.font ? args.font : 'Arial', size = args.size ? args.size : 10, size_pixels = size * 1.5, bold = args.bold, italic = args.italic, halign = args.halign ? args.halign : 'left', valign = args.valign ? args.valign : 'bottom', tag = typeof args.tag == 'string' && args.tag.length > 0 ? args.tag : '', marker = args.marker, angle = args.angle || 0; // // The text arg must be a string or a number // if (typeof text == 'number') { text = String(text); } var bounding = args.bounding, bounding_stroke = args['bounding.stroke'] ? args['bounding.stroke'] : 'black', bounding_fill = args['bounding.fill'] ? args['bounding.fill'] : 'rgba(255,255,255,0.7)', bounding_shadow = args['bounding.shadow'], bounding_shadow_color = args['bounding.shadow.color'] || '#ccc', bounding_shadow_blur = args['bounding.shadow.blur'] || 3, bounding_shadow_offsetx = args['bounding.shadow.offsetx'] || 3, bounding_shadow_offsety = args['bounding.shadow.offsety'] || 3, bounding_linewidth = typeof args['bounding.linewidth'] === 'number' ? args['bounding.linewidth'] : 1; // // Initialize the return value to an empty object // var ret = {}; // // Color // if (typeof args.color === 'string') { var orig_fillstyle = context.fillStyle; context.fillStyle = args.color; } if (typeof text !== 'string') { return; } // // This facilitates vertical text // if (angle != 0) { context.save(); context.translate(x, y); context.rotate((Math.PI / 180) * angle) x = 0; y = 0; } // // Set the font // context.font = (args.italic ? 'italic ' : '') + (args.bold ? 'bold ' : '') + size + 'pt ' + font; // // Measure the width/height. This must be done AFTER the font has been set // var width=0; for (var i=0; i 1) { y -= ((numlines - 1) * size_pixels); } var boundingY = y - size_pixels - 2; } var boundingW = width + 4; var boundingH = height + 2; // // Draw a bounding box if required // if (bounding) { var pre_bounding_linewidth = context.lineWidth, pre_bounding_strokestyle = context.strokeStyle, pre_bounding_fillstyle = context.fillStyle, pre_bounding_shadowcolor = context.shadowColor, pre_bounding_shadowblur = context.shadowBlur, pre_bounding_shadowoffsetx = context.shadowOffsetX, pre_bounding_shadowoffsety = context.shadowOffsetY; context.lineWidth = bounding_linewidth ? bounding_linewidth : 0.001; context.strokeStyle = bounding_stroke; context.fillStyle = bounding_fill; if (bounding_shadow) { context.shadowColor = bounding_shadow_color; context.shadowBlur = bounding_shadow_blur; context.shadowOffsetX = bounding_shadow_offsetx; context.shadowOffsetY = bounding_shadow_offsety; } //obj.context.strokeRect(boundingX, boundingY, width + 6, (size_pixels * numlines) + 4); //obj.context.fillRect(boundingX, boundingY, width + 6, (size_pixels * numlines) + 4); context.fillRect( boundingX, boundingY, boundingW, boundingH ); context.strokeRect( boundingX, boundingY, boundingW, boundingH ); // Reset the linewidth,colors and shadow to it's original setting context.lineWidth = pre_bounding_linewidth; context.strokeStyle = pre_bounding_strokestyle; context.fillStyle = pre_bounding_fillstyle; context.shadowColor = pre_bounding_shadowcolor context.shadowBlur = pre_bounding_shadowblur context.shadowOffsetX = pre_bounding_shadowoffsetx context.shadowOffsetY = pre_bounding_shadowoffsety } // // Draw the text // if (numlines > 1) { for (var i=0; i= 0) { if (RGraph.isNull(args.data[group])) { group++; grouped_index = 0; continue; } // Allow for numbers as well as arrays in the dataset if (typeof args.data[group] == 'number') { group++ grouped_index = 0; continue; } grouped_index++; if (grouped_index >= args.data[group].length) { group++; grouped_index = 0; } } return [group, grouped_index]; }; // // This is the reverse of the above function - converting // group/index to a sequential index // // @return number The sequential index // RGraph.groupedIndexToSequential = function () { var args = RGraph.getArgs(arguments, 'object,dataset,index'); for (var i=0,seq=0; i<=args.dataset; ++i) { for (var j=0; j 0 && pos < 20; }; // // Removes white-space from the start aqnd end of a string // // @param args object An object consisting of: // o str // OR // // @param string str The string to trim // RGraph.trim = function () { var args = RGraph.getArgs(arguments, 'str'); return RGraph.ltrim(RGraph.rtrim(args.str)); }; // // Trims the white-space from the start of a string // // @param args object An object consisting of: // o str // OR // // @param string str The string to trim // RGraph.ltrim = function () { var args = RGraph.getArgs(arguments, 'str'); return args.str.replace(/^(\s|\0)+/, ''); }; // // Trims the white-space off of the end of a string // // @param args object An object consisting of: // o str // OR // // @param string str The string to trim // RGraph.rtrim = function () { var args = RGraph.getArgs(arguments, 'str'); return args.str.replace(/(\s|\0)+$/, ''); }; // // Returns true/false as to whether the given variable is null or not // // @param args object An object consisting of: // o arg // OR // // @param mixed arg The argument to check // RGraph.isNull = function () { var args = RGraph.getArgs(arguments, 'arg'); if (typeof args.arg === 'object' && !args.arg) { return true; } return false; }; // // This function facilitates a very limited way of making your charts // whilst letting the rest of page continue - using the setTimeout function // // @param args object An object consisting of: // o func // o delay // OR // // @param function func The function to run that creates the chart // RGraph.async = function () { var args = RGraph.getArgs(arguments, 'func,delay'); return setTimeout(args.func, args.delay ? args.delay : 1); }; // // Resets (more than just clears) the canvas and clears any // pertinent objects from the ObjectRegistry. // // @param args object An object consisting of: // o canvas // OR // // @param object canvas The canvas object (as returned by document.getElementById() ). // RGraph.reset = function () { var args = RGraph.getArgs(arguments, 'canvas'); // If a string has been given then treat it as the ID // of the canvas if (typeof args.canvas === 'string') { args.canvas = document.getElementById(args.canvas); } args.canvas.width = args.canvas.width; // Clear the ObjectRegistry RGraph.ObjectRegistry.clear(args.canvas); // Get rid of references from the canvas that are added by // various RGraph dynamic features // // Do the back image first if (args.canvas.__rgraph_background_image__) { delete args.canvas.__rgraph_background_image__.__object__; delete args.canvas.__rgraph_background_image__.__canvas__; delete args.canvas.__rgraph_background_image__.__context__; } for (v of ['__object__', '__rgraph_background_image__']) { delete args.canvas[v]; } if (RGraph.text.domNodeCache && RGraph.text.domNodeCache.reset) { RGraph.text.domNodeCache.reset(args.canvas); } // Create the node and dimension caches if they don't // already exist if (!RGraph.text.domNodeCache) { RGraph.text.domNodeCache = []; } if (!RGraph.text.domNodeDimensionCache) { RGraph.text.domNodeDimensionCache = []; } // Create/reset the specific canvas arrays in the caches RGraph.text.domNodeCache[args.canvas.id] = []; RGraph.text.domNodeDimensionCache[args.canvas.id] = []; }; // // This function is due to be removed. // // 19/10/2019 Commented out // // @param string id The ID of what can be either the canvas tag or a DIV tag // //RGraph.getCanvasTag = function () //{ // var args = RGraph.getArgs(arguments, 'id'); // // var id = typeof args.id === 'object' ? args.id.id : args.id; // // var canvas = document.getElementById(id); // // return [id, canvas]; //}; // // A wrapper function that encapsulate requestAnimationFrame // // @param args object An object consisting of: // o func // OR // // @param function func The animation function // RGraph.Effects.updateCanvas = function () { var args = RGraph.getArgs(arguments, 'func'); window.requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame || window.mozRequestAnimationFrame || (function (func){setTimeout(func, 16.666);}); window.requestAnimationFrame(args.func); }; // // Checks to see if the user has requested to stop the // animation // // @param obj object The chart object // RGraph.Effects.userRequestedStop = function (obj) { var ret = false; if (obj.stopAnimationRequested) { ret = true; // Reset the flag now that its been checked obj.stopAnimationRequested = false; } return ret; }; // // This function returns an easing multiplier for effects so they eas out towards the // end of the effect. // // @param args object An object consisting of: // o frames // o frame // OR // // @param number frames The total number of frames // @param number frame The frame number // RGraph.Effects.getEasingMultiplier = function () { var args = RGraph.getArgs(arguments, 'frames,frame'); return Math.pow(Math.sin((args.frame / args.frames) * RGraph.HALFPI), 3); }; // // This function converts an array of strings to an array of numbers. Its used by the meter/gauge // style charts so that if you want you can pass in a string. It supports various formats: // // '45.2' // '-45.2' // ['45.2'] // ['-45.2'] // '45.2,45.2,45.2' // A CSV style string // // @param args object An object consisting of: // o string // o separator (optional) // OR // // @param number frames The string or array to parse // @param string separator Optional Use this instead of the default comma // RGraph.stringsToNumbers = function () { var args = RGraph.getArgs(arguments, 'string,separator'); // An optional separator to use intead of a comma var sep = args.separator || ','; // Remove preceding square brackets if (typeof args.string === 'string' && args.string.trim().match(/^\[ *\d+$/)) { args.string = args.string.replace('[', ''); } // If it's already a number just return it if (typeof args.string === 'number') { return args.string; } if (typeof args.string === 'string') { if (args.string.indexOf(sep) != -1) { args.string = args.string.split(sep); } else { args.string = parseFloat(args.string); if (isNaN(args.string)) { args.string = null; } } } if (typeof args.string === 'object' && !RGraph.isNull(args.string)) { for (var i=0,len=args.string.length; i= 2 && arguments[0].isrgraph && arguments[0].context) { var context = arguments[0].context; var p = arguments[1]; var args = arguments.length > 2 ? arguments.slice(2) : []; // Multiple args, context given } else if (arguments.length >= 2 && arguments[0].toString().indexOf('Context')) { var context = arguments[0]; var p = arguments[1]; var args = arguments.length > 2 ? arguments.slice(2) : []; } // If the path was a string - split it then collapse quoted bits together if (typeof p === 'string') { p = splitstring(p); } // Store the last path on the RGraph object RGraph.path.last = RGraph.arrayClone(p); // Go through the path information. for (var i=0,len=p.length; i= conf[i].minWidth && document.documentElement.clientWidth < conf[i].maxWidth) { matchFunction(conf[i]); } } // // If a rule matches - this is the function that runs // function matchFunction (rule) { // If a width is defined for this rule set it if (typeof rule.width === 'number') { if (obj.get('textAccessible')) { obj.canvas.parentNode.style.width = rule.width + 'px'; } obj.canvas.width = rule.width; obj.canvas.__rgraph_aa_translated__ = false; } // // If a height is defined for this rule set it // if (typeof rule.height === 'number') { if (obj.get('textAccessible')) { obj.canvas.parentNode.style.height = rule.height + 'px'; } obj.canvas.height = rule.height; obj.canvas.__rgraph_aa_translated__ = false; } // // Are there any options to be set? // if (typeof rule.options === 'object') { for (var j in rule.options) { if (typeof j === 'string') { obj.set(j, rule.options[j]); // Set the original colors to the new colors // if necessary if (j === 'colors' && obj.original_colors) { obj.original_colors = RGraph.arrayClone(rule.options[j]); } } } } // // This function simply sets a CSS property on the object. // It accommodates certain name changes // var setCSS = function (el, name, value) { var replacements = [ ['float', 'cssFloat'] ]; // Replace the name if necessary for (var i=0; i 0) { // Clear the timeout clearTimeout(timer); // Start a new timer going timer = setTimeout(func, args.delay); // If you don't want a delay before the resizing occurs // then set the delay to zero and it will be fired immediately } else { func(); } }; window.addEventListener( 'resize', RGraph.responsive.window_resize_event_listener, false ); // Call the function initially otherwise it may never run func(); // This facilitates chaining return obj; }; // // A shortcut function for the RGraph.path() function. Saves // approximately 40 characters, In each objects constructor // it is added to the object so you can call it like this: // // myBar.path({ // path: 'lw 10 b r % % % % s black f red' // args: [5,5,50,50] // }); // // Or like this (whichever you prefer): // // myBar.path( // 'lw 10 b r % % % % s black f red', // 5, 5, 50, 50 // ); // RGraph.pathObjectFunction = function () { // Siongle object argument if (arguments.length === 1 && typeof arguments[0] === 'object') { var args = RGraph.getArgs(arguments, 'path,args'); RGraph.path({ object: this, path: args.path, args: args.args }); // First arg is a string } else { var args = Array.prototype.slice.call(arguments, 1); RGraph.path({ object: this, path: arguments[0], args: args }); } }; // // A common X axis drawing function that can be used by the // Bar, HBar, Line, Scatter functions. A long time coming - but // this will eventually be joined by a common Y axis drawing // function. // //@param object obj The chart object. All the properties are // retrieved from this. // RGraph.drawXAxis = function (obj) { var properties = obj.properties, context = obj.context, tickmarksLength = (typeof properties.xaxisTickmarksLength === 'number' ? properties.xaxisTickmarksLength : 3), isSketch = (obj.type === 'bar' && properties.variant === 'sketch'); // // If the xaxisLabels property is defined then go through it converting // null and undefined values to empty strings. // if ( typeof properties.xaxisLabels === 'object' && !RGraph.isNull(properties.xaxisLabels) && properties.xaxisLabels.length) { for (var i=0; i 0 ? properties.yaxisScaleMin : 0); } else { var y = obj.getYCoord(properties.yaxisScaleMin > 0 ? properties.yaxisScaleMin : 0); } // Special case for a Line chart with an inverted scale if (obj.type === 'line' && properties.yaxisScaleInvert && properties.yaxisScaleMin === 0) { y = obj.getYCoord(obj.scale2.max); } // Special case for a Scatter chart with an inverted scale if (obj.type === 'scatter' && properties.yaxisScaleInvert) { if (properties.yaxisScaleMin >= 0) { y = obj.getYCoord(obj.scale2.max); } } // Special case for positioning an X axis Drawing API object if (obj.type === 'drawing.xaxis') { if (properties.xaxisPosition === 'center') { y = ((obj.canvas.height - properties.marginTop - properties.marginBottom) / 2) + properties.marginTop; } else { y = obj.y; } } // // Draw the axis // if (properties.xaxis) { // Draw the axis obj.path( 'lc square lw % b m % % l % % s %', properties.xaxisLinewidth, properties.marginLeft - (isSketch ? 5 : 0), y - (isSketch ? 2 : 0), obj.canvas.width - properties.marginRight + (isSketch ? 7 : 0), y + (isSketch ? 2 : 0), properties.xaxisColor ); // Draw the tickmarks if necessary if (!isSketch) { if (properties.xaxisTickmarks) { if (typeof properties.xaxisTickmarksCount === 'number') { var xaxisTickmarksCount = properties.xaxisTickmarksCount; // Bar - get number of tickmarks from the number of data points } else if (obj.type === 'bar') { var xaxisTickmarksCount = obj.data.length || 10; // HBar - get number of tickmarks from the xaxisLabelsCount property and default to 5 } else if (obj.type === 'hbar') { var xaxisTickmarksCount = (properties.xaxisLabelsCount || 5); // Line - get the number of tickmarks from the number of datapoints } else if (obj.type === 'line') { var xaxisTickmarksCount = obj.data[0].length > 0 ? obj.data[0].length - 1 : 10; // Scatter - with a scale - get the number of tickmarks from the number of scale labels } else if (obj.type === 'scatter' && properties.scale) { var xaxisTickmarksCount = 5; // Scatter - with labels - get the number of tickmarks from the number of labels } else if (obj.type === 'scatter' && properties.xaxisLabels) { var xaxisTickmarksCount = properties.xaxisLabels.length; // Scatter - with no labels and no scale } else if (obj.type === 'scatter') { var xaxisTickmarksCount = 5; // Waterfall - get the number of tickmarks from the number of datapoints } else if (obj.type === 'waterfall') { var xaxisTickmarksCount = obj.data.length + (properties.total ? 1 : 0); // Drawing API X axis } else if (obj.type === 'drawing.xaxis') { if (properties.scale) { var xaxisTickmarksCount = properties.xaxisScaleLabelsCount; } else if (properties.xaxisLabels && properties.xaxisLabels.length) { var xaxisTickmarksCount = properties.xaxisLabels.length; } else { var xaxisTickmarksCount = 5; } // Default to 5 tickmarks } else { xaxisTickmarksCount = 5; } } else { properties.xaxisTickmarksCount = 0; } // Determine the Y start coordinate for the tickmarks if (properties.xaxisPosition === 'center' && properties.yaxisScaleMin >= 0) { if (properties.yaxisScaleInvert) { var tickmarksYStart = obj.getYCoord(properties.yaxisScaleMax) - tickmarksLength; var tickmarksYEnd = obj.getYCoord(properties.yaxisScaleMax) + tickmarksLength; } else { var tickmarksYStart = obj.getYCoord(properties.yaxisScaleMin) - tickmarksLength; var tickmarksYEnd = obj.getYCoord(properties.yaxisScaleMin) + tickmarksLength; } } else if (properties.xaxisPosition === 'center' || properties.yaxisScaleMin < 0) { if (properties.yaxisScaleInvert) { var tickmarksYStart = obj.getYCoord(obj.scale2.max) - tickmarksLength; var tickmarksYEnd = obj.getYCoord(obj.scale2.max) + tickmarksLength; } else { var tickmarksYStart = obj.getYCoord(0) - tickmarksLength; var tickmarksYEnd = obj.getYCoord(0) + tickmarksLength; } // Account for offset axes if (properties.yaxisScaleMin < 0 && properties.yaxisScaleMax > 0) { var tickmarksYStart = obj.getYCoord(0) - tickmarksLength; var tickmarksYEnd = obj.getYCoord(0) + tickmarksLength; } } else if (properties.xaxisPosition === 'top') { if (obj.getYCoord) { var tickmarksYStart = obj.getYCoord(0) - tickmarksLength; var tickmarksYEnd = obj.getYCoord(0); } else { var tickmarksYStart = properties.marginTop - tickmarksLength; var tickmarksYEnd = properties.marginTop; } } else { if (obj.getYCoord) { if (obj.type === 'line' && properties.yaxisScaleInvert) { var tickmarksYStart = obj.getYCoord(obj.scale2.max); var tickmarksYEnd = obj.getYCoord(obj.scale2.max) + tickmarksLength; } else if (obj.type === 'scatter' && properties.yaxisScaleInvert) { var tickmarksYStart = obj.getYCoord(obj.scale2.max); var tickmarksYEnd = obj.getYCoord(obj.scale2.max) + tickmarksLength; } else if (obj.type === 'drawing.xaxis') { tickmarksYStart = obj.y; tickmarksYEnd = obj.y + tickmarksLength; } else { var tickmarksYStart = obj.getYCoord(properties.yaxisScaleMin); var tickmarksYEnd = obj.getYCoord(properties.yaxisScaleMin) + tickmarksLength; } } else { var tickmarksYStart = obj.canvas.height - properties.marginBottom; var tickmarksYEnd = obj.canvas.height - properties.marginBottom + tickmarksLength; } } //if (!xaxisTickmarksCount) { //xaxisTickmarksCount = 10; //} for (var i=0; i<=xaxisTickmarksCount; ++i) { // Don't draw the LEFT tickmark if there's a Y axis on the left or if specifically // told not to if (RGraph.isNull(properties.xaxisTickmarksLastLeft)) { if ( i === 0 && properties.yaxis && properties.yaxisPosition === 'left' && !(properties.xaxisScaleMax > 0 && properties.xaxisScaleMin < 0) ) { continue; } } else if (i === 0 && !properties.xaxisTickmarksLastLeft) { continue; } // Don't draw the RIGHT tickmark if there's a Y axis on the right or if specifically // told not to if (RGraph.isNull(properties.xaxisTickmarksLastRight)) { if ( i === xaxisTickmarksCount && properties.yaxis && properties.yaxisPosition === 'right') { continue; } } else if ( i === xaxisTickmarksCount && !properties.xaxisTickmarksLastRight ) { continue; } var x = (((obj.canvas.width - properties.marginLeft - properties.marginRight) / xaxisTickmarksCount) * i) + properties.marginLeft; // If the chart is an HBar and the Y axis is in the center // then don't draw a tickmark at the same position as the // Y axis. if (obj.type === 'hbar' ) { if (properties.yaxisPosition === 'center' && x > obj.getXCoord(0) - 2 && x < obj.getXCoord(0) + 2) { continue; } else if (properties.yaxisPosition === 'left' && properties.xaxisScaleMin < 0 && properties.xaxisScaleMax > 0 && x > obj.getXCoord(0) - 2 && x < obj.getXCoord(0) + 2) { continue; } } obj.path( 'b m % % l % % s %', x, tickmarksYStart, x, tickmarksYEnd, properties.xaxisColor ); } // END loop thru xaxisTickmarksCount } // END if (isSketch) } // // Draw the X axis labels if they're specified // // // Text angle // if (properties.xaxisLabelsAngle != 0) { var valign = 'center'; var halign = 'right'; var angle = 0 - properties.xaxisLabelsAngle; if (properties.xaxisPosition === 'top') { var angle = properties.xaxisLabelsAngle; } } else { var valign = 'top'; var halign = 'center'; var angle = 0; } // // Draw an X axis scale if requested. The HBar uses an X axis scale and the // Scatter chart can (optionally) too // if (properties.xaxisScale) { var scale = obj.scale2; // // Get the scale for a Scatter chart X axis // if (obj.type === 'scatter') { scale = obj.xscale2; // Get the scale for a drawing API X axis } else if (obj.type === 'drawing.xaxis') { if (properties.xaxisScale) { scale = RGraph.getScale({object: this, options: { 'scale.max': properties.xaxisScaleMax, 'scale.min': properties.xaxisScaleMin, 'scale.decimals': Number(properties.xaxisScaleDecimals), 'scale.point': properties.xaxisScalePoint, 'scale.thousand': properties.xaxisScaleThousand, 'scale.round': properties.xaxisScaleRound, 'scale.units.pre': properties.xaxisScaleUnitsPre, 'scale.units.post': properties.xaxisScaleUnitsPost, 'scale.labels.count': properties.xaxisScaleLabelsCount, 'scale.strict': true }}); for (var i=0; i<=properties.xaxisScaleLabelsCount; ++i) { var original = (((properties.xaxisScaleMax - properties.xaxisScaleMin) / properties.xaxisScaleLabelsCount) * i) + properties.xaxisScaleMin; var hmargin = properties.marginInner; if (typeof properties.xaxisScaleFormatter === 'function') { var text = (properties.xaxisScaleFormatter)(this, original) } else { text = RGraph.numberFormat({ object: obj, number: original.toFixed(original === 0 ? 0 : properties.xaxisScaleDecimals), unitspre: properties.xaxisScaleUnitsPre, unitspost: properties.xaxisScaleUnitsPost, point: properties.xaxisScalePoint, thousand: properties.xaxisScaleThousand }); } } } } for (var i=0; i 0 && properties.xaxisScaleMax > properties.xaxisScaleMin) { var x = obj.getXCoord(properties.xaxisScaleMin); } } else if ( (obj.type === 'hbar' || obj.type === 'gantt') && properties.yaxisPosition === 'right') { var x = obj.canvas.width - properties.marginRight; } else if (obj.type === 'drawing.yaxis') { var x = obj.x; } else { var x = properties.yaxisPosition === 'right' ? obj.canvas.width - properties.marginRight : (obj.type === 'hbar' ? obj.getXCoord(0) : properties.marginLeft); } // // Draw the Y axis // if (properties.yaxis) { // Draw the axis obj.path( 'lc square lw % b m % % l % % s %', properties.yaxisLinewidth, x - (isSketch ? 5 : 0), properties.marginTop - (isSketch ? 2 : 0), x + (isSketch ? 7 : 0), obj.canvas.height - properties.marginBottom + (isSketch ? 2 : 0), properties.yaxisColor ); // Draw the tickmarks for the Y axis if necessary if (!isSketch) { if (properties.yaxisTickmarks) { if (typeof properties.yaxisTickmarksCount === 'number') { var yaxisTickmarksCount = properties.yaxisTickmarksCount; // Bar - get number of tickmarks from the number of data points } else if (obj.type === 'bar') { var yaxisTickmarksCount = properties.yaxisLabelsSpecific ? properties.yaxisLabelsSpecific.length - 1 : properties.yaxisLabelsCount; // HBar - get number of tickmarks from the xaxisLabelsCount property and default to 5 } else if (obj.type === 'hbar') { var yaxisTickmarksCount = obj.data.length || 5; // Line - get the number of tickmarks from the number of datapoints } else if (obj.type === 'line') { var yaxisTickmarksCount = properties.yaxisLabelsSpecific ? properties.yaxisLabelsSpecific.length - 1 : properties.yaxisLabelsCount; // Scatter - with a scale - get the number of tickmarks from the number of scale labels } else if (obj.type === 'scatter') { var yaxisTickmarksCount = properties.yaxisLabelsCount; // Waterfall - get the number of tickmarks from the number of datapoints } else if (obj.type === 'waterfall') { var yaxisTickmarksCount = properties.yaxisLabelsCount; // Default to 5 tickmarks } else { yaxisTickmarksCount = 5; } } else { properties.yaxisTickmarksCount = 0; } // Determine the X start/end coordinates for the tickmarks if (properties.yaxisPosition === 'right') { var tickmarksXStart = x; var tickmarksXEnd = x + tickmarksLength; //} else if (properties.yaxisPosition === 'center') { // var tickmarksXStart = x - tickmarksLength; // var tickmarksXEnd = x + tickmarksLength; } else { var tickmarksXStart = x; var tickmarksXEnd = x - tickmarksLength; } // Account for HBar offset axes if (obj.type === 'hbar' && properties.xaxisScaleMin < 0 && properties.xaxisScaleMax > 0) { var tickmarksXStart = obj.getXCoord(0) - tickmarksLength; var tickmarksXEnd = obj.getXCoord(0) + tickmarksLength; } // // Now draw the tickmarks // for (var i=0; i<=yaxisTickmarksCount; ++i) { // Don't draw the TOP tickmark if there's an X axis at the top or if specifically // told not to if (RGraph.isNull(properties.yaxisTickmarksLastTop)) { if (i === 0 && properties.xaxis && properties.xaxisPosition === 'top') { continue; } } else if (i === 0 && !properties.yaxisTickmarksLastTop) { continue; } // Don't draw the BOTTOM tickmark if there's an X axis at the bottom or if specifically // told not to if (RGraph.isNull(properties.yaxisTickmarksLastBottom)) { if (i === yaxisTickmarksCount && properties.xaxis && properties.xaxisPosition === 'bottom') { continue; } } else if (i === yaxisTickmarksCount && !properties.yaxisTickmarksLastBottom) { continue; } var y = (((obj.canvas.height - properties.marginTop - properties.marginBottom) / yaxisTickmarksCount) * i) + properties.marginTop; if (obj.getYCoord) { var xaxisYCoord = obj.getYCoord(0); } else if (obj.getXCoord) { var xaxisYCoord = obj.getXCoord(0); } else { var xaxisYCoord = obj.marginTop; } if ( properties.xaxis && (properties.xaxisPosition === 'center' || (properties.xaxisPosition === 'bottom' && properties.yaxisScaleMin < 0 && properties.yaxisScaleMax > 0)) && y > (xaxisYCoord - 1) && y < (xaxisYCoord + 1)) { continue; } obj.path( 'b m % % l % % s %', tickmarksXStart, y, tickmarksXEnd, y, properties.yaxisColor ); // if the X axis is offset (eg -10,0,10,20,30,40) draw an extra // tickmark at the bottom of the axes if (properties.yaxisScaleMin < 0 && properties.yaxisScaleMax > 0) { obj.path( 'b m % % l % % s %', tickmarksXStart, obj.canvas.height - properties.marginBottom, tickmarksXEnd, obj.canvas.height - properties.marginBottom, properties.yaxisColor ); } // if the chart is: // o A Scatter chart // o X axis is in the center // o The scale is inverted if (obj.type === 'scatter' && properties.xaxisPosition === 'center' && properties.yaxisScaleInvert) { obj.path( 'b m % % l % % s %', tickmarksXStart, properties.marginTop, tickmarksXEnd, properties.marginTop, properties.yaxisColor ); } } // END for loop thru yaxisTickmarksCount } // END if (!isSketch) } // // The text angle - this does not apply to the Y axis so these // are just the alignments // var valign = 'center', halign = 'right', angle = 0; // // Draw a Y axis scale. // if (properties.yaxisScale && !properties.yaxisLabelsSpecific) { if (obj.type === 'drawing.yaxis') { if (properties.yaxisScale) { obj.scale2 = RGraph.getScale({object: obj, options: { 'scale.max': properties.yaxisScaleMax, 'scale.min': properties.yaxisScaleMin, 'scale.decimals': Number(properties.yaxisScaleDecimals), 'scale.point': properties.yaxisScalePoint, 'scale.thousand': properties.yaxisScaleThousand, 'scale.round': properties.yaxisScaleRound, 'scale.units.pre': properties.yaxisScaleUnitsPre, 'scale.units.post': properties.yaxisScaleUnitsPost, 'scale.labels.count': properties.yaxisScaleLabelsCount, 'scale.strict': true }}); for (var i=0; i<=properties.yaxisScaleLabelsCount; ++i) { var original = (((properties.yaxisScaleMax - properties.yaxisScaleMin) / properties.yaxisScaleLabelsCount) * i) + properties.yaxisScaleMin; var hmargin = properties.marginInner; if (typeof properties.yaxisScaleFormatter === 'function') { var text = (properties.yaxisScaleFormatter)(this, original) } else { text = RGraph.numberFormat({ object: obj, number: original.toFixed(original === 0 ? 0 : properties.yaxisScaleDecimals), unitspre: properties.yaxisScaleUnitsPre, unitspost: properties.yaxisScaleUnitsPost, point: properties.yaxisScalePoint, thousand: properties.yaxisScaleThousand }); } } } } var scale = obj.scale2; obj.maxLabelLength = Math.max( obj.maxLabelLength, obj.context.measureText(obj.scale2.labels[4]).width// * 2 // Don't know why this was doubled...? ); // // X axis position in the center // if (properties.xaxisPosition === 'center') { var halfHeight = ((obj.canvas.height - properties.marginTop - properties.marginBottom) / 2); // Draw the top halves labels for (var i=0; i'); text = text.replace(/___--PERCENT--___/g, '%') // Replace CR and LF with the relevant character text = text.replace(/\|CR\|/, '\r'); text = text.replace(/\|LF\|/, '\n'); return text.toString(); }; // // This splits a basic comma separated value string // into JSON. // // @param string str The string to split // @return object The resulting JSON object // RGraph.splitString = function (str) { var re = new RegExp('(["\'a-z0-9]+) *= *', 'ig'); str = str.replace(re, '"$1":'); str = str.replace(/""/g, '"'); str = str.replace(/''/g, '"'); str = str.replace(/:'/g, ':"'); str = str.replace(/' *,/g, '",'); str = str.trim().replace(/,$/, ''); var ret = JSON.parse('{' + str + '}'); return ret; }; // // A set of functions which help you get data from the GET // string (the query string). // RGraph.GET.raw = function () { return location.search; }; RGraph.GET.parse = function () { if (!RGraph.isNull(RGraph.GET.__parts__)) { return RGraph.GET.__parts__; } var raw = RGraph.GET.raw().replace(/^\?/, ''); var parts = raw.split(/\&/); // Loop thru each part splitting it for (var i=0; i 0 && args.x < (args.object.canvas.width / 2) && args.y > 0 && args.y < args.object.canvas.height ) { return true; } // Test that the cursor is over the right half } else if (clip === 'righthalf') { if ( args.x > (args.object.canvas.width / 2) && args.x < args.object.canvas.width && args.y > 0 && args.y < args.object.canvas.height ) { return true; } // Test that the cursor is over the top half } else if (clip === 'tophalf') { if ( args.x > 0 && args.x < args.object.canvas.width && args.y > 0 && args.y < (args.object.canvas.height / 2) ) { return true; } // Test that the cursor is over the bottom half } else if (clip === 'bottomhalf') { if ( args.x > 0 && args.x < args.object.canvas.width && args.y > (args.object.canvas.height / 2) && args.y < args.object.canvas.height ) { return true; } // Test that the cursor is within the clipped rect } else if ( RGraph.isArray(clip) && RGraph.isNumber(clip[0]) && RGraph.isNumber(clip[1]) && RGraph.isNumber(clip[2]) && RGraph.isNumber(clip[3]) ) { if ( args.x > clip[0] && args.x < (clip[0] + clip[2]) && args.y > clip[1] && args.y < (clip[1] + clip[3]) ) { return true; } // Test that the cursor is within the clipped path } else if (RGraph.isArray(clip) && RGraph.isArray(clip[0])) { args.object.path(RGraph.clipTo.path); return args.object.context.isPointInPath(args.x, args.y); // Test that the cursor is within the given X percentages range } else if (clip.match(/^x:([-.0-9minax]+)%?-([.0-9minax]+)%?$/i)) { // Accommodate the min/max keywords var from = RegExp.$1, to = RegExp.$2; from = from.replace(/min/, '-200'); from = from.replace(/max/, '200'); to = to.replace(/min/, '-200'); to = to.replace(/max/, '200'); from = Number(from); to = Number(to); var width = ((to - from) / 100) * (args.object.canvas.width - args.object.properties.marginLeft - args.object.properties.marginRight), height = args.object.canvas.height, x = (from / 100) * (args.object.canvas.width - args.object.properties.marginLeft - args.object.properties.marginRight) + args.object.properties.marginLeft, y = 0; args.object.path( 'b r % % % %', x,y,width,height ); return args.object.context.isPointInPath(args.x, args.y); // Test that the cursor is within the given Y percentages range } else if (clip.match(/^y:([-.0-9minax]+)%?-([.0-9minax]+)%?/i)) { // Accommodate the min/max keywords var from = RegExp.$1, to = RegExp.$2; from = from.replace(/min/, '-200'); from = from.replace(/max/, '200'); to = to.replace(/min/, '-200'); to = to.replace(/max/, '200'); from = Number(from); to = Number(to); var height = args.object.canvas.height - args.object.properties.marginTop - args.object.properties.marginBottom, x = 0, y = (from / 100) * height + args.object.properties.marginTop, width = args.object.canvas.width, //y1 = ((to - from) / 100) * (args.object.canvas.height - args.object.properties.marginTop - args.object.properties.marginBottom), y2 = (to / 100) * (args.object.canvas.height - args.object.properties.marginTop - args.object.properties.marginBottom), y = args.object.canvas.height - args.object.properties.marginBottom - y2, height = (args.object.canvas.height - args.object.properties.marginTop - args.object.properties.marginBottom) * ( (to - from) / 100); args.object.path( 'b r % % % %', x,y,width,height ); return args.object.context.isPointInPath(args.x, args.y); // Test that the cursor is within the given radius // percentages range } else if (clip.match(/^r:([-.0-9minax]+)%?-([.0-9minax]+)%?$/i)) { // Accommodate the min/max keywords var from = RegExp.$1, to = RegExp.$2; from = from.replace(/min/, '0'); from = from.replace(/max/, '2000'); to = to.replace(/min/, '0'); to = to.replace(/max/, '2000'); from = Number(from); to = Number(to); // Get the radius, centerx and centery from the // object if (!RGraph.isNumber(args.object.centerx) || !RGraph.isNumber(args.object.centery) || !RGraph.isNumber(args.object.radius)) { alert('[RGRAPH CLIPPING] To use the r: syntax the object (Type: {1}, ID: {2}, UID: {3}) must support the centerx, centery and radius properties.'.format( args.object.type, args.object.id, args.object.uid )); } var centerx = args.object.centerx, centery = args.object.centery, r1 = (from / 100) * args.object.radius, r2 = (to / 100) * args.object.radius; args.object.path( 'sa ' + 'b a % % % 0 6.29 false a % % % 6.29 0 true cl', centerx, centery, r1, centerx, centery, r2 ); return args.object.context.isPointInPath(args.x, args.y); // Test that the cursor is within the clipped scale // // IF YOU UPDATE THIS REGEXP THEN IT NEEDS TO BE // UPDATED FURTHER UP IN THE CODE TOO // } else if (clip.match(/^(?:scale:) *([-.0-9min]+) *- *([-.0-9max]+) *$/)) { if (args.object.clipToScaleTestWorker) { args.object.clipToScaleTestWorker(clip); return args.object.context.isPointInPath(args.x, args.y); } else { console.log('The scale: clipping option isn\'t implemented for this chart type (' + args.object.type + ')'); } } } }; // // Parses a string such as this: // // foo=bar,foo2=bar2,foo3="bar 3" // // @paramstr string The string to parse // @return An object of the results // RGraph.parseConfigString = function () { var args = RGraph.getArgs(arguments, 'string,separator'); // Default to a comma args.separator ??= ','; var insideQuote = false, entries = [], entry = [], parts = {}; args.string.split('').forEach(function (character) { if(character === '"' || character === "'") { insideQuote = !insideQuote; } else { if(character === args.separator && !insideQuote) { entries.push(entry.join('')); entry = []; } else { entry.push(character); } } }); entries.push(entry.join('')); for (let i=0; i { if (RGraph.isNumber(v)) { total += v; } else if (RGraph.isArray(v)) { total += RGraph.arraySum(v); } }); var num = 0; for (let i=0; i sum += parseFloat(v[1])); avg = sum / obj.data[0].length; y = obj.getYCoord(avg); } break; } // // Dotted or dashed lines // linedash = '[1,1]'; if (conf.dotted === true) { linedash = '[1,3]'; } if (conf.dashed === true || (typeof conf.dashed === 'undefined' && typeof conf.dotted === 'undefined') ) { linedash = '[6,6]'; } // // Draw the line // obj.path( 'lw % ld % b m % % l % % s %', typeof conf.linewidth === 'number' ? conf.linewidth : defaults.linewidth, linedash, obj.properties.marginLeft,y, obj.canvas.width - obj.properties.marginRight,y, conf.color || defaults.color ); // // Draw the label // // These chart types only if (['bar','line','scatter'].includes(obj.type)) { // Default pos for the label if (!conf.labelPosition) { conf.labelPosition = defaults.labelPosition; } if (typeof conf.labelPosition === 'string' && conf.labelPosition.indexOf('left') >= 0) { textX = obj.marginLeft + hmargin; halign = 'left'; } else if (typeof conf.labelPosition === 'string' && conf.labelPosition.indexOf('center') >= 0) { textX = ((obj.canvas.width - obj.properties.marginLeft - obj.properties.marginRight) / 2) + obj.properties.marginLeft; halign = 'center'; } else if (typeof conf.labelPosition === 'string' && conf.labelPosition === 'right-margin') { textX = obj.canvas.width - obj.properties.marginRight + hmargin; halign = 'left'; } else if (typeof conf.labelPosition === 'string' && conf.labelPosition ==='left-margin') { textX = obj.properties.marginLeft - hmargin; halign = 'right'; } else { textX = obj.canvas.width - obj.marginRight - hmargin; halign = 'right'; } if (typeof conf.labelPosition === 'string' && conf.labelPosition.indexOf('bottom') >= 0) { textY = y + vmargin; valign = 'top'; } else if (typeof conf.labelPosition === 'string' && conf.labelPosition === 'right-margin') { textY = y; valign = 'center'; } else if (typeof conf.labelPosition === 'string' && conf.labelPosition === 'left-margin') { textY = y; valign = 'center'; } else { textY = y - vmargin; valign = 'bottom'; } } // Account for linewidth linewidth = typeof conf.linewidth === 'number' ? conf.linewidth : defaults.linewidth; var num = RGraph.numberFormat({ object: obj, number: (typeof conf.value === 'number' ? conf.value : avg).toFixed(typeof conf.labelValueDecimals === 'number' ? conf.labelValueDecimals : defaults.labelValueDecimals), unitspre: conf.labelValueUnitsPre, unitspost: conf.labelValueUnitsPost, thousand: conf.labelValueThousand, point: conf.labelValuePoint, formatter: conf.labelValueFormatter, decimals: conf.labelValueDecimals }); // // Draw the label // RGraph.text({ object: obj, text: (typeof conf.label === 'string' ? conf.label : defaults.label).replace('%{value}', num), x: textX + parseFloat(conf.labelOffsetx || defaults.labelOffsetx), y: textY - (linewidth / 2) + parseFloat((conf.labelPosition.indexOf('top') !== -1 ? (-1 * conf.labelOffsety) : conf.labelOffsety) || defaults.labelOffsety), valign: valign, halign: halign, size: textSize, font: textFont, color: textColor, italic: textItalic, bold: textBold, bounding: true, boundingFill: 'rgba(255,255,255,0.75)', boundingStroke: 'transparent' }); } } }; // // This function allows you to run a function once (immediately) // Future calls using the same identifier are not run. // // @param obj object The chart object // @param id string A unique identifier used to identifier // this function // @param func function The function to call // @return The return value of the function // RGraph.runOnce = function (id, func) { if (RGraph.Registry.get('rgraph-runonce-functions')[id]) { return; } RGraph.Registry.get('rgraph-runonce-functions')[id] = func; return func(); }; // // This function implements the logic for whether to ignore // a particular hotspot or not // // @param object obj The chart object // @param number index The Index of the hotspot to check for // RGraph.tooltipsHotspotIgnore = function (obj, index) { if ( (RGraph.isBoolean(obj.properties.tooltipsHotspotIgnore) && obj.properties.tooltipsHotspotIgnore) || (RGraph.isNumber(obj.properties.tooltipsHotspotIgnore) && obj.properties.tooltipsHotspotIgnore === index) || (RGraph.isArray(obj.properties.tooltipsHotspotIgnore) && obj.properties.tooltipsHotspotIgnore.includes(index)) || (RGraph.isArray(obj.properties.tooltipsHotspotIgnore) && obj.properties.tooltipsHotspotIgnore[index] === true)) { return true; } return false; }; // Formatted version of a popular md5 implementation // Thanks to Paul Johnston & Greg Holt. // // @param string str The string to hash // RGraph.md5 = function (str) { var hc="0123456789abcdef"; function rh(n) {var j,s="";for(j=0;j<=3;j++) s+=hc.charAt((n>>(j*8+4))&0x0F)+hc.charAt((n>>(j*8))&0x0F);return s;} function ad(x,y) {var l=(x&0xFFFF)+(y&0xFFFF);var m=(x>>16)+(y>>16)+(l>>16);return (m<<16)|(l&0xFFFF);} function rl(n,c) {return (n<>>(32-c));} function cm(q,a,b,x,s,t) {return ad(rl(ad(ad(a,q),ad(x,t)),s),b);} function ff(a,b,c,d,x,s,t) {return cm((b&c)|((~b)&d),a,b,x,s,t);} function gg(a,b,c,d,x,s,t) {return cm((b&d)|(c&(~d)),a,b,x,s,t);} function hh(a,b,c,d,x,s,t) {return cm(b^c^d,a,b,x,s,t);} function ii(a,b,c,d,x,s,t) {return cm(c^(b|(~d)),a,b,x,s,t);} function sb(x) { var i;var nblk=((x.length+8)>>6)+1;var blks=new Array(nblk*16);for(i=0;i>2]|=x.charCodeAt(i)<<((i%4)*8); blks[i>>2]|=0x80<<((i%4)*8);blks[nblk*16-2]=x.length*8;return blks; } var i,x=sb(""+str),a=1732584193,b=-271733879,c=-1732584194,d=271733878,olda,oldb,oldc,oldd; for(i=0;i 0) ? true : false;}; //RGraph.isNull Defined above RGraph.isFunction = function (obj){return typeof obj === 'function';}; RGraph.isUndefined = function (obj){return typeof obj === 'undefined';}; // End module pattern })(window, document); // // Uses the alert() function to show the structure of the given variable // // @param mixed v The variable to print/alert the structure of // window.$p = function (v) { RGraph.pr(arguments[0], arguments[1], arguments[3]); }; // // A shorthand for the default alert() function // // @param mixed v The variable to alert // window.$a = function (v) { if (arguments.length > 1) { var args = []; for (let i=0; i=0 ? start : this.length + start; return this.substring(start, start + length); }; } // // A basic string formatting function. Use it like this: // // var str = '{0} {1} {2}'.format('a', 'b', 'c'); // // Outputs: a b c // // @param ... Replacements to use in the string // String.prototype.format = function() { // // Allow for this type of formatting: {myVar} // if (arguments.length === 0) { var s = this; // Allow for {myVar} style if (s.match(/{[a-z0-9]+?}/i)) { var s = this.replace(/{[a-z0-9]+?}/gi, function(str, idx) { str = str.substr(1) str = str.substr(0, str.length - 1) return window[str]; }); } return s; } // // If called with just a single array, then handle that // //eg '%1 %2 %3'.format(['A','B','C']); // if (arguments.length === 1 && RGraph.isArray(arguments[0])) { return this.format.apply(this, arguments[0]); } var args = arguments; for (var i in args) { if (RGraph.isNull(args[i])) { args[i] = 'null'; } } var s = this.replace(/{(\d+)}/g, function(str, idx) { return typeof args[idx - 1] !== 'undefined' ? args[idx - 1] : str; }); // Save percentage signs that are escaped with either // another percent or a backslash s = s.replace(/(?:%|\\)%(\d)/g,'__PPEERRCCEENNTT__$1'); s = s.replace(/%(\d+)/g, function(str, idx) { return typeof args[idx - 1] !== 'undefined' ? args[idx - 1] : str; }); // Now replace those saved percentage signs with a percentage sign return s.replace('__PPEERRCCEENNTT__', '%'); };