From f65a313cdb8bfbbe6b12fe77b835df5f96f86b66 Mon Sep 17 00:00:00 2001 From: Rohit Kumar Date: Wed, 26 Aug 2015 16:58:30 +0530 Subject: [PATCH] Clean up and restructure the repository - Restructure the files in repository - Add Grunt script for `lint` and `build` files with version bump - Add release version in the files --- .gitignore | 39 + .npmignore | 51 + Gruntfile.js | 68 + minified/fusioncharts-jquery-plugin.min.js | 8 - package.json | 88 + package/fusioncharts-jquery-plugin.js | 1425 +++++++++++++++++ package/fusioncharts-jquery-plugin.min.js | 8 + src/banner.js | 7 + src/fusioncharts-jquery-plugin.js | 7 - .../transcoder-htmltable.js | 758 +++++++++ 10 files changed, 2444 insertions(+), 15 deletions(-) create mode 100644 .gitignore create mode 100644 .npmignore create mode 100644 Gruntfile.js delete mode 100644 minified/fusioncharts-jquery-plugin.min.js create mode 100644 package.json create mode 100644 package/fusioncharts-jquery-plugin.js create mode 100644 package/fusioncharts-jquery-plugin.min.js create mode 100644 src/banner.js create mode 100755 src/transcoder-htmltable/transcoder-htmltable.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e07c8d3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,39 @@ +# PLATFORM +# ======== +# All exclusions that are specific to the NPM, GIT, IDE and Operating Systems. + +# - Do not allow installed node modules to be committed. Doing `npm install -d` will bring them in root or other places. +node_modules + +# - Do not commit any log file from anywhere +*.log + +# - Prevent addition of OS specific file explorer files +Thumbs.db +.DS_Store + + +# PROJECT +# ======= +# Configuration pertaining to project specific repository structure. + +# - Prevent Sublime text IDE files from being commited to repository +*.sublime-* + +# - Prevent NetBeans IDE files from being commited to repository +nbproject/ + +# - Prevent extra `jshint` configuration from being committed from anywhere. Only `package.json` will be used as +# accepted source of config. +.jshintrc + +# - Prevent CI output files from being Added +/out/ + +# - Prevent diff backups from SourceTree from showing as commit. +*.BACKUP.* +*.BASE.* +*.LOCAL.* +*.REMOTE.* +*.orig + diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..e9bfcf0 --- /dev/null +++ b/.npmignore @@ -0,0 +1,51 @@ +# PLATFORM +# ======== +# All exclusions that are specific to the NPM, GIT, IDE and Operating Systems. + +# - Do not allow installed node modules to be committed. Doing `npm install -d` will bring them in root or other places. +node_modules + +# - Do not commit any log file from anywhere +*.log + +# - Prevent addition of OS specific file explorer files +Thumbs.db +.DS_Store + + +# PROJECT +# ======= +# Configuration pertaining to project specific repository structure. + +# - Prevent Sublime text IDE files from being commited to repository +*.sublime-* + +# - Prevent NetBeans IDE files from being commited to repository +nbproject/ + + +# - Prevent extra `jshint` configuration from being committed from anywhere. Only `package.json` will be used as +# accepted source of config. +.jshintrc + +# - Prevent CI output files from being Added +/out/ + +# - Prevent diff backups from SourceTree from showing as commit. +*.BACKUP.* +*.BASE.* +*.LOCAL.* +*.REMOTE.* +*.orig + +# ---------------------------------------------------------------------------------------------------------------------- +# NPMIGNORE ADDITIONS +# =================== + + +# - Do not distribute source code +/src/ + +# - Do not distribute submodule sources +/.gitmodules + diff --git a/Gruntfile.js b/Gruntfile.js new file mode 100644 index 0000000..11eceec --- /dev/null +++ b/Gruntfile.js @@ -0,0 +1,68 @@ +"use strict"; + +module.exports = function(grunt) { + var pkg = grunt.file.readJSON('package.json'); + + // Project configuration. + grunt.initConfig({ + banner: grunt.file.read("src/banner.js").replace(/@VERSION/, pkg.version), + // Task configuration. + uglify: { + options: { + banner: "<%= banner %>" + }, + dist: { + src: "<%= build.dist.dest %>", + dest: "package/fusioncharts-jquery-plugin.min.js" + } + }, + build: { + options: { + banner: "<%= banner %>" + }, + dist: { + dest: "package/fusioncharts-jquery-plugin.js", + src: [ + "src/transcoder-htmltable/transcoder-htmltable.js", + "src/fusioncharts-jquery-plugin.js" + ] + } + }, + jshint: { + options: pkg.jshintConfig, + all: ["src/*"] + } + }); + + + // These plugins provide necessary tasks. + grunt.loadNpmTasks("grunt-contrib-uglify"); + grunt.loadNpmTasks("grunt-contrib-jshint"); + + // Special concat/build task to handle FusionCharts jQuery plugin build requirements + grunt.registerMultiTask( + "build", + "Concatenate source, remove individual closures, embed version", + function() { + var data = this.data, + name = data.dest, + src = data.src, + options = this.options({ + banner: "" + }), + // Start with banner + compiled = options.banner; + // Concatenate src + src.forEach(function(path) { + var source = grunt.file.read(path); + + compiled += source; + }); + + grunt.file.write(name, compiled); + } + ); + + // Default task. + grunt.registerTask("default", ["jshint", "build", "uglify"]); +}; \ No newline at end of file diff --git a/minified/fusioncharts-jquery-plugin.min.js b/minified/fusioncharts-jquery-plugin.min.js deleted file mode 100644 index 0364e48..0000000 --- a/minified/fusioncharts-jquery-plugin.min.js +++ /dev/null @@ -1,8 +0,0 @@ -/**! - * @license FusionCharts JavaScript Library jQuery Plugin - * Copyright FusionCharts Technologies LLP - * License Information at - * - * @author FusionCharts Technologies LLP - */ -FusionCharts.register("module",["private","extensions.jQueryPlugin",function(){var t,e,n,r,i=this,a=i.window,o=i.hcLib,s=a.document,u=a.jQuery,h=a.Math,d=h.min,c=o.isArray,f={feed:"feedData",setdata:"setData",setdataforid:"setDataForId",getdata:"getData",getdataforid:"getDataForId",clear:"clearChart",stop:"stopUpdate",start:"restartUpdate"},l={feedData:function(t){return"string"==typeof t?[t]:"object"==typeof t&&t.stream?[t.stream]:!1},getData:function(t){return isNaN(t)?"object"==typeof t&&t.index?[t.index]:[]:[t]},getDataForId:function(t){return"string"==typeof t?[t]:"object"==typeof t&&t.id?[t.id]:[]},setData:function(t,e,n){var r=[];return"object"!=typeof t?r=[t,e,n]:(t.value&&r.push(t.value),t.label&&r.push(t.label)),r},setDataForId:function(t,e,n){var r=[];return"string"==typeof t||"string"==typeof e||"string"==typeof n?r=[t,e,n]:"object"==typeof t&&(t.value&&r.push(t.value),t.label&&r.push(t.label)),r},clearChart:function(t){return[t]},stopUpdate:function(t){return[t]},restartUpdate:function(t){return[t]}};u.FusionCharts=i.core,t=function(t,e){var n,r,o,h,f;for(r=c(e)||e instanceof u?d(t.length,e.length):t.length,n=0;r>n;n+=1)o=c(e)||e instanceof u?e[n]:e,t[n].parentNode?i.core.render(u.extend({},o,{renderAt:t[n]})):(h=new FusionCharts(u.extend({},o,{renderAt:t[n]})),u.FusionCharts.delayedRender||(u.FusionCharts.delayedRender={}),u.FusionCharts.delayedRender[h.id]=t[n],f=s.createElement("script"),f.setAttribute("type","text/javascript"),/msie/i.test(a.navigator.userAgent)&&!a.opera?f.text="FusionCharts.items['"+h.id+"'].render();":f.appendChild(s.createTextNode("FusionCharts.items['"+h.id+"'].render()")),t[n].appendChild(f));return t},e=function(t,e){var n;u.extend(t,u.Event("fusioncharts"+t.eventType)),t.sender&&t.sender.options?(n=t.sender.options.containerElement||t.sender.options.containerElementId,"object"==typeof n?u(n).trigger(t,e):u("#"+n).length?u("#"+n).trigger(t,e):u(s).trigger(t,e)):u(s).trigger(t,e)},i.addEventListener("*",e),n=function(t){return t.filter(":FusionCharts").add(t.find(":FusionCharts"))},r=function(t,e,n){"object"==typeof e&&t.each(function(){this.configureLink(e,n)})},u.fn.insertFusionCharts=function(e){return t(this,e)},u.fn.appendFusionCharts=function(e){return e.insertMode="append",t(this,e)},u.fn.prependFusionCharts=function(e){return e.insertMode="prepend",t(this,e)},u.fn.attrFusionCharts=function(t,e){var r=[],i=n(this);return void 0!==e?(i.each(function(){this.FusionCharts.setChartAttribute(t,e)}),this):"object"==typeof t?(i.each(function(){this.FusionCharts.setChartAttribute(t)}),this):(i.each(function(){r.push(this.FusionCharts.getChartAttribute(t))}),r)},u.fn.updateFusionCharts=function(t){var e,r,i,a,o,s,u={},h=n(this),d=[["swfUrl",!1],["type",!1],["height",!1],["width",!1],["containerBackgroundColor",!0],["containerBackgroundAlpha",!0],["dataFormat",!1],["dataSource",!1]];for(e=0,r=d.length;r>e;e+=1)o=d[e][0],u.type=u.type||u.swfUrl,t[o]&&(d[e][1]&&(a=!0),u[o]=t[o]);return h.each(function(){return i=this.FusionCharts,a?(s=i.clone(u),void s.render()):((void 0!==u.dataSource||void 0!==u.dataFormat)&&(void 0===u.dataSource?i.setChartData(i.args.dataSource,u.dataFormat):void 0===u.dataFormat?i.setChartData(u.dataSource,i.args.dataFormat):i.setChartData(u.dataSource,u.dataFormat)),(void 0!==u.width||void 0!==u.height)&&i.resizeTo(u.width,u.height),void(u.type&&i.chartType(u.type)))}),this},u.fn.cloneFusionCharts=function(t,e){var r,i,a;return"function"!=typeof t&&"function"==typeof e&&(i=t,t=e,e=i),r=[],a=n(this),a.each(function(){r.push(this.FusionCharts.clone(e,{},!0))}),t.call(u(r),r),this},u.fn.disposeFusionCharts=function(){var t=n(this);return t.each(function(){this.FusionCharts.dispose(),delete this.FusionCharts,0===this._fcDrillDownLevel&&delete this._fcDrillDownLevel}),this},u.fn.convertToFusionCharts=function(t,e){var n=[];return void 0===t.dataConfiguration&&(t.dataConfiguration={}),u.extend(!0,t.dataConfiguration,e),t.dataSource||(t.dataSource=this.get(0)),t.renderAt?"string"==typeof t.renderAt?n.push(u("#"+t.renderAt).insertFusionCharts(t).get(0)):"object"==typeof t.renderAt&&n.push(u(t.renderAt).insertFusionCharts(t).get(0)):this.each(function(){n.push(u("
").insertBefore(this).insertFusionCharts(t).get(0))}),u(n)},u.fn.drillDownFusionChartsTo=function(){var t,e,i,a,o,s=n(this);for(void 0===this._fcDrillDownLevel&&(this._fcDrillDownLevel=0),t=0,e=arguments.length;e>t;t+=1)if(o=arguments[t],c(o))for(i=0,a=o.length;a>i;i+=1)r(s,o[i],this._fcDrillDownLevel),this._fcDrillDownLevel+=1;else r(s,o,this._fcDrillDownLevel),this._fcDrillDownLevel+=1;return this},u.fn.streamFusionChartsData=function(t,e,r,i){var a,o,s,u=n(this),h=[];if(o=f[t&&t.toLowerCase()],void 0===o){if(1!==arguments.length)return this;s=[t],o=f.feed}else s=1===arguments.length?[]:l[o](e,r,i);return"getData"===o||"getDataForId"===o?(u.each(function(){a=this.FusionCharts,"function"==typeof a[o]&&h.push(a[o].apply(a,s))}),h):(u.each(function(){a=this.FusionCharts,"function"==typeof a[o]&&a[o].apply(a,s)}),this)},u.extend(u.expr[":"],{FusionCharts:function(t){return t.FusionCharts instanceof i.core}})}]); \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..3b744b6 --- /dev/null +++ b/package.json @@ -0,0 +1,88 @@ +{ + "name": "xt-edge", + "description": "FusionCharts jQuery Plugin", + "homepage": "http://www.fusioncharts.com/", + "version": "1.0.1", + "license": "MIT", + "author": "FusionCharts Technologies ", + + "keywords": [ + "fusioncharts", + "jquery", + "plugin" + ], + + "preferGlobal": false, + "repository": { + "type": "git", + "url": "https://github.com/fusioncharts/fusioncharts-jquery-plugin.git" + }, + + "dependencies": {}, + "devDependencies": { + "grunt": "^0.4.2", + "grunt-contrib-uglify": "^0.2.7", + "grunt-contrib-jshint": "^0.11.2", + "jshint": "2.4.2" + }, + + "scripts": { + "test": "develop/ci/test/test", + "lint": "develop/ci/lint/lint", + "build": "develop/ci/build/build" + }, + + "jshintConfig": { + "bitwise": false, + "camelcase": true, + "curly": true, + "eqeqeq": false, + "es3": false, + "forin": false, + "freeze": true, + "immed": true, + "indent": 4, + "latedef": true, + "newcap": true, + "noarg": true, + "noempty": true, + "nonew": false, + "plusplus": false, + "quotmark": "single", + "undef": true, + "unused": true, + "trailing": true, + "asi": false, + "boss": true, + "debug": false, + "eqnull": true, + "evil": false, + "expr": true, + "funcscope": false, + "globalstrict": false, + "iterator": false, + "lastsemic": false, + "laxbreak": false, + "laxcomma": false, + "loopfunc": false, + "maxlen": 120, + "multistr": false, + "notypeof": false, + "proto": false, + "scripturl": false, + "smarttabs": false, + "shadow": false, + "sub": false, + "supernew": false, + + "browser": false, + "devel": false, + "node": true, + + "onevar": true, + + "globals": { + "FusionCharts": true + } + } +} diff --git a/package/fusioncharts-jquery-plugin.js b/package/fusioncharts-jquery-plugin.js new file mode 100644 index 0000000..8aedc3a --- /dev/null +++ b/package/fusioncharts-jquery-plugin.js @@ -0,0 +1,1425 @@ +/**! + * @license FusionCharts JavaScript Library jQuery Plugin v1.0.1 + * Copyright FusionCharts Technologies LLP + * License Information at + * + * @author FusionCharts Technologies LLP + */ +/** + * HTMLTable Data Handler + * This module maps chart type alias with particular FusionCharts object. + * @private + * + * @module fusioncharts.transcoder.htmltable + */ +FusionCharts.register('module', ['private', 'HTMLTableDataHandler', function () { + + var global = this, + win = global.window, + doc = win.document, + /** + * 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 element of the table. In order to support W3C + * non-compliant HTML, wherein the table does not have a containing + * the & 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 in the 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) { + + var len, l, i, j, childArr, mostEmptyCellRow = null, internalLabel = [], + emptyCellCount = [], textCellCount = 0, temp, returnObj = {}, + spanTotal = 0, spanLen, isRowLabel, maxIdx, spanLength, totalSpanLength = 0; + + + 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].getAttribute('colspan'), 10); + temp = (temp > 1) ? temp : parseInt(childArr[j].getAttribute('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; + 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].getAttribute('colspan') !== '1') { + spanLength = parseInt(childArr[j].getAttribute('colspan'), 10); + } + } else if (childArr[j].getAttribute('rowspan') !== '1') { + spanLength = parseInt(childArr[j].getAttribute('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; + + // 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. + 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); + + 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] = {}; + tempColumn = 0; + + 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: ((dataRows > 1 && dataColumns > 1) ? 'multi' : 'single'), + labelMap: columnLabelMap, + legendMap: rowLabelMap + }; + }, + + createChartFromTable = function (data, obj, config) { + + // 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, + + // Private Variables + labels: [], + legend: [], + data: [] + }, i, item1, item2, categoryArr, datasetArr, chartJSON = {}, + datasets = {}, dataObj = extractDataFromTable(data, opts), + dataMap = dataObj.data, labelMap, legendMap; + + global.extend(opts, config); + + 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 = global.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(global.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(global.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(global.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: global.core.transcodeData(chartJSON, 'JSON', 'XML'), + error: undefined + }; + }; + + global.addDataHandler('HTMLTable', { + encode: function (data, obj, config) { + return createChartFromTable(data, obj, config); + }, + decode: function (data, obj) { + /** + * @private + * + * @typedef {RuntimeException} Error-07101734 + * @memberOf FusionCharts.debugger + * @group debugger-error + */ + global.raiseError(obj, '07101734', 'run', '::HTMLTableDataHandler.decode()', + 'FusionCharts HTMLTable data-handler only supports decoding of data.'); + throw new Error('FeatureNotSupportedException()'); + }, + transportable: false + }); +}]); +/** + * @module fusioncharts.jqueryplugin + * @private + * @requires fusioncharts.transcoder.htmltable + * + * @export fusioncharts.jqueryplugin.js + */ +FusionCharts.register('module', ['private', 'extensions.jQueryPlugin', function () { + + var global = this, + win = global.window, + lib = global.hcLib, + doc = win.document, + jQ = win.jQuery, + renderFusionCharts, + captureAllFusionChartsEvents, + getChartObjectsFromSelection, + configureLinkedCharts, + math = win.Math, + mathMin = math.min, + isArray = lib.isArray, + 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 = global.core; + + /** + * 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. + global.core.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 (event, args) { + var containerElement; + + // Extending our event with the jQuery event model for proper + // delegation and bubbling. + jQ.extend(event, jQ.Event('fusioncharts' + event.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); + } + }; + + global.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.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('
') + .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 global.core); + } + }); + +}]); \ No newline at end of file diff --git a/package/fusioncharts-jquery-plugin.min.js b/package/fusioncharts-jquery-plugin.min.js new file mode 100644 index 0000000..f991ab0 --- /dev/null +++ b/package/fusioncharts-jquery-plugin.min.js @@ -0,0 +1,8 @@ +/**! + * @license FusionCharts JavaScript Library jQuery Plugin v1.0.1 + * Copyright FusionCharts Technologies LLP + * License Information at + * + * @author FusionCharts Technologies LLP + */ +FusionCharts.register("module",["private","HTMLTableDataHandler",function(){var a=this,b=a.window,c=b.document,d=function(a){var b,c,d=[];for(c=0,b=a.length;b>c;c+=1)3!==a[c].nodeType&&d.push(a[c]);return d},e=function(a){var b=d(a.childNodes);if(b.length){if("TBODY"===b[0].nodeName)return b[0];if("THEAD"===b[0].nodeName&&b[1]&&"TBODY"===b[1].nodeName)return b[1]}return a},f=function(a){var b=d(a.childNodes);return b.length&&"THEAD"===b[0].nodeName&&b[1]&&"TBODY"===b[1].nodeName?b[0].childNodes:[]},g=function(a){return void 0!==a.innerText?a.innerText:a.textContent},h=function(a){var b,c,e,f,g,h,i,j=1,k={},l=[];for(b=0,e=a.length;e>b;b+=1)for(g=d(a[b].childNodes),j=1,h=0,c=0,f=g.length;f>c;c+=1){for(i=c+j+h-1,k[i]&&b-k[i].rowNum1&&(k[i]||(k[i]={}),k[i].rowNum=b,k[i].row=parseInt(g[c].getAttribute("rowspan"),10),parseInt(g[c].getAttribute("colspan"),10)>1?k[i].col=parseInt(g[c].getAttribute("colspan"),10):k[i].col=1);l.length<=i;)l.push({childNodes:[]});l[i].childNodes.push(g[c]),parseInt(g[c].getAttribute("colspan"),10)>1&&(j+=parseInt(g[c].getAttribute("colspan"),10)-1)}return l},i=function(a,b){for(var c=a.length;c;)if(c-=1,a[c]===b)return!0;return!1},j=function(a,b,c){var e,f,h,i=d(a[b].childNodes);for(e=0,f=i.length;f>e;e+=1)if(e!==c&&(h=g(i[e]),parseFloat(h)===h))return!0;return!1},k="__fcBLANK__",l=0,m=function(a,b,c){var e,f,n,o,p,q,r,s,t,u,v=null,w=[],x=[],y=0,z={},A=0,B=0;if("undefined"==typeof c){for(p=d(a[0].childNodes),o=0,e=p.length;e>o;o+=1)if(r=o+A,w[r]=k+(r+1),q=parseInt(p[o].getAttribute("colspan"),10),q=q>1?q:parseInt(p[o].getAttribute("rowspan"),10),q>1){for(f=1;q>f;f+=1)w[r+f]=k+(r+f+1);A+=q-1}for(n=0,f=o+A,e=b.length;e>n;n+=1)b[n]>0?delete w[b[n]-1]:delete w[f+b[n]];return{index:-1,labelObj:w}}if(0===c){for(n=0,f=a.length;f>n;n+=1){for(p=d(a[n].childNodes),x[n]=0,y=0,o=0,e=p.length;e>o;o+=1)if(!i(b,o+1)&&!i(b,o-e))if(q=g(p[o]),""!==q.replace(/^\s*/,"").replace(/\s*$/,"")){if(parseFloat(q)!=q&&(y+=1,y>1))return m(a,b,n+1)}else x[n]+=1;n>0&&(x[n-1]>x[n]?v=n-1:x[n-1]c?c+=a.length:c>0&&(c-=1),p=d(a[c].childNodes),s=void 0!==a[0].nodeType?!0:!1,o=0,e=p.length;e>o;o+=1)if(u=0,s?"1"!==p[o].getAttribute("colspan")&&(u=parseInt(p[o].getAttribute("colspan"),10)):"1"!==p[o].getAttribute("rowspan")&&(u=parseInt(p[o].getAttribute("rowspan"),10)),u=u>1?u:0,q=g(p[o]),""!==q.replace(/^\s*/,"").replace(/\s*$/,"")?z[o+B]=q:j(h(a),o,c)&&(z[o+B]=k+l,l+=1),u>1){for(q=z[o+B],n=1;u>n;n+=1)z[o+B+n]=q+" ("+n+")";B+=u-1}for(t=e+B,n=0,e=b.length;e>n;n+=1)b[n]>0?delete z[b[n]-1]:delete z[t+b[n]];return{labelObj:z,index:c}},n=function(a,i){if("string"==typeof a&&(a=c.getElementById(a)),"undefined"!=typeof b.jQuery&&a instanceof b.jQuery&&(a=a.get(0)),!a)return{data:null};i.hideTable&&(a.style.display="none");var j,k,l,n,o,p,q,r,s,t,u,v,w,x={},y={},z={},A=d(f(a)).concat(d(e(a).childNodes)),B=A.length,C=0,D=0,E=0,F=0;if(i.rowLabelSource=parseInt(i.labelSource,10),i.colLabelSource=parseInt(i.legendSource,10),u=i.useLabels?m(A,i.ignoreCols,i.rowLabelSource):m(A,i.ignoreCols),w=i.useLegend?m(h(A),i.ignoreRows,i.colLabelSource):m(h(A),i.ignoreRows),delete u.labelObj[w.index],delete w.labelObj[u.index],"row"===i.major)for(r in w.labelObj)x[r]={};else for(r in u.labelObj)x[r]={};for(j=0;B>j;j+=1)if(u.index!==j&&void 0!==w.labelObj[j]){for(C+=1,l=d(A[j].childNodes),y[j]=0,z[j]={},E=0,k=0,q=l.length;q>k;k+=1){for(t=l[k],p=parseInt(t.getAttribute("colspan"),10),s=parseInt(t.getAttribute("rowspan"),10),o=k+y[j];j>F;){if(z[F])for(v in z[F]){if(v>o)break;j-F<=z[F][v].row&&(o+=z[F][v].col)}F+=1}if(p>1&&(y[j]+=p-1),s>1&&(p>1?z[j][o]={row:s-1,col:p}:z[j][o]={row:s-1,col:1}),w.index!==o&&void 0!==u.labelObj[o]){if(E+=1,n=g(t),""===n.replace(/^\s*/,"").replace(/\s*$/,"")){if(!i.convertBlankTo)continue;n=i.convertBlankTo}if(p=p>1?p:1,s=s>1?s:1,"row"===i.major)for(F=0;p>F;){for(v=0;s>v;)x[j+v][o+F]=parseFloat(n),v+=1;F+=1}else for(F=0;p>F;){for(v=0;s>v;)x[o+F][j+v]=parseFloat(n),v+=1;F+=1}}}E>D&&(D=E)}return{data:x,chartType:C>1&&D>1?"multi":"single",labelMap:w,legendMap:u}},o=function(b,c,d){var e,f,g,h,i,j,l,m={chartAttributes:{},major:"row",useLabels:!0,useLegend:!0,labelSource:0,legendSource:0,ignoreCols:[],ignoreRows:[],showLabels:!0,showLegend:!0,seriesColors:[],convertBlankTo:"0",hideTable:!1,labels:[],legend:[],data:[]},o={},p={},q=n(b,m),r=q.data;if(a.extend(m,d),"row"!==m.major?(j=q.legendMap,l=q.labelMap):(j=q.labelMap,l=q.legendMap),o.chart=a.extend({},m.chartAttributes),"multi"===q.chartType){o.categories=[{category:[]}],o.dataset=[],h=o.categories[0].category,i=o.dataset,e=0;for(f in r){m.showLabels===!0?h.push(a.extend({label:"-1"!=j.labelObj[f].indexOf(k)?"":j.labelObj[f]},m.labels[e])):h.push({label:""}),e+=1;for(g in r[f])"undefined"==typeof p[g]&&(p[g]=[]),p[g].push({value:r[f][g]})}e=0;for(f in p)m.showLegend===!0?i.push(a.extend({seriesname:"-1"!==l.labelObj[f].indexOf(k)?"":l.labelObj[f],data:p[f]},m.legend[e])):i.push({seriesname:"",data:p[f]}),e+=1}else if("single"===q.chartType)if(o.data=[],i=o.data,e=0,m.showLabels)for(f in r)for(g in r[f])i.push(a.extend({label:"-1"!==j.labelObj[f].indexOf(k)?"":j.labelObj[f],value:r[f][g]},m.labels[e])),e+=1;else for(f in r)for(g in r[f])i.push({value:r[f][g]});return{data:a.core.transcodeData(o,"JSON","XML"),error:void 0}};a.addDataHandler("HTMLTable",{encode:function(a,b,c){return o(a,b,c)},decode:function(b,c){throw a.raiseError(c,"07101734","run","::HTMLTableDataHandler.decode()","FusionCharts HTMLTable data-handler only supports decoding of data."),new Error("FeatureNotSupportedException()")},transportable:!1})}]),FusionCharts.register("module",["private","extensions.jQueryPlugin",function(){var a,b,c,d,e=this,f=e.window,g=e.hcLib,h=f.document,i=f.jQuery,j=f.Math,k=j.min,l=g.isArray,m={feed:"feedData",setdata:"setData",setdataforid:"setDataForId",getdata:"getData",getdataforid:"getDataForId",clear:"clearChart",stop:"stopUpdate",start:"restartUpdate"},n={feedData:function(a){return"string"==typeof a?[a]:"object"==typeof a&&a.stream?[a.stream]:!1},getData:function(a){return isNaN(a)?"object"==typeof a&&a.index?[a.index]:[]:[a]},getDataForId:function(a){return"string"==typeof a?[a]:"object"==typeof a&&a.id?[a.id]:[]},setData:function(a,b,c){var d=[];return"object"!=typeof a?d=[a,b,c]:(a.value&&d.push(a.value),a.label&&d.push(a.label)),d},setDataForId:function(a,b,c){var d=[];return"string"==typeof a||"string"==typeof b||"string"==typeof c?d=[a,b,c]:"object"==typeof a&&(a.value&&d.push(a.value),a.label&&d.push(a.label)),d},clearChart:function(a){return[a]},stopUpdate:function(a){return[a]},restartUpdate:function(a){return[a]}};i.FusionCharts=e.core,a=function(a,b){var c,d,g,j,m;for(d=l(b)||b instanceof i?k(a.length,b.length):a.length,c=0;d>c;c+=1)g=l(b)||b instanceof i?b[c]:b,a[c].parentNode?e.core.render(i.extend({},g,{renderAt:a[c]})):(j=new FusionCharts(i.extend({},g,{renderAt:a[c]})),i.FusionCharts.delayedRender||(i.FusionCharts.delayedRender={}),i.FusionCharts.delayedRender[j.id]=a[c],m=h.createElement("script"),m.setAttribute("type","text/javascript"),/msie/i.test(f.navigator.userAgent)&&!f.opera?m.text="FusionCharts.items['"+j.id+"'].render();":m.appendChild(h.createTextNode("FusionCharts.items['"+j.id+"'].render()")),a[c].appendChild(m));return a},b=function(a,b){var c;i.extend(a,i.Event("fusioncharts"+a.eventType)),a.sender&&a.sender.options?(c=a.sender.options.containerElement||a.sender.options.containerElementId,"object"==typeof c?i(c).trigger(a,b):i("#"+c).length?i("#"+c).trigger(a,b):i(h).trigger(a,b)):i(h).trigger(a,b)},e.addEventListener("*",b),c=function(a){return a.filter(":FusionCharts").add(a.find(":FusionCharts"))},d=function(a,b,c){"object"==typeof b&&a.each(function(){this.configureLink(b,c)})},i.fn.insertFusionCharts=function(b){return a(this,b)},i.fn.appendFusionCharts=function(b){return b.insertMode="append",a(this,b)},i.fn.prependFusionCharts=function(b){return b.insertMode="prepend",a(this,b)},i.fn.attrFusionCharts=function(a,b){var d=[],e=c(this);return void 0!==b?(e.each(function(){this.FusionCharts.setChartAttribute(a,b)}),this):"object"==typeof a?(e.each(function(){this.FusionCharts.setChartAttribute(a)}),this):(e.each(function(){d.push(this.FusionCharts.getChartAttribute(a))}),d)},i.fn.updateFusionCharts=function(a){var b,d,e,f,g,h,i={},j=c(this),k=[["swfUrl",!1],["type",!1],["height",!1],["width",!1],["containerBackgroundColor",!0],["containerBackgroundAlpha",!0],["dataFormat",!1],["dataSource",!1]];for(b=0,d=k.length;d>b;b+=1)g=k[b][0],i.type=i.type||i.swfUrl,a[g]&&(k[b][1]&&(f=!0),i[g]=a[g]);return j.each(function(){return e=this.FusionCharts,f?(h=e.clone(i),void h.render()):((void 0!==i.dataSource||void 0!==i.dataFormat)&&(void 0===i.dataSource?e.setChartData(e.args.dataSource,i.dataFormat):void 0===i.dataFormat?e.setChartData(i.dataSource,e.args.dataFormat):e.setChartData(i.dataSource,i.dataFormat)),(void 0!==i.width||void 0!==i.height)&&e.resizeTo(i.width,i.height),void(i.type&&e.chartType(i.type)))}),this},i.fn.cloneFusionCharts=function(a,b){var d,e,f;return"function"!=typeof a&&"function"==typeof b&&(e=a,a=b,b=e),d=[],f=c(this),f.each(function(){d.push(this.FusionCharts.clone(b,{},!0))}),a.call(i(d),d),this},i.fn.disposeFusionCharts=function(){var a=c(this);return a.each(function(){this.FusionCharts.dispose(),delete this.FusionCharts,0===this._fcDrillDownLevel&&delete this._fcDrillDownLevel}),this},i.fn.convertToFusionCharts=function(a,b){var c=[];return"undefined"==typeof a.dataConfiguration&&(a.dataConfiguration={}),i.extend(!0,a.dataConfiguration,b),a.dataSource||(a.dataSource=this.get(0)),a.renderAt?"string"==typeof a.renderAt?c.push(i("#"+a.renderAt).insertFusionCharts(a).get(0)):"object"==typeof a.renderAt&&c.push(i(a.renderAt).insertFusionCharts(a).get(0)):this.each(function(){c.push(i("
").insertBefore(this).insertFusionCharts(a).get(0))}),i(c)},i.fn.drillDownFusionChartsTo=function(){var a,b,e,f,g,h=c(this);for("undefined"==typeof this._fcDrillDownLevel&&(this._fcDrillDownLevel=0),a=0,b=arguments.length;b>a;a+=1)if(g=arguments[a],l(g))for(e=0,f=g.length;f>e;e+=1)d(h,g[e],this._fcDrillDownLevel),this._fcDrillDownLevel+=1;else d(h,g,this._fcDrillDownLevel),this._fcDrillDownLevel+=1;return this},i.fn.streamFusionChartsData=function(a,b,d,e){var f,g,h,i=c(this),j=[];if(g=m[a&&a.toLowerCase()],void 0===g){if(1!==arguments.length)return this;h=[a],g=m.feed}else h=1===arguments.length?[]:n[g](b,d,e);return"getData"===g||"getDataForId"===g?(i.each(function(){f=this.FusionCharts,"function"==typeof f[g]&&j.push(f[g].apply(f,h))}),j):(i.each(function(){f=this.FusionCharts,"function"==typeof f[g]&&f[g].apply(f,h)}),this)},i.extend(i.expr[":"],{FusionCharts:function(a){return a.FusionCharts instanceof e.core}})}]); \ No newline at end of file diff --git a/src/banner.js b/src/banner.js new file mode 100644 index 0000000..41dac7a --- /dev/null +++ b/src/banner.js @@ -0,0 +1,7 @@ +/**! + * @license FusionCharts JavaScript Library jQuery Plugin v@VERSION + * Copyright FusionCharts Technologies LLP + * License Information at + * + * @author FusionCharts Technologies LLP + */ diff --git a/src/fusioncharts-jquery-plugin.js b/src/fusioncharts-jquery-plugin.js index d62f3ab..87af8e5 100644 --- a/src/fusioncharts-jquery-plugin.js +++ b/src/fusioncharts-jquery-plugin.js @@ -1,10 +1,3 @@ -/**! - * @license FusionCharts JavaScript Library jQuery Plugin - * Copyright FusionCharts Technologies LLP - * License Information at - * - * @author FusionCharts Technologies LLP - */ /** * @module fusioncharts.jqueryplugin * @private diff --git a/src/transcoder-htmltable/transcoder-htmltable.js b/src/transcoder-htmltable/transcoder-htmltable.js new file mode 100755 index 0000000..ed0fe6e --- /dev/null +++ b/src/transcoder-htmltable/transcoder-htmltable.js @@ -0,0 +1,758 @@ +/** + * HTMLTable Data Handler + * This module maps chart type alias with particular FusionCharts object. + * @private + * + * @module fusioncharts.transcoder.htmltable + */ +FusionCharts.register('module', ['private', 'HTMLTableDataHandler', function () { + + var global = this, + win = global.window, + doc = win.document, + /** + * 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 element of the table. In order to support W3C + * non-compliant HTML, wherein the table does not have a containing + * the & 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 in the 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) { + + var len, l, i, j, childArr, mostEmptyCellRow = null, internalLabel = [], + emptyCellCount = [], textCellCount = 0, temp, returnObj = {}, + spanTotal = 0, spanLen, isRowLabel, maxIdx, spanLength, totalSpanLength = 0; + + + 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].getAttribute('colspan'), 10); + temp = (temp > 1) ? temp : parseInt(childArr[j].getAttribute('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; + 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].getAttribute('colspan') !== '1') { + spanLength = parseInt(childArr[j].getAttribute('colspan'), 10); + } + } else if (childArr[j].getAttribute('rowspan') !== '1') { + spanLength = parseInt(childArr[j].getAttribute('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; + + // 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. + 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); + + 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] = {}; + tempColumn = 0; + + 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: ((dataRows > 1 && dataColumns > 1) ? 'multi' : 'single'), + labelMap: columnLabelMap, + legendMap: rowLabelMap + }; + }, + + createChartFromTable = function (data, obj, config) { + + // 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, + + // Private Variables + labels: [], + legend: [], + data: [] + }, i, item1, item2, categoryArr, datasetArr, chartJSON = {}, + datasets = {}, dataObj = extractDataFromTable(data, opts), + dataMap = dataObj.data, labelMap, legendMap; + + global.extend(opts, config); + + 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 = global.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(global.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(global.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(global.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: global.core.transcodeData(chartJSON, 'JSON', 'XML'), + error: undefined + }; + }; + + global.addDataHandler('HTMLTable', { + encode: function (data, obj, config) { + return createChartFromTable(data, obj, config); + }, + decode: function (data, obj) { + /** + * @private + * + * @typedef {RuntimeException} Error-07101734 + * @memberOf FusionCharts.debugger + * @group debugger-error + */ + global.raiseError(obj, '07101734', 'run', '::HTMLTableDataHandler.decode()', + 'FusionCharts HTMLTable data-handler only supports decoding of data.'); + throw new Error('FeatureNotSupportedException()'); + }, + transportable: false + }); +}]);