File "jquery-fusioncharts.js"
Full Path: /home/analogde/www/php/integrations/jquery/js/jquery-fusioncharts.js
File size: 52.86 KB
MIME-type: text/plain
Charset: utf-8
(function (factory) {
if (typeof module === 'object' && typeof module.exports !== "undefined") {
module.exports = factory;
} else {
factory(FusionCharts);
}
}(function (FusionCharts) {
(function webpackUniversalModuleDefinition(root, factory) {
if(typeof exports === 'object' && typeof module === 'object')
module.exports = factory(require("jquery"));
else if(typeof define === 'function' && define.amd)
define(["jquery"], factory);
else {
var a = typeof exports === 'object' ? factory(require("jquery")) : factory(root["jquery"]);
for(var i in a) (typeof exports === 'object' ? exports : root)[i] = a[i];
}
})(window, function(__WEBPACK_EXTERNAL_MODULE__1__) {
return /******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
/******/ }
/******/ };
/******/
/******/ // define __esModule on exports
/******/ __webpack_require__.r = function(exports) {
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ }
/******/ Object.defineProperty(exports, '__esModule', { value: true });
/******/ };
/******/
/******/ // create a fake namespace object
/******/ // mode & 1: value is a module id, require it
/******/ // mode & 2: merge all properties of value into the ns
/******/ // mode & 4: return value when already ns object
/******/ // mode & 8|1: behave like require
/******/ __webpack_require__.t = function(value, mode) {
/******/ if(mode & 1) value = __webpack_require__(value);
/******/ if(mode & 8) return value;
/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
/******/ var ns = Object.create(null);
/******/ __webpack_require__.r(ns);
/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
/******/ return ns;
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = 0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports, __webpack_require__) {
var jQuery = __webpack_require__(1);
var HTMLTableTranscoder = __webpack_require__(2);
var win = typeof window !== 'undefined' ? window : {};
if (typeof FusionCharts === 'undefined') {
FusionCharts = win.FusionCharts;
}
if (typeof jQuery === 'undefined') {
jQuery = win.jQuery;
}
FusionCharts.addDep(HTMLTableTranscoder);
var doc = win.document,
jQ = jQuery,
renderFusionCharts,
captureAllFusionChartsEvents,
getChartObjectsFromSelection,
configureLinkedCharts,
math = win.Math,
mathMin = math.min,
// FusionCharts = FusionCharts,
isArray = function () {
// Use compiler's own isArray when available
if (Array.isArray) {
return Array.isArray;
}
// Retain references to variables for performance optimization
var objectToStringFn = Object.prototype.toString,
arrayToStringResult = objectToStringFn.call([]);
return function (subject) {
return objectToStringFn.call(subject) === arrayToStringResult;
};
}(),
realtimeCommandMap = {
feed: 'feedData',
setdata: 'setData',
setdataforid: 'setDataForId',
getdata: 'getData',
getdataforid: 'getDataForId',
clear: 'clearChart',
stop: 'stopUpdate',
start: 'restartUpdate'
},
optionsParser = {
feedData: function (options) {
if (typeof options === 'string') {
return [options];
} else if (typeof options === 'object' && options.stream) {
return [options.stream];
} else {
return false;
}
},
getData: function (options) {
// index is passed in case of multivalue charts.
if (!isNaN(options)) {
return [options];
} else if (typeof options === 'object' && options.index) {
return [options.index];
} else {
return [];
}
},
getDataForId: function (options) {
// index is passed in case of multivalue charts.
if (typeof options === 'string') {
return [options];
} else if (typeof options === 'object' && options.id) {
return [options.id];
} else {
return [];
}
},
setData: function (options, value, label) {
var arr = [];
if (typeof options !== 'object') {
arr = [options, value, label];
} else {
options.value && arr.push(options.value);
options.label && arr.push(options.label);
}
return arr;
},
setDataForId: function (options, value, label) {
var arr = [];
if (typeof options === 'string' || typeof value === 'string' || typeof label === 'string') {
arr = [options, value, label];
} else if (typeof options === 'object') {
options.value && arr.push(options.value);
options.label && arr.push(options.label);
}
return arr;
},
clearChart: function (options) {
return [options];
},
stopUpdate: function (options) {
return [options];
},
restartUpdate: function (options) {
return [options];
}
};
// Assign FusionCharts object to global jQuery object for easy use.
jQ.FusionCharts = FusionCharts;
/**
* Used purely for rendering the FusionCharts.
* Acts as a common method that is invoked by all the APIs that create a
* FusionChart object
*
* @param {array} elemList is a list of all the HTML elements that
* are selected using the jQuery selectors
*
* @param {object} chartOptions is the options that are to be passed to
* the FusionCharts contructor.
*/
renderFusionCharts = function (elemList, chartOptions) {
var i, l, cO, chartObj, scriptElement;
if (isArray(chartOptions) || chartOptions instanceof jQ) {
l = mathMin(elemList.length, chartOptions.length);
} else {
l = elemList.length;
}
for (i = 0; i < l; i += 1) {
// One-to-one mapping with the HTML elements in case of
// multiple option objects.
if (isArray(chartOptions) || chartOptions instanceof jQ) {
cO = chartOptions[i];
} else {
cO = chartOptions;
}
// check if the element is appended to the window document or not.
if (elemList[i].parentNode) {
// Adding the renderAt option to the chartOptions tells the
// constructor where to render the FusionCharts object.
FusionCharts.render(jQ.extend({}, cO, {
renderAt: elemList[i]
}));
} else {
chartObj = new FusionCharts(jQ.extend({}, cO, {
renderAt: elemList[i]
}));
if (!jQ.FusionCharts.delayedRender) {
jQ.FusionCharts.delayedRender = {};
}
jQ.FusionCharts.delayedRender[chartObj.id] = elemList[i];
scriptElement = doc.createElement('script');
scriptElement.setAttribute('type', 'text/javascript');
if (/msie/i.test(win.navigator.userAgent) && !win.opera) {
scriptElement.text = 'FusionCharts.items[\'' + chartObj.id + '\'].render();';
} else {
scriptElement.appendChild(doc.createTextNode('FusionCharts.items[\'' + chartObj.id + '\'].render()'));
}
elemList[i].appendChild(scriptElement);
}
}
return elemList;
};
/**
* Using jQuery's event model for attaching handlers to FusionCharts events.
* This is achieved by listening to the FusionCharts "*" event and then
* triggering a jQuery event on the associated DOM element.
*/
captureAllFusionChartsEvents = function (eve, args) {
var containerElement, event;
// Extending our event with the jQuery event model for proper
// delegation and bubbling.
event = jQ.extend({}, eve);
jQ.extend(event, jQ.Event('fusioncharts' + eve.eventType));
// Checking if there is an associated DOM object
if (event.sender && event.sender.options) {
containerElement = event.sender.options.containerElement || event.sender.options.containerElementId;
if (typeof containerElement === 'object') {
jQ(containerElement).trigger(event, args);
} else if (jQ('#' + containerElement).length) {
jQ('#' + containerElement).trigger(event, args);
} else {
jQ(doc).trigger(event, args);
}
} else {
// If there is no DOM object associated with the FusionCharts object
// then triggering the event on the document itself for any possible
// global handlers that might want to capture it.
jQ(doc).trigger(event, args);
}
};
FusionCharts.addEventListener('*', captureAllFusionChartsEvents);
/**
* Used to select all the HTML object/embed elements that have been created
* using the FusionCharts constructor
*
* @param {jQuery} obj, the selection of elements that need to be processed.
*
*/
getChartObjectsFromSelection = function (obj) {
// The HTML object/embed may be part of the current selection or a
// child of the current selection. Need to take both cases into account.
// @note If the FusionCharts object has not been rendered yet, e.g in
// case the container is not appended to the document, then 'find' for
// that element will NOT return the corresponding FusionCharts object.
return obj.filter(':FusionCharts').add(obj.find(':FusionCharts'));
};
/**
* Used to configure the links at various levels in a linked chart.
*
* @param {jQuery} chartObjects The FusionCharts objects for which the link
* has to be configured.
*
* @param {object} linkConfigObj contains the configuration details of the
* linked chart like swfUrl, height, width etc.
*
* @param {string} level contains the level at which the user wants to
* configure the link.
*
*/
configureLinkedCharts = function (chartObjects, linkConfigObj, level) {
if (typeof linkConfigObj === 'object') {
chartObjects.each(function () {
this.configureLink(linkConfigObj, level);
});
}
};
/**
* @id: jQuery.fn.insertFusionCharts
* @id: $.fn.insertFusionCharts
*
* @param {object} options contains the parameters that need to be passed
* to the FusionCharts constructor
*
* Inserts the FusionCharts objects in the HTML elements that are selected
* by the jQuery selector.
*/
jQ.fn.insertFusionCharts = function (options) {
return renderFusionCharts(this, options);
};
/**
* @id: jQuery.fn.appendFusionCharts
* @id: $.fn.appendFusionCharts
*
* @param {object} options contains that parameters that need to be passed
* to the FusionCharts constructor
*
* Appends the FusionCharts objects immediately after the HTML elements
* that are selected by the jQuery selector.
*/
jQ.fn.appendFusionCharts = function (options) {
options.insertMode = 'append';
return renderFusionCharts(this, options);
};
/**
* @id: jQuery.fn.prependFusionCharts
* @id: $.fn.prependFusionCharts
*
* @param {object} options contains the parameters that need to be passed
* to the FusionCharts constructor
*
* Prepends the FusionCharts objects before the HTML elements that are
* selected by the jQuery selector.
*/
jQ.fn.prependFusionCharts = function (options) {
options.insertMode = 'prepend';
return renderFusionCharts(this, options);
};
/**
* @id: jQuery.fn.attrFusionCharts
* @id: $.fn.attrFusionCharts
*
* @param {object|string} attr, If this is a string then it contains
* the FusionCharts object's attribute that needs to be set or fetched.
* If it is an object then, it contains the attributes along with the
* corresponding values that need to be set on the FusionCharts object
*
* @param {string} attrVal, To be used if attr is a string. Contains the
* value that needs to be set for the attribute that attr corresponds to.
*
* Used to set or get the attribute(s) of the FusionCharts object.
*/
jQ.fn.attrFusionCharts = function (attr, attrVal) {
/**
* @ignore
* @todo Remove ignore
*
* @var {jQuery} chartsObjects stores the FusionCharts objects in
* the selected HTML elements.
* @var {object} transfer Holds all atttributes to be returned to the
* callee Function.
*/
var transfer = [],
chartObjects = getChartObjectsFromSelection(this);
if (attrVal !== undefined) {
// Set the charts attribute attr with value attrVal.
chartObjects.each(function () {
this.FusionCharts.setChartAttribute(attr, attrVal);
});
return this;
}
if (typeof attr === 'object') {
// Set the charts attributes, in the passed object's keys with
// the corresponding values.
chartObjects.each(function () {
this.FusionCharts.setChartAttribute(attr);
});
return this;
}
// If both the above cases fail, user is trying to, in accordance with the
// jQuery paradigm, get the value of the arrtibute.
chartObjects.each(function () {
transfer.push(this.FusionCharts.getChartAttribute(attr));
});
return transfer;
};
/**
* @id jQuery.fn.updateFusionCharts
* @id $.fn.updateFusionCharts
*
* @param {object} options Contains the new options that the FusionCharts
* objects need to update themselves with. Currently, using this interface
* the dataType, data, width, height, debugMode and swfUrl can be updated.
*/
jQ.fn.updateFusionCharts = function (options) {
var filterOpts = {},
chartObjects = getChartObjectsFromSelection(this),
updateOptions = [['swfUrl', false], ['type', false], ['height', false], ['width', false], ['containerBackgroundColor', true], ['containerBackgroundAlpha', true], ['dataFormat', false], ['dataSource', false]],
i,
l,
fcChart,
renderFlag,
optStr,
newChart;
for (i = 0, l = updateOptions.length; i < l; i += 1) {
optStr = updateOptions[i][0];
filterOpts.type = filterOpts.type || filterOpts.swfUrl;
if (options[optStr]) {
if (updateOptions[i][1]) {
renderFlag = true;
}
filterOpts[optStr] = options[optStr];
}
}
chartObjects.each(function () {
// If height and width are given then resize the chart first.
fcChart = this.FusionCharts;
if (renderFlag) {
newChart = fcChart.clone(filterOpts);
newChart.render();
return;
}
if (filterOpts.dataSource !== undefined || filterOpts.dataFormat !== undefined) {
if (filterOpts.dataSource === undefined) {
fcChart.setChartData(fcChart.args.dataSource, filterOpts.dataFormat);
} else if (filterOpts.dataFormat === undefined) {
fcChart.setChartData(filterOpts.dataSource, fcChart.args.dataFormat);
} else {
fcChart.setChartData(filterOpts.dataSource, filterOpts.dataFormat);
}
}
if (filterOpts.width !== undefined || filterOpts.height !== undefined) {
fcChart.resizeTo(filterOpts.width, filterOpts.height);
}
if (filterOpts.type) {
fcChart.chartType(filterOpts.type);
}
});
return this;
};
/**
* @id: jQuery.fn.getFusionCharts
* @id: $.fn.getFusionCharts
*
* @return {object} Contains an array of FusionCharts chart objects.
*/
jQ.fn.getFusionCharts = function () {
var charts = getChartObjectsFromSelection(this);
var chartArr = [];
charts.each(function () {
chartArr.push(this.FusionCharts);
});
return chartArr;
};
/**
* @id: jQuery.fn.cloneFusionCharts
* @id: $.fn.cloneFusionCharts
*
* @param {object} options The options object that takes the additional
* parameters to be passed while cloning the FusionCharts object.
*
* @param {function} callback The callback function that has to be called
* once the FusionCharts objects have been cloned. This function will take
* the new clone objects as parameter.
*
*/
jQ.fn.cloneFusionCharts = function (callback, options) {
var transfer, temp, chartObjects;
// Check if the options parameter, which is not mandatory, has been
// passed or not. If not, that means that options is the callback function.
if (typeof callback !== 'function' && typeof options === 'function') {
temp = callback;
callback = options;
options = temp;
}
transfer = [];
chartObjects = getChartObjectsFromSelection(this);
chartObjects.each(function () {
transfer.push(this.FusionCharts.clone(options, {}, true));
});
callback.call(jQ(transfer), transfer);
return this;
};
/**
* @id: jQuery.fn.disposeFusionCharts
* @id: $.fn.disposeFusionCharts
*
*/
jQ.fn.disposeFusionCharts = function () {
var chartObjects = getChartObjectsFromSelection(this);
chartObjects.each(function () {
// Execute dispose on charts.
this.FusionCharts.dispose();
// Remove identifier reference variable
delete this.FusionCharts;
// cleanup any static objects pertaining to FusionCharts.
if (this._fcDrillDownLevel === 0) {
delete this._fcDrillDownLevel;
}
});
return this;
};
/**
* @id jQuery.fn.covertToFusionCharts
* @id $.fn.convertToFusionCharts
*
* @param {object} chartOpts Configuration options to generate FusionCharts.
* See documentation to get the list.
*
* @param {object} convertOpts Configuration options to convert the table
* into a FusionCharts object.
* See documentation to get the list.
*
*/
jQ.fn.convertToFusionCharts = function (chartOpts, convertOpts) {
var transferObj = [];
if (typeof chartOpts.dataConfiguration === 'undefined') {
chartOpts.dataConfiguration = {};
}
jQ.extend(true, chartOpts.dataConfiguration, convertOpts);
if (!chartOpts.dataSource) {
chartOpts.dataSource = this.get(0);
}
if (!chartOpts.renderAt) {
this.each(function () {
transferObj.push(jQ('<div></div>').insertBefore(this).insertFusionCharts(chartOpts).get(0));
});
} else {
if (typeof chartOpts.renderAt === 'string') {
transferObj.push(jQ('#' + chartOpts.renderAt).insertFusionCharts(chartOpts).get(0));
} else if (typeof chartOpts.renderAt === 'object') {
transferObj.push(jQ(chartOpts.renderAt).insertFusionCharts(chartOpts).get(0));
}
}
return jQ(transferObj);
};
/**
* @id jQuery.fn.drillDownFusionChartsTo
* @id $.fn.drillDownFusionChartsTo
*
* Used to set multi-level configurations of linked FusionCharts objects.
* The levels are iterated depending on the number of configuration objects
* in a single jQuery chain.
*
* To set the configuration at a specific level please refer to docs.
*/
jQ.fn.drillDownFusionChartsTo = function () {
var chartObjects = getChartObjectsFromSelection(this),
j,
len,
i,
l,
configureOpts;
// hack to support chaining of multiple drillDowns in a single chain
if (typeof this._fcDrillDownLevel === 'undefined') {
this._fcDrillDownLevel = 0;
}
for (j = 0, len = arguments.length; j < len; j += 1) {
configureOpts = arguments[j];
if (isArray(configureOpts)) {
for (i = 0, l = configureOpts.length; i < l; i += 1) {
configureLinkedCharts(chartObjects, configureOpts[i], this._fcDrillDownLevel);
this._fcDrillDownLevel += 1;
}
} else {
configureLinkedCharts(chartObjects, configureOpts, this._fcDrillDownLevel);
this._fcDrillDownLevel += 1;
}
}
return this;
};
/**
* @id jQuery.fn.streamFusionChartsData
* @id $.fn.streamFusionChartsData
*
* @param {string} command. Contains the type of operation to be performed on
* the realtime charts. If not provided, by default the command would be 'feed'
* that invokes the feedData method of the chart and passes the options to it.
*
* @param {object} options. Contains the options that has to be parsed (if it is a js object)
* and passed to the chart method (corresponding to command).
* If not an object, it is passed as is to the chart method.
*
* @param {object} value. To make the API morph the data setter methods (setData, setDataForId),
* it can also be passed the same parameters as the setter functions.
*
* @param {object} label. To make the API morph the data setter methods (setData, setDataForId),
* it can also be passed the same parameters as the setter functions.
**/
jQ.fn.streamFusionChartsData = function (command, options, value, label) {
var chartObjects = getChartObjectsFromSelection(this),
transfer = [],
fcChart,
method,
params;
// Convert the command to lower case and fetch the proper chart method name.
method = realtimeCommandMap[command && command.toLowerCase()];
// Check if the command provided is valid or not.
if (method === undefined) {
// this means the command is not a getter. which in turn means that
// the command is a data stream string and has to be handled accordingly.
if (arguments.length === 1) {
params = [command];
method = realtimeCommandMap.feed;
} else {
return this;
}
} else if (arguments.length === 1) {
// command is to invoke a method without any parameters..
params = [];
} else {
// optionsParser returns an array of parameters to be passed to the
// chart method.
params = optionsParser[method](options, value, label);
}
if (method === 'getData' || method === 'getDataForId') {
chartObjects.each(function () {
fcChart = this.FusionCharts;
if (typeof fcChart[method] === 'function') {
transfer.push(fcChart[method].apply(fcChart, params));
}
});
return transfer;
} else {
chartObjects.each(function () {
fcChart = this.FusionCharts;
if (typeof fcChart[method] === 'function') {
fcChart[method].apply(fcChart, params);
}
});
return this;
}
};
jQ.extend(jQ.expr[':'], {
/**
* Extending the jQuery selector to select all object/embed elements that
* have been created using the FusionCharts constructor i.e that are an
* instance of FusionCharts.
*
* @param {object} obj, Is the HTML element that is currently being
* checked.
*/
FusionCharts: function (obj) {
return obj.FusionCharts instanceof FusionCharts;
}
});
/***/ }),
/* 1 */
/***/ (function(module, exports) {
module.exports = __WEBPACK_EXTERNAL_MODULE__1__;
/***/ }),
/* 2 */
/***/ (function(module, exports) {
var win = window,
doc = win.document,
objectToStringFn = Object.prototype.toString,
merge = function (obj1, obj2) {
var item, str;
//check whether obj2 is an array
//if array then iterate through it's index
//**** MOOTOOLS precution
if (obj2 instanceof Array) {
for (item = 0; item < obj2.length; item += 1) {
if (typeof obj2[item] !== 'object') {
obj1[item] = obj2[item];
} else {
if (typeof obj1[item] !== 'object') {
obj1[item] = obj2[item] instanceof Array ? [] : {};
}
merge(obj1[item], obj2[item]);
}
}
} else {
for (item in obj2) {
if (typeof obj2[item] === 'object') {
str = objectToStringFn.call(obj2[item]);
if (str === '[object Object]') {
if (typeof obj1[item] !== 'object') {
obj1[item] = {};
}
merge(obj1[item], obj2[item]);
} else if (str === '[object Array]') {
if (!(obj1[item] instanceof Array)) {
obj1[item] = [];
}
merge(obj1[item], obj2[item]);
} else {
obj1[item] = obj2[item];
}
} else {
obj1[item] = obj2[item];
}
}
}
return obj1;
},
extend = function (sink, source, proto, deep) {
var item;
// When 'proto' is marked as true, the methods and properties
// of source is not added to the prototype of the sink.
if (proto && sink.prototype) {
sink = sink.prototype;
}
// If deep extend is specified, then we use the deep copy function
// 'merge'
if (deep === true) {
merge(sink, source);
}
// Copy all methods and properties of the object passed in parameter
// to the object to which this function is attached.
else {
for (item in source) {
sink[item] = source[item];
}
}
return sink;
},
/**
* Used to remove all textNodes in an array of nodes. Textnodes get created
* inadvertently, when the table HTML has newline and space. Returns an
* array of nodes that is not an HTMLCollection and has only the non-text
* nodes in it.
*
* @param {array} nodeArr Contains the array of nodes that have to be
* sanitized.
*/
sanitizeNodesArray = function (nodeArr) {
var l,
i,
sanitizedArr = [];
for (i = 0, l = nodeArr.length; i < l; i += 1) {
// if nodeType != 3 then the node is not a text node.
if (nodeArr[i].nodeType !== 3) {
sanitizedArr.push(nodeArr[i]);
}
}
return sanitizedArr;
},
/**
* Used to merge two JSON objects. The copy is a deep copy and not a
* reference copy.
*
* @param: {object} srcJSON, the source json.
*
* @param: {object} targetJSON, that json that needs to be merged with the
* source json.
*
*/
mergeJSON = function (srcJSON, targetJSON) {
var i, len, item;
if (targetJSON instanceof Array) {
for (i = 0, len = targetJSON.length; i < len; i++) {
if (targetJSON[i] instanceof Array) {
if (srcJSON[i] === undefined) {
srcJSON[i] = [];
}
mergeJSON(srcJSON[i], targetJSON[i]);
} else if (typeof targetJSON[i] === 'object') {
if (typeof srcJSON[i] === 'undefined') {
srcJSON[i] = {};
}
mergeJSON(srcJSON[i], targetJSON[i]);
} else {
srcJSON[i] = targetJSON[i];
}
}
} else if (typeof targetJSON === 'object') {
for (item in targetJSON) {
if (typeof srcJSON[item] === 'undefined') {
if (targetJSON[item] instanceof Array) {
srcJSON[item] = [];
mergeJSON(srcJSON[item], targetJSON[item]);
} else if (typeof srcJSON[item] === 'object') {
srcJSON[i] = {};
mergeJSON(srcJSON[i], targetJSON[i]);
} else {
srcJSON[i] = targetJSON[i];
}
}
}
}
return srcJSON;
},
/**
* Returns the <tbody> element of the table. In order to support W3C
* non-compliant HTML, wherein the table does not have a <tbody> containing
* the <tr> & <td> elements, the table itself is returned.
*
* @param {object} tbl Is the table whose tbody needs to fetched.
*
*/
getTbody = function (tbl) {
var tbodyArr = sanitizeNodesArray(tbl.childNodes);
// Checking if the table's childNode is a tbody or not
if (tbodyArr.length) {
if (tbodyArr[0].nodeName === 'TBODY') {
return tbodyArr[0];
} else if (tbodyArr[0].nodeName === 'THEAD' && tbodyArr[1] && tbodyArr[1].nodeName === 'TBODY') {
return tbodyArr[1];
}
}
return tbl;
},
/**
* Returns an array containing the <td> in the <thead> tag.
*
* @param {object} tbl Is the table whose tbody needs to fetched.
*
*/
getThead = function (tbl) {
var tbodyArr = sanitizeNodesArray(tbl.childNodes);
// Checking if the table's childNode is a tbody or not
if (tbodyArr.length) {
if (tbodyArr[0].nodeName === 'THEAD' && tbodyArr[1] && tbodyArr[1].nodeName === 'TBODY') {
return tbodyArr[0].childNodes;
}
}
return [];
},
/**
* Used to return the text in a given Element. Needed as firefox does not
* support 'innerText', which is a W3C standard and instead uses
* textContent attribute to store the text.
*
* @param {object} nodeEle The HTML element whose text is needed.
*/
getTextFromNode = function (nodeEle) {
return nodeEle.innerText !== undefined ? nodeEle.innerText : nodeEle.textContent;
},
/**
* Used to convert an array of rows into an array of corresponding columns.
*
* @param {array} rowArr The array of table rows that has to be transposed
* into an array of columns.
*/
getColumnArr = function (rowArr) {
var i,
j,
l,
len,
cellArr,
rowSpanInc,
colSpan = 1,
columnArrIdx,
rowSpan = {},
returnObj = [];
for (i = 0, l = rowArr.length; i < l; i += 1) {
// get all the cells of the row
cellArr = sanitizeNodesArray(rowArr[i].childNodes);
colSpan = 1;
rowSpanInc = 0;
for (j = 0, len = cellArr.length; j < len; j += 1) {
// if a previous row had a cell with a rowspan then this
// rowspan needs to be taken into account while creating
// column array.
columnArrIdx = j + colSpan + rowSpanInc - 1;
if (rowSpan[columnArrIdx] && i - rowSpan[columnArrIdx].rowNum < rowSpan[columnArrIdx].row) {
rowSpanInc += rowSpan[columnArrIdx].col;
columnArrIdx += rowSpan[columnArrIdx].col;
}
if (parseInt(cellArr[j].getAttribute('rowspan'), 10) > 1) {
if (!rowSpan[columnArrIdx]) {
rowSpan[columnArrIdx] = {};
}
rowSpan[columnArrIdx].rowNum = i;
rowSpan[columnArrIdx].row = parseInt(cellArr[j].getAttribute('rowspan'), 10);
// If the cell has both a rowspan and a colspan then they
// both need to be taken into consideration while calculating
// the column of cells in the susequent rows
if (parseInt(cellArr[j].getAttribute('colspan'), 10) > 1) {
rowSpan[columnArrIdx].col = parseInt(cellArr[j].getAttribute('colspan'), 10);
} else {
rowSpan[columnArrIdx].col = 1;
}
}
// The previous cells colspan, and the previous rows rowspans
// also needs to be added to the current cells index to get the
// proper column index.
while (returnObj.length <= columnArrIdx) {
returnObj.push({ childNodes: [] });
}
returnObj[columnArrIdx].childNodes.push(cellArr[j]);
// Adding the current cells colspan for subsequent cells in the
// current row.
if (parseInt(cellArr[j].getAttribute('colspan'), 10) > 1) {
colSpan += parseInt(cellArr[j].getAttribute('colspan'), 10) - 1;
}
}
}
return returnObj;
},
/**
* Used to check if an item is present in an array or not.
*
* @param {array} arr The array which has to be checked
*
* @param {string|number|object} item The item which needs be checked if
* present in array arr or not.
*/
arrayContains = function (arr, item) {
var i = arr.length;
// Using a decrementing while loop (optimization) since the order in which the
// array is traversed doesn't matter.
while (i) {
i -= 1;
if (arr[i] === item) {
return true;
}
}
return false;
},
/**
* Used to check if a particular row or column has all non numeric (or blank)
* content. If so, then that particular row/column must be ignored.
*
* @param {array} nodeArr Is an array of all the rows/columns of the table.
*
* @param {number} index of the row/column that is not to be analysed as it
* has already been set aside as the label row/column.
*
* @param {number} j is the cell index at which we have to check for the
* presence of numeric data in all the other rows and columns.
*/
checkData = function (nodeArr, j, index) {
var i,
l,
childArr = sanitizeNodesArray(nodeArr[j].childNodes),
text;
for (i = 0, l = childArr.length; i < l; i += 1) {
if (i !== index) {
text = getTextFromNode(childArr[i]);
if (parseFloat(text) === text) {
return true;
}
}
}
return false;
},
_blankString = '__fcBLANK__',
_blankNo = 0,
/**
* Used to get the table row that has the labels (categories or legend) and
* extract the label details from the corresponding row.
*
* @param {array} nodeArr Is an array of all the rows/columns of the table.
*
* @param {array} ignoreArr Is an array of all the indexes in the nodeArr
* that need to be ignored. The items of this array can be negative as well.
*
* @param {string} index If the label row/column details have been given by
* the user then this will contain the index which has the labels.
*
*/
getLabels = function (nodeArr, ignoreArr, index, opts) {
var len,
l,
i,
j,
childArr,
mostEmptyCellRow = null,
internalLabel = [],
emptyCellCount = [],
textCellCount = 0,
temp,
returnObj = {},
spanTotal = 0,
spanLen,
isRowLabel,
maxIdx,
spanLength,
totalSpanLength = 0,
tLabels;
if (typeof index === 'undefined') {
// Checking if the user has provided the index. If index has not been
// passed then we assume that the user does not want to give a label row
// or column.
// Creating custom labels for all cells in the first row/column.
childArr = sanitizeNodesArray(nodeArr[0].childNodes);
for (j = 0, len = childArr.length; j < len; j += 1) {
spanLen = j + spanTotal;
internalLabel[spanLen] = _blankString + (spanLen + 1);
temp = parseInt(childArr[j].colSpan, 10);
temp = temp > 1 ? temp : parseInt(childArr[j].rowSpan, 10);
if (temp > 1) {
for (l = 1; l < temp; l += 1) {
internalLabel[spanLen + l] = _blankString + (spanLen + l + 1);
}
spanTotal += temp - 1;
}
}
// Deleting the labels for the rows/columns that the user wants to
// ignore.
for (i = 0, l = j + spanTotal, len = ignoreArr.length; i < len; i += 1) {
if (ignoreArr[i] > 0) {
delete internalLabel[ignoreArr[i] - 1];
} else {
delete internalLabel[l + ignoreArr[i]];
}
}
return { 'index': -1, 'labelObj': internalLabel };
} else if (index === 0) {
// Checking if the user has provided the index. Since we expect row/column
// indices starting from 1, if index is 0 then we use our internal logic
// to find the label array from the given nodeArr.
for (i = 0, l = nodeArr.length; i < l; i += 1) {
childArr = sanitizeNodesArray(nodeArr[i].childNodes);
emptyCellCount[i] = 0;
textCellCount = 0;
if (opts && opts._extractByHeaderTag) {
for (j = 0, len = childArr.length; j < len; j += 1) {
if (childArr[j].nodeName.toLowerCase() != 'th') {
continue;
}
tLabels = getLabels(nodeArr, ignoreArr, i + 1);
delete tLabels.labelObj[opts._rowLabelIndex];
return tLabels;
}
} else {
for (j = 0, len = childArr.length; j < len; j += 1) {
if (arrayContains(ignoreArr, j + 1) || arrayContains(ignoreArr, j - len)) {
continue;
}
temp = getTextFromNode(childArr[j]);
// Checking if the cell is emtpy.
if (temp.replace(/^\s*/, '').replace(/\s*$/, '') === '') {
emptyCellCount[i] += 1;
continue;
}
// Checking if the cell has a non-number content
if (parseFloat(temp) != temp) {
textCellCount += 1;
// If there are at least 2 cells that have non-number
// content then we assume that they contain labels and
// fetch the labels from this array of nodes.
if (textCellCount > 1) {
return getLabels(nodeArr, ignoreArr, i + 1);
}
}
}
}
// If there are empty cells then we assume that the array with
// the most number of empty cells must be the label array.
if (i > 0) {
if (emptyCellCount[i - 1] > emptyCellCount[i]) {
mostEmptyCellRow = i - 1;
} else if (emptyCellCount[i - 1] < emptyCellCount[i]) {
mostEmptyCellRow = i;
}
}
}
if (mostEmptyCellRow !== null) {
return getLabels(nodeArr, ignoreArr, mostEmptyCellRow + 1);
} else {
return getLabels(nodeArr, ignoreArr);
}
}
// If this is a negative number then, calulate the index from the
// end of the table. e.g -1 would imply the last row.
if (index < 0) {
index += nodeArr.length;
} else if (index > 0) {
index -= 1;
}
/**
* Once we have the index of the row/column that contains the labels we
* go through only that row/column and extract the labels.
*/
childArr = sanitizeNodesArray(nodeArr[index].childNodes);
isRowLabel = nodeArr[0].nodeType !== undefined ? true : false;
for (j = 0, len = childArr.length; j < len; j += 1) {
spanLength = 0;
if (isRowLabel) {
if (childArr[j].colSpan !== '1') {
spanLength = parseInt(childArr[j].colSpan, 10);
}
} else if (childArr[j].rowSpan !== '1') {
spanLength = parseInt(childArr[j].rowSpan, 10);
}
spanLength = spanLength > 1 ? spanLength : 0;
temp = getTextFromNode(childArr[j]);
if (temp.replace(/^\s*/, '').replace(/\s*$/, '') !== '') {
returnObj[j + totalSpanLength] = temp;
} else if (checkData(getColumnArr(nodeArr), j, index)) {
// if the label text is missing in one of the cells of the labels
// row/column we check for the presence of numeric data in other
// cells of the corresponding column/row.
returnObj[j + totalSpanLength] = _blankString + _blankNo;
_blankNo += 1;
}
if (spanLength > 1) {
// If the spanLength > 1, then we create additional labels for
// rows/columns pertaining to the rowspan or colspan.
temp = returnObj[j + totalSpanLength];
for (i = 1; i < spanLength; i += 1) {
returnObj[j + totalSpanLength + i] = temp + ' (' + i + ')';
}
// totalSpanLength is used to adjust the indices of the
// subsequent cells using the rowspan/colspan of the current
// cell.
totalSpanLength += spanLength - 1;
}
}
// Deleting the rows/columns that the user wants to ignore.
maxIdx = len + totalSpanLength;
for (i = 0, len = ignoreArr.length; i < len; i += 1) {
if (ignoreArr[i] > 0) {
delete returnObj[ignoreArr[i] - 1];
} else {
delete returnObj[maxIdx + ignoreArr[i]];
}
}
return { 'labelObj': returnObj, 'index': index };
},
extractDataFromTable = function (tbl, opts) {
if (typeof tbl === 'string') {
tbl = doc.getElementById(tbl);
}
if (typeof win.jQuery !== 'undefined' && tbl instanceof win.jQuery) {
// jshint ignore: line
tbl = tbl.get(0);
}
if (!tbl) {
return { data: null };
}
if (opts.hideTable) {
tbl.style.display = 'none';
}
var i,
j,
rowCells,
cellText,
dataMap = {},
mapColumnIdx,
columnSpan,
len,
item,
rowSpan,
cellEle,
columnSpanObj = {},
rowSpanObj = {},
tableRows = sanitizeNodesArray(getThead(tbl)).concat(sanitizeNodesArray(getTbody(tbl).childNodes)),
l = tableRows.length,
dataRows = 0,
dataColumns = 0,
tempColumn = 0,
rowLabelMap,
m,
k = 0,
columnLabelMap,
isSingleSeries = false,
chartType = opts.chartType,
tempMap,
singleSeriesCharts = ['column2d', 'column3d', 'pie3d', 'pie2d', 'line', 'bar2d', 'area2d', 'doughnut2d', 'doughnut3d', 'pareto2d', 'pareto3d'];
if (singleSeriesCharts.indexOf(chartType) !== -1) {
isSingleSeries = true;
}
// use rowLabelSource and colLabelSource to avoid confusion
opts.rowLabelSource = parseInt(opts.labelSource, 10);
opts.colLabelSource = parseInt(opts.legendSource, 10);
// Create the labels objects for the chart.
if (opts.major === 'column') {
rowLabelMap = opts.useLabels ? getLabels(tableRows, opts.ignoreCols, opts.rowLabelSource) : getLabels(tableRows, opts.ignoreCols);
columnLabelMap = opts.useLegend ? getLabels(getColumnArr(tableRows), opts.ignoreRows, opts.colLabelSource) : getLabels(getColumnArr(tableRows), opts.ignoreRows);
} else {
tempMap = getLabels(getColumnArr(tableRows), opts.ignoreRows, opts.rowLabelSource);
if (!opts.useLabels) {
rowLabelMap = getLabels(getColumnArr(tableRows), opts.ignoreRows);
} else {
rowLabelMap = tempMap;
}
opts._rowLabelIndex = tempMap.index;
opts._extractByHeaderTag = true;
columnLabelMap = opts.useLegend ? getLabels(tableRows, opts.ignoreCols, opts.colLabelSource, opts) : getLabels(tableRows, opts.ignoreCols);
delete opts._extractByHeaderTag;
tempMap = rowLabelMap;
rowLabelMap = columnLabelMap;
columnLabelMap = tempMap;
}
delete rowLabelMap.labelObj[columnLabelMap.index];
delete columnLabelMap.labelObj[rowLabelMap.index];
// Creating the 2d map depending on whether the rows are the primary
// keys or the columns.
if (opts.major === 'row') {
for (item in columnLabelMap.labelObj) {
dataMap[item] = {};
}
} else {
for (item in rowLabelMap.labelObj) {
dataMap[item] = {};
}
}
// Populating the dataMap.
for (i = 0; i < l; i += 1) {
if (rowLabelMap.index === i || columnLabelMap.labelObj[i] === undefined) {
continue;
}
dataRows += 1;
rowCells = sanitizeNodesArray(tableRows[i].childNodes);
// columnSpanObj maintains the number of colspans in the current
// row.
// rowSpanObj maintains the number of rowspans in a rows x columns
// map.
columnSpanObj[i] = 0;
rowSpanObj[i] = {};
for (j = 0, len = rowCells.length; j < len; j += 1) {
cellEle = rowCells[j];
columnSpan = parseInt(cellEle.getAttribute('colspan'), 10);
rowSpan = parseInt(cellEle.getAttribute('rowspan'), 10);
mapColumnIdx = j + columnSpanObj[i];
// Calculating the position of the current cell in the dataMap.
while (k < i) {
if (rowSpanObj[k]) {
for (m in rowSpanObj[k]) {
if (m > mapColumnIdx) {
break;
}
if (i - k <= rowSpanObj[k][m].row) {
mapColumnIdx += rowSpanObj[k][m].col;
}
}
}
k += 1;
}
if (columnSpan > 1) {
columnSpanObj[i] += columnSpan - 1;
}
if (rowSpan > 1) {
if (columnSpan > 1) {
rowSpanObj[i][mapColumnIdx] = {
row: rowSpan - 1,
col: columnSpan
};
} else {
rowSpanObj[i][mapColumnIdx] = {
row: rowSpan - 1,
col: 1
};
}
}
if (columnLabelMap.index === mapColumnIdx || rowLabelMap.labelObj[mapColumnIdx] === undefined) {
continue;
}
tempColumn += 1;
cellText = getTextFromNode(cellEle);
// If the cell does not have any text then we covert it by
// default to 0 or to an parameterized option set by user.
if (cellText.replace(/^\s*/, '').replace(/\s*$/, '') === '') {
if (opts.convertBlankTo) {
cellText = opts.convertBlankTo;
} else {
continue;
}
}
// Filling up the dataMap based on the rowspan, colspan and
// position(row=i, column=mapColumnIdx) information of the
// table cell.
columnSpan = columnSpan > 1 ? columnSpan : 1;
rowSpan = rowSpan > 1 ? rowSpan : 1;
if (opts.major === 'row') {
k = 0;
while (k < columnSpan) {
m = 0;
while (m < rowSpan) {
dataMap[i + m][mapColumnIdx + k] = parseFloat(cellText);
m += 1;
}
k += 1;
}
} else {
k = 0;
while (k < columnSpan) {
m = 0;
while (m < rowSpan) {
dataMap[mapColumnIdx + k][i + m] = parseFloat(cellText);
m += 1;
}
k += 1;
}
}
}
if (tempColumn > dataColumns) {
dataColumns = tempColumn;
}
}
return {
data: dataMap,
chartType: chartType ? !isSingleSeries ? 'multi' : 'single' : dataRows > 1 && dataColumns > 1 ? 'multi' : 'single',
labelMap: columnLabelMap,
legendMap: rowLabelMap
};
},
createChartFromTable = function (data, obj) {
// Default configuration for HTMLTable data-handler
var opts = {
chartAttributes: {},
major: 'row',
useLabels: true,
useLegend: true,
labelSource: 0,
legendSource: 0,
ignoreCols: [],
ignoreRows: [],
showLabels: true,
showLegend: true,
seriesColors: [],
convertBlankTo: '0',
hideTable: false,
chartType: obj.chartType && obj.chartType(),
// Private Variables
labels: [],
legend: [],
data: []
},
config = obj.args.dataConfiguration || {},
i,
item1,
item2,
categoryArr,
datasetArr,
chartJSON = {},
datasets = {},
dataObj,
dataMap,
labelMap,
legendMap;
extend(opts, config);
dataObj = extractDataFromTable(data, opts);
dataMap = dataObj.data;
if (opts.major !== 'row') {
labelMap = dataObj.legendMap, legendMap = dataObj.labelMap;
} else {
labelMap = dataObj.labelMap, legendMap = dataObj.legendMap;
}
// chartAttributes should contain the configuration attributes for the chart
// e.g caption, xAxisName, yAxisName etc.
chartJSON.chart = extend({}, opts.chartAttributes);
if (dataObj.chartType === 'multi') {
chartJSON.categories = [{ 'category': [] }];
chartJSON.dataset = [];
categoryArr = chartJSON.categories[0].category;
datasetArr = chartJSON.dataset;
i = 0;
for (item1 in dataMap) {
if (opts.showLabels === true) {
// If the user has provided custom labels then those should be
// shown instead of the extracted labels.
categoryArr.push(extend({
label: labelMap.labelObj[item1].indexOf(_blankString) != -1 ? '' : labelMap.labelObj[item1]
}, opts.labels[i]));
} else {
categoryArr.push({ 'label': '' });
}
i += 1;
for (item2 in dataMap[item1]) {
if (typeof datasets[item2] === 'undefined') {
datasets[item2] = [];
}
datasets[item2].push({ 'value': dataMap[item1][item2] });
}
}
i = 0;
for (item1 in datasets) {
if (opts.showLegend === true) {
// If the user has provided custom labels then those should be
// shown instead of the extracted labels.
datasetArr.push(extend({
'seriesname': legendMap.labelObj[item1].indexOf(_blankString) !== -1 ? '' : legendMap.labelObj[item1],
'data': datasets[item1]
}, opts.legend[i]));
} else {
datasetArr.push({
'seriesname': '',
'data': datasets[item1]
});
}
i += 1;
}
} else if (dataObj.chartType === 'single') {
chartJSON.data = [];
datasetArr = chartJSON.data;
i = 0;
if (opts.showLabels) {
for (item1 in dataMap) {
for (item2 in dataMap[item1]) {
datasetArr.push(extend({
label: labelMap.labelObj[item1].indexOf(_blankString) !== -1 ? '' : labelMap.labelObj[item1],
value: dataMap[item1][item2]
}, opts.labels[i]));
i += 1;
}
}
} else {
for (item1 in dataMap) {
for (item2 in dataMap[item1]) {
datasetArr.push({ 'value': dataMap[item1][item2] });
}
}
}
}
return {
data: chartJSON,
error: undefined
};
},
htmlTableToJSON = function (data, obj) {
return createChartFromTable(data, obj);
};
/**
* method to set the HTML table data
* @param {Object} dom: HTML table dom
*/
function setHTMLTableData(dom) {
this.setChartData(dom, 'htmltable');
}
/**
* method to add functions in the fusioncharts prototype
* @param {Function} FusionCharts is required
* @return {Object} for extension
*/
function wrapper(FusionCharts) {
FusionCharts && (FusionCharts.prototype.setHTMLTableData = setHTMLTableData);
return {
format: 'htmltable',
toJSON: htmlTableToJSON
};
}
module.exports = {
extension: wrapper,
name: 'HTMLTable',
type: 'transcoder',
requiresFusionCharts: true
};
/***/ })
/******/ ]);
});}));