Skip to content

Instantly share code, notes, and snippets.

@mlarocca
Last active December 9, 2015 22:48
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mlarocca/4340039 to your computer and use it in GitHub Desktop.
Save mlarocca/4340039 to your computer and use it in GitHub Desktop.
Dynamic Charts Showcase - by Marcello La Rocca
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;
})();
}
//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);
}
<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_2.js"></script>
</body>
</html>
function full_simulation(startTime, r, w, h){
"use strict";
startTime = startTime === undefined ? "00:00" : startTime;
r = r === undefined ? 50 : r;
w = w === undefined ? 640 : w;
h = h === undefined ? 480 : h;
var MARGIN = 10;
var tw = DynamicChart.TimeWheelChart(40, startTime, r, w, h, "30;", 2)
.setPosition(MARGIN, MARGIN)
.setBarsFillColor("cornflowerblue", 0).setBarsFillColor("hotpink", 1)
.setLabelColor("blue", 0).setLabelColor("#EF69B4", 1)
.setShifitingDataMode(10)
.setOuterBorder("black", 1)
.setTitle("Aggregates per minute", undefined, "midnightblue")
.setInnerBorder("grey", 1, "1 2")
.setInnerBackgroundColor("#E4E5FF")
.setOuterBackgroundColor("#FAF5B0")
.setTimeWheelForeColor("firebrick")
.addLegend(["Males", "Females"], 130, 45, 20, 20);
//.setLocalScaling();
var sb = DynamicChart.SlidingBarChart(50, w, 250, "25 60 35 10;", 2)
.setPosition(w + 2 * MARGIN , 170)
.setBarsFillColor("cornflowerblue")
.setBarsFillColor("hotpink", 1)
.setInnerBackgroundColor("lightgrey")
.setInnerBackgroundHighlightColor("gold")
.setTicksBetweenHighlights(10)
.setOuterBorder("black", 1)
.setOuterBackgroundColor("#FAF5B0")
.setTitle("Real time values updated every second", 20, "midnightblue")
.setVerticalAxe(true, "Tweets/sec", 5, 2, "red")
.setHorizontalAxe(false, "Time (sec.)", 25, 2, "#5B48FF", 8);
var bb = DynamicChart.BasicBarChart(w, h * 0.75, "25 55 25 5;", 2)
.setPosition(MARGIN, h + 2* MARGIN)
.setBarsFillColor("cornflowerblue")
.setBarsFillColor("hotpink", 1)
.setOuterBorder("black", 1)
.setInnerBorder("grey", 1, "1 2")
.setInnerBackgroundColor("#E4E5FF")
.setOuterBackgroundColor("#FAF5B0")
.setTitle("Aggregates per minute", 20, "midnightblue")
.setVerticalAxe(true, "Tweets/sec", 5, 2, "red")
.setHorizontalAxe(false, "Time ->", 20, 2, "#5B48FF");
var fbw = DynamicChart.FixedWidthBarChart(30, undefined, w, h * 0.75, "25 5 25 25;", 2)
.setVerticalAxe(false, "Tweets/sec", 5, 2, "red")
.setHorizontalAxe(false, "Time (sec.)", 20, 2, "#5B48FF", 8, "#5B48FF", 8)
.setPosition(w + 2 * MARGIN, h + 2 * MARGIN)
.setBarsFillColor("cornflowerblue")
.setBarsFillColor("hotpink", 1)
.setInnerBackgroundColor("#E4E5FF")
.setOuterBackgroundColor("#FAF5B0")
.setOuterBorder("black", 1)
.setInnerBorder("grey", 1, "1 2")
.setTitle("Aggregates per minute", 20, "midnightblue")
.setShifitingDataMode(20)
.setLocalScaling();
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 && t > 0){
var y_s = males.sum();
var x_s = females.sum();
males.length = 0;
females.length = 0;
tw.appendData([[y_s, x_s]]);
if (t/dt < 500){
bb.appendData([[y_s, x_s]]);
}
fbw.appendData([[y_s, x_s]]);
}
t += dt;
}, dt);
function stop(){
clearInterval(timer);
}
return stop;
}
var simulation_stop = full_simulation();
@mlarocca
Copy link
Author

@mlarocca
Copy link
Author

mlarocca commented Jan 4, 2013

A lib built upon D3js to help plotting real-time data with almost no effort

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment