I'm putting together a D3 vertical bar chart and can't adjust the bar's height. When I adjust the barHeight variable from the following javascript, when rendered the bar's look exactly the same. Meaning if I took off the *50 in barHeight nothing would change. I'm probably overlooking something really really simple here, but how do I adjust the height?
var bars = svg.selectAll("rect")
.data(data_votes);
bars.enter()
.append("rect")
// .transition()
// .duration(1000);
bars.attr("x", function(d, i) {
return i * (w / data_votes.length);
})
.attr("y", function(d){
return h-d;
})
// Less bars == more width
.attr("width", w / data_votes.length - barPadding)
.attr("height", function(d){
var barHeight = d*50;
return barHeight;
})
.attr("fill", "teal");
bars.exit().remove();
Here is a working example with some corrected code:
And a very good example of why links to other sites that don't include the code are a bad idea. Tributary.io is not responding in December of 2019.
http://tributary.io/inlet/5767993
(You can click on any number and change it with the slider to see it's effect on the output)
I think the main issue was that the scale factor needed to be applied to both the "y" attribute and the height attribute.
You're right, I was adjusting the offset with the height so while the height pixel was actually increasing, it was offset proportionally and hidden off the edge of the svg canvas.
Here's how I adjusted it
.attr("y", function(d){
return h-(d*4);
})
// Less bars == more width
.attr("width", w / data_votes.length - barPadding)
.attr("height", function(d) {
return d * 4 ;
})
Related
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)')
}
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>
I'm wanting to scale the bar heights to the size of the SVG. http://jsfiddle.net/6d7984oa/
function d3_bar(s, dataset, barPadding) {
var self = s[0],
w = parseInt(d3.select(self).style("width")),
h = parseInt(d3.select(self).style("height")),
svg = d3.select(self)
.append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", function(d, i) {
return i * (w / dataset.length);
})
.attr("y", function(d) {
return h - d;
})
.attr("width", w / dataset.length - barPadding)
.attr("height", function(d) {
return d;
})
.attr("fill", function(d) {
return "rgb(0, 0, " + (d * 10) + ")";
});
}
I've tried quite a few things but the math never works.
.attr("height", function(d) {
var min = 1,
max = h,
diff = max - min;
return h - ( diff ? ((d - min) / diff) * h : 1 );
})
Struggling to see how this may be done.
You should use D3 scales. A lot of info can be found here: Quantitative scales.
A basic linear scale goes like this:
var yScale = d3.scale.linear()
.domain([0, d3.max(yourdata)])
.range([0,h]);
Please look up the d3.max() function. Basically it looks for the largest value in the given array, but you can use a function to specify what value to look at, in case your data is an array of objects.
The linear scale can be explained as following:
The domain stands for the values that go in, the values that need to be scaled into something else. You give a range of values, the min and the max values between '[]'. In your case, this is the data representing the height.
The range of a scale is what comes out. So you define the min and the maximum possible outcomes, in our case 0 and the width of the svg.
So what happens when you use the scale? well, say your data goes from 0 to 100 and the width of your svg is 50 units. When your data is 0, the scale will return 0, when your data is 100, your scale will return 50. So if your data would be 50, then the scale will return.... 25. I choose easy numbers here, it works well on more difficult cases too :-).
EDIT: if forgot to mention how to use the scale for lets say your height attribute:
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", function(d, i) {
return i * (w / dataset.length);
})
.attr("y", function(d) {
return h - d;
})
.attr("width", w / dataset.length - barPadding)
.attr("height", function(d){return yScale(d);})
.attr("fill", function(d) {
return "rgb(0, 0, " + (d * 10) + ")";
});
presuming of course that you named the scale 'yScale'.
Edit 2: My scale code had an error in the range. When scaling the y axes, you need to set the height in the range, not the width. So i fixed that.
Further more, when setting the y attribute of your rect, you need to use the scale for that one as well. Otherwise the bar height is scaled, but the matching y position isn't, resulting in awkward positioning of your rect tags. I should have mentioned that. Here is the correct code:
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", function(d, i) {
return i * (w / dataset.length);
})
.attr("y", function(d) {
return h - yScale(d);
})
.attr("width", w / dataset.length - barPadding)
.attr("height", function(d){return yScale(d);})
.attr("fill", function(d) {
return "rgb(0, 0, " + (d * 10) + ")";
});
You can see that I have changed the y attribute function using the yScale as well.
I apologize for the faulty code, I should check it better in the future.
UPDATE: New JSFIDDLE Scaling now working, ditched the defs and rect altogether and just appended the image. But still stuck on translate.
The translating is still not working on zoom. I can set the translate to say -100 for both x and y to get the non-zoomed placement correct. But, when zooming, it's of course still translating it -100 and not the larger value it would need to be to keep it in place.
Appears to need something in the code in the zoom section toward the bottom. Been messing with the part currently commented out, but no luck so far.
// .attr("transform", function(d) { return "translate(" + (d.x - v[0]) * k + "," + (d.y - v[1]) * k + ")"; })
// .attr("x", function(d) { return d.r * k; })
// .attr("y", function(d) { return d.r * k; })
.attr("width", function(d) { return d.r * k; })
.attr("height", function(d) { return d.r * k; })
Here's JSFIDDLE. I have a d3 circle packing with a raster image inside an svg rect within each node. How do you make the image scale when zooming? The container scales, but the image stays small and repeats when zoomed. Been trying to set the defs correctly, but no luck.
var defs = svg.append("defs")
// .data(nodes)
// .enter()
.append("pattern")
.attr("id", "bg")
.attr('patternUnits', 'userSpaceOnUse')
.attr('width', imageWidthHeight)
.attr('height', imageWidthHeight)
// .attr("transform", "translate(40,80)")
.append("image")
// .html("xlink:href", "img/" + function(d) { return d.image; })
.attr("xlink:href", "http://www.public-domain-photos.com/free-stock-photos-4/travel/yosemite/yosemite-meadows.jpg")
.attr('width', imageWidthHeight)
.attr('height', imageWidthHeight)
// .attr("transform", "translate(40,80)");
Also, can't get the container/image to translate into the center of the circle. I've commented those bits out for now because it screws everything up.
Have tried to apply info from these discussions, but still stuck. Thanks.
http://bl.ocks.org/mbostock/950642#graph.json
https://groups.google.com/forum/#!topic/d3-js/fL8_1BLrCyo
How to fill D3 SVG with image instead of colour with fill?
Adding elements to a D3 circle pack nodes
Answer JSFIDDLE
Got it. The trick was changing this bit of horrible:
(d.x - v[0]) * k
to this even worse bit of horrible:
(((d.x - v[0]) * (k)) - ((d.r / 2) * k))
Then the same for y.
Don't get me wrong, I'm grateful for the zoom circle pack template and the genius(es) who put it together. Thank you. It's just for someone at my noob level, the code above looks like a punishment of some kind. :)
I am attempting to add labels/axis/titles/etc to a D3 bar graph. I can get something close to the right size, however I end up clipping off part of the last bar (so the last bar is skinnier than the others).
Here is the pertinent code:
var x = d3.scale.linear()
.domain([0, object.data.length])
.range([0, object.width]);
var y = d3.scale.linear()
.domain([object.min-(object.max-object.min)*.15, object.max (object.max-object.min)*.15])
.rangeRound([ object.height - 30, 0]);
var vis = d3.select(object.ele)
.append("svg:svg")
.attr("width", object.width)
.attr("height", object.height)
.append("svg:g");
vis.selectAll("g")
.data(object.data)
.enter().append("rect")
.attr("x", function(d, i) { return x(i); })
.attr("y", function(d) { return object.height - y(d.value); })
.attr("width", object.width/object.data.length - 1)
.attr("height", function(d) { return y(d.value); })
.attr("transform", "translate(30,-30)");
At the moment everything (labels, axis, and so on) is 30px. How do I correctly alter the graph to make room for whatever else I need?
You are cutting off your last bar because you use translate the x coordinate but your the range of your x is only to the width without the extra 30 pixels.
Also it may be easier to simplify your y domain to use .domain([object.min, object.max]) then have the "y" and "height" functions reversed. This way you start the rect at the y(d.value) and make it's height object.height - y(d.value).
I would create three groups initially, one for your y axis, one for x axis, and then another for the bars. Draw your bars inside the last group and then translate the whole group itself instead of each individual bar. Increase the size of your object.width and object.height to match the total space you want.