Lately I’m on a project with tree relation between some models, and those relations are store in the database, each row represents a parent-child link.

It’s OK for the code to use but a little unintuitive for us human beings to understand the tree, for simple ones we can draw that on paper checking row by row, while for complicated trees, it will cause us ton of time to just visualize the tree.

And why not make a tool for drawing that tree :D

Overall

For decoupling, the tool is made up of 2 parts: the backend and the front-end. Then the font-end can only do the rendering and is replaceable by any other font-end.

Backend

I plan to not involve detailed logic of the backend and only define the interface for the front-end to fetch data.

And a sample response from the backend is like:

{
  "id": 1,
  "name": "root",
  "description": "description",
  "children": [
    {
      "id": 2,
      "name": "level1",
      "description": "description",
      "children": [
        {
          "id": 4,
          "name": "level22222",
          "description": "This is level2"
        },
        {
          "id": 5,
          "name": "level222",
          "description": "This is level2",
          "children": [
            {
              "id": 10,
              "name": "level333",
              "description": "Here are a few key resources to get you started with building mobile apps quickly",
              "children": [
                {
                  "id": 11,
                  "name": "level444444",
                  "description": "Introduces Android development. Covers the tool chain, Xamarin.Android projects, and Android fundamentals."
                },
                {
                  "id": 12,
                  "name": "level 4444",
                  "description": "This guide walks through the installation steps and configuration details required to install Xamarin.Android on Windows."
                }
              ]
            }
          ]
        }
      ]
    },
    {
      "id": 3,
      "name": "level1111",
      "description": "description",
      "children": [
        {
          "id": 6,
          "name": "level2222222",
          "description": "Never know this level: level2, 233333"
        }
      ]
    },
    {
      "id": 3,
      "name": "level111111111",
      "description": "description",
      "children": [
        {
          "id": 7,
          "name": "level22222222222",
          "description": "blahblahblah"
        },
        {
          "id": 8,
          "name": "level222",
          "description": "The AppDelegate.cs file contains our AppDelegate class"
        },
        {
          "id": 9,
          "name": "level two",
          "description": "which is responsible for creating our window and listening to OS events"
        }
      ]
    }
  ]
}

It’s JSON formatted with the children representing the relationship.

Front-end

This time I try to implement the front-end on Mac using Xamarin.Mac.

Xamarin is a nice tool for cross-platform development using C#. And Xamarin.Mac is for developing Mac native app.

Our result is like:

result

Just create a new solution for our TreeViewer. The IDE - Xamarin Studio takes the advantage of the Xcode to editing storyboard files.

We use a horizon SplitView for the main structure. The we draw a text field for entering root ID, and a draw button the draw the tree.

We should custom a view to draw the actual tree. Let’s make it TreeView.

The TreeView is subclass of the NSControl on Cocoa framework.

The key point is calculating the tree size and positions of each nodes/links.

Here, we use json.NET to parsing the JSON response and using a generic object to representing the tree, because the backend may response extra info in node, so there is no a fixed node class.

The width/height calculation is as follows:

private int calcTreeWidth(JObject root) {
    if (root == null) {
        return 0;
    }

    // no children, the leaf node
    if (root ["children"] == null) {
        root.Add (NODE_WIDTH_TAG, NodeWidth);
        return NodeWidth;
    }

    int childrenWidth = 0;
    foreach (var child in root["children"]) {
        childrenWidth += calcTreeWidth (child.Value<JObject>());
    }
    root.Add(NODE_WIDTH_TAG, Math.Max (childrenWidth, NodeWidth));
    return Math.Max (childrenWidth, NodeWidth);
}

private int calcTreeHeight(JObject root) {
	if (root == null) {
		return 0;
	}

	// leaf
	if (root ["children"] == null) {
		root.Add(NODE_HEIGHT_TAG, NodeHeight);
		return NodeHeight;
	}

	int childrenHeight = 0;
	foreach (var child in root["children"]) {
		int height = calcTreeHeight (child.Value<JObject>());
		childrenHeight = Math.Max (height, childrenHeight);
	}
	root.Add (NODE_HEIGHT_TAG, NodeHeight + childrenHeight);
	return NodeHeight + childrenHeight;
}

The sum of children’s width is the root’s width and the height is simply the depth of the tree.

Along the way, we can store the whole size of the subtree, and we can calculate the positions of each node later on.

private CGPoint calcTreePos (JObject root, CGPoint origin) {
	if (root == null) {
		return origin;
	}

	int width = (int)root [NODE_WIDTH_TAG];
	int height = (int)root [NODE_HEIGHT_TAG];

	// root
	CGPoint rootPos = new CGPoint(origin.X + width / 2, origin.Y + NodeHeight / 2);
	_nodes.Add (rootPos);
	if (root ["children"] != null) {
		var curX = origin.X;
		var curY = origin.Y + NodeHeight;
		foreach (var child in root["children"]) {
			JObject childObj = child.Value<JObject> ();
			CGPoint childPos = calcTreePos(childObj, new CGPoint(curX, curY));
			// add path
			_paths.Add(new Tuple<CGPoint, CGPoint>(rootPos, childPos));
			int childWidth = (int)childObj [NODE_WIDTH_TAG];
			curX += childWidth;
		}
	}

	return rootPos;
}

At last

OK, the tree is very ugly now, more should be done the make it better.