Click here to Skip to main content
15,895,815 members
Articles / Web Development / XHTML

Data Analyzer: HTML Table to Chart

Rate me:
Please Sign up or sign in to vote.
4.91/5 (23 votes)
14 Feb 2013CPOL14 min read 59.8K   1.8K   70  
A few lines of code to create dynamic charts based on the HTML table contents with filter option
/*
Data Analyzer
Author        : Niral Soni
Date created  : 10-Jan-2013
Date modified : 15-Jan-2013
Version       : 2.0
Email         : soniniral@yahoo.co.in
*/

function DataAnalyzer(thisObject, dataSourceId, vHeaderStartsAt, vAddFilter, vBypassCols, vAnalyzeCols) {
	this.chartHeight    = 800;
	this.chartWidth     = 250;
  var selfObject      = thisObject,
  dataSource          = _ge(dataSourceId) || null,
  headerStartsAt      = vHeaderStartsAt || 1,
  addFilter           = vAddFilter || false,
  bypassCols          = vBypassCols || [],
  analyzeCols         = vAnalyzeCols || [],
  MetaData            = null,
  Charts              = null,
  sourceList          = null,
  sourceType          = 0,
  ErrorCode           = { 'SOURCE_NOT_SET'    : '"DataSource" is not set.',
                        'INVALID_OBJECT'      : '"DataSource" is not an Object.',
                        'INVALID_TABLE'       : '"DataSource" is not a Table Object.',
                        'HEADERROWS_NOT_SET'  : '"headerStartsAt" count is not set.',
                        'INVALID_NUMBER'      : 'Specified value in "HeaderRows" is not a Number.',
                        'OUT_OF_RANGE'        : 'Specified value in "HeaderRows" is out of range.',
                        'NO_DATA_AVAILABLE'   : '"DataSource" does not contain any data to analyze',
                        'SUCCESS'             : 'Success',
                        'INVALID_VALUE'       : 'Method invoked with improper values.',
                        'NO_METADATA'         : 'Metadata is not available.'},
  DataTypes           = { 'NULL'              : 0,
                          'NUMBER'            : 1,
                          'STRING'            : 2},
  SourceTypes         = { 'ALL_COLUMNS'       : 0,
                          'ANALYZE_COLUMNS'   : 1,
                          'BYPASS_COLUMNS'    : 2}
	autoAnalyzeOnFilter = false,
	filterOnly          = false,
	chartSrc            = 'Flash Chart';

  function init() {
    MetaData      = [];
    Charts        = [{'Title': 'Show All Charts'}];
    sourceList    = [];
    sourceType    = SourceTypes.ALL_COLUMNS;
    if(dataSource == null) { return ErrorCode.SOURCE_NOT_SET; }

    if(typeof dataSource != 'object') { return ErrorCode.INVALID_OBJECT; }

    if(dataSource.nodeName.toUpperCase() != 'TABLE') { return ErrorCode.INVALID_TABLE; }

    if(headerStartsAt == null) { return ErrorCode.HEADERROWS_NOT_SET; }

    if(typeof headerStartsAt != 'number') { return ErrorCode.INVALID_NUMBER; }

    if((dataSource.rows.length - headerStartsAt) < 0 ) { return ErrorCode.OUT_OF_RANGE; }

    if((dataSource.rows.length - headerStartsAt) == 0) { return ErrorCode.NO_DATA_AVAILABLE; }

    if(analyzeCols.length > 0) {
      sourceList = analyzeCols;
      sourceType = SourceTypes.ANALYZE_COLUMNS;
    }
    else if(bypassCols.length > 0)  {
      sourceList = bypassCols;
      sourceType = SourceTypes.BYPASS_COLUMNS;
    }
    return ErrorCode.SUCCESS;
  }

  function columnExists(compareText, compareIndex) {
		var colCount, colValue, colLength;
    for(colCount = 0, colLength = sourceList.length; colCount < colLength; colCount++) {
      colValue = sourceList[colCount];
      if(colValue == null) continue;

      colValue = Trim(colValue);
      if(colValue.length == 0) continue;

      if(isNaN(colValue) && colValue == compareText) return true;
      else if(parseInt(colValue,10) == compareIndex) return true;
    }
    return false;
  }

  function analyzeHeaders() {
    var rowCount = headerStartsAt - 1, 
			colCount, colLength, colText, colExists,
			currentRow = dataSource.rows[rowCount];
    if(currentRow.cells == null) return;

    for(colCount = 0, colLength = currentRow.cells.length; colCount < colLength; colCount++) {
      MetaData[colCount] = null;

      if(currentRow.cells[colCount] == null) continue;

      colText = (document.all) ? currentRow.cells[colCount].innerText : currentRow.cells[colCount].textContent;
      if(colText == null) continue;

      colText = Trim(colText);
      if(colText.length == 0) continue;

      colExists = columnExists(colText, colCount);

      if((colExists && sourceType == SourceTypes.ANALYZE_COLUMNS) ||
          (!colExists && sourceType == SourceTypes.BYPASS_COLUMNS) ||
            sourceType == SourceTypes.ALL_COLUMNS ) {
        MetaData[colCount]              = {};
        MetaData[colCount].columnRow    = rowCount;
        MetaData[colCount].columnIndex  = colCount;
        MetaData[colCount].columnLabel  = colText;
        MetaData[colCount].columnType   = DataTypes.NULL;
        MetaData[colCount].columnData   = {};
      }
      else {
        MetaData[colCount] = null;
      }
    }
    if(addFilter) {
      addAutoFilter();
    }
  }

  function addAutoFilter() {
    if(_ge('filterHolder')) return;
    var filterHolder = document.createElement('DIV');
    filterHolder.id = 'filterHolder';
    dataSource.parentNode.insertBefore(filterHolder, dataSource);
    var filterContent = '<span style="float: left;">Filter By: <select id="CmbSearch">' + 
        '<option value="-1">Global Search</option>';
    for(var i = 0, j = MetaData.length; i < j; i++ ) {
      if(MetaData[i] == null) continue;
      filterContent += '<option value="' + MetaData[i].columnIndex + '">' + MetaData[i].columnLabel + '</option>';
    }
    filterContent += '</select><input onkeyup="' + selfObject + '.filterTable(this, \'' + 
        dataSource.id + '\', this.previousSibling.value,' + headerStartsAt + ')" type="text">';
    filterContent += '<input type="button" value="Clear" onclick="this.previousSibling.value = \'\'; ' + 
        'this.previousSibling.onkeyup();"></span>';
    filterContent += '<span id="TotalCount" style="float: right;">Total Records - ' + 
        (dataSource.rows.length-headerStartsAt) + '</span>';
    filterHolder.innerHTML = filterContent;
  }

	function clearAnalyzedData() {
		for(var count = 0; count < MetaData.length; count++) {
			if(MetaData[count] == null) continue;
			MetaData[count].columnData   = {};
		}
	}

  function analyzeData() {
		clearAnalyzedData();
    var visibleRows = 0, currentRow, currentCell, length, count, uniqueCount = 0, colText, colCount, rowCount;
    for(rowCount = headerStartsAt, rowLength = dataSource.rows.length; rowCount < rowLength; rowCount++) {
      currentRow = dataSource.rows[rowCount];
      if(currentRow.style.display == 'none') continue;
      visibleRows++;

      for(colCount = 0, colLength = currentRow.cells.length; colCount < colLength; colCount++) {
        if(MetaData[colCount] == null) continue;
        currentCell = currentRow.cells[colCount];
        if(currentCell == null) continue;

        colText = (document.all) ? currentCell.innerText : currentCell.textContent;
        if(colText == null) continue;

        colText = Trim(colText);
        if(colText.length == 0) continue;

        if(isNaN(colText) && MetaData[colCount].columnType <= DataTypes.STRING) {
          MetaData[colCount].columnType = DataTypes.STRING;
        }
        else if(MetaData[colCount].columnType <= DataTypes.NUMBER) {
          MetaData[colCount].columnType = DataTypes.NUMBER;
        }

        if(MetaData[colCount].columnData[colText] == null) {
          MetaData[colCount].columnData[colText]        = {};
          MetaData[colCount].columnData[colText].Count  = 1;
          MetaData[colCount].columnData[colText].Rows   = [];
        }
        else {
          MetaData[colCount].columnData[colText].Count =
            (MetaData[colCount].columnData[colText].Count + 1);
        }
        length = MetaData[colCount].columnData[colText].Rows.length;
        MetaData[colCount].columnData[colText].Rows[length] = rowCount;
      }
    }
    for(count = 0, length = MetaData.length; count < length; count++) {
      if(MetaData[count] == null) continue;
      uniqueCount = 0;
      for(data in MetaData[count].columnData) uniqueCount++;

      if(uniqueCount == visibleRows) {
        MetaData[count] = null;
      }
      else {
        MetaData[count].DataLength = uniqueCount;
      }
    }
  }

  function prepareDataSets() {
    var report_suffix = '', count, DataSet, data, length;
    Charts = [{'Title': 'Show All Charts'}];

    for(count = 0; count < MetaData.length; count++) {
      if(MetaData[count] == null) continue;

      DataSet = [];
      for(columnName in MetaData[count].columnData) {
        data = [];
        data[0] = columnName;
        data[1] = MetaData[count].columnData[columnName].Count;
        DataSet[DataSet.length] = data;
      }
      if(DataSet.length > 1) {
        length = Charts.length;
        Charts[length] = {};
        Charts[length].Title    = 'Group by ' + MetaData[count].columnLabel;
        Charts[length].LabelX = MetaData[count].columnLabel;
        Charts[length].LabelY = "Count";
        Charts[length].Source = count;
        Charts[length].DataSet  = DataSet;
      }
      else if(DataSet.length == 1) {
        report_suffix += MetaData[count].columnLabel + '=' + DataSet[0][0] + ' and ';
      }
    }
    if(report_suffix.length > 0) {
      report_suffix = ' for ' + report_suffix.substr(0 , report_suffix.length-5);
      for(count = 0; count < Charts.length; count++) {
          Charts[count].Title = Charts[count].Title + report_suffix;
      }
    }
  }

	function updateDataSet(operation, numColumn, reportIndex) {
		if(Charts[reportIndex].Source == numColumn) return;

		if(operation == -1 || numColumn == -1) {
			prepareDataSets();
			return;
		}

		var sum = 0, rowCount, colCount = 0, data, DataSet = [], currentCell, colText, label = '';

		for(columnName in MetaData[Charts[reportIndex].Source].columnData) {
			data = [], sum = colCount = 0;
			data[0] = columnName;

			for(rowCount = 0; rowCount < MetaData[Charts[reportIndex].Source].columnData[columnName].Rows.length; rowCount++) {
				row = MetaData[Charts[reportIndex].Source].columnData[columnName].Rows[rowCount];
        currentCell = dataSource.rows[row].cells[numColumn];
        if(currentCell == null) continue;

        colText = (document.all) ? currentCell.innerText : currentCell.textContent;
        if(colText == null) continue;

        colText = Trim(colText);
        if(colText.length == 0) continue;

				if(!isNaN(colText)) {				
					sum += parseInt(colText, 10);
					colCount++;
				}
			}

			switch(operation) {
				case 'Sum': 
					data[1] = sum;
					break;
				case 'Average':
					data[1] = parseFloat(sum / colCount, 10).toFixed(2);
					break;
			}			
			DataSet[DataSet.length] = data;
		}
		Charts[reportIndex].LabelY  = operation + ' of ' + MetaData[numColumn].columnLabel;
		Charts[reportIndex].DataSet = DataSet;
	}

  function report() {
    var option, cmbObj, chartType, i, j, count,
		statusObj  = _ge('AnalysisStatus'),
		errMsgObj  = _ge('errMsg'),
    dest       = _ge('chartContainer'),
    details    = [{ 'label'       : '&nbsp;&nbsp;Chart Type : ', 
                    'attributes'  : { 'id'        : 'cmbChartType', 
                                      'onchange'  : '_ge("cmbPossibleCharts").onchange()'
                                    }, 
                    'data'        : ['Bar Chart', 'Pie Chart', 'Random'], 
                    'value'       : ['bar', 'pie', 'Random'] 
                  },
                  { 'label'       : '&nbsp;&nbsp;Possible Charts : ', 
                    'attributes'  : { 'id'        : 'cmbPossibleCharts', 
                                      'size'      : 1, 
                                      'onchange'  : selfObject + '.draw(this.value);' 
                                    }, 
                    'data'        : ['----Select----'], 
                    'value'       : [-1] 
                  },
                  { 'label'       : '&nbsp;&nbsp;along with : ', 
                    'attributes'  : { 'id'        : 'cmbOperation', 
                                      'size'      : 1,
                                      'onchange'  : '_ge("cmbPossibleCharts").onchange()'
                                    }, 
                    'data'        : ['----Operation----', 'Sum', 'Average'], 
                    'value'       : [-1, 'Sum', 'Average'] 
                  },
                  { 'label'       : '&nbsp;&nbsp;of : ', 
                    'attributes'  : { 'id'        : 'cmbNumCols', 
                                      'size'      : 1,
                                      'onchange'  : '_ge("cmbPossibleCharts").onchange()'
                                    }, 
                    'data'        : ['----Numeric Column----'], 
                    'value'       : [-1] 
                  }];
    cmbObj = _ge('cmbPossibleCharts');
    if(Charts.length > 1) {
      if(cmbObj == null) {
        for(i = 0; i < details.length; i++) {
          cmbObj = document.createElement('select');
          for(attr in details[i].attributes) {
            cmbObj.setAttribute(attr, details[i].attributes[attr]);
          }
          for(j = 0; j < details[i].data.length; j++) {
            option = document.createElement('option');
            option.value = details[i].value[j];
            option.text = details[i].data[j];
            try { cmbObj.add(option); }
            catch (e) { cmbObj.add(option, null); }
          }
          statusObj.innerHTML += details[i].label;
          statusObj.appendChild(cmbObj);
        }
      }
      else {
        cmbObj.options.length = 1;
      }
			cmbObj = _ge('cmbPossibleCharts');
      cmbObj.options[0].selected = true;
      for(count = 0; count < Charts.length; count++) {
        option = document.createElement('option');
        option.value = count;
        option.text = Charts[count].Title;
        try { cmbObj.add(option); }
        catch (e) { cmbObj.add(option, null); }
      }
      cmbObj.options[1].value = Charts.length;
      if(errMsgObj) {
        errMsgObj.innerHTML = '';
      }
			cmbObj = _ge('cmbNumCols');
			if(cmbObj.options.length == 1) {
				cmbObj.options[0].selected = true;
				for(count = 0; count < MetaData.length; count++) {
					if(MetaData[count] == null) continue;
					if(MetaData[count].columnType == DataTypes.NUMBER) {
						option = document.createElement('option');
						option.value = count;
						option.text = MetaData[count].columnLabel;
						try { cmbObj.add(option); }
						catch (e) { cmbObj.add(option, null); }
					}
				}
			}
    }
    else {
      cmbObj.options.length = 1;
      if(!errMsgObj) {
        errMsgObj = document.createElement('span');
        errMsgObj.id = 'errMsg';
        statusObj.appendChild(errMsgObj);
      }
      errMsgObj.innerHTML = '<br>No "Group By" Charts possible as all available records are distinct.';
    }
    dest.innerHTML = '';
  }

  function Trim(s) {
    if(s.length > 0) {
      while (s.substring(0,1) == ' ') s = s.substring(1,s.length);
      while (s.substring(s.length-1,s.length) == ' ') s = s.substring(0,s.length-1);
      if(s.search(/[\S]/) == -1) s="";
    }
    return s;
  }

	function analyze() {
    analyzeHeaders();
    if(addFilter && filterOnly) return true;
    analyzeData();
    prepareDataSets();
    report();
	}

  this.start = function(vFilterOnly, vAutoAnalyzeOnFilter) {
    var status = init();
		autoAnalyzeOnFilter = vAutoAnalyzeOnFilter || autoAnalyzeOnFilter;
		filterOnly = vFilterOnly || filterOnly;
    if(status != ErrorCode.SUCCESS) {
      alert(status);
      return false;
    }
		analyze();
		return true;
  }

  this.filterTable = function(phrase, tableId, searchIn, headerStartsAt) {
    var words = phrase.value.toLowerCase().split(" "),
				table = _ge(tableId), ele, i,
				cnt   = headerStartsAt || 1;
    for (var r = cnt; r < table.rows.length; r++) {
			searchIn = (typeof searchIn == 'undefined' || searchIn == null ) ? -1 : searchIn;
      if(parseInt(searchIn,10) == -1)
        ele = (document.all) ? table.rows[r].innerText : table.rows[r].textContent;
      else if(parseInt(searchIn,10) > -1)
        ele = (document.all) ? table.rows[r].cells[searchIn].innerText : table.rows[r].cells[searchIn].textContent;

      var displayStyle = '';
      for (i = 0; i < words.length; i++) {
        if (ele.toLowerCase().indexOf(words[i])>=0) {
          displayStyle = '';
        }
        else {
          displayStyle = 'none';
          cnt++;
          break;
         }
      }
      table.rows[r].style.display = displayStyle;
    }
    _ge('TotalCount').innerHTML = "Total Records - " + (table.rows.length-cnt);
    cnt = 0;
    for(i = headerStartsAt; i < table.rows.length; i++) {
      if(table.rows[i].style.display != 'none') {
        table.rows[i].className = (cnt%2 == 0) ? 'TablerowBG1' : 'TablerowBG2';
        cnt++;
      }
    }
		if(!filterOnly && autoAnalyzeOnFilter) {
			analyze();
		}
  }

  this.draw = function(reportIndex) {
    var dest = _ge('chartContainer'), chartType,
    count = (reportIndex == Charts.length) ? 1 : reportIndex,
		operation = _ge('cmbOperation').value,
		numColumn = _ge('cmbNumCols').value;
    dest.innerHTML = '';
    if(reportIndex == -1) return;
    do {
			updateDataSet(operation, numColumn, count);
      var div = document.createElement('div');
      div.id = "Chart" + count;
      div.className = 'chartCSS';
      dest.appendChild(div);

			chartType =  _ge('cmbChartType').value;
			if(chartType == 'Random') {
				chartType = (Math.floor(Math.random()*2)) == 0 ? 'pie' : 'bar';
			}

      if(chartSrc == 'Flash Chart') {
        window['myChart'+count] = new SWFObject("open-flash-chart.swf", div.id, this.chartHeight, this.chartWidth, "9", "#FFFFFF");
        window['myChart'+count].addVariable("variables","true");
        window['myChart'+count].addVariable('x_label_style','10,0x0000FF,2');
        window['myChart'+count].addVariable("colours", "#d01f3c,#356aa0,#C79810,#73880A,#D15600,#6BBA70,#D54C78,#C31812");
        window['myChart'+count].addVariable("tool_tip", Charts[count].LabelX + ": #x_label#<br>" + (operation != -1 ? Charts[count].LabelY : 'Count') + ": #val#");
        window['myChart'+count].addVariable((chartType == 'pie' ? 'pie' : 'bar_glass'), '50,#7E97A6');
        window['myChart'+count].addVariable("title",Charts[count].Title + ',{font-size: 16px;}');
        window['myChart'+count].addVariable("x_legend",Charts[count].LabelX + ',12,#40515B');
        window['myChart'+count].addVariable("y_legend",Charts[count].LabelY + ',12,#40515B');
        window['myChart'+count].addVariable('y_min','0');
        var lbl = cnt = '', max = 0, j, dataset;
        for(j = 0, dataset = Charts[count].DataSet; j < dataset.length; j++) {
          lbl += dataset[j][0] + (j == dataset.length -1 ? '' : ',');
          cnt += dataset[j][1] + (j == dataset.length -1 ? '' : ',');
          max = (Math.ceil(dataset[j][1]) > max) ? Math.ceil(dataset[j][1]) : max;
        }
        window['myChart'+count].addVariable('y_max',max+5);
        window['myChart'+count].addVariable("values", cnt);
        window['myChart'+count].addVariable((chartType == 'pie' ? 'pie' : 'x') + "_labels",lbl);
        window['myChart'+count].write(div.id);
      }
      count++;
    }while(count < reportIndex);
  }
}

function _ge(id) {
	return document.getElementById(id);
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Business Analyst Tata Consultancy Services Ltd.
India India
I started my career as Java Developer. But now more inclined towards web designing, JavaScript, HTML and CSS.I have good level of expertise on Java, Oracle and JavaScript. Designing Generic components is my main expertise.

Comments and Discussions