I've created a simple bar chart in D3, but the the bars are being created from top to bottom, instead of bottom to top. Below is the xScale, yScale and bar generation code:
var xscale = d3.scale.linear()
.domain([0, data.length])
.range([0, 240]);
var yscale = d3.scale.linear()
.domain([0, 100])
.range([0, 240]);
var bar = canvas.append('g')
.attr("id", "bar-group")
.attr("transform", "translate(10,20)")
.selectAll('rect')
.data(data)
.enter()
.append('rect')
.attr("class", "bar")
.attr("height", function(d, i) {
return yscale(d);
})
.attr("width", 15)
.attr("x", function(i) {
return yscale(i);
})
.attr("y", 0)
.style("fill", function(i) {
return colors(i);
});
Tried to swap yScale ranges but no success. Here is the fiddle.
In the SVG coordinates system, the origin (0,0) is at the top left corner, not the bottom left corner.
Besides that, your SVG has only 150px heigh. Thus, change your scale:
var yscale = d3.scale.linear()
.domain([0, 100])
.range([0, 150]);
And the math of your bars:
.attr("height", function(d, i) {
return yscale(d);
})
.attr("y", function(d){
return 150 - yscale(d)
})
Here is your updated fiddle: https://jsfiddle.net/bga2q72f/
PS: don't use the same scale for the x and y positions. It's quite confusing.
Related
I am trying to create a barplot using javascript. I have created a barplot, but want to add two axis. Currently stuck on the x-axis.
I am unable to move my x-axis to the bottom of my barplot. I am Using d3 to tailor the svg. I am currently able to showcase it at the top, but want to show it at the bottom.
Any input would be useful!
My attempts thus far have been to use transform, but when I execute this my axis disappears.
Googled several other solutions, none of them being successful.
Code:
<script>
d3.json("data_week3.json", function(data){
var data_renewables = [];
var data_nations = [];
for (i = 0; i < data.length; i++)
{
data_renewables.push(data[i].Renewable);
data_nations.push(data[i].Nation)
}
var width = 1000,
height = 500;
var y = d3.scale.linear()
.domain([0, d3.max(data_renewables)])
.range([height, 0]);
var chart = d3.select(".chart")
.attr("width", width)
.attr("height", height);
var barWidth = width / data_renewables.length;
var bar = chart.selectAll("g")
.data(data_renewables)
.enter().append("g")
.attr("transform", function(d, i) { return "translate(" + i * barWidth + ",0)"; });
bar.append("rect")
.attr("y", function(d) { return y(d); })
.attr("height", function(d) { return height - y(d); })
.attr("width", barWidth - 1);
bar.append("text")
.attr("x", barWidth / 2)
.attr("y", function(d) { return y(d); + 3; })
.attr("dy", ".75em")
.text(function(d) { return d; });
var axisScale = d3.scale.linear()
.domain([0, 30])
.range([0, 1000]);
var xAxis = d3.svg.axis()
.scale(axisScale)
.orient("bottom");
chart.append("g")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
})
</script>
You're giving the chart a height of height and then your transform is moving the top of the x axis by a value of height so it will always be cut off. I suggest you look at the margin convention: https://bl.ocks.org/mbostock/3019563
I am trying to create a bar-chart using D3. However, the bar chart is not populating. The following is my code:
var data = [Math.random(), Math.random(), Math.random(), Math.random(), Math.random(), Math.random()]
var height = 900
var width = 600
var x = d3.scale.linear()
.range([0, width])
.domain([0, d3.max(data)])
var y = d3.scale.linear()
.range([height, 0])
.domain([0, 500])
var svg = d3.select(".barchart").append('svg')
.attr("height", height)
.attr("width", width)
var rect = svg.selectAll('rects')
.data(data)
.enter()
.append('rects')
.attr("height", function(d, i) { return height - y(d) })
.attr("width", function (d, i) { return x(d) })
.attr("x", function(d, i) { return x(i) })
.attr("y", function(d, i) { return y(d) })
.attr("fill", "blue")
There is more than one issue so I'll just list them out.
Changes to your x and y scales. Yours were a bit off.
var x = d3.scale.linear()
.range([0, width])
.domain([0, data.length]);
var y = d3.scale.linear()
.range([0, height])
.domain([0, d3.max(data)]);
Change 'rects' to 'rect'.
var rect = svg.selectAll('rect') // <-- here
.data(data)
.enter()
.append('rect') // <-- and here
Lastly the logic for your x, y, width, and height calculations were off.
.attr("height", function(d, i) { return y(d); })
.attr("width", width / data.length - 1)
.attr("x", function(d, i) { return x(i); })
.attr("y", function(d, i) { return height - y(d); })
Make sure you are using semicolons.
http://jsfiddle.net/gnouvg6z/
Solution:
I spent a little time looking into this, and came up with a decent solution. Lars is right, d3.js doesn't really allow for what I wanted, but with a little layering, it worked out. Basically, I created a new SVG to contain only the axis, overlaid it on the actual graph and tied the two zoom scales together. Here is the code:
var x = d3.scale.linear()
.nice()
.domain([-1, 1])
.range([0, w]);
var xScale = d3.scale.linear()
.nice()
.domain([0, 1])
.range([0, 230]);
var xAxis = d3.svg.axis()
.scale(xScale)
.ticks(3)
.tickSize(7);
var y = d3.scale.linear()
.nice()
.domain([1, -1])
.range([h, 0]);
/* Control panning and zooming */
var zoom = function() {
if( d3.event ) {
zm.scale(d3.event.scale).translate(d3.event.translate);
za.scale(d3.event.scale); // don't translate so the axis is fixed
}
/* Do other zoom/pan related translations for chart */
// Update x-axis on pan and zoom
vis2.select(".xaxis")
.transition()
.ease("sin-in-out")
.call(xAxis);
vis.selectAll("line.link")
.attr("x1", function(d) { return x(parseFloat(d.source.x)); })
.attr("y1", function(d) { return y(parseFloat(d.source.y)); })
.attr("x2", function(d) { return x(parseFloat(d.target.x)); })
.attr("y2", function(d) { return y(parseFloat(d.target.y)); });
};
var zm = d3.behavior.zoom().x(x).y(y).scaleExtent([-Infinity, Infinity]).on("zoom", zoom);
var za = d3.behavior.zoom().x(xScale).scaleExtent([-Infinity, Infinity]).on("zoom", zoom);
force = d3.layout.force()
.gravity(0)
.charge(-5)
.alpha(-20)
.size([w, h]);
nodes = force.nodes();
links = force.links();
vis = d3.select(CHART).append("svg:svg")
.attr("width", w)
.attr("height", h)
.call(zm);
vis.append("rect")
.attr("class", "overlay")
.attr("width", "100%")
.attr("height", "100%");
vis2 = vis.append("svg:svg")
.attr("width", 300)
.attr("height", 30)
.call(za);
vis2.append("svg:g")
.attr("class", "xaxis")
.attr("transform", "translate(20,10)")
.call(xAxis);
vis2.append("rect")
.attr("class", "overlay")
.attr("width", "100%")
.attr("height", "100%");
force.on("tick", zoom);
The result (with some additional CSS, of course) looks something like this, where the scale adjusts automatically as the user zooms in and out:
http://i.imgur.com/TVYVp4M.png
Original Question:
I have a chart in D3js that displays a scale along the X-axis and appropriately updates as I zoom in and out. However, rather than having the entire bottom of the chart be a scale, I'd like to display the scale as more of a legend, like a distance scale on a map (ex. http://2012books.lardbucket.org/books/essentials-of-geographic-information-systems/section_06/91302d9e3ef560ae47c25d02a32a629a.jpg). Is this possible?
Snippet of relevant code:
var x = d3.scale.linear()
.nice()
.domain([-1, 1])
.range([0, w]);
var xAxis = d3.svg.axis()
.scale(x);
var zoom = function() {
vis.selectAll("g.node")
.attr("transform", function(d) {
return "translate(" + x(parseFloat(d.x)) + "," + y(parseFloat(d.y)) + ")";
});
// Update x-axis on pan and zoom
vis.select(".xaxis")
.transition()
.ease("sin-in-out")
.call(xAxis);
};
I have a vertical bar chart that is grouped in pairs. I was trying to play around with how to flip it horizontally. In my case, the keywords would appear on the y axis, and the scale would appear on the x-axis.
I tried switching various x/y variables, but that of course just produced funky results. Which areas of my code do I need to focus on in order to switch it from vertical bars to horizontal ones?
My JSFiddle: Full Code
var xScale = d3.scale.ordinal()
.domain(d3.range(dataset.length))
.rangeRoundBands([0, w], 0.05);
// ternary operator to determine if global or local has a larger scale
var yScale = d3.scale.linear()
.domain([0, d3.max(dataset, function (d) {
return (d.local > d.global) ? d.local : d.global;
})])
.range([h, 0]);
var xAxis = d3.svg.axis()
.scale(xScale)
.tickFormat(function (d) {
return dataset[d].keyword;
})
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left")
.ticks(5);
var commaFormat = d3.format(',');
//SVG element
var svg = d3.select("#searchVolume")
.append("svg")
.attr("width", w + margin.left + margin.right)
.attr("height", h + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// Graph Bars
var sets = svg.selectAll(".set")
.data(dataset)
.enter()
.append("g")
.attr("class", "set")
.attr("transform", function (d, i) {
return "translate(" + xScale(i) + ",0)";
});
sets.append("rect")
.attr("class", "local")
.attr("width", xScale.rangeBand() / 2)
.attr("y", function (d) {
return yScale(d.local);
})
.attr("x", xScale.rangeBand() / 2)
.attr("height", function (d) {
return h - yScale(d.local);
})
.attr("fill", colors[0][1])
;
sets.append("rect")
.attr("class", "global")
.attr("width", xScale.rangeBand() / 2)
.attr("y", function (d) {
return yScale(d.global);
})
.attr("height", function (d) {
return h - yScale(d.global);
})
.attr("fill", colors[1][1])
;
sets.append("rect")
.attr("class", "global")
.attr("width", xScale.rangeBand() / 2)
.attr("y", function (d) {
return yScale(d.global);
})
.attr("height", function (d) {
return h - yScale(d.global);
})
.attr("fill", colors[1][1])
;
I just did the same thing last night, and I basically ended up rewriting the code as it was quicker than fixing all the bugs but here's the tips I can give you.
The biggest issues with flipping the x and y axis will be with things like return h - yScale(d.global) because height is calculated from the "top" of the page not the bottom.
Another key thing to remember is that when you set .attr("x", ..) make sure you set it to 0 (plus any padding for the left side) so = .attr("x", 0)"
I used this tutorial to help me think about my own code in terms of horizontal bars instead - it really helped
http://hdnrnzk.me/2012/07/04/creating-a-bar-graph-using-d3js/
here's my own code making it horizontal if it helps:
var w = 600;
var h = 600;
var padding = 30;
var xScale = d3.scale.linear()
.domain([0, d3.max(dataset, function(d){
return d.values[0]; })]) //note I'm using an array here to grab the value hence the [0]
.range([padding, w - (padding*2)]);
var yScale = d3.scale.ordinal()
.domain(d3.range(dataset.length))
.rangeRoundBands([padding, h- padding], 0.05);
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h)
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", 0 + padding)
.attr("y", function(d, i){
return yScale(i);
})
.attr("width", function(d) {
return xScale(d.values[0]);
})
.attr("height", yScale.rangeBand())
An alternative is to rotate the chart (see this). This is a bit hacky as then you need to maintain the swapped axes in your head (the height is actually the width etc), but it is arguably simpler if you already have a working vertical chart.
An example of rotating the chart is below. You might need to rotate the text as well to make it nice.
_chart.select('g').attr("transform","rotate(90 200 200)");
Here is the procedure I use in this case:
1) Inverse all Xs and Ys
2) Remember that the 0 for y is on top, thus you will have to inverse lots of values as previous values for y will be inversed (you don't want your x axis to go from left to right) and the new y axis will be inversed too.
3) Make sure the bars display correctly
4) Adapt legends if there are problems
This question may help in the sense that it shows how to go from horizontal bar charts to vertical: d3.js histogram with positive and negative values
Not sure why but my code...which is also very closely following the D3 Bar graph .js tutorial found here: http://mbostock.github.com/d3/tutorial/bar-1.html
Does not draw rectangles for data with the same values from the variable "dataset". Can anyone explain why? or how to fix it?
var dataset = [5, 2, 1, 1, 1, 50] ;
var w = setWidthToWindow(); //setWidthToWindow
var h = setHeightToWindow(); //setHeightToWindow
var x = d3.scale.linear()
.domain([0, d3.max(dataset)])
.range([0, w/2]);
var y = d3.scale.ordinal()
.domain(dataset)
.rangeBands([0, 120]);
var chart = d3.select("#over_rating")
.append("svg")
.attr("class", "chart")
.attr("width", w)
.attr("height", 20 * dataset.length);
chart.selectAll("rect")
.data(dataset)
.enter().append("rect")
.attr("y", y)
.attr("width", x )
.attr("height", y.rangeBand());
chart.selectAll("text")
.data(dataset)
.enter().append("text")
.attr("x", w/2 + 15)
.attr("y", function(d) { return y(d) + y.rangeBand() / 2; })
.attr("dx", 3) // padding-right
.attr("dy", ".35em") // vertical-align: middle
.attr("text-anchor", "end") // text-align: right
.text(String);
You're using an ordinal scale and positioning your bars based on the data value, not the index, so all the data with the value '1' scales to exactly the same position. If you look at your svg, you'll see there are three bars drawn in exactly the same place.
I guess you could set up the scale with index values:
var y = d3.scale.ordinal()
.domain(d3.range(dataset.length))
.rangeBands([0, 120]);
and then scale by the index:
.attr("y", function(d,i) { return y(i); })
which would allow you to add more data and have the width of the bars adjust to accomodate it.
http://jsfiddle.net/findango/nfdST/