JavaXT
|
|
BarGraph Classif(!javaxt) var javaxt={}; if(!javaxt.dhtml) javaxt.dhtml={}; //****************************************************************************** //** BarGraph //****************************************************************************** /** * Used to create a simple bar graph. * ******************************************************************************/ javaxt.dhtml.BarGraph = function (parent, config) { this.className = "javaxt.dhtml.BarGraph"; var me = this; var chartRow, tickRow, labelRow; var innerTable, dataTable, yAxis, horizontalLines; var cellWidth; var values = []; var minValue = 0; var maxValue = 0; var template = {}; var defaultConfig = { animate: false, //null or false to disable animationSteps: 750, maxHorizonalLabels: 3, style: { cell: { borderBottom: "1px solid #ccc", padding: "0 5px" }, bar: { backgroundColor: "#7baaf7", borderRadius: "3px 3px 0 0", cursor: "pointer" }, tick: { borderLeft: "1px solid #ccc", borderRight: "1px solid #ccc", height: "5px" }, labelX: { textAlign: "center", color: "#757575", fontSize: "10px", height: "14px" }, labelY: { textAlign: "right", color: "#757575", fontSize: "10px", padding: "0 5px 0 0", margin: "-5px 0 0 0" }, hline:{ borderTop: "1px solid #ccc", opacity: 0.5, zIndex: 1 }, select:{ backgroundColor: "orange" } } }; //************************************************************************** //** Constructor //************************************************************************** var init = function(){ if (typeof parent === "string"){ parent = document.getElementById(parent); } if (!parent) return; //Clone the config so we don't modify the original config object var clone = {}; merge(clone, config); //Merge clone with default config merge(clone, defaultConfig); config = clone; //Create relative div used to bound the horizontal bars var mainDiv = document.createElement('div'); mainDiv.style.position = "relative"; mainDiv.style.height = "100%"; mainDiv.setAttribute("desc", me.className); me.el = mainDiv; parent.appendChild(mainDiv); //Create table with one cell with a "bottom" vertical alignment //used for animation purposes var table = createTable(); mainDiv.appendChild(table); var tbody = table.firstChild; var tr = document.createElement('tr'); tbody.appendChild(tr); var td = document.createElement('td'); td.style.width = "100%"; td.style.height = "100%"; td.style.verticalAlign = "bottom"; tr.appendChild(td); //Create table with 2 columns. The first column is used for y-axis labels //and the second column is used for the bar graphs and x-axis labels. innerTable = createTable(); td.appendChild(innerTable); tbody = innerTable.firstChild; tr = document.createElement('tr'); tbody.appendChild(tr); td = document.createElement('td'); td.style.height = "100%"; td.style.width = "1px"; td.style.verticalAlign = "top"; yAxis = createTable(); yAxis.setAttribute("desc", "yAxis"); td.appendChild(yAxis); tr.appendChild(td); td = document.createElement('td'); td.style.height = "100%"; td.style.width = "100%"; tr.appendChild(td); //Create relative div for the second column var outerDiv = document.createElement('div'); outerDiv.style.position = "relative"; outerDiv.style.width = "100%"; outerDiv.style.height = "100%"; td.appendChild(outerDiv); //Add horizontal lines in the second column as an absolute div var div = document.createElement('div'); div.style.position = "absolute"; div.style.width = "100%"; div.style.height = "100%"; div.style.top = 0; div.style.overflow = 'hidden'; div.style.padding = "0 1px"; //hack for chrome and safari outerDiv.appendChild(div); var innerDiv = document.createElement('div'); innerDiv.style.position = "relative"; innerDiv.style.width = "100%"; div.appendChild(innerDiv); horizontalLines = createTable(); innerDiv.appendChild(horizontalLines); //Add vertical bars in the second column as an absolute div var div2 = document.createElement('div'); div2.style.position = "absolute"; div2.style.width = "100%"; div2.style.height = "100%"; div2.style.overflow = 'auto'; div2.style.overflowY = 'hidden'; div2.style.padding = "0 1px"; //hack for chrome and safari outerDiv.appendChild(div2); dataTable = createTable(); dataTable.setAttribute("desc", "dataTable"); tbody = dataTable.firstChild; chartRow = document.createElement('tr'); tbody.appendChild(chartRow); tickRow = document.createElement('tr'); tbody.appendChild(tickRow); labelRow = document.createElement('tr'); tbody.appendChild(labelRow); div2.appendChild(dataTable); if (config.animate===true){ innerTable.style.height = 0; } onRender(outerDiv, function(){ me.update(); }); }; //************************************************************************** //** show //************************************************************************** this.show = function(){ me.el.style.display = ''; }; //************************************************************************** //** hide //************************************************************************** this.hide = function(){ me.el.style.display = 'none'; }; //************************************************************************** //** add //************************************************************************** /** Used to add a value to the graph. Label is optional. */ this.add = function(value, label){ if (!isNumber(value)) value=0; values.push(value); if (value>maxValue) maxValue = value; if (value<minValue) minValue = value; var td, bar; if (!template.cell){ //Add column td = document.createElement('td'); setStyle(td, "cell"); td.style.height = "100%"; td.style.verticalAlign = "bottom"; var div = document.createElement('div'); div.style.position = "relative"; div.style.height = "100%"; td.appendChild(div); //Add bar bar = document.createElement('div'); setStyle(bar, "bar"); bar.style.position = "absolute"; bar.style.bottom = 0; bar.style.width = "100%"; div.appendChild(bar); template.cell = td; } else{ td = template.cell.cloneNode(true); bar = td.childNodes[0].childNodes[0]; } bar.onclick = function(){ var col = this.parentNode.parentNode; for (var i=0; i<chartRow.childNodes.length; i++){ if (chartRow.childNodes[i]==col){ me.onClick(values[i], i, this); break; } } }; td.onclick = function(){ var col = this; for (var i=0; i<chartRow.childNodes.length; i++){ if (chartRow.childNodes[i]==col){ me.onCellClick(i, col); break; } } }; chartRow.appendChild(td); //Update column and table width as needed if (config.style.cell.width){ td.style.width = config.style.bar.width; td.childNodes[0].style.width = config.style.bar.width; cellWidth = parseInt(config.style.cell.width); } else{ if (typeof config.style.cell === "string"){ if (!cellWidth){ var temp = document.createElement("div"); setStyle(temp, "cell"); temp.style.position = "absolute"; temp.style.visibility = 'hidden'; temp.style.display = 'block'; var body = document.getElementsByTagName("body")[0]; body.appendChild(temp); var style = temp.currentStyle || window.getComputedStyle(temp); var getStyle = function(prop){ if (style.getPropertyValue){ var val = style.getPropertyValue(prop); if (val && val.length>0) return val; return style.getPropertyValue(prop); } else{ return style[prop]; } }; cellWidth = parseInt(getStyle("width")); body.removeChild(temp); temp = null; } } } //Add tick var td = document.createElement('td'); setStyle(td, "tick"); tickRow.appendChild(td); //Add label td = document.createElement('td'); setStyle(td, "labelX"); if (label!=null){ var outerDiv = document.createElement('div'); outerDiv.style.position = "relative"; outerDiv.style.width = "100%"; outerDiv.style.height = "100%"; td.appendChild(outerDiv); var innerDiv = document.createElement('div'); innerDiv.style.position = "absolute"; innerDiv.style.width = "100%"; innerDiv.style.height = "100%"; outerDiv.appendChild(innerDiv); if (typeof label === "string" || isNumber(label)){ innerDiv.innerHTML = label; } else if (label instanceof Function){ label.apply(me, [innerDiv]); } } labelRow.appendChild(td); }; //************************************************************************** //** setValue //************************************************************************** this.setValue = function(idx, value){ values[idx] = value; if (value>maxValue) maxValue = value; if (value<minValue) minValue = value; }; //************************************************************************** //** update //************************************************************************** this.update = function(callback){ //Update heights of all the bars for (var i=0; i<values.length; i++){ var div = chartRow.childNodes[i].childNodes[0].childNodes[0]; if (div){ var val = (values[i]/maxValue)*100; div.style.height = val + "%"; } } //Update width of the data table if (!isNaN(cellWidth)){ if (cellWidth>0){ var width = (cellWidth*values.length) + "px"; dataTable.style.width = width; horizontalLines.parentNode.style.width = width; } } //Calculate number of horizontal bars var numHorizonalLabels = yAxis.firstChild.childNodes.length; var maxHorizonalLabels = config.maxHorizonalLabels; var y = getUniqueValues(values).length; if (y<maxHorizonalLabels){ maxHorizonalLabels = y; } var rowHeight = Math.round((1/maxHorizonalLabels)*100) + "%"; //Update maxHorizonalLabels if all the values are 0 if (y===1 && values[0]===0){ rowHeight = "100%"; maxHorizonalLabels = 0; } //Update numHorizonalLabels if the number is greater than maxHorizonalLabels var t1 = yAxis.firstChild; var t2 = horizontalLines.firstChild; if (numHorizonalLabels>maxHorizonalLabels){ while (t1.childNodes.length>0){ t1.removeChild(t1.childNodes[0]); } while (t2.childNodes.length>0){ t2.removeChild(t2.childNodes[0]); } numHorizonalLabels = 0; } //Add new horizonal bars and labels as needed for (var i=numHorizonalLabels; i<maxHorizonalLabels; i++){ //Add label and tick on the y-axis var tr = document.createElement('tr'); t1.appendChild(tr); var td = document.createElement('td'); td.style.verticalAlign = "top"; tr.appendChild(td); var div = document.createElement('div'); setStyle(div, "labelY"); td.appendChild(div); td.label = div; var td = document.createElement('td'); td.style.verticalAlign = "top"; tr.appendChild(td); var div = document.createElement('div'); setStyle(div, "hline"); div.style.width = "100%"; td.appendChild(div); //Add horizontal line tr = document.createElement('tr'); t2.appendChild(tr); td = document.createElement('td'); td.style.verticalAlign = "top"; tr.appendChild(td); var div = document.createElement('div'); setStyle(div, "hline"); div.style.width = "100%"; div.style.position = "absolute"; td.appendChild(div); } //Update heights and labels for (var i=0; i<t1.childNodes.length; i++){ var td = t1.childNodes[i].childNodes[0]; td.style.height = rowHeight; var numRows = t1.childNodes.length; var idx = numRows-i; var stepSize = maxValue/maxHorizonalLabels; var val = stepSize*idx; me.createLabel(val, idx, numRows, td.label); } for (var i=0; i<t2.childNodes.length; i++){ var td = t2.childNodes[i].childNodes[0]; td.style.height = rowHeight; } if (config.animate===true){ yAxis.style.display = 'none'; animate(new Date().getTime(), config.animationSteps, function(){ yAxis.style.display = ''; yAxis.style.height = chartRow.offsetHeight + "px"; horizontalLines.style.height = chartRow.offsetHeight + "px"; if (callback!=null){ callback.apply(me, []); } }); } else{ yAxis.style.height = chartRow.offsetHeight + "px"; horizontalLines.style.height = chartRow.offsetHeight + "px"; if (callback!=null){ callback.apply(me, []); } } }; //************************************************************************** //** clear //************************************************************************** this.clear = function(saveColumns){ minValue = 0; maxValue = 0; me.deselectAll(); if (saveColumns===true){ for (var i=0; i<values.length; i++){ values[i] = 0; } //me.update(); //<-- Do not use b/c it update labels. Instead do this: for (var i=0; i<values.length; i++){ var div = chartRow.childNodes[i].childNodes[0].childNodes[0]; div.style.height = "0%"; } } else{ values = []; while (chartRow.childNodes.length>1){ chartRow.removeChild(chartRow.childNodes[1]); tickRow.removeChild(tickRow.childNodes[1]); labelRow.removeChild(labelRow.childNodes[1]); } dataTable.style.width = "100%"; horizontalLines.parentNode.style.width = "100%"; me.update(); } }; //************************************************************************** //** onClick //************************************************************************** /** Called whenever a bar in the graph is clicked. */ this.onClick = function(val, idx, div){}; //************************************************************************** //** onCellClick //************************************************************************** /** Called whenever a cell in the graph is clicked. */ this.onCellClick = function(idx, cell){}; //************************************************************************** //** getCell //************************************************************************** /** Returns a cell in the bargraph at a given index */ this.getCell = function(idx){ return chartRow.childNodes[idx]; }; //************************************************************************** //** select //************************************************************************** /** Used to "select" a bar in the graph by adding the select style to the * bar. */ this.select = function(idx){ var td = chartRow.childNodes[idx]; if (td){ td.selected = true; var bar = td.childNodes[0].childNodes[0]; addStyle(bar, "select"); } }; //************************************************************************** //** deselect //************************************************************************** /** Used to "deselect" a bar in the graph by removing the select style from * the bar. */ this.deselect = function(idx){ var td = chartRow.childNodes[idx]; if (td){ td.selected = false; var bar = td.childNodes[0].childNodes[0]; var h = bar.style.height; setStyle(bar, "bar"); bar.style.position = "absolute"; bar.style.bottom = 0; bar.style.width = "100%"; bar.style.height = h; } }; //************************************************************************** //** selectAll //************************************************************************** /** Used to "select" all the bars in the graph by adding the select style to * the bars. */ this.selectAll = function(){ for (var i=0; i<chartRow.childNodes.length; i++){ me.select(i); } }; //************************************************************************** //** deselectAll //************************************************************************** /** Used to "deselect" all the bars in the graph by removing the select * style from all the bars. */ this.deselectAll = function(){ for (var i=0; i<chartRow.childNodes.length; i++){ me.deselect(i); } }; //************************************************************************** //** getSelectedItems //************************************************************************** /** Returns an array of selected items. Each item in the array includes the * index, value, and cell */ this.getSelectedItems = function(){ var arr = []; for (var i=0; i<chartRow.childNodes.length; i++){ var td = chartRow.childNodes[i]; if (td.selected) arr.push({ idx: i, val: values[i], cell: td }); } return arr; }; //************************************************************************** //** createLabel //************************************************************************** /** Default implementation. Users can override. */ this.createLabel = function(val, idx, max, div){ div.innerHTML = Math.round(val); }; //************************************************************************** //** getValues //************************************************************************** this.getValues = function(){ var arr = []; for (var i=0; i<values.length; i++){ arr.push(values[i]); } return arr; }; //************************************************************************** //** getUniqueValues //************************************************************************** var getUniqueValues = function(arr) { var a = []; for (var i=0, l=arr.length; i<l; i++) if (a.indexOf(arr[i]) === -1 && arr[i] !== '') a.push(arr[i]); return a; }; //************************************************************************** //** isNumber //************************************************************************** var isNumber = function(n) { return !isNaN(parseFloat(n)) && !isNaN(n - 0); }; //************************************************************************** //** animate //************************************************************************** /** Used to adjust the hieght of the bar graph. */ var animate = function(lastTick, timeLeft, callback){ var curTick = new Date().getTime(); var elapsedTicks = curTick - lastTick; //If the animation is complete, ensure that the panel is completely open if (timeLeft <= elapsedTicks){ innerTable.style.height = "100%"; if (callback!=null){ callback.apply(me, []); } return; } timeLeft -= elapsedTicks; var percentComplete = (timeLeft/config.animationSteps)*100; innerTable.style.height = (100-percentComplete) + "%"; setTimeout(function(){ animate(curTick, timeLeft, callback); }, 33); }; //************************************************************************** //** Utils //************************************************************************** var merge = javaxt.dhtml.utils.merge; var onRender = javaxt.dhtml.utils.onRender; var createTable = javaxt.dhtml.utils.createTable; var setStyle = function(el, style){ javaxt.dhtml.utils.setStyle(el, config.style[style]); }; var addStyle = function(el, style){ javaxt.dhtml.utils.addStyle(el, config.style[style]); }; init(); }; |