import Ember from 'ember';
import * as d3 from 'd3-selection';
import * as d3h from 'd3-hierarchy';
import * as d3s from 'd3-scale';
import * as d3t from 'd3-transition';
import * as d3e from 'd3-ease';
import _ from 'lodash';
// inspired by http://bl.ocks.org/ganeshv/6a8e9ada3ab7f2d88022
// and https://bost.ocks.org/mike/treemap/
export default Ember.Component.extend({
constants: Ember.inject.service(),
filter: Ember.inject.service(),
colors: Ember.inject.service(),
utils: Ember.inject.service(),
scale: Ember.computed('maxCount', 'minCount', function () {
let maxCount = this.get('maxCount');
let minCount = this.get('minCount');
return this.get('colors').getScaleLinear(minCount, maxCount);
}),
maxCount: 0,
minCount: 0,
firstRender: true,
filterLanguageObserver: Ember.observer('filter.language', function () {
Ember.$('.node').removeClass("selected");
Ember.$('.node[data-id="' + this.get('filter').get('language') + '"]').addClass("selected");
}),
filterOtherModified: false,
filterOtherObserver: Ember.observer('filter.date.[]', 'filter.discourse.[]', 'filter.theme.[]', 'filter.location', function() {
this.set('filterOtherModified', true);
this.set('firstRender', true);
}),
didRender: function () {
if(!this.get('filterOtherModified') && !this.get('firstRender')) {
return;
}
this.set('firstRender', false);
this.set('filterOtherModified', false);
var self = this;
var margin = { top: 30, right: 0, bottom: 0, left: 0 };
var width = Ember.$('#' + this.get('elementId')).width();
var height = Ember.$('#' + this.get('elementId')).height() - margin.top - margin.bottom;
var ratio = (1+Math.sqrt(5))/2;
var languages = this.get('languages');
var languagesMap = languages.reduce(function (res, l) { res[l.get('id')] = l.get('count'); return res; }, {});
var treemap = d3h.treemap()
.size([width/ratio, height])
.tile(d3h.treemapSquarify.ratio(1));
var element = d3.select('#' + this.get('elementId'))
.style("width", width + margin.left + margin.right + 'px')
.style("height", height + margin.bottom + margin.top + 'px')
.style("margin-left", -margin.left + "px")
.style("margin-right", -margin.right + "px");
var breadcrumbs = element.select("div.breadcrumbs");
if(breadcrumbs.empty()) {
breadcrumbs = element.insert("div", ":first-child")
.attr("class", "breadcrumbs")
.attr("y", -margin.top)
.style("width", width + 'px')
.style("height", margin.top + 'px');
}
var root = _.cloneDeep(this.constants.LANGUAGES_TREEMAP);
function ancestors(d) {
let ancestors = [];
let currentNode = d;
while (currentNode.parent) {
ancestors.unshift(currentNode = currentNode.parent);
}
return ancestors;
}
// This function set the count and parent attributes
function decorateData(d) {
if (d.children) {
d.count = d.children.reduce(function (p, v) { v.parent = d; return p + decorateData(v); }, 0);
} else if (d.values) {
d.count = d.values.reduce(function (p, v) { return p + (languagesMap[v] ? languagesMap[v] : 0); }, 0);
} else {
d.count = languagesMap[d.id] ? languagesMap[d.id] : 0;
}
return d.count;
}
decorateData(root);
// Clean tree with empty nodes
function cleanTree(n) {
if(n.children) {
n.children = n.children.filter(function(c) { return c.count !== 0;});
n.children.forEach(function(c) {
cleanTree(c);
});
}
}
cleanTree(root);
var transitioning = false;
display(root, 1);
function transition(d, node, nodeWrapper) {
if (transitioning || !d) {
return;
}
selectHandler(d);
transitioning = true;
var t = d3t.transition().duration(750).ease(d3e.easeLinear);
var newNode = display(d, 0);
var transitionNodes = node.transition(t);
var transitionNewNodes = newNode.transition(t);
newNode.style("fill-opacity", 0);
transitionNodes.style("opacity", 0).remove().on("end", function () {
nodeWrapper.remove();
transitioning = false;
});
transitionNewNodes.style("opacity", 1);
}
function selectHandler(d) {
if (d.id) {
self.get('filter').setFilter('language', d.id);
}
}
function display(rData, opacity) {
var countArray = rData.children.map(function (d) { return d.count; });
var dMin = Math.min.apply(null, countArray);
var dMax = Math.max.apply(null, countArray);
var globalMin = rData.children.reduce(function minRec(m, c) {
if(c.count < m) {
m = c.count;
}
if(c.children) {
m = c.children.reduce(minRec, m);
}
return m;
}, dMax);
var dataScale = d3s.scaleLinear().domain([globalMin, dMax]).range([globalMin, dMax]);
var nameArray = rData.children.reduce(function (res, d) {
res = res.concat(d.name.split(/[\s\-]/)); // eslint-disable-line no-useless-escape
return res;
}, ["(nnnnn)"]).sort(function(a,b) { return b.length - a.length; });
var w = self.get('utils').getWidthOfText(nameArray[0], '11px');
var p = w*w/(width*height);
if(dMin/dMax < p) {
dataScale = dataScale.range([p*100,100]);
}
var rNode = d3h.hierarchy(rData)
.sum(function (d) { return (d.children) ? 0 : dataScale(d.count); })
.sort(function (a, b) { return b.value - a.value; });
self.setProperties({ minCount: dMin, maxCount: dMax });
var scale = self.get('scale');
var backgroundColor = function (_d) { return scale(_d.data.count); };
var colorClass = function (_d) { return (self.get('colors').getPerceptiveLuminance(backgroundColor(_d)) >= 0.5) ? 'light-color' : 'dark-color'; };
var nodeWrapper = element.append("div")
.attr("class", "nodes")
.datum(rNode);
var descendants = treemap(rNode).descendants().filter(function (c) { return c.depth === rNode.depth + 1; });
var node = nodeWrapper.selectAll()
.data(descendants)
.enter()
.append("div")
.attr("data-id", function (d) { return d.data.id; });
node.attr("class", function (_d) { return "node " + colorClass(_d) + (_d.id === self.get('filter').get('language') ? " selected" : ""); })
.style("width", function (d) { return (Math.round(d.x1 * ratio) - Math.round(d.x0 * ratio)) + 'px'; })
.style("height", function (d) { return (d.y1 - d.y0) + 'px'; })
.style("left", function (d) { return Math.round(d.x0*ratio) + 'px'; })
.style("top", function (d) { return d.y0 + 'px'; })
.style("background-color", backgroundColor)
.style('opacity', opacity)
.attr('title', function (d) { return d.data.name + ' (' + d.data.count + ')'; })
.on("click", function (d) { selectHandler(d.data); });
node.filter(function (d) { return d.children; })
.classed("children", true)
.on("click", function (d) { transition(d.data, node, nodeWrapper); })
.append("i")
.attr("class", "fa fa-folder-o");
node.append("span")
.html(function (d) { return d.data.name + ' <span class="count">(' + d.data.count + ')</span>'; });
breadcrumbs
.selectAll("span")
.remove();
let i = 0;
ancestors(rNode.data).forEach(function (a) {
breadcrumbs
.append("span")
.attr('class', (i++) ? "level" : "root")
.html(a.name)
.datum(a)
.on("click", function (d) { transition(d, node, nodeWrapper); });
});
breadcrumbs
.append("span")
.attr('class', (i) ? "level" : "root")
.html(rNode.data.name)
.datum(rNode.data);
return node;
}
}
});