Potential enhancements to SpaceTree

914 views
Skip to first unread message

Dallan

unread,
Aug 20, 2011, 1:31:28 PM8/20/11
to JavaScript InfoVis Toolkit
Thank you for a *terrific* toolkit! It's been great to work with.
I've used it at WeRelate.org for an interactive family tree (see
http://www.werelate.org/wiki/Person:William_Bradford_%289%29 for
example and click on the "Family tree" link). To make it work for my
needs I made a few enhancements to the jit, which I'm sharing below in
case they might be useful to others.

-dallan


When the browser window is resized, I didn't want the tree to be
rescaled and reset to the center, just replotted:

diff --git a/Source/Core/Canvas.js b/Source/Core/Canvas.js
index 78fe180..0beacf5 100644
--- a/Source/Core/Canvas.js
+++ b/Source/Core/Canvas.js
@@ -118,7 +119,8 @@ var Canvas;
viz.fx.plot();
},
resize: function() {
- viz.refresh();
+ //viz.refresh();
+ viz.plot();
}
}));
//create secondary canvas

When the user drags the tree, change the cursor to a move cursor:

diff --git a/Source/Extras/Extras.js b/Source/Extras/Extras.js
index 282f896..311998b 100644
--- a/Source/Extras/Extras.js
+++ b/Source/Extras/Extras.js
@@ -730,6 +730,8 @@ Extras.Classes.Navigation = new Class({
if(!this.config.panning) return;
if(this.config.panning == 'avoid nodes' && (this.dom?
this.isLabel(e, win) : eventInfo.getNode())) return;
this.pressed = true;
+ $.event.stop($.event.get(e, win)); // required for Chrome
+ this.canvas.getElement().style.cursor = 'move';
this.pos = eventInfo.getPos();
var canvas = this.canvas,
ox = canvas.translateOffsetX,
@@ -766,5 +768,6 @@ Extras.Classes.Navigation = new Class({
onMouseUp: function(e, win, eventInfo, isRightClick) {
if(!this.config.panning) return;
this.pressed = false;
+ this.canvas.getElement().style.cursor = '';
}
});

I used nodes with very different widths, laid out in a multitree with
nodes left-aligned on one side, right-aligned on the other, and the
root node centered. The following change gave the correct width for
the root node:

diff --git a/Source/Layouts/Layouts.Tree.js b/Source/Layouts/
Layouts.Tree.js
index 7907f84..334595a 100644
--- a/Source/Layouts/Layouts.Tree.js
+++ b/Source/Layouts/Layouts.Tree.js
@@ -149,7 +149,9 @@ Layouts.Tree = (function() {
var sval = node.getData(s, prop);
var notsval = maxsize
|| (node.getData(nots, prop));
-
+ if (nots == 'width' && node.getData('align', prop) == 'center')
{
+ notsval /= 2;
+ }
var trees = [], extents = [], chmaxsize = false;
var chacum = notsval + config.levelDistance;
node.eachSubnode(function(n) {

Correct the positioning so that when you expand a node, drag the tree,
and click on another node (which either removes a subtree or expands
the node in my case), the clicked-on node is centered:

diff --git a/Source/Visualizations/Spacetree.js b/Source/
Visualizations/Spacetree.js
index 507ffce..05bf33b 100644
--- a/Source/Visualizations/Spacetree.js
+++ b/Source/Visualizations/Spacetree.js
@@ -299,8 +299,8 @@ $jit.ST= (function() {
this.compute('end');
if (this.clickedNode) {
var offset = {
- x: this.config.offsetX || 0,
- y: this.config.offsetY || 0
+ x: this.canvas.translateOffsetX +
this.config.offsetX || 0,
+ y: this.canvas.translateOffsetY +
this.config.offsetY || 0
};
this.geom.translate(this.clickedNode.endPos.add(offset).
$scale(-1), 'end');
}
@@ -604,8 +604,8 @@ $jit.ST= (function() {
var innerController = {
Move: {
enable: true,
- offsetX: config.offsetX || 0,
- offsetY: config.offsetY || 0
+ offsetX: canvas.translateOffsetX + config.offsetX ||
0,
+ offsetY: canvas.translateOffsetY + config.offsetY || 0
},
setRightLevelToShowConfig: false,
onBeforeRequest: $.empty,

I wanted the node alignment to follow it's orn (this appears to be
necessary when you have a multitree with different-width nodes), so I
assigned node.data.$align = node.data.$orn in my script. The
following changes allow nodes to have custom alignments:

@@ -1000,31 +1000,31 @@ $jit.ST.Geom = new Class({
return node.pos.add(new Complex(a, b));
};
};
- var dim = this.node;
+ var align = node.getData('align') || this.node.align;
var w = node.getData('width');
var h = node.getData('height');

if(type == 'begin') {
- if(dim.align == "center") {
+ if(align == "center") {
return this.dispatch(s, $C(0, h/2), $C(-w/2, 0),
$C(0, -h/2),$C(w/2, 0));
- } else if(dim.align == "left") {
+ } else if(align == "left") {
return this.dispatch(s, $C(0, h), $C(0, 0),
$C(0, 0), $C(w, 0));
- } else if(dim.align == "right") {
+ } else if(align == "right") {
return this.dispatch(s, $C(0, 0), $C(-w, 0),
$C(0, -h),$C(0, 0));
} else throw "align: not implemented";


} else if(type == 'end') {
- if(dim.align == "center") {
+ if(align == "center") {
return this.dispatch(s, $C(0, -h/2), $C(w/2, 0),
$C(0, h/2), $C(-w/2, 0));
- } else if(dim.align == "left") {
+ } else if(align == "left") {
return this.dispatch(s, $C(0, 0), $C(w, 0),
$C(0, h), $C(0, 0));
- } else if(dim.align == "right") {
+ } else if(align == "right") {
return this.dispatch(s, $C(0, -h),$C(0, 0),
$C(0, 0), $C(-w, 0));
} else throw "align: not implemented";
@@ -1035,7 +1035,7 @@ $jit.ST.Geom = new Class({
Adjusts the tree position due to canvas scaling or
translation.
*/
getScaledTreePosition: function(node, scale) {
- var dim = this.node;
+ var align = node.getData('align') || this.node.align;
var w = node.getData('width');
var h = node.getData('height');
var s = (this.config.multitree
@@ -1047,13 +1047,13 @@ $jit.ST.Geom = new Class({
return node.pos.add(new Complex(a, b)).$scale(1 - scale);
};
};
- if(dim.align == "left") {
+ if(align == "left") {
return this.dispatch(s, $C(0, h), $C(0, 0),
$C(0, 0), $C(w, 0));
- } else if(dim.align == "center") {
+ } else if(align == "center") {
return this.dispatch(s, $C(0, h / 2), $C(-w / 2, 0),
$C(0, -h / 2),$C(w / 2, 0));
- } else if(dim.align == "right") {
+ } else if(align == "right") {
return this.dispatch(s, $C(0, 0), $C(-w, 0),
$C(0, -h),$C(0, 0));
} else throw "align: not implemented";
@@ -1137,17 +1137,20 @@ $jit.ST.Plot = new Class({
pos - (object) A <Graph.Node> position.
width - (number) The width of the node.
height - (number) The height of the node.
+ align - (string) center, left, right, or blank to use the
default align

*/
- getAlignedPos: function(pos, width, height) {
- var nconfig = this.node;
+ getAlignedPos: function(pos, width, height, align) {
var square, orn;
- if(nconfig.align == "center") {
+ if (!align) {
+ align = this.node.align;
+ }
+ if(align == "center") {
square = {
x: pos.x - width / 2,
y: pos.y - height / 2
};
- } else if (nconfig.align == "left") {
+ } else if (align == "left") {
orn = this.config.orientation;
if(orn == "bottom" || orn == "top") {
square = {
@@ -1160,7 +1163,7 @@ $jit.ST.Plot = new Class({
y: pos.y - height / 2
};
}
- } else if(nconfig.align == "right") {
+ } else if(align == "right") {
orn = this.config.orientation;
if(orn == "bottom" || orn == "top") {
square = {
@@ -1232,7 +1235,8 @@ $jit.ST.Label.Native = new Class({
coord = node.pos.getc(true),
width = node.getData('width'),
height = node.getData('height'),
- pos = this.viz.fx.getAlignedPos(coord, width, height);
+ align = node.getData('align');
+ pos = this.viz.fx.getAlignedPos(coord, width, height, align);
ctx.fillText(node.name, pos.x + width / 2, pos.y + height / 2);
}
});
@@ -1255,7 +1259,7 @@ $jit.ST.Label.DOM = new Class({
placeLabel: function(tag, node, controller) {
var pos = node.pos.getc(true),
config = this.viz.config,
- dim = config.Node,
+ align = node.getData('align') || config.Node.align,
canvas = this.viz.canvas,
w = node.getData('width'),
h = node.getData('height'),

Scaling w and h by sx and sy respectively ensures that the labels
retain the correct width and height as the tree is scaled:

@@ -1269,12 +1273,15 @@ $jit.ST.Label.DOM = new Class({
posx = pos.x * sx + ox,
posy = pos.y * sy + oy;

- if(dim.align == "center") {
+ w = Math.round(w*sx);
+ h = Math.round(h*sy);
+
+ if(align == "center") {
labelPos= {
x: Math.round(posx - w / 2 + radius.width/2),
y: Math.round(posy - h / 2 + radius.height/2)
};
- } else if (dim.align == "left") {
+ } else if (align == "left") {
orn = config.orientation;
if(orn == "bottom" || orn == "top") {
labelPos= {
@@ -1287,7 +1294,7 @@ $jit.ST.Label.DOM = new Class({
y: Math.round(posy - h / 2 + radius.height/2)
};
}
- } else if(dim.align == "right") {
+ } else if(align == "right") {
orn = config.orientation;
if(orn == "bottom" || orn == "top") {
labelPos= {

Ensure that the labels don't disappear if only the left-hand or upper
edge of the node is outside the canvas boundary. The entire node
(upper-left-corner and lower-right-corner) must be outside the canvas
boundary in order to hide the node:

@@ -1305,7 +1312,11 @@ $jit.ST.Label.DOM = new Class({
var style = tag.style;
style.left = labelPos.x + 'px';
style.top = labelPos.y + 'px';
- style.display = this.fitsInCanvas(labelPos, canvas)? '' :
'none';
+ var lrc = {
+ x: labelPos.x + w,
+ y: labelPos.y + h
+ };
+ style.display = this.fitsInCanvas(labelPos, canvas) ||
this.fitsInCanvas(lrc, canvas) ? '' : 'none';
controller.onPlaceLabel(tag, node);
}
});

More custom alignment changes:

@@ -1387,13 +1398,15 @@ $jit.ST.Plot.NodeTypes = new Class({
'circle': {
'render': function(node, canvas) {
var dim = node.getData('dim'),
- pos = this.getAlignedPos(node.pos.getc(true), dim, dim),
+ align = node.getData('align'),
+ pos = this.getAlignedPos(node.pos.getc(true), dim, dim,
align),
dim2 = dim/2;
this.nodeHelper.circle.render('fill', {x:pos.x+dim2, y:pos.y
+dim2}, dim2, canvas);
},
'contains': function(node, pos) {
var dim = node.getData('dim'),
- npos = this.getAlignedPos(node.pos.getc(true), dim, dim),
+ align = node.getData('align'),
+ npos = this.getAlignedPos(node.pos.getc(true), dim, dim,
align),
dim2 = dim/2;
this.nodeHelper.circle.contains({x:npos.x+dim2, y:npos.y+dim2},
pos, dim2);
}
@@ -1402,12 +1415,14 @@ $jit.ST.Plot.NodeTypes = new Class({
'render': function(node, canvas) {
var dim = node.getData('dim'),
dim2 = dim/2,
- pos = this.getAlignedPos(node.pos.getc(true), dim, dim);
+ align = node.getData('align'),
+ pos = this.getAlignedPos(node.pos.getc(true), dim, dim,
align);
this.nodeHelper.square.render('fill', {x:pos.x+dim2, y:pos.y
+dim2}, dim2, canvas);
},
'contains': function(node, pos) {
var dim = node.getData('dim'),
- npos = this.getAlignedPos(node.pos.getc(true), dim, dim),
+ align = node.getData('align'),
+ npos = this.getAlignedPos(node.pos.getc(true), dim, dim,
align),
dim2 = dim/2;
this.nodeHelper.square.contains({x:npos.x+dim2, y:npos.y+dim2},
pos, dim2);
}
@@ -1416,13 +1431,15 @@ $jit.ST.Plot.NodeTypes = new Class({
'render': function(node, canvas) {
var width = node.getData('width'),
height = node.getData('height'),
- pos = this.getAlignedPos(node.pos.getc(true), width,
height);
+ align = node.getData('align'),
+ pos = this.getAlignedPos(node.pos.getc(true), width,
height, align);
this.nodeHelper.ellipse.render('fill', {x:pos.x+width/2, y:pos.y
+height/2}, width, height, canvas);
},
'contains': function(node, pos) {
var width = node.getData('width'),
height = node.getData('height'),
- npos = this.getAlignedPos(node.pos.getc(true), width,
height);
+ align = node.getData('align'),
+ npos = this.getAlignedPos(node.pos.getc(true), width,
height, align);
this.nodeHelper.ellipse.contains({x:npos.x+width/2, y:npos.y
+height/2}, pos, width, height);
}
},
@@ -1430,13 +1447,15 @@ $jit.ST.Plot.NodeTypes = new Class({
'render': function(node, canvas) {
var width = node.getData('width'),
height = node.getData('height'),
- pos = this.getAlignedPos(node.pos.getc(true), width,
height);
+ align = node.getData('align'),
+ pos = this.getAlignedPos(node.pos.getc(true), width,
height, align);
this.nodeHelper.rectangle.render('fill', {x:pos.x+width/2,
y:pos.y+height/2}, width, height, canvas);
},
'contains': function(node, pos) {
var width = node.getData('width'),
height = node.getData('height'),
- npos = this.getAlignedPos(node.pos.getc(true), width,
height);
+ align = node.getData('align'),
+ npos = this.getAlignedPos(node.pos.getc(true), width,
height, align);
this.nodeHelper.rectangle.contains({x:npos.x+width/2, y:npos.y
+height/2}, pos, width, height);
}
}
Message has been deleted

Particlem

unread,
Sep 19, 2011, 11:35:07 AM9/19/11
to JavaScript InfoVis Toolkit
These enhancements are great. Hope they end up in the base package.
Were you able to do the multi-tree in a single ST? I've been
overlaying two trees, which is proving difficult.

Nicolas Garcia Belmonte

unread,
Sep 19, 2011, 6:18:41 PM9/19/11
to javascript-information...@googlegroups.com
Well done! I love the customization.

> --
> You received this message because you are subscribed to the Google Groups "JavaScript InfoVis Toolkit" group.
> To post to this group, send email to javascript-information...@googlegroups.com.
> To unsubscribe from this group, send email to javascript-information-visua...@googlegroups.com.
> For more options, visit this group at http://groups.google.com/group/javascript-information-visualization-toolkit?hl=en.
>
>

--
Nicolas Garcia Belmonte - http://philogb.github.com/

Reply all
Reply to author
Forward
0 new messages