Difference between revisions of "Pie menu"

From Organic Design wiki
m (Feb 2 - Animation)
(Change source-code blocks to standard format)
 
(31 intermediate revisions by 2 users not shown)
Line 35: Line 35:
  
 
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.
 
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.
{{code|<js>$( function() {
+
<source lang="js">
 +
$( function() {
 
$('div.tam-star').each( function() { // apply the following to all star menus
 
$('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
 
$('a', this).css('position','absolute') // apply the following to all <a> elements in this menu
Line 50: Line 51:
 
});
 
});
 
});
 
});
});</js>}}
+
});
 +
</source>
  
 
=== Feb 1 - Recursive positioning ===
 
=== Feb 1 - Recursive positioning ===
Line 59: Line 61:
 
|&nbsp;&nbsp;&nbsp;&nbsp;
 
|&nbsp;&nbsp;&nbsp;&nbsp;
 
|
 
|
{{code|<js>// Get the depth of this element
+
<source lang="js">
 +
// Get the depth of this element
 
var li = e.parent();
 
var li = e.parent();
 
var d = 0;
 
var d = 0;
Line 72: Line 75:
 
var ox = parent.position().left + parent.width() / 2;
 
var ox = parent.position().left + parent.width() / 2;
 
var oy = parent.position().top + parent.height() / 2;
 
var oy = parent.position().top + parent.height() / 2;
}</js>}}
+
}
 +
</source>
 
|}
 
|}
  
Line 80: Line 84:
 
[[File:Star-progress-3.jpg|800px]]
 
[[File:Star-progress-3.jpg|800px]]
  
{{code|<js>// Create a unique ID and persistent data for this element
+
<source lang="js">
 +
// Create a unique ID and persistent data for this element
 
e.attr('id', 'starnode' + window.stars.length);
 
e.attr('id', 'starnode' + window.stars.length);
 
window.stars.push( { parent: p, depth: d } );
 
window.stars.push( { parent: p, depth: d } );
Line 86: Line 91:
 
// Animate the element from the parent to the circumference
 
// Animate the element from the parent to the circumference
 
var r = 120;
 
var r = 120;
e.animate( { foo: r }, { // just using a dummy property to set the range for "now"
+
e.animate( { radius: r }, { // just using a dummy property to set the range for "now"
 
duration: 1000,
 
duration: 1000,
 
step: function( now, fx ) {
 
step: function( now, fx ) {
 
var e = $(fx.elem);
 
var e = $(fx.elem);
var p = window.stars[ e.attr('id').substr(8) ].parent;
+
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 ox = p.position().left + p.width() / 2;
 
var oy = p.position().top + p.height() / 2;
 
var oy = p.position().top + p.height() / 2;
Line 96: Line 101:
 
var n = e.parent().parent().children().last().index() + 1;
 
var n = e.parent().parent().children().last().index() + 1;
 
var a = Math.PI * 2 * i / n;
 
var a = Math.PI * 2 * i / n;
var y = Math.sin(a) * now;
+
var y = Math.sin(a) * now; // radius changed to the animating "now" parameter
 
var x = Math.cos(a) * now;
 
var x = Math.cos(a) * now;
 
e.css('left', ox + x - e.width() / 2).css('top', oy + y - e.height() / 2);
 
e.css('left', ox + x - e.width() / 2).css('top', oy + y - e.height() / 2);
 
}
 
}
});</js>}}
+
});
 +
</source>
 +
 
 +
=== 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.
 +
{|
 +
|-
 +
|valign=top|<br />
 +
<html>
 +
<div class="tam-star-1">
 +
<ul><li><a rel="nofollow" class="external text" href="http://118.localhost/A">A</a>
 +
</li><li><a rel="nofollow" class="external text" href="http://118.localhost/Aa">Aa</a>
 +
</li><li><a rel="nofollow" class="external text" href="http://118.localhost/Bar">Bar</a>
 +
<ul><li><a rel="nofollow" class="external text" href="http://118.localhost/A">A</a>
 +
</li><li><a rel="nofollow" class="external text" href="http://118.localhost/B">B</a>
 +
</li><li><a rel="nofollow" class="external text" href="http://118.localhost/Biz">Biz</a>
 +
</li><li><a rel="nofollow" class="external text" href="http://118.localhost/C">C</a>
 +
<ul><li><a rel="nofollow" class="external text" href="http://118.localhost/1">1</a>
 +
</li><li><a rel="nofollow" class="external text" href="http://118.localhost/2">2</a>
 +
</li><li><a rel="nofollow" class="external text" href="http://118.localhost/3">3</a>
 +
</li></ul>
 +
</li><li><a rel="nofollow" class="external text" href="http://118.localhost/D">D</a>
 +
</li><li><a rel="nofollow" class="external text" href="http://118.localhost/E">E</a>
 +
</li></ul>
 +
</li><li><a rel="nofollow" class="external text" href="http://118.localhost/Baz">Baz</a>
 +
</li><li><a rel="nofollow" class="external text" href="http://118.localhost/Foo">Foo</a>
 +
</li><li><a rel="nofollow" class="external text" href="http://118.localhost/Hello">Hello</a>
 +
</li></ul>
 +
</div>
 +
<script>
 +
/**
 +
* Initalise bullet lists with class "tam-star"
 +
*/
 +
window.stars = [];
 +
$( function() {
 +
$('div.tam-star-1').each( function() {
 +
var tree = $(this);
 +
var root = 'starnode' + window.stars.length;
 +
tree.html( '<ul><li><a href="/">root</a>' + tree.html() + '</li></ul>' );
 +
$('ul', tree).css('list-style','none');
 +
tree.css('width','400px').css('height','400px').css('background','white');
 +
$('a', this).each( function() {
 +
 
 +
// Initialise the <a> element
 +
var e = $(this);
 +
e.css('position','absolute')
 +
.css('background','url( /files/d/d7/Star-node-4.png ) top center no-repeat' )
 +
.css('padding','15px 5px 0px 5px')
 +
.css('display','none')
 +
.attr('href','javascript:');
 +
 
 +
// Get the depth of this element
 +
var li = e.parent();
 +
var d = 0;
 +
while( li[0].tagName == 'LI' ) {
 +
li = li.parent().parent();
 +
d++;
 +
}
 +
 
 +
// Get the parent <a> or <div> and add item to parents children
 +
var p = e.parent().parent().parent();
 +
if( d > 1 ) {
 +
p = p.children().first();
 +
window.stars[p.attr('id').substr(8)].children.push(e);
 +
}
 +
 
 +
// Set initial position to parent
 +
var ox = p.position().left + p.width() / 2;
 +
var oy = p.position().top + p.height() / 2;
 +
e.css('left', ox - e.width() / 2).css('top', oy - e.height() / 2);
 +
 
 +
// Create a unique ID and persistent data for this element
 +
e.attr('id', 'starnode' + window.stars.length);
 +
window.stars.push( {
 +
children: [],
 +
parent: p,
 +
depth: d,
 +
open: false
 +
});
 +
 
 +
// Set a callback to open or close the node when clicked
 +
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 display = 'block';
 +
var o = t;
 +
 
 +
// Set origin for the children to this elements center
 +
var ox = e.position().left + e.width() / 2;
 +
var oy = e.position().top + e.height() / 2;
 +
 
 +
// Hide the labels during animation
 +
var col = t < 0.9 ? 'white' : 'black';
 +
 
 +
// If closing flip t, and hide items at end
 +
if( data.open ) {
 +
if( t > 0.9 ) display = 'none';
 +
t = 1 - t;
 +
o = 1 + o;
 +
}
 +
 
 +
 
 +
// Current radius for this elements children
 +
var d = data.depth;
 +
var r = 120;
 +
if( d == 2 ) r = 90;
 +
else if( d == 3 ) r = 40;
 +
else if( d > 3 ) r = 20;
 +
r = r * t;
 +
 
 +
// Position the children to their locations around the parent
 +
var k = Math.PI * 2 / data.children.length;
 +
for( var i in data.children ) {
 +
var child = data.children[i];
 +
var a = k * i + o;
 +
var x = Math.cos(a) * r;
 +
var y = Math.sin(a) * r;
 +
child.css( 'display','block' )
 +
.css('left', ox + x - child.width() / 2)
 +
.css('top', oy + y - child.height() / 2)
 +
.css('color', col)
 +
.css('display', display);
 +
}
 +
},
 +
 
 +
// Toggle the status on completion
 +
complete: function() {
 +
var e = $(this);
 +
var data = window.stars[e.attr('id').substr(8)];
 +
data.open = !data.open;
 +
}
 +
});
 +
});
 +
});
 +
 
 +
// Make the root node visible
 +
var e = $('#'+root);
 +
var data = window.stars[0];
 +
var p = data.parent;
 +
var ox = p.position().left + p.width() / 2;
 +
var oy = p.position().top + p.height() / 2;
 +
e.css('display','block').css('left',tree.left + tree.width()/2).css('top',tree.top + tree.height()/2);
 +
});
 +
});
 +
</script>
 +
</html>
 +
|&nbsp;&nbsp;&nbsp;&nbsp;
 +
|
 +
<source lang="js">
 +
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;
 +
}
 +
});
 +
});
 +
</source>
 +
|}
 +
 
 +
=== 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.
 +
<html>
 +
<div class="tam-star">
 +
<ul><li><a href="/wiki/index.php?title=1&amp;action=edit&amp;redlink=1" class="new" title="1 (page does not exist)">1</a>
 +
</li><li><a href="/wiki/index.php?title=2&amp;action=edit&amp;redlink=1" class="new" title="2 (page does not exist)">2</a>
 +
<ul><li><a href="/wiki/index.php?title=V&amp;action=edit&amp;redlink=1" class="new" title="V (page does not exist)">v</a>
 +
</li><li><a href="/wiki/index.php?title=Vi&amp;action=edit&amp;redlink=1" class="new" title="Vi (page does not exist)">vi</a>
 +
</li><li><a href="/wiki/index.php?title=Vii&amp;action=edit&amp;redlink=1" class="new" title="Vii (page does not exist)">vii</a>
 +
</li></ul>
 +
</li><li><a href="/wiki/index.php?title=3&amp;action=edit&amp;redlink=1" class="new" title="3 (page does not exist)">3</a>
 +
</li><li><a href="/wiki/index.php?title=4&amp;action=edit&amp;redlink=1" class="new" title="4 (page does not exist)">4</a>
 +
 
 +
<ul><li><a href="/wiki/index.php?title=Viii&amp;action=edit&amp;redlink=1" class="new" title="Viii (page does not exist)">viii</a>
 +
</li><li><a href="/wiki/index.php?title=Ix&amp;action=edit&amp;redlink=1" class="new" title="Ix (page does not exist)">ix</a>
 +
</li><li><a href="/X" title="X">x</a>
 +
</li><li><a href="/wiki/index.php?title=Xi&amp;action=edit&amp;redlink=1" class="new" title="Xi (page does not exist)">xi</a>
 +
</li></ul>
 +
</li><li><a href="/wiki/index.php?title=5&amp;action=edit&amp;redlink=1" class="new" title="5 (page does not exist)">5</a>
 +
</li></ul>
 +
</li><li><a href="/wiki/index.php?title=B&amp;action=edit&amp;redlink=1" class="new" title="B (page does not exist)">B</a>
 +
</li><li><a href="/wiki/index.php?title=C&amp;action=edit&amp;redlink=1" class="new" title="C (page does not exist)">C</a>
 +
<ul><li><a href="/wiki/index.php?title=6&amp;action=edit&amp;redlink=1" class="new" title="6 (page does not exist)">6</a>
 +
 
 +
</li><li><a href="/wiki/index.php?title=7&amp;action=edit&amp;redlink=1" class="new" title="7 (page does not exist)">7</a>
 +
<ul><li><a href="/I" title="I" class="mw-redirect">i</a>
 +
</li><li><a href="/wiki/index.php?title=Ii&amp;action=edit&amp;redlink=1" class="new" title="Ii (page does not exist)">ii</a>
 +
</li><li><a href="/wiki/index.php?title=Iii&amp;action=edit&amp;redlink=1" class="new" title="Iii (page does not exist)">iii</a>
 +
</li><li><a href="/wiki/index.php?title=Iv&amp;action=edit&amp;redlink=1" class="new" title="Iv (page does not exist)">iv</a>
 +
</li></ul>
 +
</li><li><a href="/wiki/index.php?title=8&amp;action=edit&amp;redlink=1" class="new" title="8 (page does not exist)">8</a>
 +
</li></ul>
 +
</li><li><a href="/wiki/index.php?title=D&amp;action=edit&amp;redlink=1" class="new" title="D (page does not exist)">D</a>
 +
</li><li><a href="/E" title="E">E</a>
 +
 
 +
</li><li><a href="/wiki/index.php?title=F&amp;action=edit&amp;redlink=1" class="new" title="F (page does not exist)">F</a>
 +
</li><li><a href="/wiki/index.php?title=G&amp;action=edit&amp;redlink=1" class="new" title="G (page does not exist)">G</a>
 +
</li></ul>
 +
</div>
 +
<script>/*<![CDATA[*/
 +
window.tamBaseUrl = '';
 +
window.star_config = {
 +
img_node: '/files/d/d7/Star-node-4.png',
 +
img_leaf: '/files/5/5b/Star-node-4b.png',
 +
radii: [120,90,40,20],
 +
duration: 500,
 +
easing: 'swing',
 +
out_spin: 2,
 +
in_spin: 2,
 +
width: '400px',
 +
height: '400px'
 +
}
 +
 
 +
/**
 +
* Initalise bullet lists with class "tam-star"
 +
*/
 +
window.stars = [];
 +
$( function() {
 +
$('div.tam-star').each( function() {
 +
var tree = $(this);
 +
var root = 'starnode' + window.stars.length;
 +
tree.html( '<ul><li><a href="/">root</a>' + tree.html() + '</li></ul>' );
 +
$('ul', tree).css('list-style','none');
 +
tree.css('width',window.star_config.width).css('height',window.star_config.height);
 +
 
 +
// Change all the bullet list li's content into star nodes (divs with an image and the li content)
 +
$('a', this).each( function() {
 +
var a = $(this);
 +
var img = window.tamBaseUrl + window.star_config.img_leaf;
 +
a.wrap('div').css({
 +
padding: 0,
 +
margin: 0,
 +
background: 'transparent',
 +
color: 'black',
 +
});
 +
var div = a.parent();
 +
div.attr('class','starnode').html( '<div><img src="' + img + '" /></div>' + div.html() ).css({
 +
'text-align': 'center',
 +
position: 'absolute',
 +
display: 'none'
 +
});
 +
});
 +
 
 +
// Position all the nodes and add their animation events
 +
$('div.starnode', this).each( function() {
 +
var e = $(this);
 +
 
 +
// Get the depth of this element
 +
var li = e.parent();
 +
var d = 0;
 +
while( li[0].tagName == 'LI' ) {
 +
li = li.parent().parent();
 +
d++;
 +
}
 +
 
 +
// Get the parent <a> or <div> and add item to parents children
 +
var p = e.parent().parent().parent();
 +
if( d > 1 ) {
 +
p = p.children().first();
 +
$('img', p).attr('src', window.tamBaseUrl + window.star_config.img_node);
 +
getData(p).children.push(e);
 +
}
 +
 
 +
// Set initial position to parent
 +
var ox = p.position().left + p.width() / 2;
 +
var oy = p.position().top + p.height() / 2;
 +
e.css('left', ox - e.width() / 2).css('top', oy - e.height() / 2);
 +
 
 +
// Create a unique ID and persistent data for this element
 +
e.attr('id', 'starnode' + window.stars.length);
 +
window.stars.push( {
 +
children: [],
 +
parent: p,
 +
depth: d,
 +
open: false
 +
});
 +
 
 +
// Set a callback to open or close the node when clicked
 +
e.click( function() { animateNode(this); });
 +
});
 +
 
 +
// Make the root node visible
 +
var e = $('#'+root);
 +
var data = window.stars[0];
 +
var p = data.parent;
 +
var ox = p.position().left + p.width() / 2;
 +
var oy = p.position().top + p.height() / 2;
 +
e.css('display','block').css('left',tree.left + tree.width()/2).css('top',tree.top + tree.height()/2);
 +
});
 +
});
 +
 
 +
// Animate the passed node and its children from it's current state to the opposite state
 +
function animateNode(node) {
 +
$(node).animate( { t: 100 }, {
 +
duration: window.star_config.duration,
 +
easing: window.star_config.easing,
 +
step: function(now, fx) {
 +
var t = fx.pos;
 +
var e = $(fx.elem);
 +
var data = getData(e);
 +
var display = 'block';
 +
var o = t * window.star_config.out_spin;
 +
var d = data.depth;
 +
 
 +
// Set origin for the children to this elements center
 +
var ox = e.position().left + e.width() / 2;
 +
var oy = e.position().top + e.height() / 2;
 +
 
 +
// Hide the labels during animation
 +
var col = t < 0.9 ? 'white' : 'black';
 +
 
 +
// If closing flip t, and hide items at end
 +
if( data.open ) {
 +
if( t > 0.9 ) display = 'none';
 +
o = window.star_config.out_spin + t * window.star_config.in_spin;
 +
t = 1 - t;
 +
}
 +
 
 +
// If opening, check siblings to see if one needs to be closed
 +
else if( d > 1 ) { if( fx.pos == 0 ) {
 +
var pdata = getData(data.parent);
 +
for( var i in pdata.children ) {
 +
var sibling = pdata.children[i];
 +
var sdata = getData(sibling);
 +
if( sdata.open ) animateNode(sibling);
 +
}
 +
} }
 +
 
 +
// Current radius for this elements children
 +
var r = window.star_config.radii;
 +
r = d > r.length ? r[r.length-1] : r[d-1];
 +
r = r * t;
 +
 
 +
// Position the children to their locations around the parent
 +
var k = Math.PI * 2 / data.children.length;
 +
for( var i in data.children ) {
 +
var child = data.children[i];
 +
var cdata = getData(child);
 +
var a = k * i + o;
 +
var x = Math.cos(a) * r;
 +
var y = Math.sin(a) * r;
 +
child.css( 'display','block' )
 +
.css('left', ox + x - child.width() / 2)
 +
.css('top', oy + y - child.height() / 2)
 +
.css('color', col)
 +
.css('display', display);
 +
 +
// If closing, and this is the first iteration, close this child too if open
 +
if( data.open ) if( fx.pos == 0 ) if( cdata.open ) animateNode(child);
 +
}
 +
},
 +
 
 +
// Toggle the status on completion
 +
complete: function() {
 +
var data = getData($(this));
 +
data.open = !data.open;
 +
}
 +
});
 +
}
 +
 
 +
/**
 +
* Return the passed star node elements data array
 +
*/
 +
function getData(e) {
 +
return window.stars[e.attr('id').substr(8)];
 +
}
 +
/*]]>*/</script>
 +
</html>
  
 +
==Bug==
  
[[Category:Projects]]
+
<s>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. </s>
 +
::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 6<sup>th</sup>. It wasn't until the Feb 8<sup>th</sup> session that the closing code finalised to close all opened child nodes.
 +
[[Category:Old Projects]]

Latest revision as of 18:11, 22 May 2015

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.

Example

http://www.woweb.ru/js/12/082/

Elaboration

  • 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.

Ideas

  • 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.

LinkTreeExample.jpg

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.


Star-progress-2.jpg
    
// Get the depth of this element
var li = e.parent();
var d = 0;
while( li[0].tagName == 'LI' ) {
	li = li.parent().parent();
	d++;
}

// 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.

Star-progress-3.jpg

// 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.