Reputation: 7295
I'm creating a horizontal legend to appear above a bar chart I have created in with d3.js.
This is my first attempt and I struggling to figure how I position each item in the legend to take in account the width of the previous item.
So here is what I'm looking at so far:
And here is my code relevant to the legend rendering:
var legendRectSize = 18,
legendSpacing = 4;
svg.selectAll('.legend')
.data(data.chartKeys)
.enter()
.append('g')
.attr('class', 'legend')
.attr('transform', function(d, i) {
//width = the chart area width
var horz = width/2 + i*100; //This needs to add previously rendered elements width instead
return 'translate(' + horz + ',' + 0 + ')';
});
svg.selectAll('.legend').append('rect')
.attr('width', legendRectSize)
.attr('height', legendRectSize)
.attr('class', function(d) {
return d;
})
.style('stroke', 'black');
svg.selectAll('.legend').append('text')
.attr('x', legendRectSize + legendSpacing)
.attr('y', legendRectSize - legendSpacing)
.text(function(d) {
return d;
});
As you can see in the transform function I am just adding multiplying the index of each item by a factor of 100 to spread them horizontally, but really I could do with determining the width of the previously rendered element (so coloured box and text combined) and add this instead so the spacing between them is even, and nothing overlaps.
Is there a way in the transform function to get at the previous item(s), or is a different approach best?
Thanks
Upvotes: 1
Views: 850
Reputation: 55678
If you want an inline-block style layout, you'll need to keep some kind of running offset based on the real text width. You can see an example here: http://jsfiddle.net/nrabinowitz/r7yodrm1/
The key part of this is keeping offset
in the outer scope, then using .each
to update the offset with the text width:
var offset = 0;
var legendBlockWidth = 15;
var textMargin = 3;
legend.enter().append('g')
.attr('class', 'legend')
.each(function(key, i) {
var item = d3.select(this);
var text = item.append('text')
.text(key);
// ... snip ...
// Translate the group based on the running width
item.attr('transform', function() {
return 'translate(' + offset + ',0)';
});
// Update the offset
offset += text.node().getBBox().width + legendBlockWidth + textMargin * 3;
});
You could use a variation on this to use the max text width instead, which would give you even spacing with no collisions: http://jsfiddle.net/nrabinowitz/r7yodrm1/2/
var maxOffset = 0;
legend2.enter().append('g')
.attr('class', 'legend2')
.each(function(key, i) {
var item = d3.select(this);
var text = item.append('text')
.text(key);
// ... snip ...
// Update the offset
maxOffset = Math.max(
maxOffset,
text.node().getBBox().width + legendBlockWidth + textMargin * 3
);
});
legend2.attr('transform', function(d, i) {
return 'translate(' + (maxOffset * i) +',50)';
});
Upvotes: 4