function TimelineDisplay(parentElementId, streamNodes, streamUtilities) { var mStreamUtilities = streamUtilities; this.streamNodes = streamNodes; var zIndex = 300; var closeCanvas = document.createElement( "canvas" ); closeCanvas.style.position = "absolute"; closeCanvas.width = mStreamUtilities.GetTextWidth('Close Timeline', closeContext, "normal " + 12 + "px sans-serif") + 2; closeCanvas.height = 18; document.getElementById( parentElementId ).appendChild( closeCanvas ); //closeCanvas.style.background = 'green'; $(closeCanvas).hide(); var closeContext = closeCanvas.getContext( '2d' ); closeCanvas.style.zIndex = zIndex; var mChangesBuf = new Array(); var mChangeToNodeHash = new Array(); var pageLeftButtonCanvas = document.createElement( "canvas" ); pageLeftButtonCanvas.style.position = "absolute"; document.getElementById( parentElementId ).appendChild( pageLeftButtonCanvas ); pageLeftButtonCanvas.height = 30; pageLeftButtonCanvas.width = 16; //pageLeftButtonCanvas.style.background = 'green'; pageLeftButtonCanvas.style.zIndex = zIndex; var searchInput = document.createElement( "input" ); searchInput.style.position = "absolute"; document.getElementById( parentElementId ).appendChild( searchInput ); searchInput.style.zIndex = zIndex; var searchLabel = document.createElement( "label" ); searchLabel.style.position = "absolute"; document.getElementById( parentElementId ).appendChild( searchLabel ); searchLabel.style.zIndex = zIndex; var labelText = 'Search for changelist:'; searchLabel.innerHTML = labelText; this.Location = function(x, y) { var marg = 20; searchInput.size = 5; pageLeftButtonCanvas.style.left = x + marg; searchLabelWidth = mStreamUtilities.GetTextWidth( searchLabel.innerHTML, searchLabel ); searchLabel.style.left = x + parseInt(document.body.clientWidth/2) - searchLabelWidth; searchLabel.style.top = marg/2; searchInput.style.left = searchLabel.offsetLeft + searchLabelWidth; searchInput.style.top = marg/4; closeCanvas.style.left = parseInt(document.body.clientWidth) - (parseInt(closeCanvas.width) + marg); closeCanvas.style.top = y; } function draw() { mStreamUtilities.CreateRoundedRectangle(2, 2, closeCanvas.width - 4, closeCanvas.height - 4, 4, closeContext) closeContext.fillStyle = 'darkorange'; closeContext.fill(); closeContext.strokeStyle = 'dodgerblue'; closeContext.stroke(); closeContext.font = "normal " +closeCanvas.height * 0.5 + "px sans-serif"; closeContext.textBaseline = "middle"; closeContext.fillStyle = 'black'; closeContext.fillText('Close Timeline', 8, closeCanvas.height/2 - closeCanvas.height * 0.02); var stroke = 8; var pageLeftContext = pageLeftButtonCanvas.getContext( '2d' ); pageLeftContext.beginPath(); pageLeftContext.moveTo(0, pageLeftButtonCanvas.height/2); pageLeftContext.lineTo(pageLeftButtonCanvas.width, 0); pageLeftContext.lineTo(pageLeftButtonCanvas.width, stroke); pageLeftContext.lineTo(stroke, pageLeftButtonCanvas.height / 2); pageLeftContext.lineTo(pageLeftButtonCanvas.width, pageLeftButtonCanvas.height - stroke); pageLeftContext.lineTo(pageLeftButtonCanvas.width, pageLeftButtonCanvas.height); pageLeftContext.lineTo(0, pageLeftButtonCanvas.height/2); pageLeftContext.closePath(); enableButtons(); } function enableButtons() { var pageLeftColor = 'lightgray'; if(mEnablePageLeftButton) pageLeftColor = 'dodgerblue'; var pageLeftContext = pageLeftButtonCanvas.getContext( '2d' ); pageLeftContext.fillStyle = pageLeftColor; pageLeftContext.fill(); } function changeNodeClickListener() { this.clicked = function(changelist, x, y) { changeNodeClicked(changelist, x, y); } } function changeNodeClicked(changelist, x, y) { for( var i in mChangeToNodeHash ) { mChangeToNodeHash[i].Selected = mChangeToNodeHash[i].changelist().Change == changelist.Change; mChangeToNodeHash[i].Highlighted = false;//clear highlighted nodes here, we're going to do them below } //go through all nodes, highlight all that have this changelist in their history for( var i in mChangeToNodeHash ) { var history = mChangeToNodeHash[i].changelist().history; for( var j = 0; j < history.length; j++ ) { if( history[j].Change == changelist.Change ) { mChangeToNodeHash[i].Highlighted = true; break; } } } //highlight all history nodes for( var i = 0; i < changelist.history.length; i++ ) { var change = changelist.history[i].Change; if( mChangeToNodeHash[change] ) mChangeToNodeHash[change].Highlighted = true; } //now draw the nodes to show selection and highlighting for( var i in mChangeToNodeHash ) mChangeToNodeHash[i].drawNode(); mStreamUtilities.showChangeDetails(changelist, null, null); } this.addChange = function(change) { //console.log('addChange: ' + streamNode.stream.mName + ' change: ' + change.Change); if( !mChangeToNodeHash[change.Change] ) mChangesBuf.push(change); } var mChangeData; this.changeData = function( changeData ) { mChangeData = changeData; } var mLinks = new Array(); function getNodeStartX() { var startingX = 0; //set the starting x at the length longest stream name for( var i = 0; i < streamNodes.length; i++ ) if( streamNodes[i].nameWidth() > startingX) startingX = streamNodes[i].nameWidth() + 20; return startingX; } var mLowestChangeSoFar; var mEnablePageLeftButton = false; var mCurrentPage = 0; var mNumberOfVisibleNodes; this.currentPage = function() { return mCurrentPage; } this.layoutNodes = function(numberOfVisibleNodes) { if( numberOfVisibleNodes ) mNumberOfVisibleNodes = numberOfVisibleNodes; var startingX = getNodeStartX(); var startingNode = mChangesBuf.length - 1; var endingNode = 0; if( mChangesBuf.length > mNumberOfVisibleNodes ) endingNode = mChangesBuf.length - mNumberOfVisibleNodes var endOfTheLine = 0; if( mChangesBuf.length < mNumberOfVisibleNodes ) endOfTheLine = mNumberOfVisibleNodes - mChangesBuf.length; //console.log('visibleChanges: ' + mNumberOfVisibleNodes + ' ' + 'mChangesBuf.length: ' + mChangesBuf.length); mEnablePageLeftButton = mNumberOfVisibleNodes <= mChangesBuf.length; enableButtons(); //set the position of change nodes. Since they are sorted in chronological order, we can increment // the x position based on this i in the for loop for( var i = startingNode; i > endingNode - 1; i-- ) { var change = mChangesBuf[i]; var changeNode; changeNode = new ChangeNode(change, change.streamNode.getNameHeight(), parentElementId, mStreamUtilities); changeNode.addClickListener(new changeNodeClickListener()); mChangeToNodeHash[change.Change] = changeNode; changeNode.Location(startingX, change.streamNode.getNameY()); changeNode.drawNode(); changeNode.moveRight((i - endingNode) + endOfTheLine); //console.log( 'setting change: ' + changeNode.changelist().Change + ' i: ' + i ); mLowestChangeSoFar = change.Change; } } var mMaxVisibleChangeNodes; this.maxVisibleChangeNodes = function() { var nWidth = parseInt(mTimelineWidth) - getNodeStartX(); mMaxVisibleChangeNodes = parseInt( nWidth / streamNodes[0].getNameHeight() ); //console.log( 'maxNodes: ' + mMaxVisibleChangeNodes); return mMaxVisibleChangeNodes; } this.assembleHistory = function() { for(var i in mChangeToNodeHash) { var changeList = mChangeToNodeHash[i].changelist(); //var test = 'history for changelist ' + changeList.Change + ': '; var history = changeList.history; for( var j = 0; j < history.length; j++ ) { if( j + 1 < history.length ) { var rightNode = mChangeToNodeHash[history[j].Change]; var leftNode = mChangeToNodeHash[history[j + 1].Change]; if(rightNode && leftNode && !linkAlreadyExists(rightNode, leftNode) ) { var link = new Object(); link.rightPoint = rightNode.getCenter(); link.rightChange = history[j].Change link.leftPoint = leftNode.getCenter(); link.leftChange = history[j + 1].Change link.drawn = false; mLinks.push(link); //test += ' ' + rightNode.changelist().Change + '-' + leftNode.changelist().Change + ' '; //console.log(test); } /*else if( !rightNode && !leftNode) { console.log('both right and left are missing. Does this mean they are both to the left of the currently drawn nodes? ' + history[j].Change + '-' + history[j + 1].Change); } else if( !rightNode ) { console.log('ONLY the right is missing: ' + history[j].Change + '-' + history[j + 1].Change); } else if(!leftNode) { console.log('ONLY the left is missing: ' + history[j].Change + '-' + history[j + 1].Change); }*/ } } } //console.log(test); } this.updateView = function() { this.layoutNodes(null); for( var i = 0; i < mChangesBuf.length; i++ ) this.assembleHistory(mChangesBuf[i]); this.drawLinks(); enableButtons(); } this.drawLinks = function() { for( var i = 0; i < mLinks.length; i++ ) if( mChangeToNodeHash[ mLinks[i].rightChange ] && mChangeToNodeHash[ mLinks[i].leftChange ] && !mLinks[i].drawn) drawLink( mLinks[i] ); } function linkAlreadyExists( a, b ) { if( a && b ) { for( var i = 0; i < mLinks.length; i++ ) { if( a.changelist().Change == mLinks[i].rightChange && b.changelist().Change == mLinks[i].leftChange ) { //console.log('*******link exists: ' + a.changelist().Change + ' ' + b.changelist().Change); return true; } } //console.log('NO link exists: ' + a.changelist().Change + ' ' + b.changelist().Change); } return false; } function drawLink(link) { var linkCanvas = document.createElement( "canvas" ); linkCanvas.style.position = "absolute"; document.getElementById( parentElementId ).appendChild( linkCanvas ); var x; var y; var height; var width; var label = link.leftChange + '-' + link.rightChange; var rightPoint = link.rightPoint; //console.log( label + ' rightPoint: ' + rightPoint.x + ' : ' + rightPoint.y); var leftPoint = link.leftPoint; //console.log( label + ' leftPoint: ' + leftPoint.x + ' : ' + leftPoint.y); var curveDirection; x = leftPoint.x; width = rightPoint.x - leftPoint.x; if( rightPoint.y < leftPoint.y ) { curveDirection = mStreamUtilities.CurveDirection.bottomToTop; y = rightPoint.y; height = leftPoint.y - rightPoint.y; } else if( rightPoint.y == leftPoint.y ) { curveDirection = mStreamUtilities.CurveDirection.nShaped; height = 20;// link.rightNode.getHeight(); y = rightPoint.y - height; } else { curveDirection = mStreamUtilities.CurveDirection.topToBottom; y = leftPoint.y; height = rightPoint.y - leftPoint.y; } //console.log( label + ' x: ' + x + ' y: ' + y + ' width: ' + width + ' height: ' + height); linkCanvas.style.left = x; linkCanvas.style.top = y; linkCanvas.width = width; linkCanvas.height = height; //linkCanvas.style.background = 'green'; linkCanvas.style.zIndex = 35; link.canvas = linkCanvas; mStreamUtilities.DrawHorizontalColorBezier(linkCanvas, new mStreamUtilities.StreamColor(255,140,0, 1), curveDirection, mStreamUtilities.LinkThickness.thin, 35) link.drawn = true; /*for debugging... var linkContext = linkCanvas.getContext( '2d' ); linkContext.font = "normal " +closeCanvas.height * 0.4 + "px sans-serif"; linkContext.textBaseline = "middle"; linkContext.fillStyle = 'black'; linkContext.fillText(label, 0, 10); */ } var mTimelineWidth = 0; this.Show = function(animTime) { draw(); mTimelineWidth = document.width; var topRow = streamNodes[0].mRow; var topStream = streamNodes[0]; for(var i = 0; i < streamNodes.length; i++) { var streamNode = streamNodes[i]; streamNode.moveForward(20); streamNode.expandNameCanvas(mTimelineWidth); if( topRow < streamNode.mRow ) { topRow = streamNode.mRow; topStream = streamNode; } //console.log(streamNode.stream.mName + ' row: ' + streamNode.mRow); } $(closeCanvas).show('fade', animTime); } var mPageLeftListeners = new Array(); this.addPageLeftListener = function(listener) { mPageLeftListeners.push(listener); } function cleanUp() { mChangesBuf = new Array(); } function pageLeftClicked() { if(mEnablePageLeftButton) { var moveUnits = 0; for(var i in mChangeToNodeHash ) moveUnits = mChangeToNodeHash[i].moveRight(mNumberOfVisibleNodes); for(var i = 0; i < mLinks.length; i++ ) mStreamUtilities.moveElementRight(mLinks[i].canvas, moveUnits, 500); mCurrentPage++; cleanUp(); for(var i = 0; i < mPageLeftListeners.length; i++) mPageLeftListeners[i].pageLeft(streamNodes, mLowestChangeSoFar); } } this.expandNames = function() { for(var i = 0; i < streamNodes.length; i++) streamNodes[i].expandNameCanvas(document.width); } $(pageLeftButtonCanvas).click( pageLeftClicked ); var mCloseListeners = new Array(); this.addCloseListener = function(listener) { mCloseListeners.push(listener); } this.close = function() { closeCanvas.parentElement.removeChild(closeCanvas); pageLeftButtonCanvas.parentElement.removeChild(pageLeftButtonCanvas); searchLabel.parentElement.removeChild(searchLabel); searchInput.parentElement.removeChild(searchInput); for(var i = 0; i < mCloseListeners.length; i++) mCloseListeners[i].Closed(); for (var i in mChangeToNodeHash) mChangeToNodeHash[i].Closed(); for(var i = 0; i < mLinks.length; i++) mLinks[i].canvas.parentElement.removeChild(mLinks[i].canvas); if( $( "#changeDetails" ).dialog( "isOpen" ) ) $( "#changeDetails" ).dialog( "close" ); for(var i = 0; i < streamNodes.length; i++) { streamNodes[i].moveBack(); streamNodes[i].reduceNameCanvas(); } } $(closeCanvas).bind('click', this.close); }
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#1 | 8081 | David George |
Initial submit of JavaScript StreamGraph. Main functionality is: Change Trajectory (Change Flow), Timeline, and GitStreams. |