d3.js right align nested bar chart - javascript

I'm working with this d3.js example and am looking to change the charts entire orientation to go instead from right to left.
I was able to reverse the x-axis scale:
var x = d3.scale.linear().range([width, 0]);
and the placement of the y axis:
svg.append("g").attr("class", "y axis")
.attr("transform", "translate("+width+", 0)")
.append("line").attr("y1", "100%");
I believe I have to set the transform on each bar to the chart width - the bar width however applying ANY transform to the containing g has no effect.
function bar(d) {
var bar = svg.insert("g", ".y.axis")
.attr("class", "enter")
.attr("transform", "translate(0,5)")
.selectAll("g")
.data(d.children)
.enter().append("g");
//DOESNT WORK
bar.attr("transform", function(n){ return "translate("+ (width - x(n.value)) +", 0)"; })
.style("cursor", function(d) { return !d.children ? null : "pointer"; })
.on("click", down);
bar.append("text")....
bar.append("rect")....
return bar;
}
No transform is set on bar even if I just do a test with a fixed value, the result is translate(0,0) in the resulting page.
Why doesnt the transform not get applied here and is this even the correct way to make the bars right align? I also need the text to be on the right side of the bar instead of on the left and it seems that changing the order of appending makes no difference in this regard.

The problem with this particular example is that the code is a bit confusing -- the positions of the rect elements are set in various non-obvious places. In particular, the transform you are setting is overwritten immediately by other code.
I've modified the example to do what you want here. The key points are that I'm moving the g element containing all bars to the right
var bar = svg.insert("g", ".y.axis")
.attr("class", "enter")
.attr("transform", "translate("+width+",5)")
.selectAll("g")
// etc
and setting the x position of the rect elements to be their negative width
bar.append("rect")
.attr("x", function(d) { return width-x(d.value); })
.attr("width", function(d) { return x(d.value); })
.attr("height", barHeight);
The x position needs to be set in a few other places in the code in this way -- there're almost certainly more elegant ways to do this.

Related

D3 Categorical Area Chart - Scale Issue

I'm trying to create an area chart for statistics on each US state. I have a single number statistic for each state; an element of my data list looks like the following:
{'state':'CA','count':4000}
Currently, my area chart looks like this. The task is mainly complete, but you may notice how the very last category (in this case, UTAH) isn't filled. I'm not quite sure how to get around this. close_up
I am using a scaleBand axis; this felt appropriate. Perhaps it is not the correct approach. Here is the JS behind the chart:
var svg_area = d3.select("#area")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom),
g_area = svg_area.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var x = d3.scaleBand().range([0, width]),
y = d3.scaleLinear().range([height, 0]);
var area = d3.area()
.x(function(d) { return x(d.state); })
.y0(height)
.y1(function(d) { return y(d.count); });
d3.csv('data/states.csv', function(data) {
data.forEach(function(d) {
d.count = +d.count;
});
data.sort(function(a, b){
return b.count-a.count;
});
data = data.slice(0,30);
x.domain(data.map(function(d) { return d.state; }));
y.domain([0, d3.max(data, function(d) { return d.count; })]);
g_area.append('path')
.datum(data)
.attr('fill', solar[1])
.attr("class", "area")
.attr('d', area);
g_area.append("g")
.attr("class", "x-axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
g_area.append("g")
.attr("class", "y-axis")
.attr("transform", "translate(0," + 0 + ")")
.call(d3.axisLeft(y));
});
Any suggestions on how I can fix this? Thanks for any feedback!
Contrary to your question's title (now edited), the area chart is not "leaving out the last data point".
What you're seeing is the expected result, since you are using a band scale. Actually, that value just above the horizontal axis (just in the "edge" of the area chart) is Utah value! Try to understanding it with this explanation: Imagine a bar chart with your data. Each bar has, of course, a given width. Now, draw a path going from the top left corner of one bar to the top left corner of the next bar, starting at the first bar and, when reaching the last bar, going down from the top left corner to the axis. That's the area you have right now.
There are two solutions here. The first one is using a point scale instead:
var x = d3.scalePoint().range([0, width])
However, this will trim the "margins" of the area path, before the first state and after the last state (Utah). That means, the area chart will start right over California tick and end right over Utah tick.
If you don't want that there is a second solution, which is hacky, but will keep those "margins": add the bandwidth() to the last state in the area generator:
var area = d3.area()
.x(function(d, i) {
return i === data.length - 1 ?
x(d.state) + x.bandwidth() : x(d.state)
})
It may be worth noting that, using a band scale, your chart is technically incorrect: the values in the area for each state are not over the tick for that state.

Change bar dimension on mouseover event d3 js

I am creating a bar graph and I would like to have a focus feature in it. So whenever I select, mouseover event, a particular bar, the width and height of bar increase and everything else remains the same making this bar more in focus. Something like this :-
Lets say if I hover mouse on 2nd bar, it should look like this :-
Is is possible to leverage focus and zoom functionality of d3.js?
Threw something together for you https://jsfiddle.net/guanzo/h1hdet8d/1/
It doesn't account for the axis/labels at all, but it should get you started. The idea is that on hover you increase the scale of the bar. Calculate how much more width it has, then divide that by 2 to get how much you should shift the other bars.
Important: Apply .style('transform-origin','bottom') to the bars so that they grow upward and to both sides evenly.
g.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(d) { return x(d.letter); })
.attr("y", function(d) { return y(d.frequency); })
.attr("width", x.bandwidth())
.attr("height", function(d) { return height - y(d.frequency); })
.style('transform-origin','bottom')
.on('mouseover',mouseover)
.on('mouseout',mouseout)
function mouseover(data,index){
var bar = d3.select(this)
var width = bar.attr('width')
var height = bar.attr('height')
var scale = 1.5;
var newWidth = width* scale;
var newHeight = height*scale;
var shift = (newWidth - width)/2
bar.transition()
.style('transform','scale('+scale+')')
d3.selectAll('.bar')
.filter((d,i)=> i < index)
.transition()
.style('transform','translateX(-'+shift+'px)')
d3.selectAll('.bar')
.filter((d,i)=> i > index)
.transition()
.style('transform','translateX('+shift+'px)')
}
function mouseout(data,index){
d3.select(this).transition().style('transform','scale(1)')
d3.selectAll('.bar')
.filter(d=>d.letter !== data.letter)
.transition()
.style('transform','translateX(0)')
}

In D3 v4, how do I keep the width of a bar centered around a tick mark?

I am using D3 v4 (most, if not all, of the examples out there are for v3). Back in v3, they had something called rangeBand() which would be able to dynamically position everything neatly on the x-axis for me.
Now, in v4, I am wondering how to do that.
I have a bar chart:
var barEnter = vis.selectAll("g")
.data(rawdata)
.enter()
.append('g')
.append("rect")
.attr("class", "bar")
.attr("x", function(d) { return x(d.key); })
.attr("y", function(d) { return y(d.value); })
.attr("height", function(d) { return height - y(d.value); })
.attr("width", canvas_width / rawdata.length);
It is the width of this bar this is messing me up. If I set it to canvas_width / rawdata.length, it nicely positions the bars centered around each tick on the x-axis. The problem is that all the bars are pressed together and there is no padding in between.
So, naturally, I tried to do x.paddingInner(.5) which does add some padding but now the bars are not centered around the tick marks. Doing anything with x.paddingOuter() messes things up even more.
After searching around, I found that rangeBand() is what I want but that's only for v3. In the v4 docs, there is nothing that quite looks like it. Is it rangeRound()? Is it align()? I'm not sure. If anyone can point me in the right direction, it'd be greatly appreciated.
Without seeing your code for the axis, I suppose you're using scaleOrdinal(). If that's the case, you can change for scaleBand(), in which it's very easy to center the bar around the tick.
All you need is:
band.paddingInner([padding]): Sets the inner padding of the bars
band.bandwidth(): Gives you the bandwidth of each bar.
Then, you set the x position using the corresponding variable in your data and the width using bandwidth().
This is a small snippet to show you how it works:
var w = 300, h = 100, padding = 20;
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
var data = [{name: "foo", value:50},
{name: "bar", value:80},
{name: "baz", value: 20}];
var xScale = d3.scaleBand()
.range([0,w])
.domain(data.map(function(d){ return d.name}))
.paddingInner(0.2)
.paddingOuter(0.2);
var xAxis = d3.axisBottom(xScale);
var bars = svg.selectAll(".bars")
.data(data)
.enter()
.append("rect");
bars.attr("x", function(d){ return xScale(d.name)})
.attr("width", xScale.bandwidth())
.attr("y", function(d){ return (h - padding) - d.value})
.attr("height", function(d){ return d.value})
.attr("fill", "teal");
var gX = svg.append("g")
.attr("transform", "translate(0," + (h - padding) + ")")
.call(xAxis);
<script src="https://d3js.org/d3.v4.min.js"></script>

D3 js chart inverted issue

My Fiddle
I am studying d3js,i got this implementation of a bar chart with some data.
I need the chart to be inverted.
I tried adding:
var y = d3.scale.linear()
.domain([0, d3.max(jsonData.year)])
.range([500, 0]);
and calling that on the bars:
.attr("y", function(d) {
return y(d);
});
Didnt work..
How do i get around this? I think am missing something..
This is what i want along with the axis:
how's this? http://jsfiddle.net/jTs9A/3/
the problem was your transform, translate coordinates on appending the x and y axes at the bottom.
canvas.append("g")
.call(xAxis)
.attr("transform", "translate(0,0)");
canvas.append("g")
.call(yAxis)
.attr("transform", "translate(-10,0)");
you had (0,250) and (-10,-50) before
EDIT:
http://jsfiddle.net/jTs9A/4/
you needed to add this:
.attr("y", function(d) {
return (h- d.year*10); //Height minus data value
})
where h is the height of your graph area. check the tut in the comments (not enough rep yet)

d3 x axis labels outputted as long string

I am attempting to rotate my x-axis label for a d3 stacked histogram I created in d3. All of the labels are either displayed as a long string or all on top of each other.
Here's my label code:
var shortNames = ["label1", "label2", "label3", "label4"];
// Add a label per experiment.
var label = svg.selectAll("text")
.data(shortNames)
.enter().append("svg:text")
.attr("x", function(d) { return x(d)+x.rangeBand()/2; })
.attr("y", 6)
.attr("text-anchor", "middle")
.attr("dy", ".71em")
.text(function(d) {return d})
.attr("transform", function(d) { // transform all the text elements
return "rotate(-65)" // rotate them to give a nice slope
});
I played around with the translate function and all of the labels are still treated as one long string. How do I apply translate to each individual label?
I can play around with the margins later, but for now I want to have control over my labels.
I think the issue is the order of transforms: When you rotate the text, you're also rotating its coordinate system. So when you set its x position –– even though you're setting the position earlier than the rotation transform –- you're actually moving it along the 65 degree axis resulting from the rotation.
If I'm correct about this, then inspecting the labels would reveal that they're still made up of multiple text elements (one per label), rather than one text element for all labels.
As a rule, when you introduce a transform attribute as you did for rotate, you should do all your transformation via this attribute. So, you need to use translate instead of using the "x" attribute. Then it would look like this:
var label = svg.selectAll("text")
.data(shortNames)
.enter().append("svg:text")
// REOVE THIS: .attr("x", function(d) { return x(d)+x.rangeBand()/2; })
// AND THIS TOO: .attr("y", 6)
.attr("text-anchor", "middle")
.attr("dy", ".71em")
.text(function(d) {return d})
.attr("transform", function(d) { // transform all the text elements
return "translate(" + // First translate
x(d)+x.rangeBand()/2 + ",6) " + // Translation params same as your existing x & y
"rotate(-65)" // THEN rotate them to give a nice slope
});

Categories