var chartWidth = parseInt(d3.select('#stacked').style('width'), 10); var margin = {top: 50, right: 100, bottom: 150, left: 100}, width = chartWidth - margin.left - margin.right, height = (chartWidth / 2) - margin.top - margin.bottom; var formatPercent = d3.format("%"); var titleSize = '180%'; var tableLabels = ['Type', 'Total', 'Percentage']; var xDivisors = [0, .3, .4, .5, 1], yDivisors = [0, .1, .25, .35, 1]; var x = d3.scale.linear(), y = d3.scale.ordinal(), color = d3.scale.ordinal(); var tooltip = d3.select("body").append("div") .attr("class", "stacked_tooltip") .style("opacity", 0); var svg = d3.select('#stacked').append('svg') .attr('width', width + margin.left + margin.right) .attr('height', height + margin.top + margin.bottom); var svgStacked = svg.append('g') .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')') .attr('class', 'table'); d3.json('activities.json', function(error, jsonData) { if (error) throw error; var data = jsonData.data; breadcrumb = jsonData.breadcrumb.join(' / '), scopes = jsonData.data.map(function(d) { return d.scope; }), pices = [], colors = [], picesLabels = []; // Get colors and labels for the legend for (var pice in jsonData.legend) { colors.push(jsonData.legend[pice].color) picesLabels.push(jsonData.legend[pice].text) } // Calculate the data for the table figures var nestedData = [], total; jsonData.data.forEach(function(d, i) { var rObj = {}, sum = 0; for (var prop in d.pices) { sum += parseFloat(d.pices[prop]) if (i == 0) { pices.push(prop) } } if (d.scope == "All") { total = sum; } rObj['scope'] = d.scope; rObj['total'] = sum; rObj['percentage'] = sum / total; nestedData.push(rObj); }); // Calculate the 'stacked' lengths data.forEach(function(d) { var x0 = 0; d.plotPices = pices.map(function(name, i) { return {name: name, x0: x0, x1: x0 += +d.pices[name], value: d.pices[name], label: picesLabels[i]}; }); d.total = d.plotPices[d.plotPices.length - 1].x1; }); // Define the scales x.domain([0, d3.max(data, function(d) { return d.total; })]) .range([xDivisors[3], xDivisors[3] * width]) y.domain(scopes) .rangeRoundBands([0, height * (yDivisors[4] - yDivisors[3])], .1); color.domain(pices) .range(colors); // Add svg svgStacked.selectAll('.title') .data([jsonData.title]) .enter() .append('text') .attr('class', 'title') .attr('x', 0) .attr('y', 0) .style('font-size', titleSize) .text(function(d) { return d; }); svgStacked.selectAll('.breadcrumb') .data([breadcrumb]) .enter() .append('text') .attr('class', 'breadcrumb') .attr('x', 0) .attr('y', (yDivisors[1] * height) + 'px') .style('fill', '#707070') .style('font-size', '120%') .text(function(d) { return d; }) var tableHeader = svgStacked.append('g') .attr('class', 'tableHeader') .attr('transform', 'translate(0,' + (yDivisors[2] * height) + ')'); tableHeader.selectAll('tableLabels') .data(tableLabels) .enter() .append('text') .attr('x', function(d, i) { return xDivisors[i] * width; }) .attr('y', 0) .text(function(d) { return d; }); tableHeader .append('line') .attr('x1', 0) .attr('y1', 10) .attr('x2', width) .attr('y2', 10) .attr('stroke', 'black') .attr('stroke-width', '1px'); var tableChart = svgStacked.append('g') .attr('class', 'tableChart') .attr('transform', 'translate(0,' + (yDivisors[3] * height) + ')'); var type = tableChart.append('g') .attr('class', 'type'); var total = tableChart.append('g') .attr('class', 'total') .attr('transform', 'translate(' + (xDivisors[1] * width) + ',0)'); var percentage = tableChart.append('g') .attr('class', 'percentage') .attr('transform', 'translate(' + (xDivisors[2] * width) + ',0)'); var chart = tableChart.append('g') .attr('class', 'chart') .attr('transform', 'translate(' + (xDivisors[3] * width) + ',0)'); type.selectAll('text') .data(scopes) .enter() .append('text') .attr('x', 0) .attr('y', function(d, i) { return y(d); }) .attr('dy', y.rangeBand()/5) .text(function(d) { return d; }); total.selectAll('text') .data(nestedData) .enter() .append('text') .attr('x', 0) .attr('y', function(d, i) { return y(d.scope); }) .attr('dy', y.rangeBand()/5) .text(function(d) { return d.total ; }); percentage.selectAll('text') .data(nestedData) .enter() .append('text') .attr('x', 0) .attr('y', function(d, i) { return y(d.scope); }) .attr('dy', y.rangeBand()/5) .text(function(d) { return formatPercent(d.percentage) ; }) // Draw the chart: // a group for each scope var scope = chart.selectAll(".scope") .data(data) .enter().append("g") .attr("class", function(d) { return "g scope " + normalize(d.scope); }) .attr("transform", function(d) { return "translate(0," + (y(d.scope) - y.rangeBand()/2) + ")"; }); // and inside each group scope.selectAll("rect") .data(function(d) { return d.plotPices; }) .enter().append("rect") .attr('class', function(d, i) { return 'bar ' + d.name; }) .attr("height", y.rangeBand()) .attr("x", function(d) { return x(d.x0); }) .attr("width", function(d) { return x(d.x1) - x(d.x0); }) .style("fill", function(d) { return color(d.name); }) .style('stroke', 'white') .on('mouseover', function(d) { var thisData = d3.select(this).data()[0]; var thisClass = this.classList; d3.selectAll('.bar') .filter(function(d) { return d.name != thisClass[1]; }) .transition() .duration(200) .style('opacity', .5) tooltip.transition() .duration(500) .style("opacity", .8); tooltip.html(thisData.label + '
' + thisData.value + ' activities') .style("left", (d3.event.pageX - 45) + "px") .style("top", (d3.event.pageY - 80) + "px"); }) .on('mouseout', function(d) { d3.selectAll('.bar') .transition() .duration(500) .style('opacity', 1); tooltip.transition() .duration(500) .style("opacity", 0); }) // Add the legend svg.append('g') .attr('class', 'legend') .attr('transform', 'translate(' + margin.left + ',' + (height + (margin.top * 1.5)) + ')'); var legend = d3.legend.color() .shapePadding(10) .shapeHeight(15) .shapeWidth(15) .scale(color) .labels(picesLabels); svg.select(".legend") .call(legend); }); var normalize = (function() { var from = "ÃÀÁÄÂÈÉËÊÌÍÏÎÒÓÖÔÙÚÜÛãàáäâèéëêìíïîòóöôùúüûÑñÇç ", to = "AAAAAEEEEIIIIOOOOUUUUaaaaaeeeeiiiioooouuuunncc_", mapping = {}; for(var i = 0, j = from.length; i < j; i++ ) mapping[ from.charAt( i ) ] = to.charAt( i ); return function( str ) { var ret = []; for( var i = 0, j = str.length; i < j; i++ ) { var c = str.charAt( i ); if( mapping.hasOwnProperty( str.charAt( i ) ) ) ret.push( mapping[ c ] ); else ret.push( c ); } return ret.join( '' ).toLowerCase(); } })();