Nested DataRanges in MindFusion Report Application

In this tutorial we are going to build a report with nested data ranges. In our case, we are going to retrieve all categories from the sample Norhtwind database. For each category we will get all products in it, which is the nested data range.

I. Preparing the Project

We create a new WinForms project and add a new DataSet from the Nortwind database. We choose all fields from the Categories and Products tables. Then we add a new item from the menu “Project -> Add -> New Item -> MindFusion Report” from the dialog that appears.

Adding the MindFusion Report item.

Adding the MindFusion Report item.

Drag and drop the nwindDataSet, CategoriesTableAdapter and ProductsTableAdapter on the report form. Finally, in the code behind fill the two adapters:

 productsTableAdapter1.Fill(nwindDataSet1.Products);
 categoriesTableAdapter1.Fill(nwindDataSet1.Categories); 

II. The Categories DataRange

We create the first data range by right clicking on the newly created report. There we see “Create Data Range from Data Source”. We choose the categories table and two fields – CategoryName and Picture. When the data range is generated we resize the picture to make it bigger.

Create DataRange context menu.

Create DataRange context menu.

III. Running the Report

We would use MindFusion ReportViewer to preview what we’ve done so far. We drag it from the Toolbox and place it on the form, which shows when the application runs. We compile the project and see that the Report1 class that we’ve created appears in the Toolbox, under the Data tab. This means we can create instances of our report just by drag and drop. We drag the Report1 icon and drop it on Form1. We have a report11 instance, which we assign to the Report property of the ReportView in the property grid.

Dragging the Report1 item.

Dragging the Report1 item.

Finally, we run the report:

report11.Run();

IV. The Nested DataRange

It’s time to create the second DataRange. We right-click the Report1 form and choose again “Create DataRange from Data Source.” This time we choose the Products table and we choose ProductName, UnitsInStock and UnitPrice fields. This time we check the “Generate Header” checkbox at the bottom. The second DataRange is ready. Nesting it is very easy. We resize the first DataRange to make it wider and just drag and drop the second DataRange in it. What is important is to set the MasterDetailRelation property. It must be the name of the relation between the two tables that provide data for the two DataRange-s. We can see it by clicking on the nwindDataSet -> “Edit DataSet with Designer. There we click on the relation between or two tables and see it is called “CategoriesProducts”. We place this name as a value to the MasterDetailRelation property.

Master detail relationship between the Products and Categories table.

Master detail relationship between the Products and Categories table.

V. Run the report

We run the report and see that everything is in place: the categories are listed with their picture, each category lists all its products.

VI. Style Adjustments

Finally let’s add some appearance optimizations that will make the report look better and thus be easier to read. First, we make the background of the Category label darker.

Then we make the product lines with alternating colors. This is done in the property grid for the dataRange2 object -> AlternatingBackground property.

We add a light gray border to the first data range with the Border property editor and we add a bottom margin of 30 mm. Here is the final look of the report:

Nested DataRange-s: the final report.

Nested DataRange-s: the final report.

You can download the sample from this link:

Download Nested DataRanges MindFusion Reporting Sample

More about MindFusion Reporting for WinForms component can be found here.

Create a musical score writer using MindFusion diagram component.

In this example we’ll show how to use various features of MindFusion.Diagramming API to create a musical score editor:

Custom node types
We’ll create a StaffNode class to represent the staff, and NoteNode class to represent a musical note.

Grouping
NoteNodes will be attached to the StaffNode they were dropped onto (or nearby). If users move the staff around, the notes from the group will follow it.

Custom drawing logic
We’ll show how to draw custom graphics by overriding DrawLocal method of base DiagramNode class.

Using SVG images
We’ll show how to load an SVG image (for the G clef) and draw it as part of staff graphics.

NodeListView control
NodeListView contains prototypical node instances whose clones are added to the diagram using drag-and-drop operations. We’ll add a staff and several notes to the list to let users drag them to the score diagram.

The completed sample project can be downloaded from this link:
ScoreWriter.zip

Let’s start by defining StaffNode class to draw staves in the score diagram, and implement its Draw methods to draw five lines:

public class StaffNode : DiagramNode
{
	public StaffNode()
	{
		var rect = Bounds;
		rect.Width = 200;
		SetBounds(rect, false, false);

		// disable vertical resize
		EnabledHandles =
			AdjustmentHandles.ResizeMiddleLeft |
			AdjustmentHandles.Move |
			AdjustmentHandles.ResizeMiddleRight;
	}

	public StaffNode(StaffNode prototype) : base(prototype)
	{
	}

	public override void DrawLocal(IGraphics graphics, RenderOptions options)
	{
		base.DrawLocal(graphics, options);

		for (int i = 0; i < 5; i++)
		{
			float y = i * Bounds.Height / 4;
			using (var pen = EffectivePen.CreateGdiPen())
				graphics.DrawLine(pen, 0, y, Bounds.Width, y);
		}
	}

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

Next, load an SVG drawing representing G clef and draw it at appropriate position. We’ll also override GetRepaintRect method to accommodate for parts of the clef that are drawn outside the staff’s boundaries:

static SvgContent gClef;

static StaffNode()
{
	gClef = new SvgContent();
	gClef.Parse("GClef.svg");
}

public override void DrawLocal(IGraphics graphics, RenderOptions options)
{
	// ...

	var rect = GetLocalBounds();
	rect.Inflate(0, 8);
	rect.X = 2;
	rect.Width = 14;
	gClef.Draw(graphics, rect);
}

public override RectangleF GetRepaintRect(bool includeConnected)
{
	var rect = base.GetRepaintRect(includeConnected);
	rect.Inflate(0, 8);
	return rect;
}

Create an initial StaffNode instance from Form.Load event:

var initialStaff = new StaffNode();
initialStaff.Move(10, 10);
diagram.Nodes.Add(initialStaff);

If you run the project now, you should see the following diagram:
score writer diagram in c#

Next, define the Duration enumeration and NoteNode class to represent musical notes of various durations:

enum Duration
{
	Whole,
	Half,
	Quarter,
	Eighth,
	Sixteenth
}

class NoteNode : DiagramNode
{
	public NoteNode()
	{
		Bounds = new RectangleF(0, 0, 6, 6);
		Duration = Duration.Whole;
	}

	public NoteNode(Duration duration)
	{
		Bounds = new RectangleF(0, 0, 6, 6);
		Duration = duration;
	}

	public Duration Duration { get; set; }

	int position = 0;
}

Implement NoteNode.Draw methods as follows:

public override void DrawLocal(IGraphics graphics, RenderOptions options)
{
	base.DrawLocal(graphics, options);

	var cx = Bounds.Width / 2;
	var cy = Bounds.Height / 2;

	var gs = graphics.Save();
	graphics.TranslateTransform(cx, cy);
	graphics.RotateTransform(-10);
	graphics.TranslateTransform(-cx, -cy);

	var bounds = GetLocalBounds();
	bounds.Inflate(0, -bounds.Width / 10);
	var path = new GraphicsPath();
	path.AddEllipse(bounds);

	if (Duration == Duration.Whole || Duration == Duration.Half)
	{
		bounds.Inflate(-bounds.Width / 8, -bounds.Width / 6);
		path.AddEllipse(bounds);
	}
	graphics.FillPath(Brushes.Black, path);

	graphics.Restore(gs);

	if (position < -1 || position > 8)
	{
		// draw ledger lines if above or below staff
		var pen = EffectivePen.CreateGdiPen();
		var staff = (StaffNode)MasterGroup.MainItem;
		var yoff = staff.Bounds.Y - Bounds.Y;
		int i1 = position < -1 ? position : 9;
		int i2 = position < -1 ? -2 : position;
		for (int i = i1; i <= i2; i++)
		{
			if (i % 2 != 0)
				continue;
			var y = yoff + i * staff.Bounds.Height / 8;
			graphics.DrawLine(pen, -2, y, Bounds.Width + 2, y);
		}
		pen.Dispose();
	}

	if (Duration != Duration.Whole)
	{
		// draw stem
		float x = Bounds.Width;
		float y = Bounds.Height / 2;
		var pen = new System.Drawing.Pen(Color.Black, 0.5f);
		graphics.DrawLine(pen,
				            x - pen.Width / 2, y,
				            x - pen.Width / 2, y - Bounds.Height * 2);
		pen.Dispose();
	}

	if (Duration == Duration.Eighth || Duration == Duration.Sixteenth)
	{
		DrawFlag(graphics,
				    bounds.Width,
				    bounds.Height / 2 - bounds.Height * 2,
				    bounds.Width + 1,
				    bounds.Height);
	}

	if (Duration == Duration.Sixteenth)
	{
		DrawFlag(graphics,
				    bounds.Width,
				    bounds.Height - bounds.Height * 2,
				    bounds.Width + 1,
				    bounds.Height);
	}
}

void DrawFlag(IGraphics graphics, float x, float y, float w, float h)
{
	float sh = h / 2;
	float sw = w / 3;

	var pen = new System.Drawing.Pen(Color.Black, 0.5f);
	x -= pen.Width / 2;
	graphics.DrawBezier(pen,
			            x, y,
			            x, y + sh,
			            x + sw * 1.2f, y + 2 * sh,
			            x + sw, y + 3 * sh);
	pen.Dispose();
}

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

public override RectangleF GetRepaintRect(bool includeConnected)
{
	var r = Bounds;
	r.Y -= r.Height * 2;
	r.Height *= 3;
	r.Width *= 2;
	return r;
}

Now, drag a NodeListView to the form and populate it from Load handler:

nodeListView.AddNode(new StaffNode());

nodeListView.DefaultNodeSize = new SizeF(6, 6);
nodeListView.AddNode(new NoteNode(Duration.Whole));
nodeListView.AddNode(new NoteNode(Duration.Half));
nodeListView.AddNode(new NoteNode(Duration.Quarter));
nodeListView.AddNode(new NoteNode(Duration.Eighth));
nodeListView.AddNode(new NoteNode(Duration.Sixteenth));

Drag and drop will not work just yet. First, we must enable the DiagramView.AllowDrop property to accept drag-and-drop events. Next, the custom classes must implement a copy constructor and serialization methods to be able to instantiate them through OLE drag events:

public NoteNode(NoteNode prototype) : base(prototype)
{
	Duration = prototype.Duration;
}

protected override void SaveTo(System.IO.BinaryWriter writer, PersistContext context)
{
	base.SaveTo(writer, context);
	context.Writer.Write((int)Duration);
}

protected override void LoadFrom(System.IO.BinaryReader reader, PersistContext context)
{
	base.LoadFrom(reader, context);
	Duration = (Duration)context.Reader.ReadInt32();
}

As a final touch for this example, let’s implement aligning notes to staves’ lines and spaces. First lets declare a helper method that returns the nearest StaffNode at specified location in diagram:

static class DiagramExtensions
{
	static public StaffNode NearestStaff(this Diagram diagram, PointF point)
	{
		var staves = diagram.Nodes.OfType();

		StaffNode nearest = null;
		float minDist = float.MaxValue;

		foreach (var staff in staves)
		{
			if (staff.ContainsPoint(point))
				return staff;

			var borderPoint = staff.GetNearestBorderPoint(point);
			var dist = Utilities.Distance(borderPoint, point);
			if (dist < minDist)
			{
				minDist = dist;
				nearest = staff;
			}
		}

		return minDist < 20 ? nearest : null;
	}
}

Next, implement StaffNode.Align method that aligns its argument to a line or space in the staff:

public PointF Align(PointF point, out int position)
{
	// align to pitch line/space

	float h = Bounds.Height / 8;
	float offset = point.Y - Bounds.Y;
	position = (int)Math.Round(offset / h);
	offset = (float)Math.Round(offset / h) * h;
	point.Y = Bounds.Y + offset;
	return point;
}

Add NoteNode.AlignToStaff method that will find nearest StaffNode and align the note’s position to the staff.

public StaffNode AlignToStaff()
{
	position = 0;

	var staff = Parent.NearestStaff(GetCenter());
	if (staff == null)
		return null;

	var alignedPoint = staff.Align(GetCenter(), out position);
	alignedPoint.X -= Bounds.Width / 2;
	alignedPoint.Y -= Bounds.Height / 2;
	Move(alignedPoint.X, alignedPoint.Y);

	return staff;
}

We can align notes after drag-and-drop from NodeListView by handling diagram’s NodeCreated event. We’ll use the same handler to attach notes to that staff, so that if users move a StaffNode, its attached NoteNodes will follow.

private void OnNodeCreated(object sender, NodeEventArgs e)
{
	var note = e.Node as NoteNode;
	if (note != null)
	{
		var staff = note.AlignToStaff();
		if (staff != null)
			note.AttachTo(staff, AttachToNode.TopLeft);

		note.HandlesStyle = HandlesStyle.MoveOnly;
	}
}

Finally, override NoteNode.CompleteModify to align notes after user moves them to a different position on the staff or to another staff in the score:

protected override void CompleteModify(PointF end, InteractionState ist)
{
	base.CompleteModify(end, ist);

	var staff = AlignToStaff();
	if (staff != null)
		AttachTo(staff, AttachToNode.TopLeft);
	else
	{
		Detach();
	}
}

Let’s run the project and compose some music 🙂
.net diagram control

A fully-featured scorewriter software would also allow for drawing rest, sharp and flat symbols, C and F clefs, and some other musical notation features, but these are left as exercise to the reader 😉

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!

WPF Diagram Control, V3.3.1 and WinForms Diagram Control, V6.3.3

MindFusion is pleased to announce the new releases of two of its popular flowchart control: Diagramming for WinForms, V6.3.3 and Diagramming for WPF, V3.3.1. Both releases have similar new features, which are listed below:

Resize table columns and rows
Columns and rows of a TableNode can now be resized interactively if its AllowResizeColumns or AllowResizeRows properties are enabled. In order to resize, move the mouse pointer to the border line on column’s right side or row’s bottom side until it shows resize cursor and start dragging. The control raises TableColumnResizing and TableRowResizing events to let you validate new size or prevent resizing some elements. The TableColumnResized and TableRowResized events are raised after the operation completes.

Barcode nodes
The BarcodeNode class displays EAN, UPC or QR barcodes as node’s content. In-place edit operations let users enter new numeric codes for 1D codes or text strings for QR codes. The barcode format is specified via the Format property, the encoded number or text is set via Content, and color of 1D bars / 2D modules via BarColor.

Barcode diagram nodes

Barcode diagram nodes

New Features in Diagramming for WPF, V3.3.1

ShapeDesigner improvements

  • The ShapeDesigner control supports undo. Call its Undo or Redo methods to respectively undo or redo a change done to the designed shape.
  • ZoomFactor property added to ShapeDesigner. It also supports interactive zoom in/out via mouse wheel.
  • The SelectedElement property exposes the graphic element currently selected in ShapeDesigner canvas. You can bind to its stroke and brush properties to create alternative user interface for editing element attributes.

Miscellaneous

  • NodeConstraints.KeepInsideDiagram prevents a node from leaving diagram boundaries during user interaction (the older RestrictItemsToBounds property does not stop nodes from leaving diagram area but returns them to original position if dropped outside).
  • dashed selection frames are now drawn in two colors and should be visible on both the default white background and custom darker backgrounds. You can change the second dash color via HandlesVisualStyle.DashBackground property.
  • set the WhileModifying flag in RoutingOptions.TriggerRerouting to reroute links while users drag their end points.
  • custom connection point classes can now override ConnectionPoint.NearestAnchorPoint to implement custom anchor point selection logic; the method is now also called at the beginning of interactive link creation.

New in Diagramming for WinForms, V6.3.3

Miscellaneous

  • the component now selects better end points for auto-routed links when using WhileCreating and WhileModifying routing modes;
  • custom connection point classes can now override ConnectionPoint.NearestAnchorPoint to implement custom anchor point selection logic; the method is now also called at the beginning of interactive link creation.
  • NodeConstraints.KeepInsideDiagram prevents a node from leaving diagram boundaries during user interaction (the older RestrictItemsToBounds property does not stop nodes from leaving diagram area but returns them to original position if dropped outside).
  • SvgNode supports SVG images with vector-effect=’non-scaling-stroke’ stroke attributes.
  • improved default Pen for dash-frame selection handles style. Dashed frames should now be visible on both the default white background and custom darker backgrounds.
  • ZoomControl can now be used with other components by MindFusion and has been moved to MindFusion.Common.WinForms assembly and namespace.

You are welcome to download the trial version for each control from the following links:

Diagramming for WinForms, V6.3.3 Trial Version Download

Diagramming for WPF, V3.3.1 Trial Version Download

If you have questions or run into problems using the components you can use the Diagramming components forum, the help desk or write us at support@mindfusion.dev. Our support team will be pleased to help you.

About MindFusion.Diagramming for WinForms: A programming component that provides any WinForms application with a full set of features for creating and customizing all types of diagrams, flowcharts, schemes, hierarchies, trees, graphs etc. The control provides numerous ways to save and load a diagram, six auxiliary controls and more than 10 automatic graph layout algorithms. Diagram elements include scrollable tables, container nodes, multi-segment arrows, custom diagram item types and many more. Further details here.

Diagramming for WinForms is a royalty-free component, clients get 12 month upgrade subscription when buying a license. The source code is also available for purchase. Visit the buy page for a list with the current license prices.

About MindFusion.Diagramming for Wpf: Designed and developed to be easy to integrate, use, and customize, this native WPF flowchart component places at your disposal every single feature you would ever need to create diagrams, graphs, schemes, org charts, DB relationships, genealogy trees, class hierarchies and many more. Its long list of style options gives you complete control over the appearance of the diagram. With a set of eight automatic layouts you are sure to find the arrangement that suits perfectly your WPF application.

The diagram control boasts a long list of events, properties and methods for user interaction, item creation, data input and output. You can read the full features list here. The online demo shows samples that demonstrate various capabilities of the control. The licensing scheme and prices are uploaded at the buy page. Source code is also available.

Layout Management with the WinForms Dock Control

In this post we will show you how to build a sample application with customizable layout based on the WinForms Dock control, which is part of MindFusion WinForms pack. You can find further details about the control at its web page.

For the purpose of the demonstration we’ve chosen an entertaining topic – a cooking recipes electronic book. Our book so far has only three recipes, all for sweets. Lets start by looking at the

I. Architecture of the sample.

The sample has two custom classes – Ingredient and Recipe. Each Ingredient has quantity, name and an image, which illustrates it. The Recipe class holds a strongly typed list with Ingredient objects, the title for each recipe, an image for the recipe, an icon for the recipe and preparation instructions.

The application consists of the dock control, which contains four DockItem-s : for the recipes, for their images, for their ingredients and with the preparation instructions. Any dock item can be dragged, dropped, aligned, minimized, hidden etc.

The API of the cook book application

The API of the cook book application

II. The Dock Control.

We create an empty WinForms project and add the Dock control from the toolbox. The dock control fills the entire client area:

this.dockControl1.Dock = System.Windows.Forms.DockStyle.Fill;         
this.dockControl1.Location = new System.Drawing.Point(0, 0);           
this.dockControl1.Size = new System.Drawing.Size(784, 562);

These properties are set in the Property grid.

Each dock item is created with a title and id, which helps us identify it.

titleItem = new DockItem() { Header = "Recipe Name", Id = "Name" };
titleItem.DockStyle = DockStyle.Left;      
titleItem.Content = GetContent("Name");

The DockStyle property is responsible for the initial layout of the dock item. You can choose among various dock styles – fill, bottom, top and more.

The GetContent method is very important. It prepares the controls that will be placed into each dock item and returns the appropriate one according to the parameter.

Once the dock item is created, let’s not forget to add it:

dockControl1.Items.Add(titleItem);

III. Creating the Content.

Let’s see how the content for a dock item is created. Let’s take the grid with the ingredients. It is identified with the “Ingredients” id:

private Control GetContent(string contentType)
{
  Control control = null;
  ....
  else if (contentType == "Ingredients")
         {                
             grid = new DataGridView();
             grid.DefaultCellStyle.BackColor = Color.FromArgb(247, 226, 189);
             grid.Dock = DockStyle.Fill;
             grid.MultiSelect = false;
             grid.DataSource = selectedRecipe.Ingredients;
             grid.RowTemplate.MinimumHeight = 35;
            
             grid.RowHeadersWidthSizeMode =
            DataGridViewRowHeadersWidthSizeMode.DisableResizing;
             grid.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.DisableResizing;
             grid.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
             grid.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
             grid.BackgroundColor = Color.FromArgb(253, 244, 247);

             control = grid; 
         }
...
return control;

The idea is clear: you create the control, which will be rendered in the dock item and assign it to DockItem.Content. Here we make different customization for the grid – we change the color of table rows, adjust the height, turn off multiple row select and more.

IV. Changing the Content.

We change the content by handling the SelectedIndexChanged event for the ListView, which lists our recipes. When the user selects a new list item, we extract its Recipe and change the content of the other DockItem-s:

selectedRecipe = recipes[recipesListView.SelectedIndices[0]];
tb.Text = selectedRecipe.Preparation;
grid.DataSource = selectedRecipe.Ingredients;
pictureBox.Image = Image.FromFile(selectedRecipe.ImageUrl);

Here is the final output of the sample application:

MindFusion WinForms Dock COntrol

A Sample electronic cook book based on MindFusion WinForms Dock Control

You can download the sample directly from this link:

MindFusion WinForms Layout Control Sample: Electronic Cook Book in C#

MindFusion WinForms Layout Control Sample: Electronic Cook Book in VB.NET

Enjoy the fast, easy and straight-forwarded manner in which you can create a WinForms application with a flexible layout and multiple panels.

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!