Treemap layout: Resize node values to get bigger rect when value is smal + Create and display count + Node colors
authorChloe Laisne <chloe.laisne@gmail.com>
Fri, 23 Sep 2016 21:44:56 +0200
changeset 286 57762c0b601e
parent 285 6d8e7e6dc83c
child 287 4e199fd29689
Treemap layout: Resize node values to get bigger rect when value is smal + Create and display count + Node colors
cms/app-client/app/components/discourses-component.js
cms/app-client/app/components/visu-langues.js
cms/app-client/app/styles/app.scss
cms/app-client/app/styles/tabs/langues.scss
cms/app-client/app/templates/components/visu-langues.hbs
--- a/cms/app-client/app/components/discourses-component.js	Fri Sep 23 15:56:38 2016 +0200
+++ b/cms/app-client/app/components/discourses-component.js	Fri Sep 23 21:44:56 2016 +0200
@@ -17,10 +17,12 @@
 
         d3.json(baseURL + "/discourses", function(discourses) {
             var array = Object.keys(discourses).map(function (key) { return discourses[key].count; });
-            var oldMin = Math.min(...array), oldMax = Math.max(...array);
+            var oldMin = Math.min(...array),
+                oldMax = Math.max(...array);
             var sum = array.reduce(function(a, b) { return a + b; });
             var average = sum / array.length;
-            var newMin = Math.floor((average - oldMin)), newMax = Math.floor((oldMax - average));
+            var newMin = Math.floor((average - oldMin)),
+                newMax = Math.floor((oldMax - average));
 
             var width = self.$().parent().width();
             var height = self.$().parent().height() - self.$().siblings().outerHeight(true);
--- a/cms/app-client/app/components/visu-langues.js	Fri Sep 23 15:56:38 2016 +0200
+++ b/cms/app-client/app/components/visu-langues.js	Fri Sep 23 21:44:56 2016 +0200
@@ -10,94 +10,88 @@
 
     didInsertElement: function(){
         var self = this;
-        var margin = {top: 20, right: 0, bottom: 0, left: 0},
-        width = Ember.$("#chart_div").width(),
-        height = 600 - margin.top - margin.bottom,
-        formatNumber = d3.format(",d"),
-        transitioning;
+        var baseurl = ENV.rootURL.replace(/\/$/,"")+'/api/v1';
 
-        var x = d3.scale.linear()
-            .domain([0, width])
-            .range([0, width]);
+        d3.json(baseurl+"/languages", function(languages) {
+            var margin = { top: 30, right: 0, bottom: 0, left: 0 };
+            var width = $('#' + self.get('elementId')).width();
+            var height = $('#' + self.get('elementId')).height() - margin.top - margin.bottom;
 
-        var y = d3.scale.linear()
-            .domain([0, height])
-            .range([0, height]);
-
-        var treemap = d3.layout.treemap()
-            .children(function(d, depth) { return depth ? null : d._children; })
-            .sort(function(a, b) { return a.value - b.value; })
-            .ratio(height / width * 0.5 * (1 + Math.sqrt(5)))
-            .round(false);
+            var array = Object.keys(languages).map(function (key) { return languages[key]; });
+            var oldMin = Math.min(...array),
+                oldMax = Math.max(...array);
+            var sum = array.reduce(function(a, b) { return a + b; });
+            var average = sum / array.length;
+            var newMin = Math.floor((average - oldMin)),
+                newMax = Math.floor((oldMax - average));
+            
 
-        var svg = d3.select("#chart_div").append("svg")
-            .attr("width", width + margin.left + margin.right)
-            .attr("height", height + margin.bottom + margin.top)
-            .style("margin-left", -margin.left + "px")
-            .style("margin.right", -margin.right + "px")
-            .append("g")
-            .attr("transform", "translate(" + margin.left + "," + margin.top + ")")
-            .style("shape-rendering", "crispEdges");
-
-        var grandparent = svg.append("g")
-            .attr("class", "grandparent");
+            var x = d3.scale.linear()
+                .domain([0, width])
+                .range([0, width]),
+                y = d3.scale.linear()
+                .domain([0, height])
+                .range([0, height]);
 
-        grandparent.append("rect")
-            .attr("y", -margin.top)
-            .attr("width", width)
-            .attr("height", margin.top);
+            var treemap = d3.layout.treemap()
+                .children(function(d, depth) { return depth ? null : d._children; })
+                .sort(function(a, b) { return a.value - b.value; })
+                .value(function(d){
+                    return Math.floor((((d.value - oldMin) * (newMax - newMin)) / (oldMax - oldMin)) + newMin);
+                })
+                .round(false);
 
-        grandparent.append("text")
-            .attr("x", 6)
-            .attr("y", 6 - margin.top)
-            .attr("dy", ".75em");
+            var element = d3.select('#' + self.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")
+                .attr("transform", "translate(" + margin.left + "," + margin.top + ")")
+                .style("shape-rendering", "crispEdges");
 
-        var baseurl = ENV.rootURL.replace(/\/$/,"")+'/api/v1';
-        d3.json(baseurl+"/languages", function(languages) {
+            var 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(self.constants.LANGUAGES_TREEMAP);
+            root.x = root.y = 0;
+            root.dx = width;
+            root.dy = height;
+            root.depth = 0;
+            var transitioning = false;
 
-            initialize(root);
             accumulate(root);
             layout(root);
             display(root);
 
-            function initialize(root) {
-                root.x = root.y = 0;
-                root.dx = width;
-                root.dy = height;
-                root.depth = 0;
-            }
-
-            // Aggregate the values for internal nodes. This is normally done by the
-            // treemap layout, but not here because of our custom implementation.
-            // We also take a snapshot of the original children (_children) to avoid
-            // the children being overwritten when when layout is computed.
             function accumulate(d) {
                 d._children = d.children;
-
                 if(d.children) {
                     d.value = d.children.reduce(function(p, v) { return p + accumulate(v); }, 0);
-                } else if (_.isArray(d.id)) {
-                    d.value = d.id.reduce(function(s,lid) { return s + (languages[lid]?languages[lid]:0); }, 0);
                 } else {
-                    d.value = languages[d.id]?languages[d.id]:0;
+                    d.value = languages[d.id] ? languages[d.id] : 0;
                 }
-
                 return d.value;
             }
 
-            // Compute the treemap layout recursively such that each group of siblings
-            // uses the same size (1×1) rather than the dimensions of the parent cell.
-            // This optimizes the layout for the current zoom state. Note that a wrapper
-            // object is created for the parent node for each group of siblings so that
-            // the parent’s dimensions are not discarded as we recurse. Since each group
-            // of sibling was laid out in 1×1, we must rescale to fit using absolute
-            // coordinates. This lets us use a viewport to zoom.
             function layout(d) {
                 if (d._children) {
                     treemap.nodes({_children: d._children});
                     d._children.forEach(function(c) {
+                        function getCount(node, count = 0) {
+                            var c = languages[node.id];
+                            if(typeof c === 'undefined') {
+                                node._children.forEach(function(child) {
+                                    count = getCount(child, count);
+                                });
+                                return count;
+                            } else {
+                                return count + c;
+                            }
+                        }
+                        c.count = getCount(c);
                         c.x = d.x + c.x * d.dx;
                         c.y = d.y + c.y * d.dy;
                         c.dx *= d.dx;
@@ -109,37 +103,33 @@
             }
 
             function display(d) {
-                grandparent
+                breadcrumbs
                     .datum(d.parent)
-                    .on("click", transition)
-                    .select("text")
-                    .text(name(d));
+                    .html(name(d))
+                    .on("click", transition);
+
+                var nodes = element.append("div")
+                    .attr("class", "nodes")
+                    .datum(d);
 
-                var g1 = svg.insert("g", ".grandparent")
-                    .datum(d)
-                    .attr("class", "depth");
+                var node = nodes.selectAll()
+                    .data(d._children)
+                    .enter()
+                    .append("div");
 
-                var g = g1.selectAll("g")
-                    .data(d._children)
-                    .enter().append("g");
-
-                g.classed("bla", true)
+                node.attr("class", "node")
+                    .style("width", function(d) { return x(d.x + d.dx) - x(d.x) + 'px'; })
+                    .style("height", function(d) { return y(d.y + d.dy) - y(d.y) + 'px'; })
+                    .style("left", function(d) { return d.x + 'px'; })
+                    .style("top", function(d) { return d.y + 'px'; })
                     .on("click", selectHandler);
 
-                g.filter(function(d) { return d._children; })
+                node.filter(function(d) { return d._children; })
                     .classed("children", true)
                     .on("click", transition);
 
-                g.append("rect")
-                    .attr("class", "parent")
-                    .call(rect)
-                    .append("title")
-                    .text(function(d) { return formatNumber(d.value); });
-
-                g.append("text")
-                    .attr("dy", ".75em")
-                    .text(function(d) { return d.name; })
-                    .call(text);
+                node.append("span")
+                    .html(function(d) { return d.name + ' <span class="count">(' + d.count + ')</span>'; });
 
                 function transition(d) {
                     if (transitioning || !d) {
@@ -149,32 +139,35 @@
                     selectHandler(d);
                     transitioning = true;
 
-                    var g2 = display(d),
-                    t1 = g1.transition().duration(750),
-                    t2 = g2.transition().duration(750);
+                    var newNodes = display(d),
+                    transitionNodes = nodes.transition().duration(750),
+                    transitionNewNodes = newNodes.transition().duration(750);
 
-                    // Update the domain only after entering new elements.
                     x.domain([d.x, d.x + d.dx]);
                     y.domain([d.y, d.y + d.dy]);
 
-                    // Enable anti-aliasing during the transition.
-                    svg.style("shape-rendering", null);
+                    element.style("shape-rendering", null);
 
-                    // Draw child nodes on top of parent nodes.
-                    svg.selectAll(".depth").sort(function(a, b) { return a.depth - b.depth; });
+                    element.selectAll(".nodes").sort(function(a, b) { return a.depth - b.depth; });
 
-                    // Fade-in entering text.
-                    g2.selectAll("text").style("fill-opacity", 0);
+                    newNodes.selectAll("text").style("fill-opacity", 0);
 
-                    // Transition to the new view.
-                    t1.selectAll("text").call(text).style("fill-opacity", 0);
-                    t2.selectAll("text").call(text).style("fill-opacity", 1);
-                    t1.selectAll("rect").call(rect);
-                    t2.selectAll("rect").call(rect);
+                    transitionNodes.style("fill-opacity", 0)
+                        .style("width", function(d) { return x(d.x + d.dx) - x(d.x) + 'px'; })
+                        .style("height", function(d) { return y(d.y + d.dy) - y(d.y) + 'px'; })
+                        .style("left", function(d) { return x(d.x) + 'px'; })
+                        .style("top", function(d) { return y(d.y) + 'px'; })
+                        .attr("fill", function(d) { return (d.color || "#bbb"); });
 
-                    // Remove the old node when the transition is finished.
-                    t1.remove().each("end", function() {
-                        svg.style("shape-rendering", "crispEdges");
+                    transitionNewNodes.style("fill-opacity", 1)
+                        .style("width", function(d) { return x(d.x + d.dx) - x(d.x) + 'px'; })
+                        .style("height", function(d) { return y(d.y + d.dy) - y(d.y) + 'px'; })
+                        .style("left", function(d) { return x(d.x) + 'px'; })
+                        .style("top", function(d) { return y(d.y) + 'px'; })
+                        .attr("fill", function(d) { return (d.color || "#bbb"); });
+
+                    transitionNodes.remove().each("end", function() {
+                        element.style("shape-rendering", "crispEdges");
                         transitioning = false;
                     });
                 }
@@ -185,24 +178,11 @@
                     }
                 }
 
-                return g;
-            }
-
-            function text(text) {
-                text.attr("x", function(d) { return x(d.x) + 6; })
-                .attr("y", function(d) { return y(d.y) + 6; });
-            }
-
-            function rect(rect) {
-                rect.attr("x", function(d) { return x(d.x); })
-                    .attr("y", function(d) { return y(d.y); })
-                    .attr("width", function(d) { return x(d.x + d.dx) - x(d.x); })
-                    .attr("height", function(d) { return y(d.y + d.dy) - y(d.y); })
-                    .attr("fill", function(d) { return (d.color || "#bbb"); });
+                return node;
             }
 
             function name(d) {
-                return d.parent ? name(d.parent) + "." + d.name : d.name;
+                return d.parent ? name(d.parent) + '<span class="level">' + d.name + '</span>' : '<span class="root">' + d.name + '</span>';
             }
 
         });
--- a/cms/app-client/app/styles/app.scss	Fri Sep 23 15:56:38 2016 +0200
+++ b/cms/app-client/app/styles/app.scss	Fri Sep 23 21:44:56 2016 +0200
@@ -118,7 +118,8 @@
 
 .corpus-app-wrapper {
     background-color: $medium-blue;
-    box-shadow: -4px 0px 8px -4px $dark-grey;
+    box-shadow: 0px 0px 8px 0px rgba(0,0,0,0.8);
+    position: relative;
 }
 
 .corpus-app-modal,
--- a/cms/app-client/app/styles/tabs/langues.scss	Fri Sep 23 15:56:38 2016 +0200
+++ b/cms/app-client/app/styles/tabs/langues.scss	Fri Sep 23 21:44:56 2016 +0200
@@ -1,34 +1,63 @@
-#chart_div {
+#tabs-langues .ember-view {
     width: 100%;
-    height: 540px;
+    height: 600px;
+}
+
+#tabs-langues .breadcrumbs,
+#tabs-langues .node {
+	cursor: pointer;
 }
 
-text {
-    pointer-events: none;
+#tabs-langues .breadcrumbs:hover,
+#tabs-langues .node:hover {
+	background-color: $medium-blue;
+	color: $light-white;
 }
 
-.grandparent text {
-    font-weight: bold;
+#tabs-langues .breadcrumbs {
+	background-color: $light-blue;
+    color: $medium-blue;
+    position: relative;
+	line-height: 30px;
+	padding: 0 10px;
+	border-bottom: 1px solid $medium-blue;
+	box-sizing: border-box;
 }
 
-rect {
-    stroke: $light-white;
+#tabs-langues .breadcrumbs .root:only-child {
+	font-weight: bold;
 }
 
-rect.parent,
-.grandparent rect {
-    stroke-width: 2px;
+#tabs-langues .breadcrumbs .level::before {
+	content: '>';
+	margin: 0 10px;
+	font-weight: normal;
 }
 
-.grandparent rect {
-    fill: $light-blue;
+#tabs-langues .breadcrumbs .level:last-child {
+	font-weight: bold;
+}
+
+#tabs-langues .nodes {
+	width: inherit;
+	height: inherit;
+	position: relative;
 }
 
-.children rect.parent,
-.grandparent rect {
-    cursor: pointer;
+#tabs-langues .node {
+	position: absolute;
+	margin: 0;
+	padding: 0;
+	border-right: 1px solid $dark-grey;
+	border-bottom: 1px solid $dark-grey;
+	padding: 10px;
+	box-sizing: border-box;
 }
 
-.children rect.parent {
-    fill-opacity: .5;
+#tabs-langues .node:hover {
+	border-right: none;
+}
+
+#tabs-langues .node .count {
+	font-weight: bold;
 }
\ No newline at end of file
--- a/cms/app-client/app/templates/components/visu-langues.hbs	Fri Sep 23 15:56:38 2016 +0200
+++ b/cms/app-client/app/templates/components/visu-langues.hbs	Fri Sep 23 21:44:56 2016 +0200
@@ -1,1 +0,0 @@
-<div id="chart_div"></div>