Permalink
Browse files

voronoi prototype

  • Loading branch information...
1 parent 21a120e commit b3a76f535f1f391bd8037401d23a2f97cf646eb1 ZHANG Bei committed with Mar 28, 2011
View
59 Source/Graph/Graph.Plot.js
@@ -1,4 +1,4 @@
-/*
+ /*
* File: Graph.Plot.js
*/
@@ -20,6 +20,7 @@ Graph.Plot = {
this.node = viz.config.Node;
this.edge = viz.config.Edge;
this.animation = new Animation;
+ debugger;
this.nodeTypes = new klass.Plot.NodeTypes;
this.edgeTypes = new klass.Plot.EdgeTypes;
this.labels = viz.labels;
@@ -42,7 +43,8 @@ Graph.Plot = {
'angularWidth':'number',
'span':'number',
'valueArray':'array-number',
- 'dimArray':'array-number'
+ 'dimArray':'array-number',
+ 'vertics':'polygon'
//'colorArray':'array-color'
},
@@ -170,6 +172,59 @@ Graph.Plot = {
'edge-style': function(elem, props, delta) {
this['edge'](elem, props, delta, 'canvas', 'getCanvasStyle', 'setCanvasStyle');
+ },
+
+ 'polygon': function(elem, prop, delta, getter, setter) {
+ var from = elem[getter](prop, 'start'),
+ to = elem[getter](prop, 'end'),
+ cur = [];
+ var dist = function (c1, c2) {
+ var dx = c1.x - c2.x, dy = c1.y - c2.y;
+ return dx * dx + dy * dy;
+ };
+ if(typeof from.offset == 'undefined') {
+ if(from === 0) {
+ from = $.map(to,function(){return new $jit.Complex(0, 0);});
+ from.offset = 0;
+ }
+ else {
+ if(from.length == 0) {
+ from.push(new $jit.Complex(0, 0));
+ }
+ while(from.length < to.length) {
+ from.push(from[0]);
+ }
+ while(from.length > to.length) {
+ to.push(to[0]);
+ }
+ from = from.map(function(t){ return t || new $jit.Complex(0, 0);});
+ to = to.map(function(t){ return t || new $jit.Complex(0, 0);});
+ if (from.length == 0) return;
+ var l = from.length;
+ var minDist = 1e300;
+ for(var offset = 0; offset < l; offset ++) {
+ var d = 0;
+ for(var i = 0; i < l; i++){
+ d += dist(from[(offset + i) % l], to[i]);
+ }
+ if (d < minDist)
+ {
+ from.offset = offset;
+ minDist = d;
+ }
+ }
+ }
+ }
+
+ for(var i=0, l=from.length; i<l; i++) {
+ var fromi = from[(i + from.offset) % l], toi = to[i];
+ cur.push(new $jit.Complex(
+ this.compute(fromi.x, toi.x, delta),
+ this.compute(fromi.y, toi.y, delta)
+ ));
+ }
+ elem[setter](prop, cur);
+
}
},
View
21 Source/Visualizations/Treemap.js
@@ -92,7 +92,8 @@ TM.Base = {
//right, Firefox?
width: 3,
height: 3,
- color: '#444'
+ color: '#444',
+ props: 'node-property:width:height'
},
Label: {
textAlign: 'center',
@@ -120,7 +121,10 @@ TM.Base = {
}, canvasConfig.background);
}
this.canvas = new Canvas(this, canvasConfig);
- this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
+ this.config.labelContainer = (
+ typeof canvasConfig.injectInto == 'string'?
+ canvasConfig.injectInto :
+ canvasConfig.injectInto.id) + '-label';
}
this.graphOptions = {
@@ -158,7 +162,7 @@ TM.Base = {
this.config.levelsToShow > 0 && this.geom.setRightLevelToShow(this.graph.getNode(this.clickedNode
&& this.clickedNode.id || this.root));
this.fx.animate($.merge(this.config, {
- modes: ['linear', 'node-property:width:height'],
+ modes: ['linear', this.config.Node.props],
onComplete: function() {
that.busy = false;
}
@@ -216,7 +220,6 @@ TM.Base = {
enter: function(n){
if(this.busy) return;
this.busy = true;
-
var that = this,
config = this.config,
graph = this.graph,
@@ -248,8 +251,8 @@ TM.Base = {
//TODO(nico) commenting this line didn't seem to throw errors...
that.clickedNode = previousClickedNode;
that.fx.animate({
- modes:['linear', 'node-property:width:height'],
duration: 2 * that.config.duration / 3,
+ modes:['linear', that.config.Node.props],
onComplete: function() {
that.busy = false;
//TODO(nico) check comment above
@@ -297,7 +300,7 @@ TM.Base = {
return;
}
//final plot callback
- callback = {
+ var callback = {
onComplete: function() {
that.clickedNode = parent;
if(config.request) {
@@ -325,8 +328,13 @@ TM.Base = {
//animate the visible subtree only
this.clickedNode = previousClickedNode;
this.fx.animate({
+<<<<<<< HEAD
modes:['linear', 'node-property:width:height'],
duration: 2 * this.config.duration / 3,
+=======
+ modes:['linear', this.config.Node.props],
+ duration: 1000,
+>>>>>>> voronoi prototype
onComplete: function() {
//animate the parent subtree
that.clickedNode = clickedNode;
@@ -561,7 +569,6 @@ TM.Label.Native = new Class({
height = node.getData('height'),
x = pos.x + width/2,
y = pos.y;
-
ctx.fillText(node.name, x, y, width);
}
});
View
720 Source/Visualizations/Voronoi.js
@@ -0,0 +1,720 @@
+/**
+ *
+ * @author ZHANG BEi
+ */
+
+(function(){
+
+ if(!TM) TM = {};
+ $jit.Voronoi = {};
+ var V = $jit.Voronoi;
+
+ /**
+ * Tests whether all points are in a polygon
+ */
+ var pointInPolygon = function(pts, polygon) {
+ if(polygon.length < 3) return false;
+ var conj = [];
+ $jit.util.each(pts, function(){ conj.push(0); });
+ for (var i = 0; i < polygon.length; i++) {
+ var start = polygon[i];
+ var stop = polygon[i + 1] || polygon[0];
+ $jit.util.each(pts, function(pos, i) {
+ if (start.y >= pos.y && stop.y < pos.y || start.y <= pos.y
+ && stop.y > pos.y) {
+ // find inversed k
+ var k = (stop.x - start.x) / (stop.y - start.y);
+ var cx = (pos.y - start.y) * k + start.x;
+ if (cx == pos.x)
+ return true;
+ else if (cx > pos.x)
+ conj[i]++;
+ }
+ });
+ }
+ conj = $jit.util.map(conj, function(c){ return c % 2 != 0;});
+ return conj;
+ };
+
+ /**
+ * Counter-clockwise is positive.
+ * @param p1, p2, p3 [x,y]
+ */
+ var cross = $jit.Voronoi.cross = function(p1, p2, p3) {
+ return p1.x * p2.y + p2.x * p3.y + p3.x * p1.y -
+ p1.x * p3.y - p2.x * p1.y - p3.x * p2.y;
+ }
+ /**
+ * Reserves those l[0],l[1],c[i] is counter-clockwise
+ * @param c [p1,p2,...]
+ * @param l [p1,p2] as a ex line
+ */
+ var convexCut = $jit.Voronoi.convexCut = function(c, l) {
+ if (c.length < 3) return [];
+ var result = [];
+ for(var i = 0; i < c.length; i++)
+ {
+ var start = c[i];
+ var stop = c[i + 1] || c[0];
+ if (start.x == stop.x && start.y == stop.y) continue;
+ var c1, c2;
+ if ((c1 = cross(l[0], l[1], start)) > 0) {
+ if ((c2 = cross(l[0], l[1], stop)) >= 0) {
+ result.push(stop);
+ } else {
+ var sc = c1 / (c1 - c2);
+ if (c1 - c2)
+ result.push(new $jit.Complex(
+ (stop.x - start.x) * sc + start.x,
+ (stop.y - start.y) * sc + start.y
+ ));
+ }
+ } else {
+ if ((c2 = cross(l[0], l[1], stop)) > 0) {
+ var sc = c1 / (c1 - c2);
+ result.push(new $jit.Complex(
+ (stop.x - start.x) * sc + start.x,
+ (stop.y - start.y) * sc + start.y
+ ));
+ result.push(stop);
+ } else if (c2 == 0) {
+ if (c1)
+ result.push(stop);
+ }
+ }
+ }
+ return result;
+ }
+
+ var convexIntersect = $jit.Voronoi.convexIntersect = function(c1, c2) {
+ if (c1.length < 3) return [];
+ if (c2.length < 3) return [];
+ for (var i = 0; i < c2.length ; i++) {
+ var start = c2[i];
+ var stop = c2[i + 1] || c2[0];
+ c1 = convexCut(c1, [start, stop]);
+ }
+ return c1;
+ };
+
+ var bisector = function(p1, p2){
+ return [
+ 2 * (p2.x - p1.x),
+ 2 * (p2.y - p1.y),
+ -(p2.x * p2.x - p1.x * p1.x + p2.y * p2.y - p1.y * p1.y)
+ ];
+ };
+ V.bisector = bisector;
+
+ var intersection = function(l1, l2) {
+ var det = l1[0]*l2[1] - l1[1] * l2[0];
+ if(det == 0)
+ if(l1[0] * l2[2] - l1[2] * l2[0] == 0) return l1;
+ else
+ return;
+ return new $jit.Complex(
+ -(l1[2] * l2[1] - l1[1] * l2[2]) / det,
+ -(l1[0] * l2[2] - l1[2] * l2[0]) / det
+ );
+ }
+ V.intersection = intersection;
+
+ // Adapted from Nicolas Garcia Belmonte's JIT implementation:
+ // http://blog.thejit.org/2010/02/12/voronoi-tessellation/
+ // http://blog.thejit.org/assets/voronoijs/voronoi.js
+ // See lib/jit/LICENSE for details.
+
+ var d3_voronoi_opposite = {"l": "r", "r": "l"};
+ var c = $jit.Complex;
+ function d3_voronoi_tessellate(vertices, callback) {
+ var Sites = {
+ list: vertices
+ .map(function(v, i) {
+ return {
+ index: i,
+ x: v[0],
+ y: v[1]
+ };
+ })
+ .sort(function(a, b) {
+ return a.y < b.y ? -1
+ : a.y > b.y ? 1
+ : a.x < b.x ? -1
+ : a.x > b.x ? 1
+ : 0;
+ }),
+ bottomSite: null
+ };
+
+ var EdgeList = {
+ list: [],
+ leftEnd: null,
+ rightEnd: null,
+
+ init: function() {
+ EdgeList.leftEnd = EdgeList.createHalfEdge(null, "l");
+ EdgeList.rightEnd = EdgeList.createHalfEdge(null, "l");
+ EdgeList.leftEnd.r = EdgeList.rightEnd;
+ EdgeList.rightEnd.l = EdgeList.leftEnd;
+ EdgeList.list.unshift(EdgeList.leftEnd, EdgeList.rightEnd);
+ },
+
+ createHalfEdge: function(edge, side) {
+ return {
+ edge: edge,
+ side: side,
+ vertex: null,
+ "l": null,
+ "r": null
+ };
+ },
+
+ insert: function(lb, he) {
+ he.l = lb;
+ he.r = lb.r;
+ lb.r.l = he;
+ lb.r = he;
+ },
+
+ leftBound: function(p) {
+ var he = EdgeList.leftEnd;
+ do {
+ he = he.r;
+ } while (he != EdgeList.rightEnd && Geom.rightOf(he, p));
+ he = he.l;
+ return he;
+ },
+
+ del: function(he) {
+ he.l.r = he.r;
+ he.r.l = he.l;
+ he.edge = null;
+ },
+
+ right: function(he) {
+ return he.r;
+ },
+
+ left: function(he) {
+ return he.l;
+ },
+
+ leftRegion: function(he) {
+ return he.edge == null
+ ? Sites.bottomSite
+ : he.edge.region[he.side];
+ },
+
+ rightRegion: function(he) {
+ return he.edge == null
+ ? Sites.bottomSite
+ : he.edge.region[d3_voronoi_opposite[he.side]];
+ }
+ };
+
+ var Geom = {
+
+ bisect: function(s1, s2) {
+ var newEdge = {
+ region: {"l": s1, "r": s2},
+ ep: {"l": null, "r": null}
+ };
+
+ var dx = s2.x - s1.x,
+ dy = s2.y - s1.y,
+ adx = dx > 0 ? dx : -dx,
+ ady = dy > 0 ? dy : -dy;
+
+ newEdge.c = s1.x * dx + s1.y * dy
+ + (dx * dx + dy * dy) * .5;
+
+ if (adx > ady) {
+ newEdge.a = 1;
+ newEdge.b = dy / dx;
+ newEdge.c /= dx;
+ } else {
+ newEdge.b = 1;
+ newEdge.a = dx / dy;
+ newEdge.c /= dy;
+ }
+
+ return newEdge;
+ },
+
+ intersect: function(el1, el2) {
+ var e1 = el1.edge,
+ e2 = el2.edge;
+ if (!e1 || !e2 || (e1.region.r == e2.region.r)) {
+ return null;
+ }
+ var d = (e1.a * e2.b) - (e1.b * e2.a);
+ if (Math.abs(d) < 1e-10) {
+ return null;
+ }
+ var xint = (e1.c * e2.b - e2.c * e1.b) / d,
+ yint = (e2.c * e1.a - e1.c * e2.a) / d,
+ e1r = e1.region.r,
+ e2r = e2.region.r,
+ el,
+ e;
+ if ((e1r.y < e2r.y) ||
+ (e1r.y == e2r.y && e1r.x < e2r.x)) {
+ el = el1;
+ e = e1;
+ } else {
+ el = el2;
+ e = e2;
+ }
+ var rightOfSite = (xint >= e.region.r.x);
+ if ((rightOfSite && (el.side == "l")) ||
+ (!rightOfSite && (el.side == "r"))) {
+ return null;
+ }
+ return {
+ x: xint,
+ y: yint
+ };
+ },
+
+ rightOf: function(he, p) {
+ var e = he.edge,
+ topsite = e.region.r,
+ rightOfSite = (p.x > topsite.x);
+
+ if (rightOfSite && (he.side == "l")) {
+ return 1;
+ }
+ if (!rightOfSite && (he.side == "r")) {
+ return 0;
+ }
+ if (e.a == 1) {
+ var dyp = p.y - topsite.y,
+ dxp = p.x - topsite.x,
+ fast = 0,
+ above = 0;
+
+ if ((!rightOfSite && (e.b < 0)) ||
+ (rightOfSite && (e.b >= 0))) {
+ above = fast = (dyp >= e.b * dxp);
+ } else {
+ above = ((p.x + p.y * e.b) > e.c);
+ if (e.b < 0) {
+ above = !above;
+ }
+ if (!above) {
+ fast = 1;
+ }
+ }
+ if (!fast) {
+ var dxs = topsite.x - e.region.l.x;
+ above = (e.b * (dxp * dxp - dyp * dyp)) <
+ (dxs * dyp * (1 + 2 * dxp / dxs + e.b * e.b));
+
+ if (e.b < 0) {
+ above = !above;
+ }
+ }
+ } else /* e.b == 1 */ {
+ var yl = e.c - e.a * p.x,
+ t1 = p.y - yl,
+ t2 = p.x - topsite.x,
+ t3 = yl - topsite.y;
+
+ above = (t1 * t1) > (t2 * t2 + t3 * t3);
+ }
+ return he.side == "l" ? above : !above;
+ },
+
+ endPoint: function(edge, side, site) {
+ edge.ep[side] = site;
+ if (!edge.ep[d3_voronoi_opposite[side]]) return;
+ callback(edge);
+ },
+
+ distance: function(s, t) {
+ var dx = s.x - t.x,
+ dy = s.y - t.y;
+ return Math.sqrt(dx * dx + dy * dy);
+ }
+ };
+
+ var EventQueue = {
+ list: [],
+
+ insert: function(he, site, offset) {
+ he.vertex = site;
+ he.ystar = site.y + offset;
+ for (var i=0, list=EventQueue.list, l=list.length; i<l; i++) {
+ var next = list[i];
+ if (he.ystar > next.ystar ||
+ (he.ystar == next.ystar &&
+ site.x > next.vertex.x)) {
+ continue;
+ } else {
+ break;
+ }
+ }
+ list.splice(i, 0, he);
+ },
+
+ del: function(he) {
+ for (var i=0, ls=EventQueue.list, l=ls.length; i<l && (ls[i] != he); ++i) {}
+ ls.splice(i, 1);
+ },
+
+ empty: function() { return EventQueue.list.length == 0; },
+
+ nextEvent: function(he) {
+ for (var i=0, ls=EventQueue.list, l=ls.length; i<l; ++i) {
+ if (ls[i] == he) return ls[i+1];
+ }
+ return null;
+ },
+
+ min: function() {
+ var elem = EventQueue.list[0];
+ return {
+ x: elem.vertex.x,
+ y: elem.ystar
+ };
+ },
+
+ extractMin: function() {
+ return EventQueue.list.shift();
+ }
+ };
+
+ EdgeList.init();
+ Sites.bottomSite = Sites.list.shift();
+
+ var newSite = Sites.list.shift(), newIntStar;
+ var lbnd, rbnd, llbnd, rrbnd, bisector;
+ var bot, top, temp, p, v;
+ var e, pm;
+
+ while (true) {
+ if (!EventQueue.empty()) {
+ newIntStar = EventQueue.min();
+ }
+ if (newSite && (EventQueue.empty()
+ || newSite.y < newIntStar.y
+ || (newSite.y == newIntStar.y
+ && newSite.x < newIntStar.x))) { //new site is smallest
+ lbnd = EdgeList.leftBound(newSite);
+ rbnd = EdgeList.right(lbnd);
+ bot = EdgeList.rightRegion(lbnd);
+ e = Geom.bisect(bot, newSite);
+ bisector = EdgeList.createHalfEdge(e, "l");
+ EdgeList.insert(lbnd, bisector);
+ p = Geom.intersect(lbnd, bisector);
+ if (p) {
+ EventQueue.del(lbnd);
+ EventQueue.insert(lbnd, p, Geom.distance(p, newSite));
+ }
+ lbnd = bisector;
+ bisector = EdgeList.createHalfEdge(e, "r");
+ EdgeList.insert(lbnd, bisector);
+ p = Geom.intersect(bisector, rbnd);
+ if (p) {
+ EventQueue.insert(bisector, p, Geom.distance(p, newSite));
+ }
+ newSite = Sites.list.shift();
+ } else if (!EventQueue.empty()) { //intersection is smallest
+ lbnd = EventQueue.extractMin();
+ llbnd = EdgeList.left(lbnd);
+ rbnd = EdgeList.right(lbnd);
+ rrbnd = EdgeList.right(rbnd);
+ bot = EdgeList.leftRegion(lbnd);
+ top = EdgeList.rightRegion(rbnd);
+ v = lbnd.vertex;
+ Geom.endPoint(lbnd.edge, lbnd.side, v);
+ Geom.endPoint(rbnd.edge, rbnd.side, v);
+ EdgeList.del(lbnd);
+ EventQueue.del(rbnd);
+ EdgeList.del(rbnd);
+ pm = "l";
+ if (bot.y > top.y) {
+ temp = bot;
+ bot = top;
+ top = temp;
+ pm = "r";
+ }
+ e = Geom.bisect(bot, top);
+ bisector = EdgeList.createHalfEdge(e, pm);
+ EdgeList.insert(llbnd, bisector);
+ Geom.endPoint(e, d3_voronoi_opposite[pm], v);
+ p = Geom.intersect(llbnd, bisector);
+ if (p) {
+ EventQueue.del(llbnd);
+ EventQueue.insert(llbnd, p, Geom.distance(p, bot));
+ }
+ p = Geom.intersect(bisector, rrbnd);
+ if (p) {
+ EventQueue.insert(bisector, p, Geom.distance(p, bot));
+ }
+ } else {
+ break;
+ }
+ }//end while
+
+ for (lbnd = EdgeList.right(EdgeList.leftEnd);
+ lbnd != EdgeList.rightEnd;
+ lbnd = EdgeList.right(lbnd)) {
+ callback(lbnd.edge);
+ }
+ }
+
+ /**
+ * Voronoi Tessellation with Fortune's algorithm
+ * @param vertices [[x1, y1], [x2, y2], ...]
+ * @returns polygons [[[x1, y1], [x2, y2], ...], ...]
+ */
+ var voronoiFortune = function (vertices, boundary) {
+ if (vertices.length == 1)
+ return [boundary];
+ else if (vertices.length == 2) {
+ var v1 = vertices[0];
+ var v2 = vertices[1];
+ var cx = (v1[0] + v2[0]) / 2;
+ var cy = (v1[1] + v2[1]) / 2;
+ var dx = v1[0] - cx;
+ var dy = v1[0] - cy;
+ var l1 = new c(cx - dy, cy + dx);
+ var l2 = new c(cx + dy, cy - dx);
+ return [convexCut(boundary, [l1,l2]), convexCut(boundary, [l2,l1])];
+ }
+ var polygons = vertices.map(function() { return []; });
+ // Note: we expect the caller to clip the polygons, if needed.
+ d3_voronoi_tessellate(vertices, function(e) {
+ var s1,
+ s2,
+ x1,
+ x2,
+ y1,
+ y2;
+ if (e.a == 1 && e.b >= 0) {
+ s1 = e.ep.r;
+ s2 = e.ep.l;
+ } else {
+ s1 = e.ep.l;
+ s2 = e.ep.r;
+ }
+ if (e.a == 1) {
+ y1 = s1 ? s1.y : -1e6;
+ x1 = e.c - e.b * y1;
+ y2 = s2 ? s2.y : 1e6;
+ x2 = e.c - e.b * y2;
+ } else {
+ x1 = s1 ? s1.x : -1e6;
+ y1 = e.c - e.a * x1;
+ x2 = s2 ? s2.x : 1e6;
+ y2 = e.c - e.a * x2;
+ }
+ var v1 = new c(x1, y1),
+ v2 = new c(x2, y2);
+ polygons[e.region.l.index].push(v1, v2);
+ polygons[e.region.r.index].push(v1, v2);
+ });
+
+ // Reconnect the polygon segments into counterclockwise loops.
+ polygons = polygons.map(function(polygon, i) {
+ var cx = vertices[i][0],
+ cy = vertices[i][1];
+ polygon.forEach(function(v) {
+ v.angle = Math.atan2(v.x - cx, v.y - cy);
+ });
+ return polygon.sort(function(a, b) {
+ return a.angle - b.angle;
+ }).filter(function(d, i) {
+ return !i || (d.angle - polygon[i - 1].angle > 1e-10);
+ });
+ });
+ if (boundary) {
+ polygons = polygons.map(function(p) {
+ return $jit.Voronoi.convexIntersect(p, boundary);
+ });
+ }
+
+ return polygons;
+ };
+ $jit.Voronoi.voronoiFortune = voronoiFortune;
+
+
+ TM.Plot.NodeTypes.implement({
+ 'polygon' : {
+ 'render' : function(node, canvas, animating) {
+ var leaf = this.viz.leaf(node), config = this.config,
+ border = node.getData('border'),
+ // offset = config.offset,
+ vertics = node.getData('vertics'), ctx = canvas.getCtx(),
+ titleHeight = config.titleHeight;
+ if (!vertics) {
+ return;
+ }
+ if (vertics.length == 0) return;
+ var pts = vertics.slice(0);
+ if (leaf) {
+ // TODO: Implement the offset config
+ // Create cushion gradient
+ if (config.cushion) {
+ var x = 0, y = 0, minX = pts[0].x, maxX = pts[0].x, minY = pts[0].y, maxY = pts[0].y;
+ $jit.util.each(pts, function(pt) {
+ x += pt.x;
+ y += pt.y;
+ if (minX > pt.x)
+ minX = pt.x;
+ if (minY > pt.y)
+ minY = pt.y;
+ if (maxX < pt.x)
+ minX = pt.x;
+ if (maxY < pt.y)
+ maxY = pt.y;
+ });
+ x /= pts.length;
+ y /= pts.length;
+ var width = maxX - minX + 1, height = maxY - minY + 1;
+ var lg = ctx.createRadialGradient(x, y, 1, x, y,
+ width < height ? height : width);
+ var color = node.getData('color');
+ var colorGrad = $.rgbToHex($.map($.hexToRgb(color),
+ function(r) {
+ return r * 0.2 >> 0;
+ }));
+ lg.addColorStop(0, color);
+ lg.addColorStop(1, colorGrad);
+ ctx.fillStyle = lg;
+ }
+ // Fill polygon
+ ctx.beginPath();
+ ctx.moveTo(pts[0].x, pts[0].y);
+ for (var i = 1; i < vertics.length; i++) {
+ ctx.lineTo(pts[i].x, pts[i].y);
+ }
+ ctx.closePath();
+ ctx.fill();
+ if (border) {
+ ctx.save();
+ ctx.strokeStyle = border;
+ ctx.stroke();
+ ctx.restore();
+ }
+ } else if(titleHeight > 0) {
+ // Fill polygon
+ ctx.beginPath();
+ ctx.moveTo(pts[0].x, pts[0].y);
+ for (var i = 1; i < vertics.length; i++) {
+ ctx.lineTo(pts[i].x, pts[i].y);
+ }
+ ctx.lineTo(pts[0].x, pts[0].y);
+ ctx.save();
+ ctx.lineWidth = 1;
+ ctx.stroke();
+ ctx.restore();
+ ctx.closePath();
+ }
+ },
+ 'contains' : function(node, pos) {
+ if(!this.viz.leaf(node)) return false;
+ if(this.viz.clickedNode && !node.isDescendantOf(this.viz.clickedNode.id) || node.ignore) return false;
+ var ps = node.getData('vertics').slice(0);
+ $jit.util.each(ps, function(pt, i) {
+ ps[i] = pt.getc(true);
+ });
+ return pointInPolygon([pos], ps)[0];
+ }
+ }});
+
+ Layouts.TM.Voronoi = new Class({
+ Implements : Layouts.TM.Area,
+ compute: function(prop) {
+ var root = this.graph.getNode(this.clickedNode && this.clickedNode.id || this.root);
+ this.controller.onBeforeCompute(root);
+ var size = this.canvas.getSize(),
+ config = this.config,
+ width = size.width,
+ height = size.height;
+ this.graph.computeLevels(this.root, 0, 0);
+ //set root position and dimensions
+ root.getPos(prop).setc(-5, -5);
+ if(!root.histoPos)
+ root.histoPos = [];
+ root.histoPos[0] = [0, 0];
+ var bound = [
+ new c(-width/2,-height/2),
+ new c(width/2,-height/2),
+ new c(width/2,height/2),
+ new c(-width/2,height/2)
+ ];
+ root.setData('vertics', bound, prop);
+ root.setData('width', width, prop);
+ root.setData('height', height, prop);
+ this.computePositions(root, bound, prop, 0);
+ this.controller.onAfterCompute(root);
+ },
+
+ computePositions : function(node, bound, prop, level) {
+ // FIXME: Where can i find the level of each node?
+ var chs = node.getSubnodes([1, 1], "ignore"),
+ config = this.config,
+ max = Math.max;
+ var extent = [0, 0, 0, 0];
+ var histoPos = node.histoPos[level] || [0, 0];
+ $jit.util.each(bound, function(p) {
+ if(extent[0] > p.x) extent[0] = p.x;
+ if(extent[1] > p.y) extent[1] = p.y;
+ if(extent[2] < p.x) extent[2] = p.x;
+ if(extent[3] < p.y) extent[3] = p.y;
+ });
+ node.setData('width' , extent[2] - extent[0], prop);
+ node.setData('height' , extent[3] - extent[1], prop);
+ node.getPos(prop).setc(histoPos[0] - (extent[2] - extent[0]) / 2, histoPos[1]);
+ if (chs.length > 0) {
+ var sites = $jit.util.map(chs, function(ch) {
+ if(!ch.histoPos) ch.histoPos = [];
+ if(ch.histoPos[level + 1]) {
+ return ch.histoPos[level + 1];
+ }
+ var c = new $jit.Complex(
+ Math.random() * (extent[2] - extent[0]) + extent[0],
+ Math.random() * (extent[3] - extent[1]) + extent[1]
+ );
+ while(!pointInPolygon([c], bound)[0]) {
+ c = new $jit.Complex(
+ Math.random() * (extent[2] - extent[0]) + extent[0],
+ Math.random() * (extent[3] - extent[1]) + extent[1]
+ );
+ }
+ return ch.histoPos[level + 1] = [c.x, c.y];
+ });
+ var polygons = voronoiFortune(sites, bound);
+ var self = this;
+ $jit.util.each(chs, function(ch, i) {
+ var vertics = polygons[i];
+ ch.setData('vertics', vertics, prop);
+ self.computePositions(ch, vertics, prop, level + 1);
+ });
+ }
+ }
+ });
+
+ /*
+ * Class: TM.Voronoi
+ *
+ * A Voronoi TreeMap visualization.
+ *
+ * Implements:
+ *
+ * All <TM.Base> methods and properties.
+ */
+ TM.Voronoi = new Class({
+ Implements : [Loader, Extras, TM.Base, Layouts.TM.Voronoi],
+ initialize : function(config) {
+ config.Node = config.Node || {};
+ config.Node.type = 'polygon';
+ config.Node.props = 'node-property:width:height:vertics';
+ this.config = config;
+ TM.Base.initialize.apply(this, [config]);
+ }
+ });
+
+})();
View
16 Templates/Treemap/left.html
@@ -33,10 +33,18 @@
<tr>
<td>
<label for="r-sd">SliceAndDice </label>
- </td>
- <td>
- <input type="radio" id="r-sd" name="layout" value="bottom" />
- </td>
+ </td>
+ <td>
+ <input type="radio" id="r-sd" name="layout" value="bottom" />
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <label for="r-vn">Voronoi </label>
+ </td>
+ <td>
+ <input type="radio" id="r-vn" name="layout" value="voronoi" />
+ </td>
</tr>
</table>
</div>
View
16 Templates/Voronoi/left.html
@@ -0,0 +1,16 @@
+$def with (model, type, number, max)
+
+$if number > 1:
+ <a href="/testcase/$type/${number -1}/" title="previous">previous</a>
+$if number < max:
+ <a href="/testcase/$type/${number +1}/" title="next">next</a>
+
+
+<div class="text">
+<h4>
+$model['Title']
+</h4>
+$:model['Description']
+</div>
+
+<a id="back" href="#" class="theme button white">Go to Parent</a>
View
3 Templates/Voronoi/test1.html
@@ -0,0 +1,3 @@
+$def with (model)
+
+<div id="inner-details"></div>
View
3 Templates/Voronoi/test2.html
@@ -0,0 +1,3 @@
+$def with (model)
+
+<div id="inner-details"></div>
View
3 Templates/Voronoi/test3.html
@@ -0,0 +1,3 @@
+$def with (model)
+
+<div id="inner-details"></div>
View
8 Tests/Treemap/test3.js
@@ -111,6 +111,8 @@ function init(){
var sq = $jit.id('r-sq'),
st = $jit.id('r-st'),
sd = $jit.id('r-sd');
+ vn = $jit.id('r-vn');
+
var util = $jit.util;
util.addEvent(sq, 'change', function() {
if(!sq.checked) return;
@@ -129,6 +131,12 @@ function init(){
tm.layout.orientation = "v";
tm.refresh();
});
+
+ util.addEvent(vn, 'change', function() {
+ if(!vn.checked) return;
+ util.extend(tm, new $jit.Layouts.TM.Voronoi);
+ tm.refresh();
+ });
//add event to the back button
var back = $jit.id('back');
$jit.util.addEvent(back, 'click', function() {
View
0 Tests/Voronoi/left.js
No changes.
View
49 Tests/Voronoi/test1.js
@@ -0,0 +1,49 @@
+function init(){
+ var idlist = $jit.id('infovis');
+ idlist.style.textAlign = "left";
+ idlist.style.padding = "5px";
+ var log = function(text) {
+ var element = document.createElement('div');
+ element.innerHTML = text;
+ idlist.appendChild(element);
+ }
+ var V = $jit.Voronoi;
+ // perftest(100000);
+
+ var bs1 = V.bisector({x:0,y:0},{x:1,y:1});
+ var bs2 = V.bisector({x:1,y:0},{x:0,y:1});
+ var int = V.intersection(bs1,bs2);
+ log('Intersection: (' + int.x + ', ' + int.y + ')');
+
+ var bs1 = V.bisector({x:0,y:0},{x:1,y:0});
+ var bs2 = V.bisector({x:0,y:0},{x:0,y:1});
+ var int = V.intersection(bs1,bs2);
+ log('Intersection: (' + int.x + ', ' + int.y + ')');
+
+ var bs1 = V.bisector({x:0,y:0},{x:1,y:0});
+ var bs2 = V.bisector({x:0,y:0},{x:2,y:0});
+ var int = V.intersection(bs1,bs2);
+ log('Intersection: (' + int + ')');
+
+ var bs1 = V.bisector({x:0,y:0},{x:1,y:0});
+ var bs2 = V.bisector({x:0,y:0},{x:1,y:0});
+ var int = V.intersection(bs1,bs2);
+ log('Intersection: (' + int + ')');
+
+ var c = $jit.Complex;
+ console.log($jit.Voronoi.convexCut(
+ [new c(0,0), new c(1,2), new c(2,0)],
+ [new c(0,1), new c(2,1)]
+ ));
+
+ console.log($jit.Voronoi.voronoiFortune(
+ [[0,0],[1,1]], [new c(0, 0), new c(0, 1), new c(1, 1), new c(1, 0)]));
+ console.log($jit.Voronoi.convexCut([new c(0, 0), new c(1, 2), new c(2, 0)],[new c(0, 1), new c(2, 1)]));
+ console.log($jit.Voronoi.convexCut([new c(0, 0), new c(1, 2), new c(2, 0)],[new c(2, 1), new c(0, 1)]));
+ console.log($jit.Voronoi.convexCut([new c(0, 0), new c(1, 2), new c(2, 0)],[new c(2, 1), new c(0, 0)]));
+ console.log($jit.Voronoi.convexCut([new c(1, 0), new c(2, 0), new c(1, 2),[0.5,1]],[new c(1, 0), new c(2, 0)]));
+
+ console.log($jit.Voronoi.convexIntersect(
+ [[0,0],[2,0],[1,2]],
+ [[0,2],[1,0],[2,0]]));
+}
View
117 Tests/Voronoi/test2.js
@@ -0,0 +1,117 @@
+function init(){
+ //init data
+ var json = "{\"children\": [{\"children\": [{\"children\": [], \"data\": {\"playcount\": \"276\", \"artist\": \"A Perfect Circle\", \"image\": \"http:\/\/userserve-ak.last.fm\/serve\/300x300\/11403219.jpg\", \"$area\": 276}, \"id\": \"album-Thirteenth Step\", \"name\": \"Thirteenth Step\"}, {\"children\": [], \"data\": {\"playcount\": \"271\", \"artist\": \"A Perfect Circle\", \"image\": \"http:\/\/userserve-ak.last.fm\/serve\/300x300\/11393921.jpg\", \"$area\": 271}, \"id\": \"album-Mer De Noms\", \"name\": \"Mer De Noms\"}], \"data\": {\"playcount\": 547, \"$area\": 547}, \"id\": \"artist_A Perfect Circle\", \"name\": \"A Perfect Circle\"}, {\"children\": [{\"children\": [], \"data\": {\"playcount\": \"209\", \"artist\": \"Mad Season\", \"image\": \"http:\/\/userserve-ak.last.fm\/serve\/300x300\/32349839.jpg\", \"$area\": 209}, \"id\": \"album-Above\", \"name\": \"Above\"}], \"data\": {\"playcount\": 209, \"$area\": 209}, \"id\": \"artist_Mad Season\", \"name\": \"Mad Season\"}, {\"children\": [{\"children\": [], \"data\": {\"playcount\": \"260\", \"artist\": \"Stone Temple Pilots\", \"image\": \"http:\/\/userserve-ak.last.fm\/serve\/300x300\/38753425.jpg\", \"$area\": 260}, \"id\": \"album-Tiny Music... Songs From the Vatican Gift Shop\", \"name\": \"Tiny Music... Songs From the Vatican Gift Shop\"}, {\"children\": [], \"data\": {\"playcount\": \"254\", \"artist\": \"Stone Temple Pilots\", \"image\": \"http:\/\/images.amazon.com\/images\/P\/B000002IU3.01.LZZZZZZZ.jpg\", \"$area\": 254}, \"id\": \"album-Core\", \"name\": \"Core\"}], \"data\": {\"playcount\": 514, \"$area\": 514}, \"id\": \"artist_Stone Temple Pilots\", \"name\": \"Stone Temple Pilots\"}, {\"children\": [{\"children\": [], \"data\": {\"playcount\": \"181\", \"artist\": \"Bush\", \"image\": \"http:\/\/userserve-ak.last.fm\/serve\/300x300\/8673371.jpg\", \"$area\": 181}, \"id\": \"album-The Science of Things\", \"name\": \"The Science of Things\"}], \"data\": {\"playcount\": 181, \"$area\": 181}, \"id\": \"artist_Bush\", \"name\": \"Bush\"}, {\"children\": [{\"children\": [], \"data\": {\"playcount\": \"229\", \"artist\": \"Foo Fighters\", \"image\": \"http:\/\/userserve-ak.last.fm\/serve\/300x300\/32579429.jpg\", \"$area\": 229}, \"id\": \"album-Echoes, Silence, Patience &amp; Grace\", \"name\": \"Echoes, Silence, Patience &amp; Grace\"}, {\"children\": [], \"data\": {\"playcount\": \"185\", \"artist\": \"Foo Fighters\", \"image\": \"http:\/\/images.amazon.com\/images\/P\/B0009HLDFU.01.MZZZZZZZ.jpg\", \"$area\": 185}, \"id\": \"album-In Your Honor (disc 2)\", \"name\": \"In Your Honor (disc 2)\"}], \"data\": {\"playcount\": 414, \"$area\": 414}, \"id\": \"artist_Foo Fighters\", \"name\": \"Foo Fighters\"}, {\"children\": [{\"children\": [], \"data\": {\"playcount\": \"398\", \"artist\": \"Luis Alberto Spinetta\", \"image\": \"http:\/\/images.amazon.com\/images\/P\/B00005LNP5.01._SCMZZZZZZZ_.jpg\", \"$area\": 398}, \"id\": \"album-Elija Y Gane\", \"name\": \"Elija Y Gane\"}, {\"children\": [], \"data\": {\"playcount\": \"203\", \"artist\": \"Luis Alberto Spinetta\", \"image\": \"http:\/\/images.amazon.com\/images\/P\/B0000B193V.01._SCMZZZZZZZ_.jpg\", \"$area\": 203}, \"id\": \"album-Para los Arboles\", \"name\": \"Para los Arboles\"}], \"data\": {\"playcount\": 601, \"$area\": 601}, \"id\": \"artist_Luis Alberto Spinetta\", \"name\": \"Luis Alberto Spinetta\"}, {\"children\": [{\"children\": [], \"data\": {\"playcount\": \"224\", \"artist\": \"Alice in Chains\", \"image\": \"http:\/\/userserve-ak.last.fm\/serve\/300x300\/26497553.jpg\", \"$area\": 224}, \"id\": \"album-Music Bank\", \"name\": \"Music Bank\"}, {\"children\": [], \"data\": {\"playcount\": \"217\", \"artist\": \"Alice in Chains\", \"image\": \"http:\/\/images.amazon.com\/images\/P\/B0000296JW.01.MZZZZZZZ.jpg\", \"$area\": 217}, \"id\": \"album-Music Bank (disc 1)\", \"name\": \"Music Bank (disc 1)\"}, {\"children\": [], \"data\": {\"playcount\": \"215\", \"artist\": \"Alice in Chains\", \"image\": \"http:\/\/images.amazon.com\/images\/P\/B0000296JW.01.MZZZZZZZ.jpg\", \"$area\": 215}, \"id\": \"album-Music Bank (disc 2)\", \"name\": \"Music Bank (disc 2)\"}, {\"children\": [], \"data\": {\"playcount\": \"181\", \"artist\": \"Alice in Chains\", \"image\": \"http:\/\/images.amazon.com\/images\/P\/B0000296JW.01.MZZZZZZZ.jpg\", \"$area\": 181}, \"id\": \"album-Music Bank (disc 3)\", \"name\": \"Music Bank (disc 3)\"}], \"data\": {\"playcount\": 837, \"$area\": 837}, \"id\": \"artist_Alice in Chains\", \"name\": \"Alice in Chains\"}, {\"children\": [{\"children\": [], \"data\": {\"playcount\": \"627\", \"artist\": \"Tool\", \"image\": \"http:\/\/userserve-ak.last.fm\/serve\/300x300\/8480501.jpg\", \"$area\": 627}, \"id\": \"album-10,000 Days\", \"name\": \"10,000 Days\"}], \"data\": {\"playcount\": 627, \"$area\": 627}, \"id\": \"artist_Tool\", \"name\": \"Tool\"}, {\"children\": [{\"children\": [], \"data\": {\"playcount\": \"261\", \"artist\": \"Chris Cornell\", \"image\": \"http:\/\/cdn.last.fm\/flatness\/catalogue\/noimage\/2\/default_album_medium.png\", \"$area\": 261}, \"id\": \"album-2006-09-07: O-Bar, Stockholm, Sweden\", \"name\": \"2006-09-07: O-Bar, Stockholm, Sweden\"}, {\"children\": [], \"data\": {\"playcount\": \"211\", \"artist\": \"Chris Cornell\", \"image\": \"http:\/\/userserve-ak.last.fm\/serve\/300x300\/25402479.jpg\", \"$area\": 211}, \"id\": \"album-Lost and Found\", \"name\": \"Lost and Found\"}], \"data\": {\"playcount\": 472, \"$area\": 472}, \"id\": \"artist_Chris Cornell\", \"name\": \"Chris Cornell\"}, {\"children\": [{\"children\": [], \"data\": {\"playcount\": \"197\", \"artist\": \"Disturbed\", \"image\": \"http:\/\/userserve-ak.last.fm\/serve\/300x300\/8634627.jpg\", \"$area\": 197}, \"id\": \"album-The Sickness\", \"name\": \"The Sickness\"}], \"data\": {\"playcount\": 197, \"$area\": 197}, \"id\": \"artist_Disturbed\", \"name\": \"Disturbed\"}, {\"children\": [{\"children\": [], \"data\": {\"playcount\": \"493\", \"artist\": \"Erykah Badu\", \"image\": \"http:\/\/userserve-ak.last.fm\/serve\/300x300\/8591345.jpg\", \"$area\": 493}, \"id\": \"album-Mama's Gun\", \"name\": \"Mama's Gun\"}], \"data\": {\"playcount\": 493, \"$area\": 493}, \"id\": \"artist_Erykah Badu\", \"name\": \"Erykah Badu\"}, {\"children\": [{\"children\": [], \"data\": {\"playcount\": \"249\", \"artist\": \"Audioslave\", \"image\": \"http:\/\/userserve-ak.last.fm\/serve\/300x300\/32070871.jpg\", \"$area\": 249}, \"id\": \"album-Audioslave\", \"name\": \"Audioslave\"}], \"data\": {\"playcount\": 249, \"$area\": 249}, \"id\": \"artist_Audioslave\", \"name\": \"Audioslave\"}, {\"children\": [{\"children\": [], \"data\": {\"playcount\": \"359\", \"artist\": \"Soda Stereo\", \"image\": \"http:\/\/userserve-ak.last.fm\/serve\/300x300\/15858421.jpg\", \"$area\": 359}, \"id\": \"album-Comfort y M\u00fasica Para Volar\", \"name\": \"Comfort y M\u00fasica Para Volar\"}], \"data\": {\"playcount\": 359, \"$area\": 359}, \"id\": \"artist_Soda Stereo\", \"name\": \"Soda Stereo\"}, {\"children\": [{\"children\": [], \"data\": {\"playcount\": \"302\", \"artist\": \"Sinch\", \"image\": \"http:\/\/userserve-ak.last.fm\/serve\/300x300\/8776205.jpg\", \"$area\": 302}, \"id\": \"album-Clearing the Channel\", \"name\": \"Clearing the Channel\"}], \"data\": {\"playcount\": 302, \"$area\": 302}, \"id\": \"artist_Sinch\", \"name\": \"Sinch\"}, {\"children\": [{\"children\": [], \"data\": {\"playcount\": \"177\", \"artist\": \"Dave Matthews Band\", \"image\": \"http:\/\/userserve-ak.last.fm\/serve\/300x300\/32457599.jpg\", \"$area\": 177}, \"id\": \"album-Crash\", \"name\": \"Crash\"}], \"data\": {\"playcount\": 177, \"$area\": 177}, \"id\": \"artist_Dave Matthews Band\", \"name\": \"Dave Matthews Band\"}, {\"children\": [{\"children\": [], \"data\": {\"playcount\": \"207\", \"artist\": \"Pearl Jam\", \"image\": \"http:\/\/userserve-ak.last.fm\/serve\/300x300\/30352203.jpg\", \"$area\": 207}, \"id\": \"album-Vs.\", \"name\": \"Vs.\"}], \"data\": {\"playcount\": 207, \"$area\": 207}, \"id\": \"artist_Pearl Jam\", \"name\": \"Pearl Jam\"}, {\"children\": [{\"children\": [], \"data\": {\"playcount\": \"486\", \"artist\": \"Kr\u00f8m\", \"image\": \"http:\/\/userserve-ak.last.fm\/serve\/300x300\/26053425.jpg\", \"$area\": 486}, \"id\": \"album-It All Makes Sense Now\", \"name\": \"It All Makes Sense Now\"}, {\"children\": [], \"data\": {\"playcount\": \"251\", \"artist\": \"Agua de Annique\", \"image\": \"http:\/\/userserve-ak.last.fm\/serve\/300x300\/9658733.jpg\", \"$area\": 251}, \"id\": \"album-Air\", \"name\": \"Air\"}], \"data\": {\"playcount\": 737, \"$area\": 737}, \"id\": \"artist_Kr\u00f8m\", \"name\": \"Kr\u00f8m\"}, {\"children\": [{\"children\": [], \"data\": {\"playcount\": \"345\", \"artist\": \"Temple of the Dog\", \"image\": \"http:\/\/userserve-ak.last.fm\/serve\/300x300\/8605651.jpg\", \"$area\": 345}, \"id\": \"album-Temple Of The Dog\", \"name\": \"Temple Of The Dog\"}], \"data\": {\"playcount\": 345, \"$area\": 345}, \"id\": \"artist_Temple of the Dog\", \"name\": \"Temple of the Dog\"}, {\"children\": [{\"children\": [], \"data\": {\"playcount\": \"318\", \"artist\": \"Nine Inch Nails\", \"image\": \"http:\/\/userserve-ak.last.fm\/serve\/300x300\/29274729.jpg\", \"$area\": 318}, \"id\": \"album-And All That Could Have Been (Still)\", \"name\": \"And All That Could Have Been (Still)\"}], \"data\": {\"playcount\": 318, \"$area\": 318}, \"id\": \"artist_Nine Inch Nails\", \"name\": \"Nine Inch Nails\"}, {\"children\": [{\"children\": [], \"data\": {\"playcount\": \"256\", \"artist\": \"Tryo\", \"image\": \"http:\/\/userserve-ak.last.fm\/serve\/300x300\/32595059.jpg\", \"$area\": 256}, \"id\": \"album-Mamagubida\", \"name\": \"Mamagubida\"}, {\"children\": [], \"data\": {\"playcount\": \"220\", \"artist\": \"Tryo\", \"image\": \"http:\/\/cdn.last.fm\/flatness\/catalogue\/noimage\/2\/default_album_medium.png\", \"$area\": 220}, \"id\": \"album-Reggae \u00e0 Coup de Cirque\", \"name\": \"Reggae \u00e0 Coup de Cirque\"}, {\"children\": [], \"data\": {\"playcount\": \"181\", \"artist\": \"Tryo\", \"image\": \"http:\/\/userserve-ak.last.fm\/serve\/300x300\/16799743.jpg\", \"$area\": 181}, \"id\": \"album-Grain de sable\", \"name\": \"Grain de sable\"}], \"data\": {\"playcount\": 657, \"$area\": 657}, \"id\": \"artist_Tryo\", \"name\": \"Tryo\"}, {\"children\": [{\"children\": [], \"data\": {\"playcount\": \"258\", \"artist\": \"Sublime\", \"image\": \"http:\/\/cdn.last.fm\/flatness\/catalogue\/noimage\/2\/default_album_medium.png\", \"$area\": 258}, \"id\": \"album-Best Of\", \"name\": \"Best Of\"}, {\"children\": [], \"data\": {\"playcount\": \"176\", \"artist\": \"Sublime\", \"image\": \"http:\/\/userserve-ak.last.fm\/serve\/300x300\/5264426.jpg\", \"$area\": 176}, \"id\": \"album-Robbin' The Hood\", \"name\": \"Robbin' The Hood\"}], \"data\": {\"playcount\": 434, \"$area\": 434}, \"id\": \"artist_Sublime\", \"name\": \"Sublime\"}, {\"children\": [{\"children\": [], \"data\": {\"playcount\": \"418\", \"artist\": \"Red Hot Chili Peppers\", \"image\": \"http:\/\/userserve-ak.last.fm\/serve\/300x300\/8590493.jpg\", \"$area\": 418}, \"id\": \"album-One Hot Minute\", \"name\": \"One Hot Minute\"}], \"data\": {\"playcount\": 418, \"$area\": 418}, \"id\": \"artist_Red Hot Chili Peppers\", \"name\": \"Red Hot Chili Peppers\"}, {\"children\": [{\"children\": [], \"data\": {\"playcount\": \"275\", \"artist\": \"Guns N' Roses\", \"image\": \"http:\/\/userserve-ak.last.fm\/serve\/300x300\/17597653.jpg\", \"$area\": 275}, \"id\": \"album-Chinese Democracy\", \"name\": \"Chinese Democracy\"}, {\"children\": [], \"data\": {\"playcount\": \"203\", \"artist\": \"Guns N' Roses\", \"image\": \"http:\/\/userserve-ak.last.fm\/serve\/300x300\/15231979.jpg\", \"$area\": 203}, \"id\": \"album-Use Your Illusion II\", \"name\": \"Use Your Illusion II\"}], \"data\": {\"playcount\": 478, \"$area\": 478}, \"id\": \"artist_Guns N' Roses\", \"name\": \"Guns N' Roses\"}, {\"children\": [{\"children\": [], \"data\": {\"playcount\": \"208\", \"artist\": \"Wax Tailor\", \"image\": \"http:\/\/images.amazon.com\/images\/P\/B0007LCNNE.01.MZZZZZZZ.jpg\", \"$area\": 208}, \"id\": \"album-Tales of the Forgotten Melodies\", \"name\": \"Tales of the Forgotten Melodies\"}], \"data\": {\"playcount\": 208, \"$area\": 208}, \"id\": \"artist_Wax Tailor\", \"name\": \"Wax Tailor\"}, {\"children\": [{\"children\": [], \"data\": {\"playcount\": \"208\", \"artist\": \"Radiohead\", \"image\": \"http:\/\/userserve-ak.last.fm\/serve\/300x300\/7862623.png\", \"$area\": 208}, \"id\": \"album-In Rainbows\", \"name\": \"In Rainbows\"}], \"data\": {\"playcount\": 208, \"$area\": 208}, \"id\": \"artist_Radiohead\", \"name\": \"Radiohead\"}, {\"children\": [{\"children\": [], \"data\": {\"playcount\": \"317\", \"artist\": \"Soundgarden\", \"image\": \"http:\/\/userserve-ak.last.fm\/serve\/300x300\/8600371.jpg\", \"$area\": 317}, \"id\": \"album-Down On The Upside\", \"name\": \"Down On The Upside\"}, {\"children\": [], \"data\": {\"playcount\": \"290\", \"artist\": \"Soundgarden\", \"image\": \"http:\/\/userserve-ak.last.fm\/serve\/300x300\/8590515.jpg\", \"$area\": 290}, \"id\": \"album-Superunknown\", \"name\": \"Superunknown\"}], \"data\": {\"playcount\": 607, \"$area\": 607}, \"id\": \"artist_Soundgarden\", \"name\": \"Soundgarden\"}, {\"children\": [{\"children\": [], \"data\": {\"playcount\": \"247\", \"artist\": \"Blind Melon\", \"image\": \"http:\/\/userserve-ak.last.fm\/serve\/300x300\/15113951.jpg\", \"$area\": 247}, \"id\": \"album-Nico\", \"name\": \"Nico\"}, {\"children\": [], \"data\": {\"playcount\": \"218\", \"artist\": \"Blind Melon\", \"image\": \"http:\/\/userserve-ak.last.fm\/serve\/300x300\/45729417.jpg\", \"$area\": 218}, \"id\": \"album-Soup\", \"name\": \"Soup\"}, {\"children\": [], \"data\": {\"playcount\": \"197\", \"artist\": \"Blind Melon\", \"image\": \"http:\/\/images.amazon.com\/images\/P\/B00005V5PW.01.MZZZZZZZ.jpg\", \"$area\": 197}, \"id\": \"album-Classic Masters\", \"name\": \"Classic Masters\"}, {\"children\": [], \"data\": {\"playcount\": \"194\", \"artist\": \"Blind Melon\", \"image\": \"http:\/\/userserve-ak.last.fm\/serve\/300x300\/15157989.jpg\", \"$area\": 194}, \"id\": \"album-Blind Melon\", \"name\": \"Blind Melon\"}], \"data\": {\"playcount\": 856, \"$area\": 856}, \"id\": \"artist_Blind Melon\", \"name\": \"Blind Melon\"}, {\"children\": [{\"children\": [], \"data\": {\"playcount\": \"537\", \"artist\": \"Incubus\", \"image\": \"http:\/\/userserve-ak.last.fm\/serve\/300x300\/17594883.jpg\", \"$area\": 537}, \"id\": \"album-Make Yourself\", \"name\": \"Make Yourself\"}, {\"children\": [], \"data\": {\"playcount\": \"258\", \"artist\": \"Incubus\", \"image\": \"http:\/\/userserve-ak.last.fm\/serve\/300x300\/31550385.jpg\", \"$area\": 258}, \"id\": \"album-Light Grenades\", \"name\": \"Light Grenades\"}, {\"children\": [], \"data\": {\"playcount\": \"181\", \"artist\": \"Incubus\", \"image\": \"http:\/\/userserve-ak.last.fm\/serve\/300x300\/32309285.jpg\", \"$area\": 181}, \"id\": \"album-Morning View\", \"name\": \"Morning View\"}], \"data\": {\"playcount\": 976, \"$area\": 976}, \"id\": \"artist_Incubus\", \"name\": \"Incubus\"}, {\"children\": [{\"children\": [], \"data\": {\"playcount\": \"198\", \"artist\": \"Jack Johnson\", \"image\": \"http:\/\/userserve-ak.last.fm\/serve\/300x300\/8599099.jpg\", \"$area\": 198}, \"id\": \"album-On And On\", \"name\": \"On And On\"}, {\"children\": [], \"data\": {\"playcount\": \"186\", \"artist\": \"Jack Johnson\", \"image\": \"http:\/\/userserve-ak.last.fm\/serve\/300x300\/30082075.jpg\", \"$area\": 186}, \"id\": \"album-Brushfire Fairytales\", \"name\": \"Brushfire Fairytales\"}], \"data\": {\"playcount\": 384, \"$area\": 384}, \"id\": \"artist_Jack Johnson\", \"name\": \"Jack Johnson\"}, {\"children\": [{\"children\": [], \"data\": {\"playcount\": \"349\", \"artist\": \"Mother Love Bone\", \"image\": \"http:\/\/userserve-ak.last.fm\/serve\/300x300\/21881921.jpg\", \"$area\": 349}, \"id\": \"album-Mother Love Bone\", \"name\": \"Mother Love Bone\"}], \"data\": {\"playcount\": 349, \"$area\": 349}, \"id\": \"artist_Mother Love Bone\", \"name\": \"Mother Love Bone\"}], \"data\": {}, \"id\": \"root\", \"name\": \"Top Albums\"}";
+ //end
+ //init TreeMap
+ var tm = new $jit.TM.Voronoi({
+ //where to inject the visualization
+ injectInto: 'infovis',
+ //show only one tree level
+ levelsToShow: 1,
+ //parent box title heights
+ titleHeight: 0,
+ //enable animations
+ animate: animate,
+ //box offsets
+ offset: 1,
+ //use canvas text
+ Label: {
+ type: labelType,
+ size: 9,
+ family: 'Tahoma, Verdana, Arial'
+ },
+ //enable specific canvas styles
+ //when rendering nodes
+ Node: {
+ CanvasStyles: {
+ shadowBlur: 0,
+ shadowColor: '#000'
+ }
+ },
+ //Attach left and right click events
+ Events: {
+ enable: true,
+ onClick: function(node) {
+ if(node) tm.enter(node);
+ },
+ onRightClick: function() {
+ tm.out();
+ },
+ //change node styles and canvas styles
+ //when hovering a node
+ onMouseEnter: function(node, eventInfo) {
+ if(node) {
+ //add node selected styles and replot node
+ node.setCanvasStyle('shadowBlur', 7);
+ node.setData('color', '#888');
+ tm.fx.plotNode(node, tm.canvas);
+ tm.labels.plotLabel(tm.canvas, node);
+ }
+ },
+ onMouseLeave: function(node) {
+ if(node) {
+ node.removeData('color');
+ node.removeCanvasStyle('shadowBlur');
+ tm.plot();
+ }
+ }
+ },
+ //duration of the animations
+ duration: 1000,
+ //Enable tips
+ Tips: {
+ enable: true,
+ type: 'Native',
+ //add positioning offsets
+ offsetX: 20,
+ offsetY: 20,
+ //implement the onShow method to
+ //add content to the tooltip when a node
+ //is hovered
+ onShow: function(tip, node, isLeaf, domElement) {
+ var html = "<div class=\"tip-title\">" + node.name
+ + "</div><div class=\"tip-text\">";
+ var data = node.data;
+ if(data.artist) {
+ html += "Artist: " + data.artist + "<br />";
+ }
+ if(data.playcount) {
+ html += "Play count: " + data.playcount;
+ }
+ if(data.image) {
+ html += "<img src=\""+ data.image +"\" class=\"album\" />";
+ }
+ tip.innerHTML = html;
+ }
+ },
+ //Implement this method for retrieving a requested
+ //subtree that has as root a node with id = nodeId,
+ //and level as depth. This method could also make a server-side
+ //call for the requested subtree. When completed, the onComplete
+ //callback method should be called.
+ request: function(nodeId, level, onComplete){
+ var tree = eval('(' + json + ')');
+ var subtree = $jit.json.getSubtree(tree, nodeId);
+ $jit.json.prune(subtree, 1);
+ onComplete.onComplete(nodeId, subtree);
+ },
+ //Add the name of the node in the corresponding label
+ //This method is called once, on label creation and only for DOM labels.
+ onCreateLabel: function(domElement, node){
+ domElement.innerHTML = node.name;
+ }
+ });
+
+ var pjson = eval('(' + json + ')');
+ $jit.json.prune(pjson, 1);
+
+ tm.loadJSON(pjson);
+ tm.refresh();
+ //end
+ //add event to the back button
+ var back = $jit.id('back');
+ $jit.util.addEvent(back, 'click', function() {
+ tm.out();
+ });
+}
+
View
951 Tests/Voronoi/test3.js
@@ -0,0 +1,951 @@
+function init(){
+ //init data
+ var json = {
+ "children": [
+ {
+ "children": [
+ {
+ "children": [],
+ "data": {
+ "playcount": "276",
+ "$color": "#8E7032",
+ "image": "http://userserve-ak.last.fm/serve/300x300/11403219.jpg",
+ "$area": 276
+ },
+ "id": "album-Thirteenth Step",
+ "name": "Thirteenth Step"
+ },
+ {
+ "children": [],
+ "data": {
+ "playcount": "271",
+ "$color": "#906E32",
+ "image": "http://userserve-ak.last.fm/serve/300x300/11393921.jpg",
+ "$area": 271
+ },
+ "id": "album-Mer De Noms",
+ "name": "Mer De Noms"
+ }
+ ],
+ "data": {
+ "playcount": 547,
+ "$area": 547
+ },
+ "id": "artist_A Perfect Circle",
+ "name": "A Perfect Circle"
+ },
+ {
+ "children": [
+ {
+ "children": [],
+ "data": {
+ "playcount": "209",
+ "$color": "#AA5532",
+ "image": "http://userserve-ak.last.fm/serve/300x300/32349839.jpg",
+ "$area": 209
+ },
+ "id": "album-Above",
+ "name": "Above"
+ }
+ ],
+ "data": {
+ "playcount": 209,
+ "$area": 209
+ },
+ "id": "artist_Mad Season",
+ "name": "Mad Season"
+ },
+ {
+ "children": [
+ {
+ "children": [],
+ "data": {
+ "playcount": "260",
+ "$color": "#956932",
+ "image": "http://userserve-ak.last.fm/serve/300x300/38753425.jpg",
+ "$area": 260
+ },
+ "id": "album-Tiny Music... Songs From the Vatican Gift Shop",
+ "name": "Tiny Music... Songs From the Vatican Gift Shop"
+ },
+ {
+ "children": [],
+ "data": {
+ "playcount": "254",
+ "$color": "#976732",
+ "image": "http://images.amazon.com/images/P/B000002IU3.01.LZZZZZZZ.jpg",
+ "$area": 254
+ },
+ "id": "album-Core",
+ "name": "Core"
+ }
+ ],
+ "data": {
+ "playcount": 514,
+ "$area": 514
+ },
+ "id": "artist_Stone Temple Pilots",
+ "name": "Stone Temple Pilots"
+ },
+ {
+ "children": [
+ {
+ "children": [],
+ "data": {
+ "playcount": "181",
+ "$color": "#B54932",
+ "image": "http://userserve-ak.last.fm/serve/300x300/8673371.jpg",
+ "$area": 181
+ },
+ "id": "album-The Science of Things",
+ "name": "The Science of Things"
+ }
+ ],
+ "data": {
+ "playcount": 181,
+ "$area": 181
+ },
+ "id": "artist_Bush",
+ "name": "Bush"
+ },
+ {
+ "children": [
+ {
+ "children": [],
+ "data": {
+ "playcount": "229",
+ "$color": "#A15D32",
+ "image": "http://userserve-ak.last.fm/serve/300x300/32579429.jpg",
+ "$area": 229
+ },
+ "id": "album-Echoes, Silence, Patience &amp; Grace",
+ "name": "Echoes, Silence, Patience &amp; Grace"
+ },
+ {
+ "children": [],
+ "data": {
+ "playcount": "185",
+ "$color": "#B34B32",
+ "image": "http://images.amazon.com/images/P/B0009HLDFU.01.MZZZZZZZ.jpg",
+ "$area": 185
+ },
+ "id": "album-In Your Honor (disc 2)",
+ "name": "In Your Honor (disc 2)"
+ }
+ ],
+ "data": {
+ "playcount": 414,
+ "$area": 414
+ },
+ "id": "artist_Foo Fighters",
+ "name": "Foo Fighters"
+ },
+ {
+ "children": [
+ {
+ "children": [],
+ "data": {
+ "playcount": "398",
+ "$color": "#5DA132",
+ "image": "http://images.amazon.com/images/P/B00005LNP5.01._SCMZZZZZZZ_.jpg",
+ "$area": 398
+ },
+ "id": "album-Elija Y Gane",
+ "name": "Elija Y Gane"
+ },
+ {
+ "children": [],
+ "data": {
+ "playcount": "203",
+ "$color": "#AC5232",
+ "image": "http://images.amazon.com/images/P/B0000B193V.01._SCMZZZZZZZ_.jpg",
+ "$area": 203
+ },
+ "id": "album-Para los Arboles",
+ "name": "Para los Arboles"
+ }
+ ],
+ "data": {
+ "playcount": 601,
+ "$area": 601
+ },
+ "id": "artist_Luis Alberto Spinetta",
+ "name": "Luis Alberto Spinetta"
+ },
+ {
+ "children": [
+ {
+ "children": [],
+ "data": {
+ "playcount": "224",
+ "$color": "#A35B32",
+ "image": "http://userserve-ak.last.fm/serve/300x300/26497553.jpg",
+ "$area": 224
+ },
+ "id": "album-Music Bank",
+ "name": "Music Bank"
+ },
+ {
+ "children": [],
+ "data": {
+ "playcount": "217",
+ "$color": "#A65832",
+ "image": "http://images.amazon.com/images/P/B0000296JW.01.MZZZZZZZ.jpg",
+ "$area": 217
+ },
+ "id": "album-Music Bank (disc 1)",
+ "name": "Music Bank (disc 1)"
+ },
+ {
+ "children": [],
+ "data": {
+ "playcount": "215",
+ "$color": "#A75732",
+ "image": "http://images.amazon.com/images/P/B0000296JW.01.MZZZZZZZ.jpg",
+ "$area": 215
+ },
+ "id": "album-Music Bank (disc 2)",
+ "name": "Music Bank (disc 2)"
+ },
+ {
+ "children": [],
+ "data": {
+ "playcount": "181",
+ "$color": "#B54932",
+ "image": "http://images.amazon.com/images/P/B0000296JW.01.MZZZZZZZ.jpg",
+ "$area": 181
+ },
+ "id": "album-Music Bank (disc 3)",
+ "name": "Music Bank (disc 3)"
+ }
+ ],
+ "data": {
+ "playcount": 837,
+ "$area": 837
+ },
+ "id": "artist_Alice in Chains",
+ "name": "Alice in Chains"
+ },
+ {
+ "children": [
+ {
+ "children": [],
+ "data": {
+ "playcount": "627",
+ "$color": "#00FF32",
+ "image": "http://userserve-ak.last.fm/serve/300x300/8480501.jpg",
+ "$area": 627
+ },
+ "id": "album-10,000 Days",
+ "name": "10,000 Days"
+ }
+ ],
+ "data": {
+ "playcount": 627,
+ "$area": 627
+ },
+ "id": "artist_Tool",
+ "name": "Tool"
+ },
+ {
+ "children": [
+ {
+ "children": [],
+ "data": {
+ "playcount": "261",
+ "$color": "#946A32",
+ "image": "http://cdn.last.fm/flatness/catalogue/noimage/2/default_album_medium.png",
+ "$area": 261
+ },
+ "id": "album-2006-09-07: O-Bar, Stockholm, Sweden",
+ "name": "2006-09-07: O-Bar, Stockholm, Sweden"
+ },
+ {
+ "children": [],
+ "data": {
+ "playcount": "211",
+ "$color": "#A95532",
+ "image": "http://userserve-ak.last.fm/serve/300x300/25402479.jpg",
+ "$area": 211
+ },
+ "id": "album-Lost and Found",
+ "name": "Lost and Found"
+ }
+ ],
+ "data": {
+ "playcount": 472,
+ "$area": 472
+ },
+ "id": "artist_Chris Cornell",
+ "name": "Chris Cornell"
+ },
+ {
+ "children": [
+ {
+ "children": [],
+ "data": {
+ "playcount": "197",
+ "$color": "#AE5032",
+ "image": "http://userserve-ak.last.fm/serve/300x300/8634627.jpg",
+ "$area": 197
+ },
+ "id": "album-The Sickness",
+ "name": "The Sickness"
+ }
+ ],
+ "data": {
+ "playcount": 197,
+ "$area": 197
+ },
+ "id": "artist_Disturbed",
+ "name": "Disturbed"
+ },
+ {
+ "children": [
+ {
+ "children": [],
+ "data": {
+ "playcount": "493",
+ "$color": "#36C832",
+ "image": "http://userserve-ak.last.fm/serve/300x300/8591345.jpg",
+ "$area": 493
+ },
+ "id": "album-Mama's Gun",
+ "name": "Mama's Gun"
+ }
+ ],
+ "data": {
+ "playcount": 493,
+ "$area": 493
+ },
+ "id": "artist_Erykah Badu",
+ "name": "Erykah Badu"
+ },
+ {
+ "children": [
+ {
+ "children": [],
+ "data": {
+ "playcount": "249",
+ "$color": "#996532",
+ "image": "http://userserve-ak.last.fm/serve/300x300/32070871.jpg",
+ "$area": 249
+ },
+ "id": "album-Audioslave",
+ "name": "Audioslave"
+ }
+ ],
+ "data": {
+ "playcount": 249,
+ "$area": 249
+ },
+ "id": "artist_Audioslave",
+ "name": "Audioslave"
+ },
+ {
+ "children": [
+ {
+ "children": [],
+ "data": {
+ "playcount": "359",
+ "$color": "#6C9232",
+ "image": "http://userserve-ak.last.fm/serve/300x300/15858421.jpg",
+ "$area": 359
+ },
+ "id": "album-Comfort y M\u00fasica Para Volar",
+ "name": "Comfort y M\u00fasica Para Volar"
+ }
+ ],
+ "data": {
+ "playcount": 359,
+ "$area": 359
+ },
+ "id": "artist_Soda Stereo",
+ "name": "Soda Stereo"
+ },
+ {
+ "children": [
+ {
+ "children": [],
+ "data": {
+ "playcount": "302",
+ "$color": "#847A32",
+ "image": "http://userserve-ak.last.fm/serve/300x300/8776205.jpg",
+ "$area": 302
+ },
+ "id": "album-Clearing the Channel",
+ "name": "Clearing the Channel"
+ }
+ ],
+ "data": {
+ "playcount": 302,
+ "$area": 302
+ },
+ "id": "artist_Sinch",
+ "name": "Sinch"
+ },
+ {
+ "children": [
+ {
+ "children": [],
+ "data": {
+ "playcount": "177",
+ "$color": "#B74732",
+ "image": "http://userserve-ak.last.fm/serve/300x300/32457599.jpg",
+ "$area": 177
+ },
+ "id": "album-Crash",
+ "name": "Crash"
+ }
+ ],
+ "data": {
+ "playcount": 177,
+ "$area": 177
+ },
+ "id": "artist_Dave Matthews Band",
+ "name": "Dave Matthews Band"
+ },
+ {
+ "children": [
+ {
+ "children": [],
+ "data": {
+ "playcount": "207",
+ "$color": "#AA5432",
+ "image": "http://userserve-ak.last.fm/serve/300x300/30352203.jpg",
+ "$area": 207
+ },
+ "id": "album-Vs.",
+ "name": "Vs."
+ }
+ ],
+ "data": {
+ "playcount": 207,
+ "$area": 207
+ },
+ "id": "artist_Pearl Jam",
+ "name": "Pearl Jam"
+ },
+ {
+ "children": [
+ {
+ "children": [],
+ "data": {
+ "playcount": "486",
+ "$color": "#39C532",
+ "image": "http://userserve-ak.last.fm/serve/300x300/26053425.jpg",
+ "$area": 486
+ },
+ "id": "album-It All Makes Sense Now",
+ "name": "It All Makes Sense Now"
+ },
+ {
+ "children": [],
+ "data": {
+ "playcount": "251",
+ "$color": "#986632",
+ "image": "http://userserve-ak.last.fm/serve/300x300/9658733.jpg",
+ "$area": 251
+ },
+ "id": "album-Air",
+ "name": "Air"
+ }
+ ],
+ "data": {
+ "playcount": 737,
+ "$area": 737
+ },
+ "id": "artist_Kr\u00f8m",
+ "name": "Kr\u00f8m"
+ },
+ {
+ "children": [
+ {
+ "children": [],
+ "data": {
+ "playcount": "345",
+ "$color": "#728C32",
+ "image": "http://userserve-ak.last.fm/serve/300x300/8605651.jpg",
+ "$area": 345
+ },
+ "id": "album-Temple Of The Dog",
+ "name": "Temple Of The Dog"
+ }
+ ],
+ "data": {
+ "playcount": 345,
+ "$area": 345
+ },
+ "id": "artist_Temple of the Dog",
+ "name": "Temple of the Dog"
+ },
+ {
+ "children": [
+ {
+ "children": [],
+ "data": {
+ "playcount": "318",
+ "$color": "#7D8132",
+ "image": "http://userserve-ak.last.fm/serve/300x300/29274729.jpg",
+ "$area": 318
+ },
+ "id": "album-And All That Could Have Been (Still)",
+ "name": "And All That Could Have Been (Still)"
+ }
+ ],
+ "data": {
+ "playcount": 318,
+ "$area": 318
+ },
+ "id": "artist_Nine Inch Nails",
+ "name": "Nine Inch Nails"
+ },
+ {
+ "children": [
+ {
+ "children": [],
+ "data": {
+ "playcount": "256",
+ "$color": "#966832",
+ "image": "http://userserve-ak.last.fm/serve/300x300/32595059.jpg",
+ "$area": 256
+ },
+ "id": "album-Mamagubida",
+ "name": "Mamagubida"
+ },
+ {
+ "children": [],
+ "data": {
+ "playcount": "220",
+ "$color": "#A55932",
+ "image": "http://cdn.last.fm/flatness/catalogue/noimage/2/default_album_medium.png",
+ "$area": 220
+ },
+ "id": "album-Reggae \u00e0 Coup de Cirque",
+ "name": "Reggae \u00e0 Coup de Cirque"
+ },
+ {
+ "children": [],
+ "data": {
+ "playcount": "181",
+ "$color": "#B54932",
+ "image": "http://userserve-ak.last.fm/serve/300x300/16799743.jpg",
+ "$area": 181
+ },
+ "id": "album-Grain de sable",
+ "name": "Grain de sable"
+ }
+ ],
+ "data": {
+ "playcount": 657,
+ "$area": 657
+ },
+ "id": "artist_Tryo",
+ "name": "Tryo"
+ },
+ {
+ "children": [
+ {
+ "children": [],
+ "data": {
+ "playcount": "258",
+ "$color": "#966832",
+ "image": "http://cdn.last.fm/flatness/catalogue/noimage/2/default_album_medium.png",
+ "$area": 258
+ },
+ "id": "album-Best Of",
+ "name": "Best Of"
+ },
+ {
+ "children": [],
+ "data": {
+ "playcount": "176",
+ "$color": "#B74732",
+ "image": "http://userserve-ak.last.fm/serve/300x300/5264426.jpg",
+ "$area": 176
+ },
+ "id": "album-Robbin' The Hood",
+ "name": "Robbin' The Hood"
+ }
+ ],
+ "data": {
+ "playcount": 434,
+ "$area": 434
+ },
+ "id": "artist_Sublime",
+ "name": "Sublime"
+ },
+ {
+ "children": [
+ {
+ "children": [],
+ "data": {
+ "playcount": "418",
+ "$color": "#55AA32",
+ "image": "http://userserve-ak.last.fm/serve/300x300/8590493.jpg",
+ "$area": 418
+ },
+ "id": "album-One Hot Minute",
+ "name": "One Hot Minute"
+ }
+ ],
+ "data": {
+ "playcount": 418,
+ "$area": 418
+ },
+ "id": "artist_Red Hot Chili Peppers",
+ "name": "Red Hot Chili Peppers"
+ },
+ {
+ "children": [
+ {
+ "children": [],
+ "data": {
+ "playcount": "275",
+ "$color": "#8F6F32",
+ "image": "http://userserve-ak.last.fm/serve/300x300/17597653.jpg",
+ "$area": 275
+ },
+ "id": "album-Chinese Democracy",
+ "name": "Chinese Democracy"
+ },
+ {
+ "children": [],
+ "data": {
+ "playcount": "203",
+ "$color": "#AC5232",
+ "image": "http://userserve-ak.last.fm/serve/300x300/15231979.jpg",
+ "$area": 203
+ },
+ "id": "album-Use Your Illusion II",
+ "name": "Use Your Illusion II"
+ }
+ ],
+ "data": {
+ "playcount": 478,
+ "$area": 478
+ },
+ "id": "artist_Guns N' Roses",
+ "name": "Guns N' Roses"
+ },
+ {
+ "children": [
+ {
+ "children": [],
+ "data": {
+ "playcount": "208",
+ "$color": "#AA5432",
+ "image": "http://images.amazon.com/images/P/B0007LCNNE.01.MZZZZZZZ.jpg",
+ "$area": 208
+ },
+ "id": "album-Tales of the Forgotten Melodies",
+ "name": "Tales of the Forgotten Melodies"
+ }
+ ],
+ "data": {
+ "playcount": 208,
+ "$area": 208
+ },
+ "id": "artist_Wax Tailor",
+ "name": "Wax Tailor"
+ },
+ {
+ "children": [
+ {
+ "children": [],
+ "data": {
+ "playcount": "208",
+ "$color": "#AA5432",
+ "image": "http://userserve-ak.last.fm/serve/300x300/7862623.png",
+ "$area": 208
+ },
+ "id": "album-In Rainbows",
+ "name": "In Rainbows"
+ }
+ ],
+ "data": {
+ "playcount": 208,
+ "$area": 208
+ },
+ "id": "artist_Radiohead",
+ "name": "Radiohead"
+ },
+ {
+ "children": [
+ {
+ "children": [],
+ "data": {
+ "playcount": "317",
+ "$color": "#7E8032",
+ "image": "http://userserve-ak.last.fm/serve/300x300/8600371.jpg",
+ "$area": 317
+ },
+ "id": "album-Down On The Upside",
+ "name": "Down On The Upside"
+ },
+ {
+ "children": [],
+ "data": {
+ "playcount": "290",
+ "$color": "#897532",
+ "image": "http://userserve-ak.last.fm/serve/300x300/8590515.jpg",
+ "$area": 290
+ },
+ "id": "album-Superunknown",
+ "name": "Superunknown"
+ }
+ ],
+ "data": {
+ "playcount": 607,
+ "$area": 607
+ },
+ "id": "artist_Soundgarden",
+ "name": "Soundgarden"
+ },
+ {
+ "children": [
+ {
+ "children": [],
+ "data": {
+ "playcount": "247",
+ "$color": "#9A6432",
+ "image": "http://userserve-ak.last.fm/serve/300x300/15113951.jpg",
+ "$area": 247
+ },
+ "id": "album-Nico",
+ "name": "Nico"
+ },
+ {
+ "children": [],
+ "data": {
+ "playcount": "218",
+ "$color": "#A65832",
+ "image": "http://userserve-ak.last.fm/serve/300x300/45729417.jpg",
+ "$area": 218
+ },
+ "id": "album-Soup",
+ "name": "Soup"
+ },
+ {
+ "children": [],
+ "data": {
+ "playcount": "197",
+ "$color": "#AE5032",
+ "image": "http://images.amazon.com/images/P/B00005V5PW.01.MZZZZZZZ.jpg",
+ "$area": 197
+ },
+ "id": "album-Classic Masters",
+ "name": "Classic Masters"
+ },
+ {
+ "children": [],
+ "data": {
+ "playcount": "194",
+ "$color": "#B04E32",
+ "image": "http://userserve-ak.last.fm/serve/300x300/15157989.jpg",
+ "$area": 194
+ },
+ "id": "album-Blind Melon",
+ "name": "Blind Melon"
+ }
+ ],
+ "data": {
+ "playcount": 856,
+ "$area": 856
+ },
+ "id": "artist_Blind Melon",
+ "name": "Blind Melon"
+ },
+ {
+ "children": [
+ {
+ "children": [],
+ "data": {
+ "playcount": "537",
+ "$color": "#24DA32",
+ "image": "http://userserve-ak.last.fm/serve/300x300/17594883.jpg",
+ "$area": 537
+ },
+ "id": "album-Make Yourself",
+ "name": "Make Yourself"
+ },
+ {
+ "children": [],
+ "data": {
+ "playcount": "258",
+ "$color": "#966832",
+ "image": "http://userserve-ak.last.fm/serve/300x300/31550385.jpg",
+ "$area": 258
+ },
+ "id": "album-Light Grenades",
+ "name": "Light Grenades"
+ },
+ {
+ "children": [],
+ "data": {
+ "playcount": "181",
+ "$color": "#B54932",
+ "image": "http://userserve-ak.last.fm/serve/300x300/32309285.jpg",
+ "$area": 181
+ },
+ "id": "album-Morning View",
+ "name": "Morning View"
+ }
+ ],
+ "data": {
+ "playcount": 976,
+ "$area": 976
+ },
+ "id": "artist_Incubus",
+ "name": "Incubus"
+ },
+ {
+ "children": [
+ {
+ "children": [],
+ "data": {
+ "playcount": "198",
+ "$color": "#AE5032",
+ "image": "http://userserve-ak.last.fm/serve/300x300/8599099.jpg",
+ "$area": 198
+ },
+ "id": "album-On And On",
+ "name": "On And On"
+ },
+ {
+ "children": [],
+ "data": {
+ "playcount": "186",
+ "$color": "#B34B32",
+ "image": "http://userserve-ak.last.fm/serve/300x300/30082075.jpg",
+ "$area": 186
+ },
+ "id": "album-Brushfire Fairytales",
+ "name": "Brushfire Fairytales"
+ }
+ ],
+ "data": {
+ "playcount": 384,
+ "$area": 384
+ },
+ "id": "artist_Jack Johnson",
+ "name": "Jack Johnson"
+ },
+ {
+ "children": [
+ {
+ "children": [],
+ "data": {
+ "playcount": "349",
+ "$color": "#718D32",
+ "image": "http://userserve-ak.last.fm/serve/300x300/21881921.jpg",
+ "$area": 349
+ },
+ "id": "album-Mother Love Bone",
+ "name": "Mother Love Bone"
+ }
+ ],
+ "data": {
+ "playcount": 349,
+ "$area": 349
+ },
+ "id": "artist_Mother Love Bone",
+ "name": "Mother Love Bone"
+ }
+ ],
+ "data": {},
+ "id": "root",
+ "name": "Top Albums"
+ };
+ //end
+ //init TreeMap
+ var tm = new $jit.TM.Voronoi({
+ //where to inject the visualization
+ injectInto: 'infovis',
+ //parent box title heights
+ titleHeight: 15,
+ //enable animations
+ animate: animate,
+ //box offsets
+ offset: 1,
+ Node: {
+ CanvasStyles: {
+ shadowBlur: 0,
+ shadowColor: '#000'
+ }
+ },
+ //Attach left and right click events
+ Events: {
+ enable: true,
+ onClick: function(node) {
+ if(node) tm.enter(node);
+ },
+ onRightClick: function() {
+ tm.out();
+ },
+ //change node styles and canvas styles
+ //when hovering a node
+ onMouseEnter: function(node, eventInfo) {
+ if(node) {
+ //add node selected styles and replot node
+ node.setCanvasStyle('shadowBlur', 7);
+ node.setData('color', '#888');
+ tm.fx.plotNode(node, tm.canvas);
+ tm.labels.plotLabel(tm.canvas, node);
+ }
+ },
+ onMouseLeave: function(node) {
+ if(node) {
+ node.removeData('color');
+ node.removeCanvasStyle('shadowBlur');
+ tm.plot();
+ }
+ }
+ },
+ duration: 1000,
+ //Enable tips
+ Tips: {
+ enable: true,
+ //add positioning offsets
+ offsetX: 20,
+ offsetY: 20,
+ //implement the onShow method to
+ //add content to the tooltip when a node
+ //is hovered
+ onShow: function(tip, node, isLeaf, domElement) {
+ var html = "<div class=\"tip-title\">" + node.name
+ + "</div><div class=\"tip-text\">";
+ var data = node.data;
+ if(data.playcount) {
+ html += "play count: " + data.playcount;
+ }
+ if(data.image) {
+ html += "<img src=\""+ data.image +