Combine layout algorithms

Apply FractalLayout and SpringLayout to generate a tag cloud

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 topic we’ll show how to create a tag cloud using FractalLayout and SpringLayout algorithms from MindFusion diagramming API. You can download the complete project here:

TagCloud.zip

The sample code will show several features of the Diagram control:

  • FractalLayout
  • SpringLayout
  • custom node placement
  • text-only nodes

We assume words frequencies are already counted and listed as “word: frequency” entries in a sorted file. The example uses tags extracted from the contents of Wikipedia’s Tag cloud page. Let’s start by parsing the file and creating a node for each word. We’ll assign the word frequency to the Weight property of nodes for future reference. Weight is also used by some layout algorithms (such as TreeMapLayout for creating tree maps) that could visually represent word frequencies as well:

RectangleF defaultBounds = new RectangleF(0, 0, 10, 20);
ShapeNode root;
		
private void MainForm_Load(object sender, EventArgs e)
{
	// read the tags file
	var reader = new StreamReader("words.txt");
	string line;
	while ((line = reader.ReadLine()) != null)
	{
		// each line contains "word: frequency" entries
		var parts = line.Split(new[] { ':' });
		var word = parts[0];
		var frequency = int.Parse(parts[1]);

		// create a diagram node for each word in the file
		var node = diagram.Factory.CreateShapeNode(defaultBounds);
		node.Weight = frequency;
		node.Text = word;

		// set font size corresponding to frequency
		node.Font = new Font(
			"Arial",
			8 + (float)Math.Log(node.Weight, 1.15),
			GraphicsUnit.Point);

		// resize the node to fit text
		var size = TextRenderer.MeasureText(node.Text, node.Font);
		node.Resize(
			2 + (float)MeasureUnit.Pixel.Convert(size.Width, diagram.MeasureUnit, null),
			2 + (float)MeasureUnit.Pixel.Convert(size.Height, diagram.MeasureUnit, null));

		// show only text, hide geometry
		node.Transparent = true;
	}
}

Next, let’s build a tree that will distribute largest nodes roughly uniformly when arranged by FractalLayout, where larger parent nodes are circled by their smaller child nodes:

while ((line = reader.ReadLine()) != null)
{
	// ...
	if (diagram.Nodes.Count == 1)
	{
		// save reference to the first node
		root = node;
	}
	else
	{
		// build a tree where each node has up to six children
		diagram.Factory.CreateDiagramLink(
			diagram.Nodes[diagram.Nodes.Count / 6],
			node);
	}
}

// use FractalLayout for initial placement. if the file entries are sorted, each circular
// branch will contain larger parent node centered between its smaller children
new FractalLayout().Arrange(diagram);

If you run the application now, you should see the following layout:

a tree arranged using fractal layout

FractalLayout allocates some space for links, and we’ll reclaim it by deleting the links and compressing the initial layout of nodes:

// remove the links
while (diagram.Links.Count > 0)
	diagram.Links.RemoveAt(diagram.Links.Count - 1);

// pull all nodes towards the root to eliminate empty space that was occupied by links
var center = root.GetCenter();
foreach (var node in diagram.Nodes.Where(n => n != root))
{
	var relativePos = new Vector(center, node.GetCenter());
	var newPos = center + relativePos / 10;
	node.Move(
		newPos.X - node.Bounds.Width / 2,
		newPos.Y - node.Bounds.Height / 2);
}

Now apply SpringLayout to make distances between closely placed nodes more uniform, running only a few iterations to complete faster:

// run SpringLayout to distribute nodes more uniformly
var sl = new SpringLayout();
sl.Randomize = false;
sl.SplitGraph = false;
sl.NodeDistance = 3;
sl.IterationCount = 40;
sl.Arrange(diagram);

Run the following method to remove any overlaps remaining after SpringLayout. RemoveOverlaps works by starting from specified node and offsetting any nodes that overlap it, continuing by spiraling away while processing other nodes. This method could also be useful in an interactive application if you want to disperse overlapping nodes introduced by the user when they move a node:

void RemoveOverlaps(DiagramNode modifiedNode, float minDist)
{
	var queue = new Queue();
	queue.Enqueue(modifiedNode);

	while (queue.Count > 0)
	{
		var node = queue.Dequeue();
		var nodeCenter = node.GetCenter();
		var overlaps = FindOverlaps(node, minDist);
		foreach (var overlap in overlaps)
		{
			var ovrCenter = overlap.GetCenter();
			var ovrBounds = overlap.Bounds;
			var dx = ovrCenter.X - nodeCenter.X;
			var dy = ovrCenter.Y - nodeCenter.Y;
			if (Math.Abs(dx) > Math.Abs(dy))
			{
				// offset horizontally
				if (dx < 0)
					ovrBounds.X = node.Bounds.Left - ovrBounds.Width - minDist;
				else
					ovrBounds.X = node.Bounds.Right + minDist;
			}
			else
			{
				// offset vertically
				if (dy < 0)
					ovrBounds.Y = node.Bounds.Top - ovrBounds.Height - minDist;
				else
					ovrBounds.Y = node.Bounds.Bottom + minDist;
			}

			// shifting the node might introduce new overlaps, continue processing
			overlap.Bounds = ovrBounds;
			queue.Enqueue(overlap);
		}
	}
}

List FindOverlaps(DiagramNode modifiedNode, float minDist)
{
	var bounds = modifiedNode.Bounds;
	bounds.Inflate(minDist - 1, minDist - 1);

	var overlaps = new List();
	foreach (var node in diagram.Nodes)
	{
		if (modifiedNode == node)
			continue;
		if (bounds.IntersectsWith(node.Bounds))
			overlaps.Add(node);
	}
	return overlaps;
} 

Finally run a few more iterations of SpringLayout to equalize distances again and zoom the diagram to show the whole tag cloud:

// remove any remaining overlaps
RemoveOverlaps(root, 0.1f);
sl.Arrange(diagram);

// show everything inside view
diagram.ResizeToFitItems(5);
diagramView.ZoomToFit();

If you run the application now, you should see the following image:

tag cloud generated using MindFusion diagram control

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!

Create a Dialogue Editor using MindFusion Diagram

In this post we’ll demonstrate how to create a graphical interface for editing dialogues using MindFusion diagramming API. The sample could be used as a module in different kinds of applications, such as software for creating and conducting surveys, editing interactive voice response systems, designing NPC dialogues in game development tools. You can download the complete project here:

DialogueEditor.zip

The sample code will show several features of Diagram control:

  • TableNode API
  • in-place edit
  • work with groups
  • graph traversal

Questions or IVR messages and their possible answers / responses will be displayed respectively in caption area and cells of TableNode objects. Helper nodes at table bottom will let users add or remove rows. DiagramLink objects connecting table rows to other tables will define the dialogue flow, i.e. what next question / message to display after user selects option from current message.

Let’s start by creating a new .NET Windows Forms project. If you have installed MindFusion diagram control and selected toolbox integration from setup wizard screen, you should now see a Diagram component and DiagramView control in the toolbox. If they are not available, right click and select Choose Items, navigate to installation folder and select the mindfusion.diagramming and mindfusion.diagramming.winforms assemblies. Now drag a Diagram component to the form, and set its name to “diagram”. This automatically adds a diagramming.dll reference to the project. Drag a DiagramView (which add diagramming.winforms reference) and name it diagramView. Set its Diagram property to “diagram”, selecting it from the drop-down in property grid.

We will allow creation only of tables and links, so let’s set DiagramView.Behavior property to LinkTables. Now if users draw on the diagram canvas, the component will create TableNode if the mouse pointer is over unoccupied part of the diagram, or a DiagramLink if the mouse points a table. If we wanted to support more type of nodes, we could add their prototypes to a NodeListView instance and let users create new instances via drag-and-drop.

Add the following fields and constructor code to set up appearance and behavior of diagram elements.

public MainForm()
{
	InitializeComponent();

	// set up initial table appearance
	diagram.TableNodeStyle.Brush = new MindFusion.Drawing.SolidBrush(Color.LightGray);
	diagram.TableRowCount = 1;
	diagram.TableColumnCount = 1;
	diagram.TableCaption = "question";
	diagram.NodeEffects.Add(new GlassEffect());

	// highlight a row when clicked
	diagram.AutoHighlightRows = true;

	// find link routes automatically
	diagram.RouteLinks = true;

	// allow edit texts by double click
	diagramView.AllowInplaceEdit = true;

	// row anchor points
	rightOutput = new AnchorPattern("Right");
	rightOutput.Points.Add(new AnchorPoint(100, 50, false, true));

	// table anchor points
	input = new AnchorPattern("Input");
	input.Points.Add(new AnchorPoint(50, 0, true, false));
	input.Points.Add(new AnchorPoint(50, 100, true, true));
}

AnchorPattern rightOutput;
AnchorPattern input;

Select the diagram component in form editor and double click its NodeCreated event to add event handler. NodeCreated is raised when the user draws a new node. Add following code to associate a question / IVR message with the table, and initialize some default texts. The BeginEdit method automatically opens in-place editor to let user edit caption text immediately after drawing. We also create + and – ShapeNodes that will act as button widgets attached to the table’s bottom-right corner.

private void OnNodeCreated(object sender, MindFusion.Diagramming.NodeEventArgs e)
{
	var table = e.Node as TableNode;
	if (table != null)
	{
		int tableId = (1 + diagram.Nodes.Count / 3);
		table.Id = tableId;
		table.RowAnchorPattern = rightOutput;
		table.AnchorPattern = input;
		table.ConnectionStyle = TableConnectionStyle.Both;
		table[0, 0].Text = "option 1";
		table.Caption = "question " + tableId;
		diagramView.BeginEdit(table);

		// create + button for adding new rows
		var r = table.Bounds;
		var p = new PointF(r.Right - 14, r.Bottom - 8);
		var s = new SizeF(6, 6);
		var plus = diagram.Factory.CreateShapeNode(p, s, Shapes.Cross);
		plus.Brush = new MindFusion.Drawing.SolidBrush(Color.Green);
		plus.AttachTo(table, AttachToNode.BottomRight);
		plus.Tag = "+";

		// create - button for deleting selected row
		p.X += 7;
		p.Y += 2;
		s.Height -= 4;
		var minus = diagram.Factory.CreateShapeNode(p, s, Shapes.Rectangle);
		minus.Brush = new MindFusion.Drawing.SolidBrush(Color.Red);
		minus.AttachTo(table, AttachToNode.BottomRight);
		minus.Tag = "-";

		plus.Locked = minus.Locked = true;
		table.SubordinateGroup.AutoDeleteItems = true;
	}
}

Now add a NodeClicked handler that adds or deletes rows. If the + button is clicked, the code inserts a new row before current highlighted row. If the – button is clicked, the handler deletes current highlighted row.

private void OnNodeClicked(object sender, NodeEventArgs e)
{
	if ("+".Equals(e.Node.Tag))
	{
		var table = (TableNode)e.Node.MasterGroup.MainItem;
		if (table.HighlightedRow == -1)
			table.RowCount++;
		else
			table.InsertRow(table.HighlightedRow);
	}

	if ("-".Equals(e.Node.Tag))
	{
		var table = (TableNode)e.Node.MasterGroup.MainItem;
		if (table.HighlightedRow != -1)
			table.DeleteRow(table.HighlightedRow);
	}
}

Finally, lets create export function that will traverse the dialogue graph and export it to a custom-format XML file, which could then be passed on to a system processing the dialogues, such as IVR service.

private void btnExport_Click(object sender, EventArgs e)
{
	var fileDlg = new SaveFileDialog();
	if (fileDlg.ShowDialog() == DialogResult.OK)
	{
		var doc = new XmlDocument();
		var root = doc.CreateElement("Dialogue");
		doc.AppendChild(root);

		foreach (var node in diagram.Nodes)
		{
			var table = node as TableNode;
			if (table != null)
			{
				var questionElement = doc.CreateElement("Question");
				root.AppendChild(questionElement);

				int id = (int)table.Id;
				questionElement.SetAttribute("Id", table.Id.ToString());
				questionElement.SetAttribute("Text", table.Caption);

				for (int r = 0; r < table.Rows.Count; r++)
				{
					string answer = table[0, r].Text;
					var answerElement = doc.CreateElement("Answer");
					questionElement.AppendChild(answerElement);
					answerElement.SetAttribute("Text", answer);
					if (table.Rows[r].OutgoingLinks.Count > 0)
					{
						var link = table.Rows[r].OutgoingLinks[0];
						var nextQuestion = (TableNode)link.Destination;
						answerElement.SetAttribute("Text", answer);
						answerElement.SetAttribute("NextId", nextQuestion.Id.ToString());
					}
				}
			}
		}

		doc.Save(fileDlg.FileName);
	}
}

If you run the application now and draw several tables and links, you should see a similar screen:

dialogue editor created with 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!

Combination Chart in Android

This post is a step-by-step tutorial in how to create a combination chart in android with the Charting for Android library.

I. Project configuration

Let’s create a new project. In Eclipse, we choose File -> New -> Android Application Project. We write “CombinationChart” as an application name. The package is called com.mindfusion.combinationchart. The other settings remain unchanged.

II. Adding the jar file.

With project created, it’s time to add the libraries. Copy the droidchart.jar from the libs directory of the sample project (download file here) to the libs directory of your project. Then right-click on your project and choose Properties -> Java Build Path -> Libraries -> Add JARs. Navigate to the libs folder and add the droidchart.jar.

Adding a JAR library to an Android application project

Adding a JAR library to an Android application project

III. Declaring the chart

Time to declare the chart in the layout of the application. We build a simple application, where the chart will be the only thing that shows. So, we edit the activity_main.xml file, which is found in res -> layout folder in the project tree for the CombinationChart application.

We change the layout to Linear and we introduce a new xml node – chart. The chart node refers to a class found in the com.mindfusion.charting namespace.

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:chart="http://schemas.android.com/apk/lib/com.mindfusion.charting"
...

Then we declare the chart:

<com.mindfusion.charting.AxesChart
android:id=”@+id/combi_chart”
android:layout_width=”fill_parent”
android:layout_height=”wrap_content”
chart:gridType=”horizontal”
chart:titleOffset=”40dp”
chart:titleHeight=”40dp”
chart:labelHeight=”12dp”
tools:context=”.MainActivity” />

We name it combi_chart. This is important because we’ll use the name to retrieve the chart object in the next step.

IV. General chart settings.

In this step we’ll set the general chart settings. First, we get the chart object, which is declared in the layour (see previous step).


private AxesChart chart;
....
chart = (AxesChart)findViewById(R.id.combi_chart);

Then we set the title and the offset of the title e.g. the space between the title and the plot are for the chart. We also set the height of the font for the title labels and the other labels at the chart.


chart.setTitle("Visitors in Paradise Hotels");
chart.setTitleOffset(50f);
chart.setTitleHeight(30f);
chart.setLabelHeight(20f);

V. The grid.

Our chart has a crossed grid with light gray grid stripes. This is set with the following code:


ArrayList gridStrokes = new ArrayList();
gridStrokes.add(Color.rgb(207, 207, 207));
chart.setGridStrokeColors(gridStrokes);


chart.setGridType(GridType.Crossed);

VI. The axes.

The X-axis has 10 intervals. Each division has its own label. We set the label type to custom text, specify the labels and customize the min and max numbers to be shown:


chart.xAxisSettings.setMin(0f);
chart.xAxisSettings.setMax(10f);
chart.xAxisSettings.setInterval(1f);
chart.xAxisSettings.setLabelType(AxisLabelType.Custom);


ArrayList xLabels = new ArrayList();
Collections.addAll(xLabels, "2005", "2006", "2007", "2008", "2009", "2010", "2011", "2012", "2013", "2014");
chart.xAxisSettings.setLabels(xLabels);

The Y-axis has no custom labels, it just shows the value intervals. But it has a title. Here is how we set it:


chart.yAxisSettings.setMin(0f);
chart.yAxisSettings.setMax(30f);
chart.yAxisSettings.setInterval(10f);
chart.yAxisSettings.setLabelType(AxisLabelType.Scale);
chart.yAxisSettings.setTitle("in thousands");

VII. The bar series.

The first series is a bar series. We create a new instance of the BarSeries class and add 10 x and y float numbers, which will be used to calculate the size and location of the bars:


BarSeries series1 = new BarSeries();

ArrayList xData = new ArrayList();
for(int i = 0; i < 10; i++)
xData.add((float)i);
series1.setXData(xData);


ArrayList yData1 = new ArrayList();
Collections.addAll(yData1, 15f, 17f, 18f, 19f, 18.4f, 16.4f, 12f, 17f, 18.7f, 19.1f );
series1.setYData(yData1);

The next thing to do is to specify the colors for the bars and their outlining. The library has the FillColors and StrokeColors property, which we use:


ArrayList fillColors1 = new ArrayList();
fillColors1.add(Color.rgb(174, 200, 68));
series1.setFillColors(fillColors1);


ArrayList strokeColors1 = new ArrayList();
strokeColors1.add(Color.rgb(115, 133, 45));
series1.setStrokeColors(strokeColors1);

Let’s not forget to add the ready series to the collection of series.


chart.addSeries(series1);

VIII. The line series with scatters.

The line series is an instance of the LineSeries class, where we set the ScatterType and LineType properties:


LineSeries series2 = new LineSeries();
series2.setScatterType(ScatterType.Circle);
series2.setLineType(LineType.Line);
series2.setScatterSize(20f);
...
chart.addSeries(series2);

The ScatterFillColors and ScatterStrokeColors are used for setting the colors of the scatters. The properties for the line are the same as with the bar series: StrokeColors.

IX The area series.

The area series has a different line type than the scatter series. We don’t set the scatter type here since its set to “None” by default.

The data in both line series is set in the same way as in the bar series and we don’t cite it again.


LineSeries series3 = new LineSeries();
series3.setLineType(LineType.Area);
...
chart.addSeries(series3);

Here is the final chart:

An elegant combination chart for Android mobile devices.

An elegant combination chart for Android mobile devices.

The sample is available for download from here:

Download Android Combination Chart Sample

Read more about MindFusion Charting for Android library here.

WinForms Spreadsheet Auto-Filtering

In this post we will discuss how to use the auto-filtering feature in MindFusion.Spreadsheet for WinForms.

Introduction

We start off by creating a new Windows Forms Application in Visual Studio, adding a WorkbookView control to the main form and adding the Northwind database (nwind.mdb) as a data source. For simplicity we only add the Orders table. After compiling the application we can add the DataSource and the OrdersTableAdapter as components to the main form.

Loading the data

We traverse the rows in the data source and populate the spreadsheet by assigning the data to the Data property of the respective worksheet cells. The first cell in each column is set to the name of the corresponding database field. Finally, the columns are resized to fit their contents through the ResizeColumnsToFit method of the view. The complete code of the data loading can be found in the LoadData method.

Turning auto-filtering on

Auto-filtering is enabled by calling the AutoFilter method of the CellRange class. If the CellRange represents a range of cells, then auto-filtering is applied to that range. If the CellRange represents a single cell, then auto-filtering is applied to the rectangular area of data cells, which includes this cell. In this application, we enable auto-filtering on the loaded data, by calling AutoFilter on cell A1. In addition, we apply auto-filtering criteria on the 6-th column (ShipVia) by calling the AutoFilter overload.

workbook1.Worksheets[0].CellRanges["A1"].AutoFilter();
workbook1.Worksheets[0].CellRanges["A1"].AutoFilter(
    6, "<>1", AutoFilterOperator.Or, null, true);

The following image shows the running sample:
spreadsheet-autofilter

The source code is available for download from here:
https://mindfusion.eu/_samples/SpreadsheetAutoFilter.zip

MindFusion.Spreadsheet for WinForms can be downloaded from here:
MindFusion.Spreadsheet for WinForms

About MindFusion.Spreadsheet for WinForms: A powerful .net spreadsheet component with great capabilities for editing, styling and formatting large amounts of data.

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!