Moxie
2014-10-28 584328285c5defcf59cb3fc11494f9f056be79c8
commit | author | age
48fe87 1 var w = 700,
M 2     h = w,
3     r = w / 2,
4     x = d3.scale.linear().range([0, 2 * Math.PI]),
5     y = d3.scale.pow().exponent(1.3).domain([0, 1]).range([0, r]),
6     p = 5,
7     duration = 1000;
8
9 var div = d3.select("#chart");
10
11 var vis = div.append("svg")
12     .attr("width", w + p * 2)
13     .attr("height", h + p * 2)
14   .append("g")
15     .attr("transform", "translate(" + (r + p) + "," + (r + p) + ")");
16
17 var partition = d3.layout.partition()
18     .sort(null)
19     .value(function(d) { return 5.8 - d.depth; });
20
21 var arc = d3.svg.arc()
22     .startAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x))); })
23     .endAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx))); })
24     .innerRadius(function(d) { return Math.max(0, d.y ? y(d.y) : d.y); })
25     .outerRadius(function(d) { return Math.max(0, y(d.y + d.dy)); });
26
27 d3.json("./moxie-dependencies.json", function(json) {
28   var nodes = partition.nodes(json);
29
30   var path = vis.selectAll("path").data(nodes);
31   path.enter().append("path")
32       .attr("id", function(d, i) { return "path-" + i; })
33       .attr("d", arc)
34       .attr("fill-rule", "evenodd")
35       .style("fill", colour)
36       .on("click", click);
37
38   var text = vis.selectAll("text").data(nodes);
39   var textEnter = text.enter().append("text")
40       .style("fill-opacity", 1)
41       .style("fill", function(d) {
42         return brightness(d3.rgb(colour(d))) < 125 ? "#eee" : "#000";
43       })
44       .attr("text-anchor", function(d) {
45         return x(d.x + d.dx / 2) > Math.PI ? "end" : "start";
46       })
47       .attr("dy", ".2em")
48       .attr("transform", function(d) {
49         var multiline = (d.name || "").split(" ").length > 1,
50             angle = x(d.x + d.dx / 2) * 180 / Math.PI - 90,
51             rotate = angle + (multiline ? -.5 : 0);
52         return "rotate(" + rotate + ")translate(" + (y(d.y) + p) + ")rotate(" + (angle > 90 ? -180 : 0) + ")";
53       })
54       .on("click", click);
55   textEnter.append("tspan")
56       .attr("x", 0)
57       .text(function(d) { return d.depth ? d.name.split(" ")[0] : ""; });
58   textEnter.append("tspan")
59       .attr("x", 0)
60       .attr("dy", "1em")
61       .text(function(d) { return d.depth ? d.name.split(" ")[1] || "" : ""; });
62
63   function click(d) {
64     path.transition()
65       .duration(duration)
66       .attrTween("d", arcTween(d));
67
68     // Somewhat of a hack as we rely on arcTween updating the scales.
69     text
70       .style("visibility", function(e) {
71         return isParentOf(d, e) ? null : d3.select(this).style("visibility");
72       })
73       .transition().duration(duration)
74       .attrTween("text-anchor", function(d) {
75         return function() {
76           return x(d.x + d.dx / 2) > Math.PI ? "end" : "start";
77         };
78       })
79       .attrTween("transform", function(d) {
80         var multiline = (d.name || "").split(" ").length > 1;
81         return function() {
82           var angle = x(d.x + d.dx / 2) * 180 / Math.PI - 90,
83               rotate = angle + (multiline ? -.5 : 0);
84           return "rotate(" + rotate + ")translate(" + (y(d.y) + p) + ")rotate(" + (angle > 90 ? -180 : 0) + ")";
85         };
86       })
87       .style("fill-opacity", function(e) { return isParentOf(d, e) ? 1 : 1e-6; })
88       .each("end", function(e) {
89         d3.select(this).style("visibility", isParentOf(d, e) ? null : "hidden");
90       });
91   }
92 });
93
94 function isParentOf(p, c) {
95   if (p === c) return true;
96   if (p.children) {
97     return p.children.some(function(d) {
98       return isParentOf(d, c);
99     });
100   }
101   return false;
102 }
103
104 function colour(d) {
105   if (d.children) {
106     // There is a maximum of two children!
107     var colours = d.children.map(colour),
108         a = d3.hsl(colours[0]),
109         b = d3.hsl(colours[1]);
110     // L*a*b* might be better here...
111     return d3.hsl((a.h + b.h) / 2, a.s * 1.2, a.l / 1.2);
112   }
113   return d.colour || "#fff";
114 }
115
116 // Interpolate the scales!
117 function arcTween(d) {
118   var my = maxY(d),
119       xd = d3.interpolate(x.domain(), [d.x, d.x + d.dx]),
120       yd = d3.interpolate(y.domain(), [d.y, my]),
121       yr = d3.interpolate(y.range(), [d.y ? 20 : 0, r]);
122   return function(d) {
123     return function(t) { x.domain(xd(t)); y.domain(yd(t)).range(yr(t)); return arc(d); };
124   };
125 }
126
127 function maxY(d) {
128   return d.children ? Math.max.apply(Math, d.children.map(maxY)) : d.y + d.dy;
129 }
130
131 // http://www.w3.org/WAI/ER/WD-AERT/#color-contrast
132 function brightness(rgb) {
133   return rgb.r * .299 + rgb.g * .587 + rgb.b * .114;
134 }