Click here to Skip to main content
15,885,909 members
Articles / Containers / Virtual Machine

Statistical parsing of English sentences

Rate me:
Please Sign up or sign in to vote.
4.95/5 (88 votes)
13 Dec 200617 min read 987.2K   23.6K   239  
Shows how to generate parse trees for English language sentences, using a C# port of OpenNLP, a statistical natural language parsing library.
using System;
using System.Diagnostics;
using System.Xml;
using System.Windows.Forms;
using System.Drawing;
using System.ComponentModel;
using System.IO;
using System.Collections;
namespace Netron.Lithium
{
	/// <summary>
	/// Netron's 'Lithium'  tree control
	/// </summary>
	public class LithiumControl : ScrollableControl
	{
		#region Events
	

		/// <summary>
		/// notifies the host to show the properties usually in the property grid
		/// </summary>
		public event ShowProps OnShowProps;

		/// <summary>
		/// occurs when a new node is added to the diagram
		/// </summary>
		public event ShapeData OnNewNode;

		/// <summary>
		/// occurs when certain objects send info out
		/// </summary>
		public event Messager OnInfo;

		/// <summary>
		/// occurs when a shape is deleted
		/// </summary>
		public event ShapeData OnDeleteNode;

		#endregion

		#region Fields

		/// <summary>
		/// the current layout direction
		/// </summary>
		protected TreeDirection layoutDirection = TreeDirection.Vertical;

		/// <summary>
		/// the space between the nodes
		/// </summary>
		protected int wordSpacing = 20;

		/// <summary>
		/// the height between branches
		/// </summary>
		protected int branchHeight = 60;

		/// <summary>
		/// whether the tree layout algorithm will do its work by default
		/// </summary>
		protected bool layoutEnabled = true;
		/// <summary>
		/// the default name when the root is added
		/// </summary>
		protected readonly string defaultRootName = "Root";
		/// <summary>
		/// the abstract representation of the graph
		/// </summary>
		protected internal GraphAbstract graphAbstract;		
		/// <summary>
		/// the entity hovered by the mouse
		/// </summary>
		protected Entity hoveredEntity;
		/// <summary>
		/// the unique entity currently selected
		/// </summary>
		protected Entity selectedEntity;
		/// <summary>
		/// whether we are tracking, i.e. moving something around
		/// </summary>
		protected bool tracking = false;
		/// <summary>
		/// just a reference point for the OnMouseDown event
		/// </summary>
		protected Point refp;
		/// <summary>
		/// the context menu of the control
		/// </summary>
		protected ContextMenu menu;
		/// <summary>
		/// A simple, general purpose random generator
		/// </summary>
		protected Random rnd;
		/// <summary>
		/// simple proxy for the propsgrid of the control
		/// </summary>
		protected Proxy proxy;
		/// <summary>
		/// whether the diagram is moved globally
		/// </summary>
		protected bool globalMove = false;

		/// <summary>
		/// just the default gridsize used in the paint-background method
		/// </summary>
		protected Size gridSize = new Size(10,10);

		/// <summary>
		/// the new but volatile connection
		/// </summary>
		protected internal Connection neoCon = null;

		/// <summary>
		/// memory of a connection if the volatile does not end up to a solid connection
		/// </summary>
		protected ShapeBase memChild = null, memParent = null;

		/// <summary>
		/// the type of connection
		/// </summary>
		protected ConnectionType connectionType = ConnectionType.Default;

		#endregion

		#region Properties
		
		/// <summary>
		/// Gets or sets the type of connection drawn
		/// </summary>
		public ConnectionType ConnectionType
		{
			get{return connectionType;}
			set{connectionType = value;
			Invalidate();
			}
		}

		/// <summary>
		/// Gets or sets the direction the tree-layout expands the tree
		/// </summary>
		public TreeDirection LayoutDirection
		{
			get{return layoutDirection;}
			set{layoutDirection = value;
			//update in case it has changed
				switch(value)
				{
					case TreeDirection.Horizontal:
						branchHeight = 120;
						wordSpacing = 20;
						break;
					case TreeDirection.Vertical:
						branchHeight = 70;
						wordSpacing = 20;
						break;

				}
				DrawTree();
			}
		}
		/// <summary>
		/// Gets or sets whether the tree layout algorithm will do its work by default
		/// </summary>
		public bool LayoutEnabled
		{
			get{return layoutEnabled ;}
			set{
				layoutEnabled = value;
				if(value) //if set to true, let's rectify what eventually has been distorted by the user
					DrawTree();
			}
		}

		/// <summary>
		/// Gets or sets the shape collection
		/// </summary>
		[Browsable(false)]
		public ShapeCollection Shapes
		{
			get{return graphAbstract.Shapes;}
			set{graphAbstract.Shapes = value;}
		}
		/// <summary>
		/// Gets or sets the connection collection
		/// </summary>
		[Browsable(false)]
		public ConnectionCollection Connections
		{
			get{return graphAbstract.Connections;}
			set{graphAbstract.Connections = value;}
		}
	
		/// <summary>
		/// Gets the root of the diagram
		/// </summary>
		[Browsable(false)]
		public ShapeBase Root
		{
			get{return graphAbstract.Root;}
		}

		/// <summary>
		/// Gets or sets the height between the tree branches
		/// </summary>
		[Browsable(true)]
		public int BranchHeight
		{
			get{return branchHeight;}
			set{
				branchHeight = value;
				DrawTree();
			}
		}
		/// <summary>
		/// Gets or sets the spacing between the nodes
		/// </summary>
		[Browsable(true)]
		public int WordSpacing
		{
			get{return wordSpacing;}
			set
			{
				wordSpacing = value;
				DrawTree();
			}
		}


		#endregion

		#region Constructor
		/// <summary>
		/// Default ctor
		/// </summary>
		public LithiumControl()
		{
			//double-buffering
			SetStyle(ControlStyles.AllPaintingInWmPaint, true);
			SetStyle(ControlStyles.DoubleBuffer, true);
			SetStyle(ControlStyles.UserPaint, true);
			SetStyle(ControlStyles.ResizeRedraw, true);

			//init the abstract
			graphAbstract = new GraphAbstract();			
			graphAbstract.Shapes.OnShapeAdded+=new ShapeData(OnShapeAdded);

			AddRoot(defaultRootName);

			//default menu, can be overwritten in the design of the application
			menu = new ContextMenu();
			BuildMenu();
			this.ContextMenu = menu;

			//init the randomizer
			rnd = new Random();

			//init the proxy
			proxy = new Proxy(this);

			//allow scrolling
			this.AutoScroll=true;			
			this.HScroll=true;
			this.VScroll=true;
		}

		#endregion

		#region Methods

		private void visitor_OnDelete(ShapeBase shape)
		{
			if(OnDeleteNode!=null)
				OnDeleteNode(shape);
		}
		/// <summary>
		/// Passes the event from the abstracts shape collection to the outside.
		/// Having the event in the GraphAbstract being raised centralizes it,
		/// otherwise the event should be raise in various places
		/// </summary>
		/// <param name="shape"></param>
		private void OnShapeAdded(ShapeBase shape)
		{
			if(this.OnNewNode!=null)
				OnNewNode(shape);
		}

		/// <summary>
		/// Builds the context menu
		/// </summary>
		private void BuildMenu()
		{
			MenuItem mnuDelete = new MenuItem("Delete",new EventHandler(OnDelete));
			menu.MenuItems.Add(mnuDelete);
			
			MenuItem mnuProps = new MenuItem("Properties", new EventHandler(OnProps));
			menu.MenuItems.Add(mnuProps);

			MenuItem mnuDash = new MenuItem("-");
			menu.MenuItems.Add(mnuDash);			

			MenuItem mnuShapes = new MenuItem("Change to");
			menu.MenuItems.Add(mnuShapes);

			MenuItem mnuRecShape = new MenuItem("Rectangular shape", new EventHandler(OnRecShape));
			mnuShapes.MenuItems.Add(mnuRecShape);
			
			MenuItem mnuOvalShape = new MenuItem("Oval shape", new EventHandler(OnOvalShape));
			mnuShapes.MenuItems.Add(mnuOvalShape);

			MenuItem mnuDash2 = new MenuItem("-");
			menu.MenuItems.Add(mnuDash2);

			MenuItem mnuTLShape = new MenuItem("Add Text label", new EventHandler(OnTextLabelShape));
			menu.MenuItems.Add(mnuTLShape);


			
		}


		#region Menu handlers
		/// <summary>
		/// Deletes the currently selected object from the canvas
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void OnDelete(object sender, EventArgs e)
		{
			if(selectedEntity!=null)
			{
				if(typeof(ShapeBase).IsInstanceOfType(selectedEntity))
				{
					this.Shapes.Remove(selectedEntity as ShapeBase);
					this.Invalidate();
				}
				else
					if(typeof(Connection).IsInstanceOfType(selectedEntity))
				{
					this.Connections.Remove(selectedEntity as Connection);
					this.Invalidate();
				}

			}
		}


		/// <summary>
		/// Asks the host to show the props
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void OnProps(object sender, EventArgs e)
		{
			object thing;
			if(this.selectedEntity==null) 
				thing = this.proxy;
			else
				thing =selectedEntity;
			if(this.OnShowProps!=null)
				OnShowProps(thing);

		}

		
		private void OnRecShape(object sender, EventArgs e)
		{
			throw new NotImplementedException("Sorry, not implemented yet");
		}
		private void OnOvalShape(object sender, EventArgs e)
		{
			throw new NotImplementedException("Sorry, not implemented yet");
		}
		private void OnTextLabelShape(object sender, EventArgs e)
		{
			AddShape(ShapeTypes.TextLabel,refp);
		}
	
		#endregion

		/// <summary>
		/// Paints the control
		/// </summary>
		/// <remarks>
		/// If you switch the painting order of Connections and shapes the connection line
		/// will be underneath/above the shape
		/// </remarks>
		/// <param name="e"></param>
		protected override void OnPaint(PaintEventArgs e)
		{
			Graphics g = e.Graphics;
			//use the best quality, with a performance penalty
			g.SmoothingMode= System.Drawing.Drawing2D.SmoothingMode.AntiAlias;		

			int scrollPositionX = this.AutoScrollPosition.X;
			int scrollPositionY = this.AutoScrollPosition.Y;
			g.TranslateTransform(scrollPositionX, scrollPositionY);

			//zoom
			//g.ScaleTransform(0.1f,0.1f);

			//draw the Connections
			for(int k=0; k<Connections.Count; k++)
			{
				Connections[k].Paint(g);				
			}
			if(neoCon!=null) neoCon.Paint(g);
			//loop over the shapes and draw
			for(int k=0; k<Shapes.Count; k++)
			{
				if(Shapes[k].visible)
					Shapes[k].Paint(g);
			}
			
			
		}
		/// <summary>
		/// Paints the background
		/// </summary>
		/// <param name="e"></param>
		protected override void OnPaintBackground(PaintEventArgs e)
		{
			base.OnPaintBackground (e);
//			Graphics g = e.Graphics;
//
//			g.DrawString("Lithium Tree Layout Control [version " + System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString() + "]",Font,Brushes.SlateGray,new Point(20,10));
//			g.DrawString("<Shift>: move the diagram",Font,Brushes.SlateGray,new Point(20,30));
//			g.DrawString("<Ctrl>: move a node to a new parent",Font,Brushes.SlateGray,new Point(20,40));
//			g.DrawString("<Alt>: add a new child node",Font,Brushes.SlateGray,new Point(20,50));
		}
		

		#region Add random node
		/// <summary>
		/// Adds a random node 
		/// </summary>
		public void AddRandomNode()
		{
			AddRandomNode("Random");
		}		

		/// <summary>
		/// Adds a random node to the diagram
		/// </summary>
		/// <param name="newName">the text of the newly added shape</param>
		public void AddRandomNode(string newName)
		{
			//Random rnd = new Random();

			int max = Shapes.Count-1;

			if(max<0) return;

			ShapeBase shape = Shapes[rnd.Next(0,max)];
			shape.AddChild(newName);

			DrawTree();
		}
		#endregion

		#region New diagram
		/// <summary>
		/// Starts a new diagram and forgets about everything
		/// You need to call the Save method before this if you wish to keep the current diagram.
		/// </summary>
		/// <param name="rootName">the text of the root in the new diagram</param>
		public void NewDiagram(string rootName)
		{
			this.graphAbstract=new GraphAbstract();
			this.AddRoot(rootName);
			CenterRoot();
			Invalidate();
		}

		/// <summary>
		/// Starts a new diagram and forgets about everything
		/// You need to call the Save method before this if you wish to keep the current diagram.
		/// </summary>
		public void NewDiagram()
		{
			this.graphAbstract = new GraphAbstract();
			this.AddRoot(defaultRootName);
			CenterRoot();
			Invalidate();
		}
		#endregion

		/// <summary>
		/// Adds the root of the diagram to the canvas
		/// </summary>
		/// <param name="rootText"></param>
		private ShapeBase AddRoot(string rootText)
		{
			if(Shapes.Count>0)
				throw new Exception("You cannot set the root unless the diagram is empty");
			SimpleRectangle root = new SimpleRectangle(this);
			root.Location = new Point(Width/2+50,Height/2+50);
			root.Width = 50;
			root.Height = 25;
			root.Text = rootText;
			root.ShapeColor = Color.SteelBlue;
			root.IsRoot = true;
			root.Font = Font;		
			root.visible = false;
			root.level = 0;
			Fit(root);
			//set the root of the diagram
			this.graphAbstract.Root = root;
			Shapes.Add(root);
			return root;
		}

	

		/// <summary>
		/// Centers the root on the control's canvas
		/// </summary>
		public void CenterRoot()
		{
			graphAbstract.Root.rectangle.Location = new Point(Width/2,Height/2);
			//Invalidate();
			DrawTree();
			
		}


		/// <summary>
		/// Adds a shape to the canvas or diagram
		/// </summary>
		/// <param name="shape"></param>
		public ShapeBase AddShape(ShapeBase shape)
		{
			Shapes.Add(shape);
			shape.Site = this;
			this.Invalidate();
			return shape;
		}
		/// <summary>
		/// Adds a predefined shape
		/// </summary>
		/// <param name="type"></param>
		/// <param name="location"></param>
		public ShapeBase AddShape(ShapeTypes type, Point location)
		{
			ShapeBase shape = null;
			switch(type)
			{
				case ShapeTypes.Rectangular:
					shape = new SimpleRectangle(this);
					break;
				case ShapeTypes.Oval:
					shape = new OvalShape(this);
					break;
				case ShapeTypes.TextLabel:
					shape = new TextLabel(this);
					shape.Location = location;
					shape.ShapeColor = Color.Transparent;
					shape.Text = "A text label (change the text in the property grid)";
					shape.Width = 350;
					shape.Height = 30;
					Shapes.Add(shape);
					return shape;


			}
			if(shape==null) return null;
			shape.ShapeColor = Color.FromArgb(rnd.Next(0,255),rnd.Next(0,255),rnd.Next(0,255));
			shape.Location = location;
			Shapes.Add(shape);
			return shape;
		}

		/// <summary>
		/// Move with the given vector
		/// </summary>
		/// <param name="p"></param>
		public void MoveDiagram(Point p)
		{
			
			//move the whole diagram
			
			foreach(ShapeBase shape in Shapes)
			{					
				shape.Move(p);										
				Invalidate();
			}				
			return;
			
		}




		#region Mouse event handlers

		/// <summary>
		/// Handles the mouse-down event
		/// </summary>
		/// <param name="e"></param>
		protected override void OnMouseDown(MouseEventArgs e)
		{
			base.OnMouseDown (e);

			Point p = new Point(e.X - this.AutoScrollPosition.X, e.Y - this.AutoScrollPosition.Y);		
			Rectangle r;
			#region SHIFT

//			if(Control.ModifierKeys==Keys.Shift)
//			{
//				globalMove = true;
//				refp = p; //useful for all kind of things
//				return;
//			}

			#endregion

			ShapeBase sh ;

			#region LMB & RMB
			//test for shapes
			for(int k=0; k<Shapes.Count; k++)
			{
				sh = Shapes[k];
				if(sh.childNodes.Count>0)//has a [+/-]
				{
					if(layoutDirection==TreeDirection.Vertical)
						r = new Rectangle(sh.Left + sh.Width/2 - 5, sh.Bottom, 10, 10);
					else
						r = new Rectangle(sh.Right, sh.Y + sh.Height/2-5, 10, 10);

					if(r.Contains(p))
					{
						if(sh.expanded)
							sh.Collapse(true);
						else
							sh.Expand();
						DrawTree();
					}
				}
				if(Shapes[k].Hit(p))
				{
					//shapes[k].ShapeColor = Color.WhiteSmoke;
					if(selectedEntity!=null) 
						selectedEntity.IsSelected=false;
					selectedEntity = Shapes[k];
					selectedEntity.IsSelected = true;
					sh = selectedEntity as ShapeBase;	
					#region CONTROL
//					if(Control.ModifierKeys==Keys.Control && !sh.IsRoot)
//					{
//						tracking = true;
//						//remove from parent							
//						sh.parentNode.childNodes.Remove(sh);
//						Connections.Remove(sh, sh.parentNode);						
//						//...but keep the reference in case the user didn't find a new location
//						memChild = sh;
//						memParent = sh.parentNode;
//						//now remove the reference
//						sh.parentNode = null;
//					}
					#endregion
					//set the point for the next round
					refp=p;

					#region Double-click
					if(e.Button==MouseButtons.Left && e.Clicks==2)
					{							
						if(sh.expanded)
							sh.Collapse(true);
						else
							sh.Expand();
						DrawTree();
					}
					#endregion

					#region ALT
//					if(Control.ModifierKeys==Keys.Alt)
//					{
//						sh.AddChild("New");	
//						DrawTree();
//					}



					#endregion
					if(OnShowProps!=null)
						OnShowProps(Shapes[k]);
						
					return;
				}
			}
			if(selectedEntity!=null) selectedEntity.IsSelected=false;
			selectedEntity = null;
			Invalidate();
			refp = p; //useful for all kind of things
			//nothing was selected but we'll show the props of the control in this case
			if(OnShowProps!=null)
				OnShowProps(this.proxy);
			

			
			#endregion
			

		}

		/// <summary>
		/// Handles the mouse-move event
		/// </summary>
		/// <param name="e"></param>
		protected override void OnMouseMove(MouseEventArgs e)
		{
			base.OnMouseMove (e);
		
			Point p = new Point(e.X - this.AutoScrollPosition.X, e.Y - this.AutoScrollPosition.Y);		
			//move the whole diagram
			if(globalMove)
			{
				foreach(ShapeBase shape in Shapes)
				{
					shape.Move(new Point(p.X-refp.X,p.Y-refp.Y));					
					Invalidate();
				}
				refp=p;
				return;
			}

			//move just one and its kids
			if(tracking)			
			{	
		
				ShapeBase sh = selectedEntity as ShapeBase;
				
				ResetPickup(); //forget about what happened before
				Pickup(sh); //pickup the shape hanging underneath the shape to move next				
				foreach(ShapeBase shape in Shapes)
				{
					if(!shape.pickup) continue;
					shape.Move(new Point(p.X-refp.X,p.Y-refp.Y));					
					Invalidate();
				}
				refp=p;
				//try to find the new parent
				SeekNewParent(sh);
				Invalidate();	
				return;
			}


			//hovering stuff
			for(int k=0; k<Shapes.Count; k++)
			{
				if(Shapes[k].Hit(p))
				{
					if(hoveredEntity!=null) hoveredEntity.hovered = false;
					Shapes[k].hovered = true;
					hoveredEntity = Shapes[k];
					//hoveredEntity.Invalidate();
					Invalidate();
					return;
				}
			}

			for(int k=0; k<Connections.Count; k++)
			{
				if(Connections[k].Hit(p))
				{
					if(hoveredEntity!=null) hoveredEntity.hovered = false;
					Connections[k].hovered = true;
					hoveredEntity = Connections[k];
					hoveredEntity.Invalidate();
					Invalidate();
					return;
				}
			}		
			//reset the whole process if nothing happened above
			HoverNone();
			Invalidate();


			
		}
		/// <summary>
		/// Handles the mouse-up event
		/// </summary>
		/// <param name="e"></param>
		protected override void OnMouseUp(MouseEventArgs e)
		{
			base.OnMouseUp (e);
			globalMove = false;
			Connection con = null;
			//test if we connected a connection
			if(tracking)
			{
			
				tracking = false;
				//make the volatile solid
				if(neoCon!=null)
				{
					con = new Connection(neoCon.To, neoCon.From);
					con.site = this;
					Connections.Add(con);
					//the From is the shape seeking a parent
					neoCon.To.childNodes.Add(neoCon.From);
					neoCon.From.parentNode = neoCon.To;
					con.visible = true;
					neoCon.To.Expand();
					neoCon.From.connection = con;
				}
				else //the user hasn't released near anything, so reset to the original situation
				{
					con = new Connection(memChild, memParent);
					con.site = this;
					Connections.Add(con);
					memParent.childNodes.Add(memChild);
					memChild.parentNode = memParent;
					con.visible = true;
					memChild.connection = con;
				}

				//either case, restart the process next
				neoCon = null;
				memChild = null;
				memParent = null;
				DrawTree();

			}
			
		}

	

		/// <summary>
		/// Find a new parent for the given shape. This creates a new volatile connection which will be solidified
		/// in the MouseUp handler.
		/// </summary>
		/// <param name="shape">the shape being moved around by the user</param>
		private void SeekNewParent(ShapeBase shape)
		{
			/* this is the fast way but gives problems when the shape is surrounded by other shapes
			 * which makes it difficult to attach it to one you want
			for(int k=0;k<Shapes.Count; k++)
			{
				if(Shapes[k]!=shape && Environment(Shapes[k],shape) && Shapes[k].parentNode!=shape && !Shapes[k].pickup)
				{
					neoCon = new Connection(shape, Shapes[k],  Color.Red,2f);
					neoCon.visible = true;
					Invalidate();
					return;
				}
			}
			*/
			double best = 10000d; 
			int chosen = -1;
			double dist;
			ShapeBase other;
			for(int k=0;k<Shapes.Count; k++)
			{
				other = Shapes[k];
				if(other!=shape && other.visible && other.parentNode!=shape && !other.pickup)
				{
					dist = Math.Sqrt((other.X-shape.X)*(other.X-shape.X)+(other.Y-shape.Y)*(other.Y-shape.Y));
					if(dist<best && dist< 120)
						chosen = k;				
				}
			}
			if(chosen>-1)
			{
				neoCon = new Connection(shape, Shapes[chosen],  Color.Red,2f);
				neoCon.visible = true;
				neoCon.site = this;
				return;
			}

			neoCon = null;
		}

		private  bool Environment(ShapeBase shape1, ShapeBase shape2)
		{
			return Math.Sqrt((shape1.X-shape2.X)*(shape1.X-shape2.X)+(shape1.Y-shape2.Y)*(shape1.Y-shape2.Y))<100;
		}

		private void ResetPickup()
		{
			for(int k=0; k<Shapes.Count; k++)
				Shapes[k].pickup = false;
		}

		private void Pickup(ShapeBase shape)
		{
			shape.pickup=true;
			for(int k =0; k< shape.childNodes.Count; k++)
			{
				shape.childNodes[k].pickup = true;
				if(shape.childNodes[k].childNodes.Count>0) 
					Pickup(shape.childNodes[k]);
			}

		}

		

		#endregion

		/// <summary>
		/// Resets the hovering status of the control, i.e. the hoverEntity is set to null.
		/// </summary>
		private void HoverNone()
		{
			if(hoveredEntity!=null) 
			{
				hoveredEntity.hovered = false;
				hoveredEntity.Invalidate();
			}
			hoveredEntity = null;
		}



		/// <summary>
		/// Collapses the whole diagram
		/// </summary>
		public void CollapseAll()
		{
			this.Root.Collapse(true);
		}

		/// <summary>
		/// Deletes the currently selected shape
		/// </summary>
		public void Delete()
		{
			if(selectedEntity==null) return;
			ShapeBase sh = selectedEntity as ShapeBase;
			if(sh!=null)
			{
				if(sh.IsRoot) return; //cannot delete the root
				//delete the node from the parent's children
				sh.parentNode.childNodes.Remove(sh);
				//delete everything underneath the node 
				DeleteVisitor visitor = new DeleteVisitor(this.graphAbstract);
				visitor.OnDelete+=new ShapeData(visitor_OnDelete);
				graphAbstract.DepthFirstTraversal(visitor,sh);
				DrawTree();
			}
			//Invalidate();
				
		}

		/// <summary>
		/// Expands the whole diagram
		/// </summary>
		public void ExpandAll()
		{
			IVisitor expander = new ExpanderVisitor();
			this.graphAbstract.DepthFirstTraversal(expander);
			DrawTree();
			
		}

		/// <summary>
		/// Saves the diagram to file in XML format (XML serialization)
		/// </summary>
		/// <param name="filePath"></param>
		public void SaveGraphAs(string filePath)
		{
			XmlTextWriter tw = new XmlTextWriter(filePath,System.Text.Encoding.Unicode);
			GraphSerializer g = new GraphSerializer(this);
			g.Serialize(tw);
			tw.Close();
		}

		/// <summary>
		/// Opens a diagram which was saved to XML previously (XML deserialization)
		/// </summary>
		/// <param name="filePath"></param>
		public void OpenGraph(string filePath)
		{
			XmlTextReader reader = new XmlTextReader(filePath);
			GraphSerializer ser = new GraphSerializer(this);
			graphAbstract = ser.Deserialize(reader) as GraphAbstract;
			reader.Close();
			DrawTree();
			Invalidate();
		}


		#region Layout algorithm

		int marginLeft = 10;			
		
		
	

		

		/// <summary>
		/// Generic entry point to layout the diagram on the canvas.
		/// The default LayoutDirection is vertical. If you wish to layout the tree in a certain
		/// direction you need to specify this property first. Also, the direction is global, you cannot have 
		/// different parts being drawn in different ways though it can be implemented.
		/// 
		/// </summary>
		public void DrawTree()
		{
			if(!layoutEnabled) return;
			Point p = Point.Empty; //the shift vector difference between the original and the moved root
			try
			{
				//start the recursion
				//the layout will move the root but it's reset to its original position
				switch(layoutDirection)
				{
					case TreeDirection.Vertical:
						
						p = new Point(graphAbstract.Root.X, graphAbstract.Root.Y);
						VerticalDrawTree(graphAbstract.Root,false,marginLeft,this.graphAbstract.Root.Y);						
						p = new Point(-graphAbstract.Root.X+p.X, -graphAbstract.Root.Y+ p.Y);						
						MoveDiagram(p);						
						break;
					case TreeDirection.Horizontal:
						p = new Point(graphAbstract.Root.X, graphAbstract.Root.Y);
						HorizontalDrawTree(graphAbstract.Root,false,marginLeft,10);
						p = new Point(-graphAbstract.Root.X+p.X, -graphAbstract.Root.Y+ p.Y);		
						MoveDiagram(p);	
						break;
				}

				int maxY = 0;
				foreach (ShapeBase shape in Shapes)
				{
					if (shape.ShapeColor == Color.Ivory)
					{
						if (shape.Visible)
						{
							if (shape.Y > maxY)
							{
								maxY = shape.Y;
							}
						}
					}
				}

				foreach (ShapeBase shape in Shapes)
				{
					if (shape.ShapeColor == Color.Ivory)
					{
						if (shape.Visible)
						{
							shape.Move(new Point(0, maxY - shape.Y));
						}
					}
				}

				CalculateScrollBars();
				
				Invalidate();
			}
			catch(Exception exc)
			{
				Trace.WriteLine(exc.Message);				
			}
		}

		private void CalculateScrollBars()
		{
			Point minPoint = new Point(int.MaxValue,int.MaxValue);
			Size maxSize = new Size(0,0);
			foreach(ShapeBase shape in Shapes)
			{
				if (shape.Visible)
				{
					if (shape.X + shape.Width > maxSize.Width)
					{
						maxSize.Width = shape.X + shape.Width;
					}

					if (shape.Y + shape.Height > maxSize.Height)
					{
						maxSize.Height = shape.Y + shape.Height;
					}

					if (shape.X < minPoint.X)
					{
						minPoint.X = shape.X;
					}

					if (shape.Y < minPoint.Y)
					{
						minPoint.Y = shape.Y;
					}
				}
			}

			MoveDiagram(new Point(50 - minPoint.X, 50 - minPoint.Y)); 

			maxSize.Width = maxSize.Width - minPoint.X + 100;
			maxSize.Height = maxSize.Height - minPoint.Y + 100;
			this.AutoScrollMinSize = maxSize;
		}
		/// <summary>
		/// Positions everything underneath the node and returns the total width of the kids
		/// </summary>
		/// <param name="containerNode"></param>
		/// <param name="first"></param>
		/// <param name="shiftLeft"></param>
		/// <param name="shiftTop"></param>
		/// <returns></returns>
		private int VerticalDrawTree(ShapeBase containerNode, bool first, int shiftLeft, int shiftTop)
		{
			bool isFirst = false;
			bool isParent = containerNode.childNodes.Count>0? true: false;
			int childrenWidth = 0;
			int thisX, thisY;		
			int returned = 0;
			int verticalDelta = branchHeight ; //the applied vertical shift of the child depends on the Height of the containerNode
			#region Children width
			for(int i =0; i<containerNode.childNodes.Count; i++)
			{
				//determine the width of the label
				if(i==0)			
					isFirst = true;				
				else 				
					isFirst = false;
				if(containerNode.childNodes[i].visible)
				{
					if((branchHeight - containerNode.Height) < 30) //if too close to the child, shift it with 40 units
						verticalDelta = containerNode.Height + 40;
					returned = VerticalDrawTree(containerNode.childNodes[i], isFirst, shiftLeft + childrenWidth, shiftTop + verticalDelta );									
					childrenWidth += returned;
					
				}		

			}
			if(childrenWidth>0 && containerNode.expanded)
				childrenWidth=Math.Max(Convert.ToInt32(childrenWidth + (containerNode.Width-childrenWidth)/2), childrenWidth); //in case the length of the containerNode is bigger than the total length of the children
			#endregion

			if(childrenWidth==0) //there are no children; this is the branch end
				childrenWidth = containerNode.Width+wordSpacing;
			
			#region Positioning
			thisY = shiftTop;			
			if(containerNode.childNodes.Count>0 && containerNode.expanded)
			{
				if(containerNode.childNodes.Count==1)
				{

					thisX = Convert.ToInt32(containerNode.childNodes[0].X+containerNode.childNodes[0].Width/2 - containerNode.Width/2);
				}
				else
				{
					float firstChild = containerNode.childNodes[0].Left+ containerNode.childNodes[0].Width/2;
					float lastChild = containerNode.childNodes[containerNode.childNodes.Count-1].Left + containerNode.childNodes[containerNode.childNodes.Count-1].Width/2;
					//the following max in case the containerNode is larger than the childrenWidth
					thisX = Convert.ToInt32(Math.Max(firstChild + (lastChild -firstChild - containerNode.Width)/2, firstChild));
				}
			}
			else
			{
				thisX = shiftLeft;		
				
			}
			
			containerNode.rectangle.X = thisX;
			containerNode.rectangle.Y = thisY;
			#endregion

			return childrenWidth;
		}

		/// <summary>
		/// Horizontal layout algorithm
		/// </summary>
		/// <param name="containerNode"></param>
		/// <param name="first"></param>
		/// <param name="shiftLeft"></param>
		/// <param name="shiftTop"></param>
		/// <returns></returns>
		private int HorizontalDrawTree(ShapeBase containerNode, bool first, int shiftLeft, int shiftTop)
		{
			bool isFirst = false;
			bool isParent = containerNode.childNodes.Count>0? true: false;
			int childrenHeight = 0;
			int thisX, thisY;		
			int returned = 0;
			int horizontalDelta = branchHeight;
			#region Children width
			for(int i =0; i<containerNode.childNodes.Count; i++)
			{
				//determine the width of the label
				if(i==0)			
					isFirst = true;				
				else 				
					isFirst = false;
				if(containerNode.childNodes[i].visible)
				{
					if((branchHeight - containerNode.Width) < 30) //if too close to the child, shift it with 40 units
						horizontalDelta = containerNode.Width + 40;
					returned = HorizontalDrawTree(containerNode.childNodes[i], isFirst, shiftLeft + horizontalDelta , shiftTop + childrenHeight );					
					childrenHeight += returned;
				}
				

			}
			#endregion

			if(childrenHeight==0) //there are no children; this is the branch end
				childrenHeight = containerNode.Height+wordSpacing;
			
			#region Positioning
			thisX = shiftLeft;			
			if(containerNode.childNodes.Count>0 && containerNode.expanded)
			{
				
					int firstChild = containerNode.childNodes[0].Y;
					int lastChild = containerNode.childNodes[containerNode.childNodes.Count-1].Y;
					thisY = Convert.ToInt32(firstChild + (lastChild - firstChild)/2);
				
			}
			else
			{
				thisY = Convert.ToInt32(shiftTop);		
				
			}
			
			containerNode.rectangle.X = thisX;
			containerNode.rectangle.Y = thisY;
			#endregion

			return childrenHeight;
		}

		private int Measure(string text)
		{
			Graphics g = Graphics.FromHwnd(this.Handle);

			return Size.Round(g.MeasureString(text,Font)).Width +37;
		}
		/// <summary>
		/// Resizes the shape to fit the text
		/// </summary>
		/// <param name="shape"></param>
		public void Fit(ShapeBase shape)
		{
			Graphics g = Graphics.FromHwnd(this.Handle);
			Size s =  Size.Round(g.MeasureString(shape.Text,Font));
			shape.Width =s.Width +20;
			shape.Height = s.Height+8;
		}

		private void FitAll()
		{
			foreach(ShapeBase shape in graphAbstract.Shapes)
				Fit(shape);
			Invalidate();
		}


	

		/// <summary>
		/// When the font of the control is changed all shapes get
		/// the new font assigned
		/// </summary>
		/// <param name="e"></param>
		protected override void OnFontChanged(EventArgs e)
		{
			base.OnFontChanged (e);

			foreach(ShapeBase shape in graphAbstract.Shapes)
				shape.Font = Font;
		}

		/// <summary>
		/// Adds a child node to the currently selected one
		/// </summary>
		public void AddChild()
		{
			if(selectedEntity==null) return;

			ShapeBase sh = selectedEntity as ShapeBase;
			if(sh!=null)
			{
				sh.AddChild("New node");
				DrawTree();
			}

		}

		/// <summary>
		/// DFT of the diagram with the given visitor, starting from the given shape
		/// </summary>
		/// <param name="visitor"></param>
		/// <param name="shape"></param>
		public void DepthFirstTraversal(IVisitor visitor, ShapeBase shape)
		{
			graphAbstract.DepthFirstTraversal(visitor, shape);
		}


		/// <summary>
		/// DFT of the diagram with the given visitor, starting from the root
		/// </summary>
		/// <param name="visitor"></param>
		public void DepthFirstTraversal(IVisitor visitor)
		{
			graphAbstract.DepthFirstTraversal(visitor);
		}

		/// <summary>
		/// BFT of the diagram with the given visitor, starting from the root
		/// </summary>
		/// <param name="visitor"></param>
		public void BreadthFirstTraversal(IVisitor visitor)
		{
			graphAbstract.BreadthFirstTraversal(visitor);
		}

		/// <summary>
		/// BFT of the diagram with the given visitor, starting from the given shape
		/// </summary>
		/// <param name="visitor"></param>
		/// <param name="shape"></param>
		public void BreadthFirstTraversal(IVisitor visitor, ShapeBase shape)
		{
			graphAbstract.BreadthFirstTraversal(visitor, shape);
		}



		#endregion

		#endregion

		
	}

	
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
United Kingdom United Kingdom
Richard Northedge is a senior developer with a UK Microsoft Gold Partner company. He has a postgraduate degree in English Literature, has been programming professionally since 1998 and has been an MCSD since 2000.

Comments and Discussions