Skip to content
This repository
Browse code

voronoi prototype

  • Loading branch information...
commit b3a76f535f1f391bd8037401d23a2f97cf646eb1 1 parent 21a120e
ZHANG Bei authored committed
59 Source/Graph/Graph.Plot.js
... ... @@ -1,4 +1,4 @@
1   -/*
  1 + /*
2 2 * File: Graph.Plot.js
3 3 */
4 4
@@ -20,6 +20,7 @@ Graph.Plot = {
20 20 this.node = viz.config.Node;
21 21 this.edge = viz.config.Edge;
22 22 this.animation = new Animation;
  23 + debugger;
23 24 this.nodeTypes = new klass.Plot.NodeTypes;
24 25 this.edgeTypes = new klass.Plot.EdgeTypes;
25 26 this.labels = viz.labels;
@@ -42,7 +43,8 @@ Graph.Plot = {
42 43 'angularWidth':'number',
43 44 'span':'number',
44 45 'valueArray':'array-number',
45   - 'dimArray':'array-number'
  46 + 'dimArray':'array-number',
  47 + 'vertics':'polygon'
46 48 //'colorArray':'array-color'
47 49 },
48 50
@@ -170,6 +172,59 @@ Graph.Plot = {
170 172
171 173 'edge-style': function(elem, props, delta) {
172 174 this['edge'](elem, props, delta, 'canvas', 'getCanvasStyle', 'setCanvasStyle');
  175 + },
  176 +
  177 + 'polygon': function(elem, prop, delta, getter, setter) {
  178 + var from = elem[getter](prop, 'start'),
  179 + to = elem[getter](prop, 'end'),
  180 + cur = [];
  181 + var dist = function (c1, c2) {
  182 + var dx = c1.x - c2.x, dy = c1.y - c2.y;
  183 + return dx * dx + dy * dy;
  184 + };
  185 + if(typeof from.offset == 'undefined') {
  186 + if(from === 0) {
  187 + from = $.map(to,function(){return new $jit.Complex(0, 0);});
  188 + from.offset = 0;
  189 + }
  190 + else {
  191 + if(from.length == 0) {
  192 + from.push(new $jit.Complex(0, 0));
  193 + }
  194 + while(from.length < to.length) {
  195 + from.push(from[0]);
  196 + }
  197 + while(from.length > to.length) {
  198 + to.push(to[0]);
  199 + }
  200 + from = from.map(function(t){ return t || new $jit.Complex(0, 0);});
  201 + to = to.map(function(t){ return t || new $jit.Complex(0, 0);});
  202 + if (from.length == 0) return;
  203 + var l = from.length;
  204 + var minDist = 1e300;
  205 + for(var offset = 0; offset < l; offset ++) {
  206 + var d = 0;
  207 + for(var i = 0; i < l; i++){
  208 + d += dist(from[(offset + i) % l], to[i]);
  209 + }
  210 + if (d < minDist)
  211 + {
  212 + from.offset = offset;
  213 + minDist = d;
  214 + }
  215 + }
  216 + }
  217 + }
  218 +
  219 + for(var i=0, l=from.length; i<l; i++) {
  220 + var fromi = from[(i + from.offset) % l], toi = to[i];
  221 + cur.push(new $jit.Complex(
  222 + this.compute(fromi.x, toi.x, delta),
  223 + this.compute(fromi.y, toi.y, delta)
  224 + ));
  225 + }
  226 + elem[setter](prop, cur);
  227 +
173 228 }
174 229 },
175 230
21 Source/Visualizations/Treemap.js
@@ -92,7 +92,8 @@ TM.Base = {
92 92 //right, Firefox?
93 93 width: 3,
94 94 height: 3,
95   - color: '#444'
  95 + color: '#444',
  96 + props: 'node-property:width:height'
96 97 },
97 98 Label: {
98 99 textAlign: 'center',
@@ -120,7 +121,10 @@ TM.Base = {
120 121 }, canvasConfig.background);
121 122 }
122 123 this.canvas = new Canvas(this, canvasConfig);
123   - this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
  124 + this.config.labelContainer = (
  125 + typeof canvasConfig.injectInto == 'string'?
  126 + canvasConfig.injectInto :
  127 + canvasConfig.injectInto.id) + '-label';
124 128 }
125 129
126 130 this.graphOptions = {
@@ -158,7 +162,7 @@ TM.Base = {
158 162 this.config.levelsToShow > 0 && this.geom.setRightLevelToShow(this.graph.getNode(this.clickedNode
159 163 && this.clickedNode.id || this.root));
160 164 this.fx.animate($.merge(this.config, {
161   - modes: ['linear', 'node-property:width:height'],
  165 + modes: ['linear', this.config.Node.props],
162 166 onComplete: function() {
163 167 that.busy = false;
164 168 }
@@ -216,7 +220,6 @@ TM.Base = {
216 220 enter: function(n){
217 221 if(this.busy) return;
218 222 this.busy = true;
219   -
220 223 var that = this,
221 224 config = this.config,
222 225 graph = this.graph,
@@ -248,8 +251,8 @@ TM.Base = {
248 251 //TODO(nico) commenting this line didn't seem to throw errors...
249 252 that.clickedNode = previousClickedNode;
250 253 that.fx.animate({
251   - modes:['linear', 'node-property:width:height'],
252 254 duration: 2 * that.config.duration / 3,
  255 + modes:['linear', that.config.Node.props],
253 256 onComplete: function() {
254 257 that.busy = false;
255 258 //TODO(nico) check comment above
@@ -297,7 +300,7 @@ TM.Base = {
297 300 return;
298 301 }
299 302 //final plot callback
300   - callback = {
  303 + var callback = {
301 304 onComplete: function() {
302 305 that.clickedNode = parent;
303 306 if(config.request) {
@@ -325,8 +328,13 @@ TM.Base = {
325 328 //animate the visible subtree only
326 329 this.clickedNode = previousClickedNode;
327 330 this.fx.animate({
  331 +<<<<<<< HEAD
328 332 modes:['linear', 'node-property:width:height'],
329 333 duration: 2 * this.config.duration / 3,
  334 +=======
  335 + modes:['linear', this.config.Node.props],
  336 + duration: 1000,
  337 +>>>>>>> voronoi prototype
330 338 onComplete: function() {
331 339 //animate the parent subtree
332 340 that.clickedNode = clickedNode;
@@ -561,7 +569,6 @@ TM.Label.Native = new Class({
561 569 height = node.getData('height'),
562 570 x = pos.x + width/2,
563 571 y = pos.y;
564   -
565 572 ctx.fillText(node.name, x, y, width);
566 573 }
567 574 });
720 Source/Visualizations/Voronoi.js
... ... @@ -0,0 +1,720 @@
  1 +/**
  2 + *
  3 + * @author ZHANG BEi
  4 + */
  5 +
  6 +(function(){
  7 +
  8 + if(!TM) TM = {};
  9 + $jit.Voronoi = {};
  10 + var V = $jit.Voronoi;
  11 +
  12 + /**
  13 + * Tests whether all points are in a polygon
  14 + */
  15 + var pointInPolygon = function(pts, polygon) {
  16 + if(polygon.length < 3) return false;
  17 + var conj = [];
  18 + $jit.util.each(pts, function(){ conj.push(0); });
  19 + for (var i = 0; i < polygon.length; i++) {
  20 + var start = polygon[i];
  21 + var stop = polygon[i + 1] || polygon[0];
  22 + $jit.util.each(pts, function(pos, i) {
  23 + if (start.y >= pos.y && stop.y < pos.y || start.y <= pos.y
  24 + && stop.y > pos.y) {
  25 + // find inversed k
  26 + var k = (stop.x - start.x) / (stop.y - start.y);
  27 + var cx = (pos.y - start.y) * k + start.x;
  28 + if (cx == pos.x)
  29 + return true;
  30 + else if (cx > pos.x)
  31 + conj[i]++;
  32 + }
  33 + });
  34 + }
  35 + conj = $jit.util.map(conj, function(c){ return c % 2 != 0;});
  36 + return conj;
  37 + };
  38 +
  39 + /**
  40 + * Counter-clockwise is positive.
  41 + * @param p1, p2, p3 [x,y]
  42 + */
  43 + var cross = $jit.Voronoi.cross = function(p1, p2, p3) {
  44 + return p1.x * p2.y + p2.x * p3.y + p3.x * p1.y -
  45 + p1.x * p3.y - p2.x * p1.y - p3.x * p2.y;
  46 + }
  47 + /**
  48 + * Reserves those l[0],l[1],c[i] is counter-clockwise
  49 + * @param c [p1,p2,...]
  50 + * @param l [p1,p2] as a ex line
  51 + */
  52 + var convexCut = $jit.Voronoi.convexCut = function(c, l) {
  53 + if (c.length < 3) return [];
  54 + var result = [];
  55 + for(var i = 0; i < c.length; i++)
  56 + {
  57 + var start = c[i];
  58 + var stop = c[i + 1] || c[0];
  59 + if (start.x == stop.x && start.y == stop.y) continue;
  60 + var c1, c2;
  61 + if ((c1 = cross(l[0], l[1], start)) > 0) {
  62 + if ((c2 = cross(l[0], l[1], stop)) >= 0) {
  63 + result.push(stop);
  64 + } else {
  65 + var sc = c1 / (c1 - c2);
  66 + if (c1 - c2)
  67 + result.push(new $jit.Complex(
  68 + (stop.x - start.x) * sc + start.x,
  69 + (stop.y - start.y) * sc + start.y
  70 + ));
  71 + }
  72 + } else {
  73 + if ((c2 = cross(l[0], l[1], stop)) > 0) {
  74 + var sc = c1 / (c1 - c2);
  75 + result.push(new $jit.Complex(
  76 + (stop.x - start.x) * sc + start.x,
  77 + (stop.y - start.y) * sc + start.y
  78 + ));
  79 + result.push(stop);
  80 + } else if (c2 == 0) {
  81 + if (c1)
  82 + result.push(stop);
  83 + }
  84 + }
  85 + }
  86 + return result;
  87 + }
  88 +
  89 + var convexIntersect = $jit.Voronoi.convexIntersect = function(c1, c2) {
  90 + if (c1.length < 3) return [];
  91 + if (c2.length < 3) return [];
  92 + for (var i = 0; i < c2.length ; i++) {
  93 + var start = c2[i];
  94 + var stop = c2[i + 1] || c2[0];
  95 + c1 = convexCut(c1, [start, stop]);
  96 + }
  97 + return c1;
  98 + };
  99 +
  100 + var bisector = function(p1, p2){
  101 + return [
  102 + 2 * (p2.x - p1.x),
  103 + 2 * (p2.y - p1.y),
  104 + -(p2.x * p2.x - p1.x * p1.x + p2.y * p2.y - p1.y * p1.y)
  105 + ];
  106 + };
  107 + V.bisector = bisector;
  108 +
  109 + var intersection = function(l1, l2) {
  110 + var det = l1[0]*l2[1] - l1[1] * l2[0];
  111 + if(det == 0)
  112 + if(l1[0] * l2[2] - l1[2] * l2[0] == 0) return l1;
  113 + else
  114 + return;
  115 + return new $jit.Complex(
  116 + -(l1[2] * l2[1] - l1[1] * l2[2]) / det,
  117 + -(l1[0] * l2[2] - l1[2] * l2[0]) / det
  118 + );
  119 + }
  120 + V.intersection = intersection;
  121 +
  122 + // Adapted from Nicolas Garcia Belmonte's JIT implementation:
  123 + // http://blog.thejit.org/2010/02/12/voronoi-tessellation/
  124 + // http://blog.thejit.org/assets/voronoijs/voronoi.js
  125 + // See lib/jit/LICENSE for details.
  126 +
  127 + var d3_voronoi_opposite = {"l": "r", "r": "l"};
  128 + var c = $jit.Complex;
  129 + function d3_voronoi_tessellate(vertices, callback) {
  130 + var Sites = {
  131 + list: vertices
  132 + .map(function(v, i) {
  133 + return {
  134 + index: i,
  135 + x: v[0],
  136 + y: v[1]
  137 + };
  138 + })
  139 + .sort(function(a, b) {
  140 + return a.y < b.y ? -1
  141 + : a.y > b.y ? 1
  142 + : a.x < b.x ? -1
  143 + : a.x > b.x ? 1
  144 + : 0;
  145 + }),
  146 + bottomSite: null
  147 + };
  148 +
  149 + var EdgeList = {
  150 + list: [],
  151 + leftEnd: null,
  152 + rightEnd: null,
  153 +
  154 + init: function() {
  155 + EdgeList.leftEnd = EdgeList.createHalfEdge(null, "l");
  156 + EdgeList.rightEnd = EdgeList.createHalfEdge(null, "l");
  157 + EdgeList.leftEnd.r = EdgeList.rightEnd;
  158 + EdgeList.rightEnd.l = EdgeList.leftEnd;
  159 + EdgeList.list.unshift(EdgeList.leftEnd, EdgeList.rightEnd);
  160 + },
  161 +
  162 + createHalfEdge: function(edge, side) {
  163 + return {
  164 + edge: edge,
  165 + side: side,
  166 + vertex: null,
  167 + "l": null,
  168 + "r": null
  169 + };
  170 + },
  171 +
  172 + insert: function(lb, he) {
  173 + he.l = lb;
  174 + he.r = lb.r;
  175 + lb.r.l = he;
  176 + lb.r = he;
  177 + },
  178 +
  179 + leftBound: function(p) {
  180 + var he = EdgeList.leftEnd;
  181 + do {
  182 + he = he.r;
  183 + } while (he != EdgeList.rightEnd && Geom.rightOf(he, p));
  184 + he = he.l;
  185 + return he;
  186 + },
  187 +
  188 + del: function(he) {
  189 + he.l.r = he.r;
  190 + he.r.l = he.l;
  191 + he.edge = null;
  192 + },
  193 +
  194 + right: function(he) {
  195 + return he.r;
  196 + },
  197 +
  198 + left: function(he) {
  199 + return he.l;
  200 + },
  201 +
  202 + leftRegion: function(he) {
  203 + return he.edge == null
  204 + ? Sites.bottomSite
  205 + : he.edge.region[he.side];
  206 + },
  207 +
  208 + rightRegion: function(he) {
  209 + return he.edge == null
  210 + ? Sites.bottomSite
  211 + : he.edge.region[d3_voronoi_opposite[he.side]];
  212 + }
  213 + };
  214 +
  215 + var Geom = {
  216 +
  217 + bisect: function(s1, s2) {
  218 + var newEdge = {
  219 + region: {"l": s1, "r": s2},
  220 + ep: {"l": null, "r": null}
  221 + };
  222 +
  223 + var dx = s2.x - s1.x,
  224 + dy = s2.y - s1.y,
  225 + adx = dx > 0 ? dx : -dx,
  226 + ady = dy > 0 ? dy : -dy;
  227 +
  228 + newEdge.c = s1.x * dx + s1.y * dy
  229 + + (dx * dx + dy * dy) * .5;
  230 +
  231 + if (adx > ady) {
  232 + newEdge.a = 1;
  233 + newEdge.b = dy / dx;
  234 + newEdge.c /= dx;
  235 + } else {
  236 + newEdge.b = 1;
  237 + newEdge.a = dx / dy;
  238 + newEdge.c /= dy;
  239 + }
  240 +
  241 + return newEdge;
  242 + },
  243 +
  244 + intersect: function(el1, el2) {
  245 + var e1 = el1.edge,
  246 + e2 = el2.edge;
  247 + if (!e1 || !e2 || (e1.region.r == e2.region.r)) {
  248 + return null;
  249 + }
  250 + var d = (e1.a * e2.b) - (e1.b * e2.a);
  251 + if (Math.abs(d) < 1e-10) {
  252 + return null;
  253 + }
  254 + var xint = (e1.c * e2.b - e2.c * e1.b) / d,
  255 + yint = (e2.c * e1.a - e1.c * e2.a) / d,
  256 + e1r = e1.region.r,
  257 + e2r = e2.region.r,
  258 + el,
  259 + e;
  260 + if ((e1r.y < e2r.y) ||
  261 + (e1r.y == e2r.y && e1r.x < e2r.x)) {
  262 + el = el1;
  263 + e = e1;
  264 + } else {
  265 + el = el2;
  266 + e = e2;
  267 + }
  268 + var rightOfSite = (xint >= e.region.r.x);
  269 + if ((rightOfSite && (el.side == "l")) ||
  270 + (!rightOfSite && (el.side == "r"))) {
  271 + return null;
  272 + }
  273 + return {
  274 + x: xint,
  275 + y: yint
  276 + };
  277 + },
  278 +
  279 + rightOf: function(he, p) {
  280 + var e = he.edge,
  281 + topsite = e.region.r,
  282 + rightOfSite = (p.x > topsite.x);
  283 +
  284 + if (rightOfSite && (he.side == "l")) {
  285 + return 1;
  286 + }
  287 + if (!rightOfSite && (he.side == "r")) {
  288 + return 0;
  289 + }
  290 + if (e.a == 1) {
  291 + var dyp = p.y - topsite.y,
  292 + dxp = p.x - topsite.x,
  293 + fast = 0,
  294 + above = 0;
  295 +
  296 + if ((!rightOfSite && (e.b < 0)) ||
  297 + (rightOfSite && (e.b >= 0))) {
  298 + above = fast = (dyp >= e.b * dxp);
  299 + } else {
  300 + above = ((p.x + p.y * e.b) > e.c);
  301 + if (e.b < 0) {
  302 + above = !above;
  303 + }
  304 + if (!above) {
  305 + fast = 1;
  306 + }
  307 + }
  308 + if (!fast) {
  309 + var dxs = topsite.x - e.region.l.x;
  310 + above = (e.b * (dxp * dxp - dyp * dyp)) <
  311 + (dxs * dyp * (1 + 2 * dxp / dxs + e.b * e.b));
  312 +
  313 + if (e.b < 0) {
  314 + above = !above;
  315 + }
  316 + }
  317 + } else /* e.b == 1 */ {
  318 + var yl = e.c - e.a * p.x,
  319 + t1 = p.y - yl,
  320 + t2 = p.x - topsite.x,
  321 + t3 = yl - topsite.y;
  322 +
  323 + above = (t1 * t1) > (t2 * t2 + t3 * t3);
  324 + }
  325 + return he.side == "l" ? above : !above;
  326 + },
  327 +
  328 + endPoint: function(edge, side, site) {
  329 + edge.ep[side] = site;
  330 + if (!edge.ep[d3_voronoi_opposite[side]]) return;
  331 + callback(edge);
  332 + },
  333 +
  334 + distance: function(s, t) {
  335 + var dx = s.x - t.x,
  336 + dy = s.y - t.y;
  337 + return Math.sqrt(dx * dx + dy * dy);
  338 + }
  339 + };
  340 +
  341 + var EventQueue = {
  342 + list: [],
  343 +
  344 + insert: function(he, site, offset) {
  345 + he.vertex = site;
  346 + he.ystar = site.y + offset;
  347 + for (var i=0, list=EventQueue.list, l=list.length; i<l; i++) {
  348 + var next = list[i];
  349 + if (he.ystar > next.ystar ||
  350 + (he.ystar == next.ystar &&
  351 + site.x > next.vertex.x)) {
  352 + continue;
  353 + } else {
  354 + break;
  355 + }
  356 + }
  357 + list.splice(i, 0, he);
  358 + },
  359 +
  360 + del: function(he) {
  361 + for (var i=0, ls=EventQueue.list, l=ls.length; i<l && (ls[i] != he); ++i) {}
  362 + ls.splice(i, 1);
  363 + },
  364 +
  365 + empty: function() { return EventQueue.list.length == 0; },
  366 +
  367 + nextEvent: function(he) {
  368 + for (var i=0, ls=EventQueue.list, l=ls.length; i<l; ++i) {
  369 + if (ls[i] == he) return ls[i+1];
  370 + }
  371 + return null;
  372 + },
  373 +
  374 + min: function() {
  375 + var elem = EventQueue.list[0];
  376 + return {
  377 + x: elem.vertex.x,
  378 + y: elem.ystar
  379 + };
  380 + },
  381 +
  382 + extractMin: function() {
  383 + return EventQueue.list.shift();
  384 + }
  385 + };
  386 +
  387 + EdgeList.init();
  388 + Sites.bottomSite = Sites.list.shift();
  389 +
  390 + var newSite = Sites.list.shift(), newIntStar;
  391 + var lbnd, rbnd, llbnd, rrbnd, bisector;
  392 + var bot, top, temp, p, v;
  393 + var e, pm;
  394 +
  395 + while (true) {
  396 + if (!EventQueue.empty()) {
  397 + newIntStar = EventQueue.min();
  398 + }
  399 + if (newSite && (EventQueue.empty()
  400 + || newSite.y < newIntStar.y
  401 + || (newSite.y == newIntStar.y
  402 + && newSite.x < newIntStar.x))) { //new site is smallest
  403 + lbnd = EdgeList.leftBound(newSite);
  404 + rbnd = EdgeList.right(lbnd);
  405 + bot = EdgeList.rightRegion(lbnd);
  406 + e = Geom.bisect(bot, newSite);
  407 + bisector = EdgeList.createHalfEdge(e, "l");
  408 + EdgeList.insert(lbnd, bisector);
  409 + p = Geom.intersect(lbnd, bisector);
  410 + if (p) {
  411 + EventQueue.del(lbnd);
  412 + EventQueue.insert(lbnd, p, Geom.distance(p, newSite));
  413 + }
  414 + lbnd = bisector;
  415 + bisector = EdgeList.createHalfEdge(e, "r");
  416 + EdgeList.insert(lbnd, bisector);
  417 + p = Geom.intersect(bisector, rbnd);
  418 + if (p) {
  419 + EventQueue.insert(bisector, p, Geom.distance(p, newSite));
  420 + }
  421 + newSite = Sites.list.shift();
  422 + } else if (!EventQueue.empty()) { //intersection is smallest
  423 + lbnd = EventQueue.extractMin();
  424 + llbnd = EdgeList.left(lbnd);
  425 + rbnd = EdgeList.right(lbnd);
  426 + rrbnd = EdgeList.right(rbnd);
  427 + bot = EdgeList.leftRegion(lbnd);
  428 + top = EdgeList.rightRegion(rbnd);
  429 + v = lbnd.vertex;
  430 + Geom.endPoint(lbnd.edge, lbnd.side, v);
  431 + Geom.endPoint(rbnd.edge, rbnd.side, v);
  432 + EdgeList.del(lbnd);
  433 + EventQueue.del(rbnd);
  434 + EdgeList.del(rbnd);
  435 + pm = "l";
  436 + if (bot.y > top.y) {
  437 + temp = bot;
  438 + bot = top;
  439 + top = temp;
  440 + pm = "r";
  441 + }
  442 + e = Geom.bisect(bot, top);
  443 + bisector = EdgeList.createHalfEdge(e, pm);
  444 + EdgeList.insert(llbnd, bisector);
  445 + Geom.endPoint(e, d3_voronoi_opposite[pm], v);
  446 + p = Geom.intersect(llbnd, bisector);
  447 + if (p) {
  448 + EventQueue.del(llbnd);
  449 + EventQueue.insert(llbnd, p, Geom.distance(p, bot));
  450 + }
  451 + p = Geom.intersect(bisector, rrbnd);
  452 + if (p) {
  453 + EventQueue.insert(bisector, p, Geom.distance(p, bot));
  454 + }
  455 + } else {
  456 + break;
  457 + }
  458 + }//end while
  459 +
  460 + for (lbnd = EdgeList.right(EdgeList.leftEnd);
  461 + lbnd != EdgeList.rightEnd;
  462 + lbnd = EdgeList.right(lbnd)) {
  463 + callback(lbnd.edge);
  464 + }
  465 + }
  466 +
  467 + /**
  468 + * Voronoi Tessellation with Fortune's algorithm
  469 + * @param vertices [[x1, y1], [x2, y2], ...]
  470 + * @returns polygons [[[x1, y1], [x2, y2], ...], ...]
  471 + */
  472 + var voronoiFortune = function (vertices, boundary) {
  473 + if (vertices.length == 1)
  474 + return [boundary];
  475 + else if (vertices.length == 2) {
  476 + var v1 = vertices[0];
  477 + var v2 = vertices[1];
  478 + var cx = (v1[0] + v2[0]) / 2;
  479 + var cy = (v1[1] + v2[1]) / 2;
  480 + var dx = v1[0] - cx;
  481 + var dy = v1[0] - cy;
  482 + var l1 = new c(cx - dy, cy + dx);
  483 + var l2 = new c(cx + dy, cy - dx);
  484 + return [convexCut(boundary, [l1,l2]), convexCut(boundary, [l2,l1])];
  485 + }
  486 + var polygons = vertices.map(function() { return []; });
  487 + // Note: we expect the caller to clip the polygons, if needed.
  488 + d3_voronoi_tessellate(vertices, function(e) {
  489 + var s1,
  490 + s2,
  491 + x1,
  492 + x2,
  493 + y1,
  494 + y2;
  495 + if (e.a == 1 && e.b >= 0) {
  496 + s1 = e.ep.r;
  497 + s2 = e.ep.l;
  498 + } else {
  499 + s1 = e.ep.l;
  500 + s2 = e.ep.r;
  501 + }
  502 + if (e.a == 1) {
  503 + y1 = s1 ? s1.y : -1e6;
  504 + x1 = e.c - e.b * y1;
  505 + y2 = s2 ? s2.y : 1e6;
  506 + x2 = e.c - e.b * y2;
  507 + } else {
  508 + x1 = s1 ? s1.x : -1e6;
  509 + y1 = e.c - e.a * x1;
  510 + x2 = s2 ? s2.x : 1e6;
  511 + y2 = e.c - e.a * x2;
  512 + }
  513 + var v1 = new c(x1, y1),
  514 + v2 = new c(x2, y2);
  515 + polygons[e.region.l.index].push(v1, v2);
  516 + polygons[e.region.r.index].push(v1, v2);
  517 + });
  518 +
  519 + // Reconnect the polygon segments into counterclockwise loops.
  520 + polygons = polygons.map(function(polygon, i) {
  521 + var cx = vertices[i][0],
  522 + cy = vertices[i][1];
  523 + polygon.forEach(function(v) {
  524 + v.angle = Math.atan2(v.x - cx, v.y - cy);
  525 + });
  526 + return polygon.sort(function(a, b) {
  527 + return a.angle - b.angle;
  528 + }).filter(function(d, i) {
  529 + return !i || (d.angle - polygon[i - 1].angle > 1e-10);
  530 + });
  531 + });
  532 + if (boundary) {
  533 + polygons = polygons.map(function(p) {
  534 + return $jit.Voronoi.convexIntersect(p, boundary);
  535 + });
  536 + }
  537 +
  538 + return polygons;
  539 + };
  540 + $jit.Voronoi.voronoiFortune = voronoiFortune;
  541 +
  542 +
  543 + TM.Plot.NodeTypes.implement({
  544 + 'polygon' : {
  545 + 'render' : function(node, canvas, animating) {
  546 + var leaf = this.viz.leaf(node), config = this.config,
  547 + border = node.getData('border'),
  548 + // offset = config.offset,
  549 + vertics = node.getData('vertics'), ctx = canvas.getCtx(),
  550 + titleHeight = config.titleHeight;
  551 + if (!vertics) {
  552 + return;
  553 + }
  554 + if (vertics.length == 0) return;
  555 + var pts = vertics.slice(0);
  556 + if (leaf) {
  557 + // TODO: Implement the offset config
  558 + // Create cushion gradient
  559 + if (config.cushion) {
  560 + var x = 0, y = 0, minX = pts[0].x, maxX = pts[0].x, minY = pts[0].y, maxY = pts[0].y;
  561 + $jit.util.each(pts, function(pt) {
  562 + x += pt.x;
  563 + y += pt.y;
  564 + if (minX > pt.x)
  565 + minX = pt.x;
  566 + if (minY > pt.y)
  567 + minY = pt.y;
  568 + if (maxX < pt.x)
  569 + minX = pt.x;
  570 + if (maxY < pt.y)
  571 + maxY = pt.y;
  572 + });
  573 + x /= pts.length;
  574 + y /= pts.length;
  575 + var width = maxX - minX + 1, height = maxY - minY + 1;
  576 + var lg = ctx.createRadialGradient(x, y, 1, x, y,
  577 + width < height ? height : width);
  578 + var color = node.getData('color');
  579 + var colorGrad = $.rgbToHex($.map($.hexToRgb(color),
  580 + function(r) {
  581 + return r * 0.2 >> 0;
  582 + }));
  583 + lg.addColorStop(0, color);
  584 + lg.addColorStop(1, colorGrad);
  585 + ctx.fillStyle = lg;
  586 + }
  587 + // Fill polygon
  588 + ctx.beginPath();
  589 + ctx.moveTo(pts[0].x, pts[0].y);
  590 + for (var i = 1; i < vertics.length; i++) {
  591 + ctx.lineTo(pts[i].x, pts[i].y);
  592 + }
  593 + ctx.closePath();
  594 + ctx.fill();
  595 + if (border) {
  596 + ctx.save();
  597 + ctx.strokeStyle = border;
  598 + ctx.stroke();
  599 + ctx.restore();
  600 + }
  601 + } else if(titleHeight > 0) {
  602 + // Fill polygon
  603 + ctx.beginPath();
  604 + ctx.moveTo(pts[0].x, pts[0].y);
  605 + for (var i = 1; i < vertics.length; i++) {
  606 + ctx.lineTo(pts[i].x, pts[i].y);
  607 + }
  608 + ctx.lineTo(pts[0].x, pts[0].y);
  609 + ctx.save();
  610 + ctx.lineWidth = 1;
  611 + ctx.stroke();
  612 + ctx.restore();
  613 + ctx.closePath();
  614 + }
  615 + },
  616 + 'contains' : function(node, pos) {
  617 + if(!this.viz.leaf(node)) return false;
  618 + if(this.viz.clickedNode && !node.isDescendantOf(this.viz.clickedNode.id) || node.ignore) return false;
  619 + var ps = node.getData('vertics').slice(0);
  620 + $jit.util.each(ps, function(pt, i) {
  621 + ps[i] = pt.getc(true);
  622 + });
  623 + return pointInPolygon([pos], ps)[0];
  624 + }
  625 + }});
  626 +
  627 + Layouts.TM.Voronoi = new Class({
  628 + Implements : Layouts.TM.Area,
  629 + compute: function(prop) {
  630 + var root = this.graph.getNode(this.clickedNode && this.clickedNode.id || this.root);
  631 + this.controller.onBeforeCompute(root);
  632 + var size = this.canvas.getSize(),
  633 + config = this.config,
  634 + width = size.width,
  635 + height = size.height;
  636 + this.graph.computeLevels(this.root, 0, 0);
  637 + //set root position and dimensions
  638 + root.getPos(prop).setc(-5, -5);
  639 + if(!root.histoPos)
  640 + root.histoPos = [];
  641 + root.histoPos[0] = [0, 0];
  642 + var bound = [
  643 + new c(-width/2,-height/2),
  644 + new c(width/2,-height/2),
  645 + new c(width/2,height/2),
  646 + new c(-width/2,height/2)
  647 + ];
  648 + root.setData('vertics', bound, prop);
  649 + root.setData('width', width, prop);
  650 + root.setData('height', height, prop);
  651 + this.computePositions(root, bound, prop, 0);
  652 + this.controller.onAfterCompute(root);
  653 + },
  654 +
  655 + computePositions : function(node, bound, prop, level) {
  656 + // FIXME: Where can i find the level of each node?
  657 + var chs = node.getSubnodes([1, 1], "ignore"),
  658 + config = this.config,
  659 + max = Math.max;
  660 + var extent = [0, 0, 0, 0];
  661 + var histoPos = node.histoPos[level] || [0, 0];
  662 + $jit.util.each(bound, function(p) {
  663 + if(extent[0] > p.x) extent[0] = p.x;
  664 + if(extent[1] > p.y) extent[1] = p.y;
  665 + if(extent[2] < p.x) extent[2] = p.x;
  666 + if(extent[3] < p.y) extent[3] = p.y;
  667 + });
  668 + node.setData('width' , extent[2] - extent[0], prop);
  669 + node.setData('height' , extent[3] - extent[1], prop);
  670 + node.getPos(prop).setc(histoPos[0] - (extent[2] - extent[0]) / 2, histoPos[1]);
  671 + if (chs.length > 0) {
  672 + var sites = $jit.util.map(chs, function(ch) {
  673 + if(!ch.histoPos) ch.histoPos = [];
  674 + if(ch.histoPos[level + 1]) {
  675 + return ch.histoPos[level + 1];
  676 + }
  677 + var c = new $jit.Complex(
  678 + Math.random() * (extent[2] - extent[0]) + extent[0],
  679 + Math.random() * (extent[3] - extent[1]) + extent[1]
  680 + );
  681 + while(!pointInPolygon([c], bound)[0]) {
  682 + c = new $jit.Complex(
  683 + Math.random() * (extent[2] - extent[0]) + extent[0],
  684 + Math.random() * (extent[3] - extent[1]) + extent[1]
  685 + );
  686 + }
  687 + return ch.histoPos[level + 1] = [c.x, c.y];
  688 + });
  689 + var polygons = voronoiFortune(sites, bound);
  690 + var self = this;
  691 + $jit.util.each(chs, function(ch, i) {
  692 + var vertics = polygons[i];
  693 + ch.setData('vertics', vertics, prop);
  694 + self.computePositions(ch, vertics, prop, level + 1);
  695 + });
  696 + }
  697 + }
  698 + });
  699 +
  700 + /*
  701 + * Class: TM.Voronoi
  702 + *
  703 + * A Voronoi TreeMap visualization.
  704 + *
  705 + * Implements:
  706 + *
  707 + * All <TM.Base> methods and properties.
  708 + */
  709 + TM.Voronoi = new Class({
  710 + Implements : [Loader, Extras, TM.Base, Layouts.TM.Voronoi],
  711 + initialize : function(config) {
  712 + config.Node = config.Node || {};
  713 + config.Node.type = 'polygon';
  714 + config.Node.props = 'node-property:width:height:vertics';
  715 + this.config = config;
  716 + TM.Base.initialize.apply(this, [config]);
  717 + }
  718 + });
  719 +
  720 +})();
16 Templates/Treemap/left.html
@@ -33,10 +33,18 @@
33 33 <tr>
34 34 <td>
35 35 <label for="r-sd">SliceAndDice </label>
36   - </td>
37   - <td>
38   - <input type="radio" id="r-sd" name="layout" value="bottom" />
39   - </td>
  36 + </td>
  37 + <td>
  38 + <input type="radio" id="r-sd" name="layout" value="bottom" />
  39 + </td>
  40 + </tr>
  41 + <tr>
  42 + <td>
  43 + <label for="r-vn">Voronoi </label>
  44 + </td>
  45 + <td>
  46 + <input type="radio" id="r-vn" name="layout" value="voronoi" />
  47 + </td>
40 48 </tr>
41 49 </table>
42 50 </div>
16 Templates/Voronoi/left.html
... ... @@ -0,0 +1,16 @@
  1 +$def with (model, type, number, max)
  2 +
  3 +$if number > 1:
  4 + <a href="/testcase/$type/${number -1}/" title="previous">previous</a>
  5 +$if number < max:
  6 + <a href="/testcase/$type/${number +1}/" title="next">next</a>
  7 +
  8 +
  9 +<div class="text">
  10 +<h4>
  11 +$model['Title']
  12 +</h4>
  13 +$:model['Description']
  14 +</div>
  15 +
  16 +<a id="back" href="#" class="theme button white">Go to Parent</a>
3  Templates/Voronoi/test1.html
... ... @@ -0,0 +1,3 @@
  1 +$def with (model)
  2 +
  3 +<div id="inner-details"></div>
3  Templates/Voronoi/test2.html
... ... @@ -0,0 +1,3 @@
  1 +$def with (model)
  2 +
  3 +<div id="inner-details"></div>
3  Templates/Voronoi/test3.html
... ... @@ -0,0 +1,3 @@
  1 +$def with (model)
  2 +
  3 +<div id="inner-details"></div>
8 Tests/Treemap/test3.js
@@ -111,6 +111,8 @@ function init(){
111 111 var sq = $jit.id('r-sq'),
112 112 st = $jit.id('r-st'),
113 113 sd = $jit.id('r-sd');
  114 + vn = $jit.id('r-vn');
  115 +
114 116 var util = $jit.util;
115 117 util.addEvent(sq, 'change', function() {
116 118 if(!sq.checked) return;
@@ -129,6 +131,12 @@ function init(){
129 131 tm.layout.orientation = "v";
130 132 tm.refresh();
131 133 });
  134 +
  135 + util.addEvent(vn, 'change', function() {
  136 + if(!vn.checked) return;
  137 + util.extend(tm, new $jit.Layouts.TM.Voronoi);
  138 + tm.refresh();
  139 + });
132 140 //add event to the back button
133 141 var back = $jit.id('back');
134 142 $jit.util.addEvent(back, 'click', function() {
0  Tests/Voronoi/left.js
No changes.
49 Tests/Voronoi/test1.js
... ... @@ -0,0 +1,49 @@
  1 +function init(){
  2 + var idlist = $jit.id('infovis');
  3 + idlist.style.textAlign = "left";
  4 + idlist.style.padding = "5px";
  5 + var log = function(text) {
  6 + var element = document.createElement('div');
  7 + element.innerHTML = text;
  8 + idlist.appendChild(element);
  9 + }
  10 + var V = $jit.Voronoi;
  11 + // perftest(100000);
  12 +
  13 + var bs1 = V.bisector({x:0,y:0},{x:1,y:1});
  14 + var bs2 = V.bisector({x:1,y:0},{x:0,y:1});
  15 + var int = V.intersection(bs1,bs2);
  16 + log('Intersection: (' + int.x + ', ' + int.y + ')');
  17 +
  18 + var bs1 = V.bisector({x:0,y:0},{x:1,y:0});
  19 + var bs2 = V.bisector({x:0,y:0},{x:0,y:1});
  20 + var int = V.intersection(bs1,bs2);
  21 + log('Intersection: (' + int.x + ', ' + int.y + ')');
  22 +
  23 + var bs1 = V.bisector({x:0,y:0},{x:1,y:0});
  24 + var bs2 = V.bisector({x:0,y:0},{x:2,y:0});
  25 + var int = V.intersection(bs1,bs2);
  26 + log('Intersection: (' + int + ')');
  27 +
  28 + var bs1 = V.bisector({x:0,y:0},{x:1,y:0});
  29 + var bs2 = V.bisector({x:0,y:0},{x:1,y:0});
  30 + var int = V.intersection(bs1,bs2);
  31 + log('Intersection: (' + int + ')');
  32 +
  33 + var c = $jit.Complex;
  34 + console.log($jit.Voronoi.convexCut(
  35 + [new c(0,0), new c(1,2), new c(2,0)],
  36 + [new c(0,1), new c(2,1)]
  37 + ));
  38 +
  39 + console.log($jit.Voronoi.voronoiFortune(
  40 + [[0,0],[1,1]], [new c(0, 0), new c(0, 1), new c(1, 1), new c(1, 0)]));
  41 + 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)]));
  42 + 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)]));
  43 + 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)]));
  44 + 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)]));
  45 +
  46 + console.log($jit.Voronoi.convexIntersect(
  47 + [[0,0],[2,0],[1,2]],
  48 + [[0,2],[1,0],[2,0]]));
  49 +}
117 Tests/Voronoi/test2.js
... ... @@ -0,0 +1,117 @@
  1 +function init(){
  2 + //init data
  3 + 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\"}";
  4 + //end
  5 + //init TreeMap
  6 + var tm = new $jit.TM.Voronoi({
  7 + //where to inject the visualization
  8 + injectInto: 'infovis',
  9 + //show only one tree level
  10 + levelsToShow: 1,
  11 + //parent box title heights
  12 + titleHeight: 0,
  13 + //enable animations
  14 + animate: animate,
  15 + //box offsets
  16 + offset: 1,
  17 + //use canvas text
  18 + Label: {
  19 + type: labelType,
  20 + size: 9,
  21 + family: 'Tahoma, Verdana, Arial'
  22 + },
  23 + //enable specific canvas styles
  24 + //when rendering nodes
  25 + Node: {
  26 + CanvasStyles: {
  27 + shadowBlur: 0,
  28 + shadowColor: '#000'
  29 + }
  30 + },
  31 + //Attach left and right click events
  32 + Events: {
  33 + enable: true,
  34 + onClick: function(node) {
  35 + if(node) tm.enter(node);
  36 + },
  37 + onRightClick: function() {
  38 + tm.out();
  39 + },
  40 + //change node styles and canvas styles
  41 + //when hovering a node
  42 + onMouseEnter: function(node, eventInfo) {
  43 + if(node) {
  44 + //add node selected styles and replot node
  45 + node.setCanvasStyle('shadowBlur', 7);
  46 + node.setData('color', '#888');
  47 + tm.fx.plotNode(node, tm.canvas);
  48 + tm.labels.plotLabel(tm.canvas, node);
  49 + }
  50 + },
  51 + onMouseLeave: function(node) {
  52 + if(node) {
  53 + node.removeData('color');
  54 + node.removeCanvasStyle('shadowBlur');
  55 + tm.plot();
  56 + }
  57 + }
  58 + },
  59 + //duration of the animations
  60 + duration: 1000,
  61 + //Enable tips
  62 + Tips: {
  63 + enable: true,
  64 + type: 'Native',
  65 + //add positioning offsets
  66 + offsetX: 20,
  67 + offsetY: 20,
  68 + //implement the onShow method to
  69 + //add content to the tooltip when a node
  70 + //is hovered
  71 + onShow: function(tip, node, isLeaf, domElement) {
  72 + var html = "<div class=\"tip-title\">" + node.name
  73 + + "</div><div class=\"tip-text\">";
  74 + var data = node.data;
  75 + if(data.artist) {
  76 + html += "Artist: " + data.artist + "<br />";
  77 + }
  78 + if(data.playcount) {
  79 + html += "Play count: " + data.playcount;
  80 + }
  81 + if(data.image) {
  82 + html += "<img src=\""+ data.image +"\" class=\"album\" />";
  83 + }
  84 + tip.innerHTML = html;
  85 + }
  86 + },
  87 + //Implement this method for retrieving a requested
  88 + //subtree that has as root a node with id = nodeId,
  89 + //and level as depth. This method could also make a server-side
  90 + //call for the requested subtree. When completed, the onComplete
  91 + //callback method should be called.
  92 + request: function(nodeId, level, onComplete){
  93 + var tree = eval('(' + json + ')');
  94 + var subtree = $jit.json.getSubtree(tree, nodeId);
  95 + $jit.json.prune(subtree, 1);
  96 + onComplete.onComplete(nodeId, subtree);
  97 + },
  98 + //Add the name of the node in the corresponding label
  99 + //This method is called once, on label creation and only for DOM labels.
  100 + onCreateLabel: function(domElement, node){
  101 + domElement.innerHTML = node.name;
  102 + }
  103 + });
  104 +
  105 + var pjson = eval('(' + json + ')');
  106 + $jit.json.prune(pjson, 1);
  107 +
  108 + tm.loadJSON(pjson);
  109 + tm.refresh();
  110 + //end
  111 + //add event to the back button
  112 + var back = $jit.id('back');
  113 + $jit.util.addEvent(back, 'click', function() {
  114 + tm.out();
  115 + });
  116 +}
  117 +
951 Tests/Voronoi/test3.js
... ... @@ -0,0 +1,951 @@
  1 +function init(){
  2 + //init data
  3 + var json = {
  4 + "children": [
  5 + {
  6 + "children": [
  7 + {
  8 + "children": [],
  9 + "data": {