/* Graph * Graph object * DOM element * int row_count * int col_rount * Array of Row * Array of Line Row * Row object * DOM element * Array of Cell Cell * Cell object * DOM element * Node * Line (optionally added and removed) Node * Node object * DOM element * Line * Line object * Node start * Node end * on window resize event */ /* ========================================================================== Graph ========================================================================== */ var Graph = Graph || function(target, row_count, col_count) { var self = this; this.rows = []; this.lines = []; this.nodes = []; this.row_count = 0; this.col_count = 0; this.element = target; this.populate(row_count, col_count); // create addRow button var btn = document.createElement('div'); btn.className = 'button add'; btn.addEventListener('click', function(e) { self.addCols(); }); this.element.appendChild(btn); // create delRow button btn = document.createElement('div'); btn.className = 'button rem'; btn.addEventListener('click', function(e) { self.removeCols(); }); this.element.appendChild(btn); }; Graph.prototype.populate = function(row_count, col_count) { // add our rows for (var i = 0; i < row_count; i++) this.addRow(); for (var i = 0; i < col_count; i++) this.addCols(); }; Graph.prototype.addRow = function() { var i = this.rows.length; //this.rows[i] = new GraphRow(this, this.col_count); this.rows[i] = new GraphRow(this, 0); this.rows[i].index = i; if (i == 0) { this.rows[i].element.id = 'first'; } else { if (i-1 == 0) { this.rows[i-1].element.id = 'first'; } else if (i-1 > 0) { this.rows[i-1].element.id = ''; } this.rows[i].element.id = 'last'; } this.row_count++; var event = document.createEvent('HTMLEvents'); event.initEvent('rowadded', true, true); event.row_index = i; event.row = this.rows[i]; this.element.dispatchEvent(event); }; Graph.prototype.deleteRow = function(row_index) { if (typeof row_index == 'undefined' || row_index < 0 || row_index >= this.rows.length) return; // remove row at index var row = this.rows[row_index]; row.clear(); this.rows.splice(row_index, 1); // repair row index properties for (var i = row_index; i < this.rows.length; i++) { this.rows[i].index = i; // TODO: fix id } this.row_count--; var event = document.createEvent('HTMLEvents'); event.initEvent('rowremove', true, true); event.row_index = row_index; event.row = row; this.element.dispatchEvent(event); }; Graph.prototype.addCols = function() { for (var i = 0; i < this.rows.length; i++) { this.rows[i].addColumn(); } this.col_count++; var event = document.createEvent('HTMLEvents'); event.initEvent('coladded', true, true); event.col_count = this.col_count; this.element.dispatchEvent(event); }; Graph.prototype.removeCols = function() { for (var i = 0; i < this.rows.length; i++) { this.rows[i].removeColumn(this.rows[i].cells.length-1); } this.col_count--; var event = document.createEvent('HTMLEvents'); event.initEvent('colremoved', true, true); event.col_count = this.col_count; this.element.dispatchEvent(event); }; Graph.prototype.addColumn = function() { for (var i = 0; i < this.rows.length; i++) { this.rows[i].addColumn(); } }; Graph.prototype.clear = function() { for (var i = 0; i < this.row_count; i++) { this.rows[i].clear(); this.rows[i] = null; } this.nodes = []; this.rows = []; this.row_count = 0; this.col_count = 0; }; Graph.prototype.setNode = function(id, column, value, color) { if (!this.nodes[column]) this.nodes[column] = []; var node = this.nodes[column][id]; if (node) { if (node.element.parentNode) node.element.parentNode.removeChild(node.element); } else { node = new GraphNode(null); this.nodes[column][id] = node; } // get our row and internal offset values var row = Math.round(value); // attach our node to target cell var cell = this.getCell(row, column) if (cell == null) { console.log('setNode: ' + row + 'x' + column +' OOR'); return; } node.parent = cell; node.id = id; node.column = column; node.color = color; node.value = value; cell.element.appendChild(node.element); var offset = (row-value) * 100; node.element.style.bottom = offset+'%'; node.element.style.backgroundColor = color; // connect nodes if (this.nodes[column-1]) { var prev = this.nodes[column-1][id]; if (prev) { if (prev.next != node) prev.next = node; node.prev = prev; node.prev.updateLines(); } } if (this.nodes[column+1]) { var next = this.nodes[column+1][id]; if (next) { if (next.prev != node) next.prev = node; node.next = next; } } // update lines node.updateLines(); }; Graph.prototype.setNodeText = function(id, column, value, color) { if (!this.nodes[column]) return false; var node = this.nodes[column][id]; if (!node) { return false; } node.setText(value, color); return true; }; Graph.prototype.clearNode = function(id, column) { if (!this.nodes[column]) return; var node = this.nodes[column][id]; if (!node) return; node.element.parentNode.removeChild(node.element); delete this.nodes[column][id]; }; Graph.prototype.getCell = function(row_index, col_index) { if (this.rows.length <= row_index) { console.log('getCell: row '+row_index+' is OOR'); return null; } var row = this.rows[row_index]; if (typeof row === 'undefined') { console.log('getCell: row '+row_index+' is undefined'); return null; } if (row.cells.length <= col_index) { console.log('getCell: col '+col_index+' is OOR'); return null; } return row.cells[col_index]; }; Graph.prototype.setCellText = function(row_index, col_index, text) { var row = this.rows[row_index]; var col = row.cells[col_index]; if (col) col.setText(text); }; /* ========================================================================== GraphRow ========================================================================== */ var GraphRow = GraphRow || function(parent, col_count) { this.parent = parent; this.col_count = col_count; this.cells = []; this.element = document.createElement('div'); this.element.className = 'row'; for (var i = 0; i < col_count; i++) this.addColumn(); parent.element.appendChild(this.element); }; GraphRow.prototype.clear = function() { for (var i = 0; i < this.cells.length; i++) { this.cells[i].clear(); this.cells[i] = null; } this.cells = []; this.col_count = 0; this.parent.element.removeChild(this.element); this.element = null; }; GraphRow.prototype.addColumn = function() { var i = this.cells.length; this.cells[i] = new GraphCell(this); this.cells[i].index = i; if (i == 0) { this.cells[i].element.id = 'first'; } else { if (i-1 == 0) { this.cells[i-1].element.id = 'first'; } else if (i-1 > 0) { this.cells[i-1].element.id = ''; } this.cells[i].element.id = 'last'; } // TODO: emit event this.col_count++; }; GraphRow.prototype.removeColumn = function(col_index) { if (typeof col_index == 'undefined' || col_index < 3 || col_index >= this.cells.length) return; // remove row at index this.cells[col_index].clear(); this.cells.splice(col_index, 1); // repair row index properties for (var i = col_index; i < this.cells.length; i++) { this.cells[i].index = i; if (i == 0) this.cells[i].element.id = 'first'; else this.cells[i].element.id = ''; } // FIXME if (this.cells.length > 1) this.cells[this.cells.length-1].element.id = 'last'; // TODO: emit event this.col_count--; }; /* ========================================================================== GraphCell ========================================================================== */ var GraphCell = GraphCell || function(parent) { this.parent = parent; this.column_parent = null; this.element = null; this.text_element = null; this.nodes = []; this.lines = []; this.x = -1; this.y = -1; // this.element = document.createElement('div'); this.element.className = 'cell'; parent.element.appendChild(this.element); }; GraphCell.prototype.clear = function() { for (var node in this.nodes) { this.nodes[node].clear(); delete this.nodes[node]; } this.nodes = []; if (this.text_element != null) this.text_element.parentNode.removeChild(this.text_element); this.element.parentNode.removeChild(this.element); }; GraphCell.prototype.setText = function(text) { if (this.text_element == null) { this.text_element = document.createElement('div'); this.text_element.className = 'cell_text'; this.element.appendChild(this.text_element); } this.text_element.innerHTML = text; }; /* ========================================================================== GraphNode ========================================================================== */ var GraphNode = GraphNode || function(parent) { var self = this; self.func = null; this.next = null; this.prev = null; this.y_start = 0; this.y_end = 0; this.parent = parent; this.text_element = null; this.line = null; this.element = document.createElement('div'); this.element.className = 'node'; this.element.addEventListener('mousedown', function(e) { if (e.which === 1) { self.y_start = e.clientY; self.movefunc = function(e) { self.y_end = e.clientY; }; self.func = function(e) { var delta = self.y_end - self.y_start; var pbox = self.parent.element.getBoundingClientRect(); var pheight = pbox.bottom - pbox.top; var old_index = self.parent.parent.index; var new_index = self.parent.parent.index + Math.round(delta/pheight); self.parent.parent.parent.setNode(self.id, self.parent.index, new_index, self.color); // create and send event using deprecated syntax for IE var event = document.createEvent('HTMLEvents'); event.initEvent('nodemove', true, true); event.node = self; event.from = old_index; event.to = new_index; self.parent.parent.parent.element.dispatchEvent(event); window.removeEventListener('mouseup', self.func); window.removeEventListener('mousemove', self.movefunc); }; window.addEventListener('mousemove', self.movefunc); window.addEventListener('mouseup', self.func); } e.preventDefault(); return false; }); }; GraphNode.prototype.clear = function() { this.element.parentNode.removeChild(this.element); }; GraphNode.prototype.setText = function(value, color) { if (this.text_element == null) { this.text_element = document.createElement('div'); this.text_element.className = 'text'; this.element.appendChild(this.text_element); } if (value == '') { this.text_element.parentNode.removeChild(this.text_element); this.text_element = null; } else { this.text_element.style.color = color; this.text_element.innerHTML = value; } }; GraphNode.prototype.updateLines = function() { if (this.line == null) { this.line = new GraphLine(this); } // do not create line if next node is null if (this.next == null) { // remove line DOM element if (this.line.element.parentNode) this.line.element.parentNode.removeChild(this.line.element); return; } this.line.set(0, this.value, 100, this.next.value, this.color); // attach our line to this node's parent this.parent.element.appendChild(this.line.element); }; /* ========================================================================== GraphColumn ========================================================================== */ var GraphColumn = GraphColumn || function(parent) { this.parent = parent; this.cells = []; }; GraphColumn.prototype.clear = function() { this.cells = []; }; /* ========================================================================== GraphLine ========================================================================== */ var GraphLine = GraphLine || function(parent) { this.parent = parent; this.element = document.createElement('div'); this.element.className = 'line'; this.element.style.position = 'absolute'; this.element.style.left = 0; this.element.style.bottom = 0; this.element.style.width = '100%'; this.element.style.height = '100%'; this.svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); this.svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg'); this.svg.setAttribute('version', '1.1'); this.svg.id = 'svg'; this.svg.style.position = "absolute"; this.svg.style.left = '0'; this.svg.style.bottom = '0'; this.svg.style.width = '100%'; this.svg.style.height = '100%'; this.line = document.createElementNS('http://www.w3.org/2000/svg', 'line'); this.line.setAttribute('x1', 0); this.line.setAttribute('y1', 0); this.line.setAttribute('x2', '100%'); this.line.setAttribute('y2', '100%'); this.line.setAttribute('stroke-width', 2); this.line.setAttribute('stroke', '#00f'); this.svg.appendChild(this.line); this.element.appendChild(this.svg); }; GraphLine.prototype.clear = function() { this.element.parentNode.removeChild(this.element); }; // this function is really gross GraphLine.prototype.set = function(x1, y1, x2, y2, color) { var rows = this.parent.parent.parent.parent.row_count; var ry1 = 100-((y1 % 1) * 100); var ry2 = 100-((y2 % 1) * 100); if (y1 == y2) { this.line.setAttribute('y1', '50%'); this.line.setAttribute('y2', '50%'); this.element.style.bottom = 0; if (ry1 == 50) { this.element.style.top = (50-ry1)+'%'; } else if (ry1 > 50) { this.element.style.top = ((100-ry1)+50)+'%'; } else { this.element.style.top = (50-ry1)+'%'; } this.element.style.height = '100%'; } else if (y1 > y2) { var diff = y1 - y2; this.line.setAttribute('y1', '100%'); this.line.setAttribute('y2', '0%'); if (ry1 == 50) { this.element.style.bottom = '50%'; } else if (ry1 > 50) { this.element.style.bottom = (ry1-100)+'%'; } else { this.element.style.bottom = ry1+'%'; } this.element.style.top = ''; this.element.style.height = (diff*100)+'%'; } else { var diff = y2 - y1; this.line.setAttribute('y1', '0%'); this.line.setAttribute('y2', '100%'); this.element.style.bottom = ''; if (ry1 == 50) { this.element.style.top = '50%'; } else if (ry1 > 50) { this.element.style.top = ((100-ry1)+100)+'%'; } else { this.element.style.top = (100-ry1)+'%'; } this.element.style.height = (diff*100)+'%'; } this.line.setAttribute('stroke', color); };