I'm trying to create a simple single stacked bar chart that goes left to right.
I've adapted the code found here, and I've gotten pretty close.
However, the stacked data is in the wrong direction.
The data at index 0 is all the right to the right, and the data at index 2 is all the way to the left.
I have a feeling it's got something to do with the rectangle and transition, but I'm not sure where I went wrong.
var rect = layer.selectAll("rect")
.data(function(d) {
return d;
})
.enter().append("rect")
.attr("y", function(d) {
return y(d.y);
})
.attr("x", 0)
.attr("height", y.rangeBand())
.attr("width", 0);
rect.transition()
.delay(function(d, i) {
return i * 10;
})
.attr("x", function(d) {
return x(d.x0 + d.x);
})
.attr("width", function(d) {
return x(d.x0) - x(d.x0 + d.x);
});
Fiddle
The main reason your stack starts at the right is that the range of your scale [width, 0] is inversely correlated to your domain [0, xStackMax]. Smaller input values will thus lead to larger output values, so your first x/x0 values will end up with values at the 'width' end of the scale.
Fix this so they both go in the same direction.
var x = d3.scale.linear()
.domain([0, xStackMax])
.range([0, width]);
Then change the x and width .attr calcs, the rects start at their scaled x0 coord and are as wide as the difference between x(d.x0 + d.x) - x(d.x0). For linear scales this can be simplified to x(d.x)
.attr("x", function(d) {
return x(d.x0);
})
.attr("width", function(d) {
return x(d.x0 + d.x) - x(d.x0);
});
https://jsfiddle.net/zkbxeby8/14/
Related
http://bl.ocks.org/d3noob/8952219
I want to have bar size width to be fixed..
from above example i have changed the code from
svg.selectAll("bar")
.data(data)
.enter().append("rect")
.style("fill", "steelblue")
.attr("x", function(d) { return x(d.date); })
.attr("width", x.rangeBand())
.attr("y", function(d) { return y(d.value); })
.attr("height", function(d) { return height - y(d.value); });
to
svg.selectAll("bar")
.data(data)
.enter().append("rect")
.style("fill", "steelblue")
.attr("x", function(d) { return x(d.date); })
.attr("width", 50)
.attr("y", function(d) { return y(d.value); })
.attr("height", function(d) { return height - y(d.value); });
but the labels are not moving to proper place also bars are getting overlapped
You have to change the range() of your x scale, to fit with your bar width value:
var x = d3.scale.ordinal().rangeRoundBands([0, width], .05);
to (if you want 50px as bar width)
var x = d3.scale.ordinal().range([0, data.length * 50]);
The range() method is used to define the display space for your scale.
I was looking for a similar solution. What #JulCh gave as an answer did not work out of the box for me, but lead me in the right direction.
Try:
var x = d3.scale.ordinal()
.range(d3.range(data.length).map(function (d) { return d * 50; }));
Where the inner d3.range creates an array containing the number of elements determined by data.length or some constant number (the number of bars you would like displayed).
Example: If data.length or some constant is 8 then [0,1,2,3,4,5,6,7] is returned from d3.range(8)
The map function then multiplies your fixed width of 50 against each element in the array returning [0,50,100,150,200,250,300,350].
D3 will then use these exact values to place your bars.
I have rectangles that I would like to plot from a csv file in the following format:
x1,x2,y1,y2,color
2,4,5,8,blue
4,7,9,11,red
...
So basically, I have the endpoints for each rectangle, however conventional D3 appends rects using x, y, height and width -- all of which are needed I believe. x1, x2, y1, y2 don't apply to rects if I'm not mistaken. So I'm a little confused about which approach to take.
I originally thought a line or path could do the trick, but I do not think I can create distinct shapes and distinct fills this way, as they would all be drawn in one fell swoop.
Please advise as to which methods would be most suitable for this data type.
Since width is just x2 - x1 and height is just y2 - y1:
rectangleSelection.attr("x", function(d) {
return d.x1
})
.attr("y", function(d) {
return d.y1
})
.attr("width", function(d) {
return d.x2 - d.x1
})
.attr("height", function(d) {
return d.y2 - d.y1
})
.attr("fill", function(d) {
return d.color
})
Here is a demo (I'm using a <pre> element to simulate your CSV, and also I'm increasing your values for better see the rectangles):
var svg = d3.select("svg");
var data = d3.csvParse(d3.select("#csv").text())
var rects = svg.selectAll("foo")
.data(data)
.enter()
.append("rect");
rects.attr("x", function(d) {
return d.x1
})
.attr("y", function(d) {
return d.y1
})
.attr("width", function(d) {
return d.x2 - d.x1
})
.attr("height", function(d) {
return d.y2 - d.y1
})
.attr("fill", function(d) {
return d.color
})
pre{
display: none;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<pre id="csv">x1,x2,y1,y2,color
20,40,50,80,blue
40,70,90,110,red
70,130,60,80,green</pre>
<svg></svg>
Have in mind that you have to be sure that y2 is greater than y1 and x2 is greater than x1. Otherwise you'll get an error: A negative value is not valid.
In my fiddle having horizontal bar graph; i want to give more space between the bars .my fiddle. I can tamper with the height attribute(line:101) and reduce the bar heights so that the space seems increased But i donot want to change their height. How can i increase the space between bars without changing their height?
Code for the rectangles
rects = groups.selectAll('rect')
.data(function (d) {
return d;
})
.enter()
.append('rect')
.attr('x', function (d) {
return xScale(d.x0);
})
.attr('y', function (d, i) {
return yScale(d.y);
})
.attr('height', function (d) {
return yScale.rangeBand();
})
.attr('width', function (d) {
return xScale(d.x);
})
You're already doing it in your code. When you write:
yScale = d3.scale.ordinal()
.domain(months)
.rangeRoundBands([0, height], .1);
That second argument in rangeRoundBands is the padding between the bars:
ordinal.rangeRoundBands(interval[, padding[, outerPadding]])
So, you just need to tweak that value. Check this fiddle, using 0.5: https://jsfiddle.net/catbu2oz/
But if you're talking about keeping the same height in pixels, there is only one solution: hardcoding the height value of the bars and increasing the range of the scale, as in this fiddle: https://jsfiddle.net/3xakLhfo/
I have a bar chart, which I am using transitions to animate the heights of rect elements like so:
//Create a layer for each category of data that exists, as per dataPointLegend values
//e.g. DOM will render <g class="successful"><g>
layers = svg.selectAll('g.layer')
.data(stacked, function(d) {
return d.dataPointLegend;
})
.enter()
.append('g')
.attr('class', function(d) {
return d.dataPointLegend;
})
//transform below is used to shift the entire layer up by one pixel to allow
//x-axis to appear clearly, otherwise bars inside layer appear over the top.
.attr('transform', 'translate(0,-1)');
//Create a layer for each datapoint object
//DOM will render <g class="successful"><g></g><g>
barLayers = layers.selectAll('g.layer')
.data(function(d) {
return d.dataPointValues;
})
.enter()
.append('g');
//Create rect elements inside each of our data point layers
//DOM will render <g class="successful"><g><rect></rect></g></g>
barLayers
.append('rect')
.attr('x', function(d) {
return x(d.pointKey);
})
.attr('width', x.rangeBand())
.attr('y', height - margin.bottom - margin.top)
.attr('height', 0)
.transition()
.delay(function(d, i) {
return i * transitionDelayMs;
})
.duration(transitionDurationMs)
.attr('y', function(d) {
return y(d.y0 + d.pointValue);
})
.attr('height', function(d) {
return height - margin.bottom - margin.top - y(d.pointValue)
});
I then have a further selection used for appending text elements
//Render any point labels if present
//DOM will render <g><g><rect></rect><text></text></g></g>
if (width > miniChartWidth) {
barLayers
.append('text')
.text(function(d) {
return d.pointLabel
})
.attr('x', function(d) {
return x(d.pointKey) + x.rangeBand() / 2;
})
.attr('y', function(d) {
var textHeight = d3.select(this).node().getBoundingClientRect().height;
//Position the text so it appears below the top edge of the corresponding data bar
return y(d.y0 + d.pointValue) + textHeight;
})
.attr('class', 'data-value')
.attr('fill-opacity', 0)
.transition()
.delay(function(d, i) {
return i * transitionDelayMs + transitionDurationMs;
})
.duration(transitionDurationMs)
.attr('fill-opacity', 1);
}
This fades in the text elements nicely after all the rects have finished growing in height. What I wondered, was whether its possible to append a text element to the corresponding layer as each bar finishes its transition?
I have seen the answer on this SO - Show text only after transition is complete d3.js
Which looks to be along the lines of what I am after, I tried adding an .each('end',...) in my rect rendering cycle like so
.each('end', function(d){
barLayers
.append('text')
.text(function() {
return d.pointLabel
})
.attr('x', function() {
return x(d.pointKey) + x.rangeBand() / 2;
})
.attr('y', function() {
var textHeight = d3.select(this).node().getBoundingClientRect().height;
//Position the text so it appears below the top edge of the corresponding data bar
return y(d.y0 + d.pointValue) + textHeight;
})
.attr('class', 'data-value')
.attr('fill-opacity', 0)
.transition()
.delay(function(d, i) {
return i * transitionDelayMs + transitionDurationMs;
})
.duration(transitionDurationMs)
.attr('fill-opacity', 1);
});
But I end up with lots of text elements for each of my g that holds a single rect for each of my datapoints.
I feel like I'm close, but need some assistance from you wise people :)
Thanks
whateverTheSelectionIs
.each('end', function(d){
barLayers
.append('text')
.each runs separately for every element in your selection, and inside the each you're adding text elements to every barLayer (barLayers). So you're going to get a (barLayers.size() * selection.size()) number of text elements added overall. You need to add only one text element in the each to the right bar / g.
The below is a fudge that might work. It's tricky because the text you want to add is a sibling of the rects in the selection that calls the .each function..., d3.select(this.parentNode) should move you up to the parent of the rect, which would be the right barLayer.
whateverTheSelectionIs
.each('end', function(d,i){
d3.select(this.parentNode)
.append('text')
I'm trying to add some space/padding for a nvd3 multi bar chart. "groupSpacing" is not what I need, since it only adds space between groups. I'll need space between each bar inside group. I found one link in github support. Can you post any solution or tweak?
I also found a d3 example of grouped bar chart. Any help in this example also very helpful to me.
Thanks.
I have draw a d3 group barchart:
fiddle
You can adjust the groupSpacing by change the code on line 56:
var groupSpacing = 6;
Technically i just achieve it by change the width of each rects' width:
var barsEnter = bars.enter().append('rect')
.attr('class', 'stm-d3-bar')
.attr('x', function(d,i,j) {
return (j * x1.rangeBand() );
})
.attr('y', function(d) { return y(d.y); })
.attr('height', function(d) { return height - y(d.y); })
.attr('width', x0.rangeBand() / barData.length - groupSpacing )
.attr('transform', function(d,i) {
return 'translate(' + x0(d.x) + ',0)';
})
.style("fill", function(d, i, j) {
return color(data[j].key);
});
Hope it helps you understand how you can achieve it in d3.
I minus the number of group spacing from the "width" attribute also. I found that the x-axis label looks a little off after I did that so I add the (group spacing / 2) to the "x" attribute. Here is the example of my code.
var groupSpacing = 15;
var rect = groups.selectAll("rect")
.data(function (d) { return d; })
.enter()
.append("rect")
.attr("x", function (d) { return x(d.x) + (groupSpacing / 2) ; })
.attr("y", function (d) { return y(d.y0 + d.y); })
.attr("height", function (d) { return y(d.y0) - y(d.y0 + d.y); })
.attr("width", x.rangeBand() - groupSpacing)