Visualize graph algorithms using MindFusion Diagram component

In this post we’ll explore visualization of graph processing algorithms using MindFusion.Diagramming API. The sample Visual Studio project will show animated depth-first and breadth-first search algorithms for graph traversal, but same approach can be applied for visualizing processes in any systems representable as graph data structures, such as message transmission in networks, progress of tasks in workflows, and so on. You can download the complete project here:

GraphSearch.zip

The code will show several techniques you might also find useful in other contexts:

  • build diagram programmatically from model data
  • use styles to temporarily apply several appearance attributes as a single unit
  • synchronize diagram with data coming from a worker thread

Let’s start by creating our (very simple) model classes, Graph and Vertex in this case, where connections in the graph will be stored using standard adjacency lists representation:

class Graph
{
	public List Vertices = new List();
}

class Vertex
{
	public List Neighbors = new List();
	public bool Visited;
	public int Index;
	public int SearchOrder;
}

Next, create a method that builds a diagram from the model objects. The mappings will be saved in a dictionary for later access.

private Dictionary<vertex, shapenode=""> nodes;
readonly RectangleF defaultSize = new RectangleF(0, 0, 10, 10);

///
/// Create diagram elements from graph with adjacency lists representation
/// 
void DiagramFromGraph(Graph g)
{
	diagram.ClearAll();

	// map graph vertices to diagram nodes
	nodes = new Dictionary<vertex, shapenode="">();

	// create a node for each vertex
	foreach (var v in g.Vertices)
	{
		var node = diagram.Factory.CreateShapeNode(defaultSize);
		node.Tag = v;
		nodes[v] = node;
	}

	// create links for adjacencies
	foreach (var v1 in g.Vertices)
	{
		foreach (var v2 in v1.Neighbors)
		{
			// only in one direction
			if (v1.Index < v2.Index)
				diagram.Factory.CreateDiagramLink(nodes[v1], nodes[v2]);
		}
	}

	// arrange the nodes
	new AnnealLayout { Randomize = false }.Arrange(diagram);

	// search starts from selected node
	diagram.Nodes[0].Selected = true;
}
</vertex,></vertex,>

Now create a sample graph and its corresponding drawing which we’ll use to show search progress:

void OnFormLoad(object sender, EventArgs e)
{
	// create sample graph to traverse
	var graph = new Graph();
	graph.GenerateRandom(20, 25);
	DiagramFromGraph(graph);
}

public void GenerateRandom(int v, int e)
{
    var rnd = new Random(42);
    for (int i = 0; i < v; i++)
        Vertices.Add(new Vertex { Index = i});
    int c = 0;
    while (e > 0)
    {
        var v1 = Vertices[c];
        var v2 = Vertices[rnd.Next(v)];
        if (v1 == v2 || v1.Neighbors.Contains(v2))
            continue;
        v1.Neighbors.Add(v2);
        v2.Neighbors.Add(v1);
        c = (c + 1) % v;
        e--;
    }
}

Add two styles we’ll use to show search progress. The first one is for vertices visited by the search algorithm, and the second one is applied temporarily when the algorithm back-tracks:

readonly ShapeNodeStyle visitedNodeStyle = new ShapeNodeStyle
   	{
   		Brush = new MindFusion.Drawing.SolidBrush(Color.Green)
   	};

readonly ShapeNodeStyle backtrackNodeStyle = new ShapeNodeStyle
	{
		Brush = new MindFusion.Drawing.SolidBrush(Color.DarkGreen),
		Stroke = new MindFusion.Drawing.SolidBrush(Color.Red),
		StrokeThickness = 1 // mm
	};

We’ll invoke the following methods from the search algorithm threads to show which vertices have just been processed:

void ShowProgress(Vertex v)
{
	// invoke in UI thread
	diagramView.Invoke(new System.Action(() =>
	{
		// update node style
		var node = nodes[v];
		node.Text = v.SearchOrder.ToString();
		node.Style = visitedNodeStyle;

		if (backtrackNode != null)
			backtrackNode.Style = visitedNodeStyle;
		backtrackNode = null;
	}));
	Thread.Sleep(animationDelay);
}

void ShowBacktrack(Vertex v)
{
	// invoke in UI thread
	diagramView.Invoke(new System.Action(() =>
	{
		if (backtrackNode != null)
			backtrackNode.Style = visitedNodeStyle;

		// update node style
		var node = nodes[v];
		node.Style = backtrackNodeStyle;
		backtrackNode = node;
	}));
	Thread.Sleep(animationDelay);
}

DiagramNode backtrackNode;
int animationDelay = 1000;

We now have everything ready for showing animated progress of graph algorithms. Add a form button that will run a sample depth-first search, add a click event handler called OnDepthFirstSearch, and handle it like this:

void OnDepthFirstSearch(object sender, EventArgs e)
{
	// do not search if there's no node selected
	var startNode = diagram.ActiveItem as DiagramNode;
	if (startNode == null)
		return;

	// search buttons disabled while current search thread runs
	btnDFS.Enabled = btnBFS.Enabled = false;

	// init data structures for new search
	ResetSearch();

	// get vertex corresponding to selected node
	var startVertex = (Vertex) startNode.Tag;

	// start depth-first search in a new thread
	currentSearch = new Thread(() =>
		DepthFirstSearch(startVertex, 0));
	currentSearch.Start();
}

int DepthFirstSearch(Vertex current, int order)
{
	// mark vertex as visited
	current.Visited = true;
	current.SearchOrder = order;

	// redraw its node from UI thread
	ShowProgress(current);

	// visit adjacent nodes
	foreach (var neighbor in current.Neighbors)
	{
		if (!neighbor.Visited)
		{
			// descend recursively
			order = DepthFirstSearch(neighbor, order + 1);

			// show in UI thread we are going back
			ShowBacktrack(current);
		}
	}

	if (current.SearchOrder == 0)
	{
		// enable search buttons
		SearchComplete();
	}

	return order;
}

Add a second button that will run breadth-first search thread:

private void OnBreadthFirstSearch(object sender, EventArgs e)
{
    // do not search if there's no node selected
    var startNode = diagram.ActiveItem as DiagramNode;
    if (startNode == null)
        return;

    // search buttons disabled while current search thread runs
    btnDFS.Enabled = btnBFS.Enabled = false;

    // init data structures for new search
    ResetSearch();

    // get vertex corresponding to selected node
    var startVertex = (Vertex)startNode.Tag;

    // start breadth-first search in a new thread
    currentSearch = new Thread(() =>
        BreadthFirstSearch(startVertex));
    currentSearch.Start();
}

void BreadthFirstSearch(Vertex start)
{
    int order = 0;

    // enqueue first vertex and mark as visited
    var queue = new Queue();
    queue.Enqueue(start);
    start.Visited = true;
    start.SearchOrder = order++;

    // while there are vertices in queue
    while (queue.Count > 0)
    {
        var current = queue.Dequeue();

        // show dequeued node
        ShowBacktrack(current);

        // add its neighbours to queue
        foreach (var neighbor in current.Neighbors)
        {
            if (!neighbor.Visited)
            {
                queue.Enqueue(neighbor);
                neighbor.Visited = true;
                neighbor.SearchOrder = order++;

                // show queued node
                ShowProgress(neighbor);
            }
        }
    }

    SearchComplete();
}

If you run the application now and click one of the search buttons, you should see this screen showing the algorithm progress, with current back-track position represented by a red border:

graph search visualized in mindfusion diagram control for .NET

The code above uses MindFusion’s .NET API and can be used with Windows Forms, WPF, Silverlight and ASP.NET diagramming components. The Java API for Android and desktop Swing application will look similar, with setter method calls instead of property assignments.

You can download the trial version of any MindFusion.Diagramming component from this page.

Enjoy!

Database schema diagram

In this post we’ll show how to use TableNode objects to display tabular data, more specifically database schema information. A Visual Studio sample project containing the code from this post is available for download here:

DatabaseSchema.zip

To start, create a new Windows Forms application, and place a text field for connection string, a button and a DiagramView on the form. In the code-behind file, add following field to map table name to respective TableNode objects:

Dictionary<string, tablenode=""> tables = new Dictionary<string, tablenode="">();
</string,></string,>

Add a RectangleF that stores default size passed to CreateTableNode method:

RectangleF defaultSize = new RectangleF(0, 0, 30, 30);

Create a ReadTables method, which provided an SqlConnection, parses its schema information and creates diagram nodes:

void ReadTables(SqlConnection connection)
{
	// get table schema definitions from connection
	var schema = connection.GetSchema("Tables");
	foreach (DataRow row in schema.Rows)
	{
		// fetch table name
		var name = row["TABLE_NAME"].ToString();

		// create respective node
		var table = diagram.Factory.CreateTableNode(defaultSize);
		table.Caption = name;
		table.Shape = SimpleShape.RoundedRectangle;
		table.Brush = new MindFusion.Drawing.SolidBrush(Color.LightGray);

		// register node in dictionary for future foreign key reference
		tables[name.Replace(" ", "_")] = table;
		ReadFields(table, connection,
			row["TABLE_CATALOG"].ToString(), null, name);
	}

	ReadForeignKeys(connection);
}

The ReadFields method takes table node and name parameters and creates node cells that will show information for the column name and type of database tables:

void ReadFields(TableNode node,
	SqlConnection connection, string db, string owner, string tableName)
{
	// remove default cells
	node.RowCount = 0;

	// reserve one column for name and one for data type
	node.ColumnCount = 2;

	// read column definitions of specified table
	var schema = connection.GetSchema("Columns", new[] { db, owner, tableName });
	foreach (DataRow row in schema.Rows)
	{
		// add a new row to the node
		int r = node.AddRow();

		// set cells' text to the column name and type
		node[0, r].Text = row["COLUMN_NAME"].ToString();
		node[1, r].Text = row["DATA_TYPE"].ToString();

	}

	// make table cells big enough to show all text
	node.ResizeToFitText(false);
}

The ReadForeignKeys method creates DiagramLink connectors between table nodes to show the relationships between database tables:

void ReadForeignKeys(SqlConnection connection)
{
	var schema = connection.GetSchema("ForeignKeys");
	foreach (DataRow row in schema.Rows)
	{
		// read foreign key information
		string fkName = row["CONSTRAINT_NAME"].ToString();
		string tableName = row["TABLE_NAME"].ToString().Replace(" ", "_");
		string prefix = "FK_" + tableName + "_";
		if (fkName.StartsWith(prefix))
		{
			string targetName = fkName.Substring(prefix.Length);

			// get table nodes registered for specified names
			if (tables.ContainsKey(targetName) && tables.ContainsKey(tableName))
			{
				var table = tables[tableName];
				var targetTable = tables[targetName];

				// create a link between the nodes to show relationship
				diagram.Factory.CreateDiagramLink(table, targetTable);
			}
		}
	}
}

Finally handle the button’s click event to open specified connection and call ReadTables. Apply AnnealLayout to arrange the tables so that they do not overlap:

private void btnOpen_Click(object sender, System.EventArgs e)
{
	diagram.ClearAll();

	try
	{
		var connection = new SqlConnection(tbConnection.Text);
		connection.Open();

		// read schema and create corresponding diagram items
		ReadTables(connection);

		connection.Close();
	}
	catch (Exception exception)
	{
		MessageBox.Show(exception.Message);
		diagram.ClearAll();
	}

	// arrange the tables to remove overlaps
	var layout = new AnnealLayout();
	layout.SplitGraph = true;
	layout.Randomize = false;
	layout.MultipleGraphsPlacement = MultipleGraphsPlacement.MinimalArea;
	layout.Margins = new SizeF(10, 10);
	layout.Arrange(diagram);
}

If you run the project and open the Northwind sample database by Microsoft, you should see this diagram:

database schema layout

The code above uses MindFusion’s .NET API and can be used with Windows Forms, WPF, Silverlight and ASP.NET diagramming components. The Java API for Android and desktop Swing application will look similar, with setter method calls instead of property assignments.

You can download the trial version of any MindFusion.Diagramming component from this page.

Enjoy!

Combine layout algorithms

Apply TreeLayout twice to arrange a genealogy tree

In a series of posts we’ll explore ways to combine graph layout algorithms for various purposes, such as improving layout speed or achieving specific layout constraints.

In this example we’ll show how to apply two TreeLayout instances with different settings to arrange a genealogy tree. The genealogy tree is focused on specific person, with several levels of ancestors drawn above and descendants drawn below. A Visual Studio sample project containing the code from this post is available for download here:

GenealogyLayout.zip

As a start, let’s define a new node class that will draw a person’s photo and name inside a frame, along with their partners’. This will simplify our layout code since we won’t have to take care of keeping partner nodes close to each other:

class GenealogyNode : DiagramNode
{
	public List Partners { get; set; }

	public override void DrawLocal(IGraphics graphics, RenderOptions options)
	{
		float relationLinkLen = Bounds.Width / 7;
		int relations = Partners.Count - 1;
		float personViewWidth = Bounds.Width - relations * relationLinkLen;
		personViewWidth /= Partners.Count;

		var rect = GetLocalBounds();
		rect.Width = personViewWidth;
		for (int i = 0; i < Partners.Count; i++)
		{
			DrawPerson(Partners[i], graphics, rect);

			if (i < Partners.Count - 1)
			{
				float rx = rect.Right;
				float ry = rect.Y + 4 * rect.Height / 5;
				rect.X += personViewWidth + relationLinkLen;
				graphics.DrawLine(Pens.Gray, rx, ry, rect.X, ry);
			}
		}
	}

	public override void DrawShadowLocal(IGraphics graphics, RenderOptions options)
	{
	}

	void DrawPerson(Person person, IGraphics graphics, RectangleF rect)
	{
		const float labelHeight = 5;
		const float padding = 3;

		// draw name
		var labelRect = RectangleF.FromLTRB(
			rect.Left,
			rect.Bottom - labelHeight,
			rect.Right,
			rect.Bottom);

		graphics.DrawString(person.Name,
			EffectiveFont, Brushes.Black, labelRect,
			new StringFormat { Alignment = StringAlignment.Center });

		// draw image
		var imageRect = rect;
		imageRect.Height -= labelHeight + padding;

		Utilities.DrawImage(graphics, person.Image, imageRect, ImageAlign.Fit);

		// draw frame
		var frameColor = person.Gender == Gender.Female ?
			Color.Red : Color.BlueViolet;
		var framePen = new System.Drawing.Pen(frameColor, 0);
		graphics.DrawRectangle(framePen, rect);
		framePen.Dispose();
	}
}

Alternatively, we could draw a single person per node instead, placing partners’ nodes close to each other, grouping them using AttachTo method, and later running TreeLayout with its KeepGroupLayout property enabled.

Now to generate a sample tree, we’ll define recursive methods that will create specified number of ancestor pairs (GenerateAncestors) and create random number of descendants (GenerateDescendants):

void GenerateAncestors(GenealogyNode node, int levels)
{
	if (levels == 0)
		return;
	for (int i = 0; i < 2; i++)
	{
		var p = AddPair();
		var link = diagram.Factory.CreateDiagramLink(p, node);
		link.DestinationAnchor = i;
		link.OriginAnchor = 2;
		GenerateAncestors(p, levels - 1);
	}
}

void GenerateDescendants(GenealogyNode node, int levels)
{
	if (levels == 0)
		return;
	int children = random.Next(1, 5);
	for (int i = 0; i < children; i++)
	{
		int r = random.Next(0, 3);
		if (r == 2)
		{
			var p = AddPair();
			var link = diagram.Factory.CreateDiagramLink(node, p);
			link.OriginAnchor = 2;
			link.DestinationAnchor = 0;
			GenerateDescendants(p, levels - 1);
		}
		else if (r == 1)
		{
			var p = new Person { Name = "daughter", Gender = Gender.Female, Image = fImage };
			var childNode = AddNode(p);
			diagram.Factory.CreateDiagramLink(node, childNode);
		}
		else if (r == 0)
		{
			var p = new Person { Name = "son", Gender = Gender.Male, Image = mImage };
			var childNode = AddNode(p);
			diagram.Factory.CreateDiagramLink(node, childNode);
		}
	}
}

GenealogyNode AddPair()
{
	var p1 = new Person { Name = "mom", Gender = Gender.Female, Image = fImage };
	var p2 = new Person { Name = "dad", Gender = Gender.Male, Image = mImage };
	return AddNode(p1, p2);
}

GenealogyNode AddNode(Person p)
{
	var bounds = new RectangleF(0, 0, 30, 40);

	var node = new GenealogyNode();
	node.Bounds = bounds;
	node.Partners = new List { p };
	node.AnchorPattern = AnchorPattern.TopInBottomOut;
	diagram.Nodes.Add(node);
	return node;
}

GenealogyNode AddNode(Person p1, Person p2)
{
	var bounds = new RectangleF(0, 0, 70, 40);

	var node = new GenealogyNode();
	node.Bounds = bounds;
	node.Partners = new List { p1, p2 };
	node.AnchorPattern = PairPattern;
	diagram.Nodes.Add(node);
	return node;
}

Finally we run TreeLayout twice with specified root node, arranging ancestor nodes above the root and descendant nodes below it, creating the genealogy drawing shown below:

private void GenealogyForm_Load(object sender, EventArgs e)
{
	var root = AddPair();
	GenerateAncestors(root, 2);
	GenerateDescendants(root, 3);

	var l1 = new TreeLayout();
	l1.ReversedLinks = true;
	l1.Direction = TreeLayoutDirections.BottomToTop;
	l1.Anchoring = Anchoring.Keep;
	l1.LevelDistance *= 2;
	l1.NodeDistance *= 1.4f;
	l1.LinkStyle = TreeLayoutLinkType.Cascading3;
	l1.Arrange(diagram);

	var l2 = new TreeLayout();
	l2.Root = root;
	l2.KeepRootPosition = true;
	l2.Anchoring = Anchoring.Keep;
	l2.LevelDistance *= 2;
	l2.NodeDistance *= 1.4f;
	l2.LinkStyle = TreeLayoutLinkType.Cascading3;
	l2.Arrange(diagram);

	diagram.ResizeToFitItems(5);
	//diagramView.ZoomToFit();
}

genealogy tree layout

The code above uses MindFusion’s .NET API and can be used with Windows Forms, WPF, Silverlight and ASP.NET diagramming components. The Java API for Android and desktop Swing application will look similar, with setter method calls instead of property assignments.

You can download the trial version of any MindFusion.Diagramming component from this page.

Enjoy!

Combine layout algorithms

Use OrthogonalLayout to generate initial placement for SpringLayout

In a series of posts we’ll explore ways to combine graph layout algorithms for various purposes, such as improving layout speed or achieving specific layout constraints.

In this example we’ll show how to apply OrthogonalLayout as preprocessing step for SpringLayout used to minimize edge crossings. A problem with force-directed layout algorithms such as SpringLayout is that they can reach equilibrium of the simulated forces while there are link crossings present. However if the simulation starts from an initial layout that has minimal number of crossing, it will tend to reach balance without introducing new crossings. So we can use any of the layout algorithms used for arranging planar graphs (OrthogonalLayout, TriangularLayout, CascadingLayout) to create the initial configuration for SpringLayout.

OrthogonalLayout is designed to create planar drawings of graphs (having no crossing links at all if possible) where edge segments are either horizontal or vertical. For some types of diagrams, such as flowcharts, you might use OrthogonalLayout as is. However in many cases you might prefer SpringLayout, e.g. in order to achieve aesthetic criteria like uniform edge lengths, or to conform to accepted drawing conventions such as the one used to present state machines. So when you know your graphs are planar or close to planar, you can run OrthogonalLayout as pre-processing step, and then run the physical-force simulation using SpringLayout to achieve straight-line uniform length drawings:

void ApplySpringLayout(bool preArrange)
{
    if (preArrange)
    {
        var tl = new OrthogonalLayout();
        tl.Arrange(diagram);
    }

    var sl = new SpringLayout();
    sl.Randomize = false;
    sl.MinimizeCrossings = true;
    sl.IterationCount = 50;
    sl.Arrange(diagram);

    diagramView.ZoomToFit();
}

Here are several examples of the method results when called respectively with false (on the left side) and with true (on the right side of image). Note that for such small graphs SpringLayout will probably remove the crossings if left to run for more iterations, but in the general case and with larger graphs that’s not guaranteed.

1

2

3

The code above uses MindFusion’s .NET API and can be used with Windows Forms, WPF, Silverlight and ASP.NET diagramming components. The Java API for Android and desktop Swing application will look similar, with setter method calls instead of property assignments.

Enjoy!

Spreadsheet for WinForms, V1.2

MindFusion has released a new version of Spreadsheet for WinForms with the following new features:

Formatted text support
You can import, display and export texts with mixed formatting. You can create formatted texts programmatically by assigning a FormattedText instance to the Data property of cells. Currently the control supports: font name and size, bold, italic, underlined, strikeout, and text color.

New Functions
At your disposal are 22 new functions, which have been added to the core calculation engine, including a full set of database functions, such as DGET, DSUM, and so on. Check Function Reference for additional information.

Improved in-place editing
Now when you type a new formula, pressing the arrow keys will result in navigation within the worksheet (instead of adjusting the caret position) and the selected cell or cell ranges will be inserted as a reference in the currently edited formula.

Sport events schedule

Sport events schedule

You can download the trial version from the link below:

Download MindFusion.Spreadsheet for WinForms 1.2, Trial Version

If you require technical support, you can post a message at the forum, send us an e-mail at support@mindfusion.eu. or use the help desk. MindFusion takes special effort in providing fast and detailed answers to all inquiries that we receive.

About MindFusion.Spreadsheet for WinForms: An easy-to-use programming component suitable for building all types of spreadsheets fast and easy. The tool supports formulas, tool-tips, cell annotations, cell spanning, scrolling and many more. You can add charts and images as well use the flexible style system to design the perfect spreadsheet. The component supports full undo and redo as well copy and paste from Windows clipboard.
You can import spreadsheet data from CSV, XLSX or ODS files and export the final spreadsheet in a number of formats – as images, PDF or CSV, XLSX or ODS files. Various auxiliary forms help you quickly adjust the data and appearance of your spreadsheet. Read more about the features of the component here or check the license prices at the buy page.

Spreadsheet for WinForms is part of MindFusion Pack for WinForms, which offers other useful components that are of great use when you build any type of WinForms application – from a diagramming library to map control to gauges: check them here.