LGS/lib/Graph.js

498 lines
14 KiB
JavaScript

/*
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);
};