Last active
December 9, 2015 22:39
-
-
Save mlarocca/4339162 to your computer and use it in GitHub Desktop.
Dynamically populated charts - by Marcello La Rocca
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
if (!window.ChartUtils){ | |
/** | |
Module ChartUtils | |
Extends Object class with several useful methods to allow better encapsulation mechanisms.<br> | |
Exposes a few utility functions | |
@module ChartUtils | |
*/ | |
var ChartUtils = (function(){ | |
"use strict"; | |
//Add a init method to Object | |
if (!Object.hasOwnProperty("init")) { | |
/** | |
Creates an object inheriting from a given prototype and then, if required, | |
inits it with a list of properties tha can be passed as its second argument. | |
@method init | |
@for Object | |
@param {Object} proto The protetype to inherit from; | |
@param {Object} [properties] A dictionary of key-value properties to be used for the new object's initialization; | |
@return {Object} The newly created object. | |
@throws Error, if the wrong number of arguments is passed. | |
*/ | |
Object.init = function (proto, properties){ | |
if (arguments.length !== 2){ | |
throw new Error('Object.init implementation only accepts 2 parameters.'); | |
} | |
var key, new_obj = Object.create(proto); | |
if (properties){ | |
for (key in properties){ | |
if (properties.hasHownProperty(key)){ | |
Object.defineProperty(new_obj, key, { | |
value: properties[key], | |
writable: true, | |
enumerable: true, | |
configurable: false | |
} | |
); | |
} | |
} | |
} | |
return new_obj; | |
}; | |
} | |
if (Object.prototype.clear){ | |
/** | |
Deletes every property from an object | |
@method clear | |
@for Object | |
@chainable | |
@return {Object} The same object on whom this method is called. | |
*/ | |
Object.prototype.clear = function(){ | |
for (var prop in this){ | |
if (this.hasOwnProperty(prop)){ | |
delete this[prop]; | |
} | |
} | |
return this; | |
}; | |
} | |
/** addPublicMethod(methodName, method) | |
Shortcut for defyning a method which will be considered | |
public by createSafeProxy;<br> | |
Usage: obj.addPublicMethod("name", method)<br> | |
to add function method to obj as property obj["name"]. | |
@method addPublicMethod | |
@for Object | |
@chainable | |
@param {String} methodName The name of the new property to be added to this object<br> | |
<b>WARNING</b>: if Object[methodName] exists, then it will | |
be overwritten. | |
@param method {Function} The method body. | |
@return {Object} This object, to enable method chaining | |
@throws | |
<ul> | |
<li> Wrong number of arguments Exception, if either is missing or null;</li> | |
<li> Illegal Argument Exception, if methodName is not a String;</li> | |
<li> Illegal Argument Exception, if method is not a Function.</li> | |
</ul> | |
*/ | |
function addPublicMethod(methodName, method){ | |
if (!methodName || !method){ | |
throw "Wrong number of arguments Exception"; | |
} | |
if (!Object.isString(methodName)){ | |
throw "Illegal Argument Exception: methodName must be a string"; | |
} | |
if (!Object.isFunction(method)){ | |
throw "Illegal Argument Exception: method must be a function"; | |
} | |
Object.defineProperty(this, methodName, { | |
value: method, | |
writable: false, | |
enumerable: true, | |
configurable:false | |
}); | |
return this; //Chainable | |
} | |
if (!Object.prototype.addPublicMethod){ | |
Object.defineProperty(Object.prototype, "addPublicMethod", { | |
value: addPublicMethod, | |
writable: false, | |
enumerable: false, | |
configurable: false | |
}); | |
} | |
/** addProtectedMethod(methodName, method) | |
Shortcut for defyning a method which will be considered | |
protected by createSafeProxy;<br> | |
Usage: obj.addProtectedMethod("name", method)<br> | |
to add function method to obj as property obj["name"]. | |
@method addProtectedMethod | |
@for Object | |
@chainable | |
@param {String} methodName The name of the new property to be added to this object<br> | |
<b>WARNING</b>: if Object[methodName] exists, then it will | |
be overwritten. | |
@param method {Function} The method body. | |
@return {Object} This object, to enable method chaining | |
@throws | |
<ul> | |
<li> Wrong number of arguments Exception, if either is missing or null;</li> | |
<li> Illegal Argument Exception, if methodName is not a String;</li> | |
<li> Illegal Argument Exception, if method is not a Function.</li> | |
</ul> | |
*/ | |
function addProtectedMethod(methodName, method){ | |
if (!methodName || !method){ | |
throw "Wrong number of arguments Exception"; | |
} | |
if (!Object.isString(methodName)){ | |
throw "Illegal Argument Exception: methodName must be a string"; | |
} | |
if (!Object.isFunction(method)){ | |
throw "Illegal Argument Exception: method must be a function"; | |
} | |
Object.defineProperty(this, methodName, { | |
value: method, | |
writable: false, | |
enumerable: false, | |
configurable:false | |
}); | |
} | |
if (!Object.prototype.addProtectedMethod){ | |
Object.defineProperty(Object.prototype, "addProtectedMethod", { | |
value: addProtectedMethod, | |
writable: false, | |
enumerable: false, | |
configurable: false | |
}); | |
} | |
if (!Object.prototype.createSafeProxy){ | |
/** createSafeProxy() | |
Creates and returns a safe proxy for the object passed, | |
that will wrap around it and expose only those methods | |
marked as public (i.e. those that are declared as enumerable). | |
@method createSafeProxy | |
@for Object | |
@chainable | |
@param {Boolean} [canDestroy=false] States if the proxy consumer has the authority | |
to call destroy on the original object;<br> | |
We assume the convention that object's uses destroy method | |
as their destructor. | |
@return {Object} A proxy wrapping this object. | |
@throws Any exception the original object pseudo-constructor might throw. | |
*/ | |
Object.prototype.createSafeProxy = function(canDestroy){ | |
var property; | |
var proxy = Object.create(null); | |
var obj = this; //We must retain the "this" pointer to the current object to use it inside different contexts | |
for (property in obj){ | |
//DO NOT check hasOwnProperty: the proxy must work for obj's prototype methods as well | |
//ONLY enumerable methods will be added to proxy's interface | |
if (Object.isFunction(obj[property])){ | |
//If it's a method not marked as protected, it is added to the proxy interface; | |
proxy[property] = ( function(p){ | |
return function(){ | |
var result; | |
if (obj){ | |
result = obj[p].apply(obj, Array.prototype.slice.apply(arguments, [0])); | |
//Special care is needed to support method chaining | |
if (result === obj){ | |
//obj.property returns obj itself, but we must return this proxy instead; | |
return proxy; | |
}else{ | |
return result; | |
} | |
}else{ | |
throw "Null reference: the object has been already destroyed"; | |
} | |
}; | |
})(property); | |
} | |
} | |
//Adds a wrapping destroy method to allow withdrawal of the privileges given up introducing | |
//the consumer to obj; | |
proxy.destroy = function(){ | |
try{ | |
if (canDestroy){ | |
obj.destroy(); //Destroys the original object only if authorized to | |
} | |
}finally{ | |
obj = null; | |
} | |
}; | |
return proxy; | |
}; | |
} | |
/** superMethod(methodName) | |
Checks if the super object of this object has a method (i.e. a property which is a function) whose name is methodName, | |
and then calls it. Otherwise checks recursively its super object, i.e. its prototype. | |
@method superMethod | |
@for Object | |
@param {String} methodName The name of the method to look up for in this object's super objects. | |
@param [args]* The arguments to be passed to the super method, if any is needed; | |
@return The result of the call to the method named methodName of this object's super object. | |
@throws | |
<ul> | |
<li>Wrong number of arguments Exception, if methodName is missing or null;</li> | |
<li>Illegal Argument Exception, if methodName is not a String;</li> | |
<li>Method not found Exception, if there isn't such a method in the whole inheritance chain.</li> | |
</ul> | |
*/ | |
function superMethod(methodName /*, args*/){ | |
if (!methodName){ | |
throw "Wrong number of arguments Exception"; | |
} | |
if (!Object.isString(methodName)){ | |
throw "Illegal Argument Exception: methodName must be a string"; | |
} | |
//Looks up for this object's prototype | |
var proto = this.prototype && this.prototype[methodName] ? this.prototype : this.__proto__; | |
if (proto && proto[methodName] && Object.isFunction(proto[methodName])){ | |
return proto[methodName].apply(proto, Array.prototype.slice.apply(arguments, [1])); | |
}else{ | |
throw "Super object has no method " + methodName; | |
} | |
} | |
if (!Object.prototype.superMethod){ | |
Object.defineProperty(Object.prototype, "superMethod", { | |
value: superMethod, | |
writable: false, | |
enumerable: false, | |
configurable: false | |
}); | |
} | |
/** setProperty(property, value) | |
Assign the value "value" to the property "property" of the current object.<br> | |
"property" MUST be an existing property of current object or of its ancestors: | |
if this[property] is undefined, it recursively checks along its inheritance chain. | |
@method setProperty | |
@for Object | |
@chainable | |
@param {String} property The name of the property to look up for in this object and its super object. | |
@param value The value to be assigned to the property. | |
@return This object, to allow for method chaining | |
@throws | |
<ul> | |
<li>Wrong number of arguments Exception, if property is missing or null; (undefined is accepted for value)</li> | |
<li>Illegal Argument Exception, if property is not a String;</li> | |
<li>Method not found Exception, if neither this object or its super object has such a property.</li> | |
<li>TypeError, if property exists but it isn't writable.</li> | |
</ul> | |
*/ | |
function setProperty(property, value){ | |
if (!property){ | |
throw "Wrong number of arguments Exception"; | |
} | |
if (!Object.isString(property)){ | |
throw "Illegal Argument Exception: property must be a string"; | |
} | |
if (this.hasOwnProperty(property)){ | |
this[property] = value; | |
return this; | |
} | |
//Looks up for this object's prototype | |
var proto = this.prototype && this.prototype[property] ? this.prototype : this.__proto__; | |
if (proto && !Object.isUndefined(proto[property])){ | |
proto.setProperty(property, value); | |
return this; | |
}else{ | |
throw "Super object has no property " + property; | |
} | |
} | |
if (!Object.prototype.setProperty){ | |
Object.defineProperty(Object.prototype, "setProperty", { | |
value: setProperty, | |
writable: false, | |
enumerable: false, | |
configurable: false | |
}); | |
} | |
if (Array.prototype.clear){ | |
/** | |
Clears an Array, removing all its elements; | |
@method clear | |
@for Array | |
@param {Boolean} [deepClear=false] If it is required for all the array elements to be cleared, | |
this paramenter should be true; can be omitted otherwise; <br> | |
If deep clearance is required, thie method will try to | |
call the clear method for each and every element of this array | |
(Object class has already been extended with a clear method). | |
@return {Array} This array. | |
*/ | |
Array.prototype.clear = function(deepClear){ | |
if (deepClear){ | |
//If a deep clear is required, try to execute clear method of every element | |
for (var i=0; i<this.length; i++){ | |
if (Object.isFunction(this[i].clear)){ | |
this[i].clear(); | |
} | |
} | |
} | |
//Clear this array (the efficient way) | |
this.length = 0; | |
return this; | |
}; | |
} | |
if (!Array.prototype.map){ | |
/** | |
Maps a function on every element of the array, creting a new array populated | |
with the results of these calls. | |
@method map | |
@for Array | |
@param {Function} f The function to map on the array; | |
@param {Object} [contest=window] The new this pointer for the function to map, if needed; | |
@return {Array} The array of the results of mapping f over the elements this array. | |
@throws TypeError if f isn't a function. | |
*/ | |
Array.prototype.map = function(f /*,contest*/){ | |
if (!Function.isFunction(f)){ | |
throw new TypeError(); | |
}else{ | |
var len = this.length; | |
var res = new Array(len); | |
var contest = arguments[1]; | |
for (var i = 0; i < len; i++){ | |
if (i in this){ | |
res[i] = f.call(contest, this[i], i, this); | |
} | |
} | |
return res; | |
} | |
}; | |
} | |
if (!Array.prototype.sum){ | |
/** | |
Sums the elements of an array. | |
@method sum | |
@for Array | |
@return {Number|String} The sum of the elements in the array;<br> | |
<br> | |
<b>WARNING</b>: | |
<ul> | |
<li>If all the elements in the array are numbers, then returs their arithmetic sum</li> | |
<li>If any element of the array isn't a number, returns a string obtained by concatenating | |
the partial arithmetic sum until that element, and the concatenation | |
of the string conversion of every other element in the array.</li> | |
</ul> | |
*/ | |
Array.prototype.sum = function(){ | |
var len = this.length; | |
var res = 0; | |
for (var i = 0; i < len; i++){ | |
res += this[i]; | |
} | |
return res; | |
}; | |
} | |
if (!Array.prototype.shallowCopy){ | |
/** Array.shallowCopy([n]) | |
Creates a new array (shallow) copying the elements of the current one | |
@method shallowCopy | |
@for Array | |
@param [n] If defined, the max number of elements to copy from the current array | |
@return {Array} A new array, with a shallow copy of all the elements in the original one. | |
*/ | |
Array.prototype.shallowCopy = function(n){ | |
var len = this.length; | |
if (Object.isUndefined(n)){ | |
n = len; | |
}else{ | |
n = parseInt(n, 10); | |
if (isNaN(n) || n <= 0){ | |
throw "Invalid argument: n"; | |
}else{ | |
n = Math.min(n, len); | |
} | |
} | |
var res = new Array(n); | |
for (var i = 0; i < n; i++){ | |
res[i] = this[i]; | |
} | |
return res; | |
}; | |
} | |
if (!Array.prototype.max){ | |
/** | |
Return the maximum element of an array. | |
@method max | |
@for Array | |
@param [extract_element_key] A function that, given any element of the array, will produce | |
a numeric value used for ranking the element itself (its sorting key) | |
@return {Number|NaN} The maximum value in the array, if all elements (or their keys) | |
are Number;<br> | |
NaN otherwise | |
*/ | |
Array.prototype.max = function(extract_element_key){ | |
if (Function.isFunction(extract_element_key)){ | |
return Math.max.apply(Math, this.map(extract_element_key)); | |
}else{ | |
return Math.max.apply(Math, this); | |
} | |
}; | |
} | |
if (!Array.prototype.min){ | |
/** | |
Return the minimum element of an array. | |
@method min | |
@for Array | |
@param [extract_element_key] A function that, given any element of the array, will produce | |
a numeric value used for ranking the element itself (its sorting key) | |
@return {Number|NaN} The minimum value in the array, if all elements (or their keys) | |
are Number;<br> | |
NaN otherwise | |
*/ | |
Array.prototype.min = function(extract_element_key){ | |
if (Function.isFunction(extract_element_key)){ | |
return Math.min.apply(Math, this.map(extract_element_key)); | |
}else{ | |
return Math.min.apply(Math, this); | |
} | |
}; | |
} | |
if (!Object.prototype.isArray){ | |
/** | |
Checks if its argument is an array. | |
@method isArray | |
@for Object | |
@param {Object} obj The argument to be checked. | |
@return {Boolean} true <=> the object is an Array. | |
*/ | |
Object.prototype.isArray = function(obj) { | |
return obj && (obj.constructor === Array); | |
}; | |
} | |
if (!Object.prototype.isString){ | |
/** | |
Checks if its argument is a string. | |
@method isString | |
@for Object | |
@param {Object} obj The argument to be checked. | |
@return {Boolean} true <=> the object is a String. | |
*/ | |
Object.prototype.isString = function(arg) { | |
return typeof(arg)==='string'; | |
}; | |
} | |
if (!Object.prototype.isFunction){ | |
/** | |
Checks if its argument is a Function. | |
@method isFunction | |
@for Object | |
@param {Object} arg The argument to be checked. | |
@return {Boolean} true <=> the object is a Function. | |
*/ | |
Object.prototype.isFunction = function(arg){ | |
return typeof(arg) === 'function'; | |
}; | |
} | |
if (!Object.prototype.isNumber){ | |
/** | |
Checks if its argument is a Number. | |
@method isNumber | |
@for Object | |
@param {Object} obj The argument to be checked. | |
@return {Boolean} true <=> the object is a Number. | |
*/ | |
Object.prototype.isNumber = function(n){ | |
return !isNaN(parseFloat(n)) && isFinite(n); | |
}; | |
} | |
if (!Object.prototype.isUndefined){ | |
/** | |
Checks if its argument is undefined. | |
@method isUndefined | |
@for Object | |
@param {Object} arg The argument to be checked. | |
@return {Boolean} true <=> the argument is undefined. | |
*/ | |
Object.prototype.isUndefined = function(arg){ | |
return typeof(arg) === "undefined"; | |
}; | |
} | |
if (!Object.prototype.shallowCopy){ | |
/** Array.shallowCopy() | |
Creates a new object (shallow)copying the elements of the current one. | |
@method shallowCopy | |
@for Object | |
@return {Object} A new object, with a shallow copy of all the properties in the original one. | |
*/ | |
Object.prototype.shallowCopy = function(){ | |
var res = {}; | |
for (var key in this){ | |
if (this.hasOwnProperty(key)){ | |
res[key] = this[key]; | |
} | |
} | |
return res; | |
}; | |
} | |
var TIME_REGEXP = /^\d\d?:\d\d?$/; | |
/** formatTimeString(HH, MM) | |
Format a hours, minutes couple into a proper time string<br> | |
<br> | |
<b>INVARIANT</b>: HH and MM must be valid, positive integers | |
(since it's a private method, defensive programming is avoided<br> | |
If the overcome their range, proper formatting is enforced: | |
F.i. HH=23, MM=60 -> "00:00" | |
@method formatTimeString | |
@for ChartUtils | |
@private | |
@param {Number} HH An int value (between 0 and 23), representing the hours | |
@param {Number} MM An int value (between 0 and 59), representing the minutes | |
@return {String} The properly formatted time string. | |
*/ | |
function formatTimeString(HH, MM){ | |
HH = (HH + Math.floor(MM/60)) % 24; | |
MM = MM % 60; | |
return (HH < 10 ? "0" + HH : HH) + ":" + (MM < 10 ? "0" + MM : MM); | |
} | |
/** | |
Abbreviations suffixes for large numbers; | |
@property SUFFIXES | |
@for ChartUtils | |
@type {Array} | |
@final | |
@private | |
*/ | |
var SUFFIXES = ["", "K", "M", "G", "T", "P", "E"]; | |
var utils = { | |
/** abbreviateNumber(val) | |
Takes a value and returns it's abbreviated text representation.<br> | |
<ul> | |
<li>If abs(val) > 1, the following standard abbreviations will be used: | |
<ul> | |
<li><b>K</b> thousands</li> | |
<li><b>M</b> million</li> | |
<li><b>G</b> billion</li> | |
<li><b>T</b> trillion</li> | |
<li><b>P</b> quadrillion</li> | |
<li><b>E</b> quintillion</li> | |
</ul> | |
One decimal place is always kept.<br> | |
F.i.: | |
<ul> | |
<li>123 -> "123"</li> | |
<li>1234 -> "1.2K"</li> | |
<li>12345 -> "12.3K"</li> | |
<li>123456789 -> "123.4M"</li> | |
</ul> | |
</li> | |
<li> If abs(val) | |
</li> | |
</ul> | |
<br> | |
<b>WARNING</b>: since shallow copy is used, only works for | |
primitive (immutable) values | |
@method abbreviateNumber | |
@for ChartUtils | |
@param {String|Number} value The value to assign to each element of the newly created array. | |
If value is a function, it is called n times, with no parameters | |
@param {Number} n The size of the final array;<br> | |
Must be a positive integer. | |
@return {Array} The newly created array. | |
@throws {Invalid Argument Exception} if n isn't passed, it's not a number, or it's not positive. | |
*/ | |
abbreviateNumber: function (val){ | |
var index; | |
if (val === 0){ | |
return "0"; | |
} | |
if (Math.abs(val) < 1){ | |
//Very small number | |
index = 0; //WARNING: Reusing the variable index as the exponent of scientific notation | |
while (Math.abs(val) < 1){ | |
index += 1; | |
val *= 10; | |
} | |
return (Math.round(val * 100) / 100.0) + "e-" + index; | |
} else{ | |
index = 0; //WARNING: Reusing the variable index as the index of the suffixes array | |
while (index < SUFFIXES.length - 1){ | |
if (Math.abs(val) < 1000){ | |
if (index === 0){ | |
val = Math.round(val * 100.0) / 100.0; //Keeps only two decimal digits for numbers smaller than 100 | |
} | |
return val + SUFFIXES[index]; | |
}else{ | |
index += 1; | |
val = Math.round(val / 100.0) / 10.0; //Keep one decimal digits when dividing by 1K | |
} | |
} | |
//Here, index == SUFFIXES.length - 1 | |
if (Math.abs(val) < 1000){ | |
return val + SUFFIXES[index]; | |
}else{ | |
//If it reaches here, the number is too long for the suffixes list: we abbreviate as much as we can anyway | |
return val + SUFFIXES[SUFFIXES.length - 1]; | |
} | |
} | |
}, | |
/** fillArray(value, n) | |
Takes a value and a positive integer n and returns an Array of n copies of that value.<br> | |
<br> | |
<b>WARNING</b>: since shallow copy is used, only works for | |
primitive (immutable) values | |
@method fillArray | |
@for ChartUtils | |
@param {String|Number} value The value to assign to each element of the newly created array. | |
If value is a function, it is called n times, with no parameters | |
@param {Number} n The size of the final array;<br> | |
Must be a positive integer. | |
@return {Array} The newly created array. | |
@throws {Invalid Argument Exception} if n isn't passed, it's not a number, or it's not positive. | |
*/ | |
fillArray: function(value, n){ | |
n = parseInt(n, 10); | |
if (isNaN(n) || n <= 0){ | |
throw "Invalid Argument: n"; | |
} | |
var i, res = new Array(n); | |
if (Function.isFunction(value)){ | |
for (i = 0; i < n; i++){ | |
res[i] = value(); | |
} | |
}else{ | |
for (i = 0; i < n; i++){ | |
res[i] = value; | |
} | |
} | |
return res; | |
}, | |
/** validateTimeString(timeString) | |
Takes a string as input and checks if it is a valid | |
time representation, according to the format HH:MM | |
@method validateTimeString | |
@for ChartUtils | |
@param {String} timeString The string to be evaluated | |
@return {Array|null} <ul> | |
<li>An array with two integers, the values for hours and minutes | |
<=> The input string validates successfully</li> | |
<li>null <-> Otherwise</li> | |
</ul> | |
*/ | |
validateTimeString: function(timeString){ | |
if (!TIME_REGEXP.test(timeString)){ | |
return null; | |
}//else, the formatting is fine, but we MUST check the values (i.e.: HH < 24, MM < 60 | |
var v = timeString.split(":").map(function(s){return parseInt(s, 10);}); | |
if (v[0] >= 24 || v[1] >= 60){ | |
return null; | |
}else{ | |
return v; | |
} | |
}, | |
/** addTimeStrings(timeString_1, timeString_2) | |
Takes two strings as input and checks if they are valid | |
time representations, according to the format HH:MM<br> | |
If it is so, sums them and returns a new string representing | |
the resulting time; | |
@method addTimeStrings | |
@for ChartUtils | |
@param {String} timeString_1 The first time string to be added | |
@param {String} timeString_2 The second time string to be added | |
@return {String} A string representation of the sum of the two timeStamps, in the format HH:MM (modulo 24 hours) | |
<=> Both strings validates successfully | |
@throws {Invalid Argument Exception} if either input fails to validate. | |
*/ | |
addTimeStrings: function(timeString_1, timeString_2){ | |
var t1 = this.validateTimeString(timeString_1); | |
if (!t1){ | |
throw "Invalid Argument: timeString_1"; | |
} | |
var t2 = this.validateTimeString(timeString_2); | |
if (!t2){ | |
throw "Invalid Argument: timeString_2"; | |
} | |
//INVARIANT: either validateTimeString returns null, or it returns an array with integers; | |
t1[1] += t2[1]; | |
t1[0] += t2[0]; | |
return formatTimeString(t1[0], t1[1]); | |
}, | |
/** addIntToTimeString(timeString, minutes) | |
Takes a string and an int value and checks if it is a valid | |
time representation, according to the format HH:MM | |
@method addIntToTimeString | |
@for ChartUtils | |
@param {String} timeString The time string taken as initial time | |
@param {Number} minutes How many minutes needs to be added to the time string; | |
@return {String} A string representation of the sum of the two time values, in the format HH:MM (modulo 24 hours) | |
<=> Both inputs validates successfully | |
@throws {Invalid Argument Exception} if either input fails to validate | |
*/ | |
addIntToTimeString: function(timeString, minutes){ | |
var t = this.validateTimeString(timeString); | |
if (!t){ | |
throw "Invalid Argument: timeString"; | |
} | |
if (minutes < 0){ | |
throw "Invalid Argument: minutes"; | |
} | |
//INVARIANT: either validateTimeString returns null, or it returns an array with integers; | |
t[1] += minutes; | |
return formatTimeString(t[0], t[1]); | |
} | |
}; | |
Object.freeze(utils); | |
return utils; | |
})(); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//require d3.v2.js | |
//require chart_utils.js | |
if (!window.DynamicChart){ | |
/** | |
Module DynamicChart | |
@version 0.2 | |
This module requires: | |
<ol> | |
<li>{{#crossLinkModule "chart_utils.js"}}{{/crossLinkModule}}</li> | |
<li>{{#crossLink "http://d3js.org/d3.v2.js"}}{{/crossLink}}</li> | |
</ol> | |
<br> | |
Exposes methods for creating different types of dynamic charts: | |
<ul> | |
<li>BasicBarChart</li> | |
<li>FixedWidthBarChart</li> | |
<li>SlidingBarChart</li> | |
<li>TimeWheelChart</li> | |
</ul> | |
@module DynamicChart | |
*/ | |
var DynamicChart = (function (){ | |
"use strict"; | |
// ----- CONSTANTS -------- | |
/** | |
Maximum dimension of the input space, i.e. max number of subvalues for each single point | |
@property MAX_SPACE_DIMENSION | |
@for DynamicChart | |
@type {Number} | |
@final | |
@private | |
*/ | |
var MAX_SPACE_DIMENSION = 10; | |
/** | |
Default fill colors bor graphic elements; | |
@property FILL_COLORS | |
@for DynamicChart | |
@type {Array} | |
@final | |
@private | |
*/ | |
var FILL_COLORS = ["blue", "red", "green", "orange", "purple", "cyan", "magenta", "yellow", "limegreen", "brown"]; | |
/** | |
Default size for chart's labels (in points); | |
@property DEFAULT_LABEL_SIZE | |
@for DynamicChart | |
@type {Number} | |
@final | |
@private | |
*/ | |
var DEFAULT_LABEL_SIZE = 12; | |
/** | |
Default size for chart's title text (in points); | |
@property DEFAULT_TITLE_SIZE | |
@for DynamicChart | |
@type {Number} | |
@final | |
@private | |
*/ | |
var DEFAULT_TITLE_SIZE = 22; | |
/** | |
Default axes margin (in pixels); | |
@property DEFAULT_AXE_MARGIN | |
@for DynamicChart | |
@type {Number} | |
@final | |
@private | |
*/ | |
var DEFAULT_AXE_MARGIN = 25; | |
/** | |
Default background color for the main chart area. | |
@property DEFAULT_INNER_BACKGROUND | |
@for DynamicChart | |
@type {String|Object} | |
@default = "white" | |
@final | |
@private | |
*/ | |
var DEFAULT_INNER_BACKGROUND = "white"; | |
/** | |
Default background color for the outer chart area. | |
@property DEFAULT_OUTER_BACKGROUND | |
@for DynamicChart | |
@type {String|Object} | |
@default = "white" | |
@final | |
@private | |
*/ | |
var DEFAULT_OUTER_BACKGROUND = "white"; | |
/** | |
Default border for the main chart area. | |
@property DEFAULT_INNER_BORDER | |
@for DynamicChart | |
@type {Object} | |
@default = none | |
@final | |
@private | |
*/ | |
var DEFAULT_INNER_BORDER = {fill: "none", width:0, dash: null}; | |
/** | |
Default border for the outer chart area. | |
@property DEFAULT_OUTER_BORDER | |
@for DynamicChart | |
@type {Object} | |
@default = none | |
@final | |
@private | |
*/ | |
var DEFAULT_OUTER_BORDER = {fill: "none", width:0, dash: null}; | |
/** | |
Default size of axes' labels lext | |
@property DEFAULT_AXES_LABEL_SIZE | |
@for DynamicChart | |
@type {Number} | |
@default = 14 | |
@final | |
@private | |
*/ | |
var DEFAULT_AXES_LABEL_SIZE = 12; | |
/** | |
Length of notch lines, in pixels; | |
@property NOTCH_LINE_LENGTH | |
@for DynamicChart | |
@type {Number} | |
@default = 5 | |
@final | |
@private | |
*/ | |
var NOTCH_LINE_LENGTH = 5; | |
/** | |
Default line width for axes and notches; | |
@property DEFAULT_AXES_LINE_WIDTH | |
@for DynamicChart | |
@type {Number} | |
@default = 1 | |
@final | |
@private | |
*/ | |
var DEFAULT_AXES_LINE_WIDTH = 1; | |
/** | |
Default line width for axes and notches; | |
@property DEFAULT_AXES_COLOR | |
@for DynamicChart | |
@type {Number} | |
@default = "black" | |
@final | |
@private | |
*/ | |
var DEFAULT_AXES_COLOR = "black"; | |
/** | |
Default width for legend's item boxes (in pixel); | |
@property LEGEND_ITEM_WIDTH | |
@for DynamicChart | |
@type {Number} | |
@final | |
@private | |
*/ | |
var LEGEND_ITEM_WIDTH = 25, | |
/** | |
Default height for legend's item boxes (in pixel); | |
@property LEGEND_ITEM_HEIGHT | |
@for DynamicChart | |
@type {Number} | |
@final | |
@private | |
*/ | |
LEGEND_ITEM_HEIGHT = 15, | |
/** | |
Default margin for legends (in pixel); | |
@property LEGEND_MARGIN | |
@for DynamicChart | |
@type {Number} | |
@final | |
@private | |
*/ | |
LEGEND_MARGIN = 5, | |
/** | |
Default left margin for legend's items (in pixel); | |
@property LEGEND_ITEM_LEFT | |
@for DynamicChart | |
@type {Number} | |
@final | |
@private | |
*/ | |
LEGEND_ITEM_LEFT = LEGEND_MARGIN, | |
/** | |
Default font size for labels attached to legend's items (in pixel); | |
@property LEGEND_ITEM_FONT_SIZE | |
@for DynamicChart | |
@type {Number} | |
@final | |
@private | |
*/ | |
LEGEND_ITEM_FONT_SIZE = 11; | |
// ----- /CONSTANTS -------- | |
// ----- LEGEND -------- | |
var legendPrototype = { | |
/** setPosition(left, top) | |
Sets the position of the legend in the page. Position is assumed to be absolute. | |
@method setPosition | |
@for Legend | |
@chainable | |
@param {Number} left [Mandatory] | |
The horizontal position of the legend bounding box; | |
Only Integers and values that can be converted to integers are accepted. | |
@param {Number} top [Mandatory] | |
The vertical position of the legend bounding box; | |
Only Integers and values that can be converted to integers are accepted. | |
@return {Object} This legend object, to allow for method chaining. | |
@throws Never: if either argument is not valid, simply ignores the action. | |
*/ | |
setPosition: function(left, top){ | |
left = parseInt(left, 10); | |
top = parseInt(top, 10); | |
if (this.__divElement__ && !isNaN(left) && !isNaN(top)){ | |
this.__divElement__.attr("style", "position:absolute; left:" + left + "px; top:"+top +"px;" ); | |
} | |
return this; //Method chaining support | |
}, | |
/** setWidth(width) | |
Sets the width of the legend bounding box. | |
@method setWidth | |
@for Legend | |
@chainable | |
@param {Number} width [Mandatory] | |
The new width of the legend; | |
Only positive integers and values that can be converted | |
to positive Integers are accepted. | |
@return {Object} This legend object, to allow for method chaining. | |
@throws {Illegal Argument Exception} if the argument is not valid (see above). | |
*/ | |
setWidth: function(width){ | |
width = parseInt(width, 10); | |
if (this.__svgElement__ && !isNaN(width) && width >= 0){ | |
this.__svgElement__.attr("width", width); | |
this.__divElement__.attr("width", width); | |
}else{ | |
throw "Illegal Argument: width"; | |
} | |
return this; //Method chaining support | |
}, | |
/** setHeight(height) | |
Sets the height of the legend bounding box. | |
@method setHeight | |
@for Legend | |
@chainable | |
@param {Number} height [Mandatory] | |
The new height of the legend; | |
Only positive Integers and values that can be converted | |
to positive integers are accepted. | |
@return {Object} This legend object, to allow for method chaining. | |
@throws <ul> | |
<li> Illegal Argument Exception, if the argument is not valid (see above). </li> | |
</ul> | |
*/ | |
setHeight: function(height){ | |
height = parseInt(height, 10); | |
if (this.__svgElement__ && !isNaN(height) && height >= 0){ | |
this.__svgElement__.attr("height", height); | |
}else{ | |
throw "Illegal Argument: height"; | |
} | |
return this; //Method chaining support | |
}, | |
/** addItem(label, color) | |
Adds an item to the legend and then redraws it; | |
@method addItem | |
@for Legend | |
@chainable | |
@param {String} labelText [Mandatory] | |
The text of the label for this new item; | |
@param {String} labelColor [Mandatory] | |
The color to be used to draw new item's label; | |
@param {String} fillColor [Mandatory] | |
The color associated with this new item; | |
@return {Object} This legend object, to allow for method chaining; | |
@throws <ul> | |
<li> Wrong Number of arguments Exception, if either argument is missing. </li> | |
</ul> | |
*/ | |
addItem: function(labelText, labelColor, fillColor){ | |
if (Object.isUndefined(labelText) || Object.isUndefined(labelColor) || Object.isUndefined(fillColor)){ | |
throw "Wrong number of arguments: label and color are both mandatory"; | |
} | |
this.__items__.push({labelText: labelText, labelColor: labelColor, fillColor: fillColor}); | |
var n = this.__items__.length - 1; | |
this.__svgElement__.append("rect") | |
.attr("stroke", "black") | |
.attr("fill", fillColor) | |
.attr("width", LEGEND_ITEM_WIDTH) | |
.attr("height", LEGEND_ITEM_HEIGHT) | |
.attr("x", LEGEND_ITEM_LEFT) | |
.attr("y", LEGEND_MARGIN + n * (LEGEND_ITEM_HEIGHT + LEGEND_MARGIN)); | |
this.__svgElement__.append("text") | |
.text(labelText) | |
.attr("fill", labelColor) | |
.attr("x", LEGEND_ITEM_LEFT + LEGEND_ITEM_WIDTH + LEGEND_MARGIN) //Aligns to the bottom of the rect | |
.attr("y", n * (LEGEND_ITEM_HEIGHT + LEGEND_MARGIN) + LEGEND_ITEM_HEIGHT) | |
.attr("font-family", "sans-serif") | |
.attr("font-size", LEGEND_ITEM_FONT_SIZE) | |
.attr("text-anchor", "left"); | |
}, | |
/** removeItem(index) | |
@method removeItem | |
@for Legend | |
@chainable | |
@param {Number} index [Mandatory] | |
The index of the item to update; | |
Only positive Integers and values that can be converted | |
to positive integers are accepted. | |
@return {Object} This legend object, to allow for method chaining; | |
@throws <ul> | |
<li>Illegal Argument Exception, if index is not in its valid range.</li> | |
</ul> | |
*/ | |
removeItem: function(index){ | |
index = parseInt(index, 10); | |
if (isNaN(index) || index < 0 || index >= this.__items__.length){ | |
throw "Illegal Argument: index"; | |
} | |
this.__items__.splice(index,1); | |
this.__svgElement__.selectAll("rect").data(this.__items__).exit().remove(); | |
this.__svgElement__.selectAll("text").data(this.__items__).exit().remove(); | |
this.__redrawLegend__(); | |
return this; //Method chaining support | |
}, | |
/** updateItem(index [, newLabelText, newLabelColor, newFillColor]) | |
Updates the attributes of an item of the legend and then redraws it; | |
@method updateItem | |
@for Legend | |
@chainable | |
@param {Number} index [Mandatory] | |
The index of the item to update; | |
@param {String} [labelText] [Optional] | |
The new text for the label of the index-th item; | |
If omitted or undefined won't be changed; | |
@param {String} [labelColor] [Optional] | |
The new color to be used to draw the index-th item's label; | |
If omitted or undefined won't be changed; | |
@param {String} [fillColor] [Optional] | |
The new color associated with the index-th item; | |
If omitted or undefined won't be changed; | |
@return {Object} This legend object, to allow for method chaining; | |
@throws | |
- Illegal Argument Exception, if index is not in its valid range. | |
*/ | |
updateItem: function(index, newLabelText, newLabelColor, newFillColor){ | |
index = parseInt(index, 10); | |
if (isNaN(index) || index < 0 || index >= this.__items__.length){ | |
throw "Illegal Argument: index"; | |
} | |
var oldItem = this.__items__[index]; | |
if (!Object.isUndefined(newLabelText)){ | |
oldItem.labelText = newLabelText; | |
} | |
if (!Object.isUndefined(newLabelColor)){ | |
oldItem.labelColor = newLabelColor; | |
} | |
if (!Object.isUndefined(newFillColor)){ | |
oldItem.fillColor = newFillColor; | |
} | |
this.__redrawLegend__(); | |
return this; //Method chaining support | |
}, | |
/** destroy() | |
Object's destructor: helps garbage collector freeing memory, and removes legend's DOM elements. | |
Object's destructor: helps garbage collector freeing memory, and removes chart DOM elements.<br> | |
<br> | |
<b>WARNING</b>: calling destroy on an object will force any further reference | |
to its attributes / methods to throw exceptions.<br> | |
<br> | |
<b>NOTE</b>: This function should be override by any class inheriting from this object.<br> | |
In order to properly work, any overriding destroyer should: | |
<ol> | |
<li> Free any array specific to the object on which is called;</li> | |
<li> Remove any event listener on chart objects;</li> | |
<li> Call super object's destroy method.</li> | |
</ol> | |
@method destroy | |
@for Legend | |
@return {null} to state that the object has been destroyed. | |
*/ | |
destroy: function(){ | |
//Deletes all the elements from object's arrays | |
this.__items__.length = 0; | |
//Removes DOM objects | |
this.__svgElement__.remove(); | |
this.__divElement__.remove(); | |
return null; | |
} | |
}; | |
/** | |
Legend for a chart;<br> | |
Adds a div and an SVG element to the page to represent a chart's legend. | |
@class Legend | |
@private | |
*/ | |
//@for BasicBarChart | |
/** LegendFactory(width, height [, left, top, parent]) | |
Creates, upon request, a new Legend object and returns it; | |
@method LegendFactory | |
@for Legend | |
@param {Number} [width] [Mandatory] | |
The desired width for the chart (<b>can't be changed later</b>) | |
Can be any value that is or can be converted to a positive integer. | |
@param {Number} [height] [Mandatory] | |
The desired height for the chart (<b>can't be changed later</b>) | |
Can be any value that is or can be converted to a positive integer. | |
@param {Number} [left] [Optional] | |
The horizontal position of the legend bounding box; | |
@param {Number} [top] [Optional] | |
The vertical position of the legend bounding box; | |
@param {Object} [parent= page's body element] [Optional] | |
The DOM element to which the diagram should be appended as a child | |
@return {Object} A new Legend object; | |
@throws | |
- Wrong number of arguments Exception, if width or height are not passed as arguments (directly) | |
- Illegal Argument exception , if width or height aren't valid (numeric, positive) values | |
(through setWidth or setHeight) | |
- Illegal Argument exception, if dataDim is passed but it's invalid (not numeric or not positive) | |
- Exception, if dataDim exceeds the maximum data dimension | |
- Exception, if parent is passed but it is not a valid DOM element | |
*/ | |
function LegendFactory(width, height, left, top, parent){ | |
/** __init__(legend, width, height) | |
[Private method, not visible from consumers] | |
@method __init__ | |
@private | |
@param {Object} legend [Mandatory] | |
The legend object to be initialized; | |
@param {Number} width [Mandatory] | |
The width of the legend object; | |
@param {Number} height [Mandatory] | |
The height of the legend object; | |
@param {Number} left [Mandatory] | |
Horizontal absolute position of the legend; | |
@param {Number} top [Mandatory] | |
Vertical absolute position of the legend; | |
@return {undefined} | |
@throws | |
- Illegal Argument exception , if width or height aren't valid (numeric, positive) values | |
(through setWidth or setHeight) | |
*/ | |
function __init__(legend, width, height, left, top){ | |
legend.setWidth(width); | |
legend.setHeight(height); | |
if (Object.isUndefined(left)){ | |
left = 0; | |
} | |
if (Object.isUndefined(top)){ | |
top = 0; | |
} | |
legend.setPosition(left, top); | |
legend.__svgElement__.append("rect") | |
.attr("stroke", "black") | |
.attr("width", width) | |
.attr("fill", "white") | |
.attr("height", height - 2) | |
.attr("x", 0) | |
.attr("y", 1); | |
} | |
var legend = Object.create(legendPrototype); | |
/** __redrawLegend__() | |
[Protected method, not visible outside this library] | |
Update the drawings of this legend object; | |
@method __redrawLegend__ | |
@protected | |
@return {undefined} | |
@throws | |
- Illegal Argument exception , if width or height aren't valid (numeric, positive) values | |
(through setWidth or setHeight) | |
*/ | |
Object.defineProperty(legend, "__redrawLegend__", { | |
value: function(){ | |
this.__svgElement__.selectAll("rect").data(this.__items__) | |
.attr("stroke", "black") | |
.attr("fill", function(item){return item.fillColor;}) | |
.attr("width", LEGEND_ITEM_WIDTH) | |
.attr("height", LEGEND_ITEM_HEIGHT) | |
.attr("x", LEGEND_ITEM_LEFT) | |
.attr("y", function(item, i){return LEGEND_MARGIN + i * (LEGEND_ITEM_HEIGHT + LEGEND_MARGIN);}); | |
this.__svgElement__.selectAll("text").data(this.__items__) | |
.text(function(item){return item.labelText;}) | |
.attr("fill", function(item){return item.labelColor;}) | |
.attr("x", LEGEND_ITEM_LEFT + LEGEND_ITEM_WIDTH + LEGEND_MARGIN) //Aligns to the bottom of the rect | |
.attr("y", function(item, i){return i * (LEGEND_ITEM_HEIGHT + LEGEND_MARGIN) + LEGEND_ITEM_HEIGHT;}) | |
.attr("font-family", "sans-serif") | |
.attr("font-size", LEGEND_ITEM_FONT_SIZE) | |
.attr("text-anchor", "left"); | |
return; | |
}, | |
writable: false, | |
enumerable: false, | |
configurable:false | |
}); | |
if (Object.isUndefined(width) || Object.isUndefined(height)){ | |
throw "Wrong number of arguments: width and height are mandatory"; | |
} | |
if (Object.isUndefined(parent )){ //By default, added to the | |
parent = d3.select("body"); | |
} | |
var div = parent.append("div"); | |
var svg = div.append("svg").attr("id", "chart_legend"); | |
/** | |
The div element that will be a container to the legend's svg element | |
@property __divElement__ | |
@type {Object} | |
@readOnly | |
@protected | |
*/ | |
Object.defineProperty(legend, "__divElement__", { | |
value: div, | |
writable: false, | |
enumerable: false, | |
configurable:false | |
}); | |
/** | |
The svg element for this Legend | |
@property __svgElement__ | |
@type {Object} | |
@readOnly | |
@protected | |
*/ | |
Object.defineProperty(legend, "__svgElement__", { | |
value: svg, | |
writable: false, | |
enumerable: false, | |
configurable:false | |
}); | |
/** | |
Array of the items contained in the legend | |
@property __items__ | |
@type {Array} | |
@readOnly | |
@protected | |
*/ | |
Object.defineProperty(legend, "__items__", { | |
value: [], | |
writable: false, | |
enumerable: false, | |
configurable:false | |
}); | |
__init__(legend, width, height, left, top); | |
Object.seal(legend); | |
return legend; | |
} | |
// ----- DYNAMIC CHARTS -------- | |
/** | |
Base Chart Class: <b>BasicBarChart</b><br> | |
Basic bar histogram chart.<br> | |
Values are represented using vertical bars;<br> | |
Each point or value can have up to 10 subcomponents, where each component can be | |
any non-nregative real number (i.e., each point can be in R_+^i, for 1 <= i <= 10). | |
@class BasicBarChart | |
@private | |
@beta | |
*/ | |
//uses Legend | |
var next_id = 0; | |
var basicBarChartSharedPrototype = { | |
/** setPosition(left, top) | |
Sets the position of the chart in the page. Position is assumed to be absolute. | |
@method setPosition | |
@chainable | |
@param {Number} left [Mandatory] | |
The horizontal position of the chart bounding box; | |
@param {Number} top [Mandatory] | |
The vertical position of the chart bounding box; | |
@return {Object} This chart object, to allow for method chaining. | |
@throws Never: if either argument is not valid, simply ignores the action. | |
*/ | |
setPosition: function(left, top){ | |
left = parseInt(left, 10); | |
top = parseInt(top, 10); | |
if (this.__divElement__ && !isNaN(left) && !isNaN(top)){ | |
this.__divElement__.attr("style", "position:absolute; left:" + left + "px; top:"+top +"px;" ); | |
} | |
return this; //Method chaining support | |
}, | |
/** setWidth(width) | |
Sets the width of the chart bounding box. | |
@method setWidth | |
@chainable | |
@param {Number} width [Mandatory] | |
The new width of the chart;<br> | |
Only positive integers and values that can be converted | |
to positive Integers are accepted. | |
@return {Object} This chart object, to allow for method chaining. | |
@throws {Illegal Argument Exception} if the argument is not valid (see above). | |
*/ | |
setWidth: function(width){ | |
width = parseInt(width, 10); | |
if (this.__svgElement__ && !isNaN(width) && width >= 0){ | |
this.__svgElement__.attr("width", width); | |
this.__divElement__.attr("width", width); | |
}else{ | |
throw "Illegal Argument: width"; | |
} | |
return this; //Method chaining support | |
}, | |
/** setHeight(height) | |
Sets the height of the chart bounding box. | |
@method setHeight | |
@chainable | |
@param {Number} height [Mandatory] | |
The new height of the chart; <br> | |
Only positive Integers and values that can be converted | |
to positive integers are accepted. | |
@return {Object} This chart object, to allow for method chaining. | |
@throws <ul> | |
<li> Illegal Argument Exception, if the argument is not valid (see above). </li> | |
</ul> | |
*/ | |
setHeight: function(height){ | |
height = parseInt(height, 10); | |
if (this.__svgElement__ && !isNaN(height) && height >= 0){ | |
this.__svgElement__.attr("height", height); | |
this.__divElement__.attr("height", height); | |
}else{ | |
throw "Illegal Argument: height"; | |
} | |
return this; //Method chaining support | |
}, | |
/** getAxesWidth() | |
Returns the width of each of the four axe areas surrounding the chart. | |
@method getAxesWidth | |
@return {Array} An array with the width of the four axes in the following order:<br> | |
[top, right, bottom, left]. | |
*/ | |
getAxesWidth: function(){ | |
return [this.__svgElement__.select("#axe_top").attr("width"), | |
this.__svgElement__.select("#axe_right").attr("width"), | |
this.__svgElement__.select("#axe_bottom").attr("width"), | |
this.__svgElement__.select("#axe_left").attr("width")]; | |
}, | |
/** getAxesHeight() | |
Returns the height of each of the four axe areas surrounding the chart. | |
@method getAxesHeight | |
@return {Array} An array with the height of the four axes in the following order:<br> | |
[top, right, bottom, left]. | |
*/ | |
getAxesHeight: function(){ | |
return [this.__svgElement__.select("#axe_top").attr("height"), | |
this.__svgElement__.select("#axe_right").attr("height"), | |
this.__svgElement__.select("#axe_bottom").attr("height"), | |
this.__svgElement__.select("#axe_left").attr("height")]; | |
}, | |
/** setTitle(title [, size, color, left, top]) | |
Sets the title for the chart, including all its attributes. | |
@method setTitle | |
@chainable | |
@param {String} title [Mandatory] | |
The new title for the chart; | |
@param {Number} [size=DEFAULT TITLE SIZE] [Optional] | |
The size of the new title;<br> | |
Only positive Integers and values that can be converted | |
to positive integers are accepted. | |
@param {String} [color=black] [Optional] | |
The color of the new title;<br> | |
@param {Number} [left=centered] [Optional] | |
The horizontal position of the title, relative to the chart; | |
the text will be centered around this point<br> | |
Only positive Integers and values that can be converted | |
to positive integers are accepted. | |
@param {Number} [top=0] [Optional] | |
The vertical position of the title, relative to the chart;<br> | |
Only positive Integers and values that can be converted | |
to positive integers are accepted. | |
@return {Object} This legend object, to allow for method chaining. | |
@throws <ul> | |
<li> Illegal Argument Exception, if the argument is not valid (see above). </li> | |
</ul> | |
*/ | |
setTitle: function(title, size, color, left, top){ | |
var titleElement = this.__axeTop__.selectAll("text[type=title_element]"); | |
size = parseInt(size, 10); | |
if (!Object.isNumber(size)){ | |
size = DEFAULT_TITLE_SIZE; | |
} | |
left = parseInt(left, 10); | |
if (!Object.isNumber(left)){ | |
left = this.__axeTop__.attr("width") / 2; | |
} | |
top = parseInt(top, 10); | |
if (!Object.isNumber(top)){ | |
top = size; | |
} | |
if (Object.isUndefined(color)){ | |
color = "black"; | |
} | |
if (titleElement.empty()){ | |
titleElement = this.__axeTop__.append("text") | |
.attr("type", "title_element") | |
.attr("text-anchor", "middle") | |
.attr("font-family", "sans-serif"); | |
} | |
titleElement.text(title) | |
.attr("font-size", size) | |
.attr("x", left) | |
.attr("y", top) | |
.attr("fill", color); | |
return this; //Method chaining support | |
}, | |
/** addLegend: function(labels, width, height [, left, top, parent]) | |
Insert new data into the chart, at runtime; | |
@method addLegend | |
@chainable | |
@param {Array} labels [Mandatory] | |
An array containing exactly one label for each component of the data space.<br> | |
A new legend object will be created and attached to the chart, and then | |
for every subcomponent [label] a new item will be added to the legend. | |
@return {Object} This chart object, to support method chaining; | |
@throws | |
- Illegal Argument Exception: if labels isn't an Array object. | |
- Invalid array size Exception: if the number of elements in the array is different | |
from the number of subcomponents of the data space | |
(i.e. from the __dataDim__ attribute) | |
*/ | |
addLegend: function(labels, width, height, left, top, parent){ | |
if (this.hasOwnProperty("__legend__")){ | |
if (!Object.isArray(labels)){ | |
throw "Illegal Argument: labels"; | |
} | |
else if (labels.length !== this.__dataDim__){ | |
throw "Invalid array size: " + this.__dataDim__ + " labels needed"; | |
} | |
if (this.__legend__ && this.__legend__.destroy){ | |
this.__legend__.destroy(); | |
} | |
if (Object.isUndefined(parent)){ | |
parent = this.__parent__; //By default, they'll have the same parent; | |
} | |
this.__legend__ = LegendFactory(width, height, left, top, parent); | |
for (var i=0; i<labels.length; i++){ | |
this.__legend__.addItem(labels[i], this.getLabelColor(i), this.getBarsFillColor(i)); | |
} | |
}else{ | |
//Looks for object's prototype | |
var proto = this.prototype ? this.prototype : this.__proto__; | |
if (proto && proto.addLegend){ | |
proto.addLegend(labels, width, height, left, top, parent); | |
} | |
} | |
return this; //Method chaining supported | |
}, | |
/** appendData(newDataArray) | |
Insert new data into the chart, at runtime; | |
@method appendData | |
@chainable | |
@param {Array} newDataArray [Mandatory] | |
An array containing the next values that needs to be drawn in the chart;<br> | |
Each array element, in order to be added to the chart, must be compliant | |
with the data format defined by the function __formatData__ (which | |
can itself be set at runtime, and by default accepts arrays of | |
__dataDim__ integers, neglecting to render the negative ones). | |
@return {Object} This chart object, to support method chaining; | |
@throws | |
- Illegal Argument Exception: if newDataArray isn't an Array object. | |
*/ | |
appendData: function(newDataArray){ | |
/*for (var key in newData){ | |
this.data[key] = newData[key]; | |
}*/ | |
//Save the number to use it during drawing (for scaling) | |
var oldDataLength = Math.max(this.__dataDim__, this.__getDatasetLength__() * this.__dataDim__); | |
//Checks how much data can be appended, and if any action is needed to do so | |
newDataArray = this.__canAppendData__(newDataArray); | |
var i, j, val; | |
if (Object.isArray(newDataArray)){ | |
var n = newDataArray.length; | |
for (i=0; i < n; i++){ | |
val = this.__formatValue__(newDataArray[i]); | |
if (val !== null){ | |
this.setProperty("__dataCounter__", this.__dataCounter__ + 1); | |
for (j=0; j < this.__dataDim__; j++){ | |
this.__data__[j].push(val[j]); | |
if (val[j] > this.__maxVals__[j]){ | |
this.__maxVals__[j] = val[j]; | |
} | |
} | |
} | |
} | |
}else{ | |
throw "Illegal Argument: newDataArray must be an Array"; | |
} | |
var newDataLength = this.__getDatasetLength__() * this.__dataDim__; | |
//The max is recomputed every time to retain the ability to switch on the fly between scaling locally and globally | |
var max_val; | |
if (this.__scaleGlobally__){ | |
max_val = ChartUtils.fillArray(this.__maxVals__.max(), this.__dataDim__); | |
}else{ | |
max_val = this.__maxVals__; //Values aren't going to be modified, so we can just copy the reference | |
} | |
this.__updateBackground__(); | |
for (j = 0; j < this.__dataDim__; j++){ | |
//Set the old X scale until the new data is added | |
this.__xScale__.domain([0, oldDataLength]); | |
var dataSet = this.__selectData__(this.__data__, j); | |
var labelsSet = this.__selectLabels__(this.__data__, j); | |
this.__drawNewData__(dataSet, labelsSet, j, this.__xScale__, this.__yScale__[j]); | |
//Computes the new X and Y scale | |
this.__xScale__.domain([0, newDataLength]); | |
this.__yScale__[j].domain([0, max_val[j]]); | |
this.__updateDrawing__(dataSet, labelsSet, j, this.__xScale__, this.__yScale__[j]); | |
} | |
this.__updateAxes__(this.__yScale__[0]); | |
return this; //Method chaining oriented | |
}, | |
/** setFormatValueFunction(formaValueFunction) | |
Change the data formatting function, allowing to pass a custom handler | |
to cope with JSON or other data formats. | |
@method setFormatValueFunction | |
@chainable | |
@param {Function} formaValueFunction [Mandatory] | |
The new data formatting/parsing function; | |
@return {Object} This object, to allow for method chaining; | |
@throws | |
- Illegal Argument Exception, if the argument passed isn't a valid function. | |
*/ | |
setFormatValueFunction: function(formaValueFunction){ | |
if (Object.isUndefined(formaValueFunction) || | |
!Object.isFunction(formaValueFunction)){ | |
throw "Illegal Argument: formaValueFunction"; | |
} | |
Object.defineProperty(this, "__formatValue__", { | |
value: formaValueFunction, | |
writable: true, | |
enumerable: false, | |
configurable:false | |
}); | |
return this; //Method chaining support | |
}, | |
/** clearData(n) | |
Remove all the data, or part of it, from the chart; | |
@method clearData | |
@chainable | |
@param {Number} [n] [Optional, For internal use only] | |
The number of elements to remove from the beginning of the data array, | |
i.e. how many of the oldest values should be removed from the chart; | |
@return {Object} This object, to allow for method chaining; | |
@throws Illegal Argument Exception, if n is passed but it isn't valid, i.e. it isn't convertible to a positive int. | |
*/ | |
clearData: function(n){ | |
var i, dataSet, labelsSet; | |
if (!Object.isUndefined(n)){ | |
//Only part of the chart should be cleared | |
n = parseInt(n, 10); | |
if (isNaN(n) || n <= 0 || n > this.__getDatasetLength__()){ | |
throw "Illegal Argument: n"; | |
} | |
}else{ | |
//The whole chart must be cleared | |
n = this.__getDatasetLength__(); | |
} | |
for (i=0; i < this.__dataDim__; i++){ | |
dataSet = this.__selectData__(this.__data__, i, n); | |
labelsSet = this.__selectLabels__(this.__data__, i, n); | |
this.__clearDrawing__(dataSet, labelsSet); | |
this.__data__[i].splice(0, n); //Removes the first n elements | |
//Recomputes the max values | |
this.__maxVals__[i] = this.__data__[i].max(); | |
} | |
this.__onClearData__(n); | |
return this; //Method chaining oriented | |
}, | |
/** toggleLabels([index, visible]) | |
Toggles the visibility of labels in the chart | |
@method toggleLabels | |
@chainable | |
@param {Number} [index=0] [Optional] | |
For multi-dimensional data spaces, specifies | |
which component is going to be affected; | |
@param {Boolean} [visible] [Optional] | |
If specified overwrites toggle behaviour and set | |
the visibility to visible. | |
@return {Object} This chart object, to allow for method chaining | |
@throws | |
- Invalid Index Exception: if the index specified isn't valid. | |
*/ | |
toggleLabels: function(index, visible){ | |
if (!index){ //index === undefined || index === null | |
index = 0; | |
}else{ | |
index = parseInt(index, 10); | |
} | |
if (!isNaN(index) && index >= 0 && this.__labelsVisible__.length > index){ | |
if (!Object.isUndefined(visible)){ | |
this.__labelsVisible__[index] = visible ? true : false; | |
}else{ | |
this.__labelsVisible__[index] = ! this.__labelsVisible__; | |
} | |
}else{ | |
throw "Invalid Index"; | |
} | |
return this; //Method chaining oriented | |
}, | |
/** areLabelsVisible([index]) | |
Checks if labels for the index-th dimension are visible | |
@method areLabelsVisible | |
@param {Number} [index=0] [Optional] | |
For multi-dimensional data spaces, specifies | |
which component is going to be affected; | |
@return {Boolean} The visibility of the label | |
@throws | |
Invalid Index Exception: if the index specified isn't valid. | |
*/ | |
areLabelsVisible: function(index){ | |
if (!index){ //index === undefined || index === null | |
index = 0; | |
}else{ | |
index = parseInt(index, 10); | |
} | |
if (!isNaN(index) && index >= 0 && this.__labelsVisible__.length > index){ | |
return this.__labelsVisible__[index]; | |
}else{ | |
throw "Invalid Index"; | |
} | |
}, | |
/** setGlobalScaling() | |
Sets scaling to global<br> | |
<br> | |
When data space has a dimension greater than 1 (i.e. when each data value has more than 1 component) | |
these charts support either global scaling (relative to the whole dataset) | |
or local scaling (relative to value of the same component) of each subcomponent. | |
@method setGlobalScaling | |
@chainable | |
@return {Object} This chart object, to allow for method chaining. | |
*/ | |
setGlobalScaling: function(){ | |
if (this.hasOwnProperty("__scaleGlobally__")){ | |
this.__scaleGlobally__ = true; | |
}else{ | |
//Looks for object's prototype | |
var proto = this.prototype ? this.prototype : this.__proto__; | |
if (proto && proto.setGlobalScaling){ | |
proto.setGlobalScaling(); | |
} | |
} | |
return this; //Method chaining oriented | |
}, | |
/** setLocalScaling() | |
Sets scaling to local<br> | |
<br> | |
When data space has a dimension greater than 1 (i.e. when each data value has more than 1 component) | |
these charts support either global scaling (relative to the whole dataset) | |
or local scaling (relative to value of the same component) of each subcomponent. | |
@method setLocalScaling | |
@chainable | |
@return {Object} This chart object, to allow for method chaining. | |
*/ | |
setLocalScaling: function(){ | |
if (this.hasOwnProperty("__scaleGlobally__")){ | |
this.__scaleGlobally__ = false; | |
}else{ | |
//Looks for object's prototype | |
var proto = this.prototype ? this.prototype : this.__proto__; | |
if (proto && proto.setLocalScaling){ | |
proto.setLocalScaling(); | |
} | |
} | |
return this; //Method chaining oriented | |
}, | |
/** | |
Use getBarsFillColor instead. | |
@method getFillColor | |
@deprecated | |
*/ | |
/** getBarsFillColor([index]) | |
Gets the fill color used to draw the index-th component of the data space. | |
@method getBarsFillColor | |
@chainable | |
@param {Number} [index=0] [Optional] | |
For multi-dimensional data spaces, specifies | |
which component is going to be selected; | |
@return {String|Object} The selected fill color. | |
@throws | |
- Invalid Index Exception: if the index specified isn't valid. | |
*/ | |
getBarsFillColor: function(index){ | |
if (!index){ //index === undefined || index === null | |
index = 0; | |
}else{ | |
index = parseInt(index, 10); | |
} | |
if (!isNaN(index) && index >= 0 && this.__barsFillColors__.length > index){ | |
return this.__barsFillColors__[index]; | |
}else{ | |
throw "Invalid Index"; | |
} | |
}, | |
/** | |
Use getBarsStrokeColor instead. | |
@method getStrokeColor | |
@deprecated | |
*/ | |
/** getBarsStrokeColor([index]) | |
Gets the stroke color used to draw the index-th component of the data space. | |
@method getBarsStrokeColor | |
@param {Number} [index=0] [Optional] | |
For multi-dimensional data spaces, specifies | |
which component is going to be selected; | |
@return {String} The selected stroke color. | |
@throws | |
- Invalid Index Exception: if the index specified isn't valid. | |
*/ | |
getBarsStrokeColor: function(index){ | |
if (!index){ //index === undefined || index === null | |
index = 0; | |
}else{ | |
index = parseInt(index, 10); | |
} | |
if (!isNaN(index) && index >= 0 && this.__barsStrokeColors__.length > index){ | |
return this.__barsStrokeColors__[index]; | |
}else{ | |
throw "Invalid Index"; | |
} | |
}, | |
/** getLabelColor([index]) | |
Gets the fill color used for the labels attached to the index-th component of the data space. | |
@method getLabelColor | |
@param {Number} [index=0] [Optional] | |
For multi-dimensional data spaces, specifies | |
which component is going to be selected; | |
@return {String} The selected label color. | |
@throws | |
- Invalid Index Exception: if the index specified isn't valid. | |
*/ | |
getLabelColor: function(index){ | |
if (!index){ //index === undefined || index === null | |
index = 0; | |
}else{ | |
index = parseInt(index, 10); | |
} | |
if (!isNaN(index) && index >= 0 && this.__labelColors__.length > index){ | |
return this.__labelColors__[index]; | |
}else{ | |
throw "Invalid Index"; | |
} | |
}, | |
/** getLabelsSize([index]) | |
Gets the size used for the labels attached to the index-th component of the data space. | |
@method getLabelsSize | |
@param {Number} [index=0] [Optional] | |
For multi-dimensional data spaces, specifies | |
which component is going to be selected; | |
@return {Number} The selected size. | |
@throws | |
- Invalid Index Exception: if the index specified isn't valid. | |
*/ | |
getLabelsSize: function(index){ | |
if (!index){ //index === undefined || index === null | |
index = 0; | |
}else{ | |
index = parseInt(index, 10); | |
} | |
if (!isNaN(index) && index >= 0 && this.__labelsSize__.length > index){ | |
return this.__labelsSize__[index]; | |
}else{ | |
throw "Invalid Index"; | |
} | |
}, | |
/** getBarWidth([xScale]) | |
Gets the current bar width for this chart; | |
For this chart, bar width is computed at runtime according to the number of bars plotted; | |
@method getBarWidth | |
@param {Object} [xScale=this.__xScale__] [Optional] | |
It is possible to pass a d3 scale object to get the bar width | |
computed with respect to a different scale metric;<br> | |
On default, the value is computed with respect to this chart's | |
current metric. | |
@return {Number} The value computed for the bar width under current object state. | |
@throws | |
- Illegal Argument Exception: if an invalid xScale object is passed. | |
*/ | |
getBarWidth: function(xScale){ | |
if (Object.isUndefined(xScale)){ | |
xScale = this.__xScale__; | |
} | |
if (!xScale){ | |
throw "Illegal Argument: xScale"; | |
} | |
return xScale(1) - xScale(0) - 1; | |
}, | |
/** getOuterBackgroundColor() | |
Returns current color for background | |
@method getOuterBackgroundColor | |
@return {String|Object} the value set for __innerBackgroundColor__ | |
*/ | |
getOuterBackgroundColor: function(){ | |
return this.__outerBackgroundColor__; | |
}, | |
/** getOuterBackgroundColor() | |
Returns current color for background | |
@method getInnerBackgroundColor | |
@return {String|Object} the value set for __innerBackgroundColor__ | |
*/ | |
getInnerBackgroundColor: function(){ | |
return this.__innerBackgroundColor__; | |
}, | |
/** getInnerBorder() | |
Returns the current border settings for the main chart area. | |
@method getInnerBorder | |
@return {Object} the value set for __innerBackgroundColor__ | |
*/ | |
getInnerBorder: function(){ | |
return this.__innerBorder__; | |
}, | |
/** getOuterBorder() | |
Returns the current border settings for the outer chart area. | |
@method getOuterBorder | |
@return {Object} the value set for __innerBackgroundColor__ | |
*/ | |
getOuterBorder: function(){ | |
return this.__outerBorder__; | |
}, | |
/** | |
Use setBarsFillColor instead. | |
@method setFillColor | |
@deprecated | |
*/ | |
/** setBarsFillColor(color, [index]) | |
Sets the fill color used to draw the index-th component of the data space. | |
@method setBarsFillColor | |
@chainable | |
@param {String|Object] color [Mandatory] | |
The new fill color for the selected component's; | |
@param {Number} [index=0] [Optional] | |
For multi-dimensional data spaces, specifies | |
which component is going to be selected; | |
@return {Object} This chart object, to allow for method chaining. | |
@throws | |
- Invalid Index Exception: if the index specified isn't valid; | |
*/ | |
setBarsFillColor: function(color, index, superCall){ | |
if (!index){ //index === undefined || index === null | |
index = 0; | |
}else{ | |
index = parseInt(index, 10); | |
} | |
if (!isNaN(index) && index >= 0 && this.__barsFillColors__.length > index){ | |
this.__barsFillColors__[index] = color; | |
}else{ | |
throw "Invalid Index"; | |
} | |
if (this.__legend__){ //&& this.__legend__.updateItem | |
//INVARIANT: __legend__ is either null or a valid legend [Since it's an inner class, we avoid defensive programming] | |
this.__legend__.updateItem(index, undefined, undefined, color, true); | |
} | |
if (!superCall){ | |
this.__redrawAll__(); | |
} | |
return this; | |
}, | |
/** | |
Use setBarsStrokeColor instead. | |
@method setStrokeColor | |
@deprecated | |
*/ | |
/** setBarsStrokeColor(color, [index]) | |
Sets the stroke color used to draw the index-th component of the data space. | |
@method setBarsStrokeColor | |
@chainable | |
@param {String} color [Mandatory] | |
The new stroke color for the selected component's; | |
@param {Number} [index=0] [Optional] | |
For multi-dimensional data spaces, specifies | |
which component is going to be selected; | |
@return {Object} This chart object, to allow for method chaining. | |
@throws | |
- Invalid Index Exception: if the index specified isn't valid; | |
*/ | |
setBarsStrokeColor: function(color, index){ | |
if (!index){ //index === undefined || index === null | |
index = 0; | |
}else{ | |
index = parseInt(index, 10); | |
} | |
if (!isNaN(index) && index >= 0 && this.__barsStrokeColors__.length > index){ | |
this.__barsStrokeColors__[index] = color; | |
}else{ | |
throw "Invalid Index"; | |
} | |
return this; | |
}, | |
/** setLabelColor(color, [index]) | |
Sets the fill color used for the labels attached to the index-th component of the data space. | |
@method setLabelColor | |
@chainable | |
@param {String} color [Mandatory] | |
The new color for the selected component's labels; | |
@param {Number} [index=0] [Optional] | |
For multi-dimensional data spaces, specifies | |
which component is going to be selected; | |
@return {Object} This chart object, to allow for method chaining. | |
@throws | |
- Invalid Index Exception: if the index specified isn't valid; | |
*/ | |
setLabelColor: function(color, index){ | |
if (!index){ //index === undefined || index === null | |
index = 0; | |
}else{ | |
index = parseInt(index, 10); | |
} | |
if (!isNaN(index) && index >= 0 && this.__labelColors__.length > index){ | |
this.__labelColors__[index] = color; | |
}else{ | |
throw "Invalid Index"; | |
} | |
if (this.__legend__){ //&& this.__legend__.updateItem | |
//INVARIANT: __legend__ is either null or a valid legend [Since it's an inner class, we avoid defensive programming] | |
this.__legend__.updateItem(index, undefined, color, undefined); | |
} | |
return this; | |
}, | |
/** setLabelSize(size, [index]) | |
Sets the size used for the labels attached to the index-th component of the data space. | |
@method setLabelSize | |
@chainable | |
@param {Number} size [Mandatory] | |
The new size for the selected component's labels; | |
Must be a positive integer, or a value that can be converted | |
to a positive integer; | |
@param {Number} [index=0] [Optional] | |
For multi-dimensional data spaces, specifies | |
which component is going to be selected; | |
@return {Object} This chart object, to allow for method chaining. | |
@throws | |
- Invalid Index Exception: if the index specified isn't valid; | |
- Illegal Argument Exception: if size isn't valid (see above). | |
*/ | |
setLabelSize: function(size, index){ | |
size = parseInt(size, 10); | |
if (isNaN(size) || size <= 0){ | |
throw "Illegal Argument: size"; | |
} | |
if (!index){ //index === undefined || index === null | |
index = 0; | |
}else{ | |
index = parseInt(index, 10); | |
} | |
if (!isNaN(index) && index >= 0 && this.__labelsSize__.length > index){ | |
this.__labelsSize__[index] = size; | |
}else{ | |
throw "Invalid Index"; | |
} | |
return this; | |
} , | |
/** setBarWidth() | |
Sets the bars width property; | |
For this chart, bar width is computed at runtime according to the number of bars plotted, | |
so this property can't be set.<br> | |
<b>Unless overridden, any call to this method will cause an exception to be thrown</b><br> | |
This method is declared to improve the consistency of the interface. | |
@method setBarWidth | |
@throws Read only property Exception | |
*/ | |
setBarWidth: function(/*barWidth*/){ | |
throw "Read only property: barWidth"; | |
}, | |
/** setInnerBackgroundColor(bgColor) | |
Changes the background color | |
@method setInnerBackgroundColor | |
@chainable | |
@param {String|Object} bgColor [Mandatory] | |
The new color for background; | |
@return {Object} This chart object, to allow for method chaining. | |
*/ | |
setInnerBackgroundColor: function(bgColor, superCall){ | |
// {Boolean} superCall is an internal parameter | |
// used to distinguish calls to a super method | |
if (this.hasOwnProperty("__innerBackgroundColor__")){ | |
this.__innerBackgroundColor__ = bgColor; | |
}else{ | |
//Looks the same method in the super class | |
this.superMethod("setInnerBackgroundColor", bgColor, true); | |
} | |
if (!superCall){ | |
this.__redrawInnerBackground__(); | |
} | |
return this; //Method chaining oriented | |
}, | |
/** setOuterBackgroundColor(bgColor) | |
Changes the background color of the outer area of the chart. | |
@method setOuterBackgroundColor | |
@since 0.2 | |
@chainable | |
@param {String|Object} bgColor [Mandatory] | |
The new color for background of the outer area; | |
@return {Object} This chart object, to allow for method chaining. | |
*/ | |
setOuterBackgroundColor: function(bgColor, superCall){ | |
//{Boolean} superCall is an internal parameter | |
// used to distinguish calls to a super method | |
if (this.hasOwnProperty("__outerBackgroundColor__")){ | |
this.__outerBackgroundColor__ = bgColor; | |
}else{ | |
//Looks the same method in the super class | |
this.superMethod("setOuterBackgroundColor", bgColor, true); | |
} | |
if (!superCall){ | |
this.__redrawOuterBackground__(); | |
} | |
return this; //Method chaining oriented | |
}, | |
/** setInnerBorder(fill [, width, dash]) | |
Changes the border of the main chart area. | |
@method setInnerBorder | |
@since 0.2 | |
@chainable | |
@param {String|Object} fill [Mandatory] | |
The new color for the border, or "none" | |
if it has to be removed; | |
@param {Number} [width] [Mandatory] | |
The width of the border line, in pixels;<br> | |
@param {String} [dash] [Mandatory] | |
The dash pattern for the border;<br> | |
The format for the dash string parameter allows | |
to specify n couples of positive integers | |
<i>"#line_1 #space_1 #line_2 #space_2 ... #line_n #space_n"</i> | |
where each #line and #space represents the number | |
of pixels in the pattern for lines and spaces | |
segments respectively. | |
@return {Object} This chart object, to allow for method chaining. | |
*/ | |
setInnerBorder: function(fill, width, dash, superCall){ | |
// {Boolean} superCall is an internal parameter | |
// used to distinguish calls to a super method | |
if (this.hasOwnProperty("__innerBorder__")){ | |
this.__innerBorder__.fill = fill; | |
if (!Object.isUndefined(width)){ | |
this.__innerBorder__.width = width; | |
} | |
if (!Object.isUndefined(dash)){ | |
this.__innerBorder__.dash = dash; | |
} | |
}else{ | |
//Looks the same method in the super class | |
this.superMethod("setInnerBorder", fill, width, dash, true); | |
} | |
if (!superCall){ | |
this.__redrawInnerBorder__(); | |
} | |
return this; //Method chaining oriented | |
}, | |
/** setOuterBorder(fill [, width, dash]) | |
Changes the border of the outer chart area. | |
@method setOuterBorder | |
@since 0.2 | |
@chainable | |
@param {String|Object} fill [Mandatory] | |
The new color for the border, or "none" | |
if it has to be removed; | |
@param {Number} [width] [Mandatory] | |
The width of the border line, in pixels;<br> | |
@param {String} [dash] [Mandatory] | |
The dash pattern for the border;<br> | |
The format for the dash string parameter allows | |
to specify n couples of positive integers | |
<i>"#line_1 #space_1 #line_2 #space_2 ... #line_n #space_n"</i> | |
where each #line and #space represents the number | |
of pixels in the pattern for lines and spaces | |
segments respectively. | |
@return {Object} This chart object, to allow for method chaining. | |
*/ | |
setOuterBorder: function(fill, width, dash, superCall){ | |
// {Boolean} superCall is an internal parameter | |
// used to distinguish calls to a super method | |
if (this.hasOwnProperty("__outerBorder__")){ | |
this.__outerBorder__.fill = fill; | |
if (!Object.isUndefined(width)){ | |
this.__outerBorder__.width = width; | |
} | |
if (!Object.isUndefined(dash)){ | |
this.__outerBorder__.dash = dash; | |
} | |
}else{ | |
//Looks the same method in the super class | |
this.superMethod("setOuterBorder", fill, width, dash, true); | |
} | |
if (!superCall){ //undefined or falsey | |
this.__redrawOuterBorder__(); | |
} | |
return this; //Method chaining oriented | |
}, | |
/** setAbbreviatedLabel() | |
Displays abbreviated text for bars' label.<br> | |
F.i.: 1.1M instead of 1,123,543 or 4.3K instead of 4,356 | |
@method setAbbreviatedLabel | |
@since 0.2 | |
@chainable | |
@param {Number} [index=0] [Optional] | |
For multi-dimensional data spaces, specifies | |
which component is going to be selected; | |
@return {Object} This chart object, to allow for method chaining. | |
@throws {Invalid Index Exception} If the index specified isn't valid. | |
*/ | |
setAbbreviatedLabel: function(index, superCall){ | |
// {Boolean} superCall is an internal parameter | |
// used to distinguish calls to a super method | |
if (this.hasOwnProperty("__abbreviatedLabels__")){ | |
if (!index){ //index === undefined || index === null | |
index = 0; | |
}else{ | |
index = parseInt(index, 10); | |
} | |
if (!isNaN(index) && index >= 0 && this.__abbreviatedLabels__.length > index){ | |
this.__abbreviatedLabels__[index] = true; | |
} | |
}else{ | |
//Looks the same method in the super class | |
this.superMethod("setAbbreviatedLabel", index, true); | |
} | |
if (!superCall){ | |
this.__redrawAll__(); | |
} | |
return this; //Method chaining oriented | |
}, | |
/** setExtendedLabel() | |
Displays extended text for bars' label.<br> | |
@method setExtendedLabel | |
@since 0.2 | |
@chainable | |
@param {Number} [index=0] [Optional] | |
For multi-dimensional data spaces, specifies | |
which component is going to be selected; | |
@return {Object} This chart object, to allow for method chaining. | |
@throws {Invalid Index Exception} If the index specified isn't valid. | |
*/ | |
setExtendedLabel: function(index, superCall){ | |
// {Boolean} superCall is an internal parameter | |
// used to distinguish calls to a super method | |
if (this.hasOwnProperty("__abbreviatedLabels__")){ | |
if (!index){ //index === undefined || index === null | |
index = 0; | |
}else{ | |
index = parseInt(index, 10); | |
} | |
if (!isNaN(index) && index >= 0 && this.__abbreviatedLabels__.length > index){ | |
this.__abbreviatedLabels__[index] = false; | |
} | |
}else{ | |
//Looks the same method in the super class | |
this.superMethod("setExtendedLabel", index, true); | |
} | |
if (!superCall){ | |
this.__redrawAll__(); | |
} | |
return this; //Method chaining oriented | |
}, | |
/** setHorizontalAxe([displayOnTop, title, notches, lineWidth, labelColor, labelSize, titleColor, titleSize]) | |
Set the properties for an horizontal axe and draws it anew, clearing previous versions.<br> | |
@method setHorizontalAxe | |
@since 0.2 | |
@chainable | |
@param {Boolean} [displayOnTop=false] [Optional] | |
If passed and thrutey, the vertical axe will be added to the displayOnTop instead than to the bottom | |
of the chart. | |
@param {String} [title=""] [Optional] | |
The title to use for the vertical axe. | |
@param {Number} [notches=0] [Optional] | |
Number of notches to display on the vertical axe (excluding the 2 extremes).<br> | |
<b>MUST</b> be a non negative integer. | |
@param {Number} [lineWidth=DEFAULT_AXES_LINE_WIDTH] [Optional] | |
The desired line width for the axe and notches.<br> | |
If passed MUST be a positive integer; otherwise it is set to the default value. | |
@param {String} [labelColor=DEFAULT_AXES_COLOR] [Optional] | |
Color to use for axe's drawings and labels. | |
@param {Number} [labelSize=DEFAULT_AXES_LABEL_SIZE] [Optional] | |
Size for the notches label's text. | |
@param {String} [titleColor=DEFAULT_AXES_COLOR] [Optional] | |
Color to use for axe's title. | |
@param {Number} [titleSize=DEFAULT_AXES_LABEL_SIZE] [Optional] | |
The desired size for the title.<br> | |
If passed MUST be a positive integer; otherwise it is set to the default value. | |
@return {Object} This chart object, to allow for method chaining. | |
@throws {Invalid Argument Exception} If notches is passed but it's not valid (see above). | |
*/ | |
setHorizontalAxe: function(displayOnTop, title, notches, lineWidth, labelColor, labelSize, titleColor, titleSize){ | |
if (Object.isUndefined(notches)){ | |
notches = 0; | |
}else{ | |
notches = parseInt(notches, 10); | |
if (isNaN(notches) || notches < 0){ | |
throw "Illegal Argument: notches"; | |
} | |
} | |
lineWidth = parseInt(lineWidth, 10); | |
if (isNaN(lineWidth) || lineWidth <= 0){ | |
lineWidth = DEFAULT_AXES_LINE_WIDTH; | |
} | |
labelSize = parseInt(labelSize, 10); | |
if (isNaN(labelSize) || labelSize <= 0){ | |
labelSize = DEFAULT_AXES_LABEL_SIZE; | |
} | |
if (Object.isUndefined(labelColor)){ | |
labelColor = DEFAULT_AXES_COLOR; | |
} | |
//First it clears the previous drawing of the axe, if any is present | |
this.__clearHorizontalAxe__(); | |
var axe = { | |
notches: notches, | |
color: labelColor, | |
lineWidth: lineWidth, | |
labelSize: labelSize, | |
title: title, | |
titleSize: parseInt(titleSize, 10), | |
titleColor: titleColor | |
}; | |
if (displayOnTop){ | |
axe.side = "top"; | |
axe.svgElement = this.__axeTop__; | |
}else{ | |
axe.side = "bottom"; | |
axe.svgElement = this.__axeBottom__; | |
} | |
if (isNaN(axe.titleSize) || axe.titleSize < 0){ | |
axe.titleSize = DEFAULT_AXES_LABEL_SIZE; | |
} | |
if (!axe.titleColor){ | |
axe.titleColor = axe.color; | |
} | |
Object.freeze(axe); //Prevent further modifications | |
this.setProperty("__horizontalAxe__", axe); | |
this.__drawHorizontalAxe__(); | |
return this; | |
}, | |
/** removeHorizontalAxe() | |
Removes the horizontal axe object and all related drawings from this chart. | |
@method removeHorizontalAxe | |
@since 0.2 | |
@chainable | |
@return {Object} This chart object, to allow for method chaining. | |
*/ | |
removeHorizontalAxe: function(){ | |
this.__clearHorizontalAxe__(); | |
this.setProperty("__horizontalAxe__", null); | |
return this; | |
}, | |
/** setVerticalAxe([displayOnRight, title, notches, lineWidth, labelColor, labelSize, titleColor, titleSize]) | |
Set the properties for a vertical axe and draws it anew, clearing previous versions.<br> | |
@method setVerticalAxe | |
@since 0.2 | |
@chainable | |
@param {Boolean} [displayOnRight=false] [Optional] | |
If passed and thrutey, the vertical axe will be added to the displayOnRight instead than to the left | |
of the chart. | |
@param {String} [title=""] [Optional] | |
The title to use for the vertical axe. | |
@param {Number} [notches=0] [Optional] | |
Number of notches to display on the vertical axe (excluding the 2 extremes).<br> | |
<b>MUST</b> be a non negative integer. | |
@param {Number} [lineWidth=DEFAULT_AXES_LINE_WIDTH] [Optional] | |
The desired line width for the axe and notches.<br> | |
If passed MUST be a positive integer; otherwise it is set to the default value. | |
@param {String} [labelColor=DEFAULT_AXES_COLOR] [Optional] | |
Color to use for axe's drawings and labels. | |
@param {Number} [labelSize=DEFAULT_AXES_LABEL_SIZE] [Optional] | |
Size for the notches label's text. | |
@param {String} [titleColor=DEFAULT_AXES_COLOR] [Optional] | |
Color to use for axe's title. | |
@param {Number} [titleSize=DEFAULT_AXES_LABEL_SIZE] [Optional] | |
The desired size for the title.<br> | |
If passed MUST be a positive integer; otherwise it is set to the default value. | |
@return {Object} This chart object, to allow for method chaining. | |
@throws {Invalid Argument Exception} If notches is passed but it's not valid (see above). | |
*/ | |
setVerticalAxe: function(displayOnRight, title, notches, lineWidth, labelColor, labelSize, titleColor, titleSize){ | |
if (Object.isUndefined(notches)){ | |
notches = 0; | |
}else{ | |
notches = parseInt(notches, 10); | |
if (isNaN(notches) || notches < 0){ | |
throw "Illegal Argument: notches"; | |
} | |
} | |
lineWidth = parseInt(lineWidth, 10); | |
if (isNaN(lineWidth) || lineWidth <= 0){ | |
lineWidth = DEFAULT_AXES_LINE_WIDTH; | |
} | |
labelSize = parseInt(labelSize, 10); | |
if (isNaN(labelSize) || labelSize <= 0){ | |
labelSize = DEFAULT_AXES_LABEL_SIZE; | |
} | |
if (Object.isUndefined(labelColor)){ | |
labelColor = DEFAULT_AXES_COLOR; | |
} | |
//First it clears the previous drawing of the axe, if any is present | |
this.__clearVerticalAxe__(); | |
var axe = { | |
notches: notches, | |
color: labelColor, | |
lineWidth: lineWidth, | |
labelSize: labelSize, | |
title: title, | |
titleSize: parseInt(titleSize, 10), | |
titleColor: titleColor | |
}; | |
if (displayOnRight){ | |
axe.side = "right"; | |
axe.svgElement = this.__axeRight__; | |
}else{ | |
axe.side = "left"; | |
axe.svgElement = this.__axeLeft__; | |
} | |
if (isNaN(axe.titleSize) || axe.titleSize < 0){ | |
axe.titleSize = DEFAULT_AXES_LABEL_SIZE; | |
} | |
if (!axe.titleColor){ | |
axe.titleColor = axe.color; | |
} | |
Object.freeze(axe); //Prevent further modifications | |
this.setProperty("__verticalAxe__", axe); | |
this.__drawVerticalAxe__(title, titleSize, titleColor); | |
return this; | |
}, | |
/** removeVerticalAxe() | |
Removes the vertical axe object and all related drawings from this chart. | |
@method removeVerticalAxe | |
@since 0.2 | |
@chainable | |
@return {Object} This chart object, to allow for method chaining. | |
*/ | |
removeVerticalAxe: function(){ | |
this.__clearVerticalAxe__(); | |
this.setProperty("__verticalAxe__", null); | |
return this; | |
}, | |
/** destroy() | |
Object's destructor: helps garbage collector freeing memory, and removes chart DOM elements.<br> | |
<br> | |
<b>WARNING</b>: calling destroy on an object will force any further reference | |
to its attributes / methods to throw exceptions.<br> | |
<br> | |
<b>NOTE</b>: This function should be override by any class inheriting from this chart.<br> | |
In order to properly work, any overriding destroyer should: | |
<ol> | |
<li> Free any array specific to the object on which is called;</li> | |
<li> Remove any event listener on chart objects;</li> | |
<li> Call super object's destroy method.</li> | |
</ol> | |
@method destroy | |
@return {null} to state that the object has been destroyed. | |
*/ | |
destroy: function(){ | |
//Removes all the data from the chart; | |
this.clearData(); | |
//Deletes all the elements from object's arrays | |
this.__data__.length = 0; | |
this.__maxVals__.length = 0; | |
this.__yScale__.length = 0; | |
this.__barsStrokeColors__.length = 0; | |
this.__barsFillColors__.length = 0; | |
this.__labelColors__.length = 0; | |
this.__labelsSize__.length = 0; | |
this.__labelsVisible__.length = 0; | |
//Removes DOM objects | |
this.__svgElement__.remove(); | |
this.__divElement__.remove(); | |
//Removes legend, if any | |
if (this.__legend__ && this.__legend__.destroy){ | |
this.__legend__.destroy(); | |
} | |
return null; | |
} | |
}; | |
// --------------------- PROTECTED METHODS --------------------------------------- | |
Object.defineProperty(basicBarChartSharedPrototype, "__getDatasetLength__", { | |
/** __getDatasetLength__() | |
Utility function to take account of the number of points currently added to the chart | |
@method __getDatasetLength__ | |
@protected | |
@return {Number} How many points are stored in the dataset right now. | |
*/ | |
value: function(){ | |
//INVARIANT: there will always be at least 1 element in __data__ array [assumed to avoid defensive programming] | |
return this.__data__[0].length; | |
}, | |
writable: false, | |
enumerable: false, | |
configurable:false | |
}); | |
Object.defineProperty(basicBarChartSharedPrototype, "__canAppendData__", { | |
/** __canAppendData__(newDataArray) | |
Checks that new data can be added to the chart (if the chart can represent only a limited number of points);<br> | |
<br> | |
<b>WARNING</b>: This function SHOULD be overriden in any class inheriting from the base class | |
in order to handle differents needs | |
@method __canAppendData__ | |
@protected | |
@param {Array} newDataArray [Mandatory] | |
The array of values that should be added; | |
@return {Array} The array of values that can still be added to the chart;<br> | |
If no other value can be added, return the empty list. | |
*/ | |
value: function(newDataArray){ | |
if (!Object.isArray(newDataArray)){ | |
return []; | |
} | |
//else, if the bar width still has a valid value, returns the input value, otherwise the empty list | |
return this.__getDatasetLength__() === 0 || this.getBarWidth(this.__xScale__) > 0 ? newDataArray : []; | |
}, | |
writable: false, | |
enumerable: false, | |
configurable:false | |
}); | |
basicBarChartSharedPrototype.addProtectedMethod("__redrawAll__", | |
/** __redrawAll__() | |
Redraws completely the whole chart, updating all the non-fixed attributes of the drawings. | |
@method __redrawAll__ | |
@since 0.2 | |
@protected | |
@return {undefined} Pseudo protected method called only internally, no need to return anything | |
*/ | |
function __redrawAll__(){ | |
//The whole chart must be updated | |
var dataSet, labelsSet; | |
var newDataLength = this.__getDatasetLength__() * this.__dataDim__; | |
//The max is recomputed every time to retain the ability to switch on the fly between scaling locally and globally | |
var max_val; | |
if (this.__scaleGlobally__){ | |
max_val = ChartUtils.fillArray(this.__maxVals__.max(), this.__dataDim__); | |
}else{ | |
max_val = this.__maxVals__; //Values aren't going to be modified, so we can just copy the reference | |
} | |
this.__xScale__.domain([0, newDataLength]); | |
for (var i=0; i < this.__dataDim__; i++){ | |
this.__yScale__[i].domain([0, max_val[i]]); | |
dataSet = this.__selectData__(this.__data__, i); | |
labelsSet = this.__selectLabels__(this.__data__, i); | |
this.__refreshDrawing__(dataSet, labelsSet, i, this.__xScale__, this.__yScale__[i]); | |
} | |
return ; | |
}); | |
Object.defineProperty(basicBarChartSharedPrototype, "__formatValue__", { | |
/** __formatValue__(value) | |
Checks that the value passed corresponds to the data format allowed for the current chart; | |
This function can be overriden in any class inheriting from the base class | |
in order to handle differents data formats (i.e. Objects or JSON). | |
@method __formatValue__ | |
@protected | |
@param {Array|Object} value [Mandatory] | |
The value to be tested; | |
@return {Array} <ul> | |
<li>An array with properly formatted values, each of whom | |
converted to float <=> value is correctly validated</li> | |
<li>null <-> Otherwise</li> | |
</ul> | |
*/ | |
value: function(value){ | |
if (Object.isArray(value)){ | |
if (value.length !== this.__dataDim__){ | |
//Invalid data; | |
return null; | |
} | |
for (var i=0; i < this.__dataDim__; i++){ | |
if (!Object.isNumber(value[i])){ | |
return null; | |
} | |
} | |
//At this point we can assume the value is valid | |
return value.map(parseFloat); | |
}else if (Object.isNumber(value) && this.__dataDim__ === 1){ | |
return [parseFloat(value)]; | |
}else{ | |
//Invalid value | |
return null; | |
} | |
}, | |
writable: false, | |
enumerable: false, | |
configurable:false | |
}); | |
Object.defineProperty(basicBarChartSharedPrototype, "__selectData__", { | |
/** __selectData__(data, index [, n]) | |
Returns the list of the svg elements used to represent data subcomponents | |
with the required index.<br> | |
I.e.: if data space is 3-dimensional (i.e. every point has 3 components) | |
__selectData__(data, 2) would select the svg elements representing | |
the 2nd component of every point in data | |
@method __selectData__ | |
@protected | |
@param {Array} data [Mandatory] | |
The dataset on which selection should be applied | |
@param {Number} index [Mandatory] | |
The index of the required component<br> | |
<b>INVARIANT</b>: to avoid defensive programming, | |
it is assumed 0 <= index < this.__dataDim__ | |
@param {Number} [n] [Optional] | |
The maximum number of elements to return; | |
@return {Object} The proper set of d3 elements. | |
*/ | |
value: function(data, index, n){ | |
if (Object.isUndefined(n)){ | |
return this.__chartArea__.selectAll("rect[index=data_"+index+"]").data(data[index]); | |
}else{ | |
return this.__chartArea__.selectAll("rect[index=data_"+index+"]").data(data[index].slice(0, n)); | |
} | |
}, | |
writable: false, | |
enumerable: false, | |
configurable:false | |
}); | |
Object.defineProperty(basicBarChartSharedPrototype, "__selectLabels__", { | |
/** __selectLabels__(data, index [, n]) | |
Returns the list of the svg elements used to draw the labels of | |
subcomponents of data with the required index.<br> | |
I.e.: if data space is 3-dimensional (i.e. every point has 3 components) | |
__selectLabels__(data, 3) would select the svg elements representing | |
the labels of the 3nd component of every point in data | |
@method __selectLabels__ | |
@protected | |
@param {Array} data [Mandatory] | |
The dataset on which selection should be applied; | |
@param {Number} index [Mandatory] | |
The index of the required component;<br> | |
<b>INVARIANT</b>: to avoid defensive programming, | |
it is assumed 0 <= index < this.__dataDim__ | |
@param {Number} [n] [Optional] | |
The maximum number of elements to return; | |
@return {Object} The proper set of d3 elements. | |
*/ | |
value: function(data, index, n){ | |
if (Object.isUndefined(n)){ | |
return this.__chartArea__.selectAll("text[index=data_"+index+"]").data(data[index]); | |
}else{ | |
return this.__chartArea__.selectAll("text[index=data_"+index+"]").data(data[index].slice(0, n)); | |
} | |
}, | |
writable: false, | |
enumerable: false, | |
configurable:false | |
}); | |
Object.defineProperty(basicBarChartSharedPrototype, "__drawNewData__", { | |
/** __drawNewData__(dataSet, labelsSet, dataIndex, xScale, yScale) | |
Called by appendData() to draw the newly added points in dataSet, once for | |
every data subcomponent.<br> | |
<br> | |
<b>WARNING</b>: if you inherit from this class you might want to override both | |
this method and __updateDrawing__ in order to obtain a custom chart. | |
@method __drawNewData__ | |
@protected | |
@param {Object} dataSet [Mandatory] | |
The set of svg elements created so far to represent the data;<br> | |
<b>WARNING</b>: this parameter should be generated by an appropriate | |
call to __selectData__; | |
@param {Object} labelsSet [Mandatory] | |
The set of svg elements created so far to represent the labels of the data;<br> | |
<b>WARNING</b>: this parameter should be generated by an appropriate | |
call to __selectLabels__; | |
@param {Number} dataIndex [Mandatory] | |
The index of the component of the data which is to be drawn; | |
@param {Object} xScale [Mandatory] | |
D3 scale object for X axis; | |
@param {Object} yScale [Mandatory] | |
D3 scale object for Y axis (specific to current component); | |
@return {undefined} | |
*/ | |
value: function(dataSet, labelsSet, dataIndex, xScale, yScale){ | |
var that = this; | |
var height = this.__getChartAreaHeight__(); | |
var barWidth = this.getBarWidth(xScale); | |
dataSet.enter().append("rect").attr("index", "data_" + dataIndex) | |
.attr("x", function(d, i){return xScale(i * that.__dataDim__ + dataIndex);}) | |
.attr("y", height) | |
.attr("width", barWidth) | |
.attr("height", 0) | |
.attr("fill", that.getBarsFillColor(dataIndex)) | |
.attr("stroke", that.getBarsStrokeColor(dataIndex)) | |
.attr("opacity", function(d){return that.__getBarOpacity__((0.0 + yScale(d)) / height);}); | |
if (that.areLabelsVisible(dataIndex)){ | |
labelsSet.enter().append("text").attr("index", "data_" + dataIndex) | |
.text(function(d) {return that.__makeLabel__(d, dataIndex);}) | |
.attr("text-anchor", "middle") | |
.attr("x", function(d, i){return xScale(i * that.__dataDim__ + dataIndex) + barWidth / 2;}) | |
.attr("y", height) | |
.attr("font-family", "sans-serif") | |
.attr("font-size", that.getLabelsSize(dataIndex)) | |
.attr("fill", that.getLabelColor(dataIndex)) | |
.attr("opacity", function(){ //Show the label only if it fits the bar | |
if (this.getComputedTextLength() <= barWidth){ | |
return 1; | |
}else{ | |
return 0; | |
} | |
}); | |
}else{ | |
labelsSet.remove(); | |
} | |
return; //(Pseudo)Private method, no need to return this | |
}, | |
writable: false, | |
enumerable: false, | |
configurable:false | |
}); | |
Object.defineProperty(basicBarChartSharedPrototype, "__updateDrawing__", { | |
/** __updateDrawing__(dataSet, labelsSet, dataIndex, xScale, yScale) | |
Called by appendData() to update drawing of the points in dataSet, once for | |
every data subcomponent.<br> | |
After new data is inserted by __drawNewData__, appendData performs adjustments | |
to accomodate for scale change or shift in the drawing due to time, and this | |
function takes care of updating and fixing the chart representation.<br> | |
<br> | |
<b>WARNING</b>: if you inherit from this class you might want to override both | |
this method and __drawNewData__ in order to obtain a custom chart. | |
@method __updateDrawing__ | |
@protected | |
@param {Object} dataSet [Mandatory] | |
The set of svg elements created so far to represent the data;<br> | |
<b>WARNING</b>: this parameter should be generated by an appropriate | |
call to __selectData__; | |
@param {Object} labelsSet [Mandatory] | |
The set of svg elements created so far to represent the labels of the data;<br> | |
<b>WARNING</b>: this parameter should be generated by an appropriate | |
call to __selectLabels__; | |
@param {Number} dataIndex [Mandatory] | |
The index of the component of the data which is to be drawn; | |
@param {Object} xScale [Mandatory] | |
D3 scale object for X axis; | |
@param {Object} yScale [Mandatory] | |
D3 scale object for Y axis (specific to current component). | |
@return {undefined} | |
*/ | |
value: function(dataSet, labelsSet, dataIndex, xScale, yScale){ | |
var that = this; | |
var height = this.__getChartAreaHeight__(); | |
var barWidth = this.getBarWidth(xScale); | |
dataSet.transition()//.delay(250) | |
.attr("x", function(d, i){return xScale(i * that.__dataDim__ + dataIndex);}) | |
.attr("y", function(d){return height - yScale(d);}) | |
.attr("width", barWidth) | |
.attr("height", function(d){return yScale(d);}) | |
.attr("opacity", function(d){return that.__getBarOpacity__((0.0 + yScale(d)) / height);}); | |
if (that.areLabelsVisible(dataIndex)){ | |
labelsSet.transition()//.delay(250) | |
.text(function(d) {return that.__makeLabel__(d, dataIndex);}) | |
.attr("x", function(d, i){return xScale(i * that.__dataDim__ + dataIndex) + barWidth / 2;}) | |
.attr("y", function(d){return height - yScale(d) + 15 ;}) | |
.attr("opacity", function(){ //Show the label only if it fits the bar | |
if (this.getComputedTextLength() <= barWidth){ | |
return 1; | |
}else{ | |
return 0; | |
} | |
}); | |
}else{ | |
labelsSet.remove(); | |
} | |
return; //(Pseudo)Private method, no need to return this | |
}, | |
writable: false, | |
enumerable: false, | |
configurable:false | |
}); | |
basicBarChartSharedPrototype.addProtectedMethod("__refreshDrawing__", | |
/** __refreshDrawing__(dataSet, labelsSet, dataIndex, xScale, yScale) | |
Called by __redrawAll__() to redraw all the data-related drawings, once for | |
every data subcomponent.<br> | |
The difference with __updateDrawing__ is that the latter is incremental with respect to | |
__drawNewData__ and updates only the properties used to provide animations of the drawing, | |
while this method redraws from scratch the data. | |
<br> | |
<b>WARNING</b>: if you inherit from this class you might want to override both | |
this method following __updateDrawing__ behaviour in order to obtain a custom chart. | |
@method __refreshDrawing__ | |
@since 0.2 | |
@protected | |
@param {Object} dataSet [Mandatory] | |
The set of svg elements created so far to represent the data;<br> | |
<b>WARNING</b>: this parameter should be generated by an appropriate | |
call to __selectData__; | |
@param {Object} labelsSet [Mandatory] | |
The set of svg elements created so far to represent the labels of the data;<br> | |
<b>WARNING</b>: this parameter should be generated by an appropriate | |
call to __selectLabels__; | |
@param {Number} dataIndex [Mandatory] | |
The index of the component of the data which is to be drawn; | |
@param {Object} xScale [Mandatory] | |
D3 scale object for X axis; | |
@param {Object} yScale [Mandatory] | |
D3 scale object for Y axis (specific to current component); | |
@return {undefined} | |
*/ | |
function __refreshDrawing__(dataSet, labelsSet, dataIndex, xScale, yScale){ | |
var that = this; | |
var height = this.__getChartAreaHeight__(); | |
var barWidth = this.getBarWidth(xScale); | |
dataSet.transition() | |
.attr("x", function(d, i){return xScale(i * that.__dataDim__ + dataIndex);}) | |
.attr("y", function(d){return height - yScale(d);}) | |
.attr("width", barWidth) | |
.attr("height", function(d){return yScale(d);}) | |
.attr("fill", that.getBarsFillColor(dataIndex)) | |
.attr("stroke", that.getBarsStrokeColor(dataIndex)) | |
.attr("opacity", function(d){return that.__getBarOpacity__((0.0 + yScale(d)) / height);}); | |
if (that.areLabelsVisible(dataIndex)){ | |
labelsSet.transition() | |
.text(function(d) {return that.__makeLabel__(d, dataIndex);}) | |
.attr("text-anchor", "middle") | |
.attr("x", function(d, i){return xScale(i * that.__dataDim__ + dataIndex) + barWidth / 2;}) | |
.attr("y", function(d){return height - yScale(d) + 15 ;}) | |
.attr("font-family", "sans-serif") | |
.attr("font-size", that.getLabelsSize(dataIndex)) | |
.attr("fill", that.getLabelColor(dataIndex)) | |
.attr("opacity", function(){ //Show the label only if it fits the bar | |
if (this.getComputedTextLength() <= barWidth){ | |
return 1; | |
}else{ | |
return 0; | |
} | |
}); | |
}else{ | |
labelsSet.remove(); | |
} | |
return; //(Pseudo)Private method, no need to return this | |
}); | |
Object.defineProperty(basicBarChartSharedPrototype, "__onClearData__", { | |
/** __onClearData__(n) | |
Takes care of the remaining details related to the removal of part of the values from the chart, | |
based on to the particular chart needs.<br> | |
<br> | |
<b>WARNING</b>: Inherited objects MIGHT NEED to override this function. | |
@method __onClearData__ | |
@protected | |
@param {Number} [n] [Mandatory] | |
Must be a positive Integer, or a value that | |
can be converted to a positive Integer; | |
Number of elements removed from the chart | |
@return {undefined} | |
*/ | |
value: function(n){ | |
//Do nothing: for this object no special action required (but it's required to be in the class interface) | |
return; //(Pseudo)Private method, no need to return this | |
}, | |
writable: false, | |
enumerable: false, | |
configurable:false | |
}); | |
Object.defineProperty(basicBarChartSharedPrototype, "__clearDrawing__", { | |
/** __clearDrawing__(dataSet, labelsSet) | |
Removes the svg objects related to the data cleared by the caller (clearData). | |
@method __clearDrawing__ | |
@protected | |
@param {Object} dataSet [Mandatory] | |
List of drawing objects (default: rects) representing data | |
@param {Object} labelsSet [Mandatory] | |
List of labels related to data removed | |
@return {undefined} | |
*/ | |
value: function(dataSet, labelsSet){ | |
dataSet.remove(); | |
labelsSet.remove(); | |
return; //(Pseudo)Private method, no need to return this | |
}, | |
writable: false, | |
enumerable: false, | |
configurable:false | |
}); | |
//HORIZONTAL AXE | |
basicBarChartSharedPrototype.addProtectedMethod("__clearHorizontalAxe__", | |
/** __clearHorizontalAxe__() | |
* Clear all the drawings related to the horizontal axe. | |
* | |
* @method __clearHorizontalAxe__ | |
* @since 0.2 | |
* @protected | |
* @return {undefined} | |
* @throws {TypeError} If attribute __horizontalAxe__ is invalid. | |
*/ | |
function __clearHorizontalAxe__(){ | |
if (this.__horizontalAxe__ === null){ | |
return ; | |
} | |
//Clear previous axes | |
var axe = this.__horizontalAxe__.svgElement; | |
axe.selectAll("line").remove(); | |
axe.selectAll("text").remove(); | |
axe.selectAll("svg").remove(); | |
return ; | |
}); | |
basicBarChartSharedPrototype.addProtectedMethod("__drawHorizontalAxeTitle__", | |
/** __drawHorizontalAxeTitle__(axe, width, x, y) | |
* | |
* Draws the horizontal axe's title. | |
* | |
* @method __drawHorizontalAxeTitle__ | |
* @since 0.2 | |
* @protected | |
* @param {Object} axe [Mandatory] | |
* The object storing horizontal axe's properties. | |
* @param {Number} width [Mandatory] | |
The width of the title label (tipically the same width as the axe). | |
* @param {Number} x [Mandatory] | |
* The x coordinate for the title label. | |
* @param {Number} y [Mandatory] | |
* The y coordinate for the title label. | |
* @return {undefined} | |
* @throws {TypeError} If axe is invalid. | |
*/ | |
function __drawHorizontalAxeTitle__(axe, width, x, y){ | |
axe.svgElement | |
.append("svg") | |
.attr("x", x) | |
.attr("y", y) | |
.attr("width", width) | |
.attr("height", axe.titleSize + LEGEND_MARGIN); | |
/* No need to overwrite labels for horizontal axe | |
axe.svgElement.select("svg") | |
.append("rect") | |
.attr("fill", this.getOuterBackgroundColor()) | |
.attr("x", 0) | |
.attr("y", 0) | |
.attr("width", width) | |
.attr("height", axe.titleSize + LEGEND_MARGIN); */ | |
axe.svgElement.select("svg") | |
.append("text") | |
.attr("type", "axe_title") | |
.text(axe.title) | |
.attr("text-anchor", "middle") | |
.attr("x", width / 2)// LEGEND_MARGIN / 2) | |
.attr("y", axe.titleSize) | |
.attr("fill", axe.titleColor) | |
.attr("font-family", "sans-serif") | |
.attr("font-size", axe.titleSize); | |
}); | |
/** __drawHorizontalAxe__([title, titleSize, titleColor]) | |
* | |
* Draws the horizontal axe. | |
* | |
* @method __drawHorizontalAxe__ | |
* @since 0.2 | |
* @protected | |
* @return {undefined} | |
* @throws {TypeError} If attribute __horizontalAxe__ is invalid. | |
*/ | |
basicBarChartSharedPrototype.addProtectedMethod("__drawHorizontalAxe__", | |
function __drawHorizontalAxe__(){ | |
var axe = this.__horizontalAxe__; | |
if (axe === null){ | |
return ; | |
} | |
var width = axe.svgElement.attr("width"), | |
height = axe.svgElement.attr("height"), | |
notchStep = width / (axe.notches + 1); | |
switch (axe.side){ | |
case "bottom": | |
//Notches | |
axe.svgElement.selectAll("line").data(d3.range(axe.notches + 2)) | |
.enter() | |
.append("svg:line") | |
.attr("x1", function(d,i){ | |
return i * notchStep; | |
}) | |
.attr("y1", 0) | |
.attr("x2", function(d,i){ | |
return i * notchStep; | |
}) | |
.attr("y2", NOTCH_LINE_LENGTH) | |
.attr("stroke", axe.color) | |
.attr("stroke-width", axe.lineWidth); | |
//Horizontal line | |
axe.svgElement.append("svg:line") | |
.attr("x1", 0) | |
.attr("y1", 0) | |
.attr("x2", width) | |
.attr("y2", 0) | |
.attr("stroke", axe.color) | |
.attr("stroke-width", axe.lineWidth); | |
//Axe Title | |
if (!Object.isUndefined(axe.title)){ | |
this.__drawHorizontalAxeTitle__(axe, width, 0, height - axe.titleSize - LEGEND_MARGIN); | |
} | |
break; | |
case "top": | |
//Notches | |
axe.svgElement.selectAll("line").data(d3.range(axe.notches + 2)) | |
.enter() | |
.append("svg:line") | |
.attr("x1", function(d,i){ | |
return i * notchStep; | |
}) | |
.attr("y1", height - NOTCH_LINE_LENGTH) | |
.attr("x2", function(d,i){ | |
return i * notchStep; | |
}) | |
.attr("y2", height) | |
.attr("stroke", axe.color) | |
.attr("stroke-width", axe.lineWidth); | |
//Vertical line | |
axe.svgElement.append("svg:line") | |
.attr("x1", 0) | |
.attr("y1", height) | |
.attr("x2", width) | |
.attr("y2", height) | |
.attr("stroke", axe.color) | |
.attr("stroke-width", axe.lineWidth); | |
//Axe Title | |
if (!Object.isUndefined(axe.title)){ | |
this.__drawHorizontalAxeTitle__(axe, width, 0, LEGEND_MARGIN); | |
} | |
break; | |
default: | |
throw new TypeError("__verticalAxe__ object is invalid"); | |
} | |
}); | |
//VERTICAL AXE | |
basicBarChartSharedPrototype.addProtectedMethod("__clearVerticalAxe__", | |
/** __clearVerticalAxe__() | |
* Clear all the drawings related to the vertical axe. | |
* | |
* @method __clearVerticalAxe__ | |
* @since 0.2 | |
* @protected | |
* @return {undefined} | |
* @throws {TypeError} If attribute __verticalAxe__ is invalid. | |
*/ | |
function __clearVerticalAxe__(){ | |
if (this.__verticalAxe__ === null){ | |
return ; | |
} | |
//Clear previous axes | |
var axe = this.__verticalAxe__.svgElement; | |
axe.selectAll("line").remove(); | |
axe.selectAll("text").remove(); | |
axe.selectAll("svg").remove(); | |
return ; | |
}); | |
basicBarChartSharedPrototype.addProtectedMethod("__drawVerticalAxeTitle__", | |
/** __drawVerticalAxeTitle__(axe, height, x, y, textAngle, textX ,textPivot) | |
* | |
* Draws the vertical axe's title. | |
* | |
* @method __drawVerticalAxeTitle__ | |
* @since 0.2 | |
* @protected | |
* @param {Object} axe [Mandatory] | |
* The object storing vertical axe's properties. | |
* @param {Number} height [Mandatory] | |
The height of the title label (tipically the same height as the axe). | |
* @param {Number} x [Mandatory] | |
* The x coordinate for the title label. | |
* @param {Number} y [Mandatory] | |
* The y coordinate for the title label. | |
* @param {Number} textAngle [Mandatory] | |
* The textAngle for the rotation of the title label.<br> | |
* It can be + or - 90 degrees: together with x, y, textX and textPivot parameters, | |
* this allows to reuse this method for both left and right axes. | |
* @param {Number} textX [Mandatory] | |
* Internally computed.<br> | |
* X position of the axe's title. | |
* @param {Number} textPivot [Mandatory] | |
* Internally computed.<br> | |
* X position of the pivot point around whom the axe title has to be rotated. | |
* @return {undefined} | |
* @throws {TypeError} If axe is invalid. | |
*/ | |
function __drawVerticalAxeTitle__(axe, height, x, y, textAngle, textX, textPivot){ | |
axe.svgElement | |
.append("svg") | |
.attr("x", x) | |
.attr("y", 0) | |
.attr("width", axe.titleSize + LEGEND_MARGIN) | |
.attr("height", height); | |
axe.svgElement.select("svg") | |
.append("rect") | |
.attr("fill", this.getOuterBackgroundColor()) | |
.attr("x", 0) | |
.attr("y", 0) | |
.attr("width", axe.titleSize) | |
.attr("height", height); | |
axe.svgElement.select("svg") | |
.append("text") | |
.attr("type", "axe_title") | |
.text(axe.title) | |
.attr("text-anchor", "middle") | |
.attr("x", textX)// LEGEND_MARGIN / 2) | |
.attr("y", y) | |
.attr("transform", "rotate(" + textAngle + " " + textPivot + "," + y + ")")//(LEGEND_MARGIN/2) | |
.attr("fill", axe.titleColor) | |
.attr("font-family", "sans-serif") | |
.attr("font-size", axe.titleSize); | |
}); | |
/** __drawVerticalAxe__([title, titleSize, titleColor]) | |
* | |
* Draws the vertical axe. | |
* | |
* @method __drawVerticalAxe__ | |
* @since 0.2 | |
* @protected | |
* @return {undefined} | |
* @throws {TypeError} If attribute __verticalAxe__ is invalid. | |
*/ | |
basicBarChartSharedPrototype.addProtectedMethod("__drawVerticalAxe__", | |
function __drawVerticalAxe__(){ | |
var axe = this.__verticalAxe__; | |
if (axe === null){ | |
return ; | |
} | |
var width = axe.svgElement.attr("width"), | |
height = axe.svgElement.attr("height"), | |
notchStep = height / (axe.notches + 1); | |
switch (axe.side){ | |
case "right": | |
//Notches | |
axe.svgElement.selectAll("line").data(d3.range(axe.notches + 2)) | |
.enter() | |
.append("svg:line") | |
.attr("x1", 0) | |
.attr("y1", function(d,i){ | |
return i * notchStep; | |
}) | |
.attr("x2", NOTCH_LINE_LENGTH) | |
.attr("y2", function(d,i){ | |
return i * notchStep; | |
}) | |
.attr("stroke", axe.color) | |
.attr("stroke-width", axe.lineWidth); | |
//Vertical line | |
axe.svgElement.append("svg:line") | |
.attr("x1", 0) | |
.attr("y1", 0) | |
.attr("x2", 0) | |
.attr("y2", height) | |
.attr("stroke", axe.color) | |
.attr("stroke-width", axe.lineWidth); | |
//Axe Title | |
if (!Object.isUndefined(axe.title)){ | |
this.__drawVerticalAxeTitle__(axe, height, width - axe.titleSize - LEGEND_MARGIN, height / 2, | |
90, LEGEND_MARGIN / 2, LEGEND_MARGIN / 2); | |
} | |
break; | |
case "left": | |
//Notches | |
axe.svgElement.selectAll("line").data(d3.range(axe.notches + 2)) | |
.enter() | |
.append("svg:line") | |
.attr("x1", width - NOTCH_LINE_LENGTH) | |
.attr("y1", function(d,i){ | |
return i * notchStep; | |
}) | |
.attr("x2", width) | |
.attr("y2", function(d,i){ | |
return i * notchStep; | |
}) | |
.attr("stroke", axe.color) | |
.attr("stroke-width", axe.lineWidth); | |
//Vertical line | |
axe.svgElement.append("svg:line") | |
.attr("x1", width) | |
.attr("y1", 0) | |
.attr("x2", width) | |
.attr("y2", height) | |
.attr("stroke", axe.color) | |
.attr("stroke-width", axe.lineWidth); | |
//Axe Title | |
if (!Object.isUndefined(axe.title)){ | |
this.__drawVerticalAxeTitle__(axe, height, 1, height / 2, -90, 0 , axe.titleSize); | |
} | |
break; | |
default: | |
throw new TypeError("__verticalAxe__ object is invalid"); | |
} | |
}); | |
Object.defineProperty(basicBarChartSharedPrototype, "__getBarOpacity__", { | |
/** __getBarOpacity__(val) | |
Computes and return the suggested value for the opacity of the bar | |
drawn to represent a certain value. | |
@method __getBarOpacity__ | |
@protected | |
@param {Number} val [Mandatory] | |
The value to be represented;<br> | |
Accepts only normalized values (scaled between 0 and 1).<br> | |
<b>INVARIANT</b>: to avoid defensive programming, | |
it is assumed 0 <= val <=1 | |
@return {Number} The opacity to apply to the value representation in the chart. | |
*/ | |
value: function(val){ | |
return 0.25 + val * 0.75; | |
}, | |
writable: false, | |
enumerable: false, | |
configurable:false | |
}); | |
basicBarChartSharedPrototype.addProtectedMethod("__makeLabel__", | |
/** __makeLabel__(val [index, forceAbbreviate]) | |
Converts an input number to | |
@method __makeLabel__ | |
@since 0.2 | |
@protected | |
@param {Number} val [Mandatory] | |
The value that must be used in the label. | |
@param {Number} [index=0] [Optional] | |
The index of the subcomponent of the data. | |
@param {Boolean} [forceAbbreviate=false] [Optional] | |
Should the label be coercefully abbreviated? | |
@return {String} The properly formatted text for the label. | |
*/ | |
function(val, index, forceAbbreviate){ | |
//INVARIANT: if passed, index is a non-negative integer in the range 0..__dataDim__ | |
// (We avoid defensive programming and omit checks) | |
try{ | |
if ((forceAbbreviate || (!Object.isUndefined(index) && | |
this.__abbreviatedLabels__[index])) && | |
Object.isNumber(val)){ | |
return ChartUtils.abbreviateNumber(val); | |
}else{ | |
return val; | |
} | |
}catch(e){ | |
//console.log(e); | |
return val; | |
} | |
}); | |
basicBarChartSharedPrototype.addProtectedMethod("__updateBackground__", | |
/** __updateBackground__() | |
Called by __drawNewData__() to redraw the background properly;<br> | |
<br> | |
<b>WARNING</b>: if you inherit from this class you might want to override | |
this method as well as __drawNewData__ and __updateDrawing__ | |
in order to obtain a custom chart. | |
@method __updateBackground__ | |
@protected | |
@return {undefined} | |
*/ | |
function(){ | |
//Nothing to do for this class (added to have a coherent interface) | |
return; //(Pseudo)Private method, no need to return this | |
}); | |
basicBarChartSharedPrototype.addProtectedMethod("__redrawInnerBackground__", | |
/** __redrawInnerBackground__() | |
Properly redraws the background of the main chart area <br> | |
<br> | |
<b>WARNING</b>: if you inherit from this class you might want to override | |
this method to reflect its expected behaviour. | |
@method __redrawInnerBackground__ | |
@protected | |
@return {undefined} | |
*/ | |
function(){ | |
this.__chartArea__.select("#innerBackground").attr("fill", this.getInnerBackgroundColor()); | |
return; //(Pseudo)Private method, no need to return this | |
}); | |
basicBarChartSharedPrototype.addProtectedMethod("__redrawOuterBackground__", | |
/** __redrawOuterBackground__() | |
Properly redraws the background of the outer chart area <br> | |
<br> | |
<b>WARNING</b>: if you inherit from this class you might want to override | |
this method to reflect its expected behaviour. | |
@method __redrawOuterBackground__ | |
@protected | |
@return {undefined} | |
*/ | |
function(){ | |
this.__svgElement__.select("#outerBackground").attr("fill", this.getOuterBackgroundColor()); | |
//For both axes, must ensure that the title will be repainted accordingly | |
try{ //Try, in case it is null | |
this.__verticalAxe__.svgElement.selectAll("rect").attr("fill", this.getOuterBackgroundColor()); | |
}catch(e){ | |
//Nothing to do | |
} | |
try{ //Try, in case it is null | |
this.__horizontalAxe__.svgElement.selectAll("rect").attr("fill", this.getOuterBackgroundColor()); | |
}catch(e){ | |
//Nothing to do | |
} | |
return; //(Pseudo)Private method, no need to return this | |
}); | |
basicBarChartSharedPrototype.addProtectedMethod("__redrawInnerBorder__", | |
/** __redrawInnerBorder__([border]) | |
Properly redraws the border of the inner chart area. | |
<br> | |
<b>WARNING</b>: if you inherit from this class you might want to override | |
this method to reflect its expected behaviour. | |
@method __redrawInnerBorder__ | |
@protected | |
@param {Object} [border=__innerBorder__] [Optional] | |
An object summarizing all the border properties: | |
<ul> | |
<li><b>fill</b>: The color of the border;</li> | |
<li><b>width</b>: The width of the border line, in pixels;</li> | |
<li><b>dash</b>: The dash pattern of the line</li> | |
</ul> | |
By default, if border is omitted, the chart's __innerBorder__ attribute is used. | |
@return {undefined} | |
*/ | |
function(border){ | |
if (Object.isUndefined(border)){ | |
border = this.__innerBorder__; | |
} | |
//INVARIANT: border is a valid Object | |
// Protected function: no checking to avoid defensive programming | |
this.__chartArea__.select("#innerBackground") | |
.attr("stroke", border.fill) | |
.attr("stroke-width", border.width) | |
.attr("stroke-dasharray", border.dash); | |
return; //(Pseudo)Private method, no need to return this | |
}); | |
basicBarChartSharedPrototype.addProtectedMethod("__redrawOuterBorder__", | |
/** __redrawOuterBorder__([border]) | |
Properly redraws the border of the outer chart area. | |
<br> | |
<b>WARNING</b>: if you inherit from this class you might want to override | |
this method to reflect its expected behaviour. | |
@method __redrawOuterBorder__ | |
@protected | |
@param {Object} [border=__outerBorder__] [Optional] | |
An object summarizing all the border properties: | |
<ul> | |
<li><b>fill</b>: The color of the border;</li> | |
<li><b>width</b>: The width of the border line, in pixels;</li> | |
<li><b>dash</b>: The dash pattern of the line</li> | |
</ul> | |
By default, if border is omitted, the chart's __outerBorder__ attribute is used. | |
@return {undefined} | |
*/ | |
function(border){ | |
if (Object.isUndefined(border)){ | |
border = this.__outerBorder__; | |
} | |
//INVARIANT: border is a valid Object | |
// Protected function: no checking to avoid defensive programming | |
this.__svgElement__.select("#outerBackground") | |
.attr("stroke", border.fill) | |
.attr("stroke-width", border.width) | |
.attr("stroke-dasharray", border.dash); | |
return; //(Pseudo)Private method, no need to return this | |
}); | |
basicBarChartSharedPrototype.addProtectedMethod("__getWidth__", | |
/** __getWidth__() | |
Return the width of the drawing area for the chart. | |
@method __getWidth__ | |
@protected | |
@return {Number} The total width of the chart. | |
*/ | |
function(){ | |
return parseInt(this.__svgElement__.attr("width"), 10); | |
}); | |
basicBarChartSharedPrototype.addProtectedMethod("__getHeight__", | |
/** __getHeight__() | |
Return the height of the drawing area for the chart. | |
@method __getHeight__ | |
@protected | |
@return {Number} The total height of the chart. | |
*/ | |
function(){ | |
return parseInt(this.__svgElement__.attr("height"), 10); | |
}); | |
basicBarChartSharedPrototype.addProtectedMethod("__getChartAreaWidth__", | |
/** __getChartAreaWidth__() | |
Return the width of the drawing area for the chart. | |
@method __getChartAreaWidth__ | |
@protected | |
@return {Number} The width of the drawing area for the chart. | |
*/ | |
function(){ | |
return parseInt(this.__chartArea__.attr("width"), 10); | |
}); | |
basicBarChartSharedPrototype.addProtectedMethod("__getChartAreaHeight__", | |
/** __getChartAreaHeight__() | |
Return the height of the drawing area for the chart. | |
@method __getChartAreaHeight__ | |
@protected | |
@return {Number} The height of the drawing area for the chart. | |
*/ | |
function(){ | |
return parseInt(this.__chartArea__.attr("height"), 10); | |
}); | |
basicBarChartSharedPrototype.addProtectedMethod("__decodeCSSMultivalueString__", | |
/** __decodeCSSMultivalueString__(str, undefinedOnMiss) | |
@method __decodeCSSMultivalueString__ | |
@protected | |
@param {String} str [Mandatory] | |
A string representing a CSS property with 4 possible subfields, as for margins or border-width: | |
<b>top, right, bottom and left</b>.<br> | |
Unless undefinedOnMiss is passed and it's truthy, the format of the string | |
is the same as for CSS margin; it can therefore be one of the following: | |
<ul> | |
<li>""<br>No margin is set</li> | |
<li>"top-bottom-right-left;"<br>All 4 subfields are set to the same value</li> | |
<li>"top-bottom right-left;"<br>Top and bottom and right and left subfields have the same value</li> | |
<li>"top right-left bottom;"<br>right and left subfield are set to the same value</li> | |
<li>"top right bottom left;"<br>All 4 subfields are set separately</li> | |
</ul> | |
@param {Boolean} defaultValue [Optional] | |
If it is not passed or it's false, the standard CSS format specification for | |
multivalue string attributes will be used, as described above. | |
Otherwise, if less than 4 values are provided in the string, they are assigned | |
to subfields in the order top, right, bottom, left, while for the | |
fields for whom a value isn't explicitely inserted, a defaultValue is used. | |
@return {Object}: An object with values for top, right, bottom and left subfields. | |
@throws {Illegal Argument Exception}, if the input string contains more than four values. | |
@throws {Illegal Argument Exception}, if the input string contains no value, | |
and a default value is not supplied either. | |
*/ | |
function __decodeCSSMultivalueString__(str, defaultValue){ | |
var values = str.match(/(\d+(px)?\s+)|(\d+(px)?\s*;$)/g); //Extract all integers from string | |
var result, val, i; | |
var keys = ["top", "right", "bottom", "left"]; | |
if (!values){ | |
if (Object.isUndefined(defaultValue)){ | |
throw "Illegal Arguments: either at least one value or a default value MUST be supplied"; | |
} | |
result = {}; | |
if (!Object.isUndefined(defaultValue)){ | |
for (i=0; i < 4; i++){ | |
result[keys[i]] = defaultValue; | |
} | |
} | |
return result; | |
}else{ | |
values = values.map(function(s){return parseInt(s,10);}); | |
} | |
if (!Object.isUndefined(defaultValue)){ | |
if (values.length > 4){ | |
throw "Illegal Argument Exception: CSS string contains too many values."; | |
} | |
result = {}; | |
for (i = 0; i < values.length; i++){ | |
result[keys[i]] = values[i]; | |
} | |
for (; i < 4; i++){ | |
result[keys[i]] = defaultValue; | |
} | |
}else{ | |
switch (values.length){ | |
case 0: | |
result = {}; | |
break; | |
case 1: | |
val = values[0]; | |
result = {"top": val, "right":val, "bottom": val, "left": val}; | |
break; | |
case 2: | |
result = {"top": values[0], "right": values[1], "bottom": values[0], "left": values[1]}; | |
break; | |
case 3: | |
result = {"top": values[0], "right": values[1], "bottom": values[2], "left": values[1]}; | |
break; | |
case 4: | |
result = {"top": values[0], "right": values[1], "bottom": values[2], "left": values[3]}; | |
break; | |
default: //More than 4 values defined | |
throw "Illegal Argument Exception: CSS string contains too many values."; | |
} | |
} | |
return result; | |
}); | |
basicBarChartSharedPrototype.addProtectedMethod("__encodeCSSMultivalueString__", | |
/** __encodeCSSMultivalueString__(str, undefinedOnMiss) | |
@method __encodeCSSMultivalueString__ | |
@protected | |
@param {Object} obj [Mandatory] | |
An object storing the values for the property.<br> | |
Fields top, right, bottom, left are checked in order: as soon as | |
one is missing, no more data will be added to the string | |
@return {String}: A string with a proper representation of the property. | |
@throws {Illegal Argument Exception}, if the input string contains more than four values. | |
*/ | |
function __encodeCSSMultivalueString__(obj){ | |
var values = []; | |
var i, keys = ["top", "right", "bottom", "left"]; | |
for (i=0; i < 4; i++){ | |
try{ | |
values.push(obj[keys[i]]); | |
}catch(e){ | |
break; | |
} | |
} | |
if (values.length > 0){ | |
return values.join(" ") + ";"; //Need to add a final semicolon | |
}else{ | |
return ""; | |
} | |
}); | |
basicBarChartSharedPrototype.addProtectedMethod("__updateAxes__", | |
/** __updateAxes__(yScale) | |
Called by __updateDrawing__() to update the labels of the vertical axe | |
when vertical scale changes;<br> | |
<br> | |
<b>WARNING</b>: if you inherit from this class you might want to override | |
this method as well as __drawNewData__ and __updateDrawing__ | |
in order to obtain a custom chart. | |
@method __updateAxes__ | |
@protected | |
@chainable | |
@param {Object} yScale [Mandatory] | |
D3 scale object for Y axis; | |
@return {Object} The current chart object, to allow for method chaining. | |
*/ | |
function __updateAxes__(yScale){ | |
var vAxe = this.__verticalAxe__; | |
var i, notches; | |
var axeLabels, m, | |
anchor, | |
labels, | |
x, | |
height, | |
that = this; | |
//VERTICAL AXE | |
if (vAxe){ | |
if (this.__scaleGlobally__ || this.__dataDim__ === 1){ | |
//If data is drawn with a global scaling, or there is only one subcomponent, | |
//then Vertical axe CAN BE DRAWN | |
m = this.__maxVals__.max(); | |
notches = vAxe.notches; | |
axeLabels = [m]; | |
for (i = notches; i > 0; i--){ | |
axeLabels.push(m * i / (notches + 1)); | |
} | |
axeLabels.push(0); | |
}else{ | |
//If data is drawn with local scaling for each component | |
//then Vertical axe WOULD HAVE NO MEANING | |
axeLabels = []; | |
} | |
labels = vAxe.svgElement.selectAll("text[type=axe_label]").data(axeLabels); | |
height = vAxe.svgElement.attr("height"); | |
if (vAxe.side === "right"){ | |
x = NOTCH_LINE_LENGTH; | |
anchor = "start"; | |
}else{ | |
x = vAxe.svgElement.attr("width") - NOTCH_LINE_LENGTH; | |
anchor = "end"; | |
} | |
labels.exit().remove(); | |
labels.enter().insert("text", "svg") //Insert before svg block | |
.attr("type", "axe_label") | |
.attr("text-anchor", anchor) | |
.attr("font-family", "sans-serif") | |
.attr("font-size", vAxe.labelSize); | |
labels.text(function(d){return that.__makeLabel__(d, undefined, true);}) | |
.attr("x", x) | |
.attr("y", function(d){return Math.max(vAxe.labelSize *0.75, | |
Math.min(height, height - yScale(d) + vAxe.labelSize * 0.375));}) | |
.attr("fill", vAxe.color); | |
} | |
return; //(Pseudo)Private method, no need to return this | |
}); | |
basicBarChartSharedPrototype.addProtectedMethod("__initAxes__", | |
/** __initAxes__(width, height) | |
Inits the 4 axes surrounding the chart main area according to the specific chart type.<br> | |
<b>WARNING</b>: This method SHOULD be overridden in any inheriting class.<br> | |
This method sets only those properties of the four axes that are peculiar to this chart | |
and that can be set: | |
<ul> | |
<li>For top and bottom axes, only height can be set (their width is the same as the svg containing element)</li> | |
<li>For left and right axes, only width can be set (their height is the same as the main chart area)</li> | |
</ul> | |
@method __initAxes__ | |
@protected | |
@param {Number} width [Mandatory] | |
The desired width for the chart (<b>can't be changed later</b>)<br> | |
Can be any value that is or can be converted to a positive integer. | |
@param {Number} height [Mandatory] | |
The desired height for the chart (<b>can't be changed later</b>)<br> | |
Can be any value that is or can be converted to a positive integer. | |
@return {undefined} | |
*/ | |
function(width, height){ | |
this.__svgElement__.insert("rect", "#chart_area") //Insert before the chart main area | |
.attr("id","outerBackground") | |
.attr("width", width) | |
.attr("height", height) | |
.attr("x", 0) | |
.attr("y", 0); | |
var chartWidth = width - this.__margins__.left - this.__margins__.right - 2, | |
chartHeight = height - this.__margins__.top - this.__margins__.bottom - 2; | |
//Top axe | |
this.__axeTop__.attr("width", chartWidth) | |
.attr("height", this.__margins__.top) | |
.attr("x", this.__margins__.left) | |
.attr("y", 0); | |
//Right Axe | |
this.__axeRight__.attr("width", this.__margins__.right) | |
.attr("height", chartHeight) | |
.attr("x", width - this.__margins__.right) | |
.attr("y", this.__margins__.top + 1); | |
//Bottom axe | |
this.__axeBottom__.attr("width", chartWidth) | |
.attr("height", this.__margins__.bottom) | |
.attr("x", this.__margins__.left) | |
.attr("y", height - this.__margins__.bottom); | |
//Left Axe | |
this.__axeLeft__.attr("width", this.__margins__.left) | |
.attr("height", chartHeight) | |
.attr("x", 0) | |
.attr("y", this.__margins__.top + 1); | |
//Main Chart | |
this.__chartArea__.attr("width", chartWidth) | |
.attr("height", chartHeight) | |
.attr("x", this.__margins__.left + 1) | |
.attr("y", this.__margins__.top + 1); | |
this.__chartArea__.append("rect") | |
.attr("id","innerBackground") | |
.attr("width", this.__chartArea__.attr("width")) | |
.attr("height", this.__chartArea__.attr("height")) | |
.attr("x", 0) | |
.attr("y", 0); | |
this.__redrawInnerBackground__(); | |
this.__redrawOuterBackground__(); | |
this.__redrawInnerBorder__(); | |
this.__redrawOuterBorder__(); | |
return ; | |
}); | |
Object.freeze(basicBarChartSharedPrototype); | |
/** BasicBarChart(width, height [, chartMargins, dataDim, parent]) | |
@method BasicBarChart | |
@chainable | |
@param {Number} width [Mandatory] | |
The desired width for the chart (<b>can't be changed later</b>)<br> | |
Can be any value that is or can be converted to a positive integer. | |
@param {Number} height [Mandatory] | |
The desired height for the chart (<b>can't be changed later</b>)<br> | |
Can be any value that is or can be converted to a positive integer. | |
@param {String} [chartMargins=""] [Optional] | |
A String of 0 to 4 space-separated values that specifies the 4 margins of the chart.<br> | |
The string should respect the following format: <b>"top right bottom left;"</b> (notice the trailing semicolon)<br> | |
If less then 4 values are passed, only the covered subfield will be assigned using the input string, | |
while the remaining ones will take a default value specified as an inner attribute of the class. | |
@param {Number} [dataDim=1] [Optional] | |
The dimension of the data space, i.e. the number of subvalues for each data entry<br> | |
Can be any value that is or can be converted to an integer between 1 and MAX_SPACE_DIMENSION. | |
@param {Object} [parent=body] [Optional] | |
The DOM element to which the diagram should be appended as a child | |
@return {Object} A new BasicBarChart object | |
@throws | |
- Wrong number of arguments Exception, if width or height are not passed as arguments (directly) | |
- Illegal Argument exception , if width or height aren't valid (numeric, positive) values | |
(through setWidth or setHeight) | |
- Illegal Argument exception, if dataDim is passed but it's invalid (not numeric or not positive) | |
- Exception, if dataDim exceeds the maximum data dimension | |
- Exception, if parent is passed but it is not a valid DOM element | |
*/ | |
function BasicBarChart(width, height, chartMargins, dataDim, parent){ | |
/** __initChart__(width, height [, chartMargins]) | |
Inits the chart DIV and SVG container, setting width and height, if they are passed as arguments; | |
@method __initChart__ | |
@private | |
@param {Object} chart [Mandatory] | |
The chart object that needs initialization; | |
@param {Number} width [Mandatory] | |
The desired width for the chart; | |
If passed, MUST be a positive integer, or a value that | |
can be converted to a positive integer | |
@param {Number} height [Mandatory] | |
The desired height for the chart; | |
If passed, MUST be a positive integer, or a value that | |
can be converted to a positive integer | |
@param {String} [margins] [Optional] | |
A String of 0 to 4 comma-separated valued that specifies the 4 margins of the chart.<br> | |
Omitted margins will get the default margin for this class. | |
@return {undefined} | |
@throws | |
- Inconsitent Chart State Exception, if the internale state of the object is compromised; | |
- Illegal Argument Exception, through setWidth or setHeight, if one of the arguments is | |
not valid. | |
*/ | |
function __initChart__(chart, width, height, chartMargins){ | |
if (!chart.__svgElement__ || !chart.__divElement__){ | |
throw "Inconsitent Chart State: null"; | |
} | |
//else | |
chart.setWidth(width); | |
chart.setHeight(height); | |
if (Object.isUndefined(chartMargins)){ | |
chartMargins = ""; | |
} | |
/** | |
The four margins of the chart object; | |
@property __margins__ | |
@type {Object} | |
@readOnly | |
@protected | |
*/ | |
Object.defineProperty(chart, "__margins__", { | |
value: chart.__decodeCSSMultivalueString__(chartMargins, DEFAULT_AXE_MARGIN), | |
writable: false, | |
enumerable: false, | |
configurable: false | |
}); | |
chart.__initAxes__(width, height); | |
var __xScale__ = d3.scale.linear().range([0, chart.__getChartAreaWidth__()]); | |
var __yScaleGenerator__ = function(){return d3.scale.linear().range([0, chart.__getChartAreaHeight__()]);}; | |
/** | |
Scale object for the horizontal axis of the chart | |
(common to all data subcomponents) | |
@property __xScale__ | |
@type {Object} | |
@readOnly | |
@protected | |
*/ | |
Object.defineProperty(chart, "__xScale__", { | |
value: __xScale__, | |
writable: false, | |
enumerable: false, | |
configurable: false | |
}); | |
/** | |
Scale objects for the vertical axis of the chart | |
(array with one obj for each data subcomponents, | |
so that each component can be scaled independently) | |
@property __yScale__ | |
@type {Array} | |
@readOnly | |
@protected | |
*/ | |
Object.defineProperty(chart, "__yScale__", { | |
value: ChartUtils.fillArray(__yScaleGenerator__, chart.__dataDim__), | |
writable: false, | |
enumerable: false, | |
configurable: false | |
}); | |
return; //(Pseudo)private method, no need to retun anything | |
} | |
/** __initData__(basicCharObj [, dataDim]) | |
Performs all the settings related to the data handling area of the chart; | |
@method __initData__ | |
@private | |
@param {Object} basicCharObj [Mandatory] | |
The chart object to init; | |
@param {Number} [dataDim=1] [Optional] | |
The dimension of the data space, i.e. the number of subvalues | |
for each data entry;<br> | |
Can take any value that is or can be converted to an integer | |
between 1 and MAX_SPACE_DIMENSION. | |
@return {undefined} | |
@throws | |
- Illegal Argument exception, if dataDim is passed but it's | |
not valid (not numeric or not positive) | |
- Exception, if dataDim exceeds the maximum data dimension | |
*/ | |
function __initData__(basicCharObj, dataDim){ | |
if (Object.isUndefined(dataDim)){ | |
dataDim = 1; //1 by default | |
}else{ | |
dataDim = parseInt(dataDim, 10); | |
if (isNaN(dataDim) || dataDim <= 0){ | |
throw "Illegal Argument: dataDim"; | |
}else if (dataDim > MAX_SPACE_DIMENSION){ | |
throw "Max number of subvalues for each point (" + MAX_SPACE_DIMENSION + ") exceeded"; | |
} | |
} | |
/** | |
Dimension of the data space, | |
i.e. number of subcomponents of each data "point" | |
@property __dataDim__ | |
@type {Number} | |
@readOnly | |
@protected | |
*/ | |
Object.defineProperty(basicBarChart, "__dataDim__", { | |
value: dataDim, | |
writable: false, | |
enumerable: false, | |
configurable: false | |
}); | |
/** | |
The array that will hold data, separately for each component | |
Initially every component's array is set to [] | |
@property __data__ | |
@type {Array} | |
@readOnly | |
@protected | |
*/ | |
Object.defineProperty(basicBarChart, "__data__", { | |
value: ChartUtils.fillArray(function(){return [];}, basicBarChart.__dataDim__), | |
writable: false, | |
enumerable: false, | |
configurable: false | |
}); | |
/** | |
Array of maximum values for each component | |
(used to compute the vertical scale)<br> | |
@property __maxVals__ | |
@type {Number} | |
@readOnly | |
@protected | |
@default [0]* | |
*/ | |
Object.defineProperty(basicBarChart, "__maxVals__", { | |
value: ChartUtils.fillArray(0, basicBarChart.__dataDim__), | |
writable: false, | |
enumerable: false, | |
configurable: false | |
}); | |
/** | |
Keeps track of how much data has been actually inserted into | |
the chart from its creation (to synch the highlighted ticks). | |
@property __dataCounter__ | |
@type {Number} | |
@protected | |
@default 0 | |
*/ | |
Object.defineProperty(basicBarChart, "__dataCounter__", { | |
value: 0, | |
writable: true, | |
enumerable: false, | |
configurable:false | |
}); | |
return; //Private method, no need to return anything | |
} | |
if (Object.isUndefined(width) || Object.isUndefined(height)){ | |
throw "Wrong number of arguments: width and height are mandatory"; | |
} | |
if (Object.isUndefined(parent)){ //By default, added to page's body | |
parent = d3.select("body"); | |
} | |
var div = parent.append("div").attr("id", "dynamic_chart_" + next_id); | |
var svg = div.append("svg").attr("id", "dynamic_chart_" + next_id); //NOTE: ++next_id needed after this | |
next_id += 1; | |
var basicBarChart = Object.create(basicBarChartSharedPrototype); | |
__initData__(basicBarChart, dataDim); | |
/** | |
The parent object to whom the chart is added | |
@property __parent__ | |
@type {Object} | |
@readOnly | |
@protected | |
*/ | |
Object.defineProperty(basicBarChart, "__parent__", { | |
value: parent, | |
writable: false, | |
enumerable: false, | |
configurable: false | |
}); | |
/** | |
The div element that will be a container to the chart's svg element | |
@property __divElement__ | |
@type {Object} | |
@readOnly | |
@protected | |
*/ | |
Object.defineProperty(basicBarChart, "__divElement__", { | |
value: div, | |
writable: false, | |
enumerable: false, | |
configurable: false | |
}); | |
/** | |
The chart's svg element | |
@property __svgElement__ | |
@type {Object} | |
@readOnly | |
@protected | |
*/ | |
Object.defineProperty(basicBarChart, "__svgElement__", { | |
value: svg, | |
writable: false, | |
enumerable: false, | |
configurable: false | |
}); | |
/** | |
The svg element for the main chart area | |
@property __chartArea__ | |
@type {Object} | |
@readOnly | |
@protected | |
*/ | |
Object.defineProperty(basicBarChart, "__chartArea__", { | |
value: svg.append("svg").attr("id", "chart_area"), | |
writable: false, | |
enumerable: false, | |
configurable: false | |
}); | |
/** | |
The svg element for the left axe area | |
@property __axeLeft__ | |
@type {Object} | |
@readOnly | |
@protected | |
*/ | |
Object.defineProperty(basicBarChart, "__axeLeft__", { | |
value: svg.append("svg").attr("id", "axe_left").attr("fill","none"), | |
writable: false, | |
enumerable: false, | |
configurable: false | |
}); | |
/** | |
The svg element for the right axe area | |
@property __axeRight__ | |
@type {Object} | |
@readOnly | |
@protected | |
*/ | |
Object.defineProperty(basicBarChart, "__axeRight__", { | |
value: svg.append("svg").attr("id", "axe_right").attr("fill","none"), | |
writable: false, | |
enumerable: false, | |
configurable: false | |
}); | |
/** | |
The svg element for the top axe area | |
@property __axeTop__ | |
@type {Object} | |
@readOnly | |
@protected | |
*/ | |
Object.defineProperty(basicBarChart, "__axeTop__", { | |
value: svg.append("svg").attr("id", "axe_top").attr("fill","none"), | |
writable: false, | |
enumerable: false, | |
configurable: false | |
}); | |
/** | |
The svg element for the bottom axe area | |
@property __axeBottom__ | |
@type {Object} | |
@readOnly | |
@protected | |
*/ | |
Object.defineProperty(basicBarChart, "__axeBottom__", { | |
value: svg.append("svg").attr("id", "axe_bottom").attr("fill","none"), | |
writable: false, | |
enumerable: false, | |
configurable: false | |
}); | |
/** | |
An object describing the Y axe's properties, or null if | |
it isn't supposed to be drawn.<br> | |
If assigned, the object must have the following fields: | |
<ul> | |
<li>side</li> - Can be either "left" or "right" | |
<li>svgElement</li> - Can be either __axeLeft__ or __axeRight__ | |
<li>notches</li> - The number of notches to be shown (Must be a positive integer) | |
</ul> | |
@property __verticalAxe__ | |
@type {Object} | |
@default null | |
@readOnly | |
@protected | |
*/ | |
Object.defineProperty(basicBarChart, "__verticalAxe__", { | |
value: null, | |
writable: true, | |
enumerable: false, | |
configurable: false | |
}); | |
/** | |
An object describing the X axe's properties, or null if | |
it isn't supposed to be drawn.<br> | |
If assigned, the object must have the following fields: | |
<ul> | |
<li>side</li> - Can be either "top" or "bottom" | |
<li>svgElement</li> - Can be either __axeBottom__ or __axeTop__ | |
<li>notches</li> - The number of notches to be shown (Must be a positive integer) | |
</ul> | |
@property __horizontalAxe__ | |
@type {Object} | |
@default null | |
@readOnly | |
@protected | |
*/ | |
Object.defineProperty(basicBarChart, "__horizontalAxe__", { | |
value: null, | |
writable: true, | |
enumerable: false, | |
configurable: false | |
}); | |
/** | |
For data space with dimension gt 1, states | |
if the different components should scale locally or globally | |
@property __scaleGlobally__ | |
@type {Boolean} | |
@protected | |
@default true | |
*/ | |
Object.defineProperty(basicBarChart, "__scaleGlobally__", { | |
value: true, //By default, scales globally | |
writable: true, | |
enumerable: false, | |
configurable: false | |
}); | |
/** | |
For each data subcomponent, stores the size to be used | |
for its label | |
@property __labelsSize__ | |
@type {Number} | |
@readOnly | |
@protected | |
@default DEFAULT_LABEL_SIZE | |
*/ | |
Object.defineProperty(basicBarChart, "__labelsSize__", { | |
value: ChartUtils.fillArray(DEFAULT_LABEL_SIZE, basicBarChart.__dataDim__), | |
writable: false, | |
enumerable: false, | |
configurable: false | |
}); | |
/** | |
For each data subcomponent, states whether or not | |
its label is visible | |
@property __labelsVisible__ | |
@type {Array} | |
@readOnly | |
@protected | |
@default [true]* | |
*/ | |
Object.defineProperty(basicBarChart, "__labelsVisible__", { | |
value: ChartUtils.fillArray(true, basicBarChart.__dataDim__), //All labels visible by default | |
writable: false, | |
enumerable: false, | |
configurable: false | |
}); | |
/** | |
For each data subcomponent, states whether or not | |
its label is abbreviated (as in, f.i., 1.1M instead of 1,123,456) | |
@property __abbreviatedLabels__ | |
@type {Array} | |
@readOnly | |
@protected | |
@default [false]* | |
*/ | |
Object.defineProperty(basicBarChart, "__abbreviatedLabels__", { | |
value: ChartUtils.fillArray(false, basicBarChart.__dataDim__), //All labels visible by default | |
writable: false, | |
enumerable: false, | |
configurable: false | |
}); | |
/** | |
For each data subcomponent, stores the color to be used | |
to fill its drawing component | |
@property __barsFillColors__ | |
@type {Array} | |
@readOnly | |
@protected | |
*/ | |
Object.defineProperty(basicBarChart, "__barsFillColors__", { | |
value: FILL_COLORS.shallowCopy(basicBarChart.__dataDim__), //Default values | |
writable: false, | |
enumerable: false, | |
configurable: false | |
}); | |
/** | |
For each data subcomponent, stores the color to be used | |
for the stroke of its drawing component | |
@property __barsStrokeColors__ | |
@type {Array} | |
@readOnly | |
@protected | |
@default ["black"]* | |
*/ | |
Object.defineProperty(basicBarChart, "__barsStrokeColors__", { | |
value: ChartUtils.fillArray("black", basicBarChart.__dataDim__), //Default values: black | |
writable: false, | |
enumerable: false, | |
configurable: false | |
}); | |
/** | |
For each data subcomponent, stores the color to be used | |
to draw its labels | |
@property __labelColors__ | |
@type {Array} | |
@readOnly | |
@protected | |
@default ["black"]* | |
*/ | |
Object.defineProperty(basicBarChart, "__labelColors__", { | |
value: ChartUtils.fillArray("black", basicBarChart.__dataDim__), //Default values: black | |
writable: false, | |
enumerable: false, | |
configurable: false | |
}); | |
/** | |
Color of the background of the main chart area. | |
@property __innerBackgroundColor__ | |
@type {String|Object} | |
@protected | |
@default DEFAULT_INNER_BACKGROUND | |
*/ | |
Object.defineProperty(basicBarChart, "__innerBackgroundColor__", { | |
value: DEFAULT_INNER_BACKGROUND, | |
writable: true, | |
enumerable: false, | |
configurable:false | |
}); | |
/** | |
Color of the background of the outer area of the whole chart div. | |
@property __outerBackgroundColor__ | |
@type {String|Object} | |
@protected | |
@default DEFAULT_OUTER_BACKGROUND | |
*/ | |
Object.defineProperty(basicBarChart, "__outerBackgroundColor__", { | |
value: DEFAULT_OUTER_BACKGROUND, | |
writable: true, | |
enumerable: false, | |
configurable:false | |
}); | |
/** | |
Border of the main chart area. | |
@property __innerBorder__ | |
@type {Object} | |
@protected | |
@default DEFAULT_INNER_BORDER | |
*/ | |
Object.defineProperty(basicBarChart, "__innerBorder__", { | |
value: DEFAULT_INNER_BORDER.shallowCopy(), | |
writable: true, | |
enumerable: false, | |
configurable:false | |
}); | |
/** | |
Color of the background of the outer area of the whole chart div. | |
@property __outerBorder__ | |
@type {Object} | |
@protected | |
@default DEFAULT_OUTER_BORDER | |
*/ | |
Object.defineProperty(basicBarChart, "__outerBorder__", { | |
value: DEFAULT_OUTER_BORDER.shallowCopy(), | |
writable: true, | |
enumerable: false, | |
configurable:false | |
}); | |
/** | |
Placeholder for a possible legend object, if the consumer | |
decides to add a legend to the chart; | |
@property __legend__ | |
@type {Object} | |
@protected | |
@default null | |
*/ | |
Object.defineProperty(basicBarChart, "__legend__", { | |
value: null, //Default value: none | |
writable: true, | |
enumerable: false, | |
configurable: false | |
}); | |
__initChart__(basicBarChart, width, height, chartMargins); | |
Object.seal(basicBarChart); | |
return basicBarChart; | |
} | |
/** | |
Advanced Chart: <b>FixedWidthBarChart</b><br> | |
Inherits from BasicBarChart redefining the drawing methods.<br> | |
As for its super class values are represented using vertical bars, and each point | |
can have up to 10 subcomponents, where each component can be any non-negative | |
real number (i.e., each point can be in R_+^i, for 1 <= i <= 10).<br> | |
<br> | |
For this chart the bar width is fixed (although can be set at run time) | |
It is possible to choose between having only a fixed number of values accepted, | |
or if a certain number of the oldest values should be removed when the | |
chart is full. | |
@class FixedWidthBarChart | |
@private | |
@beta | |
@extends BasicBarChart | |
*/ | |
/** FixedWidthBarChart(ticks, startingPoint, width, height [, chartMargins, dataDim, parent]) | |
FixedWidthBarChart (pseudo)Constructor | |
@method FixedWidthBarChart | |
@param {Number} ticks [Mandatory] | |
The number of values that can be drawn at the same time (<b>can't be changed later</b>)<br> | |
Can be any value that is or can be converted to a positive integer. | |
@param {Number} startingPoint [Mandatory, but not used at the moment: inserted for future back-compatibility]<br> | |
The reference for the label of the first point.<br> | |
Should be an incrementable value; | |
@param {Number} width [Mandatory] | |
The desired width for the chart (<b>can't be changed later</b>)<br> | |
Can be any value that is or can be converted to a positive integer. | |
@param {Number} height [Mandatory] | |
The desired height for the chart (<b>can't be changed later</b>)<br> | |
Can be any value that is or can be converted to a positive integer. | |
@param {String} [chartMargins=""] [Optional] | |
A String of 0 to 4 space-separated values that specifies the 4 margins of the chart.<br> | |
The string should respect the following format: <b>"top right bottom left;"</b> (notice the trailing semicolon)<br> | |
If less then 4 values are passed, only the covered subfield will be assigned using the input string, | |
while the remaining ones will take a default value specified as an inner attribute of the class. | |
@param {Number} [dataDim=1] [Optional] | |
The dimension of the data space, i.e. the number of subvalues for each data entry<br> | |
Can be any value that is or can be converted to an integer between 1 and MAX_SPACE_DIMENSION. | |
@param {Object} [parent=body] [Optional] | |
The DOM element to which the diagram should be appended as a child | |
@return: A new FixedWidthBarChart object | |
@throws | |
- Illegal Argument Exception, if ticks isn't a positive integer | |
- Wrong number of arguments Exception, if width or height are not passed as arguments (directly) | |
- Illegal Argument Exception, if width or height aren't valid (numeric, positive) values | |
(through setWidth or setHeight) | |
- Illegal Argument Exception, if dataDim is passed but it's invalid (not numeric or not positive) | |
- Exception, if dataDim exceeds the maximum data dimension | |
- Exception, if the ratio between chart's width and number of ticks is such | |
that the computed bar height is smaller than 1 pixel | |
- Exception, if parent is passed but it is not a valid DOM element | |
*/ | |
function FixedWidthBarChart(ticks, startingPoint, width, height, chartMargins, dataDim, parent){ | |
ticks = parseInt(ticks, 10); | |
if (isNaN(ticks) || ticks <= 0){ | |
throw "Illegal Argument: ticks"; | |
} | |
var proto = BasicBarChart(width, height, chartMargins, dataDim, parent); | |
var fixedWidthBarChart = Object.create(proto); | |
/** | |
Number of different values that can be | |
drawn at the same time in this chart | |
@property __ticks__ | |
@type {Number} | |
@readOnly | |
@protected | |
*/ | |
Object.defineProperty(fixedWidthBarChart, "__ticks__", { | |
value: ticks, | |
writable: false, | |
enumerable: false, | |
configurable: false | |
}); | |
/** | |
Tick length, in minutes | |
@property __tickLength__ | |
@type {Number} | |
@protected | |
@default 1 | |
*/ | |
Object.defineProperty(fixedWidthBarChart, "__tickLength__", { | |
value: 1, | |
writable: true, | |
enumerable: false, | |
configurable: false | |
}); | |
/** | |
When __ticks__ data points have already been plotted, | |
new plots would override previous ones. | |
Two solutions are made available: | |
1) By default, new data is rejected, generating a full stack exception; | |
2) A certain number of the oldest data points can be purged off the chart, | |
counter-clockwise rotating the data. | |
@property __ticksToRemoveOnFullQueue__ | |
@type {Number} | |
@protected | |
@default 0 | |
*/ | |
Object.defineProperty(fixedWidthBarChart, "__ticksToRemoveOnFullQueue__", { | |
value: 0, //By default, no previous data is cleared: new data is simply rejected | |
writable: true, | |
enumerable: false, | |
configurable: false | |
}); | |
Object.defineProperty(fixedWidthBarChart, "setFixedDataLengthMode", { | |
/** setFixedDataLengthMode() | |
Sets fixed data length mode.<br> | |
<br> | |
When __ticks__ data points have already been plotted, | |
new plots would override previous ones.<br> | |
Two solutions are made available: | |
<ol> | |
<li> By default, new data is rejected, generating a full stack exception;</li> | |
<li> A certain number of the oldest data points can be purged off the chart, | |
counter-clockwise rotating the data;</li> | |
</ol> | |
<br> | |
This function sets the first option. | |
@method setFixedDataLengthMode | |
@chainable | |
@return {Object} This chart object, to allow for methd chaining. | |
*/ | |
value: function(){ | |
if (this.hasOwnProperty("__ticksToRemoveOnFullQueue__")){ | |
this.__ticksToRemoveOnFullQueue__ = 0; | |
}else{ | |
//Looks for object's prototype | |
var proto = this.prototype ? this.prototype : this.__proto__; | |
if (proto && proto.setFixedDataLengthMode){ | |
proto.setFixedDataLengthMode(); | |
} | |
} | |
return this; //Method chaining oriented | |
}, //No previous data is cleared: new data is simply rejected | |
writable: false, | |
enumerable: false, | |
configurable: false | |
}); | |
Object.defineProperty(fixedWidthBarChart, "setShifitingDataMode", { | |
/** setShifitingDataMode(ticksToRemove) | |
Sets data shift mode.<br> | |
<br> | |
When __ticks__ data points have already been plotted, | |
new plots would override previous ones.<br> | |
Two solutions are made available: | |
<ol> | |
<li> By default, new data is rejected, generating a full stack exception;</li> | |
<li> A certain number of the oldest data points can be purged off the chart, | |
counter-clockwise rotating the data;</li> | |
</ol> | |
<br> | |
This function sets the second option. | |
@method setShifitingDataMode | |
@chainable | |
@param {Number} ticksToRemove [Mandatory] | |
How much data to remove on full chart; | |
@return {Object} This object, to allow for method chaining; | |
@throws Illegal Argument Exception, if the argument isn't valid (see above). | |
*/ | |
value: function(ticksToRemove){ | |
ticksToRemove = parseInt(ticksToRemove, 10); | |
if (isNaN(ticksToRemove) || ticksToRemove <= 0){ | |
throw "Illegal Arguments: ticksToRemove"; | |
} | |
if (this.hasOwnProperty("__ticksToRemoveOnFullQueue__")){ | |
this.__ticksToRemoveOnFullQueue__ = ticksToRemove; | |
}else{ | |
//Looks for object's prototype | |
var proto = this.prototype ? this.prototype : this.__proto__; | |
if (proto && proto.setShifitingDataMode){ | |
proto.setShifitingDataMode(ticksToRemove); | |
} | |
} | |
return this; //Method chaining oriented | |
}, //No previous data is cleared: new data is simply rejected | |
writable: false, | |
enumerable: true, | |
configurable: false | |
}); | |
Object.defineProperty(fixedWidthBarChart, "getBarWidth", { | |
/** getBarWidth() | |
Returns current bars' width. | |
The overridden version takes a parameter, but this method | |
doesn't need it because barWidth is fixed for this chart. | |
@method getBarWidth | |
@return {Number} the value set for __barWidth__. | |
@override BasicBarChart.getBarWidth | |
*/ | |
value: function(){ | |
return this.__barWidth__; //Stroke tickness is 1 point per side | |
}, | |
writable: false, | |
enumerable: true, | |
configurable: false | |
}); | |
Object.defineProperty(fixedWidthBarChart, "__canAppendData__", { | |
/** __canAppendData__(newDataArray) | |
<b>WARNING</b>: This function SHOULD be overriden in any class inheriting from the base class | |
in order to handle differents needs.<br> | |
See base class for method signature and details. | |
@method __canAppendData__ | |
@protected | |
@override BasicBarChart.__canAppendData__ | |
*/ | |
value: function(newDataArray){ | |
if (!Object.isArray(newDataArray)){ | |
return []; | |
} | |
//else, checks if there is room for the new data | |
var m = this.__getDatasetLength__(), | |
n = this.__ticks__ - m; | |
if (newDataArray.length <= n){ | |
return newDataArray; | |
}else if (this.__ticksToRemoveOnFullQueue__ === 0){ | |
if (n <= 0){ | |
//Can't add any more data | |
return []; | |
}else{ | |
//Can add at most n elements | |
return newDataArray.splice(0, n); | |
} | |
}else{ | |
//Must delete __ticksToRemoveOnFullQueue__ elements from the data, and then can add the new ones | |
m = Math.min(this.__ticksToRemoveOnFullQueue__, m); | |
this.clearData(m); | |
//Will update as soon as it returns control to the caller (appendData) | |
return this.__canAppendData__(newDataArray); //reiterate the request | |
} | |
}, | |
writable: false, | |
enumerable: false, | |
configurable:false | |
}); | |
Object.defineProperty(fixedWidthBarChart, "__drawNewData__", { | |
/** __drawNewData__(dataSet, labelsSet, dataIndex, xScale, yScale) | |
<b>WARNING</b>: if you inherit from this class you might want to override both | |
this method and __updateDrawing__ in order to obtain a custom chart.<br> | |
See base class for method signature and details. | |
@method __drawNewData__ | |
@protected | |
@override BasicBarChart.__drawNewData__ | |
*/ | |
value: function(dataSet, labelsSet, dataIndex, xScale, yScale){ | |
var that = this; | |
var height = this.__getChartAreaHeight__(); | |
var barWidth = this.getBarWidth(xScale); | |
dataSet.enter().append("rect").attr("index", "data_" + dataIndex) | |
.attr("x", function(d, i){return (i * that.__dataDim__ + dataIndex)*barWidth;}) | |
.attr("y", height) | |
.attr("width", barWidth) | |
.attr("height", 0) | |
.attr("fill", that.getBarsFillColor(dataIndex)) | |
.attr("stroke", that.getBarsStrokeColor(dataIndex)) | |
.attr("opacity", function(d){return that.__getBarOpacity__((0.0 + yScale(d)) / height);}); | |
if (that.areLabelsVisible(dataIndex) && barWidth > that.getLabelsSize(dataIndex)){ | |
labelsSet.enter().append("text").attr("index", "data_" + dataIndex) | |
.text(function(d) {return that.__makeLabel__(d, dataIndex);}) | |
.attr("text-anchor", "middle") | |
.attr("x", function(d, i){return (i * that.__dataDim__ + dataIndex + 0.5) * barWidth;}) | |
.attr("y", height) | |
.attr("font-family", "sans-serif") | |
.attr("font-size", that.getLabelsSize(dataIndex)) | |
.attr("fill", that.getLabelColor(dataIndex)); | |
}else{ | |
labelsSet.remove(); | |
} | |
return; //(Pseudo)Private method, no need to return this | |
}, | |
writable: false, | |
enumerable: false, | |
configurable:false | |
}); | |
Object.defineProperty(fixedWidthBarChart, "__updateDrawing__", { | |
/** __updateDrawing__(dataSet, labelsSet, dataIndex, xScale, yScale) | |
<b>WARNING</b>: if you inherit from this class you might want to override both | |
this method and __drawNewData__ in order to obtain a custom chart.<br> | |
See base class for method signature and details. | |
@method __updateDrawing__ | |
@protected | |
@override BasicBarChart.__updateDrawing__ | |
*/ | |
value: function(dataSet, labelsSet, dataIndex, xScale, yScale){ | |
var that = this; | |
var height = this.__getChartAreaHeight__(); | |
var barWidth = this.getBarWidth(xScale); | |
dataSet.transition()//.delay(250) | |
.attr("x", function(d, i){return (i * that.__dataDim__ + dataIndex)*barWidth;}) | |
.attr("y", function(d){return height - yScale(d);}) | |
.attr("width", barWidth) | |
.attr("height", function(d){return yScale(d);}) | |
.attr("opacity", function(d){return that.__getBarOpacity__((0.0 + yScale(d)) / height);}); | |
if (that.areLabelsVisible(dataIndex) && barWidth > that.getLabelsSize(dataIndex)){ | |
labelsSet.transition()//.delay(250) | |
.text(function(d) {return that.__makeLabel__(d, dataIndex);}) | |
.attr("x", function(d, i){return (i * that.__dataDim__ + dataIndex + 0.5) * barWidth;}) | |
.attr("y", function(d){return height - yScale(d) + that.getLabelsSize(dataIndex) ;}); | |
}else{ | |
labelsSet.remove(); | |
} | |
return; //(Pseudo)Private method, no need to return this | |
}, | |
writable: false, | |
enumerable: false, | |
configurable:false | |
}); | |
fixedWidthBarChart.addProtectedMethod("__refreshDrawing__", | |
/** __refreshDrawing__(dataSet, labelsSet, dataIndex, xScale, yScale) | |
Called by __redrawAll__() to redraw all the data-related drawings, once for | |
every data subcomponent.<br> | |
The difference with __updateDrawing__ is that the latter is incremental with respect to | |
__drawNewData__ and updates only the properties used to provide animations of the drawing, | |
while this method redraws from scratch the data. | |
<br> | |
<b>WARNING</b>: if you inherit from this class you might want to override both | |
this method following __updateDrawing__ behaviour in order to obtain a custom chart. | |
@method __refreshDrawing__ | |
@protected | |
@override BasicBarChart.__refreshDrawing__ | |
*/ | |
function __refreshDrawing__(dataSet, labelsSet, dataIndex, xScale, yScale){ | |
var that = this; | |
var height = this.__getChartAreaHeight__(); | |
var barWidth = this.getBarWidth(xScale); | |
dataSet.transition() | |
.attr("x", function(d, i){return (i * that.__dataDim__ + dataIndex)*barWidth;}) | |
.attr("y", height) | |
.attr("width", barWidth) | |
.attr("height", 0) | |
.attr("fill", that.getBarsFillColor(dataIndex)) | |
.attr("stroke", that.getBarsStrokeColor(dataIndex)) | |
.attr("opacity", function(d){return that.__getBarOpacity__((0.0 + yScale(d)) / height);}); | |
if (that.areLabelsVisible(dataIndex) && barWidth > that.getLabelsSize(dataIndex)){ | |
labelsSet.transition() | |
.text(function(d) {return that.__makeLabel__(d, dataIndex);}) | |
.attr("text-anchor", "middle") | |
.attr("x", function(d, i){return (i * that.__dataDim__ + dataIndex + 0.5) * barWidth;}) | |
.attr("y", height) | |
.attr("font-family", "sans-serif") | |
.attr("font-size", that.getLabelsSize(dataIndex)) | |
.attr("fill", that.getLabelColor(dataIndex)); | |
}else{ | |
labelsSet.remove(); | |
} | |
return; //(Pseudo)Private method, no need to return this | |
}); | |
fixedWidthBarChart.addProtectedMethod("__updateAxes__", | |
/** __updateAxes__(yScale) | |
Called by __updateDrawing__() to update the labels of the vertical axe | |
when vertical scale changes;<br> | |
<br> | |
<b>WARNING</b>: if you inherit from this class you might want to override | |
this method as well as __drawNewData__ and __updateDrawing__ | |
in order to obtain a custom chart. | |
@method __updateAxes__ | |
@protected | |
@chainable | |
@override FixedWidthBarChart.__updateAxes__ | |
@param {Object} yScale [Mandatory] | |
D3 scale object for Y axis; | |
@return {Object} The current chart object, to allow for method chaining. | |
*/ | |
function __updateAxes__(yScale){ | |
var vAxe = this.__verticalAxe__, | |
hAxe = this.__horizontalAxe__; | |
var i, notches; | |
var axeLabels, m, n, k, | |
anchor, | |
labels, | |
x, y, | |
width, height, | |
scaleFactor, | |
that = this; | |
//VERTICAL AXE | |
if (vAxe){ | |
if (this.__scaleGlobally__ || this.__dataDim__ === 1){ | |
//If data is drawn with a global scaling, or there is only one subcomponent, | |
//then Vertical axe CAN BE DRAWN | |
m = this.__maxVals__.max(); | |
notches = vAxe.notches; | |
axeLabels = [m]; | |
for (i = notches; i > 0; i--){ | |
axeLabels.push(m * i / (notches + 1)); | |
} | |
axeLabels.push(0); | |
}else{ | |
//If data is drawn with local scaling for each component | |
//then Vertical axe WOULD HAVE NO MEANING | |
axeLabels = []; | |
} | |
labels = vAxe.svgElement.selectAll("text[type=axe_label]").data(axeLabels); | |
height = vAxe.svgElement.attr("height"); | |
if (vAxe.side === "right"){ | |
x = NOTCH_LINE_LENGTH; | |
anchor = "start"; | |
}else{ | |
x = vAxe.svgElement.attr("width") - NOTCH_LINE_LENGTH; | |
anchor = "end"; | |
} | |
labels.exit().remove(); | |
labels.enter().insert("text", "svg") //Insert before svg block | |
.attr("type", "axe_label") | |
.attr("text-anchor", anchor) | |
.attr("font-family", "sans-serif") | |
.attr("font-size", vAxe.labelSize); | |
labels.text(function(d){return that.__makeLabel__(d, undefined, true);}) | |
.attr("x", x) | |
.attr("y", function(d){return Math.max(vAxe.labelSize *0.75, | |
Math.min(height, height - yScale(d) + vAxe.labelSize * 0.375));}) | |
.attr("fill", vAxe.color); | |
} | |
//HORIZONTAL AXE | |
if (hAxe){ | |
width = hAxe.svgElement.attr("width"); | |
height = hAxe.svgElement.attr("height"); | |
m = this.__dataCounter__ - this.__getDatasetLength__(); | |
notches = hAxe.notches; | |
scaleFactor = width / (notches + 1); | |
axeLabels = [m]; | |
for (i = 1; i <= notches; i++){ | |
k = m + (this.__ticks__* i / (notches + 1)); | |
axeLabels.push(k); //Add element to the head of the list | |
} | |
axeLabels.push(m + this.__ticks__); | |
labels = hAxe.svgElement.selectAll("text[type=axe_label]").data(axeLabels); | |
if (hAxe.side === "bottom"){ | |
y = NOTCH_LINE_LENGTH + hAxe.labelSize; | |
}else{ | |
y = height - NOTCH_LINE_LENGTH; | |
anchor = "end"; | |
} | |
n = 0; | |
labels.exit().remove(); | |
labels.enter().insert("text", "svg") //Insert before svg block | |
.attr("type", "axe_label") | |
.attr("font-family", "sans-serif") | |
.attr("text-anchor", function(){ ++n; return n === 1 ? "start" : n === axeLabels.length ? "end" :"middle";}) | |
.attr("font-size", hAxe.labelSize); | |
//Updates labels | |
labels.text(function(d){return Object.isNumber(d) ? Math.round(d) : d;}) | |
.attr("x", function(d, i){return i * scaleFactor;}) | |
.attr("y", y) | |
.attr("fill", hAxe.color); | |
} | |
return; //(Pseudo)Private method, no need to return this | |
}); | |
/** __init__() | |
Inits the chart by computing the allowed barWidth; | |
@method __init__ | |
@private | |
@param {Object} chart [Mandatory] | |
The chart object that needs initialization; | |
@param {Number} width [Mandatory] | |
Chart's width; | |
@param {Number} height [Mandatory] | |
Chart's height; | |
@return {undefined} | |
*/ | |
function __init__(chart, width, height){ | |
var barWidth = width / (chart.__dataDim__ * chart.__ticks__); | |
if (barWidth <= 0){ | |
throw "Illegal Arguments combination: width too small to draw 'ticks' values"; | |
} | |
/** | |
Chart's bars' width, in pixel <br> | |
Can be changed at runtime | |
@property __barWidth__ | |
@type {Number} | |
@protected | |
@default 8 | |
@override FixedWidthBarChart.__barWidth__ | |
*/ | |
Object.defineProperty(chart, "__barWidth__", { | |
value: barWidth, | |
writable: false, | |
enumerable: false, | |
configurable: false | |
}); | |
return ; | |
} | |
__init__(fixedWidthBarChart, fixedWidthBarChart.__getChartAreaWidth__(), fixedWidthBarChart.__getChartAreaHeight__()); | |
Object.seal(fixedWidthBarChart); | |
return fixedWidthBarChart; | |
} | |
/** | |
Advanced Chart: <b>SlidingBarChart</b><br> | |
Inherits from FixedWidthBarChart redefining the drawing methods.<br> | |
As for its super class values are represented using vertical bars, and each point | |
can have up to 10 subcomponents, where each component can be any non-negative | |
real number (i.e., each point can be in R_+^i, for 1 <= i <= 10).<br> | |
<br> | |
For this chart the bar width is fixed (although can be set at run time) | |
It is possible to choose between having only a fixed number of values accepted, | |
or if a certain number of the oldest values should be removed when the | |
chart is full.<br> | |
<br> | |
Every __ticksBetweenHighlights__ values inserted (where __ticksBetweenHighlights__ can | |
be set at runtime, although it defaults to 10) the background of those values is highlighted, | |
to stress out time progression. | |
@class SlidingBarChart | |
@private | |
@beta | |
@extends FixedWidthBarChart | |
*/ | |
/** SlidingBarChart(ticks, width, height [, chartMargins, dataDim, parent]) | |
SlidingBarChart (pseudo)Constructor. | |
@method SlidingBarChart | |
@param {Number} ticks [Mandatory] | |
The number of values that can be drawn at the same time (<b>can't be changed later</b>)<br> | |
Can be any value that is or can be converted to a positive integer. | |
@param {Number} width [Mandatory] | |
The desired width for the chart (<b>can't be changed later</b>)<br> | |
Can be any value that is or can be converted to a positive integer. | |
@param {Number} height [Mandatory] | |
The desired height for the chart (<b>can't be changed later</b>)<br> | |
Can be any value that is or can be converted to a positive integer. | |
@param {String} [chartMargins=""] [Optional] | |
A String of 0 to 4 space-separated values that specifies the 4 margins of the chart.<br> | |
The string should respect the following format: <b>"top right bottom left;"</b> (notice the trailing semicolon)<br> | |
If less then 4 values are passed, only the covered subfield will be assigned using the input string, | |
while the remaining ones will take a default value specified as an inner attribute of the class. <br> | |
For this particular chart the right margin can't be less than AXES_LABEL_WIDTH pixel wide (if a smaller | |
value is passed, it will be overwritten). | |
@param {Number} [dataDim=1] [Optional] | |
The dimension of the data space, i.e. the number of subvalues for each data entry<br> | |
Can be any value that is or can be converted to an integer between 1 and MAX_SPACE_DIMENSION. | |
@param {Object} [parent=body] [Optional] | |
The DOM element to which the diagram should be appended as a child | |
@return {Object} A new SlidingBarChart object. | |
@throws | |
- Illegal Argument Exception, if ticks isn't a positive integer | |
- Wrong number of arguments Exception, if width or height are not passed as arguments (directly) | |
- Illegal Argument Exception, if width or height aren't valid (numeric, positive) values | |
(through setWidth or setHeight) | |
- Illegal Argument Exception, if dataDim is passed but it's invalid (not numeric or not positive) | |
- Exception, if dataDim exceeds the maximum data dimension | |
- Exception, if the ratio between chart's width and number of ticks is such | |
that the computed bar height is smaller than 1 pixel | |
- Exception, if parent is passed but it is not a valid DOM element | |
*/ | |
function SlidingBarChart (ticks, width, height, chartMargins, dataDim, parent){ | |
ticks = parseInt(ticks, 10); | |
if (isNaN(ticks) || ticks <= 0){ | |
throw "Illegal Argument: ticks"; | |
} | |
/** | |
Default highlight color for background | |
@property DEFAULT_INNER_BACKGROUND_HIGHLIGHT | |
@for SlidingBarChart | |
@type {String|Object} | |
@default = "lightpink" | |
@final | |
@private | |
*/ | |
var DEFAULT_INNER_BACKGROUND_HIGHLIGHT = "lightpink"; | |
/** | |
Default width of axes' labels | |
@property AXES_LABEL_WIDTH | |
@for SlidingBarChart | |
@type {Number} | |
@default = 55 | |
@final | |
@private | |
*/ | |
var AXES_LABEL_WIDTH = 55; | |
var chartMarginsObj; | |
try{ | |
chartMarginsObj = basicBarChartSharedPrototype.__decodeCSSMultivalueString__(chartMargins, undefined); | |
chartMarginsObj.right = Math.max(chartMarginsObj.right, AXES_LABEL_WIDTH); | |
}catch(e){ | |
chartMarginsObj = {top: 0, right: AXES_LABEL_WIDTH}; | |
} | |
chartMargins = basicBarChartSharedPrototype.__encodeCSSMultivalueString__(chartMarginsObj); | |
var proto = FixedWidthBarChart(ticks, 0, width, height, chartMargins, dataDim, parent); | |
var slidingBarChart = Object.create(proto); | |
//Override any possible value passed | |
/** | |
Every __ticksBetweenHighlights__ ticks, the data is "higlighted" | |
by applying the selected highlight style to the background. | |
@property __ticksBetweenHighlights__ | |
@type {Number} | |
@protected | |
@default 10 | |
*/ | |
Object.defineProperty(slidingBarChart, "__ticksBetweenHighlights__", { | |
value: 10, | |
writable: true, | |
enumerable: false, | |
configurable:false | |
}); | |
Object.defineProperty(slidingBarChart, "getTicksBetweenHighlights", { | |
/** getTicksBetweenHighlights() | |
Returns the number of ticks between two consecutive highlights (one extreme inclusive) | |
@method getTicksBetweenHighlights | |
@return {Number} The number of ticks between two consecutive highlights; | |
*/ | |
value: function(){ | |
return this.__ticksBetweenHighlights__; | |
}, //No previous data is cleared: new data is simply rejected | |
writable: false, | |
enumerable: true, | |
configurable: false | |
}); | |
Object.defineProperty(slidingBarChart, "setTicksBetweenHighlights", { | |
/** setTicksBetweenHighlights(ticks) | |
Sets the number of ticks between two consecutive highlights (one extreme inclusive) | |
@method setTicksBetweenHighlights | |
@chainable | |
@param {Number} ticks [Mandatory] | |
The number of ticks between two consecutive highlights; | |
@return {Object} This object, to allow for method chaining; | |
*/ | |
value: function(ticks){ | |
if (this.hasOwnProperty("__ticksBetweenHighlights__")){ | |
this.__ticksBetweenHighlights__ = ticks; | |
}else{ | |
//Looks for object's prototype | |
var proto = this.prototype ? this.prototype : this.__proto__; | |
if (proto && proto.setTicksBetweenHighlights){ | |
proto.setTicksBetweenHighlights(ticks); | |
} | |
} | |
return this; //Method chaining oriented | |
}, //No previous data is cleared: new data is simply rejected | |
writable: false, | |
enumerable: true, | |
configurable: false | |
}); | |
/** | |
Color of the background bars when highlighted | |
@property __backgroundHighlightColor__ | |
@type {String|Object} | |
@protected | |
@default DEFAULT_INNER_BACKGROUND_HIGHLIGHT | |
*/ | |
Object.defineProperty(slidingBarChart, "__backgroundHighlightColor__", { | |
value: DEFAULT_INNER_BACKGROUND_HIGHLIGHT, | |
writable: true, | |
enumerable: false, | |
configurable:false | |
}); | |
Object.defineProperty(slidingBarChart, "setInnerBackgroundHighlightColor", { | |
/** setInnerBackgroundHighlightColor(bgHColor) | |
Changes the background color for "highlighted" values | |
@method setInnerBackgroundHighlightColor | |
@chainable | |
@param {String|Object} bgHColor [Mandatory] | |
The new color for highlighted background bars; | |
@return {Object} This object, to allow for method chaining. | |
*/ | |
value: function(bgHColor){ | |
if (this.hasOwnProperty("__backgroundHighlightColor__")){ | |
this.__backgroundHighlightColor__ = bgHColor; | |
}else{ | |
//Looks for object's prototype | |
var proto = this.prototype ? this.prototype : this.__proto__; | |
if (proto && proto.setInnerBackgroundHighlightColor){ | |
proto.setInnerBackgroundHighlightColor(bgHColor); | |
} | |
} | |
return this; //Method chaining oriented | |
}, //No previous data is cleared: new data is simply rejected | |
writable: false, | |
enumerable: true, | |
configurable: false | |
}); | |
Object.defineProperty(slidingBarChart, "getBackgroundHighlightColor", { | |
/** getBackgroundHighlightColor() | |
Returns current color for background highlighted bars | |
@method getBackgroundHighlightColor | |
@return {String|Object} The value set for __backgroundHighlightColor__ | |
*/ | |
value: function(){ | |
return this.__backgroundHighlightColor__; | |
}, | |
writable: false, | |
enumerable: true, | |
configurable: false | |
}); | |
Object.defineProperty(slidingBarChart, "__canAppendData__", { | |
/** __canAppendData__(newDataArray) | |
<b>WARNING</b>: This function SHOULD be overriden in any class inheriting from the base class | |
in order to handle differents needs.<br> | |
See base class for method signature and details. | |
@method __canAppendData__ | |
@protected | |
@override FixedWidthBarChart.__canAppendData__ | |
*/ | |
value: function(newDataArray){ | |
if (!Object.isArray(newDataArray)){ | |
return []; | |
} | |
//else, checks if there is room for the new data | |
var m = this.__getDatasetLength__(), | |
n = this.__ticks__ - m, | |
k = newDataArray.length; | |
//Checks that it isn't trying to add more data than it is allowed to... | |
if (k > this.__ticks__){ | |
//... if it is so, discards the oldest surplus among the new data | |
newDataArray.splice(0, k - this.__ticks__); | |
k = this.__ticks__; | |
} | |
if (k <= n){ | |
return newDataArray; | |
}else{ | |
//Must delete the exceding elements from the data, and then can add the new ones | |
m = Math.min(k, m); | |
this.clearData(m); | |
//Will update as soon as it returns control to the caller (appendData) | |
return newDataArray; //reiterate the request | |
} | |
}, | |
writable: false, | |
enumerable: false, | |
configurable:false | |
}); | |
Object.defineProperty(slidingBarChart, "__selectBackgroundBars__", { | |
/** __selectBackgroundBars__([filter]) | |
Returns the list of the svg elements used to draw background; <br> | |
Elements can be filtered using a custom filter passad as an optional | |
parameter; | |
@method __selectBackgroundBars__ | |
@protected | |
@param {String|Object} filter [Optional] | |
A filter to be applied to the selection; | |
@return {Object} The proper set of d3 elements. | |
*/ | |
value: function(filter){ | |
if (Object.isUndefined(filter)){ | |
return this.__chartArea__.selectAll("rect[type=back]"); | |
}else{ | |
return this.__chartArea__.selectAll("rect[type=back]").filter(filter); | |
} | |
}, | |
writable: false, | |
enumerable: false, | |
configurable:false | |
}); | |
Object.defineProperty(slidingBarChart, "__updateBackground__", { | |
/** __updateBackground__() | |
Called by __drawNewData__() to redraw the background properly;<br> | |
<br> | |
<b>WARNING</b>: if you inherit from this class you might want to override | |
this method as well as __drawNewData__ and __updateDrawing__ | |
in order to obtain a custom chart. | |
@method __updateBackground__ | |
@protected | |
@override BasicBarChart.__updateBackground__ | |
@return {undefined} | |
*/ | |
value: function(){ | |
var counter = this.__dataCounter__, | |
ticks = this.getTicksBetweenHighlights(); | |
var bgColor = this.getInnerBackgroundColor(), | |
bgHColor = this.getBackgroundHighlightColor(); | |
//Redraws all background bars with normal style | |
this.__selectBackgroundBars__() | |
.attr("fill", bgColor); | |
//Redraws only the highlighted ones with highlighted style | |
this.__selectBackgroundBars__( function(){ | |
return counter > this.d_index && | |
(counter - this.d_index) % ticks === 0; | |
}) | |
.attr("fill", bgHColor); | |
return; //(Pseudo)Private method, no need to return this | |
}, | |
writable: false, | |
enumerable: false, | |
configurable:false | |
}); | |
slidingBarChart.addProtectedMethod("__updateAxes__", | |
/** __updateAxes__(yScale) | |
Called by __updateDrawing__() to update the labels of the vertical axe | |
when vertical scale changes;<br> | |
<br> | |
<b>WARNING</b>: if you inherit from this class you might want to override | |
this method as well as __drawNewData__ and __updateDrawing__ | |
in order to obtain a custom chart. | |
@method __updateAxes__ | |
@protected | |
@chainable | |
@override FixedWidthBarChart.__updateAxes__ | |
@param {Object} yScale [Mandatory] | |
D3 scale object for Y axis; | |
@return {Object} The current chart object, to allow for method chaining. | |
*/ | |
function(yScale){ | |
var vAxe = this.__verticalAxe__, | |
hAxe = this.__horizontalAxe__; | |
var i, notches; | |
var axeLabels, m, n, k, | |
anchor, | |
labels, | |
x, y, | |
width, height, | |
scaleFactor, | |
that = this; | |
//VERTICAL AXE | |
if (vAxe){ | |
if (this.__scaleGlobally__ || this.__dataDim__ === 1){ | |
//If data is drawn with a global scaling, or there is only one subcomponent, | |
//then Vertical axe CAN BE DRAWN | |
m = this.__maxVals__.max(); | |
notches = vAxe.notches; | |
axeLabels = [m]; | |
for (i = notches; i > 0; i--){ | |
axeLabels.push(m * i / (notches + 1)); | |
} | |
axeLabels.push(0); | |
}else{ | |
//If data is drawn with local scaling for each component | |
//then Vertical axe WOULD HAVE NO MEANING | |
axeLabels = []; | |
} | |
labels = vAxe.svgElement.selectAll("text[type=axe_label]").data(axeLabels); | |
height = vAxe.svgElement.attr("height"); | |
if (vAxe.side === "right"){ | |
x = NOTCH_LINE_LENGTH; | |
anchor = "start"; | |
}else{ | |
x = vAxe.svgElement.attr("width") - NOTCH_LINE_LENGTH; | |
anchor = "end"; | |
} | |
labels.exit().remove(); | |
labels.enter().insert("text", "svg") //Insert before svg block | |
.attr("type", "axe_label") | |
.attr("text-anchor", anchor) | |
.attr("font-family", "sans-serif") | |
.attr("font-size", vAxe.labelSize); | |
labels.text(function(d){return that.__makeLabel__(d, undefined, true);}) | |
.attr("x", x) | |
.attr("y", function(d){return Math.max(vAxe.labelSize *0.75, | |
Math.min(height, height - yScale(d) + vAxe.labelSize * 0.375));}) | |
.attr("fill", vAxe.color); | |
} | |
//HORIZONTAL AXE | |
if (hAxe){ | |
width = hAxe.svgElement.attr("width"); | |
height = hAxe.svgElement.attr("height"); | |
m = this.__dataCounter__ - this.__ticks__; | |
notches = hAxe.notches; | |
scaleFactor = width / (notches + 1); | |
axeLabels = [m >= 0 ? m : ""]; | |
for (i = 1; i <= notches; i++){ | |
k = m + (this.__ticks__* i / (notches + 1)); | |
axeLabels.push(k >= 0 ? k : ""); | |
} | |
axeLabels.push(this.__dataCounter__); | |
labels = hAxe.svgElement.selectAll("text[type=axe_label]").data(axeLabels); | |
if (hAxe.side === "bottom"){ | |
y = NOTCH_LINE_LENGTH + hAxe.labelSize; | |
}else{ | |
y = height - NOTCH_LINE_LENGTH; | |
anchor = "end"; | |
} | |
n = 0; | |
labels.exit().remove(); | |
labels.enter().insert("text", "svg") //Insert before svg block | |
.attr("type", "axe_label") | |
.attr("font-family", "sans-serif") | |
.attr("text-anchor", function(){ ++n; return n === 1 ? "start" : n === axeLabels.length ? "end" :"middle";}) | |
.attr("font-size", hAxe.labelSize); | |
//Updates labels | |
labels.text(function(d){return Object.isNumber(d) ? Math.round(d) : d;}) | |
.attr("x", function(d, i){return i * scaleFactor;}) | |
.attr("y", y) | |
.attr("fill", hAxe.color); | |
} | |
return; //(Pseudo)Private method, no need to return this | |
}); | |
Object.defineProperty(slidingBarChart, "__drawNewData__", { | |
/** __drawNewData__(dataSet, labelsSet, dataIndex, xScale, yScale) | |
<b>WARNING</b>: if you inherit from this class you might want to override both | |
this method and __updateDrawing__ in order to obtain a custom chart.<br> | |
See base class for method signature and details. | |
@method __drawNewData__ | |
@protected | |
@override FixedWidthBarChart.__drawNewData__ | |
*/ | |
value: function(dataSet, labelsSet, dataIndex, xScale, yScale){ | |
var that = this; | |
var height = this.__getChartAreaHeight__(); | |
var barWidth = this.getBarWidth(xScale); | |
var initial_x = (this.__ticks__ - this.__getDatasetLength__()) * that.__dataDim__ * barWidth; | |
//Adds value bars | |
dataSet.enter().append("rect") | |
.attr("index", "data_" + dataIndex) | |
.attr("x", function(d, i){return initial_x + (i * that.__dataDim__ + dataIndex) * barWidth + 1;}) | |
.attr("y", height) | |
.attr("width", barWidth - 3) | |
.attr("height", 0) | |
.attr("fill", that.getBarsFillColor(dataIndex)) | |
.attr("stroke", that.getBarsStrokeColor(dataIndex)) | |
.attr("opacity", function(d){return that.__getBarOpacity__((0.0 + yScale(d)) / height);}); | |
if (that.areLabelsVisible(dataIndex) && barWidth > that.getLabelsSize(dataIndex)){ | |
labelsSet.enter().append("text").attr("index", "data_" + dataIndex) | |
.text(function(d) {return that.__makeLabel__(d, dataIndex);}) | |
.attr("text-anchor", "middle") | |
.attr("x", function(d, i){return initial_x + (i * that.__dataDim__ + dataIndex + 0.5) * barWidth + 1;}) | |
.attr("y", height) | |
.attr("font-family", "sans-serif") | |
.attr("font-size", that.getLabelsSize(dataIndex)) | |
.attr("fill", that.getLabelColor(dataIndex)); | |
}else{ | |
labelsSet.remove(); | |
} | |
return; //(Pseudo)Private method, no need to return this | |
}, | |
writable: false, | |
enumerable: false, | |
configurable:false | |
}); | |
Object.defineProperty(slidingBarChart, "__updateDrawing__", { | |
/** __updateDrawing__(dataSet, labelsSet, dataIndex, xScale, yScale) | |
<b>WARNING</b>: if you inherit from this class you might want to override both | |
this method and __drawNewData__ in order to obtain a custom chart.<br> | |
See base class for method signature and details. | |
@method __updateDrawing__ | |
@protected | |
@override FixedWidthBarChart.__updateDrawing__ | |
*/ | |
value: function(dataSet, labelsSet, dataIndex, xScale, yScale){ | |
var that = this; | |
var height = this.__getChartAreaHeight__(); | |
var barWidth = this.getBarWidth(xScale); | |
var initial_x = (this.__ticks__ - this.__getDatasetLength__()) * that.__dataDim__ * barWidth; | |
dataSet.transition().duration(100)//.delay(250) | |
.attr("x", function(d, i){return initial_x + (i * that.__dataDim__ + dataIndex) * barWidth + 1;}) | |
.attr("y", function(d){return height - yScale(d);}) | |
.attr("width", barWidth - 3) | |
.attr("height", function(d){return yScale(d);}) | |
.attr("opacity", function(d){return that.__getBarOpacity__((0.0 + yScale(d)) / height);}); | |
if (that.areLabelsVisible(dataIndex) && barWidth > that.getLabelsSize(dataIndex)){ | |
labelsSet.transition()//.delay(250) | |
.text(function(d) {return that.__makeLabel__(d, dataIndex);}) | |
.attr("x", function(d, i){return initial_x + (i * that.__dataDim__ + dataIndex + 0.5) * barWidth + 1;}) | |
.attr("y", function(d){return height - yScale(d) + that.getLabelsSize(dataIndex) ;}); | |
}else{ | |
labelsSet.remove(); | |
} | |
return; //(Pseudo)Private method, no need to return this | |
}, | |
writable: false, | |
enumerable: false, | |
configurable:false | |
}); | |
slidingBarChart.addProtectedMethod("__refreshDrawing__", | |
/** __refreshDrawing__(dataSet, labelsSet, dataIndex, xScale, yScale) | |
Called by __redrawAll__() to redraw all the data-related drawings, once for | |
every data subcomponent.<br> | |
The difference with __updateDrawing__ is that the latter is incremental with respect to | |
__drawNewData__ and updates only the properties used to provide animations of the drawing, | |
while this method redraws from scratch the data. | |
<br> | |
<b>WARNING</b>: if you inherit from this class you might want to override both | |
this method following __updateDrawing__ behaviour in order to obtain a custom chart. | |
@method __refreshDrawing__ | |
@protected | |
@override FixedWidthBarChart.__refreshDrawing__ | |
*/ | |
function __refreshDrawing__(dataSet, labelsSet, dataIndex, xScale, yScale){ | |
var that = this; | |
var height = this.__getChartAreaHeight__(); | |
var barWidth = this.getBarWidth(xScale); | |
var initial_x = (this.__ticks__ - this.__getDatasetLength__()) * that.__dataDim__ * barWidth; | |
//Adds value bars | |
dataSet.transition() | |
.attr("index", "data_" + dataIndex) | |
.attr("x", function(d, i){return initial_x + (i * that.__dataDim__ + dataIndex) * barWidth + 1;}) | |
.attr("y", function(d){return height - yScale(d);}) | |
.attr("width", barWidth - 3) | |
.attr("height", function(d){return yScale(d);}) | |
.attr("fill", that.getBarsFillColor(dataIndex)) | |
.attr("stroke", that.getBarsStrokeColor(dataIndex)) | |
.attr("opacity", function(d){return that.__getBarOpacity__((0.0 + yScale(d)) / height);}); | |
if (that.areLabelsVisible(dataIndex) && barWidth > that.getLabelsSize(dataIndex)){ | |
labelsSet.transition() | |
.text(function(d) {return that.__makeLabel__(d, dataIndex);}) | |
.attr("text-anchor", "middle") | |
.attr("x", function(d, i){return initial_x + (i * that.__dataDim__ + dataIndex + 0.5) * barWidth + 1;}) | |
.attr("y", function(d){return height - yScale(d) + that.getLabelsSize(dataIndex) ;}) | |
.attr("font-family", "sans-serif") | |
.attr("font-size", that.getLabelsSize(dataIndex)) | |
.attr("fill", that.getLabelColor(dataIndex)); | |
}else{ | |
labelsSet.remove(); | |
} | |
return; //(Pseudo)Private method, no need to return this | |
}); | |
slidingBarChart.addProtectedMethod("__redrawInnerBackground__", | |
/** __redrawInnerBackground__() | |
Properly redraws the background of the main chart area <br> | |
<br> | |
<b>WARNING</b>: if you inherit from this class you might want to override | |
this method to reflect its expected behaviour. | |
@method __redrawInnerBackground__ | |
@protected | |
@override | |
@return {undefined} | |
*/ | |
function(){ | |
//Nothing to do other than preventing super method execution | |
return; //(Pseudo)Private method, no need to return this | |
}); | |
/** __init__() | |
Inits the chart; | |
@method __init__ | |
@private | |
@param {Object} chart [Mandatory] | |
The chart object that needs initialization; | |
@param {Number} width [Mandatory] | |
Chart's width; | |
@param {Number} height [Mandatory] | |
Chart's height; | |
@return {undefined} | |
*/ | |
function __init__(chart, width, height){ | |
//Hides all labels by default | |
for (var i=0; i<chart.__dataDim__; i++){ | |
chart.toggleLabels(i, false); | |
} | |
var barWidth = width / (chart.__dataDim__ * chart.__ticks__); | |
//var barWidth = slidingBarChart.getBarWidth() ; | |
if (barWidth <= 0){ | |
throw "Illegal Arguments combination: width too small to draw 'ticks' values"; | |
} | |
/** | |
The width of each bar; | |
@property __barWidth__ | |
@type {Number} | |
@readOnly | |
@protected | |
@override FixedWidthBarChart.__barWidth__ | |
*/ | |
Object.defineProperty(chart, "__barWidth__", { | |
value: barWidth, | |
writable: false, | |
enumerable: false, | |
configurable: false | |
}); | |
//Removes the background possibly set by super method: | |
chart.__chartArea__.select("#innerBackground").attr("fill", "white"); | |
var totalBarsNumber = chart.__ticks__ * chart.__dataDim__; | |
//Adds background bars | |
chart.__selectBackgroundBars__() | |
.data(new Array(totalBarsNumber)) | |
.enter().append("rect") | |
.attr("type", "back") | |
//Data enter form right | |
.property("d_index", function(d,i){return Math.floor((totalBarsNumber - i - 1) / chart.__dataDim__);}) | |
.attr("x", function(d, i){return i * barWidth;}) | |
.attr("y", 0) | |
.attr("width", barWidth - 1) | |
.attr("height", height) | |
.attr("fill", chart.__innerBackgroundColor__); | |
chart.setVerticalAxe(true, "", 5, 2); //Default axe | |
return; //Private method, no need to return anything | |
} | |
__init__(slidingBarChart, slidingBarChart.__getChartAreaWidth__(), slidingBarChart.__getChartAreaHeight__()); | |
Object.seal(slidingBarChart); | |
return slidingBarChart; | |
} | |
/** | |
Advanced Chart: <b>TimeWheelChart</b><br> | |
Inherits from BasicBarChart redefining the drawing methods.<br> | |
<br> | |
Data is represented as bars drawn around a time wheel.<br> | |
<br> | |
It is possible to choose between having only a fixed number of values accepted, | |
or if a certain number of the oldest values should be removed when the | |
chart is full. | |
@class TimeWheelChart | |
@private | |
@beta | |
@extends FixedWidthBarChart | |
*/ | |
/** TimeWheelChart(ticks, startTime, wheelRadius, width, height [, chartMargins, dataDim, parent]) | |
TimeWheelChart (pseudo)Constructor. | |
@method TimeWheelChart | |
@param {Number} ticks [Mandatory] | |
The number of values that can be drawn at the same time (<b>can't be changed later</b>)<br> | |
Can be any value that is or can be converted to a positive integer. | |
@param {String} startTime [Mandatory] | |
The reference for the label of the first point.<br> | |
Should be an incrementable value. | |
@param {Number} width [Mandatory] | |
The desired width for the chart (<b>can't be changed later</b>)<br> | |
Can be any value that is or can be converted to a positive integer. | |
@param {Number} height [Mandatory] | |
The desired height for the chart (<b>can't be changed later</b>)<br> | |
Can be any value that is or can be converted to a positive integer. | |
@param {String} [chartMargins=""] [Optional] | |
A String of 0 to 4 space-separated values that specifies the 4 margins of the chart.<br> | |
The string should respect the following format: <b>"top right bottom left;"</b> (notice the trailing semicolon)<br> | |
If less then 4 values are passed, only the covered subfield will be assigned using the input string, | |
while the remaining ones will take a default value specified as an inner attribute of the class. | |
@param {Number} [dataDim=1] [Optional] | |
The dimension of the data space, i.e. the number of subvalues for each data entry;<br> | |
Can be any value that is or can be converted to an integer between 1 and MAX_SPACE_DIMENSION. | |
@param {Object} [parent=body] | |
[Optional] | |
The DOM element to which the diagram should be appended as a child | |
@return {Object} A new TimeWheelChart object | |
@throws | |
- Illegal Argument Exception, if ticks isn't a positive integer | |
- Wrong number of arguments Exception, if width or height are not passed as arguments (directly) | |
- Illegal Argument Exception, if width or height aren't valid (numeric, positive) values | |
(through setWidth or setHeight) | |
- Illegal Argument Exception, if dataDim is passed but it's invalid (not numeric or not positive) | |
- Exception, if dataDim exceeds the maximum data dimension | |
- Exception, if parent is passed but it is not a valid DOM element | |
*/ | |
function TimeWheelChart (ticks, startTime, wheelRadius, width, height, chartMargins, dataDim, parent){ | |
/** | |
Length of notch lines, in pixels; | |
@property TICK_LINES_LENGTH | |
@for TimeWheelChart | |
@type {Number} | |
@default = 5 | |
@final | |
@private | |
*/ | |
var TICK_LINES_LENGTH = 5; | |
/** | |
Margin between bars and their related labels; | |
@property BAR_TEXT_MARGIN | |
@for TimeWheelChart | |
@type {Number} | |
@default = 5 | |
@final | |
@private | |
*/ | |
var BAR_TEXT_MARGIN = 5; | |
var proto = FixedWidthBarChart(ticks, 0, width, height, chartMargins, dataDim, parent); | |
//proto_properties = | |
var timeWheelChart = Object.create(proto); | |
if (ChartUtils.validateTimeString(startTime)){ | |
/** | |
Label stating the time corresponding to the first tick; | |
@property __startTime__ | |
@type {String} | |
@protected | |
*/ | |
Object.defineProperty(timeWheelChart, "__startTime__", { | |
value: startTime, | |
writable: true, | |
enumerable: false, | |
configurable: false | |
}); | |
} | |
/** | |
Size in points of the static labels showing time references on the wheel; | |
@property __startTime__ | |
@type {String} | |
@protected | |
@default data labels' size | |
*/ | |
Object.defineProperty(timeWheelChart, "__timeWheelLabelsSize__", { | |
value: timeWheelChart.getLabelsSize(0), //Use the default value for value labels | |
writable: true, | |
enumerable: false, | |
configurable: false | |
}); | |
Object.defineProperty(timeWheelChart, "setTimeWheelLabelsSize", { | |
/** setTimeWheelLabelsSize(size) | |
Sets the size of the labels used for the wheel. | |
@method setTimeWheelLabelsSize | |
@chainable | |
@param {Number} size [Mandatory] | |
The new size for the labels (must be an integer gt zero); | |
@return {Object} This chart object, to allow for method chaining; | |
@throws | |
- Illegal Argument Exception, if the argument is not valid (see above). | |
*/ | |
value: function(size){ | |
size = parseInt(size, 10); | |
if (isNaN(size) || size <= 0){ | |
throw "Illegal Argument: size"; | |
} | |
if (this.hasOwnProperty("__timeWheelLabelsSize__")){ | |
this.__timeWheelLabelsSize__ = size; | |
}else{ | |
//Looks for object's prototype | |
var proto = this.prototype ? this.prototype : this.__proto__; | |
if (proto && proto.setTimeWheelLabelsSize){ | |
proto.setTimeWheelLabelsSize(size); | |
} | |
} | |
//Now must update the static part of the wheel chart | |
this.__updateWheelDrawing__(); | |
return this; //Method chaining support; | |
}, | |
writable: true, | |
enumerable: false, | |
configurable: false | |
}); | |
/** | |
Color used for the static part of the wheel | |
@property __startTime__ | |
@type {String|Object} | |
@protected | |
@default "lightgrey" | |
*/ | |
Object.defineProperty(timeWheelChart, "__timeWheelForeColor__", { | |
value: "lightgrey", //Defaults to lightgrey | |
writable: true, | |
enumerable: false, | |
configurable: false | |
}); | |
timeWheelChart.addPublicMethod("setTimeWheelForeColor", | |
/** setTimeWheelForeColor(color) | |
Sets the color used for the static part of the wheel's drawing, | |
i.e. for labels and lines representing time ticks | |
of the time wheel. | |
@method setTimeWheelForeColor | |
@chainable | |
@param {String|Object} color [Mandatory] | |
The new forecolor for the wheel; | |
@return {Object} This chart object, to allow for method chaining; | |
@throws {Illegal Argument Exception} If color isn't passed or is null. | |
*/ | |
function(color){ | |
if (Object.isUndefined(color) || color === null){ | |
throw "Illegal Argument: color"; | |
} | |
if (this.hasOwnProperty("__timeWheelForeColor__")){ | |
this.__timeWheelForeColor__ = color; | |
}else{ | |
//Looks for object's prototype | |
var proto = this.prototype ? this.prototype : this.__proto__; | |
if (proto && proto.setTimeWheelForeColor){ | |
proto.setTimeWheelForeColor(color); | |
} | |
} | |
//Now must update the static part of the wheel chart | |
this.__updateWheelDrawing__(); | |
return this; //Method chaining support; | |
}); | |
Object.defineProperty(timeWheelChart, "__timeLabelsVisible__", { | |
/** __timeLabelsVisible__() | |
Checks whether or not the labels showing time references on the wheel | |
should be drawn | |
@method __timeLabelsVisible__ | |
@protected | |
@return {Boolean} True <=> the time reference labels are visible. | |
*/ | |
value: function(){ | |
return !Object.isUndefined(this.__startTime__); | |
}, | |
writable: false, | |
enumerable: false, | |
configurable: false | |
}); | |
timeWheelChart.addPublicMethod("setBarWidth", | |
/** setBarWidth(barWidth) | |
Sets the width of this chart's bars. | |
@method setBarWidth | |
@chainable | |
@param {Number} barWidth [Mandatory] | |
The new bar width to be set;<br> | |
MUST be a positive number or its base 10 string representation. | |
@return {Object} This object, to allow for method chaining; | |
@throws Illegal Argument Exception, if the argument isn't valid (see above). | |
@override BasicBarChart.setBarWidth | |
*/ | |
function(barWidth){ | |
barWidth = parseInt(barWidth, 10); | |
if (isNaN(barWidth) || barWidth <= 0){ | |
throw "Illegal Argument: barWidth"; | |
} | |
this.__barWidth__ = barWidth; | |
return this; //Method chaining oriented | |
}); | |
timeWheelChart.addPublicMethod("setWheelCenter", | |
/** setWheelCenter(cx, cy) | |
Sets the position of the center of the wheel. | |
If it is valid and it is different from the current position, | |
the drawing is moved to the new position | |
@method setWheelCenter | |
@chainable | |
@param {Number} cx [Mandatory] | |
x coordinate of the new center;<br> | |
Only non negative integers or values that can be converted | |
to non negative integers are accepted; | |
@param {Number} cy [Mandatory] | |
y coordinate of the new center;<br> | |
Only non negative integers or values that can be converted | |
to non negative integers are accepted; | |
@return {Object} This chart object, to allow for method chaining; | |
@throws | |
- Illegal Argument Exception, if cx or cy aren't valid. | |
*/ | |
function(cx, cy){ | |
cx = parseInt(cx, 10); | |
cy = parseInt(cy, 10); | |
if (!isNaN(cx) && !isNaN(cy)){ | |
this.__moveWheelCenter__(cx, cy); | |
}else{ | |
throw "Illegal Arguments: cx, cy"; | |
} | |
return this; //Method chaining oriented | |
}); | |
Object.defineProperty(timeWheelChart, "__moveWheelCenter__", { | |
/** __moveWheelCenter__(cx, cy) | |
When the center of the time wheel is moved, | |
it takes care of all the updates needed for the chart | |
@method __moveWheelCenter__ | |
@protected | |
@param {Number} cx [Mandatory] | |
x coordinate of the new center; | |
@param {Number} cy [Mandatory] | |
y coordinate of the new center; | |
@return {undefined} | |
*/ | |
value: function(cx, cy){ | |
if (!Object.isNumber(cx) || !Object.isNumber(cy) || //Extra precaution, since it's not really a "private" method | |
(this.__cx__ === cx && this.__cy__ === cy)){ //If values doesn't change, no reason to hassle | |
return; //(Pseudo)Private method, no need to return this | |
} | |
//else | |
this.__wheel__.transition()//.delay(250) | |
.attr("x", cx - this.__r__).attr("y", cy - this.__r__); | |
this.__cx__ = cx; | |
this.__cy__ = cy; | |
//Now updates all the bars | |
var newDataLength = this.__getDatasetLength__() * this.__dataDim__; | |
//The max is recomputed every time to retain the ability to switch on the fly between scaling locally and globally | |
var max_val; | |
if (this.__scaleGlobally__){ | |
max_val = ChartUtils.fillArray(this.__maxVals__.max(), this.__dataDim__); | |
}else{ | |
max_val = this.__maxVals__; //Values aren't going to be modified, so we can just copy the reference | |
} | |
for (var j = 0; j < this.__dataDim__; j++){ | |
var dataSet = this.__selectData__(this.__data__, j); | |
var labelsSet = this.__selectLabels__(this.__data__, j); | |
//Computes the new X and Y scale | |
this.__xScale__.domain([0, newDataLength]); | |
this.__yScale__[j].domain([0, max_val[j]]); | |
this.__updateDrawing__(dataSet, labelsSet, j, this.__xScale__, this.__yScale__[j]); | |
} | |
return; //(Pseudo)Private method, no need to return this | |
}, | |
writable: false, | |
enumerable: false, | |
configurable:false | |
}); | |
Object.defineProperty(timeWheelChart, "__drawNewData__", { | |
/** __drawNewData__(dataSet, labelsSet, dataIndex, xScale, yScale) | |
<b>WARNING</b>: if you inherit from this class you might want to override both | |
this method and __updateDrawing__ in order to obtain a custom chart.<br> | |
See base class for method signature and details. | |
@method __drawNewData__ | |
@protected | |
@override FixedWidthBarChart.__drawNewData__ | |
*/ | |
value: function(dataSet, labelsSet, dataIndex, xScale, yScale){ | |
var that = this; | |
var initial_x = that.__cx__ + dataIndex * (that.__barWidth__ + 1), | |
initial_y = that.__cy__ - that.__r__ ; | |
dataSet.enter().append("rect").attr("index", "data_" + dataIndex) | |
.attr("x", initial_x) | |
.attr("y", initial_y) | |
.attr("width", that.__barWidth__) | |
.attr("height", 0) | |
.attr("transform", function(d, i){ return "rotate(" + (180 / Math.PI * (2 * i * that.__tickStep__)) + | |
" " + that.__cx__ + ", " + that.__cy__ + ")";}) | |
.attr("fill", that.getBarsFillColor(dataIndex)) | |
.attr("stroke", that.getBarsStrokeColor(dataIndex)) | |
.attr("opacity", function(d){return that.__getBarOpacity__((0.0 + yScale(d)) / that.__barHeight__);}); | |
if (that.areLabelsVisible(dataIndex) ){ | |
initial_x += that.getBarWidth(); | |
initial_y -= BAR_TEXT_MARGIN; | |
labelsSet.enter() | |
.append("text").attr("index", "data_" + dataIndex) | |
.text(function(d) {return that.__makeLabel__(d, dataIndex);}) //("" + d).split("").reverse().join("") ;}) | |
.attr("text-anchor", "left") | |
.attr("x", initial_x) | |
.attr("y", function(){return initial_y;}) | |
.attr("transform", function(d, i){ | |
return "rotate(" + (180 / Math.PI * (2 * i * that.__tickStep__)) + | |
" " + that.__cx__ + ", " + that.__cy__ + ")" + | |
"rotate(-90 " + initial_x + ", " + initial_y +")"; | |
}) | |
.attr("font-family", "sans-serif") | |
.attr("font-size", that.getLabelsSize(dataIndex)) | |
.attr("fill", that.getLabelColor(dataIndex)); | |
//.attr("class", "wheelText") | |
}else{ | |
labelsSet.remove(); | |
} | |
return; //(Pseudo)Private method, no need to return this | |
}, | |
writable: false, | |
enumerable: false, | |
configurable:false | |
}); | |
Object.defineProperty(timeWheelChart, "__updateDrawing__", { | |
/** __updateDrawing__(dataSet, labelsSet, dataIndex, xScale, yScale) | |
<b>WARNING</b>: if you inherit from this class you might want to override both | |
this method and __drawNewData__ in order to obtain a custom chart.<br> | |
See base class for method signature and details. | |
@method __updateDrawing__ | |
@protected | |
@override FixedWidthBarChart.__updateDrawing__ | |
*/ | |
value: function(dataSet, labelsSet, dataIndex, xScale, yScale){ | |
var that = this; | |
var initial_x = that.__cx__ + dataIndex * (that.__barWidth__ + 1); | |
dataSet.transition() | |
.attr("x", initial_x) | |
.attr("y", function(d){return that.__cy__ - that.__r__ - yScale(d);}) | |
.attr("height", function(d){return yScale(d);}) | |
.attr("transform", function(d, i){ return "rotate(" + (180 / Math.PI * (2 * i * that.__tickStep__)) + | |
" " + that.__cx__ + ", " + that.__cy__ + ")";}) | |
.attr("opacity", function(d){return that.__getBarOpacity__((0.0 + yScale(d)) / that.__barHeight__);}); | |
if (that.areLabelsVisible(dataIndex)){ | |
initial_x += that.getBarWidth(); | |
labelsSet.transition() | |
.attr("y", function(d){return that.__cy__ - that.__r__ - yScale(d) - BAR_TEXT_MARGIN; }) | |
.attr("x", initial_x) | |
.attr("transform", function(d, i){ | |
return "rotate(" + (180 / Math.PI * (2 * i * that.__tickStep__)) + | |
" " + that.__cx__ + ", " + that.__cy__ + ")" + | |
"rotate(-90 " + initial_x + ", " + (that.__cy__ - that.__r__ - yScale(d) - BAR_TEXT_MARGIN) +")"; | |
}); | |
}else{ | |
labelsSet.remove(); | |
} | |
return; //(Pseudo)Private method, no need to return this | |
}, | |
writable: false, | |
enumerable: false, | |
configurable:false | |
}); | |
timeWheelChart.addProtectedMethod("__refreshDrawing__", | |
/** __refreshDrawing__(dataSet, labelsSet, dataIndex, xScale, yScale) | |
Called by __redrawAll__() to redraw all the data-related drawings, once for | |
every data subcomponent.<br> | |
The difference with __updateDrawing__ is that the latter is incremental with respect to | |
__drawNewData__ and updates only the properties used to provide animations of the drawing, | |
while this method redraws from scratch the data. | |
<br> | |
<b>WARNING</b>: if you inherit from this class you might want to override both | |
this method following __updateDrawing__ behaviour in order to obtain a custom chart. | |
@method __refreshDrawing__ | |
@protected | |
@override FixedWidthBarChart.__refreshDrawing__ | |
*/ | |
function __refreshDrawing__(dataSet, labelsSet, dataIndex, xScale, yScale){ | |
var that = this; | |
var initial_x = that.__cx__ + dataIndex * (that.__barWidth__ + 1), | |
initial_y = that.__cy__ - that.__r__ ; | |
dataSet.transition() | |
.attr("x", initial_x) | |
.attr("y", function(d){return that.__cy__ - that.__r__ - yScale(d);}) | |
.attr("height", function(d){return yScale(d);}) | |
.attr("transform", function(d, i){ return "rotate(" + (180 / Math.PI * (2 * i * that.__tickStep__)) + | |
" " + that.__cx__ + ", " + that.__cy__ + ")";}) | |
.attr("fill", that.getBarsFillColor(dataIndex)) | |
.attr("stroke", that.getBarsStrokeColor(dataIndex)) | |
.attr("opacity", function(d){return that.__getBarOpacity__((0.0 + yScale(d)) / that.__barHeight__);}); | |
if (that.areLabelsVisible(dataIndex) ){ | |
initial_x += that.getBarWidth(); | |
initial_y -= BAR_TEXT_MARGIN; | |
labelsSet.transition() | |
.text(function(d) {return that.__makeLabel__(d, dataIndex);}) //("" + d).split("").reverse().join("") ;}) | |
.attr("text-anchor", "left") | |
.attr("x", initial_x) | |
.attr("y", function(d){return that.__cy__ - that.__r__ - yScale(d) - BAR_TEXT_MARGIN; }) | |
.attr("transform", function(d, i){ | |
return "rotate(" + (180 / Math.PI * (2 * i * that.__tickStep__)) + | |
" " + that.__cx__ + ", " + that.__cy__ + ")" + | |
"rotate(-90 " + initial_x + ", " + (that.__cy__ - that.__r__ - yScale(d) - BAR_TEXT_MARGIN) +")"; | |
}) | |
.attr("font-family", "sans-serif") | |
.attr("font-size", that.getLabelsSize(dataIndex)) | |
.attr("fill", that.getLabelColor(dataIndex)); | |
//.attr("class", "wheelText") | |
}else{ | |
labelsSet.remove(); | |
} | |
return; //(Pseudo)Private method, no need to return this | |
}); | |
Object.defineProperty(timeWheelChart, "__onClearData__", { | |
/** __onClearData__(n) | |
[Protected method, not supposed to be used by consumers] | |
<b>WARNING</b>: Inherited objects MIGHT NEED to override this function<br> | |
See base class for method signature and details. | |
@method __onClearData__ | |
@protected | |
@override BasicBarChart.__onClearData__ | |
*/ | |
value: function(n){ | |
this.__timeLabels__.map(function(label){label.text(ChartUtils.addIntToTimeString(label.text(), n));}); | |
return; //(Pseudo)Private method, no need to return this | |
}, | |
writable: false, | |
enumerable: false, | |
configurable:false | |
}); | |
Object.defineProperty(timeWheelChart, "__updateWheelDrawing__", { | |
/** __updateWheelDrawing__() | |
Updates the drawing of the static elements of the wheel<br> | |
<br> | |
<b>WARNING</b>: Inherited objects MIGHT NEED to override this function | |
@method __updateWheelDrawing__ | |
@protected | |
@return {undefined} | |
*/ | |
value: function(){ | |
var that = this; | |
var tmpLabel; | |
//Update labels text | |
this.__timeLabels__.map(function(label){ | |
label.attr("fill", that.__timeWheelForeColor__) | |
.attr("font-size", that.__timeWheelLabelsSize__); | |
}); | |
//Now updates their position | |
for (var i=0; i < this.__timeLabels__.length; i++){ | |
tmpLabel = this.__timeLabels__[i]; | |
switch(tmpLabel.property("clock_time")){ | |
case "12": | |
tmpLabel | |
.attr("x", this.__r__) | |
.attr("y", TICK_LINES_LENGTH + this.__timeWheelLabelsSize__); | |
break; | |
case "3": | |
tmpLabel | |
.attr("x", 2 * this.__r__ - TICK_LINES_LENGTH - this.__timeLabels__[i].node().getComputedTextLength() ) | |
.attr("y", this.__r__ + this.__timeWheelLabelsSize__ / 2); | |
break; | |
case "6": | |
tmpLabel | |
.attr("x", this.__r__) | |
.attr("y", 2 * this.__r__ - TICK_LINES_LENGTH); | |
break; | |
case "9": | |
tmpLabel | |
.attr("x", TICK_LINES_LENGTH ) | |
.attr("y", this.__r__ + this.__timeWheelLabelsSize__ / 2); | |
break; | |
} | |
} | |
//Updates wheel tick lines | |
this.__wheel__.selectAll("line") | |
.attr("stroke", this.__timeWheelForeColor__); | |
//Updates wheel tick lines | |
this.__wheel__.selectAll("circle") | |
.attr("stroke", this.__timeWheelForeColor__); | |
return; //(Pseudo)Private method, no need to return this | |
}, | |
writable: false, | |
enumerable: false, | |
configurable:false | |
}); | |
timeWheelChart.addProtectedMethod("__updateAxes__", | |
/** __updateAxes__(yScale) | |
Called by appendData() to update the labels of the vertical axe | |
when vertical scale changes;<br> | |
<br> | |
<b>WARNING</b>: if you inherit from this class you might want to override | |
this method as well as __drawNewData__ and __updateDrawing__ | |
in order to obtain a custom chart. | |
@method __updateAxes__ | |
@protected | |
@chainable | |
@override FixedWidthBarChart.__updateAxes__ | |
@param {Object} yScale [Mandatory] | |
D3 scale object for Y axis; | |
@return {undefined} | |
*/ | |
function __updateAxes__(/*yScale*/){ | |
//Nothing to do for this class | |
return; //(Pseudo)Private method, no need to return this | |
}); | |
timeWheelChart.addPublicMethod("setHorizontalAxe", | |
/** setHorizontalAxe() | |
@method setHorizontalAxe | |
@protected | |
@deprecated Ortogonal axes can't be added for charts of this class. | |
@override BasicBarChart.setHorizontalAxe | |
@return {Exception} To make explicit the fact that this method <b>isn't available</b> for this class. | |
@throws {Deprecated Method Exception} | |
*/ | |
function setHorizontalAxe(){ | |
//Nothing to do for this class | |
throw "Deprecated Method"; | |
}); | |
timeWheelChart.addPublicMethod("setVerticalAxe", | |
/** setVerticalAxe() | |
@method setVerticalAxe | |
@protected | |
@deprecated Ortogonal axes can't be added for charts of this class. | |
@override BasicBarChart.setVerticalAxe | |
@return {Exception} To make explicit the fact that this method <b>isn't available</b> for this class. | |
@throws {Deprecated Method Exception} | |
*/ | |
function setVerticalAxe(){ | |
//Nothing to do for this class | |
throw "Deprecated Method"; | |
}); | |
Object.defineProperty(timeWheelChart, "destroy", { | |
/** destroy() | |
Object's destructor: helps garbage collector freeing memory, and removes chart DOM elements.<br> | |
<br> | |
<b>WARNING</b>: calling destroy on an object will force any further reference | |
to its attributes / methods to throw exceptions.<br> | |
<br> | |
<b>NOTE</b>: This function should be override by any class inheriting from this chart.<br> | |
In order to properly work, any overriding destroyer should: | |
<ol> | |
<li> Free any array specific to the object on which is called;</li> | |
<li> Remove any event listener on chart objects;</li> | |
<li> Call super object's destroy method.</li> | |
</ol> | |
@method destroy | |
@return {null} to state that the object has been destroyed. | |
@override BasicBarChart.destroy() | |
*/ | |
value: function(){ | |
//Deletes all the elements from object's arrays | |
if (this.__timeLabels__){ | |
this.__timeLabels__.length = 0; | |
} | |
//Removes DOM objects | |
this.__wheel__.remove(); | |
//Looks for object's prototype destructor | |
var proto = this.prototype ? this.prototype : this.__proto__; | |
if (proto && proto.destroy){ | |
proto.destroy(); | |
} | |
return null; | |
}, | |
writable: false, | |
enumerable: false, | |
configurable: false | |
}); | |
/** __init__() | |
Inits the chart; | |
@method __init__ | |
@private | |
@param {Object} chart [Mandatory] | |
The chart that need initialization; | |
@param {Number} width [Mandatory] | |
Chart's width; | |
@param {Number} height [Mandatory] | |
Chart's height; | |
@param {Number} wheelRadius [Mandatory] | |
Wheel inner radius; | |
@return {undefined} | |
*/ | |
function __init__(chart, width, height, wheelRadius){ | |
var __r__, __barHeight__; | |
//Computes drawing related object contants | |
/** | |
Chart's bars' width, in pixel <br> | |
Can be changed at runtime | |
@property __barWidth__ | |
@type {Number} | |
@protected | |
@default 8 | |
@override FixedWidthBarChart.__barWidth__ | |
*/ | |
Object.defineProperty(chart, "__barWidth__", { | |
value: 8, | |
writable: true, | |
enumerable: false, | |
configurable: false | |
}); | |
/** | |
X coordinate of the center of the wheel | |
Can be changed at runtime | |
@property __cx__ | |
@type {Number} | |
@protected | |
@default the horizontal center of the chart | |
*/ | |
Object.defineProperty(chart, "__cx__", { | |
value: width / 2, //Aligns left by default | |
writable: true, //to allow enough space for the legend | |
enumerable: false, | |
configurable:false | |
}); | |
/** | |
Y coordinate of the center of the wheel<br> | |
Can be changed at runtime | |
@property __cy__ | |
@type {Number} | |
@protected | |
@default the vertical center of the chart | |
*/ | |
Object.defineProperty(chart, "__cy__", { | |
value: height / 2, | |
writable: true, | |
enumerable: false, | |
configurable:false | |
}); | |
wheelRadius = parseInt(wheelRadius, 10); | |
if (!isNaN(wheelRadius) && wheelRadius > 0){ | |
//If a value for the wheel radius is set, then computes the height accordingly... | |
__r__ = wheelRadius; | |
__barHeight__ = ((Math.min(width, height) - 2 * wheelRadius) / 2) * 0.75; | |
}else{ | |
//...Otherwise makes the radius 3/4 of the available height; | |
__barHeight__ = Math.min(width, height) / 4; | |
__r__ = __barHeight__ * 0.75; | |
} | |
/** | |
Radius of the wheel<br> | |
<b>CAN NOT</b> be changed at runtime | |
@property __r__ | |
@type {Number} | |
@protected | |
@readOnly | |
*/ | |
Object.defineProperty(chart, "__r__", { | |
value: __r__, | |
writable: false, | |
enumerable: false, | |
configurable:false | |
}); | |
/** | |
Maximum height for each bar<br> | |
<b>CAN NOT</b> be changed at runtime | |
@property __barHeight__ | |
@type {Number} | |
@protected | |
@readOnly | |
*/ | |
Object.defineProperty(chart, "__barHeight__", { | |
value: __barHeight__, | |
writable: false, | |
enumerable: false, | |
configurable:false | |
}); | |
//Modify the range for each of the data components | |
for (var i=0; i < chart.__dataDim__; i++){ | |
chart.__yScale__[i].range([0, chart.__barHeight__]); | |
} | |
//Computes the angle between two consecutive ticks | |
/** | |
The angle between two consecutive ticks<br> | |
<b>CAN NOT</b> be changed at runtime | |
@property __tickStep__ | |
@type {Number} | |
@protected | |
@readOnly | |
*/ | |
Object.defineProperty(chart, "__tickStep__", { | |
value: Math.PI / (chart.__ticks__), // == 2 * Math.PI / (chart.__ticks__ * 2) | |
writable: false, | |
enumerable: false, | |
configurable:false | |
}); | |
var initial_x = chart.__cx__ - chart.__r__, | |
initial_y = chart.__cy__ - chart.__r__; | |
/** | |
The actual svg object for the static part of the wheel | |
@property __wheel__ | |
@type {Object} | |
@protected | |
@readOnly | |
*/ | |
Object.defineProperty(chart, "__wheel__", { | |
value: chart.__chartArea__.append("svg") | |
.attr("id", "timeWheel") | |
.attr("x", initial_x) | |
.attr("y", initial_y), | |
writable: false, | |
enumerable: false, | |
configurable:false | |
}); | |
//Appends an inner circle to represent the wheel | |
chart.__wheel__.append("circle") | |
.attr("id", "timeWheel") | |
.attr("cx", chart.__r__) | |
.attr("cy", chart.__r__) | |
.attr("stroke", chart.__timeWheelForeColor__) | |
.attr("stroke-dasharray", "2, 4") | |
.attr("fill", "none") | |
.attr("r", chart.__r__) | |
.attr("stroke-width", 1); | |
chart.__wheel__.selectAll("line").data(d3.range(chart.__ticks__)) | |
.enter() | |
.append("svg:line") | |
.attr("x1", chart.__r__) | |
.attr("y1", 0) | |
.attr("x2", chart.__r__) | |
.attr("y2", TICK_LINES_LENGTH) | |
.attr("stroke", chart.__timeWheelForeColor__) | |
.attr("transform", function(d, i){ | |
return "rotate(" + (180 / Math.PI * (2 * i * chart.__tickStep__)) + | |
" " + chart.__r__ + ", " + chart.__r__ +")"; | |
}); | |
if (chart.__timeLabelsVisible__()){ | |
var timeLabels = []; | |
timeLabels.push( | |
chart.__wheel__ | |
.append("text") | |
.property("clock_time", "12") | |
.text(chart.__startTime__) | |
.attr("font-family", "sans-serif") | |
.attr("font-size", chart.__timeWheelLabelsSize__) | |
.attr("fill", chart.__timeWheelForeColor__) | |
.attr("text-anchor", "middle") | |
.attr("x", chart.__r__) | |
.attr("y", TICK_LINES_LENGTH + chart.__timeWheelLabelsSize__) | |
); | |
//For the "3 o'clock" label we must take particular care because it needs to be aligned "right" | |
//So we need to move it after creation, once we know its size | |
var tmpLabel = chart.__wheel__ | |
.append("text") | |
.property("clock_time", "3") | |
.text(ChartUtils.addIntToTimeString(chart.__startTime__, Math.floor(chart.__ticks__/4))) | |
.attr("font-family", "sans-serif") | |
.attr("font-size", chart.__timeWheelLabelsSize__) | |
.attr("fill", chart.__timeWheelForeColor__) | |
.attr("text-anchor", "left"); | |
timeLabels.push(tmpLabel); | |
tmpLabel.attr("x", 2 * chart.__r__ - TICK_LINES_LENGTH - tmpLabel.node().getComputedTextLength() ) | |
.attr("y", chart.__r__ + chart.__timeWheelLabelsSize__ / 2); | |
timeLabels.push( | |
chart.__wheel__ | |
.append("text") | |
.property("clock_time", "6") | |
.text(ChartUtils.addIntToTimeString(chart.__startTime__, chart.__ticks__/2)) | |
.attr("font-family", "sans-serif") | |
.attr("font-size", chart.__timeWheelLabelsSize__) | |
.attr("fill", chart.__timeWheelForeColor__) | |
.attr("text-anchor", "middle") | |
.attr("x", chart.__r__) | |
.attr("y", 2 * chart.__r__ - TICK_LINES_LENGTH) | |
); | |
timeLabels.push( | |
chart.__wheel__ | |
.append("text") | |
.property("clock_time", "9") | |
.text(ChartUtils.addIntToTimeString(chart.__startTime__, Math.floor(3 * chart.__ticks__ / 4))) | |
.attr("font-family", "sans-serif") | |
.attr("font-size", chart.__timeWheelLabelsSize__) | |
.attr("fill", chart.__timeWheelForeColor__) | |
.attr("text-anchor", "left") | |
.attr("x", TICK_LINES_LENGTH ) | |
.attr("y", chart.__r__ + chart.__timeWheelLabelsSize__ / 2) | |
); | |
/** | |
List of labels for wheel's time references | |
@property __timeLabels__ | |
@type {Array} | |
@protected | |
@readOnly | |
*/ | |
Object.defineProperty(chart, "__timeLabels__", { | |
value: timeLabels, | |
writable: false, | |
enumerable: false, | |
configurable:false | |
}); | |
} | |
return ; | |
} | |
__init__(timeWheelChart, timeWheelChart.__getChartAreaWidth__(), timeWheelChart.__getChartAreaHeight__(), wheelRadius); | |
Object.seal(timeWheelChart); | |
return timeWheelChart; | |
} | |
var modulePrototype = { | |
/** | |
@method BasicBarChart | |
@for DynamicChart | |
@beta | |
@chainable | |
@param {Number} width [Mandatory] | |
The desired width for the chart (<b>can't be changed later</b>)<br> | |
Can be any value that is or can be converted to a positive integer. | |
@param {Number} height [Mandatory] | |
The desired height for the chart (<b>can't be changed later</b>)<br> | |
Can be any value that is or can be converted to a positive integer. | |
@param {String} [chartMargins=""] [Optional] | |
A String of 0 to 4 space-separated values that specifies the 4 margins of the chart.<br> | |
The string should respect the following format: <b>"top right bottom left;"</b> (notice the trailing semicolon)<br> | |
If less then 4 values are passed, only the covered subfield will be assigned using the input string, | |
while the remaining ones will take a default value specified as an inner attribute of the class. | |
@param {Number} [dataDim=1] [Optional] | |
The dimension of the data space, i.e. the number of subvalues for each data entry<br> | |
Can be any value that is or can be converted to an integer between 1 and MAX_SPACE_DIMENSION. | |
@param {Object} [parent=body] [Optional] | |
The DOM element to which the diagram should be appended as a child | |
@return {Object} A (wrapped-in-a-proxy version of a) BasicBarChart object | |
@throws | |
- Wrong number of arguments Exception, if width or height are not passed as arguments (directly) | |
- Illegal Argument exception , if width or height aren't valid (numeric, positive) values | |
(through setWidth or setHeight) | |
- Illegal Argument exception, if dataDim is passed but it's invalid (not numeric or not positive) | |
- Exception, if dataDim exceeds the maximum data dimension | |
- Exception, if parent is passed but it is not a valid DOM element | |
*/ | |
BasicBarChart: function(){ | |
return BasicBarChart.apply(null, Array.prototype.slice.apply(arguments, [0])).createSafeProxy(true); | |
}, | |
/** | |
@method FixedWidthBarChart | |
@for DynamicChart | |
@beta | |
@chainable | |
@param {Number} ticks [Mandatory] | |
The number of values that can be drawn at the same time (<b>can't be changed later</b>)<br> | |
Can be any value that is or can be converted to a positive integer. | |
@param {Number} startingPoint [Mandatory, but not used at the moment: inserted for future back-compatibility]<br> | |
The reference for the label of the first point.<br> | |
Should be an incrementable value; | |
@param {Number} width [Mandatory] | |
The desired width for the chart (<b>can't be changed later</b>)<br> | |
Can be any value that is or can be converted to a positive integer. | |
@param {Number} height [Mandatory] | |
The desired height for the chart (<b>can't be changed later</b>)<br> | |
Can be any value that is or can be converted to a positive integer. | |
@param {String} [chartMargins=""] [Optional] | |
A String of 0 to 4 space-separated values that specifies the 4 margins of the chart.<br> | |
The string should respect the following format: <b>"top right bottom left;"</b> (notice the trailing semicolon)<br> | |
If less then 4 values are passed, only the covered subfield will be assigned using the input string, | |
while the remaining ones will take a default value specified as an inner attribute of the class. | |
@param {Number} [dataDim=1] [Optional] | |
The dimension of the data space, i.e. the number of subvalues for each data entry<br> | |
Can be any value that is or can be converted to an integer between 1 and MAX_SPACE_DIMENSION. | |
@param {Object} [parent=body] [Optional] | |
The DOM element to which the diagram should be appended as a child | |
@return {Object} A properly initialized (wrapped-in-a-proxy version of a) FixedWidthBarChart object | |
@throws | |
- Illegal Argument Exception, if ticks isn't a positive integer | |
- Wrong number of arguments Exception, if width or height are not passed as arguments (directly) | |
- Illegal Argument Exception, if width or height aren't valid (numeric, positive) values | |
(through setWidth or setHeight) | |
- Illegal Argument Exception, if dataDim is passed but it's invalid (not numeric or not positive) | |
- Exception, if dataDim exceeds the maximum data dimension | |
- Exception, if parent is passed but it is not a valid DOM element | |
*/ | |
FixedWidthBarChart: function(){ | |
return FixedWidthBarChart.apply(null, Array.prototype.slice.apply(arguments, [0])).createSafeProxy(true); | |
}, | |
/** | |
@method SlidingBarChart | |
@for DynamicChart | |
@beta | |
@chainable | |
@param {Number} ticks [Mandatory] | |
The number of values that can be drawn at the same time (<b>can't be changed later</b>)<br> | |
Can be any value that is or can be converted to a positive integer. | |
@param {Number} width [Mandatory] | |
The desired width for the chart (<b>can't be changed later</b>)<br> | |
Can be any value that is or can be converted to a positive integer. | |
@param {Number} height [Mandatory] | |
The desired height for the chart (<b>can't be changed later</b>)<br> | |
Can be any value that is or can be converted to a positive integer. | |
@param {String} [chartMargins=""] [Optional] | |
A String of 0 to 4 space-separated values that specifies the 4 margins of the chart.<br> | |
The string should respect the following format: <b>"top right bottom left;"</b> (notice the trailing semicolon)<br> | |
If less then 4 values are passed, only the covered subfield will be assigned using the input string, | |
while the remaining ones will take a default value specified as an inner attribute of the class. | |
For this particular chart the right margin can't be less than AXES_LABEL_WIDTH pixel wide (if a smaller | |
value is passed, it will be overwritten). | |
@param {Number} [dataDim=1] [Optional] | |
The dimension of the data space, i.e. the number of subvalues for each data entry <br> | |
Can be any value that is or can be converted to an integer between 1 and MAX_SPACE_DIMENSION. | |
@param {Object} [parent=body] The DOM element to which the diagram should be appended as a child | |
@return {Object} A properly initialized (wrapped-in-a-proxy version of a) SlidingBarChart object | |
@throws | |
- Illegal Argument Exception, if ticks isn't a positive integer | |
- Wrong number of arguments Exception, if width or height are not passed as arguments (directly) | |
- Illegal Argument Exception, if width or height aren't valid (numeric, positive) values | |
(through setWidth or setHeight) | |
- Illegal Argument Exception, if dataDim is passed but it's invalid (not numeric or not positive) | |
- Exception, if dataDim exceeds the maximum data dimension | |
- Exception, if the ratio between chart's width and number of ticks is such | |
that the computed bar height is smaller than 1 pixel | |
- Exception, if parent is passed but it is not a valid DOM element | |
*/ | |
SlidingBarChart: function(){ | |
return SlidingBarChart.apply(null, Array.prototype.slice.apply(arguments, [0])).createSafeProxy(true); | |
}, | |
/** | |
@method TimeWheelChart | |
@for DynamicChart | |
@beta | |
@chainable | |
@param {Number} ticks [Mandatory] | |
The number of values that can be drawn at the same time (<b>can't be changed later</b>) | |
Can be any value that is or can be converted to a positive integer. | |
@param {String} startTime [Mandatory] | |
The reference for the label of the first point.<br> | |
Should be an incrementable value. | |
@param {Number} width [Mandatory] | |
The desired width for the chart (<b>can't be changed later</b>)<br> | |
Can be any value that is or can be converted to a positive integer. | |
@param {Number} height [Mandatory] | |
The desired height for the chart (<b>can't be changed later</b>)<br> | |
Can be any value that is or can be converted to a positive integer. | |
@param {String} [chartMargins=""] [Optional] | |
A String of 0 to 4 space-separated values that specifies the 4 margins of the chart.<br> | |
The string should respect the following format: <b>"top right bottom left;"</b> (notice the trailing semicolon)<br> | |
If less then 4 values are passed, only the covered subfield will be assigned using the input string, | |
while the remaining ones will take a default value specified as an inner attribute of the class.<br> | |
@param {Number} [dataDim=1] [Optional] | |
The dimension of the data space, i.e. the number of subvalues for each data entry<br> | |
Can be any value that is or can be converted to an integer between 1 and MAX_SPACE_DIMENSION. | |
@param {Object} [parent=body] [Optional] | |
The DOM element to which the diagram should be appended as a child<br> | |
@return {Object} A properly initialized (wrapped-in-a-proxy version of a) TimeWheelChart object | |
@throws | |
- Illegal Argument Exception, if ticks isn't a positive integer | |
- Wrong number of arguments Exception, if width or height are not passed as arguments (directly) | |
- Illegal Argument Exception, if width or height aren't valid (numeric, positive) values | |
(through setWidth or setHeight) | |
- Illegal Argument Exception, if dataDim is passed but it's invalid (not numeric or not positive) | |
- Exception, if dataDim exceeds the maximum data dimension | |
- Exception, if parent is passed but it is not a valid DOM element | |
*/ | |
TimeWheelChart: function(){ | |
return TimeWheelChart.apply(null, Array.prototype.slice.apply(arguments, [0])).createSafeProxy(true); | |
} | |
}; | |
Object.freeze(modulePrototype); | |
return Object.create(modulePrototype); | |
})(); | |
Object.freeze(DynamicChart); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<html> | |
<head> | |
<title>Dynamic Charts Showcase - by Marcello La Rocca</title> | |
</head> | |
<body> | |
<script type="text/javascript" src="http://d3js.org/d3.v2.js"></script> | |
<script type="text/javascript" src="chart_utils.js"></script> | |
<script type="text/javascript" src="dynamic_chart.js"></script> | |
<script type="text/javascript" src="test.js"></script> | |
</body> | |
</html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function simulation(startTime, r, w, h){ | |
"use strict"; | |
startTime = startTime === undefined ? "12:20" : startTime; | |
r = r === undefined ? 150 : r; | |
w = w === undefined ? 1024 : w; | |
h = h === undefined ? 600 : h; | |
var tw = DynamicChart.TimeWheelChart(40, startTime, r, w, h, "30 100 10 100;", 2).setPosition(10, 5) | |
.setBarsFillColor("cornflowerblue", 0).setBarsFillColor("hotpink", 1) | |
.setOuterBackgroundColor("lavenderblush") | |
.setInnerBorder("#E50000", 2, "3 2 3 1") | |
.setOuterBorder("black", 1) | |
//.setWheelCenter(512, 300) | |
.setShifitingDataMode(10) | |
.setAbbreviatedLabel(0) | |
.setTitle("Aggregates per minute", 25, "#DD0000") | |
.addLegend(["Males", "Females"], 130, 45, 20, 20); | |
//.setLocalScaling(); | |
var sb = DynamicChart.SlidingBarChart(50, w, 200, "25 60 35 1;", 2) | |
.setPosition(10, h + 10) | |
.setBarsFillColor("cornflowerblue") | |
.setBarsFillColor("hotpink", 1) | |
.setInnerBackgroundColor("lightgrey") | |
.setInnerBackgroundHighlightColor("gold") | |
.setOuterBackgroundColor("antiquewhite") | |
.setOuterBorder("black", 1) | |
.setTicksBetweenHighlights(10) | |
.setTitle("Real time values updated every second", 18, "firebrick") | |
.setVerticalAxe(true, "Tweets/sec", 5, 2, "red") | |
.setHorizontalAxe(false, "Time (sec.)", 20, 2, "#5B48FF"); | |
var males = [], females = [], dt = 200, t = dt; | |
var timer = setInterval(function(){ | |
var x = parseInt(Math.floor(Math.random()*(101*Math.log(t))), 10); | |
var y = parseInt(Math.floor(Math.random()*(101*Math.log(t))), 10); | |
males.push(y); | |
females.push(x); | |
sb.appendData([[y, x]]); | |
if (t % 2000 === 0){ | |
var y_s = males.sum(); | |
var x_s = females.sum(); | |
males.length = 0; | |
females.length = 0; | |
tw.appendData([[y_s, x_s]]); | |
} | |
t += dt; | |
}, dt); | |
function stop(){ | |
clearInterval(timer); | |
} | |
return stop; | |
} | |
var simulation_stop = simulation(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
A lib built upon D3js to help plotting real-time data with almost no effort
Version 0.2
Fork me on github: https://github.com/mlarocca/Dynamic-Charts/