diff --git a/Makefile b/Makefile index be6efd5a..66e91602 100644 --- a/Makefile +++ b/Makefile @@ -94,8 +94,10 @@ d3.layout.js: \ src/start.js \ src/layout/layout.js \ src/layout/chord.js \ + src/layout/force.js \ src/layout/pie.js \ src/layout/stack.js \ + src/layout/treemap.js \ src/end.js d3.geo.js: \ diff --git a/d3.js b/d3.js index ef2dbad2..bc2ca64a 100644 --- a/d3.js +++ b/d3.js @@ -1,4 +1,4 @@ -(function(){d3 = {version: "1.4.0"}; // semver +(function(){d3 = {version: "1.5.0"}; // semver if (!Date.now) Date.now = function() { return +new Date(); }; diff --git a/d3.layout.js b/d3.layout.js index 2dab21e3..aa6c49ea 100644 --- a/d3.layout.js +++ b/d3.layout.js @@ -150,6 +150,200 @@ d3.layout.chord = function() { return chord; }; +// A rudimentary force layout using Gauss-Seidel. +d3.layout.force = function() { + var force = {}, + event = d3.dispatch("tick"), + size = [1, 1], + alpha = .5, + distance = 30, + interval, + nodes, + links, + distances; + + function tick() { + var n = distances.length, + i, // current index + o, // current link + s, // current source + t, // current target + l, // current distance + x, // x-distance + y; // y-distance + + // gauss-seidel relaxation + for (i = 0; i < n; ++i) { + o = distances[i]; + s = o.source; + t = o.target; + x = t.x - s.x; + y = t.y - s.y; + if (l = Math.sqrt(x * x + y * y)) { + l = alpha / (o.distance * o.distance) * (l - distance * o.distance) / l; + x *= l; + y *= l; + if (s.fixed) { + if (t.fixed) continue; + t.x -= x; + t.y -= y; + } else if (t.fixed) { + s.x += x; + s.y += y; + } else { + s.x += x; + s.y += y; + t.x -= x; + t.y -= y; + } + } + } + + // simulated annealing, basically + if ((alpha *= .99) < 1e-6) force.stop(); + + event.tick.dispatch({type: "tick"}); + } + + force.on = function(type, listener) { + event[type].add(listener); + return force; + }; + + force.nodes = function(x) { + if (!arguments.length) return nodes; + nodes = x; + return force; + }; + + force.links = function(x) { + if (!arguments.length) return links; + links = x; + return force; + }; + + force.size = function(x) { + if (!arguments.length) return size; + size = x; + return force; + }; + + force.distance = function(d) { + if (!arguments.length) return distance; + distance = d; + return force; + }; + + force.start = function() { + var i, + j, + k, + n = nodes.length, + m = links.length, + w = size[0], + h = size[1], + o; + + var paths = []; + for (i = 0; i < n; ++i) { + o = nodes[i]; + o.x = o.x || Math.random() * w; + o.y = o.y || Math.random() * h; + o.fixed = 0; + paths[i] = []; + for (j = 0; j < n; ++j) { + paths[i][j] = Infinity; + } + paths[i][i] = 0; + } + + for (i = 0; i < m; ++i) { + o = links[i]; + paths[o.source][o.target] = 1; + paths[o.target][o.source] = 1; + o.source = nodes[o.source]; + o.target = nodes[o.target]; + } + + // Floyd-Warshall + for (k = 0; k < n; ++k) { + for (i = 0; i < n; ++i) { + for (j = 0; j < n; ++j) { + paths[i][j] = Math.min(paths[i][j], paths[i][k] + paths[k][j]); + } + } + } + + distances = []; + for (i = 0; i < n; ++i) { + for (j = i + 1; j < n; ++j) { + distances.push({ + source: nodes[i], + target: nodes[j], + distance: paths[i][j] * paths[i][j] + }); + } + } + + distances.sort(function(a, b) { + return a.distance - b.distance; + }); + + if (interval) clearInterval(interval); + interval = setInterval(tick, 24); + return force; + }; + + force.resume = function() { + alpha = .1; + if (!interval) interval = setInterval(tick, 24); + return force; + }; + + force.stop = function() { + interval = clearInterval(interval); + return force; + }; + + // use `node.call(force.drag)` to make nodes draggable + force.drag = function() { + var node, element; + + this + .on("mouseover", function(d) { d.fixed = true; }) + .on("mouseout", function(d) { if (d != node) d.fixed = false; }) + .on("mousedown", mousedown); + + d3.select(window) + .on("mousemove", mousemove) + .on("mouseup", mouseup); + + function mousedown(d) { + (node = d).fixed = true; + element = this; + d3.event.preventDefault(); + } + + function mousemove() { + if (!node) return; + var m = d3.svg.mouse(element); + node.x = m[0]; + node.y = m[1]; + force.resume(); // restart annealing + } + + function mouseup() { + if (!node) return; + mousemove(); + node.fixed = false; + node = element = null; + } + + return force; + }; + + return force; +}; d3.layout.pie = function() { var value = Number, sort = null, @@ -407,4 +601,185 @@ function d3_layout_stackMaxIndex(array) { function d3_layout_stackSum(p, d) { return p + d.y; } +// Squarified Treemaps by Mark Bruls, Kees Huizing, and Jarke J. van Wijk +d3.layout.treemap = function() { + var children = d3_layout_treemapChildren, + value = d3_layout_treemapValue, + round = Math.round, + size = [1, 1]; // width, height + + // Recursively compute the node depth and value. + // Also converts the data representation into a standard tree structure. + // Also sorts child nodes by descending value to optimize squarification. + function sum(data, depth, nodes) { + var datas = children.call(treemap, data, depth), + node = {depth: depth, data: data}; + nodes.push(node); + if (datas) { + var i = -1, + n = datas.length, + c = node.children = [], + v = 0, + j = depth + 1; + while (++i < n) { + d = sum(datas[i], j, nodes); + if (d.value > 0) { // ignore NaN, negative, etc. + c.push(d); + v += d.value; + } + } + node.value = v; + } else { + node.value = value.call(treemap, data, depth); + } + if (!depth) scale(node, size[0] * size[1] / node.value); // root + return node; + } + + // Recursively compute the node area based on value & scale. + function scale(node, k) { + var children = node.children; + node.area = node.value * k; + if (children) { + var i = -1, + n = children.length; + while (++i < n) scale(children[i], k); + } + } + + // Recursively arranges the specified node's children into squarified rows. + function squarify(node) { + if (!node.children) return; + var rect = {x: node.x, y: node.y, dx: node.dx, dy: node.dy}, + row = [], + children = node.children.slice().sort(d3_layout_treemapSort), + child, + best = Infinity, // the best row score so far + score, // the current row score + u = Math.min(rect.dx, rect.dy), // initial orientation + n; + row.area = 0; + while ((n = children.length) > 0) { + row.push(child = children[n - 1]); + row.area += child.area; + if ((score = worst(row, u)) <= best) { // continue with this orientation + children.pop(); + best = score; + } else { // abort, and try a different orientation + row.area -= row.pop().area; + position(row, u, rect, false); + u = Math.min(rect.dx, rect.dy); + row.length = row.area = 0; + best = Infinity; + } + } + if (row.length) { + position(row, u, rect, true); + row.length = row.area = 0; + } + node.children.forEach(squarify); + } + + // Computes the score for the specified row, as the worst aspect ratio. + function worst(row, u) { + var s = row.area, + r, + rmax = 0, + rmin = Infinity, + i = -1, + n = row.length; + while (++i < n) { + r = row[i].area; + if (r < rmin) rmin = r; + if (r > rmax) rmax = r; + } + s *= s; + u *= u; + return Math.max((u * rmax) / s, s / (u * rmin)); + } + + // Positions the specified row of nodes. Modifies `rect`. + function position(row, u, rect, flush) { + var i = -1, + n = row.length, + x = rect.x, + y = rect.y, + v = u ? round(row.area / u) : 0, + o; + if (u == rect.dx) { // horizontal subdivision + if (flush || v > rect.dy) v = rect.dy; // over+underflow + while (++i < n) { + o = row[i]; + o.x = x; + o.y = y; + o.dy = v; + x += o.dx = round(o.area / v); + } + o.dx += rect.x + rect.dx - x; // rounding error + rect.y += v; + rect.dy -= v; + } else { // vertical subdivision + if (flush || v > rect.dx) v = rect.dx; // over+underflow + while (++i < n) { + o = row[i]; + o.x = x; + o.y = y; + o.dx = v; + y += o.dy = round(o.area / v); + } + o.dy += rect.y + rect.dy - y; // rounding error + rect.x += v; + rect.dx -= v; + } + } + + function treemap(d) { + var nodes = [], + root = sum(d, 0, nodes); + root.x = 0; + root.y = 0; + root.dx = size[0]; + root.dy = size[1]; + squarify(root); + return nodes; + } + + treemap.children = function(x) { + if (!arguments.length) return children; + children = x; + return treemap; + }; + + treemap.value = function(x) { + if (!arguments.length) return value; + value = x; + return treemap; + }; + + treemap.size = function(x) { + if (!arguments.length) return size; + size = x; + return treemap; + }; + + treemap.round = function(x) { + if (!arguments.length) return round != Number; + round = x ? Math.round : Number; + return treemap; + }; + + return treemap; +}; + +function d3_layout_treemapChildren(d) { + return d.children; +} + +function d3_layout_treemapValue(d) { + return d.value; +} + +function d3_layout_treemapSort(a, b) { + return b.area - a.area; +} })() \ No newline at end of file diff --git a/d3.layout.min.js b/d3.layout.min.js index 1dae9954..a10306db 100644 --- a/d3.layout.min.js +++ b/d3.layout.min.js @@ -1,7 +1,14 @@ -(function(){function A(a){return a.reduce(B,0)}function C(a){for(var j=1,f=0,c=a[0].y,i,e=a.length;jc){f=j;c=i}return f}function B(a,j){return a+j.y}d3.layout={};d3.layout.chord=function(){function a(){var g={},l=[],t=d3.range(b),q=[],r,n,w,o,s;c=[];i=[];r=0;for(o=-1;++oe)e=k;i.push(k)}h=0;for(b=j[0];hi){n=h;i=o}return n}function B(c,h){return c+h.y}function D(c){return c.children}function E(c){return c.value}function F(c,h){return h.area-c.area}d3.layout={};d3.layout.chord=function(){function c(){var a={},j=[],f=d3.range(g),s=[],t,k,q,r,u;i=[];o=[];t=0;for(r=-1;++rl)l=p;o.push(p)}m=0;for(g=h[0];m0){t.push(d);k+=d.value}}f.value=k}else f.value=g.call(o,e,b);b||h(f,p[0]*p[1]/f.value);return f}function h(e,b){var a=e.children;e.area=e.value*b;if(a)for(var j=-1,f=a.length;++j0;){a.push(f=j[f-1]);a.area+=f.area;f=t;for(var k=a.area,q=void 0,r=0,u=Infinity,v=-1,w=a.length;++v +r)r=q}k*=k;f*=f;if((f=Math.max(f*r/k,k/(f*u)))<=s){j.pop();s=f}else{a.area-=a.pop().area;i(a,t,b,false);t=Math.min(b.dx,b.dy);a.length=a.area=0;s=Infinity}}if(a.length){i(a,t,b,true);a.length=a.area=0}e.children.forEach(n)}}function i(e,b,a,j){var f=-1,s=e.length,t=a.x,k=a.y,q=b?m(e.area/b):0,r;if(b==a.dx){if(j||q>a.dy)q=a.dy;for(;++fa.dx)q=a.dx;for(;++f1){h=b[1];c=a[i];i++;g+="C"+(e[0]+f[0])+","+(e[1]+f[1])+","+(c[0]-h[0])+","+(c[1]-h[1])+","+c[0]+","+c[1];for(e=2;eb?1:0};d3.descending=function(a,b){return ba?1:0};d3.min=function(a,b){var d=0,g=a.length,e=a[0], +function Qa(a){return a.radius}function Ra(){return 64}function Sa(){return"circle"}d3={version:"1.5.0"};if(!Date.now)Date.now=function(){return+new Date};if(!Object.create)Object.create=function(a){function b(){}b.prototype=a;return new b};var N=function(a){return Array.prototype.slice.call(a)};try{N(document.documentElement.childNodes)}catch(eb){N=ua}d3.ascending=function(a,b){return ab?1:0};d3.descending=function(a,b){return ba?1:0};d3.min=function(a,b){var d=0,g=a.length,e=a[0], c;if(arguments.length==1)for(;++d(c=a[d]))e=c}else for(e=b(a[0]);++d(c=b(a[d])))e=c;return e};d3.max=function(a,b){var d=0,g=a.length,e=a[0],c;if(arguments.length==1)for(;++d=g.length)return f?f.call(d,h):c?h.sort(c):h;for(var k=-1,j=h.length,o=g[i++],p,m,n={};++k= g.length)return h;var k=[],j=e[i++],o;for(o in h)k.push({key:o,values:b(h[o],i)});j&&k.sort(function(p,m){return j(p.key,m.key)});return k}var d={},g=[],e=[],c,f;d.map=function(h){return a(h,0)};d.entries=function(h){return b(a(h,0),0)};d.key=function(h){g.push(h);return d};d.sortKeys=function(h){e[g.length-1]=h;return d};d.sortValues=function(h){c=h;return d};d.rollup=function(h){f=h;return d};return d};d3.keys=function(a){var b=[],d;for(d in a)b.push(d);return b};d3.values=function(a){var b=[], d;for(d in a)b.push(a[d]);return b};d3.entries=function(a){var b=[],d;for(d in a)b.push({key:d,value:a[d]});return b};d3.merge=function(a){return Array.prototype.concat.apply([],a)};d3.split=function(a,b){var d=[],g=[],e,c=-1,f=a.length;if(arguments.length<2)b=va;for(;++cClustered Network - + + + - +
+ diff --git a/examples/treemap/treemap.js b/examples/treemap/treemap.js new file mode 100644 index 00000000..802e43ec --- /dev/null +++ b/examples/treemap/treemap.js @@ -0,0 +1,26 @@ +var w = 960, + h = 500, + color = d3.scale.category20c(); + +var treemap = d3.layout.treemap() + .size([w, h]) + .children(function(d, i) { return typeof d.value == "object" && d3.entries(d.value); }) + .value(function(d) { return d.value; }); + +var div = d3.select("#chart").append("div") + .style("position", "relative") + .style("width", w + "px") + .style("height", h + "px"); + +d3.json("flare.json", function(json) { + div.data(d3.entries(json)).selectAll("div") + .data(treemap) + .enter().append("div") + .attr("class", "cell") + .style("background", function(d) { return d.children ? color(d.data.key) : null; }) + .style("left", function(d) { return d.x + "px"; }) + .style("top", function(d) { return d.y + "px"; }) + .style("width", function(d) { return d.dx - 1 + "px"; }) + .style("height", function(d) { return d.dy - 1 + "px"; }) + .text(function(d) { return d.children ? null : d.data.key; }); +}); diff --git a/src/core/core.js b/src/core/core.js index 2359cdd0..5baff628 100644 --- a/src/core/core.js +++ b/src/core/core.js @@ -1 +1 @@ -d3 = {version: "1.4.0"}; // semver +d3 = {version: "1.5.0"}; // semver diff --git a/examples/force/layout.js b/src/layout/force.js similarity index 97% rename from examples/force/layout.js rename to src/layout/force.js index 7f39ddc0..9d20f027 100644 --- a/examples/force/layout.js +++ b/src/layout/force.js @@ -1,8 +1,8 @@ // A rudimentary force layout using Gauss-Seidel. -function layout_force() { +d3.layout.force = function() { var force = {}, event = d3.dispatch("tick"), - size = {x: 1, y: 1}, + size = [1, 1], alpha = .5, distance = 30, interval, @@ -88,8 +88,8 @@ function layout_force() { k, n = nodes.length, m = links.length, - w = size.x, - h = size.y, + w = size[0], + h = size[1], o; var paths = []; @@ -183,6 +183,7 @@ function layout_force() { function mouseup() { if (!node) return; mousemove(); + node.fixed = false; node = element = null; } @@ -190,4 +191,4 @@ function layout_force() { }; return force; -} +}; diff --git a/examples/treemap/layout.js b/src/layout/treemap.js similarity index 93% rename from examples/treemap/layout.js rename to src/layout/treemap.js index 2d6c6d40..3053c8b9 100644 --- a/examples/treemap/layout.js +++ b/src/layout/treemap.js @@ -1,7 +1,7 @@ // Squarified Treemaps by Mark Bruls, Kees Huizing, and Jarke J. van Wijk -function layout_treemap() { - var children = layout_treemapChildren, - value = layout_treemapValue, +d3.layout.treemap = function() { + var children = d3_layout_treemapChildren, + value = d3_layout_treemapValue, round = Math.round, size = [1, 1]; // width, height @@ -49,7 +49,7 @@ function layout_treemap() { if (!node.children) return; var rect = {x: node.x, y: node.y, dx: node.dx, dy: node.dy}, row = [], - children = node.children.slice().sort(layout_treemapSort), + children = node.children.slice().sort(d3_layout_treemapSort), child, best = Infinity, // the best row score so far score, // the current row score @@ -166,16 +166,16 @@ function layout_treemap() { }; return treemap; -} +}; -function layout_treemapChildren(d) { +function d3_layout_treemapChildren(d) { return d.children; } -function layout_treemapValue(d) { +function d3_layout_treemapValue(d) { return d.value; } -function layout_treemapSort(a, b) { +function d3_layout_treemapSort(a, b) { return b.area - a.area; }