// // This work is licensed under the Creative Commons Attribution 2.5 License. To // view a copy of this license, visit // http://creativecommons.org/licenses/by/2.5/ // or send a letter to Creative Commons, 543 Howard Street, 5th Floor, San // Francisco, California, 94105, USA. // // All copies and derivatives of this source must contain the license statement // above and the following attribution: // // Author: Kyle Scholz http://kylescholz.com/ // Copyright: 2006 // /** * DOMTreeGraphController: * * This is the Controller in our MVC. It performs most or all of the interactions with * the Model and View components, so most applications will only need to interact * with this class. */ var DOMTreeGraphController = function( frameWidth, frameHeight, frameLeft, frameTop ) { this.initialize( frameWidth, frameHeight, frameLeft, frameTop ); }; DOMTreeGraphController.prototype = { nodes: {}, /* * Setup the environment, instantiating and connecting the Timer, * Model, and View components. */ initialize: function( frameWidth, frameHeight, frameLeft, frameTop ) { // instantiate our 'model' and 'view' this['model'] = new TreeGraphModel( frameWidth, frameHeight ); this['view'] = new DOMGraphView( this['model'], frameLeft, frameTop ); // subscribe the view to the model this['model'].subscribe( this['view'] ); // create a timer this['timer'] = new Timer(); this['timer'].initialize( 100 ); // subscribe the model to the timer this['timer'].subscribe( this['model'] ); // start the timer this['timer'].start(); // attach an onresize event window.onresize = new EventHandler( this, this.handleResizeEvent ); // attach an onmousemove event window.onmousemove = new EventHandler( this, this.handleMouseMoveEvent ); // attach an onmouseup event window.onmouseup = new EventHandler( this, this.handleMouseUpEvent ); }, /* * Add a node. In a Tree Graph, nodes should be added using * DOMTreeGraphController.addRootNode() or Node.addChild(). The parameters to each * of these methods are the same and they are broken down below: * * - domElement: A reference to an element in the DOM that should either be * cloned, or added directly to the graph. * - doClone: A boolean indicated that specifies whether the domElement * should be cloned or added directly. * - radius: The edgeLength for this nodes children. * - fanAngle: The maximum angle that may be occupied by this node's children. * - startX: The initial x-coordinate of this node's position. * - startY: The initial y-coordinate of this node's position. */ addNode: function( domElement, doClone, radius, fanAngle, startX, startY ) { domElement.style.display='block'; domElement.style.position='absolute'; // if ( !startX ) { startX = parseInt( domElement.style.left ) + this['view']['frameLeft']; } // if ( !startY ) { startY = parseInt( domElement.style.top ) + this['view']['frameTop']; } if ( !startX ) { startX = parseInt( this['view']['frameLeft']/2); } if ( !startY ) { startY = parseInt( this['view']['frameTop']/2); } if ( doClone ) { domElement = domElement.cloneNode(true); } var modelNode = this['model'].addNode( radius, fanAngle, startX, startY ); var viewNode = this['view'].addNode( modelNode, domElement ); var node = new DOMTreeNode( this, modelNode, viewNode ); // add a mousedown event handler viewNode.onmousedown = new EventHandler( node, node.handleMouseDownEvent ); this.view.drawNode(modelNode); return node; }, /* * Add a node directly to the graph without specifying a parent. */ addRootNode: function( domElement, doClone, radius, fanAngle, startX, startY ) { var node = this.addNode( domElement, doClone, radius, fanAngle, startX, startY ); this['model']['updateQueue'].push( node['modelNode'] ); return node; }, /* * Handle a resize event. */ handleResizeEvent: function() { }, /* * Handle a mouse move event. */ handleMouseMoveEvent: function( e ) { if ( this['selectedNode'] && !this['selectedNode']['modelNode']['isStatic'] ) { var mouseX; var mouseY; // get the cursor position if (document.all) { mouseX = event.clientX + document.body.scrollLeft; mouseY = event.clientY + document.body.scrollTop; } else { mouseX = e.pageX; mouseY = e.pageY; } mouseX -= this['view'].frameLeft; mouseY -= this['view'].frameTop; // set the node position this['selectedNode']['modelNode'].position['x']=mouseX; this['selectedNode']['modelNode'].position['y']=mouseY; // determine new angle and radius var dist = new Distance( this['selectedNode']['modelNode']['parent']['position'], this['selectedNode']['modelNode']['position'] ); var dx = dist['dx']/this['model']['skew']; var d = Math.sqrt( dx*dx+dist['dy']*dist['dy'] ) var t = Math.acos( dx/d ) * (180/Math.PI); if ( this['selectedNode']['modelNode'].position['y'] > this['selectedNode']['modelNode']['parent'].position['y'] ) { t*=(-1); } t=90-t; if ( (this['selectedNode']['modelNode']['target']['t'] - t) < -180) { t-=360; } this['selectedNode']['modelNode']['rootAngle']=t; this['selectedNode']['modelNode'].position['r']=d; this['selectedNode']['modelNode'].position['t']=t; this['selectedNode']['modelNode'].updateChildren(); this['model'].setChildrenForces( this['selectedNode']['modelNode'] ); this['view'].drawNode( this['selectedNode']['modelNode'] ); } }, /* * Handle a mouse up event. */ handleMouseUpEvent: function() { if ( this['timer']['interupt'] ) { this['timer'].start(); } this.clearSelected(); }, /* * Handle a pause event. */ handlePauseEvent: function() { this['timer'].stop(); }, /* * Handle an unpause event. */ handleUnpauseEvent: function() { }, /* * */ setSelected: function( node ) { if ( !this['timer']['interupt'] ) { this['timer'].stop(); } this['selectedNode'] = node; this['selectedNode']['modelNode'].selected = true; this['selectedNode']['modelNode'].done = false; }, /* * */ clearSelected: function() { this['timer'].start(); if ( this['selectedNode'] ) { this['selectedNode']['modelNode']['parent'].updateChildren(); this['selectedNode']['modelNode'].selected = false; } this['selectedNode'] = null; } } /** * DOMTreeNode: * * Encapsulates the Model and View perspectives of a node. */ var DOMTreeNode = function( graphControl, modelNode, viewNode ) { this['graphControl'] = graphControl; this['modelNode'] = modelNode; this['viewNode'] = viewNode; /* * Adds a node to the graph as a child of an existing node. These fields will * be inherited from the parent if they are omitted: * - radius * - fanAngle * - startX * - startY */ this['addEdge'] = function( domElement, doClone, visible, radius, fanAngle, startX, startY, partId ) { //alert("domtreenode:addedge offet:"+offset); if ( !domElement ) { domElement=this['viewNode']; doClone=true; } if ( visible != true && visible != false ) { visible=true; } if ( !radius ) { radius = this['modelNode']['radius']; } if ( !fanAngle ) { fanAngle = this['modelNode']['fanAngle']; } if ( !startX ) { startX = this['modelNode']['position']['x']; } if ( !startY ) { startY = this['modelNode']['position']['y']; } var node = this['graphControl'].addNode( domElement, doClone, radius, fanAngle, startX, startY ); this['modelNode'].addEdge( node['modelNode'], partId ); this['graphControl']['view'].addEdge( node['modelNode'], this['modelNode'], visible ); return node; }; /* * Handle a mouse down event. */ this['handleMouseDownEvent'] = function() { this['graphControl'].setSelected( this ); }; }