d3 MQTT topic tree visualiser

As part of writing my talks for the developerWorks Days Zurich conference next week I’ve been working on some code for the demo in the MQTT session. I was looking for a nice way to display a MQTT topic structure and the most recent value posted to topic.

In the past I have used a plugin to the freemind mindmap tool that plots the topics as and their values. This worked well but it does require a machine with Java and a very old version of freemind to make it work so it was time to see if there was another approach.

Nick O’Leary had created a neat little python MQTT to Websockets* bridge that we have been using round the office for a while and there are some really great javascript visualisation libraries out there now, so I thought I would see how hard it would be to run up a browser based version.

Starting with the d3 Collapsible Tree Layout example seamed like a good idea and it didn’t take too long to tweak it a little to make it dynamically add new topic branches when a message was received.

function addNode(topic, body) {
	var parts = topic.split("/");
	if (root.children[0]===undefined){
		var newnode = {"name": parts.shift(), "children":[]};
		root.children = [newnode];
		walk(parts,newnode,body);
	} else {
		walk(parts,root,body);
	}
	update(root);
}

function walk(parts, node, body) {
	if (parts.length != 0) {
		var current = parts.shift()
		if (node.children && node.children.length != 0) {
			var z=0;
			for(z=0; z < node.children.length; z++) {
				if (node.children[z].name == current) {
					walk(parts,node.children[z], body);
					break;
				}
			}
			if (z == node.children.length) {
				var newnode = {"name": current, "children":[]};
				node.children.push(newnode);
				walk(parts,node.children[z],body);
			}
		} else {
			var newnode = {"name": current, "children":[]};
			node.children = [newnode];
			walk(parts,node.children[0],body);
		}
	}
}

I also swapped out the where the data was loaded to use a static root rather than loading it from a json file on the server.

var root = {"name": "/","children": []};
root.x0 = h / 2;
root.y0 = 0;
update(root);

This worked pretty well so next I set about looking to see if I could add the payload of the message. To do this I added a data field to the json object for each node when populating the tree.

function walk(parts, node, body) {
	if (parts.length != 0) {
		var current = parts.shift()
		if (node.children && node.children.length != 0) {
			var z=0;
			for(z=0; z < node.children.length; z++) {
				if (node.children[z].name == current) {
					walk(parts,node.children[z], body);
					break;
				}
			}
			if (z == node.children.length) {
				var newnode = {"name": current, "children":[]};
				node.children.push(newnode);
				walk(parts,node.children[z],body);
			}
		} else {
			var newnode = {"name": current, "children":[]};
			node.children = [newnode];
			walk(parts,node.children[0],body);
		}
	} else {
		console.log("body");
		node.data = body;
	}
}

Then updated where the SVG is inserted to add a text element in the right place.

...
  nodeEnter.append("svg:text")
      .attr("x", function(d) { return d.children || d._children ? -10 : 10; })
      .attr("dy", ".35em")
      .attr("text-anchor", function(d) { return d.children || d._children ? "end" : "start"; })
      .text(function(d) { return d.name; })
      .style("fill-opacity", 1e-6);
  
  // Add message payload
  nodeEnter.append("svg:text")
  	.attr("class","data")
	.attr("x", 10)
	.attr("y", 10)
	.attr("dy", ".35em")
	.text(function(d) { return d.data || ""; })
	.style("fill-opacity", 1);
  
  // Transition nodes to their new position.
  var nodeUpdate = node.transition()
      .duration(duration)
      .attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; });
...

Again this worked very well, except that it would not update the payload value after adding the first version. I tried a number of things to get this to work, but didn’t get very far, until James Thomas pointed me at this page about how some of the internals of d3 work. It talks about entry, update and exit methods to work on new, existing and removed elements in the data. There are examples of entry and exit functions in the example so I tried adding an update method but kept getting errors that it wasn’t found. I went back and had another look at the examples in the page James had linked to and noticed that update code doesn’t actually use a functions.

...
// Add message payload
  nodeEnter.append("svg:text")
  	.attr("class","data")
	.attr("x", 10)
	.attr("y", 10)
	.attr("dy", ".35em")
	.text(function(d) { return d.data || ""; })
	.style("fill-opacity", 1);

  // Update payload if changed  
  var nodeChange = node.selectAll("text.data")
      .text(function(d) {return d.data || "";});

  // Transition nodes to their new position.
  var nodeUpdate = node.transition()
      .duration(duration)
      .attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; });
...

An that was basically it. I’ve added a bit more code to update values when the tree branches are collapsed and to trim payloads to no more than 20 chars so the display is not too cluttered. I’m planning on improving the style sheet a little soon and possibly adding an effect so you see which payloads have been updated recently and maybe add hover text with the full payload for the shortened ones.

You can see the whole thing running here

* There is also some work being done to support Websockets with the Mosquitto MQTT broker, you can see the testing page here

3 thoughts on “d3 MQTT topic tree visualiser”

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>