function StreamNode(stream, parentNode, layout, streamUtilities) { var horizontalLayout = false; if( layout && layout=='ancestor' ) horizontalLayout = true; var mStreamUtilities = streamUtilities; this.stream = stream; var shadowStroke = 2; var strokeWidth = 1; var showStateIcons = true; var scaleFactor = 1; var MINZOOMFACTOR = 0.2; var outlineWidth = 1; this.firmness = .6; this.selected = false; this.highlighted = false; this.mParent = parentNode; // these are used to calculate position, calculated once the tree is built this.mX = 0; // calculated final x position (init to 0) this.mCurPos = 0; // iterative position that approaches mX this.mWidth = 50; this.mSubtreeIndex = 0; // this is set as the tree is built, but needed by the placement algorithm this.lowerChildren = new Array(); this.upperChildren = new Array(); this.mLeftSibling = null; var nodeList = new NodeList(mStreamUtilities); // how tall each circle is var BOX_HEIGHT = 60; // how much space a row consumes var ROW_HEIGHT = BOX_HEIGHT*2; if( horizontalLayout ) ROW_HEIGHT = BOX_HEIGHT * 1.40; this.RowHeight = function() { return ROW_HEIGHT; } this.position = function() { return new mStreamUtilities.Point(this.mX, (-1*this.mRow)*(ROW_HEIGHT)); } this.firmerThanParent = function() { //console.log('isUpperStream() type: ' + this.mType); if( this.stream ) return this.stream.mFirmerThanParent ; return false; } this.isFirmType = function() { return this.stream.type() == mStreamUtilities.StreamType.release || this.stream.type() == mStreamUtilities.StreamType.staging; } this.isMainline = function() { if( this.stream ) return this.stream.mType == mStreamUtilities.StreamType.mainLine; return false; } this.calcChildRow = function( parentNode, bypass) { if( !bypass ) { if (parentNode == null || parentNode.stream.mType == mStreamUtilities.StreamType.typeCount) return 0; // no parent implies mainline (row 0) //console.log('calChildRow( parentNode) parentNode: ' + parentNode + ' parentNode.stream.mType: ' + parentNode.stream.mType + ' this.stream: ' + this.stream + ' StreamType: ' + this.stream.mType ); var row = parentNode.mRow; if (!this.firmerThanParent()) return row - 1; else // must be "up" return row + 1; } } this.mRow = this.calcChildRow(this.mParent, false); this.row = function(){ return this.mRow; } this.subtreeIndex = function(){ return this.mSubtreeIndex; } var mAllChildren = new Array(); this.addChild = function( s ) { // main stores in both directions, other Streams just have "children" var debug = 'lower'; var dir = this.lowerChildren; if(s.stream && s.firmerThanParent()) { dir = this.upperChildren; debug = 'upper'; } // set the left sibling of s and the index in the parent list s.mLeftSibling = (dir.length > 0) ? dir[dir.length - 1] : null; s.mSubtreeIndex = dir.length; // add it to our list dir.push(s); mAllChildren.push(s); //console.log('adding ' + s.stream.mName + ' to ' + this.stream.mName + ' ' + debug + ' array: ' + dir.join()); } if (this.mParent) this.mParent.addChild(this); this.children = function( dir ) //const { if( !dir ) return mAllChildren; if (this.stream && (this.stream.mType == mStreamUtilities.StreamType.typeCount || dir == 0)) { //console.log('returning lowerChildren of: ' + this.stream.mName); return this.lowerChildren; } if(dir < 0) { //console.log('returning lowerChildren of: ' + this.stream.mName); return this.lowerChildren } else { //console.log('returning upperChildren of: ' + this.stream.mName); return this.upperChildren; } } this.x = function() { return this.mX; } this.nameWidth = function() { return mStreamUtilities.GetTextWidth(this.stream.mName, nameContext); } this.width = function() { var w = this.nameWidth(); var multiplier = .1; var workspaceWidth = 20; if( nodeList.count() > 0 ) { workspaceWidth = nodeList.width(); multiplier = .1; } var marg = BOX_HEIGHT * multiplier; if( w > BOX_HEIGHT + workspaceWidth ) return w + marg + workspaceWidth; return BOX_HEIGHT + marg + workspaceWidth; //return w + this.mWidth + 12; } // we want a margin of 12 pix this.leftSibling = function() { return this.mLeftSibling; } this.isLeaf = function(dir){ return this.children(dir).length == 0; } this.leftmostChild = function(dir) { return (this.isLeaf(dir)) ? null : this.children(dir)[0]; } this.rightmostChild = function(dir) { return (this.isLeaf(dir)) ? null : this.children(dir)[this.children(dir).length - 1]; } this.leftmostSibling = function(dir) { return this.mParent.leftmostChild(dir); } this.isSibling = function( p, dir) { var kids = this.mParent.children(dir); for(var i = 0; i < kids.length; i++) if( kids[i] === p ) return true; return false; } var mSceneX = 0;//dg--this replaces moving the mGI scene stuff, one hopes... this.stepMove = function(moveRate) { if (this.stream && this.stream.mType != mStreamUtilities.StreamType.typeCount) { var pos = this.position(); var posParent = this.mParent.position(); var newPos = (pos.x - posParent.x)*moveRate; mSceneX = newPos; } // now all children for(var i = 0; i < this.lowerChildren.length; i++) this.lowerChildren[i].stepMove(moveRate); for(var i = 0; i < this.upperChildren.length; i++) this.upperChildren[i].stepMove(moveRate); } this.offsetX = 0; this.offsetY = 0; this.setOffset = function(point) { //console.log('offset.x: ' + point.x + ' offset.y: ' + point.y); this.offsetX = point.x; this.offsetY = point.y; this.LocationAndHeight(this.getX(), this.getY(), this.getHeight()); } this.getX = function() { return this.offsetX + this.mX + mSceneX; } this.getY = function() { return this.offsetY + -this.mRow*ROW_HEIGHT; } this.getHeight = function() { return BOX_HEIGHT; } var topLinkPoint; var bottomLinkPoint; var mClickListeners = new Array(); this.addClickListener = function(listener) { mClickListeners.push(listener); } function streamSelected(mouseEvt) { for(var i = 0; i < mClickListeners.length; i++) mClickListeners[i].nodeClicked(stream, mouseEvt.x, mouseEvt.y, mouseEvt.shiftKey); } this.backgroundCanvas = document.createElement( "canvas" ); this.backgroundCanvas.style.position = "absolute"; document.getElementById( "streamGraph" ).appendChild( this.backgroundCanvas ); $(this.backgroundCanvas).hide(); this.shapeCanvas = document.createElement( "canvas" ); this.shapeCanvas.style.position = "absolute"; document.getElementById( "streamGraph" ).appendChild( this.shapeCanvas ); //this.shapeCanvas.style.background = 'red'; this.shapeCanvas.onclick = function(evt) { streamSelected(evt); } var nameCanvas=document.createElement( "canvas" ); nameCanvas.style.position = "absolute"; document.getElementById( "streamGraph" ).appendChild( nameCanvas ); nameCanvas.onclick = function(evt) { streamSelected(evt); } var mergeCanvas=document.createElement( "canvas" ); var mergeContext = mergeCanvas.getContext( '2d' ); mergeCanvas.onclick = function(evt) { streamSelected(evt); } var promoteCanvas=document.createElement( "canvas" ); var promoteContext = promoteCanvas.getContext( '2d' ); promoteCanvas.onclick = function(evt) { streamSelected(evt); } this.upperChildCanvas=document.createElement( "canvas" ); var upperChildContext = this.upperChildCanvas.getContext( '2d' ); var lowerChildCanvas=document.createElement( "canvas" ); var lowerChildContext = lowerChildCanvas.getContext( '2d' ); this.workspaceNodes = function(workspaces) { nodeList.nodes(workspaces); } this.setMenu = function(menuId, menuHandler) { setContextMenu(this.shapeCanvas, menuId, menuHandler); setContextMenu(nameCanvas, menuId, menuHandler); setContextMenu(mergeCanvas, menuId, menuHandler); setContextMenu(promoteCanvas, menuId, menuHandler); setContextMenu(this.upperChildCanvas, menuId, menuHandler); setContextMenu(lowerChildCanvas, menuId, menuHandler); } setContextMenu = function(element, menuId, menuHandler) { $(element).contextMenu( { menu: menuId }, menuHandler ); } this.ShowSendMergeBadge = function() { if( !this.stream ) return false; if(this.stream.mType == mStreamUtilities.StreamType.release) return this.stream.mChangeFlowsToParent; return false; } this.ShowReceiveMergeBadge = function() { if( !this.stream ) return false; if(this.stream.mType == mStreamUtilities.StreamType.development) return this.stream.mChangeFlowsFromParent; return false; } this.ShowSendPromotionBadge = function() { if( !this.stream ) return false; if(this.stream.mType == mStreamUtilities.StreamType.development) return this.stream.mChangeFlowsToParent; return false; } this.ShowReceivePromotionBadge = function() { if( !this.stream ) return false; if(this.stream.mType == mStreamUtilities.StreamType.release) return this.stream.mChangeFlowsFromParent; return false; } this.getBadgePoint = function() { var nudge = 8 return new mStreamUtilities.Point( this.getX() + this.mWidth/2 + nudge, this.getY() + this.shapeCanvas.height/1.5 + nudge); } this.CreateChildAttachNodes = function() { //this.upperChildCanvas.style.background = 'green'; //lowerChildCanvas.style.background = 'red'; //strokeWidth = outlineWidth * scaleFactor; var diameter = this.shapeCanvas.width/6; if (this.isFirmType()) { this.upperChildCanvas.style.position = "absolute"; document.getElementById( "streamGraph" ).appendChild( this.upperChildCanvas ); this.upperChildCanvas.onclick = function(evt) { streamSelected(evt); } this.upperChildCanvas.style.top = this.shapeCanvas.offsetTop - diameter/4; this.upperChildCanvas.width = diameter; this.upperChildCanvas.height = diameter; this.upperChildCanvas.style.left = this.shapeCanvas.offsetLeft + this.shapeCanvas.width/2 - diameter/2; upperChildContext.beginPath(); upperChildContext.arc(this.upperChildCanvas.width/2, this.upperChildCanvas.width/2, this.upperChildCanvas.width/2 - strokeWidth, 0, Math.PI*2, true); upperChildContext.closePath(); //topLinkPoint = new mStreamUtilities.Vector( this.upperChildCanvas.offsetLeft + diameter / 2, this.upperChildCanvas.offsetTop + strokeWidth); bottomLinkPoint = new mStreamUtilities.Vector( this.shapeCanvas.offsetLeft + this.shapeCanvas.width / 2, this.upperChildCanvas.offsetTop + this.shapeCanvas.height); } else if(this.isMainline()) { this.upperChildCanvas.style.position = "absolute"; document.getElementById( "streamGraph" ).appendChild( this.upperChildCanvas ); this.upperChildCanvas.onclick = function(evt) { streamSelected(evt); } this.upperChildCanvas.style.top = this.shapeCanvas.offsetTop - diameter/4; this.upperChildCanvas.width = diameter; this.upperChildCanvas.height = diameter; this.upperChildCanvas.style.left = this.shapeCanvas.offsetLeft + this.shapeCanvas.width/2 - diameter/2; upperChildContext.beginPath(); upperChildContext.arc(this.upperChildCanvas.width/2, this.upperChildCanvas.width/2, this.upperChildCanvas.width/2 - strokeWidth, 0, Math.PI*2, true); upperChildContext.closePath(); lowerChildCanvas.style.position = "absolute"; document.getElementById( "streamGraph" ).appendChild( lowerChildCanvas ); lowerChildCanvas.onclick = function(evt) { streamSelected(evt); } lowerChildCanvas.style.top = this.shapeCanvas.offsetTop + this.shapeCanvas.height - diameter*1.3; lowerChildCanvas.width = diameter; lowerChildCanvas.height = diameter; lowerChildCanvas.style.left = this.shapeCanvas.offsetLeft + this.shapeCanvas.width/2 - diameter/2; lowerChildContext.beginPath(); lowerChildContext.arc(lowerChildCanvas.width/2, lowerChildCanvas.width/2, lowerChildCanvas.width/2 - strokeWidth, 0, Math.PI*2, true); lowerChildContext.closePath(); topLinkPoint = new mStreamUtilities.Vector( this.upperChildCanvas.offsetLeft + diameter / 2, this.upperChildCanvas.offsetTop + this.upperChildCanvas.height / 2); bottomLinkPoint = new mStreamUtilities.Vector( lowerChildCanvas.offsetLeft + lowerChildCanvas.width / 2, lowerChildCanvas.offsetTop + lowerChildCanvas.height / 2); } else { lowerChildCanvas.style.position = "absolute"; document.getElementById( "streamGraph" ).appendChild( lowerChildCanvas ); lowerChildCanvas.onclick = function(evt) { streamSelected(evt); } lowerChildCanvas.style.top = this.shapeCanvas.offsetTop + this.shapeCanvas.height - diameter; lowerChildCanvas.width = diameter; lowerChildCanvas.height = diameter; lowerChildCanvas.style.left = this.shapeCanvas.offsetLeft + this.shapeCanvas.width/2 - diameter/2; lowerChildContext.beginPath(); lowerChildContext.arc(lowerChildCanvas.width/2, lowerChildCanvas.width/2, lowerChildCanvas.width/2 - strokeWidth, 0, Math.PI*2, true); lowerChildContext.closePath(); //topLinkPoint = new mStreamUtilities.Vector( this.shapeCanvas.offsetLeft + this.shapeCanvas.width / 2, this.upperChildCanvas.offsetTop); bottomLinkPoint = new mStreamUtilities.Vector( lowerChildCanvas.offsetLeft + lowerChildCanvas.width / 2, lowerChildCanvas.offsetTop + lowerChildCanvas.height / 2 + strokeWidth); //bottomLinkPoint = new mStreamUtilities.Vector( this.shapeCanvas.offsetLeft + this.shapeCanvas.width / 2, this.shapeCanvas.offsetTop + y + height); } } this.CreateStatusIcons = function() { //mergeCanvas.style.background = 'green'; //promoteCanvas.style.background = 'red'; if (showStateIcons && scaleFactor >= MINZOOMFACTOR) { //var strokeWidth = outlineWidth * scaleFactor; var nudge = strokeWidth; var iconWidth = this.shapeCanvas.width *.18; var iconHeight = this.shapeCanvas.height * .28; if (this.isFirmType()) { var stagingNudge = 0; if(true/*stream.type == stream.STAGING*/) stagingNudge = nameCanvas.height/6; if (this.ShowSendMergeBadge() && this.ShowReceivePromotionBadge()) { mergeCanvas.style.position = "absolute"; document.getElementById( "streamGraph" ).appendChild( mergeCanvas ); promoteCanvas.style.position = "absolute"; document.getElementById( "streamGraph" ).appendChild( promoteCanvas ); var rightX = this.shapeCanvas.offsetLeft + this.shapeCanvas.width / 2 + strokeWidth; var leftX = (rightX - iconWidth) - nudge; mergeCanvas.style.top = nameCanvas.offsetTop + nameCanvas.height + stagingNudge; mergeCanvas.style.left = leftX mergeCanvas.width = iconWidth; mergeCanvas.height = iconHeight; this.CreateStatusShape(new mStreamUtilities.Rectangle(0, 0, mergeCanvas.width, mergeCanvas.height), false, mergeContext); promoteCanvas.style.top = nameCanvas.offsetTop + nameCanvas.height + stagingNudge; promoteCanvas.style.left = rightX; promoteCanvas.width = iconWidth; promoteCanvas.height = iconHeight; this.CreateStatusShape(new mStreamUtilities.Rectangle(0, 0, promoteCanvas.width, promoteCanvas.height), true, promoteContext); } else if( this.ShowSendMergeBadge() ) { mergeCanvas.style.position = "absolute"; document.getElementById( "streamGraph" ).appendChild( mergeCanvas ); var x = this.shapeCanvas.offsetLeft + this.shapeCanvas.width / 2 - iconWidth / 2; mergeCanvas.style.top = nameCanvas.offsetTop + nameCanvas.height + stagingNudge; mergeCanvas.style.left = x mergeCanvas.width = iconWidth; mergeCanvas.height = iconHeight; this.CreateStatusShape(new mStreamUtilities.Rectangle(0, 0, mergeCanvas.width, mergeCanvas.height), false, mergeContext); } else if( this.ShowReceivePromotionBadge() ) { promoteCanvas.style.position = "absolute"; document.getElementById( "streamGraph" ).appendChild( promoteCanvas ); var x = this.shapeCanvas.offsetLeft + this.shapeCanvas.width / 2 - iconWidth / 2; promoteCanvas.style.top = nameCanvas.offsetTop + nameCanvas.height; promoteCanvas.style.left = x promoteCanvas.width = iconWidth; promoteCanvas.height = iconHeight; this.CreateStatusShape(new mStreamUtilities.Rectangle(0, 0, promoteCanvas.width, promoteCanvas.height), true, promoteContext); } } else//draw arrows for soft streams { if( !this.isMainline() ) { if ( this.ShowReceiveMergeBadge() && this.ShowSendPromotionBadge()) { mergeCanvas.style.position = "absolute"; document.getElementById( "streamGraph" ).appendChild( mergeCanvas ); promoteCanvas.style.position = "absolute"; document.getElementById( "streamGraph" ).appendChild( promoteCanvas ); var rightX = this.shapeCanvas.offsetLeft + this.shapeCanvas.width / 2 + strokeWidth; var leftX = (rightX - iconWidth) - nudge; mergeCanvas.style.top = nameCanvas.offsetTop - iconHeight/1.2; mergeCanvas.style.left = leftX; mergeCanvas.width = iconWidth; mergeCanvas.height = iconHeight; this.CreateStatusShape(new mStreamUtilities.Rectangle(0, 0, mergeCanvas.width, mergeCanvas.height), false, mergeContext); promoteCanvas.style.top = nameCanvas.offsetTop - iconHeight/1.2; promoteCanvas.style.left = rightX; promoteCanvas.width = iconWidth; promoteCanvas.height = iconHeight; this.CreateStatusShape(new mStreamUtilities.Rectangle(0, 0, promoteCanvas.width, promoteCanvas.height), true, promoteContext); } else if( this.ShowReceiveMergeBadge() ) { mergeCanvas.style.position = "absolute"; document.getElementById( "streamGraph" ).appendChild( mergeCanvas ); var middleX = this.shapeCanvas.offsetLeft + this.shapeCanvas.width / 2 - iconWidth/2; mergeCanvas.style.top = nameCanvas.offsetTop - iconHeight; mergeCanvas.style.left = middleX; mergeCanvas.width = iconWidth; mergeCanvas.height = iconHeight; this.CreateStatusShape(new mStreamUtilities.Rectangle(0, 0, mergeCanvas.width, mergeCanvas.height), false, mergeContext); } else if ( this.ShowSendPromotionBadge() ) { promoteCanvas.style.position = "absolute"; document.getElementById( "streamGraph" ).appendChild( promoteCanvas ); var middleX = this.shapeCanvas.offsetLeft + this.shapeCanvas.width / 2 - iconWidth/2; promoteCanvas.style.top = nameCanvas.offsetTop - iconHeight; promoteCanvas.style.left = middleX; promoteCanvas.width = iconWidth; promoteCanvas.height = iconHeight; this.CreateStatusShape(new mStreamUtilities.Rectangle(0, 0, promoteCanvas.width, promoteCanvas.height), true, promoteContext); } } } } } /*this.Width = function() { return (nameCanvas.offsetLeft + nameCanvas.width) - this.shapeCanvas.offsetLeft; }*/ this.LocationAndHeight = function(x, y, height) { this.backgroundCanvas.style.left = x ; this.backgroundCanvas.style.top = y ; this.backgroundCanvas.width = height; this.backgroundCanvas.height = height; this.shapeCanvas.style.left = x ; this.shapeCanvas.style.top = y ; this.shapeCanvas.width = height; this.shapeCanvas.height = height; shadowStroke = this.shapeCanvas.height/10; strokeWidth = (shadowStroke / 8) * scaleFactor; //now that we know the height and width of the background, we can place the name canvas var nameLeft = x + this.shapeCanvas.width * 0.25; nameCanvas.style.left = nameLeft; var nameCanvasTop; if( this.isFirmType() ) nameCanvasTop = y + this.shapeCanvas.height * 0.1;//put the name at the top else if( this.isMainline() ) nameCanvasTop = y + this.shapeCanvas.height * .28;//in the middle else nameCanvasTop = y + this.shapeCanvas.height * 0.48;//at the bottom nameCanvas.style.top = nameCanvasTop; nameCanvas.height = this.shapeCanvas.height * 0.36; //set the font size of the canvas so we can get an accurate width var nameFont = "normal " + nameCanvas.height * 0.5 + "px sans-serif"; nameContext.font = nameFont; if( this.stream ) nameCanvas.width = mStreamUtilities.GetTextWidth(this.stream.mName, nameContext, nameFont) + shadowStroke*2; //if the name canvas isn't at least as wide as the background canvas, make it so (plus a little, for aesthetics) if( nameCanvas.offsetLeft + nameCanvas.width < this.shapeCanvas.offsetLeft + this.shapeCanvas.width ) nameCanvas.width = ((this.shapeCanvas.offsetLeft + this.shapeCanvas.width) - nameCanvas.offsetLeft) * 1.2; this.CreateShapes(); nodeList.position(x + this.shapeCanvas.width, nameCanvasTop + nameCanvas.height); } var backgroundContext = this.backgroundCanvas.getContext( '2d' ); var shapeContext = this.shapeCanvas.getContext( '2d' ); var nameContext = nameCanvas.getContext( '2d' ); this.GetColor = function() { //'rgb(30, 144, 255)' is dodgerblue var r = 120; var g = 180; var b = 255; var nodeColor = new mStreamUtilities.StreamColor(200, 200, 200, 1); //the multiplier keeps the color from hitting pure white var multiplier = 0.9; if (this.stream != null) { //if( !mStreamUtilities.isNonConformingRelationship(this.mParent, this) ) { r = Math.floor(r + (255 - r) * this.firmness * multiplier); g = Math.floor(g + (255 - g) * this.firmness * multiplier); b = Math.floor(b + (255 - b) * this.firmness * multiplier); var sColor = new mStreamUtilities.StreamColor(r,g,b,1); nodeColor = sColor; } } return nodeColor; } this.CreateStreamShape = function(rectangle, context) { //GraphicsPath path = new GraphicsPath(); var l = rectangle.x; var t = rectangle.y; var w = rectangle.width; var h = rectangle.height; var d = h / 4; if (this.stream != null) { if (this.stream.mType == mStreamUtilities.StreamType.release || this.stream.mType == mStreamUtilities.StreamType.experimental) { //draw a pointing downwards rounded triangle //this triangle is based upon three circles. Tangent lines are drawn on the outside //between each circle, then the tangents are connected by quadratic curves. Sounds fancy! var circleAOrigin = new mStreamUtilities.Vector(l + d, t + d); var circleBOrigin = new mStreamUtilities.Vector(l + (w - d), t + d); var circleCOrigin = new mStreamUtilities.Vector(l + w/2, t + (h - d)); //grab the normalized perpendicular vector, and use it to determine a parallel vector //to the vector between two circle origins var topNormal = mStreamUtilities.GetNormalizedPerpendicularVector( circleAOrigin, circleBOrigin); topNormal.X *= d; topNormal.Y *= d; var tanATop = new mStreamUtilities.Vector(circleAOrigin.X + topNormal.X, circleAOrigin.Y + topNormal.Y); var tanBTop = new mStreamUtilities.Vector(circleBOrigin.X + topNormal.X, circleBOrigin.Y + topNormal.Y); var rightNormal = mStreamUtilities.GetNormalizedPerpendicularVector( circleBOrigin, circleCOrigin); rightNormal.X *= d; rightNormal.Y *= d; var tanBRight = new mStreamUtilities.Vector(circleBOrigin.X + rightNormal.X, circleBOrigin.Y + rightNormal.Y); var tanCRight = new mStreamUtilities.Vector(circleCOrigin.X + rightNormal.X, circleCOrigin.Y + rightNormal.Y); var leftNormal = mStreamUtilities.GetNormalizedPerpendicularVector( circleAOrigin, circleCOrigin); leftNormal.X *= d; leftNormal.Y *= d; var tanALeft = new mStreamUtilities.Vector(circleAOrigin.X - leftNormal.X, circleAOrigin.Y - leftNormal.Y); var tanCLeft = new mStreamUtilities.Vector(circleCOrigin.X - leftNormal.X, circleCOrigin.Y - leftNormal.Y); //top line context.lineTo(tanATop.X, tanATop.Y); context.lineTo(tanBTop.X, tanBTop.Y); //right line context.quadraticCurveTo(l + w + d/2, t, tanBRight.X, tanBRight.Y); context.lineTo(tanCRight.X, tanCRight.Y); //bottom curve context.quadraticCurveTo( l + w/2, t + h + d, tanCLeft.X, tanCLeft.Y); //left line context.lineTo(tanALeft.X, tanALeft.Y); context.quadraticCurveTo(l - d/2, t, tanATop.X, tanATop.Y); context.closePath(); } else if (this.isMainline()) { //draw a rounded rectangle context.beginPath(); context.lineTo(l,t+h-d);//start at lower left corner - the radius context.quadraticCurveTo(l,t+h,l+d,t+h);//curve around to bottom context.lineTo(l+w-d,t+h);//draw bottom line context.quadraticCurveTo(l+w,t+h,l+w,t+h-d);//curve around to right side context.lineTo(l+w,t+d);//draw right side context.quadraticCurveTo(l+w,t,l+w-d,t);//curve around to top context.lineTo(l+d,t);//draw top context.quadraticCurveTo(l,t,l,t+d);//curve around to left context.closePath();//draws the left side } else { //draw a pointing upwards rounded triangle //this triangle is based upon three circles. Tangent lines are drawn on the outside //between each circle, then the tangents are connected by quadratic curves. t += d/4;//move the whole thing down a little var circleTopOrigin = new mStreamUtilities.Vector(l + w/2, t + d); var circleRightOrigin = new mStreamUtilities.Vector(l + (w - d), t + (h - d)); var circleLeftOrigin = new mStreamUtilities.Vector(l + d, t + (h - d)); //grab the normalized perpendicular vector, and use it to determine a parallel vector //to the vector between two circle origins var rightNormal = mStreamUtilities.GetNormalizedPerpendicularVector( circleTopOrigin, circleRightOrigin); rightNormal.X *= d; rightNormal.Y *= d; var tanARight = new mStreamUtilities.Vector(circleTopOrigin.X + rightNormal.X, circleTopOrigin.Y + rightNormal.Y); var tanBRight = new mStreamUtilities.Vector(circleRightOrigin.X + rightNormal.X, circleRightOrigin.Y + rightNormal.Y); var bottomNormal = mStreamUtilities.GetNormalizedPerpendicularVector( circleRightOrigin, circleLeftOrigin); bottomNormal.X *= d; bottomNormal.Y *= d; var tanBBottom = new mStreamUtilities.Vector(circleRightOrigin.X + bottomNormal.X, circleRightOrigin.Y + bottomNormal.Y); var tanCBottom = new mStreamUtilities.Vector(circleLeftOrigin.X + bottomNormal.X, circleLeftOrigin.Y + bottomNormal.Y); var leftNormal = mStreamUtilities.GetNormalizedPerpendicularVector( circleLeftOrigin, circleTopOrigin); leftNormal.X *= d; leftNormal.Y *= d; var tanALeft = new mStreamUtilities.Vector(circleTopOrigin.X + leftNormal.X, circleTopOrigin.Y + leftNormal.Y); var tanCLeft = new mStreamUtilities.Vector(circleLeftOrigin.X + leftNormal.X, circleLeftOrigin.Y + leftNormal.Y); //right line context.lineTo(tanARight.X, tanARight.Y); context.lineTo(tanBRight.X, tanBRight.Y); //bottom line context.quadraticCurveTo(l + w + d/2, t + h, tanBBottom.X, tanBBottom.Y); context.lineTo(tanCBottom.X, tanCBottom.Y); //left curve context.quadraticCurveTo( l - d/2, t + h, tanCLeft.X, tanCLeft.Y); //left line context.lineTo(tanCLeft.X, tanCLeft.Y); context.lineTo(tanALeft.X, tanALeft.Y); context.quadraticCurveTo(l + w/2, t - d, tanARight.X, tanARight.Y); context.closePath(); } } } this.CreateNameShape = function(rectangle, context) { var x = rectangle.x; var y = rectangle.y; var width = rectangle.width; var height = rectangle.height; var radius = height/2; context.beginPath(); context.lineTo(x,y+height-radius);//start at lower left corner - the radius context.quadraticCurveTo(x,y+height,x+radius,y+height);//curve around to bottom context.lineTo(x+width-radius,y+height);//draw bottom line context.quadraticCurveTo(x+width,y+height,x+width,y+height-radius);//curve around to right side context.lineTo(x+width,y+radius);//draw right side context.quadraticCurveTo(x+width,y,x+width-radius,y);//curve around to top context.lineTo(x+radius,y);//draw top context.quadraticCurveTo(x,y,x,y+radius);//curve around to left context.closePath();//draws the left side } this.CreateStatusShape = function(rectangle, pointingUp, context) { var l = rectangle.x; var t = rectangle.y; var w = rectangle.width; var h = rectangle.height; var arrowHeight = rectangle.height / 4; var halfArrow = arrowHeight/2; var halfWidth = rectangle.width / 2; if (pointingUp) { //chevron context.beginPath(); context.lineTo(l, t + arrowHeight); context.lineTo(l + halfWidth, t); context.lineTo(l + w, t + arrowHeight); context.lineTo(l + w, t + arrowHeight + arrowHeight); context.lineTo(l + halfWidth, t + arrowHeight); context.lineTo(l, t + arrowHeight + arrowHeight); context.lineTo(l, t + arrowHeight); context.lineTo(l, t + arrowHeight); context.moveTo(l, t + halfArrow + arrowHeight + arrowHeight); context.lineTo(l + halfWidth, t + halfArrow + arrowHeight); context.lineTo(l + w, t + halfArrow + arrowHeight + arrowHeight ); context.lineTo(l + w, t + halfArrow + arrowHeight + arrowHeight + arrowHeight); context.lineTo(l + halfWidth, t + halfArrow + arrowHeight + arrowHeight); context.lineTo(l, t + halfArrow + arrowHeight + arrowHeight + arrowHeight); context.lineTo(l, t + halfArrow + arrowHeight + arrowHeight); context.closePath(); } else { //arrow pointing downward... //chevron context.beginPath(); context.lineTo(l, t); context.lineTo(l + halfWidth, t + arrowHeight); context.lineTo(l + w, t); context.lineTo(l + w, t + arrowHeight); context.lineTo(l + halfWidth, t + arrowHeight + arrowHeight); context.lineTo(l, t + arrowHeight); context.lineTo(l, t); context.moveTo(l, t + halfArrow + arrowHeight); context.lineTo(l + halfWidth, t + halfArrow + arrowHeight + arrowHeight); context.lineTo(l + w, t + halfArrow + arrowHeight); context.lineTo(l + w, t + halfArrow + arrowHeight + arrowHeight); context.lineTo(l + halfWidth, t + halfArrow + arrowHeight + arrowHeight + arrowHeight); context.lineTo(l, t + halfArrow + arrowHeight + arrowHeight); context.lineTo(l, t + halfArrow + arrowHeight); context.closePath(); } } this.CreateShapes = function() { mStreamUtilities.clearCanvas(this.backgroundCanvas); mStreamUtilities.clearCanvas(this.shapeCanvas); mStreamUtilities.clearCanvas(nameCanvas); var x = shadowStroke/2; var y = shadowStroke/2; var width = this.shapeCanvas.width - shadowStroke; var height= this.shapeCanvas.height - shadowStroke * 2; topLinkPoint = new mStreamUtilities.Vector( this.shapeCanvas.offsetLeft + this.shapeCanvas.width / 2, this.shapeCanvas.offsetTop + shadowStroke); bottomLinkPoint = new mStreamUtilities.Vector( this.shapeCanvas.offsetLeft + this.shapeCanvas.width / 2, this.shapeCanvas.offsetTop + y + height); //create the shapes, but don't fill it 'til later... this.CreateStreamShape(new mStreamUtilities.Rectangle(x, y, width, height), backgroundContext); this.CreateStreamShape(new mStreamUtilities.Rectangle(x, y, width, height), shapeContext); this.CreateNameShape(new mStreamUtilities.Rectangle(x, y, nameCanvas.width - shadowStroke, nameCanvas.height - shadowStroke), nameContext); this.CreateStatusIcons(); this.CreateChildAttachNodes(); } this.selectionColor = new mStreamUtilities.StreamColor(30, 144, 255, 1);// dodgerblue this.highlightColor = new mStreamUtilities.StreamColor(176, 196, 222, 1);//'lightsteelblue'; this.getOutlineColor = function() { var outlineColor = this.selectionColor; if(this.highlighted && !this.selected) outlineColor = this.highlightColor; //if( mStreamUtilities.isNonConformingRelationship(this.mParent, this) ) // outlineColor = new mStreamUtilities.StreamColor(128,128,128,1);//'gray'; return outlineColor; } this.draw = function(showStatus) { var color = this.GetColor(); mStreamUtilities.clearCanvas(this.backgroundCanvas); mStreamUtilities.clearCanvas(this.shapeCanvas); var outlineColor = this.getOutlineColor(); var nonConforming = mStreamUtilities.isNonConformingRelationship(this.mParent, this); //this.DrawShadow(backgroundContext, shadowStroke); this.DrawShadow(shapeContext, shadowStroke); this.DrawChildAttachNodes(outlineColor, strokeWidth); backgroundContext.fillStyle = this.GetGradientFill(new mStreamUtilities.StreamColor(200, 200, 200, .1), backgroundContext, this.backgroundCanvas, false, nonConforming); backgroundContext.fill(); shapeContext.fillStyle = this.GetGradientFill(color, shapeContext, this.shapeCanvas, false, nonConforming); shapeContext.fill(); this.drawName(); shapeContext.strokeStyle = outlineColor.rgba; shapeContext.lineWidth = strokeWidth; if( this.selected || this.highlighted ) shapeContext.lineWidth = strokeWidth*4; shapeContext.stroke(); //console.log('drawing: ' + this.stream.mName + " x: " + this.shapeCanvas.offsetLeft + " y: " + this.shapeCanvas.offsetTop + " sceneX: " + mSceneX); if(showStatus) this.DrawStatusIcons(); if(nodeList) nodeList.draw(); } this.getNameY = function() { return nameCanvas.offsetTop; } this.getNameHeight = function() { return nameCanvas.height; } this.drawName = function() { this.CreateNameShape(new mStreamUtilities.Rectangle(shadowStroke/2, shadowStroke/2, nameCanvas.width - shadowStroke, nameCanvas.height - shadowStroke), nameContext); //create a gradient brush to fill the name background var aliceBlue = new mStreamUtilities.StreamColor(240, 248, 255, 1); var nameLingrad = this.GetGradientFill(aliceBlue, nameContext, nameCanvas, true); nameContext.save(); nameContext.fillStyle = nameLingrad; nameContext.fill(); nameContext.restore(); //not sure why I have to re-set the font size here... nameContext.font = "normal " + nameCanvas.height * 0.5 + "px sans-serif"; nameContext.textBaseline = "middle"; nameContext.fillText(this.stream.mName, shadowStroke, nameCanvas.height/2 - nameCanvas.height * 0.02); nameContext.strokeStyle = this.getOutlineColor().rgba; nameContext.stroke(); } var expandNameWidth = 0; var expandNameLeft = 0; var initialNameSize = true; this.expandNameCanvas = function(width) { if( initialNameSize ) { expandNameWidth = nameCanvas.width; initialNameSize = false; } expandNameLeft += nameCanvas.offsetLeft; $(nameCanvas).animate({ left: '-=' + nameCanvas.offsetLeft + 'px', //width: '+=' + width }, 500 ); nameCanvas.width = width; this.drawName(); } this.reduceNameCanvas = function() { $(nameCanvas).animate({ left: '+=' + expandNameLeft + 'px', }, 500 ); expandNameLeft = 0; nameCanvas.width = expandNameWidth; initialNameSize = true; this.drawName(); } this.DrawChildAttachNodes = function(outlineColor, strokeWidth) { if (this.isFirmType()) { upperChildContext.fillStyle = 'white'; upperChildContext.fill(); upperChildContext.strokeStyle = outlineColor.rgba; upperChildContext.lineWidth = strokeWidth; upperChildContext.stroke(); } else if(this.isMainline()) { upperChildContext.fillStyle = 'white'; upperChildContext.fill(); upperChildContext.strokeStyle = outlineColor.rgba; upperChildContext.lineWidth = strokeWidth; upperChildContext.stroke(); lowerChildContext.fillStyle = 'white'; lowerChildContext.fill(); lowerChildContext.strokeStyle = outlineColor.rgba; lowerChildContext.lineWidth = strokeWidth; lowerChildContext.stroke(); } else { lowerChildContext.fillStyle = 'white'; lowerChildContext.fill(); lowerChildContext.strokeStyle = outlineColor.rgba; lowerChildContext.lineWidth = strokeWidth; lowerChildContext.stroke(); } } this.getMergeNeededColor = function() { return 'rgba(255, 215, 0, 1)'; } this.DrawStatusIcons = function() { var cornflowerBlue = new mStreamUtilities.StreamColor(100, 149, 237, 1); var orangeRed = new mStreamUtilities.StreamColor(255, 69, 0, 1); var gold = new mStreamUtilities.StreamColor(255, 215, 0, 1); var darkGoldenrod = new mStreamUtilities.StreamColor(218,165,32,1); var cadetBlue = new mStreamUtilities.StreamColor(95,158,160,1); var mergeColor = cornflowerBlue; var mergeStroke = cadetBlue; if(this.ShowMergeAvailable()) { mergeColor = gold; mergeStroke = darkGoldenrod; } mergeContext.fillStyle = this.GetGradientFill(mergeColor, mergeContext, mergeCanvas, false); mergeContext.fill(); mergeContext.strokeStyle = mergeStroke.rgba; mergeContext.stroke(); var promoteColor = cornflowerBlue; var promoteStroke = cadetBlue; if(this.ShowPromotionsAvailable() ) { if( this.ShowMergeAvailable() ) { promoteColor = orangeRed; promoteStroke = orangeRed; } else { promoteColor = gold; promoteStroke = darkGoldenrod; } } promoteContext.fillStyle = this.GetGradientFill(promoteColor, promoteContext, promoteCanvas, false); promoteContext.fill(); promoteContext.strokeStyle = promoteStroke.rgba; promoteContext.stroke(); } this.GetGradientFill = function(color, context, canvas, whiteHighlight, stripes) { var lightColor = 'white'; var linearGradient = context.createLinearGradient(0,0,0,canvas.height); //console.log(this.firmness); if (!whiteHighlight) { var r = Math.floor(color.r + (255 - color.r)/1.2); var g = Math.floor(color.g + (255 - color.g)/1.2); var b = Math.floor(color.b + (255 - color.b)/1.2); lightColor = 'rgb(' + r + ',' + g + ',' + b + ')'; } //console.log('GetGradientFill(color, context, canvas, whiteHighlight) color: ' + color + ' this.highlightColor: ' + this.highlightColor ); /*if(stripes) { linearGradient = context.createLinearGradient(0,0,canvas.width,canvas.height/1.6); linearGradient.addColorStop(0, lightColor); linearGradient.addColorStop(.1, lightColor); linearGradient.addColorStop(.1, color.rgba); linearGradient.addColorStop(0.2, color.rgba); linearGradient.addColorStop(.2, lightColor); linearGradient.addColorStop(.3, lightColor); linearGradient.addColorStop(.3, color.rgba); linearGradient.addColorStop(0.4, color.rgba); linearGradient.addColorStop(0.4, lightColor); linearGradient.addColorStop(.5, lightColor); linearGradient.addColorStop(.5, color.rgba); linearGradient.addColorStop(0.6, color.rgba); linearGradient.addColorStop(.6, lightColor); linearGradient.addColorStop(.7, lightColor); linearGradient.addColorStop(.7, color.rgba); linearGradient.addColorStop(0.8, color.rgba); linearGradient.addColorStop(.8, lightColor); linearGradient.addColorStop(.9, lightColor); linearGradient.addColorStop(.9, color.rgba); linearGradient.addColorStop(1, color.rgba); } else*/ { try { linearGradient.addColorStop(0, lightColor); linearGradient.addColorStop(0.3, color.rgba); linearGradient.addColorStop(0.6, color.rgba); linearGradient.addColorStop(1, lightColor); }catch(e) { console.log(e); } } return linearGradient; } this.DrawShadow = function( context, width ) { context.save(); var hue = 230; context.lineJoin = 'round'; context.strokeStyle = 'rgba(' + hue + ',' + hue + ',' + hue + ',0.16)'; for(var i = 0; i < width; ++i) { context.lineWidth = i; context.stroke(); } context.restore(); } this.integToParent = false; this.integFromParent = false; this.ShowMergeAvailable = function() { if(this.isFirmType()) { return this.integToParent; } else if(!this.isMainline()) { return this.integFromParent; } return false; } this.ShowPromotionsAvailable = function() { if(this.isFirmType()) { return this.integFromParent; } else if(!this.isMainline()) { return this.integToParent; } return false; } this.GetTopLinkPoint = function() { return topLinkPoint; } this.GetBottomLinkPoint = function() { return bottomLinkPoint; } this.firstLinkCanvas; this.secondLinkCanvas; this.firstLinkCanvas = document.createElement( "canvas" ); this.firstLinkCanvas.style.position = "absolute"; this.firstLinkCanvas.style.zIndex = -10; document.getElementById( "streamGraph" ).appendChild( this.firstLinkCanvas ); this.secondLinkCanvas = document.createElement( "canvas" ); this.secondLinkCanvas.style.position = "absolute"; this.secondLinkCanvas.style.zIndex = -10; document.getElementById( "streamGraph" ).appendChild( this.secondLinkCanvas ); var alignNudge = 0; var animAlignTime = 0; this.alignWithOtherNode = function(otherNode, animTime) { animAlignTime = animTime; $(this.backgroundCanvas).hide(); alignNudge = this.mX - otherNode.mX; moveElementHorizontally(this.shapeCanvas, alignNudge, animAlignTime); moveElementHorizontally(nameCanvas, alignNudge, animAlignTime); moveElementHorizontally(mergeCanvas, alignNudge, animAlignTime); moveElementHorizontally(promoteCanvas, alignNudge, animAlignTime); moveElementHorizontally(this.upperChildCanvas, alignNudge, animAlignTime); moveElementHorizontally(lowerChildCanvas, alignNudge, animAlignTime); $(this.backgroundCanvas).show('fade', animTime); this.mX -= alignNudge; } this.returnToOriginalPosition = function() { moveElementHorizontally(this.shapeCanvas, -alignNudge, animAlignTime); moveElementHorizontally(nameCanvas, -alignNudge, animAlignTime); moveElementHorizontally(mergeCanvas, -alignNudge, animAlignTime); moveElementHorizontally(promoteCanvas, -alignNudge, animAlignTime); moveElementHorizontally(this.upperChildCanvas, -alignNudge, animAlignTime); moveElementHorizontally(lowerChildCanvas, -alignNudge, animAlignTime); this.mX += alignNudge; } function moveElementHorizontally(element, units, animTime) { $(element).animate({ left: '-=' + units + 'px', }, animTime ); } var mZMoveIncrement = 0; this.moveForward = function(moveForwardIncrement) { mZMoveIncrement = moveForwardIncrement; this.shapeCanvas.style.zIndex += mZMoveIncrement; nameCanvas.style.zIndex += mZMoveIncrement; mergeCanvas.style.zIndex += mZMoveIncrement; promoteCanvas.style.zIndex += mZMoveIncrement; this.upperChildCanvas.style.zIndex += mZMoveIncrement; lowerChildCanvas.style.zIndex += mZMoveIncrement; } this.moveBack = function() { this.shapeCanvas.style.zIndex -= mZMoveIncrement; nameCanvas.style.zIndex -= mZMoveIncrement; mergeCanvas.style.zIndex -= mZMoveIncrement; promoteCanvas.style.zIndex -= mZMoveIncrement; this.upperChildCanvas.style.zIndex -= mZMoveIncrement; lowerChildCanvas.style.zIndex -= mZMoveIncrement; } this.toString = function() { if( this.stream ) return this.stream.mStream + "StreamNode"; return "undefined stream in StreamNode"; } this.clearLinks = function() { mStreamUtilities.clearCanvas(this.firstLinkCanvas); mStreamUtilities.clearCanvas(this.secondLinkCanvas); } this.linkType = function() { return mStreamUtilities.LinkType.Parallel; } return this; }
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#1 | 8214 | David George |
Re-implemented gumdrop skin in stream graph. Made selection work in P4V. |
||
//guest/david_george/StreamGraph/js/p4v/StreamNode.js | |||||
#1 | 8081 | David George |
Initial submit of JavaScript StreamGraph. Main functionality is: Change Trajectory (Change Flow), Timeline, and GitStreams. |