diff --git a/navigator/iTop/GraphNode.as b/navigator/iTop/GraphNode.as index 1cd8b21d9..04e6ab816 100644 --- a/navigator/iTop/GraphNode.as +++ b/navigator/iTop/GraphNode.as @@ -5,7 +5,8 @@ import flash.net.*; import flash.events.*; import flash.text.*; - import flash.xml.*; + import flash.xml.*; + import flash.filters.*; import flash.ui.ContextMenu; import flash.ui.ContextMenuItem; import iTop.ToolTip; @@ -17,11 +18,12 @@ private var m_oIcon:Loader; private var m_sClass:String; private var m_sClassName:String; - private var m_iId:Number; + private var m_iId:int; private var m_sParentKey:String; private var m_oToolTip:ToolTip; private var m_fZoom:Number; private var m_oParent:Navigator; + private var m_bRoot:Boolean; private static const ROUND:Number = 20; private static const PADDING:Number = 5; public var m_speed_x:Number = 0; @@ -36,6 +38,7 @@ m_sClass = sClass; m_sClassName = sClassName; m_iId = iId; + m_bRoot = false; m_sLabel.autoSize = TextFieldAutoSize.LEFT; m_sLabel.multiline = false; m_sLabel.text = sLabel; @@ -59,12 +62,16 @@ m_oToolTip.scaleY = 1 / m_fZoom; m_oParent = oParent; - var myURL:URLRequest = new URLRequest(sIconPath); - m_oIcon = new Loader(); - m_oIcon.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, onLoadError); - m_oIcon.contentLoaderInfo.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onLoadError); - m_oIcon.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoadComplete); - m_oIcon.load(myURL); + if (!m_oParent.DEBUG) + { + var myURL:URLRequest = new URLRequest(sIconPath); + m_oIcon = new Loader(); + m_oIcon.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, onLoadError); + m_oIcon.contentLoaderInfo.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onLoadError); + m_oIcon.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoadComplete); + m_oIcon.load(myURL); + } + //addChild(m_oIcon); addEventListener(MouseEvent.MOUSE_DOWN, mouseDown) addEventListener(MouseEvent.MOUSE_UP, mouseReleased); @@ -79,6 +86,22 @@ } + public function MarkAsRoot() + { + m_bRoot = true; + SetGlowFilter(); + } + + protected function SetGlowFilter() + { + var glow:GlowFilter = new GlowFilter(); + glow.color = 0x3333FF; + glow.blurX = 15; + glow.blurY = 15; + glow.inner = false; + filters = [glow]; + } + public function GetKey() { return m_sClass+'/'+m_iId; @@ -115,8 +138,19 @@ { trace("Click in Node"); m_oParent.m_bChildDragging = true; + m_oParent.m_bAutoPanAndZoom = false; + m_oParent.m_bHighFrictionMode = true; + m_oParent.m_bComputationNeeded = true; m_bInDrag = true; - m_oToolTip.timer.stop(); // Don't show the tooltip while dragging + var shadow:DropShadowFilter = new DropShadowFilter(); + shadow.distance = 5; + shadow.angle = 45; + shadow.alpha = 0.5; + filters = [shadow]; + if ((m_oToolTip) && (m_oToolTip.timer)) + { + m_oToolTip.timer.stop(); // Don't show the tooltip while dragging + } startDrag(); } @@ -125,6 +159,14 @@ m_bInDrag = false; stopDrag(); m_oParent.m_bChildDragging = false; + if (m_bRoot) + { + SetGlowFilter(); // Apply the glow filter + } + else + { + filters = []; // Remove the drop shadow + } } public function mouseOver( e:MouseEvent ):void diff --git a/navigator/iTop/Navigator.as b/navigator/iTop/Navigator.as index 1955d8e3a..b26929efb 100644 --- a/navigator/iTop/Navigator.as +++ b/navigator/iTop/Navigator.as @@ -14,6 +14,8 @@ // The main canvas public class Navigator extends MovieClip { + public var DEBUG:Boolean = false; + protected var CURRENT_VERSION:String = 'v. 2.0.6'; protected var m_oLoader:URLLoader; protected var m_aNodes:Object; protected var m_aLinks:Array; @@ -33,21 +35,29 @@ // Constants protected var m_RADIUS = 150; protected var m_Q = 0.9; // Electrostatic forces coeff - protected var m_K = 1.0; // Elastic forces coeff + protected var m_K = 1; // Elastic forces coeff protected var m_Kf = 0.7; // Fluid friction coeff protected var m_Ks = 30; // Solid friction coeff + protected var m_KsLowFriction = 10; // Solid friction coeff + protected var m_KsHighFriction = 30; // Solid friction coeff protected var m_deltaT = 0.1; // Interval of time between updates protected var m_MAX_ITEMS_PER_ROW = 8; - protected var m_FOCUS_DELAY_COUNTDOWN = 30; // 30 images to zoom & pan correctly + protected var m_FOCUS_DELAY_COUNTDOWN; // Number of images to zoom & pan correctly protected var m_fZoom:Number; + protected var m_iIterations:int = 0; + public var m_bComputationNeeded = true; + public var m_bAutoPanAndZoom = true; + public var m_bHighFrictionMode = false; + protected var m_iNbNodes:int = 0; // Constructor public function Navigator() { - m_aLinks = new Array(); - m_aNodes = new Array(); + m_aLinks = []; + m_aNodes = []; m_sExclude = ''; m_fZoom = 1; + m_Ks = m_KsLowFriction; m_oLoader = null; initParameters(); var success = true; @@ -83,15 +93,21 @@ function Reset() { - m_aLinks = new Array(); + m_aLinks = []; for (var i:String in m_aNodes) { m_oCanvas.removeChild(m_aNodes[i]); } - m_aNodes = new Array(); + m_aNodes = []; + m_bAutoPanAndZoom = true; + m_bHighFrictionMode = false; + m_iIterations = 0; + m_iNbNodes = 0; + m_bComputationNeeded = true; removeEventListener(Event.ENTER_FRAME, drawLines); stage.removeEventListener(MouseEvent.MOUSE_DOWN, mouseDown) stage.removeEventListener(MouseEvent.MOUSE_UP, mouseReleased); + stage.removeEventListener(MouseEvent.MOUSE_WHEEL, mouseWheelHandler); } function DoFilter(sExcludeList:String):void @@ -104,7 +120,9 @@ protected function initParameters():void { - m_sDataUrl = ReadParam('xmlUrl', 'http://localhost:81/pages/xml.navigator.php?operation=relation'); + m_sDataUrl = ReadParam('xmlUrl', 'c:\\temp\\test.xml'); + //m_sDataUrl = ReadParam('xmlUrl', 'c:\\temp\\test2.xml'); + //m_sDataUrl = ReadParam('xmlUrl', 'c:\\temp\\navigator.xml'); m_sDetailsUrl = ReadParam('drillUrl', 'http://localhost/pages/UI.php?operation=details'); m_sRelation = ReadParam('relation', 'impacts'); m_sObjClass = ReadParam('obj_class', 'DBServerInstance'); @@ -114,6 +132,7 @@ function initGraphics(event:Event):void { + stage.align = 'left'; m_oCanvas = new NavigatorCanvas(); // All drawings will occur here addChild(m_oCanvas); m_oCanvas.scaleX = m_fZoom; @@ -122,26 +141,54 @@ removeEventListener(Event.ENTER_FRAME,initGraphics); m_oZoomSlider.value = 100; m_oZoomSlider.addEventListener(SliderEvent.CHANGE, onZoomChange); + m_oVersion.text = CURRENT_VERSION; } function mouseDown(event:MouseEvent):void { trace("Click in canvas"); if (!m_bChildDragging) { + m_bAutoPanAndZoom = false; m_oCanvas.startDrag(); } - } + } function mouseReleased(event:MouseEvent):void { if (!m_bChildDragging) { - m_oCanvas.stopDrag(); + m_oCanvas.stopDrag(); + trace('Canvas.x = '+m_oCanvas.x+', Canvas.y = '+m_oCanvas.y); } } + function mouseWheelHandler(event:MouseEvent):void + { + trace("mouseWheelHandler delta: " + event.delta); + var newZoomLevel:Number = m_fZoom; + if (event.delta > 0) + { + newZoomLevel += 0.05; + if (newZoomLevel < 1.0) + { + SetZoomLevel(newZoomLevel); + m_oZoomSlider.value = Math.round(100*newZoomLevel); + } + } + else + { + newZoomLevel -= 0.05; + if (newZoomLevel > 0.0) + { + SetZoomLevel(newZoomLevel); + m_oZoomSlider.value = Math.round(100*newZoomLevel); + } + } + } + function onZoomChange(event:SliderEvent):void - { + { + m_bAutoPanAndZoom = false; SetZoomLevel(event.value/100); } @@ -190,10 +237,18 @@ //trace("==========================="); parseXMLData(null, myXML, 0, 0); m_sTitle.text = decodeEntities(myXML.attribute("title").toString()); - m_oZoomSlider.enabled = true; + + + // Initial positions computation + for(var i=0; i<10; i++) + { + UpdatePositions(); + } + UpdatePanAndZoom(1); addEventListener(Event.ENTER_FRAME, drawLines); stage.addEventListener(MouseEvent.MOUSE_DOWN, mouseDown) stage.addEventListener(MouseEvent.MOUSE_UP, mouseReleased); + stage.addEventListener(MouseEvent.MOUSE_WHEEL, mouseWheelHandler); //trace('======= Initial Posistions ========='); //DumpPositions(); } @@ -215,6 +270,8 @@ { m_oPreloader.visible = false; } + m_oZoomSlider.enabled = true; + m_oVersion.visible = false; } } @@ -263,7 +320,7 @@ var iId = oXMLData.@id; var sLabel:String = decodeEntities(oXMLData.@name); var sIcon:String = oXMLData.@icon; - var oDetails:Object = new Object; + var oDetails:Object = {}; var sZlist:String = oXMLData.@zlist; var oNode:GraphNode = GetNode(sClass+'/'+iId); @@ -287,7 +344,13 @@ //} } oNode = new GraphNode(this, oPt, sClass, sClassName, iId, sLabel, sIcon, sParentKey, m_fZoom, oDetails); + if (oParent == null) + { + // this is the root node, higlight it + oNode.MarkAsRoot(); + } this.m_aNodes[oNode.GetKey()] = oNode; //Keep it referenced + m_iNbNodes++; if (oParent == null) { m_oRootNode = oNode; @@ -437,30 +500,77 @@ { var color:uint = 0x666666; m_oCanvas.graphics.clear(); - m_oCanvas.graphics.lineStyle(2,0x666666,100); - UpdatePositions(); - for (var index:String in m_aLinks) - { - var oStartNode:GraphNode = GetNode(m_aLinks[index].GetStart()); - var oEndNode = GetNode(m_aLinks[index].GetEnd()); - m_oCanvas.graphics.moveTo(oStartNode.x, oStartNode.y); - m_oCanvas.graphics.lineTo(oEndNode.x, oEndNode.y); - var oMiddlePoint:Point = new Point((oEndNode.x+oStartNode.x)/2, (oEndNode.y+oStartNode.y)/2); - drawArrow(oMiddlePoint, oEndNode.x - oStartNode.x, oEndNode.y - oStartNode.y, color); - } - - if (this.m_FOCUS_DELAY_COUNTDOWN > 0) - { - this.m_FOCUS_DELAY_COUNTDOWN--; - trace('FOCUS_DELAY:'+this.m_FOCUS_DELAY_COUNTDOWN); - UpdatePanAndZoom(m_FOCUS_DELAY_COUNTDOWN / 30); - } - else if (this.m_FOCUS_DELAY_COUNTDOWN == 0) + + if (m_bHighFrictionMode) { // Increase the friction so that manually manipulating objects gets easier trace("More friction now..."); - m_Ks = 5*m_Ks; // 5 times more friction - this.m_FOCUS_DELAY_COUNTDOWN--; + m_Ks = m_KsHighFriction; // 5 times more friction + } + else + { + m_Ks = m_KsLowFriction; + } + + if (DEBUG) + { + m_oCanvas.graphics.lineStyle(2,0xff3333,100,false,"normal",CapsStyle.ROUND); + + var sceneRect:Rectangle = null; + for(var i:String in this.m_aNodes) + { + if (sceneRect == null) + { + sceneRect = GetNode(i).getBounds(m_oCanvas); + } + else + { + sceneRect = sceneRect.union(GetNode(i).getBounds(m_oCanvas)); + } + } + if (sceneRect != null) + { + /* + m_oCanvas.graphics.lineStyle(2,0xff0000,100,false,"normal",CapsStyle.ROUND); + + m_oCanvas.graphics.moveTo(-500, 0); + m_oCanvas.graphics.lineTo(500, 0); + + m_oCanvas.graphics.moveTo(0, -500); + m_oCanvas.graphics.lineTo(0, 500); + + m_oCanvas.graphics.moveTo(sceneRect.x, sceneRect.y); + m_oCanvas.graphics.lineTo(sceneRect.x + sceneRect.width, sceneRect.y); + m_oCanvas.graphics.lineTo(sceneRect.x + sceneRect.width, sceneRect.y + sceneRect.height); + m_oCanvas.graphics.lineTo(sceneRect.x, sceneRect.y + sceneRect.height); + m_oCanvas.graphics.lineTo(sceneRect.x, sceneRect.y); + */ + } + } + + m_oCanvas.graphics.lineStyle(2,0x666666,100); + UpdatePositions(); + if (m_bAutoPanAndZoom) + { + UpdatePanAndZoom(1); + } + + for (var index:String in m_aLinks) + { + + var oStartNode:GraphNode = GetNode(m_aLinks[index].GetStart()); + var oEndNode = GetNode(m_aLinks[index].GetEnd()); + + m_oCanvas.graphics.moveTo(oStartNode.x, oStartNode.y); + m_oCanvas.graphics.lineTo(oEndNode.x, oEndNode.y); + + if (DEBUG) + { + m_oCanvas.graphics.drawCircle(oStartNode.x, oStartNode.y, 5); + m_oCanvas.graphics.drawCircle(oEndNode.x, oEndNode.y, 5); + } + var oMiddlePoint:Point = new Point((oEndNode.x+oStartNode.x)/2, (oEndNode.y+oStartNode.y)/2); + drawArrow(oMiddlePoint, oEndNode.x - oStartNode.x, oEndNode.y - oStartNode.y, color); } } function drawArrow(oPt:Point, dx:Number, dy:Number, color:uint):void @@ -493,7 +603,7 @@ public function ComputeElectrostaticForces():Array { - var aForces:Array = new Array; + var aForces:Array = []; //trace('====== BEGIN ComputeElectrostaticForces() ======='); for (var i:String in this.m_aNodes) @@ -510,7 +620,6 @@ var dx:Number = oRemoteNode.x - oCurrentNode.x; var dy:Number = oRemoteNode.y - oCurrentNode.y; var d2:Number = (dx*dx + dy*dy) / (this.m_RADIUS * this.m_RADIUS); - var d:Number = Math.sqrt(d2); var Fx:Number = 0; var Fy:Number = 0; if (d2 < 0.05) @@ -554,7 +663,7 @@ function ComputeElasticForces() { //trace('====== BEGIN ComputeElasticForces() ======='); - var aForces:Array = new Array; + var aForces:Array = []; for (var i:String in this.m_aNodes) { @@ -562,9 +671,11 @@ aForces[i].FxTotal = 0; aForces[i].FyTotal = 0; } + // Elastic forces: each link applies a force proportional to its length (F = - K * x) for(i in this.m_aLinks) { + trace(i); var oStartNode:GraphNode = GetNode(m_aLinks[i].GetStart()); var oEndNode = GetNode(m_aLinks[i].GetEnd()); var dx = oStartNode.x - oEndNode.x; @@ -572,10 +683,12 @@ //d = Math.sqrt(dx*dx + dy*dy); //Fx = -K * d * dx / d; //Fy = -K * d * dy / d; - // Links with more weight attached are more rigid ! - //weightCoeff = (aWeights[aLinks[l].start] + aWeights[aLinks[l].end])/2; + //var Fy = -m_K * d * dy / d; + // Which can be simplified as: var Fx = -this.m_K * dx; var Fy = -this.m_K * dy; + // Links with more weight attached are more rigid ! + //weightCoeff = (aWeights[aLinks[l].start] + aWeights[aLinks[l].end])/2; aForces[oStartNode.GetKey()].FxTotal += Fx; aForces[oStartNode.GetKey()].FyTotal += Fy; aForces[oEndNode.GetKey()].FxTotal -= Fx; @@ -602,11 +715,26 @@ */ function UpdatePositions() { + if (!m_bComputationNeeded) + { + trace('Nothing is moving, halting the computation, deltaT='+m_deltaT); + return; // everything is still, no need to recompute + } //trace('====== BEGIN UpdatePositions() ======='); var aElasticForces:Array = ComputeElasticForces(); var aElectrostaticForces:Array = ComputeElectrostaticForces(); - //DrawForces(aElectrostaticForces, 0xcc0000); - //DrawForces(aElectrostaticForces, 0x0000cc); + if (DEBUG) + { + DrawForces(aElasticForces, 0xcc0000); + DrawForces(aElectrostaticForces, 0x0000cc); + } + var Ks2:Number = this.m_Ks*this.m_Ks; + var nbNodes:int = 0; + var dxMax:Number = 0; + var dyMax:Number = 0; + var maxAccel2 = 50000.0/(m_fZoom*m_fZoom); + m_bComputationNeeded = false; // Needed only if something is still moving + var nextDeltaT = m_deltaT; for (var i:String in this.m_aNodes) { var oNode:GraphNode = GetNode(i); @@ -625,16 +753,38 @@ } else { + var Ax = this.m_deltaT*Fx; + var Ay = this.m_deltaT*Fy; + if ((Ax * Ax + Ay *Ay) > maxAccel2) + { + nextDeltaT = m_deltaT*0.95; // Slow down for the next pass + } oNode.m_speed_x = oNode.m_speed_x*this.m_Kf + this.m_deltaT*Fx; oNode.m_speed_y = oNode.m_speed_y*this.m_Kf + this.m_deltaT*Fy; - var dx:Number = oNode.m_speed_x * this.m_deltaT; - var dy:Number = oNode.m_speed_y * this.m_deltaT; - oNode.x = Math.round(oNode.x + dx); - oNode.y = Math.round(oNode.y + dy); + + var dx:int = oNode.m_speed_x * this.m_deltaT; + var dy:int = oNode.m_speed_y * this.m_deltaT; + dxMax = Math.max(dxMax, dx); + dyMax = Math.max(dyMax, dy); + if ((dx*m_fZoom > 1) || (dx*m_fZoom > 1)) + { + // visibly moving by more than 1 px + oNode.x = oNode.x + dx; + oNode.y = oNode.y + dy; + m_bComputationNeeded = true; + } //trace('object '+i+' moves (Force: Fx='+Fx+', Fy='+Fy+')! '); } } + else + { + m_bComputationNeeded = true; + } + nbNodes++; } + m_deltaT = nextDeltaT; + trace('Iteration: '+(m_iIterations)+', '+nbNodes+' nodes processed. Max displacement dx='+dxMax+', dy='+dyMax); + m_iIterations++; //trace('======= Updated Positions ========='); //DumpPositions(); //trace('====== END UpdatePositions() ======='); @@ -649,8 +799,8 @@ m_oCanvas.graphics.lineStyle(2,color,100,false,"normal",CapsStyle.ROUND); m_oCanvas.graphics.moveTo(oNode.x, oNode.y); var oEndPoint:Point = new Point; - oEndPoint.x = Math.round(oNode.x + oForce.FxTotal); - oEndPoint.y = Math.round(oNode.y + oForce.FyTotal); + oEndPoint.x = oNode.x + oForce.FxTotal; + oEndPoint.y = oNode.y + oForce.FyTotal; m_oCanvas.graphics.lineTo(oEndPoint.x, oEndPoint.y); drawArrow(oEndPoint, oForce.FxTotal, oForce.FyTotal, color); //trace('Drawinf vector '+i+': (x='+oNode.x+', y='+oNode.y+') to (x='+oEndPoint.x+', y='+oEndPoint.y+')'); @@ -676,27 +826,22 @@ var idealZoomLevel:Number = 1; trace('Stage dimensions: width:'+stage.stageWidth+' height:'+stage.stageHeight); - if ((sceneRect.width > stage.stageWidth) || (sceneRect.height > (stage.stageHeight - 50))) - { - var wRatio:Number = stage.stageWidth / sceneRect.width; - var hRatio:Number = (stage.stageHeight - 50) / sceneRect.height; - idealZoomLevel = Math.min(wRatio, hRatio); - SetZoomLevel(idealZoomLevel); - m_oZoomSlider.value = Math.round(100*idealZoomLevel); - - } + var wRatio:Number = stage.stageWidth / sceneRect.width; + var hRatio:Number = (stage.stageHeight - 50) / sceneRect.height; + idealZoomLevel = Math.min(Math.min(wRatio, hRatio), 1); + SetZoomLevel(idealZoomLevel); + m_oZoomSlider.value = Math.round(100*idealZoomLevel); + var xOffset:Number = 0; - var yOffset:Number = 50; - if (stage.stageWidth > sceneRect.width) - { - xOffset = (stage.stageWidth-sceneRect.width)/2 - } - if (stage.stageHeight > sceneRect.height) - { - yOffset = 50 + (stage.stageHeight-50-sceneRect.height)/2 - } - m_oCanvas.x = xOffset-sceneRect.x; - m_oCanvas.y = yOffset-sceneRect.y; + var yOffset:Number = 0; + var newWidth:Number = sceneRect.width * idealZoomLevel; + var newHeight:Number = sceneRect.height * idealZoomLevel; + + xOffset = stage.stageWidth/2 - newWidth/2 - sceneRect.x*idealZoomLevel; + yOffset = 50 + stage.stageHeight/2 - newHeight/2 - sceneRect.y*idealZoomLevel; + + m_oCanvas.x = xOffset; + m_oCanvas.y = yOffset; trace('Scene bounding rect: x:'+sceneRect.x+' y:'+sceneRect.y+' width:'+sceneRect.width+' height:'+sceneRect.height+' zoomLevel:'+idealZoomLevel); } diff --git a/navigator/navigator.fla b/navigator/navigator.fla index f587149ef..bfc580e5b 100755 Binary files a/navigator/navigator.fla and b/navigator/navigator.fla differ