Is there a way to minimize the padding between ranges in a grouped horizontal bar chart?
I am building a horizontal bar chart directive for AngularJS and at the moment I am pretty close but it's the spacing that I am not exactly happy with. If there is a better way to build the vertical ranges I would very much appreciate any tips. Here's my result so far:
angular.module('MissionControlApp').directive('d3GroupedHorizontalBarChart2', ['d3', '$timeout', function(d3, $timeout) {
return {
restrict: 'E',
scope: {
data: '=',
onClick: '&d3OnClick'
},
link: function(scope, ele) {
var refreshScope = function() {
scope.$apply();
};
var svg = d3.select(ele[0])
.append("svg")
.attr("width", "100%");
// on window resize, re-render d3 canvas
window.onresize = function() {
return scope.$apply();
};
scope.$watch(function(){
return angular.element(window)[0].innerWidth;
}, function(){
return scope.render(scope.data);
}
);
// watch for data changes and re-render
scope.$watch("data", function(newVals) {
if(!newVals) return;
return scope.render(newVals);
}, true);
// define render function for grouped bar charts
scope.render = function(data){
if(!data) return;
// remove all previous items before render
svg.selectAll("*").remove();
// setup variables
var margin = {top: 25, right: 40, bottom: 70, left: 150};
var width = d3.select(ele[0])._groups[0][0].offsetWidth - margin.left - margin.right;
var height = (scope.data.length * 60);
svg.attr('height', height + margin.top + margin.bottom);
var y0 = d3.scaleBand()
.rangeRound([0, height])
.paddingInner(0.01);
var y1 = d3.scaleBand()
.padding(0.01);
var x = d3.scaleLinear()
.rangeRound([0, width]);
var color = d3.scaleLinear()
.domain([0, 25, 50, 75, 100])
.range(["#51b75d", "#90eb9d","#ffff8c","#f5c93f","#c45c44"])
.interpolate(d3.interpolateHcl);
var xAxis = d3.axisBottom(x)
.tickSizeInner(-(height-5))
.tickPadding(8);
var keys = d3.keys(data[0]).filter(function(key) { return key !== "user"; });
y0.domain(data.map(function(d) { return d.user; }));
y1.domain(keys).rangeRound([0, y0.bandwidth()]);
x.domain([0, 100]);
// Define bars
var bar = svg.selectAll(".bar")
.data(data)
.enter().append("g")
.attr("transform", function(d) { return "translate(" + margin.left + "," + (y0(d.user) + (y0.bandwidth()/2) + margin.top - y1.bandwidth()) + ")"; });
var barEnter = bar.selectAll("rect")
.data(function(d) { return d.values; })
.enter();
barEnter.append("rect")
.attr("height", y1.bandwidth())
.attr("y", function(d) {return y1(d.name); })
.attr("x", 0)
.attr("value", function(d){return d.name;})
.attr("width", 0)
.attr("fill", function(d) { return color(d.value); })
.on("mouseover", function() { d3.select(this).style("cursor", "pointer");})
.on("mouseout", function() { d3.select(this).style("cursor", "default");})
.on("click", function(d){
scope.onClick({item: d});
d3.select(".selectedBlueFill").classed("selectedBlueFill", false);
d3.select(this).classed("selectedBlueFill", true);
$timeout(refreshScope, 0, false); // flush the digest cycle
})
.transition()
.duration(1000)
.attr("width", function(d) { return x(d.value); });
barEnter.append("text")
.attr("fill", "#000")
.attr("y", function(d){return y1(d.name) + (y1.bandwidth() / 2);})
.attr("x", function(d){return x(d.value);})
.attr("dx", 5)
.attr("dy", ".35em")
.text(function(d){return parseFloat(d.value).toFixed(0) + "%";})
.attr("fill-opacity", 0)
.transition()
.duration(1500)
.attr("fill-opacity", 1);
// Set up x axis
svg.append("g")
.attr("class", "axisHorizontal")
.attr("transform", "translate(" + margin.left + "," + (height + margin.top) + ")")
.call(xAxis);
// Set up y axis
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(d3.axisLeft(y0));
// Draw the legend
// Create the gradient for the legend
svg.append("defs")
.append("linearGradient")
.attr("id", "legend-traffic")
.attr("x1", "0%").attr("y1", "0%")
.attr("x2", "100%").attr("y2", "0%")
.selectAll("stop")
.data(color.range())
.enter().append("stop")
.attr("offset", function(d,i) { return i/(color.range().length-1); })
.attr("stop-color", function(d) { return d; });
// Legend variables
var legendWidth = width * 0.6;
var legendHeight = 10;
// Legend container
var legendSvg = svg.append('g')
.attr("class", "legendWrapper")
.attr("transform", "translate(" + ((width + margin.left + margin.right)/2) + "," + (height + margin.top + margin.bottom) + ")");
// Draw the rectangle
legendSvg.append("rect")
.attr("class", "legendRect")
.attr("x", -legendWidth/2)
.attr("y", -30)
.attr("width", legendWidth)
.attr("height", legendHeight)
.attr("fill", "url(#legend-traffic)");
// Append title
legendSvg.append("text")
.attr("class", "legendTitle")
.attr("x", 0)
.attr("y", -35)
.attr("text-anchor", "middle")
.text("Worksets Opened %");
// Set scale for x-axis
var xScale = d3.scaleLinear()
.range([0, legendWidth])
.domain([0,100]);
// Define x-axis
var legendAxis = d3.axisBottom(xScale).ticks(5);
// Set up x-axis
legendSvg.append("g")
.attr("class", "axisLegend")
.attr("transform", "translate(" + (-legendWidth/2) + "," + (legendHeight-30) + ")")
.call(legendAxis);
};
}
};
}]);
However the result I am getting is with large inner spacing. I am setting the .paddingInner
property to 0.001 and still pretty much end up with large spacing......ideas?
The idea would be to make groups, where each group hold unique bars for the group.
Now once you have the group you can alter the between distance with some maths, as shown below in snippet below:
var bar = chart
.selectAll("g")
.data(zippedData)
.enter().append("g")
.attr("transform", function(d, i) {
//here barHeight is the width of the bars.
return "translate(" + spaceForLabels + ","
+ (i * barHeight + gapBetweenGroups * (0.5 + Math.floor(i/data.series.length))) + ")";
});
By changing value of gapBetweenGroups
you can regulate the distance of the groups.
Working code here
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With