function StreamView(streamUtilities) { var mStreamUtilities = streamUtilities; var mLayout = 'vertical'; this.refresh = function(rootNode, drawStatus, layout) { mLayout = layout; layoutStreams(rootNode); var offsetPoint = new mStreamUtilities.Point(0, 0); getTreeOffset(rootNode, offsetPoint); var padding = 50; offsetPoint.y = -offsetPoint.y + padding; offsetPoint.x = -offsetPoint.x + padding; setOffsets(rootNode, mStreamUtilities.Direction.up, offsetPoint); setOffsets(rootNode, mStreamUtilities.Direction.down, offsetPoint); //setSelectedStream(mSelectedStream); this.drawStreams(rootNode, drawStatus); } this.clearTree = function(elementId) { var graph = document.getElementById(elementId); if ( graph.hasChildNodes() ) { while ( graph.childNodes.length >= 1 ) { graph.removeChild( graph.firstChild ); } } } //go through all of the streams, finding the smallest x and y values (presumably negative), then return a point //that contains the values by which to offset the tree so that it is moved onto the visible document function getTreeOffset(streamNode, offsetPoint) { var pos = streamNode.position(); //console.log('Before offset, stream: ' + streamNode.stream.mName + ' y: ' + pos.y); if( pos.y < offsetPoint.y ) offsetPoint.y = pos.y; //console.log('After test offset, stream: ' + streamNode.stream.mName + ' offsetY: ' + offsetPoint.y); if( pos.x < offsetPoint.x ) offsetPoint.x = pos.x; if( mLayout == 'ancestor' ) { var children = streamNode.children(); for( var i = 0; i < children.length; i++ ) getTreeOffset(children[i], offsetPoint); } else { var upChildren = streamNode.children(mStreamUtilities.Direction.up); var downChildren = streamNode.children(mStreamUtilities.Direction.down); for( var i = 0; i < upChildren.length; i++ ) getTreeOffset(upChildren[i], offsetPoint); for( var i = 0; i < downChildren.length; i++ ) getTreeOffset(downChildren[i], offsetPoint); } } function setOffsets(stream, direction, offsetPoint) { //console.log(stream.stream.mName + " children(" + direction + ") length: " + stream.children(direction)); for(var i = 0; i < stream.children().length; i++) { var child = stream.children()[i]; child.setOffset(offsetPoint); setOffsets(child, direction, offsetPoint); } } this.drawStreams = function(streamNode, showStatus) { //console.log('drawStreams, stream: ' + stream.stream.mName + ' row: ' + stream.row()); //console.log('drawStreams: ' + streamNode.stream.mName + " children(" + direction + ") length: " + streamNode.children(direction).length); if( !streamNode ) return; for(var i = 0; i < streamNode.children(mStreamUtilities.Direction.up).length; i++) { var child = streamNode.children(mStreamUtilities.Direction.up)[i]; if(streamNode.stream.type() != mStreamUtilities.StreamType.typeCount) { var link = new StreamLink(streamNode, child, showStatus, mLayout, mStreamUtilities); link.draw(); } child.draw(showStatus); this.drawStreams(child, showStatus); } for(var i = 0; i < streamNode.children(mStreamUtilities.Direction.down).length; i++) { var child = streamNode.children(mStreamUtilities.Direction.down)[i]; if(streamNode.stream.type() != mStreamUtilities.StreamType.typeCount) { var link = new StreamLink(streamNode, child, showStatus, mLayout, mStreamUtilities); link.draw(); } child.draw(showStatus); this.drawStreams(child, showStatus); } } function sortNonConformingNodes(nodeA, nodeB) { //sort non-conforming nodes by their distance from their root; this keeps 'em from crossing lines. if( nodeA.nonConformingBranchParent && nodeB.nonConformingBranchParent && (nodeA.nonConformingBranchParent == nodeB.nonConformingBranchParent)) { //console.log('nodeA: ' + nodeA.stream.mName + 'nodeB: ' + nodeB.stream.mName + ' sort: ' + nodeA.nonConformingBranchLevel - nodeB.nonConformingBranchLevel) return nodeA.nonConformingBranchLevel - nodeB.nonConformingBranchLevel; } return nodeA.mX - nodeB.mX; } var mRows; function layoutStreams(root) { // set positions if( mLayout == 'ancestor' ) { var ancestorLayout = new AncestorLayout(mStreamUtilities); ancestorLayout.treeLayout(root); } else { var layout = new ModifiedWalkerLayout(mStreamUtilities); layout.treeLayout(root); //fix any overlapping nodes. This can happen when a non-conforming stream, e.g. a release stream with a dev stream for a parent, overlaps //a stream above it's parent //store all streams by row, then sort each row by x value, left to right. Go through each row node by node; if there is overlap //between a node and the previous node, move the node to right. Wash, rinse, and repeat. mRows = new Array(); var children = root.children(mStreamUtilities.Direction.down);//gets all mainline nodes for(var i = 0; i < children.length; i++) setRowArray(children[i]); //two passes... //first pass, if a stream is non-conforming, make it's x and all of it's descendants x the same as it's parent for(var i = 0; i < children.length; i++) setNonconformingX(children[i]); //second pass, move it to the right of the previous node for(var i = 0; i < mRows.length; i++) { //console.log('row: ' + row); var graphRow = mRows[i]; graphRow.sort(sortNonConformingNodes); var lastX = null; for( var j = 0; j < graphRow.length; j++ ) { var node = graphRow[j]; if( lastX && node.mX <= lastX) node.mX = lastX; lastX = node.mX + node.width() + 10; //console.log('node: ' + node.stream.mName + ' x: ' + node.mX + ' width: ' + node.width()); } } } var children = root.children(mStreamUtilities.Direction.down);//gets all mainline nodes for(var i = 0; i < children.length; i++) setTofuFirmness(children[i]); } function setNonconformingX(node) { //test for nonconforming relationships. If a node is non-conforming, set it and all of it's descedants x to the parent x if( mStreamUtilities.isNonConformingRelationship( node.mParent, node ) ) { node.mParent.nonConformingBranchParent = node.mParent.stream.mStream; node.mParent.nonConformingBranchLevel = 0; recursiveXSetter( node, node.mParent.mX - node.mParent.width()*.1, node.mParent.stream.mStream, 0 ) } else { var kids = node.children(); for(var i = 0; i < kids.length; i++ ) setNonconformingX(kids[i]); } } function recursiveXSetter(node, x, nonConformingBranchParent, nonConformingBranchLevel) { node.mX = x; node.nonConformingBranchParent = nonConformingBranchParent; node.nonConformingBranchLevel = ++nonConformingBranchLevel; var kids = node.children(); for(var i = 0; i < kids.length; i++ ) recursiveXSetter(kids[i], x, nonConformingBranchParent, nonConformingBranchLevel); } function setRowArray(node) { if( !mRows[node.mRow] ) mRows[node.mRow] = new Array(); mRows[node.mRow].push(node) var children = node.children(); for(var i = 0; i < children.length; i++) setRowArray(children[i]); } function setTofuFirmness(mainline) { var highest = getLastRow(mainline, mStreamUtilities.Direction.up, 0); var lowest = getLastRow(mainline, mStreamUtilities.Direction.down, 0); var numRows = 1; if( highest != lowest ) numRows = (highest - lowest) + 1; var tofuDelta = 1 / numRows; //console.log(' mainline: ' + mainline.stream.mName + 'highest: ' + highest + ' lowest: ' + lowest + ' numRows: ' + numRows + ' tofuDelta: ' + tofuDelta ); setFirmness(mainline, highest, tofuDelta); } function setFirmness(streamNode, highest, tofuDelta) { streamNode.firmness = (highest - streamNode.mRow) * tofuDelta; var upChildren = streamNode.children(mStreamUtilities.Direction.up); var downChildren = streamNode.children(mStreamUtilities.Direction.down); for(var i = 0; i < upChildren.length; i++) setFirmness(upChildren[i], highest, tofuDelta); for(var i = 0; i < downChildren.length; i++) setFirmness(downChildren[i], highest, tofuDelta); } function getLastRow(streamNode, direction, last) { var children = streamNode.children(direction); for(var i = 0; i < children.length; i++) { var row = getLastRow(children[i], direction, last); if(direction == mStreamUtilities.Direction.up) { if( row > last ) last = row; } else { if( row < last ) last = row; } } var row = streamNode.mRow; if(direction == mStreamUtilities.Direction.up) { if( row > last ) last = row; } else { if( row < last ) last = row; } //console.log('stream: ' + streamNode.stream.mName + ' last: ' + last); return last; } }
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#2 | 8110 | David George | This is a fairly unstable checkin that is a first attempt at getting the StreamGraph to work with the refactored ClientKit. | ||
#1 | 8081 | David George |
Initial submit of JavaScript StreamGraph. Main functionality is: Change Trajectory (Change Flow), Timeline, and GitStreams. |