node-red-contrib-smartnode-hook
Version:
this project is dependenced by the node-red-contrib-smartnode for some IoT display function
1,614 lines (1,179 loc) • 135 kB
JavaScript
// version: 2014-11-15
/**
* o--------------------------------------------------------------------------------o
* | This file is part of the RGraph package - you can learn more at: |
* | |
* | http://www.rgraph.net |
* | |
* | This package is licensed under the Creative Commons BY-NC license. That means |
* | that for non-commercial purposes it's free to use and for business use there's |
* | a 99 GBP per-company fee to pay. You can read the full license here: |
* | |
* | http://www.rgraph.net/license |
* o--------------------------------------------------------------------------------o
*/
RGraph = window.RGraph || {isRGraph: true};
// Module pattern
(function (win, doc, undefined)
{
var RG = RGraph,
ua = navigator.userAgent,
ma = Math;
/**
* Initialise the various objects
*/
RG.Highlight = {};
RG.Registry = {};
RG.Registry.store = [];
RG.Registry.store['chart.event.handlers'] = [];
RG.Registry.store['__rgraph_event_listeners__'] = []; // Used in the new system for tooltips
RG.Background = {};
RG.background = {};
RG.objects = [];
RG.Resizing = {};
RG.events = [];
RG.cursor = [];
RG.Effects = RG.Effects || {};
RG.cache = [];
RG.ObjectRegistry = {};
RG.ObjectRegistry.objects = {};
RG.ObjectRegistry.objects.byUID = [];
RG.ObjectRegistry.objects.byCanvasID = [];
/**
* Some "constants". The ua variable is navigator.userAgent (definedabove)
*/
RG.PI = ma.PI;
RG.HALFPI = RG.PI / 2;
RG.TWOPI = RG.PI * 2;
RG.ISFF = ua.indexOf('Firefox') != -1;
RG.ISOPERA = ua.indexOf('Opera') != -1;
RG.ISCHROME = ua.indexOf('Chrome') != -1;
RG.ISSAFARI = ua.indexOf('Safari') != -1 && !RG.ISCHROME;
RG.ISWEBKIT = ua.indexOf('WebKit') != -1;
RG.ISIE = ua.indexOf('Trident') > 0 || navigator.userAgent.indexOf('MSIE') > 0;
RG.ISIE6 = ua.indexOf('MSIE 6') > 0;
RG.ISIE7 = ua.indexOf('MSIE 7') > 0;
RG.ISIE8 = ua.indexOf('MSIE 8') > 0;
RG.ISIE9 = ua.indexOf('MSIE 9') > 0;
RG.ISIE10 = ua.indexOf('MSIE 10') > 0;
RG.ISOLD = RGraph.ISIE6 || RGraph.ISIE7 || RGraph.ISIE8; // MUST be here
RG.ISIE11UP = ua.indexOf('MSIE') == -1 && ua.indexOf('Trident') > 0;
RG.ISIE10UP = RG.ISIE10 || RG.ISIE11UP;
RG.ISIE9UP = RG.ISIE9 || RG.ISIE10UP;
/**
* Returns five values which are used as a nice scale
*
* @param max int The maximum value of the graph
* @param obj object The graph object
* @return array An appropriate scale
*/
RG.getScale = function (max, obj)
{
/**
* 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('chart.scale.round') ? [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 = ma.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('chart.scale.round')) == 'boolean' && obj.Get('chart.scale.round')) {
topValue = 10 * interval;
}
return [topValue * 0.2, topValue * 0.4, topValue * 0.6, topValue * 0.8, topValue];
};
/**
* Returns an appropriate scale. The return value is actualy an object consisting of:
* scale.max
* scale.min
* scale.scale
*
* @param obj object The graph object
* @param prop object An object consisting of configuration properties
* @return object An object containg scale information
*/
RG.getScale2 = function (obj, opt)
{
var ca = obj.canvas;
var co = obj.context;
var prop = obj.properties;
var numlabels = typeof(opt['ylabels.count']) == 'number' ? opt['ylabels.count'] : 5;
var units_pre = typeof(opt['units.pre']) == 'string' ? opt['units.pre'] : '';
var units_post = typeof(opt['units.post']) == 'string' ? opt['units.post'] : '';
var max = Number(opt['max']);
var min = typeof(opt['min']) == 'number' ? opt['min'] : 0;
var strict = opt['strict'];
var decimals = Number(opt['scale.decimals']); // Sometimes the default is null
var point = opt['scale.point']; // Default is a string in all chart libraries so no need to cast it
var thousand = opt['scale.thousand']; // Default is a string in all chart libraries so no need to cast it
var original_max = max;
var round = opt['scale.round'];
var scale = {'max':1,'labels':[]};
/**
* Special case for 0
*
* ** Must be first **
*/
if (!max) {
var max = 1;
var scale = {max:1,min:0,labels:[]};
for (var i=0; i<numlabels; ++i) {
var label = ((((max - min) / numlabels) + min) * (i + 1)).toFixed(decimals);
scale.labels.push(units_pre + label + units_post);
}
/**
* Manually do decimals
*/
} else if (max <= 1 && !strict) {
if (max > 0.5) {
max = 1;
min = min;
scale.min = min;
for (var i=0; i<numlabels; ++i) {
var label = ((((max - min) / numlabels) * (i + 1)) + min).toFixed(decimals);
scale.labels.push(units_pre + label + units_post);
}
} else if (max >= 0.1) {
max = 0.5;
min = min;
scale = {'max': 0.5, 'min':min,'labels':[]}
for (var i=0; i<numlabels; ++i) {
var label = ((((max - min) / numlabels) + min) * (i + 1)).toFixed(decimals);
scale.labels.push(units_pre + label + units_post);
}
} else {
scale = {'min':min,'labels':[]}
var max_str = String(max);
if (max_str.indexOf('e') > 0) {
var numdecimals = ma.abs(max_str.substring(max_str.indexOf('e') + 1));
} else {
var numdecimals = String(max).length - 2;
}
var max = 1 / ma.pow(10,numdecimals - 1);
for (var i=0; i<numlabels; ++i) {
var label = ((((max - min) / numlabels) + min) * (i + 1));
label = label.toExponential();
label = label.split(/e/);
label[0] = ma.round(label[0]);
label = label.join('e');
scale.labels.push(label);
}
//This makes the top scale value of the format 10e-2 instead of 1e-1
tmp = scale.labels[scale.labels.length - 1].split(/e/);
tmp[0] += 0;
tmp[1] = Number(tmp[1]) - 1;
tmp = tmp[0] + 'e' + tmp[1];
scale.labels[scale.labels.length - 1] = tmp;
// Add the units
for (var i=0; i<scale.labels.length ; ++i) {
scale.labels[i] = units_pre + scale.labels[i] + units_post;
}
scale.max = Number(max);
}
} else if (!strict) {
/**
* Now comes the scale handling for integer values
*/
// This accomodates decimals by rounding the max up to the next integer
max = ma.ceil(max);
var interval = ma.pow(10, ma.max(1, Number(String(Number(max) - Number(min)).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(round) == 'boolean' && round) {
topValue = 10 * interval;
}
scale.max = topValue;
// Now generate the scale. Temporarily set the objects chart.scale.decimal and chart.scale.point to those
//that we've been given as the number_format functuion looks at those instead of using argumrnts.
var tmp_point = prop['chart.scale.point'];
var tmp_thousand = prop['chart.scale.thousand'];
obj.Set('chart.scale.thousand', thousand);
obj.Set('chart.scale.point', point);
for (var i=0; i<numlabels; ++i) {
scale.labels.push( RG.number_format(obj, ((((i+1) / numlabels) * (topValue - min)) + min).toFixed(decimals), units_pre, units_post) );
}
obj.Set('chart.scale.thousand', tmp_thousand);
obj.Set('chart.scale.point', tmp_point);
} else if (typeof(max) == 'number' && strict) {
/**
* ymax is set and also strict
*/
for (var i=0; i<numlabels; ++i) {
scale.labels.push( RG.number_format(obj, ((((i+1) / numlabels) * (max - min)) + min).toFixed(decimals), units_pre, units_post) );
}
// ???
scale.max = max;
}
scale.units_pre = units_pre;
scale.units_post = units_post;
scale.point = point;
scale.decimals = decimals;
scale.thousand = thousand;
scale.numlabels = numlabels;
scale.round = Boolean(round);
scale.min = min;
return scale;
};
/**
* Makes a clone of an object
*
* @param obj val The object to clone
*/
RG.arrayClone =
RG.array_clone = function (obj)
{
if(obj === null || typeof obj !== 'object') {
return obj;
}
var temp = [];
for (var i=0,len=obj.length;i<len; ++i) {
if (typeof obj[i] === 'number') {
temp[i] = (function (arg) {return Number(arg);})(obj[i]);
} else if (typeof obj[i] === 'string') {
temp[i] = (function (arg) {return String(arg);})(obj[i]);
} else if (typeof obj[i] === 'function') {
temp[i] = obj[i];
} else {
temp[i] = RG.array_clone(obj[i]);
}
}
return temp;
};
/**
* Returns the maximum numeric value which is in an array
*
* @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
*/
RG.arrayMax =
RG.array_max = function (arr)
{
var max = null;
var ma = Math;
if (typeof arr === 'number') {
return arr;
}
if (RG.is_null(arr)) {
return 0;
}
for (var i=0,len=arr.length; i<len; ++i) {
if (typeof arr[i] === 'number') {
var val = arguments[1] ? ma.abs(arr[i]) : arr[i];
if (typeof max === 'number') {
max = ma.max(max, val);
} else {
max = val;
}
}
}
return max;
};
/**
* Returns the maximum value which is in an array
*
* @param array arr The array
* @param int len The length to pad the array to
* @param mixed The value to use to pad the array (optional)
*/
RG.arrayPad =
RG.array_pad = function (arr, len)
{
if (arr.length < len) {
var val = arguments[2] ? arguments[2] : null;
for (var i=arr.length; i<len; i+=1) {
arr[i] = val;
}
}
return arr;
};
/**
* An array sum function
*
* @param array arr The array to calculate the total of
* @return int The summed total of the arrays elements
*/
RG.arraySum =
RG.array_sum = function (arr)
{
// Allow integers
if (typeof arr === 'number') {
return arr;
}
// Account for null
if (RG.is_null(arr)) {
return 0;
}
var i, sum, len = arr.length;
for(i=0,sum=0;i<len;sum+=arr[i++]);
return sum;
};
/**
* Takes any number of arguments and adds them to one big linear array
* which is then returned
*
* @param ... mixed The data to linearise. You can strings, booleans, numbers or arrays
*/
RG.arrayLinearize =
RG.array_linearize = function ()
{
var arr = [];
var args = arguments;
for (var i=0,len=args.length; i<len; ++i) {
if (typeof args[i] === 'object' && args[i]) {
for (var j=0,len2=args[i].length; j<len2; ++j) {
var sub = RG.array_linearize(args[i][j]);
for (var k=0,len3=sub.length; k<len3; ++k) {
arr.push(sub[k]);
}
}
} else {
arr.push(args[i]);
}
}
return arr;
};
/**
* Takes one off the front of the given array and returns the new array.
*
* @param array arr The array from which to take one off the front of array
*
* @return array The new array
*/
RG.arrayShift =
RG.array_shift = function(arr)
{
var ret = [];
for(var i=1,len=arr.length; i<len; ++i) {
ret.push(arr[i]);
}
return ret;
};
/**
* Reverses the order of an array
*
* @param array arr The array to reverse
*/
RG.arrayReverse =
RG.array_reverse = function (arr)
{
var newarr=[];
for(var i=arr.length - 1; i>=0; i-=1) {
newarr.push(arr[i]);
}
return newarr;
};
/**
* Clears the canvas by setting the width. You can specify a colour if you wish.
*
* @param object canvas The canvas to clear
* @param mixed Usually a color string to use to clear the canvas
* with - could also be a gradient object
*/
RG.clear =
RG.Clear = function (ca)
{
var obj = ca.__object__;
var co = ca.getContext('2d');
var color = arguments[1] || (obj && obj.get('clearto'));
if (!ca) {
return;
}
RG.FireCustomEvent(obj, 'onbeforeclear');
if (RG.ISIE8 && !color) {
color = 'white';
}
/**
* Can now clear the canvas back to fully transparent
*/
if (!color || (color && color === 'rgba(0,0,0,0)' || color === 'transparent')) {
co.clearRect(0,0,ca.width, ca.height);
// Reset the globalCompositeOperation
co.globalCompositeOperation = 'source-over';
} else {
co.fillStyle = color;
co.beginPath();
if (RG.ISIE8) {
co.fillRect(0,0,ca.width,ca.height);
} else {
co.fillRect(-10,-10,ca.width + 20,ca.height + 20);
}
co.fill();
}
//if (RG.ClearAnnotations) {
//RG.ClearAnnotations(ca.id);
//}
/**
* This removes any background image that may be present
*/
if (RG.Registry.Get('chart.background.image.' + ca.id)) {
var img = RG.Registry.Get('chart.background.image.' + ca.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 (RG.Registry.Get('chart.tooltip')) {
RG.HideTooltip(ca);
//RG.Redraw();
}
/**
* Set the cursor to default
*/
ca.style.cursor = 'default';
RG.FireCustomEvent(obj, 'onclear');
};
/**
* Draws the title of the graph
*
* @param object canvas The canvas object
* @param string text The title to write
* @param integer gutter The size of the gutter
* @param integer The center X point (optional - if not given it will be generated from the canvas width)
* @param integer Size of the text. If not given it will be 14
* @param object An optional object which has canvas and context properties to use instead of those on
* the obj argument (so as to enable caching)
*/
RG.drawTitle =
RG.DrawTitle = function (obj, text, gutterTop)
{
var ca = canvas = obj.canvas;
var co = context = obj.context;
var prop = obj.properties;
if (arguments[5]) {
var ca = canvas = arguments[5].canvas;
var co = context = arguments[5].context;
}
var gutterLeft = prop['chart.gutter.left'];
var gutterRight = prop['chart.gutter.right'];
var gutterTop = gutterTop;
var gutterBottom = prop['chart.gutter.bottom'];
var size = arguments[4] ? arguments[4] : 12;
var bold = prop['chart.title.bold'];
var centerx = (arguments[3] ? arguments[3] : ((ca.width - gutterLeft - gutterRight) / 2) + gutterLeft);
var keypos = prop['chart.key.position'];
var vpos = prop['chart.title.vpos'];
var hpos = prop['chart.title.hpos'];
var bgcolor = prop['chart.title.background'];
var x = prop['chart.title.x'];
var y = prop['chart.title.y'];
var halign = 'center';
var valign = 'center';
// Account for 3D effect by faking the key position
if (obj.type == 'bar' && prop['chart.variant'] == '3d') {
keypos = 'gutter';
}
co.beginPath();
co.fillStyle = prop['chart.text.color'] ? prop['chart.text.color'] : 'black';
/**
* Vertically center the text if the key is not present
*/
if (keypos && keypos != 'gutter') {
var valign = 'center';
} else if (!keypos) {
var valign = 'center';
} else {
var valign = 'bottom';
}
// if chart.title.vpos is a number, use that
if (typeof prop['chart.title.vpos'] === 'number') {
vpos = prop['chart.title.vpos'] * gutterTop;
if (prop['chart.xaxispos'] === 'top') {
vpos = prop['chart.title.vpos'] * gutterBottom + gutterTop + (ca.height - gutterTop - gutterBottom);
}
} else {
vpos = gutterTop - size - 5;
if (prop['chart.xaxispos'] === 'top') {
vpos = ca.height - gutterBottom + size + 5;
}
}
// if chart.title.hpos is a number, use that. It's multiplied with the (entire) canvas width
if (typeof hpos === 'number') {
centerx = hpos * ca.width;
}
/**
* Now the chart.title.x and chart.title.y settings override (is set) the above
*/
if (typeof x === 'number') centerx = x;
if (typeof y === 'number') vpos = y;
/**
* Horizontal alignment can now (Jan 2013) be specified
*/
if (typeof prop['chart.title.halign'] === 'string') {
halign = prop['chart.title.halign'];
}
/**
* Vertical alignment can now (Jan 2013) be specified
*/
if (typeof prop['chart.title.valign'] === 'string') {
valign = prop['chart.title.valign'];
}
// Set the colour
if (typeof prop['chart.title.color'] !== null) {
var oldColor = co.fillStyle
var newColor = prop['chart.title.color'];
co.fillStyle = newColor ? newColor : 'black';
}
/**
* Default font is Arial
*/
var font = prop['chart.text.font'];
/**
* Override the default font with chart.title.font
*/
if (typeof prop['chart.title.font'] === 'string') {
font = prop['chart.title.font'];
}
/**
* Draw the title
*/
RG.Text2(co, {'font':font,
'size':size,
'x':centerx,
'y':vpos,
'text':text,
'valign':valign,
'halign':halign,
'bounding':bgcolor != null,
'bounding.fill':bgcolor,
'bold':bold,
'tag':'title'
});
// Reset the fill colour
co.fillStyle = oldColor;
};
/**
* Gets the mouse X/Y coordinates relative to the canvas
*
* @param object e The event object. As such this method should be used in an event listener.
*/
RG.getMouseXY = function(e)
{
var el = e.target;
var ca = el;
var caStyle = ca.style;
var offsetX = 0;
var offsetY = 0;
var x;
var y;
var ISFIXED = (ca.style.position == 'fixed');
var borderLeft = parseInt(caStyle.borderLeftWidth) || 0;
var borderTop = parseInt(caStyle.borderTopWidth) || 0;
var paddingLeft = parseInt(caStyle.paddingLeft) || 0
var paddingTop = parseInt(caStyle.paddingTop) || 0
var additionalX = borderLeft + paddingLeft;
var additionalY = borderTop + paddingTop;
if (typeof e.offsetX === 'number' && typeof e.offsetY === 'number') {
if (ISFIXED) {
if (RG.ISOPERA) {
x = e.offsetX;
y = e.offsetY;
} else if (RG.ISWEBKIT) {
x = e.offsetX - paddingLeft - borderLeft;
y = e.offsetY - paddingTop - borderTop;
} else if (RG.ISIE) {
x = e.offsetX - paddingLeft;
y = e.offsetY - paddingTop;
} else {
x = e.offsetX;
y = e.offsetY;
}
} else {
if (!RG.ISIE && !RG.ISOPERA) {
x = e.offsetX - borderLeft - paddingLeft;
y = e.offsetY - borderTop - paddingTop;
} else if (RG.ISIE) {
x = e. offsetX - paddingLeft;
y = e.offsetY - paddingTop;
} else {
x = e.offsetX;
y = e.offsetY;
}
}
} else {
if (typeof el.offsetParent !== 'undefined') {
do {
offsetX += el.offsetLeft;
offsetY += el.offsetTop;
} while ((el = el.offsetParent));
}
x = e.pageX - offsetX - additionalX;
y = e.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
*/
RG.getCanvasXY = function (canvas)
{
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' && (RG.ISCHROME || RG.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 whther a canvas is fixed (CSS positioning) or not. If not it returns
* false. If it is then the element that is fixed is returned (it may be a parent of the canvas).
*
* @return Either false or the fixed positioned element
*/
RG.isFixed = function (canvas)
{
var obj = canvas;
var i = 0;
while (obj && obj.tagName.toLowerCase() != 'body' && i < 99) {
if (obj.style.position == 'fixed') {
return obj;
}
obj = obj.offsetParent;
}
return false;
};
/**
* Registers a graph object (used when the canvas is redrawn)
*
* @param object obj The object to be registered
*/
RG.register =
RG.Register = function (obj)
{
// Checking this property ensures the object is only registered once
if (!obj.Get('chart.noregister')) {
// As of 21st/1/2012 the object registry is now used
RGraph.ObjectRegistry.Add(obj);
obj.Set('chart.noregister', true);
}
};
/**
* Causes all registered objects to be redrawn
*
* @param string An optional color to use to clear the canvas
*/
RG.redraw =
RG.Redraw = function ()
{
var objectRegistry = RGraph.ObjectRegistry.objects.byCanvasID;
// Get all of the canvas tags on the page
var tags = document.getElementsByTagName('canvas');
for (var i=0,len=tags.length; i<len; ++i) {
if (tags[i].__object__ && tags[i].__object__.isRGraph) {
// Only clear the canvas if it's not Trace'ing - this applies to the Line/Scatter Trace effects
if (!tags[i].noclear) {
RGraph.clear(tags[i], arguments[0] ? arguments[0] : null);
}
}
}
// Go through the object registry and redraw *all* of the canvas'es that have been registered
for (var i=0,len=objectRegistry.length; i<len; ++i) {
if (objectRegistry[i]) {
var id = objectRegistry[i][0];
objectRegistry[i][1].Draw();
}
}
};
/**
* Causes all registered objects ON THE GIVEN CANVAS to be redrawn
*
* @param canvas object The canvas object to redraw
* @param bool Optional boolean which defaults to true and determines whether to clear the canvas
*/
RG.redrawCanvas =
RG.RedrawCanvas = function (ca)
{
var objects = RG.ObjectRegistry.getObjectsByCanvasID(ca.id);
/**
* First clear the canvas
*/
if (!arguments[1] || (typeof arguments[1] === 'boolean' && !arguments[1] == false) ) {
var color = arguments[2] || ca.__object__.get('clearto') || 'transparent';
RG.clear(ca, color);
}
/**
* Now redraw all the charts associated with that canvas
*/
for (var i=0,len=objects.length; i<len; ++i) {
if (objects[i]) {
if (objects[i] && objects[i].isRGraph) { // Is it an RGraph object ??
objects[i].Draw();
}
}
}
};
/**
* This function draws the background for the bar chart, line chart and scatter chart.
*
* @param object obj The graph object
*/
RG.Background.draw =
RG.background.draw =
RG.background.Draw = function (obj)
{
var func = function (obj, canvas, context)
{
var ca = canvas;
var co = context;
var prop = obj.properties;
var height = 0;
var gutterLeft = obj.gutterLeft;
var gutterRight = obj.gutterRight;
var gutterTop = obj.gutterTop;
var gutterBottom = obj.gutterBottom;
var variant = prop['chart.variant'];
co.fillStyle = prop['chart.text.color'];
// If it's a bar and 3D variant, translate
if (variant == '3d') {
co.save();
co.translate(10, -5);
}
// X axis title
if (typeof prop['chart.title.xaxis'] === 'string' && prop['chart.title.xaxis'].length) {
var size = prop['chart.text.size'] + 2;
var font = prop['chart.text.font'];
var bold = prop['chart.title.xaxis.bold'];
if (typeof(prop['chart.title.xaxis.size']) == 'number') {
size = prop['chart.title.xaxis.size'];
}
if (typeof(prop['chart.title.xaxis.font']) == 'string') {
font = prop['chart.title.xaxis.font'];
}
var hpos = ((ca.width - gutterLeft - gutterRight) / 2) + gutterLeft;
var vpos = ca.height - gutterBottom + 25;
if (typeof prop['chart.title.xaxis.pos'] === 'number') {
vpos = ca.height - (gutterBottom * prop['chart.title.xaxis.pos']);
}
// Specifically specified X/Y positions
if (typeof prop['chart.title.xaxis.x'] === 'number') {
hpos = prop['chart.title.xaxis.x'];
}
if (typeof prop['chart.title.xaxis.y'] === 'number') {
vpos = prop['chart.title.xaxis.y'];
}
RG.Text2(co, {'font':font,
'size':size,
'x':hpos,
'y':vpos,
'text':prop['chart.title.xaxis'],
'halign':'center',
'valign':'center',
'bold':bold,
'tag': 'title xaxis'
});
}
// Y axis title
if (typeof(prop['chart.title.yaxis']) == 'string' && prop['chart.title.yaxis'].length) {
var size = prop['chart.text.size'] + 2;
var font = prop['chart.text.font'];
var angle = 270;
var bold = prop['chart.title.yaxis.bold'];
var color = prop['chart.title.yaxis.color'];
if (typeof(prop['chart.title.yaxis.pos']) == 'number') {
var yaxis_title_pos = prop['chart.title.yaxis.pos'] * gutterLeft;
} else {
var yaxis_title_pos = ((gutterLeft - 25) / gutterLeft) * gutterLeft;
}
if (typeof prop['chart.title.yaxis.size'] === 'number') {
size = prop['chart.title.yaxis.size'];
}
if (typeof prop['chart.title.yaxis.font'] === 'string') {
font = prop['chart.title.yaxis.font'];
}
if ( prop['chart.title.yaxis.align'] == 'right'
|| prop['chart.title.yaxis.position'] == 'right'
|| (obj.type === 'hbar' && prop['chart.yaxispos'] === 'right' && typeof prop['chart.title.yaxis.align'] === 'undefined' && typeof prop['chart.title.yaxis.position'] === 'undefined')
) {
angle = 90;
yaxis_title_pos = prop['chart.title.yaxis.pos'] ? (ca.width - gutterRight) + (prop['chart.title.yaxis.pos'] * gutterRight) :
ca.width - gutterRight + prop['chart.text.size'] + 5;
} else {
yaxis_title_pos = yaxis_title_pos;
}
var y = ((ca.height - gutterTop - gutterBottom) / 2) + gutterTop;
// Specifically specified X/Y positions
if (typeof prop['chart.title.yaxis.x'] === 'number') {
yaxis_title_pos = prop['chart.title.yaxis.x'];
}
if (typeof prop['chart.title.yaxis.y'] === 'number') {
y = prop['chart.title.yaxis.y'];
}
co.fillStyle = color;
RG.text2(co, {'font':font,
'size':size,
'x':yaxis_title_pos,
'y':y,
'valign':'center',
'halign':'center',
'angle':angle,
'bold':bold,
'text':prop['chart.title.yaxis'],
'tag':'title yaxis'
});
}
/**
* If the background color is spec ified - draw that. It's a rectangle that fills the
* entire area within the gutters
*/
var bgcolor = prop['chart.background.color'];
if (bgcolor) {
co.fillStyle = bgcolor;
co.fillRect(gutterLeft + 0.5, gutterTop + 0.5, ca.width - gutterLeft - gutterRight, ca.height - gutterTop - gutterBottom);
}
/**
* Draw horizontal background bars
*/
var numbars = (prop['chart.ylabels.count'] || 5);
var barHeight = (ca.height - gutterBottom - gutterTop) / numbars;
co.beginPath();
co.fillStyle = prop['chart.background.barcolor1'];
co.strokeStyle = co.fillStyle;
height = (ca.height - gutterBottom);
for (var i=0; i<numbars; i+=2) {
co.rect(gutterLeft,
(i * barHeight) + gutterTop,
ca.width - gutterLeft - gutterRight,
barHeight
);
}
co.fill();
co.beginPath();
co.fillStyle = prop['chart.background.barcolor2'];
co.strokeStyle = co.fillStyle;
for (var i=1; i<numbars; i+=2) {
co.rect(gutterLeft,
(i * barHeight) + gutterTop,
ca.width - gutterLeft - gutterRight,
barHeight
);
}
co.fill();
// Draw the background grid
if (prop['chart.background.grid']) {
// If autofit is specified, use the .numhlines and .numvlines along with the width to work
// out the hsize and vsize
if (prop['chart.background.grid.autofit']) {
/**
* Align the grid to the tickmarks
*/
if (prop['chart.background.grid.autofit.align']) {
// Align the horizontal lines
obj.Set('chart.background.grid.autofit.numhlines', prop['chart.ylabels.count']);
// Align the vertical lines for the line
if (obj.type === 'line') {
if (prop['chart.labels'] && prop['chart.labels'].length) {
obj.Set('chart.background.grid.autofit.numvlines', prop['chart.labels'].length - 1);
} else {
obj.Set('chart.background.grid.autofit.numvlines', obj.data[0].length - 1);
}
// Align the vertical lines for the bar
} else if ( (obj.type === 'bar' || obj.type === 'scatter') && prop['chart.labels'] && prop['chart.labels'].length) {
obj.Set('chart.background.grid.autofit.numvlines', prop['chart.labels'].length);
}
}
var vsize = ((ca.width - gutterLeft - gutterRight)) / prop['chart.background.grid.autofit.numvlines'];
var hsize = (ca.height - gutterTop - gutterBottom) / prop['chart.background.grid.autofit.numhlines'];
obj.Set('chart.background.grid.vsize', vsize);
obj.Set('chart.background.grid.hsize', hsize);
}
co.beginPath();
co.lineWidth = prop['chart.background.grid.width'] ? prop['chart.background.grid.width'] : 1;
co.strokeStyle = prop['chart.background.grid.color'];
// Dashed background grid
if (prop['chart.background.grid.dashed'] && typeof co.setLineDash == 'function') {
co.setLineDash([3,2]);
}
// Dotted background grid
if (prop['chart.background.grid.dotted'] && typeof co.setLineDash == 'function') {
co.setLineDash([1,2]);
}
co.beginPath();
// Draw the horizontal lines
if (prop['chart.background.grid.hlines']) {
height = (ca.height - gutterBottom)
var hsize = prop['chart.background.grid.hsize'];
for (y=gutterTop; y<=height; y+=hsize) {
context.moveTo(gutterLeft, ma.round(y));
context.lineTo(ca.width - gutterRight, ma.round(y));
}
}
if (prop['chart.background.grid.vlines']) {
// Draw the vertical lines
var width = (ca.width - gutterRight)
var vsize = prop['chart.background.grid.vsize'];
for (x=gutterLeft; x<=width; x+=vsize) {
co.moveTo(ma.round(x), gutterTop);
co.lineTo(ma.round(x), ca.height - gutterBottom);
}
}
if (prop['chart.background.grid.border']) {
// Make sure a rectangle, the same colour as the grid goes around the graph
co.strokeStyle = prop['chart.background.grid.color'];
co.strokeRect(ma.round(gutterLeft), ma.round(gutterTop), ca.width - gutterLeft - gutterRight, ca.height - gutterTop - gutterBottom);
}
}
co.stroke();
// Necessary to ensure the gris drawn before continuing
co.beginPath();
co.closePath();
// If it's a bar and 3D variant, translate
if (variant == '3d') {
co.restore();
}
// Reset the line dash
if (typeof co.setLineDash == 'function') {
co.setLineDash([1,0]);
}
// Draw the title if one is set
if ( typeof(prop['chart.title']) == 'string') {
if (obj.type == 'gantt') {
gutterTop -= 10;
}
RG.DrawTitle(obj,
prop['chart.title'],
gutterTop,
null,
prop['chart.title.size'] ? prop['chart.title.size'] : prop['chart.text.size'] + 2,
{canvas: ca, context: co});
}
co.stroke();
}
// Now a cached draw in newer browsers
RG.ISOLD ? func(obj, obj.canvas, obj.context) : RG.cachedDraw(obj, obj.uid + '_background', func);
};
/**
* Formats a number with thousand seperators so it's easier to read
*
* @param integer obj The chart object
* @param integer num The number to format
* @param string The (optional) string to prepend to the string
* @param string The (optional) string to append to the string
* @return string The formatted number
*/
RG.numberFormat =
RG.number_format = function (obj, num)
{
var ca = obj.canvas;
var co = obj.context;
var prop = obj.properties;
var i;
var prepend = arguments[2] ? String(arguments[2]) : '';
var append = arguments[3] ? String(arguments[3]) : '';
var output = '';
var decimal = '';
var decimal_seperator = typeof prop['chart.scale.point'] == 'string' ? prop['chart.scale.point'] : '.';
var thousand_seperator = typeof prop['chart.scale.thousand'] == 'string' ? prop['chart.scale.thousand'] : ',';
RegExp.$1 = '';
var i,j;
if (typeof prop['chart.scale.formatter'] === 'function') {
return prop['chart.scale.formatter'](obj, num);
}
// Ignore the preformatted version of "1e-2"
if (String(num).indexOf('e') > 0) {
return String(prepend + String(num) + append);
}
// We need then number as a string
num = String(num);
// Take off the decimal part - we re-append it later
if (num.indexOf('.') > 0) {
var tmp = num;
num = num.replace(/\.(.*)/, ''); // The front part of the number
decimal = tmp.replace(/(.*)\.(.*)/, '$2'); // The decimal part of the number
}
// Thousand seperator
//var seperator = arguments[1] ? String(arguments[1]) : ',';
var seperator = thousand_seperator;
/**
* Work backwards adding the thousand seperators
*/
var foundPoint;
for (i=(num.length - 1),j=0; i>=0; j++,i--) {
var character = num.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('-' + prop['chart.scale.thousand']) == 0) {
output = '-' + output.substr(('-' + prop['chart.scale.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;
}
return prepend + output + append;
};
/**
* Draws horizontal coloured bars on something like the bar, line or scatter
*/
RG.drawBars =
RG.DrawBars = function (obj)
{
var prop = obj.properties;
var co = obj.context;
var ca = obj.canvas;
var hbars = prop['chart.background.hbars'];
if (hbars === null) {
return;
}
/**
* Draws a horizontal bar
*/
co.beginPath();
for (i=0,len=hbars.length; i<len; ++i) {
var start = hbars[i][0];
var length = hbars[i][1];
var color = hbars[i][2];
// Perform some bounds checking
if(RG.is_null(start))start = obj.scale2.max
if (start > obj.scale2.max) start = obj.scale2.max;
if (RG.is_null(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 (prop['chart.xaxispos'] == 'center' && start == obj.scale2.max && length < (obj.scale2.max * -2)) {
length = obj.scale2.max * -2;
}
/**
* Draw the bar
*/
var x = prop['chart.gutter.left'];
var y = obj.getYCoord(start);
var w = ca.width - prop['chart.gutter.left'] - prop['chart.gutter.right'];
var h = obj.getYCoord(start + length) - y;
// Accommodate Opera :-/
if (RG.ISOPERA != -1 && prop['chart.xaxispos'] == 'center' && h < 0) {
h *= -1;
y = y - h;
}
/**
* Account for X axis at the top
*/
if (prop['chart.xaxispos'] == 'top') {
y = ca.height - y;
h *= -1;
}
co.fillStyle = color;
co.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('chart.xaxispos') == '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('chart.xaxispos') == 'center') {
ystart /= 2;
//height /= 2;
}
ystart += obj.Get('chart.gutter.top')
var x = obj.Get('chart.gutter.left');
var y = ystart - height;
var w = obj.canvas.width - obj.Get('chart.gutter.left') - obj.Get('chart.gutter.right');
var h = height;
// Accommodate Opera :-/
if (navigator.userAgent.indexOf('Opera') != -1 && obj.Get('chart.xaxispos') == 'center' && h < 0) {
h *= -1;
y = y - h;
}