|
1 <!DOCTYPE html> |
|
2 <html> |
|
3 <head> |
|
4 <title>Hierarchical Bar Chart</title> |
|
5 <script type="text/javascript" src="../../d3.js"></script> |
|
6 <script type="text/javascript" src="../../d3.layout.js"></script> |
|
7 <style type="text/css"> |
|
8 |
|
9 text { |
|
10 font: 10px sans-serif; |
|
11 } |
|
12 |
|
13 rect.background { |
|
14 fill: white; |
|
15 } |
|
16 |
|
17 .axis { |
|
18 shape-rendering: crispEdges; |
|
19 } |
|
20 |
|
21 .axis path, .axis line { |
|
22 fill: none; |
|
23 stroke: #000; |
|
24 } |
|
25 |
|
26 </style> |
|
27 </head> |
|
28 <body> |
|
29 <script type="text/javascript"> |
|
30 |
|
31 var m = [20, 20, 20, 120], // top right bottom left |
|
32 w = 960 - m[1] - m[3], // width |
|
33 h = 500 - m[0] - m[2], // height |
|
34 x = d3.scale.linear().range([0, w]), |
|
35 y = 20, // bar height |
|
36 z = d3.scale.ordinal().range(["steelblue", "#ccc"]), // bar color |
|
37 duration = 750, |
|
38 delay = 25; |
|
39 |
|
40 var hierarchy = d3.layout.partition() |
|
41 .value(function(d) { return d.size; }); |
|
42 |
|
43 var xAxis = d3.svg.axis() |
|
44 .scale(x) |
|
45 .orient("top"); |
|
46 |
|
47 var svg = d3.select("body").append("svg:svg") |
|
48 .attr("width", w + m[1] + m[3]) |
|
49 .attr("height", h + m[0] + m[2]) |
|
50 .append("svg:g") |
|
51 .attr("transform", "translate(" + m[3] + "," + m[0] + ")"); |
|
52 |
|
53 svg.append("svg:rect") |
|
54 .attr("class", "background") |
|
55 .attr("width", w) |
|
56 .attr("height", h) |
|
57 .on("click", up); |
|
58 |
|
59 svg.append("svg:g") |
|
60 .attr("class", "x axis"); |
|
61 |
|
62 svg.append("svg:g") |
|
63 .attr("class", "y axis") |
|
64 .append("svg:line") |
|
65 .attr("y1", "100%"); |
|
66 |
|
67 d3.json("../data/flare.json", function(root) { |
|
68 hierarchy.nodes(root); |
|
69 x.domain([0, root.value]).nice(); |
|
70 down(root, 0); |
|
71 }); |
|
72 |
|
73 function down(d, i) { |
|
74 if (!d.children || this.__transition__) return; |
|
75 var end = duration + d.children.length * delay; |
|
76 |
|
77 // Mark any currently-displayed bars as exiting. |
|
78 var exit = svg.selectAll(".enter").attr("class", "exit"); |
|
79 |
|
80 // Entering nodes immediately obscure the clicked-on bar, so hide it. |
|
81 exit.selectAll("rect").filter(function(p) { return p === d; }) |
|
82 .style("fill-opacity", 1e-6); |
|
83 |
|
84 // Enter the new bars for the clicked-on data. |
|
85 // Per above, entering bars are immediately visible. |
|
86 var enter = bar(d) |
|
87 .attr("transform", stack(i)) |
|
88 .style("opacity", 1); |
|
89 |
|
90 // Have the text fade-in, even though the bars are visible. |
|
91 // Color the bars as parents; they will fade to children if appropriate. |
|
92 enter.select("text").style("fill-opacity", 1e-6); |
|
93 enter.select("rect").style("fill", z(true)); |
|
94 |
|
95 // Update the x-scale domain. |
|
96 x.domain([0, d3.max(d.children, function(d) { return d.value; })]).nice(); |
|
97 |
|
98 // Update the x-axis. |
|
99 svg.selectAll(".x.axis").transition().duration(duration).call(xAxis); |
|
100 |
|
101 // Transition entering bars to their new position. |
|
102 var enterTransition = enter.transition() |
|
103 .duration(duration) |
|
104 .delay(function(d, i) { return i * delay; }) |
|
105 .attr("transform", function(d, i) { return "translate(0," + y * i * 1.2 + ")"; }); |
|
106 |
|
107 // Transition entering text. |
|
108 enterTransition.select("text").style("fill-opacity", 1); |
|
109 |
|
110 // Transition entering rects to the new x-scale. |
|
111 enterTransition.select("rect") |
|
112 .attr("width", function(d) { return x(d.value); }) |
|
113 .style("fill", function(d) { return z(!!d.children); }); |
|
114 |
|
115 // Transition exiting bars to fade out. |
|
116 var exitTransition = exit.transition() |
|
117 .duration(duration) |
|
118 .style("opacity", 1e-6) |
|
119 .remove(); |
|
120 |
|
121 // Transition exiting bars to the new x-scale. |
|
122 exitTransition.selectAll("rect").attr("width", function(d) { return x(d.value); }); |
|
123 |
|
124 // Rebind the current node to the background. |
|
125 svg.select(".background").data([d]).transition().duration(end); d.index = i; |
|
126 } |
|
127 |
|
128 function up(d) { |
|
129 if (!d.parent || this.__transition__) return; |
|
130 var end = duration + d.children.length * delay; |
|
131 |
|
132 // Mark any currently-displayed bars as exiting. |
|
133 var exit = svg.selectAll(".enter").attr("class", "exit"); |
|
134 |
|
135 // Enter the new bars for the clicked-on data's parent. |
|
136 var enter = bar(d.parent) |
|
137 .attr("transform", function(d, i) { return "translate(0," + y * i * 1.2 + ")"; }) |
|
138 .style("opacity", 1e-6); |
|
139 |
|
140 // Color the bars as appropriate. |
|
141 // Exiting nodes will obscure the parent bar, so hide it. |
|
142 enter.select("rect") |
|
143 .style("fill", function(d) { return z(!!d.children); }) |
|
144 .filter(function(p) { return p === d; }) |
|
145 .style("fill-opacity", 1e-6); |
|
146 |
|
147 // Update the x-scale domain. |
|
148 x.domain([0, d3.max(d.parent.children, function(d) { return d.value; })]).nice(); |
|
149 |
|
150 // Update the x-axis. |
|
151 svg.selectAll(".x.axis").transition().duration(duration).call(xAxis); |
|
152 |
|
153 // Transition entering bars to fade in over the full duration. |
|
154 var enterTransition = enter.transition() |
|
155 .duration(end) |
|
156 .style("opacity", 1); |
|
157 |
|
158 // Transition entering rects to the new x-scale. |
|
159 // When the entering parent rect is done, make it visible! |
|
160 enterTransition.select("rect") |
|
161 .attr("width", function(d) { return x(d.value); }) |
|
162 .each("end", function(p) { if (p === d) d3.select(this).style("fill-opacity", null); }); |
|
163 |
|
164 // Transition exiting bars to the parent's position. |
|
165 var exitTransition = exit.selectAll("g").transition() |
|
166 .duration(duration) |
|
167 .delay(function(d, i) { return i * delay; }) |
|
168 .attr("transform", stack(d.index)); |
|
169 |
|
170 // Transition exiting text to fade out. |
|
171 exitTransition.select("text") |
|
172 .style("fill-opacity", 1e-6); |
|
173 |
|
174 // Transition exiting rects to the new scale and fade to parent color. |
|
175 exitTransition.select("rect") |
|
176 .attr("width", function(d) { return x(d.value); }) |
|
177 .style("fill", z(true)); |
|
178 |
|
179 // Remove exiting nodes when the last child has finished transitioning. |
|
180 exit.transition().duration(end).remove(); |
|
181 |
|
182 // Rebind the current parent to the background. |
|
183 svg.select(".background").data([d.parent]).transition().duration(end);; |
|
184 } |
|
185 |
|
186 // Creates a set of bars for the given data node, at the specified index. |
|
187 function bar(d) { |
|
188 var bar = svg.insert("svg:g", ".y.axis") |
|
189 .attr("class", "enter") |
|
190 .attr("transform", "translate(0,5)") |
|
191 .selectAll("g") |
|
192 .data(d.children) |
|
193 .enter().append("svg:g") |
|
194 .style("cursor", function(d) { return !d.children ? null : "pointer"; }) |
|
195 .on("click", down); |
|
196 |
|
197 bar.append("svg:text") |
|
198 .attr("x", -6) |
|
199 .attr("y", y / 2) |
|
200 .attr("dy", ".35em") |
|
201 .attr("text-anchor", "end") |
|
202 .text(function(d) { return d.name; }); |
|
203 |
|
204 bar.append("svg:rect") |
|
205 .attr("width", function(d) { return x(d.value); }) |
|
206 .attr("height", y); |
|
207 |
|
208 return bar; |
|
209 } |
|
210 |
|
211 // A stateful closure for stacking bars horizontally. |
|
212 function stack(i) { |
|
213 var x0 = 0; |
|
214 return function(d) { |
|
215 var tx = "translate(" + x0 + "," + y * i * 1.2 + ")"; |
|
216 x0 += x(d.value); |
|
217 return tx; |
|
218 }; |
|
219 } |
|
220 |
|
221 </script> |
|
222 </body> |
|
223 </html> |