1 import Ember from 'ember'; |
1 import Ember from 'ember'; |
2 import d3 from 'd3'; |
2 import * as d3 from 'd3-selection'; |
|
3 import * as d3h from 'd3-hierarchy'; |
|
4 import * as d3s from 'd3-scale'; |
3 import _ from 'lodash/lodash'; |
5 import _ from 'lodash/lodash'; |
|
6 |
|
7 const MINIMUM_CIRCLE_WIDTH = 60.0; |
4 |
8 |
5 export default Ember.Component.extend({ |
9 export default Ember.Component.extend({ |
6 |
10 |
7 classNames: ['discourses-component'], |
11 classNames: ['discourses-component'], |
8 |
12 |
9 constants: Ember.inject.service(), |
13 constants: Ember.inject.service(), |
10 filter: Ember.inject.service(), |
14 filter: Ember.inject.service(), |
|
15 utils: Ember.inject.service(), |
11 |
16 |
12 discourseObserver: Ember.observer('filter.discourse', function() { |
17 discourseObserver: Ember.observer('filter.discourse', function() { |
13 Ember.$('.item').removeClass("selected"); |
18 Ember.$('.item').removeClass("selected"); |
14 if(this.get('filter').get('discourse') !== null) { |
19 if(this.get('filter').get('discourse') !== null) { |
15 this.get('filter').get('discourse').forEach(function(e) { |
20 this.get('filter').get('discourse').forEach(function(e) { |
23 var previousDiv = d3.selectAll(".discourses"); |
28 var previousDiv = d3.selectAll(".discourses"); |
24 var self = this; |
29 var self = this; |
25 |
30 |
26 var discourses = this.get('discourses'); |
31 var discourses = this.get('discourses'); |
27 var array = discourses.map(function (d) { return d.get('count'); }); |
32 var array = discourses.map(function (d) { return d.get('count'); }); |
28 var oldMin = Math.min(...array), |
|
29 oldMax = Math.max(...array); |
|
30 var sum = array.reduce(function(a, b) { return a + b; }); |
|
31 var average = sum / array.length; |
|
32 var newMin = Math.floor((average - oldMin)), |
|
33 newMax = Math.floor((oldMax - average)); |
|
34 |
33 |
35 var width = self.$().parent().width(); |
34 var width = self.$().parent().width(); |
36 var height = self.$().parent().height() - self.$().siblings().outerHeight(true); |
35 var height = self.$().parent().height() - self.$().siblings().outerHeight(true); |
37 |
36 |
38 var bubble = d3.layout.pack() |
37 //Determine the minimum circle width |
39 .sort(function comparator(a, b) { return a.value + b.value; }) |
38 var longerStr = _.max( |
|
39 [].concat(...(discourses.map(function(d) { return d.get('label').split(' ');}))), |
|
40 function(s) { return s.length; } |
|
41 ); |
|
42 var w = this.get('utils').getWidthOfText(longerStr, '11px'); |
|
43 // we try to take into account the fact that there is at least 2 lines |
|
44 var minimum_circle_width = Math.max( Math.sqrt((w*w)+Math.pow(11+11/2, 2)) + 10, MINIMUM_CIRCLE_WIDTH); |
|
45 |
|
46 // to avoid division by zero. In any case it makes no sense to consider dimensions |
|
47 // under MINIMUM_CIRCLE_WIDTH |
|
48 var scaleFactor = minimum_circle_width/Math.max(minimum_circle_width, Math.min(width, height)); |
|
49 |
|
50 var min = Math.min(...array), |
|
51 max = Math.max(...array); |
|
52 |
|
53 var scale = d3s.scaleLinear(); |
|
54 // The range is the range for font sizes |
|
55 var fontScale = d3s.scaleQuantize().domain([min, max]).range(_.range(10, 14)); |
|
56 |
|
57 if((min/max) < scaleFactor) { |
|
58 scale = scale.domain([min, max]).range([scaleFactor, 1]); |
|
59 } |
|
60 |
|
61 var bubble = d3h.pack() |
40 .size([width, height]) |
62 .size([width, height]) |
41 .value(function(d){ |
|
42 return Math.floor((((d.value - oldMin) * (newMax - newMin)) / (oldMax - oldMin)) + newMin); |
|
43 }) |
|
44 .padding(10); |
63 .padding(10); |
45 |
64 |
46 var element = d3.select('#' + self.get('elementId')) |
65 var element = d3.select('#' + self.get('elementId')) |
47 .append('div') |
66 .append('div') |
48 .attr('class','discourses') |
67 .attr('class','discourses') |
49 .style('position','absolute') |
68 .style('position','absolute') |
50 .style('left', -100000); |
69 .style('left', -100000); |
51 |
70 |
52 var bubbles = bubble.nodes(self.createNodes()); |
71 var root = d3h.hierarchy(self.createNodes()) |
|
72 .sum(function(d) {return scale(d.value);}); |
53 |
73 |
|
74 var bubbles = bubble(root).descendants(); |
54 var nodes = element |
75 var nodes = element |
55 .selectAll() |
76 .selectAll() |
56 .data(bubbles); |
77 .data(bubbles); |
57 |
78 |
58 nodes.enter().append("div") |
79 nodes.enter().append("div") |
59 .attr("class", function(d) { return ( d.children ? "category": "item" ) + ( (self.get('filter').get('discourse') !== null && _.contains(self.get('filter').get('discourse'), d.id)) ? " selected" : "" ) ; }); |
80 .attr("class", function(d) { return ( d.children ? "category": "item" ) + ( (self.get('filter').get('discourse') !== null && _.contains(self.get('filter').get('discourse'), d.id)) ? " selected" : "" ) ; }); |
60 |
81 |
61 var item = element.selectAll(".item") |
82 var item = element.selectAll(".item") |
62 .attr("data-id", function(d) { return d.id; }) |
83 .attr("data-id", function(d) { return d.data.id; }) |
63 .style("left", function(d) { return ( d.x - d.r) + "px"; }) |
84 .style("left", function(d) { return ( d.x - d.r) + "px"; }) |
64 .style("top", function(d) { return ( d.y - d.r) + "px"; }) |
85 .style("top", function(d) { return ( d.y - d.r) + "px"; }) |
65 .style("width", function(d) { return (d.r * 2) + "px"; }) |
86 .style("width", function(d) { return (d.r * 2) + "px"; }) |
66 .style("height", function(d) { return (d.r * 2) + "px"; }) |
87 .style("height", function(d) { return (d.r * 2) + "px"; }) |
67 .style("background-color", function(d) { return d.fill; }) |
88 .style("background-color", function(d) { return d.data.fill; }) |
68 .style("border-color", function(d) { return d.stroke; }) |
89 .style("border-color", function(d) { return d.data.stroke; }) |
69 .style("font-size", function(d) { return Math.floor((((d.value - oldMin) * (13 - 10)) / (oldMax - oldMin)) + 10) + 'px'; }) |
90 .style("font-size", function(d) { return fontScale(d.data.count) + 'px'; }) |
70 .on('click', function(d) { |
91 .on('click', function(d) { |
71 self.get('filter').setFilter('discourse', d.id); |
92 self.get('filter').setFilter('discourse', d.data.id); |
72 }); |
93 }); |
73 |
94 |
74 item.append("span") |
95 item.append("span") |
75 .html(function(d) { return d.name + ' <span class="count">(' + d.count + ')</span>'; }) |
96 .html(function(d) { return d.data.name + ' <span class="count">(' + d.data.count + ')</span>'; }) |
76 .style("margin-left", function() { return ( Ember.$(this).width() > Ember.$(this).parent().width() ? - ( Ember.$(this).width() / 2 ) + ( Ember.$(this).parent().width() / 2 ) : 0 ) + 'px'; }) |
97 .style("margin-left", function() { return ( Ember.$(this).width() > Ember.$(this).parent().width() ? - ( Ember.$(this).width() / 2 ) + ( Ember.$(this).parent().width() / 2 ) : 0 ) + 'px'; }) |
77 .style("margin-top", function() { return Ember.$(this).parent().height() / 2 - Ember.$(this).height() / 2 + 'px'; }); |
98 .style("margin-top", function() { return Ember.$(this).parent().height() / 2 - Ember.$(this).height() / 2 + 'px'; }); |
78 |
99 |
79 previousDiv.remove(); |
100 previousDiv.remove(); |
80 element.style('position',null).style('left',null); |
101 element.style('position',null).style('left',null); |