// // 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.Queue = {__store__: []}; RGraph.PHP = {}; 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 // (defined above). // 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 function 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]; // }; // // IMPORTANT: // // THIS FUNCTION IS NO LONGER USED TO TRY AND IMPROVE // PERFORMANCE. // // 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 function 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 arr.length || Math.abs(col) > Math.abs(arr.length + 1)) { return null; } return arr.map(v => v[col < 0 ? col + v.length : col]); }; // // A convenient way to get the last element in the array: // // foo = [8,6,6,7,4,2,3,8]; // RGraph.arrayLast(foo); // 8 // // @param array array The array to get the last element from. // RGraph.arrayLast = function (array) { return array[array.length - 1]; }; // // Converts an the truthy values to falsey values and // vice-versa. // // @param array array The array to invert. // RGraph.arrayInvert = function (array) { for (var i=0,len=array.length; i= 0) { ret = new Array(); for (var i=0; i 0 && objects) { ret = new Object(); for (var i in array) { ret[i] = RGraph.arrayClone(array[i], true, maxdepth - 1); } } break; case 'function': ret = array; break; } return ret; }; // // Returns the maximum numeric value which is in an array. This // function IS NOT recursive. // // @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 (array, ignore = false) { var max = null; if (typeof array === 'number') { return array; } if (RGraph.isNullish(array)) { return 0; } for (var i=0,len=array.length; i=0; i-=1) { newarr.push(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 array value The number or array to work on. // @return mixed The absolute value of the number // or the numbers in the array. // RGraph.abs = function (value) { if (typeof value === 'string') { value = parseFloat(value) || 0; } if (typeof value === 'number') { return Math.abs(value); } if (typeof value === 'object') { for (i in value) { if ( typeof value[i] === 'string' || typeof value[i] === 'number' || typeof value[i] === 'object') { value[i] = RGraph.abs(value[i]); } } return value; } return 0; }; // // Clears the canvas by setting the width. You can specify a // colour if you wish. // // @param object canvas The canvas to clear. // @param string color Optional - the colour to clear the // canvas to. Defaults to transparent. // @param mixed Usually a color string to use to clear // the canvas with - could also be a // gradient object. // RGraph.clear = RGraph.Clear = function (canvas, color = 'transparent') { var obj = canvas.__object__; var context = canvas.getContext('2d'); var color = color || (obj && obj.get('clearto')); if (!canvas) { return; } RGraph.fireCustomEvent(obj, 'onbeforeclear'); // // Set the CSS display: to none for DOM text. // if (RGraph.text.domNodeCache && RGraph.text.domNodeCache[canvas.id]) { for (var i in RGraph.text.domNodeCache[canvas.id]) { var el = RGraph.text.domNodeCache[canvas.id][i]; if (el && el.style) { el.style.display = 'none'; } } } // // Can now clear the canvas back to fully transparent. // if ( !color || (color && color.replace(/\s+/, '') === 'rgba(0,0,0,0)' || color === 'transparent') ) { context.clearRect( -100, -100, canvas.width + 200, canvas.height + 200 ); // Reset the globalCompositeOperation. context.globalCompositeOperation = 'source-over'; } else if (color) { obj.path( 'fs % fr -100 -100 % %', color, canvas.width + 200, canvas.height + 200 ); } else { obj.path( 'fs % fr -100 -100 % %', obj.get('clearto'), canvas.width + 200, 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.' + canvas.id)) { var img = RGraph.Registry.get('background.image.' + 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(canvas); } // // Set the cursor to default. // canvas.style.cursor = 'default'; RGraph.fireCustomEvent(obj, 'onclear'); }; // // Draws the title of the chart. // // @param object obj The chart object. // RGraph.drawTitle = function (obj) { var halign = 'center', valign = 'center', x = ((obj.canvas.width - obj.properties.marginLeft - obj.properties.marginRight) / 2) + obj.properties.marginLeft, y = null, textConf = RGraph.getTextConf({ object: obj, 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 = Boolean(obj.properties.titleBold); } // Determine the Y coordinate. y = obj.properties.marginTop - textConf.size - (5 * RGraph.getScaleFactor(obj) ); if (obj.properties.xaxisPosition === 'top') { y = obj.canvas.height - obj.properties.marginBottom + textConf.size + (5 * RGraph.getScaleFactor(obj) ); } // // 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( 'Mg', obj.properties.keyLabelsItalic, obj.properties.keyLabelsBold, obj.properties.keyLabelsFont, obj.properties.keyLabelsSize ); } 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 = 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.isNullish(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 object event The event object. As such this method // should be used in an event listener. // RGraph.getMouseXY = function (event) { // If window.event.offsetX and window.event.offsetY are // available just return them. if ( typeof window.event.offsetX === 'number' && typeof window.event.offsetY === 'number' ) { var ret = [ window.event.offsetX, window.event.offsetY ]; // // Is the canvas scaled in order for it to look much // nicer. If so, the scaling needs to be accounted // for here. // var obj = window.event.target.__object__; if (obj && obj.properties && obj.properties.scale) { ret[0] *= obj.properties.scaleFactor; ret[1] *= obj.properties.scaleFactor; } return ret; } // This is necessary for IE9. if (!event.target) { return; } var el = 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 event.offsetX === 'number' && typeof event.offsetY === 'number') { if (!RGraph.ISIE && !RGraph.ISOPERA) { x = event.offsetX - borderLeft - paddingLeft; y = event.offsetY - borderTop - paddingTop; } else if (RGraph.ISIE) { x = event.offsetX - paddingLeft; y = event.offsetY - paddingTop; } else { x = event.offsetX; y = event.offsetY; } } else { if (typeof el.offsetParent !== 'undefined') { do { offsetX += el.offsetLeft; offsetY += el.offsetTop; } while ((el = el.offsetParent)); } x = event.pageX - offsetX - additionalX; y = 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 object canvas The canvas to get the position of. // RGraph.getCanvasXY = function (canvas) { // If the getBoundingClientRect function is // available - use that. // if (canvas.getBoundingClientRect) { var rect = 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 = 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 = canvas.style.paddingLeft ? parseInt(canvas.style.paddingLeft) : 0; var paddingTop = canvas.style.paddingTop ? parseInt(canvas.style.paddingTop) : 0; var borderLeft = canvas.style.borderLeftWidth ? parseInt(canvas.style.borderLeftWidth) : 0; var borderTop = canvas.style.borderTopWidth ? parseInt(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 whether 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 object canvas The canvas to test. // @return Either false or the fixed positioned // element. // RGraph.isFixed = function (canvas) { var i = 0; while (canvas && canvas.tagName.toLowerCase() != 'body' && i < 99) { if (canvas.style.position == 'fixed') { return canvas; } canvas = canvas.offsetParent; } return false; }; // // Registers a graph object (used when the canvas is redrawn). // // @param object obj The object to be registered. // RGraph.register = function (obj) { // Allow the registration of functions. if (typeof obj === 'function') { var func = obj; // 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();}; }; obj = new temp(); } // Checking this property ensures the object is only // registered once. if (!obj.get('noregister') && obj.get('register') !== false) { RGraph.ObjectRegistry.add(obj); obj.set('register', false); } }; // // Causes all registered objects to be redrawn. // // @param string color An optional color to use to clear the canvas. // RGraph.redraw = function (color = 'transparent') { var objectRegistry = RGraph.ObjectRegistry.objects.byCanvasID; // If the argument is a canvas object (ie not a color // string) then call .redrawCanvas() instead. if ( typeof color === 'object' && color && typeof color.toString === 'function' && typeof color.toString().indexOf === 'function' && color.toString().indexOf('HTMLCanvasElement') > -1) { return RGraph.redrawCanvas(color, arguments[1] ? arguments[1] : null); } // 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.isNullish(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 * scaleFactor, 5 * scaleFactor]); } // Dotted background grid. if (properties.backgroundGridDotted && typeof cacheContext.setLineDash == 'function') { cacheContext.setLineDash([1 * scaleFactor, 3 * scaleFactor]); } // 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(obj.canvas.width - marginRight, Math.round(y)); if ( obj.properties.variant === '3d' && obj.type === 'bar' && obj.properties.backgroundGridThreedYaxis) { cacheContext.moveTo(marginLeft, Math.round(y)); cacheContext.lineTo( marginLeft - obj.properties.variantThreedOffsetx, Math.round(y) + obj.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( obj, obj.uid + '_background', func ); // If it's a bar and 3D variant, translate. if (variant == '3d') { obj.context.restore(); } // Reset the line dash if (typeof obj.context.setLineDash == 'function') { //obj.context.setLineDash([1, 0]); // Old obj.context.setLineDash([]); // New - should be faster. } obj.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 obj.context.setLineDash == 'function') { obj.context.setLineDash([3 * scaleFactor, 5 * scaleFactor]); } // Dotted background grid. if (properties.backgroundBorderDotted && typeof obj.context.setLineDash == 'function') { obj.context.setLineDash([1 * scaleFactor, 3 * scaleFactor]); } // Custom linedash if (RGraph.isArray(properties.backgroundBorderDashArray)) { obj.context.setLineDash([ properties.backgroundBorderDashArray[0] * scaleFactor, properties.backgroundBorderDashArray[1] * scaleFactor, ]); } obj.path( 'b lc square lw % r % % % % s %', linewidth, obj.properties.marginLeft, obj.properties.marginTop, obj.canvas.width - obj.properties.marginLeft - obj.properties.marginRight, obj.canvas.height - obj.properties.marginTop - obj.properties.marginBottom, color ); // Reset the linedash. //obj.context.setLineDash([1,0]); // Old. obj.context.setLineDash([]);// New - should be faster. } // Draw the title if one is set. if ( typeof obj.properties.title === 'string') { RGraph.drawTitle(obj); } // Fire the background event. RGraph.fireCustomEvent(obj, '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 // // @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 point ** 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 object obj The chart object. // RGraph.drawBars = function (obj) { var properties = obj.properties, hbars = properties.backgroundHbars; if (hbars === null) { return; } // // Draws a horizontal bar. // obj.context.beginPath(); for (var i=0,len=hbars.length; i obj.scale2.max) start = obj.scale2.max; if (RGraph.isNullish(length)) length = obj.scale2.max - start; if (start + length > obj.scale2.max) length = obj.scale2.max - start; if (start + length < (-1 * obj.scale2.max) ) length = (-1 * obj.scale2.max) - start; if (properties.xaxisPosition == 'center' && start == obj.scale2.max && length < (obj.scale2.max * -2)) { length = obj.scale2.max * -2; } // // Draw the bar. // var x = properties.marginLeft; var y = obj.getYCoord(start); var w = obj.canvas.width - properties.marginLeft - properties.marginRight; var h = obj.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 = obj.canvas.height - y; h *= -1; } obj.context.fillStyle = color; obj.context.fillRect(x, y, w, h); } // // // If the X axis is at the bottom, and a negative max is // // given, warn the user. // if (obj.get('xaxisPosition') == 'bottom' && (hbars[i][0] < 0 || (hbars[i][1] + hbars[i][1] < 0)) ) { // alert('[' + obj.type.toUpperCase() + ' (ID: ' + obj.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 = (obj.grapharea - (((hbars[i][0] - obj.scale2.min) / (obj.scale2.max - obj.scale2.min)) * obj.grapharea)); // //var height = (Math.min(hbars[i][1], obj.max - hbars[i][0]) / (obj.scale2.max - obj.scale2.min)) * obj.grapharea; // var height = obj.getYCoord(hbars[i][0]) - obj.getYCoord(hbars[i][1]); // // // Account for the X axis being in the center. // if (obj.get('xaxisPosition') == 'center') { // ystart /= 2; // //height /= 2; // } // // ystart += obj.get('marginTop') // // var x = obj.get('marginLeft'); // var y = ystart - height; // var w = obj.canvas.width - obj.get('marginLeft') - obj.get('marginRight'); // var h = height; // // // Accommodate Opera. // if (navigator.userAgent.indexOf('Opera') != -1 && obj.get('xaxisPosition') == 'center' && h < 0) { // h *= -1; // y = y - h; // } // // // // // Account for X axis at the top. // // // //if (obj.get('xaxisPosition') == 'top') { // // y = obj.canvas.height - y; // // h *= -1; // //} // // //obj.context.fillStyle = hbars[i][2]; // //obj.context.fillRect(x, y, w, h); // //} }; // // Draws in-graph labels. // // @param object obj The graph object. // RGraph.drawIngraphLabels = RGraph.drawInGraphLabels = function (obj) { var properties = obj.properties, labels = properties.labelsIngraph, labels_processed = [], scaleFactor = RGraph.getScaleFactor(obj); // Defaults var fgcolor = 'black', bgcolor = 'white', direction = 1; if (!labels) { return; } // Get the text configuration. var textConf = RGraph.getTextConf({ object: obj, 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 = ((obj.type == 'bar' ? coords[0] + (coords[2] / 2) : coords[0])) + (properties.labelsIngraphOffsetx || 0); var y = (obj.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 * scaleFactor); obj.context.beginPath(); obj.context.fillStyle = 'black'; obj.context.strokeStyle = 'black'; if (obj.type === 'bar') { // // X axis at the top. // if (obj.get('xaxisPosition') == 'top') { length *= -1; } if (properties.variant == 'dot') { obj.context.moveTo(Math.round(x), obj.coords[i][1] - (5 * scaleFactor) ); obj.context.lineTo(Math.round(x), obj.coords[i][1] - (5 * scaleFactor) - (length * scaleFactor)); var text_x = Math.round(x); var text_y = obj.coords[i][1] - (5 * scaleFactor) - (length * scaleFactor); } else if (properties.variant == 'arrow') { obj.context.moveTo(Math.round(x), obj.coords[i][1] - (5 * scaleFactor) ); obj.context.lineTo(Math.round(x), obj.coords[i][1] - (5 * scaleFactor) - (length * scaleFactor)); var text_x = Math.round(x); var text_y = obj.coords[i][1] - (5 * scaleFactor) - (length * scaleFactor) ; } else { obj.context.arc(Math.round(x), y, (2.5 * scaleFactor), 0, 6.29, 0); obj.context.moveTo(Math.round(x), y); obj.context.lineTo(Math.round(x), y - (length * scaleFactor) ); var text_x = Math.round(x); var text_y = y - (length * scaleFactor); } obj.context.stroke(); obj.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 * scaleFactor) + (length * scaleFactor); } else { var text_x = x; var text_y = y - (5 * scaleFactor) - (length * scaleFactor); if (text_y < 5 && (typeof labels_processed[i] === 'string' || typeof labels_processed[i][3] === 'undefined')) { text_y = y + (5 * scaleFactor) + (length * scaleFactor) ; var valign = 'top'; } if (valign === 'top') { /// Draw an down arrow. drawUpArrow(x, y); } else { /// Draw an up arrow. drawDownArrow(x, y); } } obj.context.fill(); } obj.context.beginPath(); // Foreground color. if ((typeof labels_processed[i] === 'object' && typeof labels_processed[i][1] === 'string')) { obj.context.fillStyle = labels_processed[i][1]; } else { obj.context.fillStyle = properties.labelsIngraphColor; } RGraph.text({ object: obj, font: textConf.font, size: textConf.size, color: obj.context.fillStyle || textConf.color, bold: textConf.bold, italic: textConf.italic, x: text_x, y: text_y + (obj.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' }); obj.context.fill(); } // Draws a down arrow. function drawUpArrow (x, y) { obj.context.moveTo(Math.round(x), y + (5 * scaleFactor) ); obj.context.lineTo(Math.round(x), y + (5 * scaleFactor) + (length * scaleFactor)); obj.context.stroke(); obj.context.beginPath(); // This draws the arrow. obj.context.moveTo(Math.round(x), y + (5 * scaleFactor)); obj.context.lineTo(Math.round(x) - (3 * scaleFactor), y + (10 * scaleFactor)); obj.context.lineTo(Math.round(x) + (3 * scaleFactor), y + (10 * scaleFactor) ); obj.context.closePath(); } // Draw an up arrow. function drawDownArrow (x, y) { obj.context.moveTo(Math.round(x), y - (5 * scaleFactor)); obj.context.lineTo(Math.round(x), y - (5 * scaleFactor) - (length * scaleFactor) ); obj.context.stroke(); obj.context.beginPath(); // This draws the arrow. obj.context.moveTo(Math.round(x), y - (5 * scaleFactor)); obj.context.lineTo(Math.round(x) - (3 * scaleFactor), y - (10 * scaleFactor)); obj.context.lineTo(Math.round(x) + (3 * scaleFactor), y - (10 * scaleFactor)); obj.context.closePath(); } valign = undefined; } } } }; // // This function hides the crosshairs coordinates. This function // has no arguments and no return value. // 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 object obj The chart object. // RGraph.draw3DAxes = function (obj) { var properties = obj.properties, marginLeft = obj.marginLeft, marginRight = obj.marginRight, marginTop = obj.marginTop, marginBottom = obj.marginBottom, xaxispos = properties.xaxisPosition, graphArea = obj.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(obj); } // X axis. if (xaxis) { if (xaxispos === 'center') { obj.path( 'b m % % l % % l % % l % % c s #aaa f %', marginLeft,marginTop + halfGraphArea, marginLeft + offsetx,marginTop + halfGraphArea - offsety, obj.canvas.width - marginRight + offsetx,marginTop + halfGraphArea - offsety, obj.canvas.width - marginRight,marginTop + halfGraphArea, properties.variantThreedXaxisColor ); } else { if (obj.type === 'hbar') { var xaxisYCoord = obj.canvas.height - obj.properties.marginBottom; } else { var xaxisYCoord = obj.getYCoord(0); } obj.path( 'm % % l % % l % % l % % c s #aaa f %', marginLeft,xaxisYCoord, marginLeft + offsetx,xaxisYCoord - offsety, obj.canvas.width - marginRight + offsetx,xaxisYCoord - offsety, obj.canvas.width - marginRight,xaxisYCoord, properties.variantThreedXaxisColor ); } } }; // // Draws the3D Y axis/background. // // @param object obj The chart object. // RGraph.draw3DYAxis = function (obj) { var properties = obj.properties; var marginLeft = obj.marginLeft, marginRight = obj.marginRight, marginTop = obj.marginTop, marginBottom = obj.marginBottom, xaxispos = properties.xaxisPosition, graphArea = obj.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 ( (obj.type === 'hbar' || obj.type === 'bar') && properties.yaxisPosition === 'center') { var x = ((obj.canvas.width - marginLeft - marginRight) / 2) + marginLeft; } else if ((obj.type === 'hbar' || obj.type === 'bar') && properties.yaxisPosition === 'right') { var x = obj.canvas.width - marginRight; } else { var x = marginLeft; } obj.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, obj.canvas.height - marginBottom - offsety, x,obj.canvas.height - marginBottom, properties.variantThreedYaxisColor ); //} }; // // Draws a filled rectangle with curvy corners. // // @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 radius number The radius of the curved corners. // @param roundtl boolean OPTIONAL Whether the top left corner is curvy. // @param roundtr boolean OPTIONAL Whether the top right corner is curvy. // @param roundbl boolean OPTIONAL Whether the bottom left corner is // curvy/ // @param roundbr boolean OPTIONAL Whether the bottom right corner is // curvy. // RGraph.roundedRect = function (context, x, y, width, height, radius, roundtl = true, roundtr = true, roundbl = true, roundbr = true) { // The corner radius. var r = radius ? radius : 3; // Change the radius based on the smallest width or height. r = Math.min( Math.min(width, height) / 2, radius ); // The corners. All default to being rounded. var corner_tl = (roundtl === false) ? false : true, corner_tr = (roundtr === false) ? false : true, corner_bl = (roundbl === false) ? false : true, corner_br = (roundbr === false) ? false : true; context.beginPath(); context.moveTo(x, y + r); // Top left corner. if (corner_tl) { context.arc(x + r, y + r, r, RGraph.PI, RGraph.PI + RGraph.HALFPI, false); } else { context.lineTo(x, y); context.lineTo(x + r, y); } // Top right corner. if (corner_tr) { context.arc(x + width - r, y + r, r, RGraph.PI + RGraph.HALFPI, 0, false); } else { context.lineTo(x + width, y); context.lineTo(x + width, y + r); } // Bottom right corner. if (corner_br) { context.arc(x + width - r, y + height - r, r, 0, RGraph.HALFPI, false); } else { context.lineTo(x + width, y + height); context.lineTo(x + width - r, y + height); } // Bottom left corner. if (corner_bl) { context.arc(x + r, y - r + height, r, RGraph.HALFPI, RGraph.PI, false); } else { context.lineTo(x, y + height); context.lineTo(x, y + height - r); } 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 object obj The graph object. // @param string event The name of the event, eg ontooltip. // @param object func The callback function. // RGraph.addCustomEventListener = function (obj, name, func) { // Initialise the events array if necessary. if (typeof RGraph.events[obj.uid] === 'undefined') { RGraph.events[obj.uid] = []; } // Prepend "on" if necessary. if (name.substr(0, 2) !== 'on') { name = 'on' + name; } RGraph.events[obj.uid].push([ obj, name, func ]); return RGraph.events[obj.uid].length - 1; }; // // Used to fire one of the RGraph custom events. // // @param object obj The graph object that fires the event. // @param string event The name of the event to fire. // @param mixed Meta information which is passed to the event // listener. OPTIONAL. // RGraph.fireCustomEvent = function (obj, name, meta = {}) { // Prepend the name with "on" if necessary. if (name.substring(0,2) !== 'on') { name = 'on' + name; } // The event name without preceding "on", eg // draw, firstdraw or beforedraw etc. var eventName = name.replace(/^on/,''); if (obj && obj.isrgraph) { // This allows the eventsMouseout property to // work. // // 25/10/19 - Taken out. // //if (name.match(/(on)?mouseout/) && typeof obj.properties.eventsMouseout === 'function') { // (obj.properties.eventsMouseout)(obj); //} // DOM1 style of adding custom events. if (obj[name]) { (obj[name])(obj, meta); } var uid = obj.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(event); if (obj) { return obj; } } }; // // Retrieves the relevant objects based on the X/Y position. // NOTE This function returns an array of objects. // // @param object event The event object. // @return An array of pertinent objects. Note // the there may be only one object. // RGraph.ObjectRegistry.getObjectsByXY = function (event) { var canvas = 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(event); if (obj) { ret.push(obj); } } return ret; }; // // Retrieves the object with the corresponding UID. // // @param string uid The UID to get the relevant object for. // RGraph.ObjectRegistry.get = RGraph.ObjectRegistry.getObjectByUID = function (uid) { var objects = RGraph.ObjectRegistry.objects.byUID; for (var i=0,len=objects.length; i= cx && y >= cy) { angle += RGraph.TWOPI; } else if (x >= cx && y < cy) { angle = (RGraph.HALFPI - angle) + (RGraph.PI + RGraph.HALFPI); } else if (x < cx && y < 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 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 (x1,y1,x2,y2) { return Math.sqrt(((x2 - x1) * (x2 - x1)) + ((y2 - y1) * (y2 - 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 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 (cx, cy, angle, radius) { var x = cx + (Math.cos(angle) * radius); var y = cy + (Math.sin(angle) * radius); return [x, y]; }; // // This installs all of the event listeners on to the canvas. // // @param object obj The chart object. // RGraph.installEventListeners = function (obj) { var properties = obj.properties; // // If this function exists, then the dynamic file has been included. // if (RGraph.installCanvasClickListener) { RGraph.installWindowMousedownListener(obj); RGraph.installWindowMouseupListener(obj); RGraph.installCanvasMousemoveListener(obj); RGraph.installCanvasMouseupListener(obj); RGraph.installCanvasMousedownListener(obj); RGraph.installCanvasClickListener(obj); } else if ( RGraph.hasTooltips(obj) || properties.adjustable || properties.annotatable || properties.contextmenu || properties.keyInteractive || typeof obj.onclick === 'function' || typeof obj.onmousemove === 'function' || typeof obj.onmouseout === 'function' || typeof obj.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 mixed obj The variable you want to see. // @param boolean notify Whether to use the alert() function // to notify you or not. Defaults to // true. // @param string indent The indent to yse when showing nested // properties. Defaults to 4 spaces. // @param number counter Used internally to control nesting. // You won't need to give this. // RGraph.pr = function (obj, notify = true, indent = ' ', counter = 0) { var str = ''; if (counter >= 3) { return 'INDENT TOO MUCH!'; } switch (typeof obj) { case 'string': str += obj + ' (' + (typeof obj) + ', ' + obj.length + ')'; break; case 'number': str += obj + ' (' + (typeof obj) + ')'; break; case 'boolean': str += obj + ' (' + (typeof obj) + ')'; break; case 'function': str += 'function () {}'; break; case 'undefined': str += 'undefined'; break; case 'null': str += 'null'; break; case 'object': // In case of null if (RGraph.isNullish(obj)) { str += 'null(ish)'; } else { str += 'Object {' + '\n' for (var j in obj) { str += indent + ' ' + j + ' => ' + RGraph.pr(obj[j], false, indent + ' ', counter + 1) + '\n'; } str += indent + '}'; } break; default: str += 'Unknown type: ' + typeof obj + ''; break; } // // Finished, now either return if we're in a recursed // call, or alert() if we're not. // if (notify) { alert(str); } return str; }; // // Produces a dashed line. This funtion only PATHS the // line - it does not stroke() or fill() it. // // @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(context, x1, y1, x2, y2, size = 3) { var dx = x2 - x1, dy = y2 - y1, num = Math.floor(Math.sqrt((dx * dx) + (dy * dy)) / size), xLen = dx / num, yLen = dy / num, count = 0; do { if (count % 2 == 0 && count > 0) { context.lineTo(x1, y1); } else { context.moveTo(x1, y1); } x1 += xLen; y1 += yLen; count++; } while(count <= num); }; // // Makes an AJAX call. It calls the given callback (a function) // when ready. // // @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 (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__ = callback; this.__user_callback__(this.responseText); } } httpRequest.open('GET', 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 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 (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__ = callback; this.__user_callback__(this.responseText); } } httpRequest.open('POST', url, true); httpRequest.setRequestHeader("Content-type","application/x-www-form-urlencoded"); for (i in data) { if (typeof i == 'string') { crumbs.push(i + '=' + encodeURIComponent(data[i])); } } httpRequest.send(crumbs.join('&')); }; // // Uses the above function but calls the call back passing // a number as its argument. // // @param url string The URL to fetch. // @param callback function Your callback function (which is passed the // number as an argument). // RGraph.AJAX.number = RGraph.AJAX.getNumber = function (url, callback) { RGraph.AJAX(url, function () { var num = parseFloat(this.responseText); callback(num); }); }; // // Uses the above function but calls the call back passing a // string as its argument. // // @param url string The URL to fetch. // @param callback function Your callback function (which is // passed the string as an argument). // RGraph.AJAX.string = RGraph.AJAX.getString = function (url, callback) { RGraph.AJAX(url, function () { var str = String(this.responseText); callback(str); }); }; // // Uses the above function but calls the call back passing // JSON (ie a JavaScript object ) as its argument. // // @param url string The URL to fetch. // @param callback function Your callback function (which is // passed the JSON object as an // argument). // RGraph.AJAX.json = RGraph.AJAX.JSON = RGraph.AJAX.getJSON = function (url, callback) { RGraph.AJAX(url, function () { var json = eval('(' + this.responseText + ')'); callback(json); }); }; // // Uses the above RGraph.AJAX function but calls the callback // passing an array as its argument. Useful if you're // retrieving CSV data. // // @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. // If not given it defaults to a // comma. // RGraph.AJAX.csv = RGraph.AJAX.CSV = RGraph.AJAX.getCSV = function (url, callback, separator = ',') { RGraph.AJAX(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 = font; div.style.fontWeight = bold ? 'bold' : 'normal'; div.style.fontStyle = italic ? 'italic' : 'normal'; div.style.fontSize = (size || 12) + 'pt'; //div.style.backgroundColor = 'red'; //document.body.removeChild(div); RGraph.measuretext_cache[str] = [div.offsetWidth, div.offsetHeight]; return [div.offsetWidth, div.offsetHeight]; }; // // New text function. Accepts one argument: // // 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.offsetWidth + 'px'; wrapper.style.height = obj.canvas.offsetHeight + '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 considered 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.italic || false, 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 only // 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.isNullish(data[group])) { group++; grouped_index = 0; continue; } // Allow for numbers as well as arrays in the dataset. if (typeof data[group] == 'number') { group++ grouped_index = 0; continue; } grouped_index++; if (grouped_index >= 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. // // @param object obj The chart object. // @param number dataset The dataset (or the group in Bar // charts and similar charts). // @param number index The index. // @return number The sequential index. // RGraph.groupedIndexToSequential = function (obj, dataset, index) { // Handle just the data being given instead of the whole // object. if (RGraph.isObject(obj) && obj.isrgraph) { var data = obj.data; } else { var data = obj; } for (var i=0,seq=0; i<=dataset; ++i) { for (var j=0; j 0 && pos < 20; }; // // Removes white-space from the start and end of a string. // // @param string str The string to trim. // @return string The resulting string. // RGraph.trim = function (str) { return RGraph.ltrim(RGraph.rtrim(str)); }; // // Trims the white-space from the start of a string. // // @param string str The string to trim. // @return string The resulting string. // RGraph.ltrim = function (str) { return str.replace(/^(\s|\0)+/, ''); }; // // Trims the white-space from the end of a string. // // @param string str The string to trim. // @return string The resulting string. // RGraph.rtrim = function (str) { return str.replace(/(\s|\0)+$/, ''); }; // // Returns true/false as to whether the given variable is // null or not. // // @param mixed obj The argument to check. // @return boolean Whether the value is null or not. // RGraph.isNull = function (obj) { return typeof obj === 'object' && !obj; }; // // Returns true/false as to whether the given variable is // null or not. This function also returns true if the // variable is undefined or NaN. // // @param mixed obj The argument to check. // @return boolean Whether the value is nullish (null, // undefined, NaN) or not. // RGraph.isNullish = function (obj) { if (RGraph.isUndefined(obj)) return true; if (RGraph.isNull(obj)) return true; if (Number.isNaN(obj)) 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. It's not true // asynchonicity but does appear that way somewhat. // // @param function func The function to run that creates // the chart. // @param delay number OPTIONAL A delay (in milliseconds) // that's given to the setTimeout // function. // RGraph.async = function (func, delay = 1) { return setTimeout(func, delay); }; // // Resets (more than just clears) the canvas and clears any // pertinent objects from the ObjectRegistry. // // @param object canvas The canvas object (as returned by // the document.getElementById() function). // RGraph.reset = function (canvas) { // If a string has been given, then treat it as the ID // of the canvas. if (typeof canvas === 'string') { canvas = document.getElementById(canvas); } // Lose the scaling attributes. if (canvas.getAttribute('data-rgraph-scale')) { var original_width = parseInt(canvas.getAttribute('data-rgraph-scale-original-width')); var original_height = parseInt(canvas.getAttribute('data-rgraph-scale-original-height')); canvas.removeAttribute('data-rgraph-scale'); canvas.removeAttribute('data-rgraph-scale-factor'); canvas.removeAttribute('data-rgraph-scale-original-width'); canvas.removeAttribute('data-rgraph-scale-original-height'); canvas.width = original_width; canvas.height = original_height; canvas.style.width = ''; canvas.style.height = ''; RGraph.cache = []; // TODO (???) // // Need to get all objects that are in the object // registry as pertaining to this canvas and go thru // the properties_scale and halve each. } // First things first - if there's a // visible tooltip then see if it's associated // with this canvas. If it is then hide it. var tooltip = RGraph.Registry.get('tooltip'); if (tooltip && tooltip.__canvas__ === canvas) { RGraph.hideTooltip(); } canvas.width = canvas.width; // Clear the ObjectRegistry. RGraph.ObjectRegistry.clear(canvas); // Get rid of references from the canvas that are added by // various RGraph dynamic features. // // Do the back image first. if (canvas.__rgraph_background_image__) { delete canvas.__rgraph_background_image__.__object__; delete canvas.__rgraph_background_image__.__canvas__; delete canvas.__rgraph_background_image__.__context__; } for (v of ['__object__', '__rgraph_background_image__']) { delete canvas[v]; } if (RGraph.text.domNodeCache && RGraph.text.domNodeCache.reset) { RGraph.text.domNodeCache.reset(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[canvas.id] = []; RGraph.text.domNodeDimensionCache[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 (id) //{ // var id = typeof id === 'object' ? id.id : id; // // var canvas = document.getElementById(id); // // return [id, canvas]; //}; // // A wrapper function that encapsulates requestAnimationFrame. // // @param function func The animation function that updates the // canvas. // RGraph.Effects.updateCanvas = function (func) { window.requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame || window.mozRequestAnimationFrame || (function (func){setTimeout(func, 16.666);}); window.requestAnimationFrame(func); }; // // Checks to see if the user has requested to stop the // animation. // // @param object obj 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 ease out towards the end of the effect. // // @param number frames The total number of frames. // @param number frame The frame number. // RGraph.Effects.getEasingMultiplier = function (frames, frame) { return Math.pow(Math.sin((frame / frames) * RGraph.HALFPI), 3); }; // // This function converts an array of strings to an array of // numbers. It's 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 string str The string or array to parse. // @param string separator OPTIONAL Use this instead of // the default comma. // RGraph.stringsToNumbers = function (str, sep = ',') { // If it's already a number just return it. if (typeof str === 'number') { return str; } // Remove preceding square brackets. if (typeof str === 'string' && str.trim().match(/^\[ *\d+$/)) { str = str.replace('[', ''); } if (typeof str === 'string') { if (str.indexOf(sep) != -1) { str = str.split(sep); } else { str = parseFloat(str); if (isNaN(str)) { str = null; } } } if (typeof str === 'object' && !RGraph.isNullish(str)) { for (var i=0,len=str.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 arguments givem, 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; // Account for scaling. if (obj.properties.scale) { obj.canvas.style.width = obj.canvas.width + 'px'; obj.canvas.width = obj.properties.scaleFactor * obj.canvas.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; // Account for scaling. if (obj.properties.scale) { obj.canvas.style.height = obj.canvas.height + 'px'; obj.canvas.height = obj.properties.scaleFactor * obj.canvas.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') { // Account for scaling by doubling property // values. var scaleFactor = RGraph.getScaleFactor(obj); obj.set( j, RGraph.isNumeric(rule.options[j]) ? (rule.options[j] * scaleFactor) : RGraph.arrayClone(rule.options[j]) ); // Set the original colors to the new colors // if necessary. if (obj.original_colors && !RGraph.isNullish(obj.original_colors[j])) { obj.original_colors[j] = 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') { RGraph.path({ object: this, path: arguments[0].path, args: arguments[0].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. // // @param object obj The chart object. All the properties are // retrieved from this. // @param object opt Options to the function. // RGraph.drawXAxis = function (obj, opt = {}) { var properties = obj.properties, context = obj.context, tickmarksLength = (typeof properties.xaxisTickmarksLength === 'number' ? properties.xaxisTickmarksLength : 3), isSketch = (obj.type === 'bar' && properties.variant === 'sketch'), scaleFactor = RGraph.getScaleFactor(obj); // // If the xaxisLabels property is defined then go through // it converting null and undefined values to empty // strings. // if ( typeof properties.xaxisLabels === 'object' && !RGraph.isNullish(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.isNullish(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.isNullish(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. // if (opt.labels !== false) { // // 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.isNullish(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.isNullish(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) } // // Whether to draw the labels. // if (opt.labels !== false) { // // 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 ); // // 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; }; // // Split a string into an array whilst allowing for quoted // sections. // // @param str string The string to Split // @param split string The character to split on (default=,) // @param quote string The quote character - either a single // or double quote (default=") // @param empty boolean Whether to keep empty elements or not // (default=true) // @return array The resulting array // RGraph.splitStringArray = function (str, split = ',', quote = '"', empty = true) { // Loop over the string, character-by-character var ret = [], quoted = false, buf = ''; for (var i=0; i 0 && x < (obj.canvas.width / 2) && y > 0 && y < obj.canvas.height ) { return true; } // Test that the cursor is over the right half. } else if (RGraph.isString(clip) && clip.toLowerCase().trim() === 'righthalf') { if ( x > (obj.canvas.width / 2) && x < obj.canvas.width && y > 0 && y < obj.canvas.height ) { return true; } // Test that the cursor is over the top half. } else if (RGraph.isString(clip) && clip.toLowerCase().trim() === 'tophalf') { if ( x > 0 && x < obj.canvas.width && y > 0 && y < (obj.canvas.height / 2) ) { return true; } // Test that the cursor is over the bottom half. } else if (RGraph.isString(clip) && clip.toLowerCase().trim() === 'bottomhalf') { if ( x > 0 && x < obj.canvas.width && y > (obj.canvas.height / 2) && y < obj.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 ( x > clip[0] && x < (clip[0] + clip[2]) && y > clip[1] && y < (clip[1] + clip[3]) ) { return true; } // // Test that the cursor is within the clipped path // based on the given coordinates. // } else if (RGraph.isArray(clip) && RGraph.isArray(clip[0])) { obj.path(RGraph.clipTo.path); return obj.context.isPointInPath(x, y); // // Test that the cursor is within the given X // percentages range. // } else if (clip.match(/^\s*x\s*:\s*([-.0-9minax]+)\s*%?\s*-\s*([.0-9minax]+)\s*%?\s*$/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) * (obj.canvas.width - obj.properties.marginLeft - obj.properties.marginRight), height = obj.canvas.height, startX = (from / 100) * (obj.canvas.width - obj.properties.marginLeft - obj.properties.marginRight) + obj.properties.marginLeft, startY = 0; obj.path( 'b r % % % %', startX,startY,width,height ); return obj.context.isPointInPath(x, y); // Test that the cursor is within the given Y // percentages range. } else if (clip.match(/^\s*y\s*:\s*([-.0-9minax]+)\s*%?\s*-\s*([.0-9minax]+)\s*%?\s*/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 = obj.canvas.height - obj.properties.marginTop - obj.properties.marginBottom, startX = 0, startY = (from / 100) * height + obj.properties.marginTop, width = obj.canvas.width, //y1 = ((to - from) / 100) * (obj.canvas.height - obj.properties.marginTop - obj.properties.marginBottom), y2 = (to / 100) * (obj.canvas.height - obj.properties.marginTop - obj.properties.marginBottom), startY = obj.canvas.height - obj.properties.marginBottom - y2, height = (obj.canvas.height - obj.properties.marginTop - obj.properties.marginBottom) * ( (to - from) / 100); obj.path( 'b r % % % %', startX,startY,width,height ); return obj.context.isPointInPath(x, y); // // Test that the cursor is within the given radius // percentages range. // } else if (clip.match(/^\s*r(?:adius)?\s*:\s*([-.0-9minax]+)\s*%?\s*-\s*([.0-9minax]+)\s*%?\s*$/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(obj.centerx) || !RGraph.isNumber(obj.centery) || !RGraph.isNumber(obj.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( obj.type, obj.id, obj.uid )); } var centerx = obj.centerx, centery = obj.centery, r1 = (from / 100) * obj.radius, r2 = (to / 100) * obj.radius; obj.path( 'sa ' + 'b a % % % 0 6.29 false a % % % 6.29 0 true cl', centerx, centery, r1, centerx, centery, r2 ); return obj.context.isPointInPath(x, 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(/^\s*(?:scale)\s*:\s*([-.0-9min]+)\s*-\s*([-.0-9max]+)\s*$/)) { if (obj.clipToScaleTestWorker) { obj.clipToScaleTestWorker(clip); return obj.context.isPointInPath(x, y); } else { console.log('The scale: clipping option isn\'t implemented for this chart type (' + obj.type + ')'); } // Test that the cursor is within the clipped // circle. // } else if (clip.match(/^\s*circle\s*:\s*([-.0-9]+)\s*,?\s*([-.0-9]+)\s*,?\s*([-.0-9]+)\s*$/i)) { var centerx = RegExp.$1, centery = RegExp.$2, radius = RegExp.$3; obj.path( 'sa b a % % % 0 6.29 false s #ddd cl rs', centerx, centery, radius, ); return obj.context.isPointInPath(x, y); // Test that the cursor is within the specified segment // or arc. } else if (clip.match(/^\s*(?:segment|arc)\s*:\s*([-.0-9]+)\s*,?\s*([-.0-9]+)\s*,?\s*([-.0-9]+)\s*,?\s*([-.0-9degrad]+)\s*,?\s*([-.0-9degrad]+)\s*$/i)) { var centerx = RegExp.$1, centery = RegExp.$2, radius = RegExp.$3, start = RegExp.$4, end = RegExp.$5; // // If radians has been stipulated then get rid of // it. // start = start.replace(/rad$/, ''); end = end.replace(/rad$/, ''); // // Convert degrees to radians for the start angle. // if (start.match(/deg$/)) { start = parseFloat(start); start = start * (RGraph.PI / 180); } // // Convert degrees to radians for the end angle. // if (end.match(/deg$/)) { end = parseFloat(end); end = end * (RGraph.PI / 180); } obj.path( 'sa b m % % a % % % % % false c cl', centerx, centery, centerx, centery, radius, start - RGraph.HALFPI, end - RGraph.HALFPI ); return obj.context.isPointInPath(x, y); // // Test the clipping when it's an RGraph path. // } else if (RGraph.isString(clip)) { obj.path('sa'); obj.path(clip); obj.path('rs'); return obj.context.isPointInPath(x, y); } } }; // // A shortcut for performing a function whilst clipping // is enabled (if it's configured to be enabled). If it's // not then the function is still called - albeit without // clipping enabled. // // @param object obj The chart object. // @param function The function to run. This function is run // regardless of whether clipping is enabled // or not. If it is clipping will be installed // first. // RGraph.clipTo.callback = function (obj, callback) { var endClipping = false; // Install clipping so that the highlight is clipped // as well as the main chart. if (!RGraph.isNullish(obj.properties.clip)) { RGraph.clipTo.start(obj, obj.properties.clip); endClipping = true; } // // Call the user supplied function. // var ret = callback(obj); // // Get rid of clipping if it's been enabled. // if (endClipping) { RGraph.clipTo.end(); } // Return whatever the user function returns. return ret; }; // // Parses a string such as this: // // foo=bar,foo2=bar2,foo3="bar 3" // // @param string string The string to parse. // @param string separator The separator used to separate values. // @param boolean preserve By default the values are // trim()med to remove any trailing // or leading whitepsace - by // setting this to true this will // not be done and any whitespace // will be preserved. // @return An object of the results. // RGraph.parseConfigString = function (string, separator = ',', preserve = true) { var insideQuote = false, entries = [], entry = [], parts = {}; string.split('').forEach(function (character) { if(character === '"' || character === "'") { insideQuote = !insideQuote; } else { if(character === 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 * scaleFactor) + ','+ (scaleFactor * 3) + ']'; } if (conf.dashed === true || (typeof conf.dashed === 'undefined' && typeof conf.dotted === 'undefined') ) { linedash = '['+ (6 * scaleFactor) + ','+ (scaleFactor * 6) + ']'; } // // Draw the line. // obj.path( 'lw % ld % b m % % l % % s %', (typeof conf.linewidth === 'number' ? conf.linewidth : defaults.linewidth) * scaleFactor, 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 * scaleFactor || defaults.labelOffsetx * scaleFactor), y: textY - (linewidth / 2) + parseFloat((conf.labelPosition.indexOf('top') !== -1 ? (-1 * (conf.labelOffsety * scaleFactor) || (defaults.labelOffsety * scaleFactor) ) : conf.labelOffsety * scaleFactor) || defaults.labelOffsety * scaleFactor), 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' }); } } }; // // Adds a style to the document. You give the whole // selector/style string and it will add it. // // If a style element hasn't already been created // it will create one and then reuse it on subsequent // calls to this function. // // @param string style The styles to assign to the // new CSS. An example: // // table tr td {color: red;} // // @param object styleElement The style element to which the // style is attached. // @param object parent If given (default is null), // this is the node to which the // style element is attached. If // not given then the style element // is added to the . // RGraph.addCss = function (style, styleElement = null, parent = null) { // Create a style element and add the CSS var el = styleElement || document.createElement('style'); el.insertAdjacentHTML('beforeend', style); // Append the style element to the document? Only // do this on newly created style elements. You can // optionally pass in an element on which to attach // the style element instead of the head. if (parent) { parent.appendChild(el); } else { document.head.appendChild(el); } return el; }; // // This function allows you to run a function once // (immediately). Future calls using the same identifier // are not run. // // @param string id A unique identifier used to // identifier this function. // @param function func The function to call. // @param function alternative An alternative function to run // if the main function has already // been installed (with this // runOnce() function). // @return The return value of the function. // RGraph.runOnce = function (id, func, alternative = null) { if (RGraph.Registry.get('rgraph-runonce-functions')[id]) { if (RGraph.isFunction(alternative)) { (alternative)(); } return; } RGraph.Registry.get('rgraph-runonce-functions')[id] = func; return func(); }; // // Clears all of the runOnce data - allowing functions to // run again. // // @param id string An optional ID that can be given to limit // the clearing to. Otherwise all of the] // runOnce functons are cleared. // RGraph.runOnce.clear = function (id = null) { if (id) { RGraph.Registry.get('rgraph-runonce-functions')[id] = null; } else { RGraph.Registry.set('rgraph-runonce-functions', []); } }; // // 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. // @return string The resulting 32 character hash. // RGraph.md5 = function (str) { // Allow various data structures to be given as well as // strings. if (!RGraph.isString(str)) { str = JSON.stringify(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 1) target = 1; if (target < 0) target = 0; // The diff between the target opacity and // the current opacity. var diff = target - current; for (var i=0; i<=frames; ++i) { (function (index) { setTimeout(function() { el.style.opacity = current + ((diff / frames) * index); }, (delay / frames) * index); })(i); } if (RGraph.isFunction(callback)) { setTimeout(callback, delay); } }; RGraph.fade.OUT = 0; RGraph.fade.IN = 1; // // A queuing function. Give this a name of a queue name and a // function to run and it will run that function when the queue // is resolved. // // @param string name The queue to add the function to. // @param function func The function that will be queued and // run when the queue is resolved. // RGraph.Queue.add = function (name, func) { if (!RGraph.isArray(RGraph.Queue.__store__)) { RGraph.Queue.__store__ = {}; } if (!RGraph.Queue.__store__[name]) { RGraph.Queue.__store__[name] = []; } // Add the function to the queue RGraph.Queue.__store__[name].push(func); }; // // Run all the functions in a queue (if there are any) that // have been added. // // @param string name The name of the queue to resolve. // RGraph.Queue.resolve = function (name) { // Loop through the queue running the functions if (RGraph.isArray(RGraph.Queue.__store__[name])) { for (var i=0; i' + text, 'text/html' ); return dom.body.textContent; }; // // This is an implementation of the PHP date() function // which came from the phpJS project (phpjs.org - though // the website seems to be offline at the moment). You // can use it like this: // // RGraph.PHP.date('jS F Y'); // 16th May 2025 // // See the API docs for more information on how to use this // function: https://www.rgraph.net/api // // @param string format The date format that you want to be // returned. // RGraph.PHP.date = function (format, timestamp) { // // If the modifier is present then use that and pass it // to the RGraph.PHP.time() function to get the relevant // unix timestamp. // if (RGraph.isString(timestamp) && RGraph.isNumeric(timestamp)) { timestamp = parseInt(timestamp); } else if (RGraph.isString(timestamp)) { timestamp = RGraph.PHP.time(timestamp); } var that = this; var jsdate, f; // Keep this here (works, but for code commented-out // below for file size reasons). // var tal= []; var txt_words = [ 'Sun', 'Mon', 'Tues', 'Wednes', 'Thurs', 'Fri', 'Satur', 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ]; // trailing backslash -> (dropped) // a backslash followed by any character (including // backslash) -> the character empty string -> empty // string. var formatChr = /\\?(.?)/gi; var formatChrCb = function(t, s) { return f[t] ? f[t]() : s; }; var _pad = function(n, c) { n = String(n); while (n.length < c) { n = '0' + n; } return n; }; f = { // Day of month w/leading 0; 01..31. d: function() { return _pad(f.j(), 2); }, // Shorthand day name; Mon - Sun. D: function() { return f.l().slice(0, 3); }, // Day of month; 1 - 31 j: function() { return jsdate.getDate(); }, // Full day name; Monday...Sunday. l: function() { return txt_words[f.w()] + 'day'; }, // ISO-8601 day of week; 1[Mon]..7[Sun]. N: function() { return f.w() || 7; }, // Ordinal suffix for day of month; st, nd, rd, th. S: function() { var j = f.j(); var i = j % 10; if (i <= 3 && parseInt((j % 100) / 10, 10) == 1) { i = 0; } return ['st', 'nd', 'rd'][i - 1] || 'th'; }, // Day of week; 0[Sun]..6[Sat]. w: function() { return jsdate.getDay(); }, // Day of year; 0..365. z: function() { var a = new Date(f.Y(), f.n() - 1, f.j()); var b = new Date(f.Y(), 0, 1); return Math.round((a - b) / 864e5); }, // ISO-8601 week number. W: function() { var a = new Date(f.Y(), f.n() - 1, f.j() - f.N() + 3); var b = new Date(a.getFullYear(), 0, 4); return _pad(1 + Math.round((a - b) / 864e5 / 7), 2); }, // Full month name; January...December. F: function() { return txt_words[6 + f.n()]; }, // Month w/leading 0; 01...12 m: function() { return _pad(f.n(), 2); }, // Shorthand month name; Jan...Dec. M: function() { return f.F().slice(0, 3); }, // Month; 1...12. n: function() { return jsdate.getMonth() + 1; }, // Days in month; 28...31. t: function() { return (new Date(f.Y(), f.n(), 0)).getDate(); }, // Is leap year?; 0 or 1 L: function() { var j = f.Y(); return j % 4 === 0 & j % 100 !== 0 | j % 400 === 0; }, // ISO-8601 year o: function() { var n = f.n(); var W = f.W(); var Y = f.Y(); return Y + (n === 12 && W < 9 ? 1 : n === 1 && W > 9 ? -1 : 0); }, // Full year; e.g. 1980...2010. Y: function() { return jsdate.getFullYear(); }, // Last two digits of year; 00...99. y: function() { return f.Y().toString().slice(-2); }, // am or pm. a: function() { return jsdate.getHours() > 11 ? 'pm' : 'am'; }, // AM or PM. A: function() { return f.a().toUpperCase(); }, // Swatch Internet time; 000..999. B: function() { var H = jsdate.getUTCHours() * 36e2; // Hours var i = jsdate.getUTCMinutes() * 60; // Minutes var s = jsdate.getUTCSeconds(); // Seconds return _pad(Math.floor((H + i + s + 36e2) / 86.4) % 1e3, 3); }, // 12-Hours; 1..12. g: function() { return f.G() % 12 || 12; }, // 24-Hours; 0..23. G: function() { return jsdate.getHours(); }, // 12-Hours w/leading 0; 01..12. h: function() { return _pad(f.g(), 2); }, // 24-Hours w/leading 0; 00..23. H: function() { return _pad(f.G(), 2); }, // Minutes w/leading 0; 00..59. i: function() { return _pad(jsdate.getMinutes(), 2); }, // Seconds w/leading 0; 00..59. s: function() { return _pad(jsdate.getSeconds(), 2); }, // Microseconds; 000000-999000. u: function() { return _pad(jsdate.getMilliseconds() * 1000, 6); }, // Timezone identifier; e.g. Atlantic/Azores, ... e: function() { // The following works, but requires inclusion of // the very large timezone_abbreviations_list() // function. // // return that.date_default_timezone_get(); // console.log('Not supported (see source code of date() for timezone on how to add support)'); return ''; }, // DST observed?; 0 or 1. I: function() { // Compares Jan 1 minus Jan 1 UTC to Jul 1 minus Jul 1 UTC. // If they are not equal, then DST is observed. var a = new Date(f.Y(), 0); // Jan 1 var c = Date.UTC(f.Y(), 0); // Jan 1 UTC. var b = new Date(f.Y(), 6); // Jul 1. var d = Date.UTC(f.Y(), 6); // Jul 1 UTC return ((a - c) !== (b - d)) ? 1 : 0; }, // Difference to GMT in hour format; e.g. +0200. O: function() { var tzo = jsdate.getTimezoneOffset(); var a = Math.abs(tzo); return (tzo > 0 ? '-' : '+') + _pad(Math.floor(a / 60) * 100 + a % 60, 4); }, // Difference to GMT w/colon; e.g. +02:00. P: function() { var O = f.O(); return (O.substr(0, 3) + ':' + O.substr(3, 2)); }, // Timezone abbreviation; e.g. EST, MDT, ... T: function() { // The following works, but requires inclusion of the // very large timezone_abbreviations_list() function. // //var abbr, i, os, _default; // //if (!tal.length) { // tal = that.timezone_abbreviations_list(); //} //if (that.php_js && that.php_js.default_timezone) { // _default = that.php_js.default_timezone; // // for (abbr in tal) { // for (i = 0; i < tal[abbr].length; i++) { // if (tal[abbr][i].timezone_id === _default) { // return abbr.toUpperCase(); // } // } // } //} // //for (abbr in tal) { // for (i = 0; i < tal[abbr].length; i++) { // os = -jsdate.getTimezoneOffset() * 60; // // if (tal[abbr][i].offset === os) { // return abbr.toUpperCase(); // } // } //} return 'UTC'; }, // Timezone offset in seconds (-43200...50400). Z: function() { return -jsdate.getTimezoneOffset() * 60; }, // ISO-8601 date. c: function() { return 'Y-m-d\\TH:i:sP'.replace(formatChr, formatChrCb); }, // RFC 2822 r: function() { return 'D, d M Y H:i:s O'.replace(formatChr, formatChrCb); }, // Seconds since UNIX epoch. U: function() { return jsdate / 1000 | 0; } }; var date = function(format, timestamp) { that = this; jsdate = (timestamp === undefined ? new Date() : (timestamp instanceof Date) ? new Date(timestamp) : new Date(timestamp * 1000) // UNIX timestamp (auto-convert to int) ); return format.replace(formatChr, formatChrCb); }; return date(format, timestamp); }; // // Returns the current number of seconds since the UNIX epoch. // // @param string modifier You can optionally supply a string // that will be used to modify the // timestamp, for example: // // var timestamp = RGraph.PHP.time('now + 1 day 6 hours') // var timestamp = RGraph.PHP.time('now - 90 minutes') // // See the API docs on the website for more // modifiers that you can use. // @return number Seconds. // RGraph.PHP.time = function (modifier = null) { // Get the time ine seconds - not milloseconds. var timestamp = Math.floor(Date.now() / 1000); if (RGraph.isString(modifier)) { modifier = modifier.toLowerCase(); modifier = modifier.replace('now', timestamp); modifier = modifier.replace(/([.0-9]+) *y(ear)?s?/g, '($1 * 31536000)'); modifier = modifier.replace(/([.0-9]+) *d(ay)?s?/g, '($1 * 86400)'); modifier = modifier.replace(/([.0-9]+) *w(eek)?s?/g, '($1 * 86400 * 7)'); modifier = modifier.replace(/([.0-9]+) *h(our)?s?/g, '($1 * 3600)'); modifier = modifier.replace(/([.0-9]+) *m(in)?(?:ute)?(?:s)?/g, '($1 * 60)'); modifier = modifier.replace(/([.0-9]+) *s(ec)?(?:ond)?(?:s)?/g, '($1 * 1)'); return parseInt(eval(modifier)); } else { return Math.floor(Date.now() / 1000); } }; // // Returns a data: string that represents an image of the // canvas. // // @param object canvas The canvas to get the image of. // @param mixed options This can be either a number or // an object of options. This object // can contain: // o backgroundColor // o background Using this option // you can specify a // background color // for the image. By // default the // background color // of the image is // white. // o width The width of the // image. // o height The height of the // image. // // This can also be a number between // 0 and 1 in which case it's multiplied // with the original width height to // get the desired wideth/height. // // @return string A base64 encoded data: string // RGraph.getImage = function (canvas,opt = null) { // // If the width argument is a number between 0 and 2 // then use it as a multiplier and multiply it with // the original width/height. // if (RGraph.isNumber(opt) && opt >= 0 && opt <= 2 ) { var multiplier = opt; opt = { height: multiplier * canvas.offsetHeight, width: multiplier * canvas.offsetWidth } } else if (RGraph.isObject(opt) && (RGraph.isNumber(opt.width) || RGraph.isNumber(opt.height)) ) { opt.width = RGraph.isNumber(opt.width) ? opt.width : canvas.offsetWidth; opt.height = RGraph.isNumber(opt.height) ? opt.height : canvas.offsetHeight; } else if ( (RGraph.isObject(opt) && (!RGraph.isNullish(opt.background) || !RGraph.isNullish(opt.backgroundColor))) || RGraph.isNullish(opt) ) { RGraph.clear(canvas, opt ? (opt.background || opt.backgroundColor) : 'white'); var objects = RGraph.ObjectRegistry.getObjectsByCanvasID(canvas.id); // Now draw each object. for (var i=0; i 0; }; // // Remove a node from the tree given its index. // //@param mixed i This can either be a number - ie the // index of the child node to remove or // it can be the child node itself. // @return object The node that has been removed. // this.remove = function (i) { // // Argument given is a number. // if (RGraph.isNumber(i)) { // Does the requested child exist? if (!this.children[i]) { return null; } // // This is the node to be removed from the tree. // var node = this.children[i]; node.parent = null; node.depth = 0; // // Redo the depth property now that the node has // been removed. OPERATES ON THE NODE THAT HAS // BEEN REMOVED!! // node.iterate(function (n) { n.depth = n.parent ? n.parent.depth + 1 : 0; }); // This nullifies the node in the tree. this.children[i] = null; // Now reindex the .children array so there are // no empty slots in it. this.children = this.children.filter(val => val !== null); return node; // // Argument given is a tree node. // } else if (RGraph.isObject(i) && !RGraph.isNull(i.parent)) { // Look for the object in this nodes child nodes. for (var j=0; j 1) { while (count-- > 0) { this.moveUp(); } } else if (count == 0) { // DOUBLE EQUALS. return; } else { var currentIndex = this.parent.index(this); // Only move the node if the currentIndex is bigger // than 0. if (currentIndex > 0) { var tmp = this.parent.children[currentIndex - 1]; this.parent.children[currentIndex - 1] = this; this.parent.children[currentIndex] = tmp; } } }; // // Moves a node down in the list of children. So if a node // is the second child it becomes the third. If a node is // the third child it becomes the fourth. And if a node // is the last child nothing happens. // // @param number count The number of nodes to move this // node up by. This argument defaults // to 1. // this.moveDown = function (count = 1) { count = Number(count); if (count > 1) { while (count-- > 0) { this.moveDown(); } } else if (count == 0) { // DOUBLE EQUALS. return; } else { var currentIndex = this.parent.index(this); var numChildren = this.parent.numChildren(); // Only move the node if the currentIndex is not the // last. if (currentIndex < (numChildren - 1) ) { var tmp = this.parent.children[currentIndex + 1]; this.parent.children[currentIndex + 1] = this; this.parent.children[currentIndex] = tmp; } } }; // // Copy this node to the given node. Copies are done on // a shallow basis. So strings and numbers are copied // but if the data for this node is an object then the // new nodes data will be a reference to the same object. // Child nodes are not copied. // // @param object node The new node to copy this one to. // @return object The new copy of this node. // this.copyTo = function (node) { var newnode = node.add(this.data); return newnode; }; // // Iterates through the child nodes, running the provided // function (which is passed the child node as the // argument). This function recurses into each and every // branch. // // @param function func The function to run. The function // is given the current child node as // the argument. // @param numer maxDepth The maximum depth that you wish to // recurse to. // this.iterate = function (func, maxDepth = null) { var root = this; var currentDepth = 0; // Reset the stop variable. root.stopIteration = false; function iterate_worker (node, func, currentDepth) { // Call the callback function for this node. var result = func(root, node, currentDepth); if (result === false) { root.stopIteration = true; } // Loop through the child nodes if we haven't // been requested to stop. if (root.stopIteration !== true) { for (var i=0; (i'; var folderImg = ''; var folderexpImg = ''; var pageImg = ''; var lineImg = ''; var branchImg = ''; var branchtopImg = ''; var branchbottomImg = ''; var plusImg = ''; var plusbottomImg = ''; var plustopImg = ''; var minusImg = ''; var minusbottomImg = ''; var minustopImg = ''; // // First convert all the data properties on the nodes // to objects. // for (var i=0; i' + indent; // // Determine the relevant branch icon. // if (node.getRoot() === node) { var icon = ''; } else { if (node.isLastChild()) { var icon = node.hasChildren() ? minusbottomImg : branchbottomImg; } else { var icon = node.hasChildren() ? minusImg : branchImg; } } html += icon; // // Add the image for this branch. // if (RGraph.isObject(node.getData()) && RGraph.isString(node.getData().image)) { html += ''; } else if (node.hasChildren()) { html += folderImg; } else { html += pageImg; } html += '' + (node.getData().link ? '{2}'.format(node.getData().link, node.getData().text) : node.getData().text) + ''; html += ''; html +='
'; if (node.hasChildren()) { for (var i=0; i block in the head. RGraph.addCss(str); } // This will get the children of a node: // document.querySelectorAll('div[-id^="node-0-2-0-"]') }; }; // // A (sort of) pseudo class for creating tree menus. // RGraph.Treemenu = function (conf) { this.id = conf.id; this.tree = conf.data; // // The main draw function. // this.draw = function () { conf.options.id = this.id; this.tree.toHTML(conf.options); }; }; // Some utility functions that help identify the type of an object. // // Note that isUndefined() should be used like this or you'll get an // error (with the window. prefix): // // RGraph.isUndefined(window.foo) // RGraph.isString = function(obj){return typeof obj === 'string';}; RGraph.isNumber = function(obj){return typeof obj === 'number';}; RGraph.isTextual = function(obj){return (typeof obj === 'string' || typeof obj === 'number');}; RGraph.isNumeric = function(value){if(RGraph.isArray(value)){return false;}value=String(value);return Boolean(value.match(/^-?[0-9]+(?:.[0-9]+)?$/))|| Boolean(value.match(/^-?[0-9]+(?:.[0-9+])?e-?[0-9]+$/))|| (value==='Infinity')|| (value==='-Infinity')|| !RGraph.isNullish(value.match(/^[0-9]+(?:.[0-9]+)?x[0-9a-f]+$/i));}; RGraph.isBool = RGraph.isBoolean = function(obj){return typeof obj === 'boolean';}; RGraph.isRegex = RGraph.isRegexp = function (obj){return obj.constructor.toString().toLowerCase().indexOf('regex') > 0;}; // RGraph.isArray Defined above. RGraph.isObject = function(obj){return (obj && typeof obj === 'object' && obj.constructor.toString().toLowerCase().indexOf('object') > 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 mixed ... 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.isNullish(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__', '%'); };