498 lines
14 KiB
JavaScript
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);
|
|
};
|