D3.js stacked bar chart - javascript

I've spent enough time modifying the stacked bar chart sample that it would be wise to get a second pair of eyes to look at what I'm doing wrong. The modified sample is in a js fiddle.
My main changes are these:
1: On line 10 reversed the array for range
y = d3.scale.linear().range([height, 0])
2: On lines 17 and 22 added axis
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickFormat(format);
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
3: Line 62 changed the addition of y0 and y (this is the change that might be creating problem)
.attr("y", function (d) { return y(d.y) - y(d.y0); })
As you can see bars are going way above the specified height.
If someone could tell me what I'm doing wrong I'd be very thankful. I spent too much time looking just by myself. I do think I'm very close as chart is displaying other than the height problem.

There is one more change, which you haven't mentioned: that you the chart containing g is now translated to the top-left point instead of the bottom-left point, as was in the original plot.
Changing the following two lines around the line you mentioned will solve the problem:
.attr("y", function (d) { return y(d.y + d.y0); })
.attr("height", function (d) { return y(d.y0) - y(d.y +d.y0); })
Demo
This is how I generally do these calculations with an inverted scale such as y = d3.scale.linear().range([height, 0]):
Here, the point S(y + y0) will be the value of the point closer to the top, or with the lower y value, and the height of the bar will be = S(y0) - S(y + y0) or = abs(S(y0+y) - S(y0)), whichever you find more palatable.
However, to be honest, there is plenty of trial and error involved before I actually draw this down. Then I do more trial and error to find the solution and convince myself that this drawing helped.
So ... your mileage might vary. :)

Related

Updating x axis d3js

I'm trying to update my x axis in a D3js bar chart (is partially working) depending on a user filter, the bars are actually changing but is not doing it well. I don't really know where is the problem and I need some help.
in this part of the code I'm updating the bar chart
function update(selectedGroup) {
svg.selectAll("rect").remove()
var groups = d3.map(dataFilter, function(d){return(d.group)}).keys();
x.domain(groups);
var dataFilter = result.filter(function(d){return d.group==selectedGroup});
console.log(dataFilter);
var rectG=rectangulos(dataFilter)
}
the complete bar chart
how is working now:
the result should be something like this
I have an live example here
There is a relatively straightforward reason you are seeing this behavior.
When the domain of the scale x is all the groups x.bandwidth() is small. But when the domain of x is only one value, x.bandwidth() is large. In both cases, the first band starts in the same location.
Next we have a nested scale here xSubgroup - the range of this is equal to x.bandwidth(). When the domain of x changes, we need to update the range of xSubgroup. If we don't do this, the bars will still be very thin and start at the beginning of the axis (as the bars' bandwidth aren't changing even if the group's bandwidth does). You don't update the sub scale's range, but we need to do that:
x.domain(groups);
xSubgroup.range([0, x.bandwidth()])
With this we get the update we're looking for.
But the axis labels remain unchanged. Updating a scale doesn't update the axis unless we explicitly do so. I'll break up your method chaining and store a reference for the g holding the axis:
var xAxis = svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
xAxis.selectAll("text")
...
Now we can update the axis, I'm carrying forward the text styling as well. You can simplify the code by using an update function to do all the entering/exiting/updating of axes and data - here we have some duplication in that both the initial set up and the update function have overlap.
To update the axis we use:
// Call the axis again to update
xAxis.call(d3.axisBottom(x))
xAxis.selectAll("text")
.style("text-anchor", "end")
.attr("font-size", "55px")
.attr("y", "-7")
.attr("x", "-7")
.attr("transform", "rotate(-90)");
Which gives the desired behavior if I understand correctly, updated plunkr

d3 stacked area graph not working with log scale

I have been able to put together a stacked area graph.
See this fiddle, which uses dummy data. However, I want to implement the y axis as a logarithmic scale. But when I change the line
var y = d3.scale.linear()
.range([HEIGHT, 0]);
to
var y = d3.scale.log()
.range([HEIGHT, 0]);
my graph stops rendering with errors like
d3.v3.min.js:1 Error: Invalid value for attribute
d="M0,NaNQ400,NaN,500,NaNQ600,NaN,1000,NaNL1000,NaNQ600,NaN,500,NaNQ400,NaN,0,NaNZ".
I am not sure where I am going wrong. Can anyone please help?
Have a look at the documentation on Log Scales:
As log(0) is negative infinity, a log scale must have either an exclusively-positive or exclusively-negative domain; the domain must not include or cross zero.
You need to ensure that your domain does not include the zero value. Setting it to some small value will suffice:
y.domain([1e-6, d3.max(data, function(d) { return d.y0 + d.y; })]);
See the updated JSFiddle for a working example.

d3 js linear scale bar position and height in stacked to multiple charts

I'm using Mike Bostock's example as a template and building on it. My bar chart here.
After transition to stacked version, I am unable to get the y position of the bars. Bars of higher height overshadow the smaller ones. Most likely because of the valueOffset attribute of the stack. I am stuck on this issue for few days now.
Changes from Mike's example:
removed group labels in stacked chart
new y-axis y2 on linear scale. The domain for this axis is from 0 to the maximum of all the sums of values in each year which is 141.
defined new stack stack_year for relative positions of the bars.
Relevant code:
// y2 definition
y2.domain([0, d3.max(dataByGroup_year, function(d) { return d.year_wise_sum; })]).range([height, 0]);
// calculates sum of all wins per year
dataByGroup_year.forEach(function(d) {
var order = d.values.map(function(d) { return d.value; });
d.year_wise_sum = d3.sum(order);
});
function transitionStacked() {
var t = svg.transition().duration(750),
g = t.selectAll(".group").attr("transform", "translate(0," + y0(y0.domain()[0]) + ")");
g.selectAll("rect").attr("x", function(d) { return x(d.year); })
.attr("y", function(d) { return height - y2(d.valueOffset); })
.attr("height", function(d) { return height - y2(d.value); });
g.selectAll(".group-label").text("");
}
y0 is the ordinal scale used for multiple charts. y1 is the linear scale used for each chart in multiple charts.
Full HTML code at github
Data used: input file. I disabled tips for each bar.
Update: JSFIDDLE
Any help is much appreciated! Thank you
There were a number of issues here, which I've fixed up in this fiddle: http://jsfiddle.net/henbox/wL9x6cjk/4/
Part of the problems was the data itself (as per my comment above). There were some repeated values, which was causing issues when calculating the valueOffset correctly (using the d3.layout.stack)
I've also made some changes to how the y and attribute for each rect are calculated in the transitionStacked function. I changed what you had:
.attr("y", function(d) {
return height - y2(d.valueOffset);
})
to:
.attr("y", function (d) {
return y2(d.value + d.valueOffset) - height;
})
Note that you need to sum the d.value and d.valueOffset, before applying the scaling, to calculate the top left corner position of the rect. Additionally, you don't need to recalculate the x attribute value since this doesn't change between the two chart views, so I removed it
I also removed the call to stack_year(dataByGroup_year);. You don't need to build the stack layout here, just to calculate the maximum sum per year.
Finally I also tidied up the y-axis positioning a bit so there's enough space for the x-axis labels, and simplified the positioning of group elements in the stacked view. I also moved the x-axis to be appended to svg rather than group, which simplified positioning of elements

d3 zooming/panning behavior changes at millisecond intervals

I am working in a problem domain where I need to display data at millisecond time intervals. I have found that when you zoom in far enough on a graph, the behavior of the zoom/pan functions suddenly change! If you are using a time scale, this happens exactly when your axis starts displaying milliseconds.
Specifically, the behavior change is how d3 zooms around the mouse cursor. For intervals measured in seconds or above, the gridlines zoom with respect to the cursor position. i.e Mousing over 6PM and scrolling will cause 6PM to take up more space. But once you get to milliseconds, the grid seems to be zoomed from the far left, regardless of your cursor position. The panning also stops moving the axis around at all.
I am not very experienced in d3, but to me, this looks like a bug. I see nothing in the zoom documentation about this. Is there a reason why it behaves this way, and if so, can it be overridden somehow?
In the links below, as soon as you zoom far enough that the x-axis labels change from :YY to .YYY (ie. :50 to .653) you should see the problem.
This shows the "broken" behavior: http://jsfiddle.net/JonathanPullano/LuYDY/3/
This shows the "correct" behavior: http://jsfiddle.net/JonathanPullano/LuYDY/2/
EDIT: I have discovered that the problem is not in the zoom behavior, but rather the time.scale() itself. I made another fiddle which uses setTimeout to automatically rescale the axis without using zoom. The problem still persists. Try it here.
http://jsfiddle.net/JonathanPullano/LuYDY/4/
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 600 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
var x = d3.time.scale()
.domain([-5000, 5000])
.range([0, width]);
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append("rect")
.attr("width", width)
.attr("height", height);
var gXAxis = svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
function chart(selection) {
selection.each(function(data) {
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(5)
.tickSize(-height);
gXAxis.call(xAxis);
});
}
update();
function update() {
var start = new Date(x.domain()[0].getTime() + 10);
var end = new Date(x.domain()[1].getTime() - 10);
if(start.getTime() < end.getTime()) {
x.domain([start,end]);
setTimeout(update,10);
}
svg.call(chart);
}
The zoom and pan is behaving as expected, but the tick marks/ grid lines are being drawn at different values each time, which makes it look erratic at first and at extreme zooms makes it look like the grids aren't moving.
I've modified your fiddle to use a more complete tick format function, and also to draw a circle in the same point (on the scales) each time:
http://jsfiddle.net/LuYDY/5/
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(5)
.tickSize(-height)
.tickFormat(d3.time.format("%H:%M.%L") );
As you can see, the values zoom and pan as expected, but the tick values always get spaced at the same points on the window, rather than at round numbers of milliseconds.
The bug is simply that the time scale's definition of "nice" round datetime value falls apart at this scale. Explicitly telling the scale to be .nice() doesn't work, either.
The time scale methods try to find a suitable time unit for choosing ticks, and that doesn't work when the entire domain is less than 1 second. What would be ideal is if it switched to a linear scale definition of "nice" (i.e., round to multiples of powers of 10).
I.e. if you do:
x = d3.time.scale()
.domain([10, 477])
.nice();
And then query the x domain and convert the values back to integers, you still get exactly [10,477]. If you did that with a linear scale, the domain would be rounded off to [0,500]. Similarly, tick values for that time scale are placed at exact fractions of the domain, but are placed at round numbers for the linear scale.
You could make this an issue request on github. In the meantime, if your data domain is in the millisecond range, maybe just use a linear scale.

D3: Scale graph according the data points

I am having a bit of a trouble scaling my graph, according to the length on the bars. For example, in the jsfiddle, I can't draw a bar beyond the data point of size 25. I know one way to fix this would be to make the width and height of the body larger. But I was thinking scaling the entire graph would be much more efficient, so that one bar doesn't end up looking abnormally large.
http://jsfiddle.net/NkkDC/
I was thinking, I would have to scale the "y" function here, but I wasn't sure how.
bars.on("click", clickEvent)
.transition().duration(2000).delay(200)
.attr("y", function(d, i) { return i * 20; })
.attr("width", x)
.attr("height", 20);
Thanks in advance :)
The input domain of your xScale can change every time you add a new value (since you could add a new maximum), so we need to recalculate the xScale when we re-render the chart. Moving the declaration of the x-scale inside your render function should do the trick :
var xScale = d3.scale.linear()
.domain([0, d3.max(data)])
.range([0, 420]);
http://jsfiddle.net/NkkDC/1/

Categories