Pie menu

From Organic Design

User story

As a user, I want to easily navigate to key points in an article so that I can see what other concepts are linked to in an article, and also see what key points (children) are related to the key points in the current article (parent). I also want to simply see what key points (grandchildren) are linked to children articles.




  • Given an article foo with [[ ]]links to articles foobar, bar and barfoo i want to see a concept menu that centers on foo (current article) and when i click on (+) next to foo, it will expand out dynamic indexed all hyperlinks in given article in an equidistant format.
  • (+) will expand out nodes, instead of being indicated by color
  • starburst or "concept map"
  • initially, only 2 levels deep: parent (current article), child (links in article), grandchild (links in children article)
  • if article bar contains links to a and b, then when i expand the foo node out to bar, and click on a (+) sign for bar, I will see a and b as the terminal (leaf) node.
  • Dynamic: If I add a link to the article foo, then the "pie menu" will be autimatically updated
  • call it pie because that is what is is in sims. also, will need to use pi to calculate the distribution of nodes
  • with a large amount of links, may need to do a lighbox kinda thing and fade out bacground when the map is activated, and new nodes may be larger than parent nodes, and may need to fade out / overlap parent nodes.


  • different color of nodes?
  • Base it on category instead of links
  • Base it on headers instead of links
  • n nodes, set terminus (leaf) node
  • use with TreeAndMenu syntax

Progress reports

Jan 30 - Link extraction

Ok the first easy bit has been done which is the LinkTree extension that extracts the links out from the page to the requested depth. It adds a #linktree parser function which takes one parameter for the depth (defaults to 1). Here's a screenshot of how it's used, the last example shows how to use it with TreeAndMenu which will soon have a new #star function added.


Jan 31 - Star menu started

I've made a start on the complex stuff now, here's a screenshot of the results after the first session of adding the #star parser-function to the TreeAndMenu extension: Star-progress-1.jpg

All the changes made to convert the links from a standard bullet list to a star menu are done in JavaScript/jQuery, all the PHP does is wrap the bullet list in a class so that the JavaScript knows which bullet lists to convert. Here's the code which operates directly on the link elements composing the bullet list and re-positions them into a circle.

$( function() {
	$('div.tam-star').each( function() { // apply the following to all star menus
		$('a', this).css('position','absolute') // apply the following to all <a> elements in this menu
			    .css('left', $(this).position().left + r)
			    .css('top', $(this).position().top + r)
			    .each( function() {
				var e = $(this);
				var i = e.parent().index(); // the index of the <li> that this <a> is in
				var n = e.parent().parent().children().last().index() + 1; // the index of the last <li> + 1
				var a = Math.PI * 2 * i / n;
				var y = Math.sin(a) * 100;
				var x = Math.cos(a) * 100;
				e.css('left', e.position().left+x-e.width()/2).css('top', e.position().top+y-e.height()/2);

Feb 1 - Recursive positioning

The difficult calculation of the recursive position was worked out today. Here's a screenshot of it calculating the position of nested items to three levels. The circles have been added for clarity. The depth of each <a> element was calculated by checking how many <li> elements it has hierarchically above it, then the centre position for this element is calculated from the position of the <a> in the parent <li>, or from the centre of the root <div> element if it's a top-level item (depth=1). The code to the right of the screenshot shows these calculations.

// Get the depth of this element
var li = e.parent();
var d = 0;
while( li[0].tagName == 'LI' ) {
	li = li.parent().parent();

// Get the origin for this element from the parent <a> or <div>
var parent = e.parent().parent().parent();
if( d > 1 ) parent = parent.children().first();
var ox = parent.position().left + parent.width() / 2;
var oy = parent.position().top + parent.height() / 2;

Feb 2 - Animation

In today's session on the star menu I focussed on getting the basics of the animation working using jQuery's animate() method. Currently I've just applied the animation in a general way so that every <a> element simply animates from it's parent's centre (which is also moving) to its designated position on the circumference around its parent. I had to create a global array of all the <a> elements so that data could be associated with them and retrieved from within the animation callback. Here's some screenshots from the animation sequence, followed by the global data and animation code.


// Create a unique ID and persistent data for this element
e.attr('id', 'starnode' + window.stars.length);
window.stars.push( { parent: p, depth: d } );

// Animate the element from the parent to the circumference
var r = 120;
e.animate( { radius: r }, { // just using a dummy property to set the range for "now"
	duration: 1000,
	step: function( now, fx ) {
		var e = $(fx.elem);
		var p = window.stars[ e.attr('id').substr(8) ].parent; // get parent element from the global array
		var ox = p.position().left + p.width() / 2;
		var oy = p.position().top + p.height() / 2;
		var i = e.parent().index();
		var n = e.parent().parent().children().last().index() + 1;
		var a = Math.PI * 2 * i / n;
		var y = Math.sin(a) * now; // radius changed to the animating "now" parameter
		var x = Math.cos(a) * now;
		e.css('left', ox + x - e.width() / 2).css('top', oy + y - e.height() / 2);

Feb 6 - Interaction

Today was the big one which has 99% completed the project. It involved adding the click event to the nodes so that they can animate open or closed depending on their current state. The example below is an active version of the code after today's session which opens opens out to three levels (click Root, then Bar, then C). A snippet of the mouse click event and the associated animation code are shown to the right of the example. There's still a few things required to finalise the project though, such as making the items link to their targets properly, allowing only the outer-most open nodes to be clickable, and using different icons to show which are disabled or contain no children.

e.click( function() {
	$(this).animate( { t: 100 }, {
		duration: 500,
		easing: 'swing',
		step: function(now, fx) {
			var t = fx.pos;
			var e = $(fx.elem);
			var data = window.stars[e.attr('id').substr(8)];
			var k = Math.PI * 2 / data.children.length;
			for( var i in data.children ) {
				var child = data.children[i];



		// Toggle the status on completion
		complete: function() {
			var e = $(this);
			var data = window.stars[e.attr('id').substr(8)];
			data.open = !data.open;

Feb 8 finishing touches

Today in the hotel at Foz do Iguaçu I got the finishing touches done. The whole structure closes if its open to a deep level and then a higher level such as the root node is clicked. The links work properly (clicking on the word follows the link, but clicking on the node icon manipulates the menu). And different icons are used to indicate whether or not the node contains any child nodes that can be opened. All the characteristics such as animation speed, the icons used, whether and how much the animation rotates when opening/closing, and the radius for each depth are all adjustable from a configuration array.

  • B
  • C
  • D
  • E
  • F
  • G
  • Bug

    Click on root, then bar, then baz, then root. Bar will not collapse. It seems that if a non-expanded node clicked on, it "trips" the focus from baz so that baz doesn't collapse with the rest of the leafs.

    refreshed page, can't duplicate.
    Are you doing that testing right here? or did you install TreeAndMenu etc? if here - you were probably testing on the star-menu above under Feb 6th. It wasn't until the Feb 8th session that the closing code finalised to close all opened child nodes.