2 import d3 from 'd3'; |
2 import d3 from 'd3'; |
3 import ENV from 'app-client/config/environment'; |
3 import ENV from 'app-client/config/environment'; |
4 import _ from 'lodash/lodash'; |
4 import _ from 'lodash/lodash'; |
5 |
5 |
6 export default Ember.Component.extend({ |
6 export default Ember.Component.extend({ |
7 constants: Ember.inject.service(), |
7 |
8 didInsertElement: function(){ |
8 constants: Ember.inject.service(), |
9 var _this = this; |
9 |
10 var margin = {top: 20, right: 0, bottom: 0, left: 0}, |
10 didInsertElement: function(){ |
|
11 var self = this; |
|
12 var margin = {top: 20, right: 0, bottom: 0, left: 0}, |
11 width = Ember.$("#chart_div").width(), |
13 width = Ember.$("#chart_div").width(), |
12 height = 600 - margin.top - margin.bottom, |
14 height = 600 - margin.top - margin.bottom, |
13 formatNumber = d3.format(",d"), |
15 formatNumber = d3.format(",d"), |
14 transitioning; |
16 transitioning; |
15 |
17 |
16 var x = d3.scale.linear() |
18 var x = d3.scale.linear() |
17 .domain([0, width]) |
19 .domain([0, width]) |
18 .range([0, width]); |
20 .range([0, width]); |
19 |
21 |
20 var y = d3.scale.linear() |
22 var y = d3.scale.linear() |
21 .domain([0, height]) |
23 .domain([0, height]) |
22 .range([0, height]); |
24 .range([0, height]); |
23 |
25 |
24 var treemap = d3.layout.treemap() |
26 var treemap = d3.layout.treemap() |
25 .children(function(d, depth) { return depth ? null : d._children; }) |
27 .children(function(d, depth) { return depth ? null : d._children; }) |
26 .sort(function(a, b) { return a.value - b.value; }) |
28 .sort(function(a, b) { return a.value - b.value; }) |
27 .ratio(height / width * 0.5 * (1 + Math.sqrt(5))) |
29 .ratio(height / width * 0.5 * (1 + Math.sqrt(5))) |
28 .round(false); |
30 .round(false); |
29 |
31 |
30 var svg = d3.select("#chart_div").append("svg") |
32 var svg = d3.select("#chart_div").append("svg") |
31 .attr("width", width + margin.left + margin.right) |
33 .attr("width", width + margin.left + margin.right) |
32 .attr("height", height + margin.bottom + margin.top) |
34 .attr("height", height + margin.bottom + margin.top) |
33 .style("margin-left", -margin.left + "px") |
35 .style("margin-left", -margin.left + "px") |
34 .style("margin.right", -margin.right + "px") |
36 .style("margin.right", -margin.right + "px") |
35 .append("g") |
37 .append("g") |
36 .attr("transform", "translate(" + margin.left + "," + margin.top + ")") |
38 .attr("transform", "translate(" + margin.left + "," + margin.top + ")") |
37 .style("shape-rendering", "crispEdges"); |
39 .style("shape-rendering", "crispEdges"); |
38 |
40 |
39 var grandparent = svg.append("g") |
41 var grandparent = svg.append("g") |
40 .attr("class", "grandparent"); |
42 .attr("class", "grandparent"); |
41 |
43 |
42 grandparent.append("rect") |
44 grandparent.append("rect") |
43 .attr("y", -margin.top) |
45 .attr("y", -margin.top) |
44 .attr("width", width) |
46 .attr("width", width) |
45 .attr("height", margin.top); |
47 .attr("height", margin.top); |
46 |
48 |
47 grandparent.append("text") |
49 grandparent.append("text") |
48 .attr("x", 6) |
50 .attr("x", 6) |
49 .attr("y", 6 - margin.top) |
51 .attr("y", 6 - margin.top) |
50 .attr("dy", ".75em"); |
52 .attr("dy", ".75em"); |
51 |
53 |
52 var baseurl = ENV.baseURL.replace(/\/$/,"")+'/api/v1'; |
54 var baseurl = ENV.baseURL.replace(/\/$/,"")+'/api/v1'; |
53 d3.json(baseurl+"/languages", function(languages) { |
55 d3.json(baseurl+"/languages", function(languages) { |
54 |
56 |
55 var root = _.cloneDeep(_this.constants.LANGUAGES_TREEMAP); |
57 var root = _.cloneDeep(self.constants.LANGUAGES_TREEMAP); |
56 |
58 |
57 initialize(root); |
59 initialize(root); |
58 accumulate(root); |
60 accumulate(root); |
59 layout(root); |
61 layout(root); |
60 display(root); |
62 display(root); |
61 |
63 |
62 function initialize(root) { |
64 function initialize(root) { |
63 root.x = root.y = 0; |
65 root.x = root.y = 0; |
64 root.dx = width; |
66 root.dx = width; |
65 root.dy = height; |
67 root.dy = height; |
66 root.depth = 0; |
68 root.depth = 0; |
67 } |
69 } |
68 |
70 |
69 // Aggregate the values for internal nodes. This is normally done by the |
71 // Aggregate the values for internal nodes. This is normally done by the |
70 // treemap layout, but not here because of our custom implementation. |
72 // treemap layout, but not here because of our custom implementation. |
71 // We also take a snapshot of the original children (_children) to avoid |
73 // We also take a snapshot of the original children (_children) to avoid |
72 // the children being overwritten when when layout is computed. |
74 // the children being overwritten when when layout is computed. |
73 function accumulate(d) { |
75 function accumulate(d) { |
74 d._children = d.children; |
76 d._children = d.children; |
75 if(d.children) { |
77 |
76 d.value = d.children.reduce(function(p, v) { return p + accumulate(v); }, 0); |
78 if(d.children) { |
77 } else if (_.isArray(d.id)) { |
79 d.value = d.children.reduce(function(p, v) { return p + accumulate(v); }, 0); |
78 d.value = d.id.reduce( |
80 } else if (_.isArray(d.id)) { |
79 function(s,lid) { return s + (languages[lid]?languages[lid]:0); }, |
81 d.value = d.id.reduce(function(s,lid) { return s + (languages[lid]?languages[lid]:0); }, 0); |
80 0 |
82 } else { |
81 ); |
83 d.value = languages[d.id]?languages[d.id]:0; |
82 } |
84 } |
83 else { |
85 |
84 d.value = languages[d.id]?languages[d.id]:0; |
86 return d.value; |
85 } |
87 } |
86 return d.value; |
88 |
87 } |
89 // Compute the treemap layout recursively such that each group of siblings |
88 |
90 // uses the same size (1×1) rather than the dimensions of the parent cell. |
89 // Compute the treemap layout recursively such that each group of siblings |
91 // This optimizes the layout for the current zoom state. Note that a wrapper |
90 // uses the same size (1×1) rather than the dimensions of the parent cell. |
92 // object is created for the parent node for each group of siblings so that |
91 // This optimizes the layout for the current zoom state. Note that a wrapper |
93 // the parent’s dimensions are not discarded as we recurse. Since each group |
92 // object is created for the parent node for each group of siblings so that |
94 // of sibling was laid out in 1×1, we must rescale to fit using absolute |
93 // the parent’s dimensions are not discarded as we recurse. Since each group |
95 // coordinates. This lets us use a viewport to zoom. |
94 // of sibling was laid out in 1×1, we must rescale to fit using absolute |
96 function layout(d) { |
95 // coordinates. This lets us use a viewport to zoom. |
97 if (d._children) { |
96 function layout(d) { |
98 treemap.nodes({_children: d._children}); |
97 if (d._children) { |
99 d._children.forEach(function(c) { |
98 treemap.nodes({_children: d._children}); |
100 c.x = d.x + c.x * d.dx; |
99 d._children.forEach(function(c) { |
101 c.y = d.y + c.y * d.dy; |
100 c.x = d.x + c.x * d.dx; |
102 c.dx *= d.dx; |
101 c.y = d.y + c.y * d.dy; |
103 c.dy *= d.dy; |
102 c.dx *= d.dx; |
104 c.parent = d; |
103 c.dy *= d.dy; |
105 layout(c); |
104 c.parent = d; |
106 }); |
105 layout(c); |
107 } |
106 }); |
108 } |
107 } |
109 |
108 } |
110 function display(d) { |
109 |
111 grandparent |
110 function display(d) { |
112 .datum(d.parent) |
111 grandparent |
113 .on("click", transition) |
112 .datum(d.parent) |
114 .select("text") |
113 .on("click", transition) |
115 .text(name(d)); |
114 .select("text") |
116 |
115 .text(name(d)); |
117 var g1 = svg.insert("g", ".grandparent") |
116 |
118 .datum(d) |
117 var g1 = svg.insert("g", ".grandparent") |
119 .attr("class", "depth"); |
118 .datum(d) |
120 |
119 .attr("class", "depth"); |
121 var g = g1.selectAll("g") |
120 |
122 .data(d._children) |
121 var g = g1.selectAll("g") |
123 .enter().append("g"); |
122 .data(d._children) |
124 |
123 .enter().append("g"); |
125 g.classed("bla", true) |
124 |
126 .on("click", selectHandler); |
125 g.classed("bla", true).on("click", selectHandler); |
127 |
126 |
128 g.filter(function(d) { return d._children; }) |
127 g.filter(function(d) { return d._children; }) |
129 .classed("children", true) |
128 .classed("children", true) |
130 .on("click", transition); |
129 .on("click", transition); |
131 |
130 |
132 g.append("rect") |
131 g.append("rect") |
133 .attr("class", "parent") |
132 .attr("class", "parent") |
134 .call(rect) |
133 // .attr("fill", (d.color || "#bbb")) |
135 .append("title") |
134 .call(rect) |
136 .text(function(d) { return formatNumber(d.value); }); |
135 .append("title") |
137 |
136 .text(function(d) { return formatNumber(d.value); }); |
138 g.append("text") |
137 |
139 .attr("dy", ".75em") |
138 g.append("text") |
140 .text(function(d) { return d.name; }) |
139 .attr("dy", ".75em") |
141 .call(text); |
140 .text(function(d) { return d.name; }) |
142 |
141 .call(text); |
143 function transition(d) { |
142 |
144 if (transitioning || !d) { |
143 function transition(d) { |
145 return; |
144 if (transitioning || !d) { return; } |
146 } |
145 selectHandler(d); |
147 |
146 transitioning = true; |
148 selectHandler(d); |
147 |
149 transitioning = true; |
148 var g2 = display(d), |
150 |
149 t1 = g1.transition().duration(750), |
151 var g2 = display(d), |
150 t2 = g2.transition().duration(750); |
152 t1 = g1.transition().duration(750), |
151 |
153 t2 = g2.transition().duration(750); |
152 // Update the domain only after entering new elements. |
154 |
153 x.domain([d.x, d.x + d.dx]); |
155 // Update the domain only after entering new elements. |
154 y.domain([d.y, d.y + d.dy]); |
156 x.domain([d.x, d.x + d.dx]); |
155 |
157 y.domain([d.y, d.y + d.dy]); |
156 // Enable anti-aliasing during the transition. |
158 |
157 svg.style("shape-rendering", null); |
159 // Enable anti-aliasing during the transition. |
158 |
160 svg.style("shape-rendering", null); |
159 // Draw child nodes on top of parent nodes. |
161 |
160 svg.selectAll(".depth").sort(function(a, b) { return a.depth - b.depth; }); |
162 // Draw child nodes on top of parent nodes. |
161 |
163 svg.selectAll(".depth").sort(function(a, b) { return a.depth - b.depth; }); |
162 // Fade-in entering text. |
164 |
163 g2.selectAll("text").style("fill-opacity", 0); |
165 // Fade-in entering text. |
164 |
166 g2.selectAll("text").style("fill-opacity", 0); |
165 // Transition to the new view. |
167 |
166 t1.selectAll("text").call(text).style("fill-opacity", 0); |
168 // Transition to the new view. |
167 t2.selectAll("text").call(text).style("fill-opacity", 1); |
169 t1.selectAll("text").call(text).style("fill-opacity", 0); |
168 t1.selectAll("rect").call(rect); |
170 t2.selectAll("text").call(text).style("fill-opacity", 1); |
169 t2.selectAll("rect").call(rect); |
171 t1.selectAll("rect").call(rect); |
170 |
172 t2.selectAll("rect").call(rect); |
171 // Remove the old node when the transition is finished. |
173 |
172 t1.remove().each("end", function() { |
174 // Remove the old node when the transition is finished. |
173 svg.style("shape-rendering", "crispEdges"); |
175 t1.remove().each("end", function() { |
174 transitioning = false; |
176 svg.style("shape-rendering", "crispEdges"); |
175 }); |
177 transitioning = false; |
176 } |
178 }); |
177 |
179 } |
178 function selectHandler (d){ |
180 |
179 if (d.name === "Global"){ |
181 function selectHandler (d){ |
180 return _this.sendAction('action', null); |
182 if (d.id){ |
181 } |
183 self.get('setQueryParameters')(d.id); |
182 _this.sendAction('action', d.name); |
184 } |
183 } |
185 } |
184 |
186 |
185 return g; |
187 return g; |
186 } |
188 } |
187 |
189 |
188 function text(text) { |
190 function text(text) { |
189 text.attr("x", function(d) { return x(d.x) + 6; }) |
191 text.attr("x", function(d) { return x(d.x) + 6; }) |
190 .attr("y", function(d) { return y(d.y) + 6; }); |
192 .attr("y", function(d) { return y(d.y) + 6; }); |
191 } |
193 } |
192 |
194 |
193 function rect(rect) { |
195 function rect(rect) { |
194 rect.attr("x", function(d) { return x(d.x); }) |
196 rect.attr("x", function(d) { return x(d.x); }) |
195 .attr("y", function(d) { return y(d.y); }) |
197 .attr("y", function(d) { return y(d.y); }) |
196 .attr("width", function(d) { return x(d.x + d.dx) - x(d.x); }) |
198 .attr("width", function(d) { return x(d.x + d.dx) - x(d.x); }) |
197 .attr("height", function(d) { return y(d.y + d.dy) - y(d.y); }) |
199 .attr("height", function(d) { return y(d.y + d.dy) - y(d.y); }) |
198 .attr("fill", function(d) { return (d.color || "#bbb"); }); |
200 .attr("fill", function(d) { return (d.color || "#bbb"); }); |
199 } |
201 } |
200 |
202 |
201 function name(d) { |
203 function name(d) { |
202 return d.parent ? name(d.parent) + "." + d.name : d.name; |
204 return d.parent ? name(d.parent) + "." + d.name : d.name; |
203 } |
205 } |
204 }); |
206 |
205 } |
207 }); |
|
208 |
|
209 } |
|
210 |
206 }); |
211 }); |