I ran into a common problem while making a bar graph in d3: my y-axis was upside down. After some googling, I found out that the best way to fix this is by reversing the y-domain. The only problem is, when I did that, the bars on my graph switched positions, so the largest was at the beginning instead of the end. I need the y-axis to be correct, without changing my bars.
Bars in correct positions, but the y-axis is upside-down
Bars are incorrect, but the y-axis is right-side-up
Here is the code: https://codepen.io/lucassorenson/pen/rPRadR?editors=0010
const yScale = d3.scaleLinear()
.domain([0, d3.max(json, (d) => d[1])])
.range([h - padding, padding]);
const yAxis = d3.axisLeft(yScale);
This is the code that I changed. If you reverse the range ([padding, h - padding]), the bars are correct but the axis is not.
The fix is simply to exchange the callback functions of the attributes "height" and "y".
Your code was:
...
svg.selectAll('rect')
.data(json)
.enter()
.append('rect')
.attr('width', 3)
.attr('height', (d) => yScale(d[1]))
.attr('x', function(d, i){
return i*4 + padding
})
.attr('y', function(d){
return h - yScale(d[1]) - padding
})
change it to:
...
svg.selectAll('rect')
.data(json)
.enter()
.append('rect')
.attr('width', 3)
.attr('y', (d) => yScale(d[1]))
.attr('x', function(d, i){
return i*4 + padding
})
.attr('height', function(d){
return h - yScale(d[1]) - padding
})
With the attribute "y" you typically set the upper edge of the rectangle and with "height" you denote how far it goes down.
Related
I would like to place images between the axis labels and the axis line or the bars in my case. Now it's a bit tricky because I don't have much space. I am restricted by the graph size and I have to work with the current dimensions. I tried the option of adding tickPadding() to the y-axis but that meant I went over the graph size and the labels were cut-off. is there a way I could move the bars to the right? or make the width a bit smaller?
here is my code for the y-axis and the bars:
let yScale_h = d3.scaleBand()
.range([0, height])
.padding(0.2);
let xScale_h = d3.scaleLinear()
.range([0, width]);
let yAxis = d3.axisLeft()
.scale(yScale_h)
.tickSize(0);
svg_bar.selectAll('rect')
.data(dataset_performance, key)
.enter()
.append('rect')
.attr("class", "bar")
.attr('width', function (d) { return xScale_h(d.Award); })
.attr('y', function (d) { return yScale_h(d.clean_test); })
.attr('height', yScale_h.bandwidth())
One way to manually offset the bars to the right is to reduce the scale range, and add the padding to the 'x' property of the bars.
This example adds a padding of 20px:
let xScale_h = d3.scaleLinear()
.range([0, width - 20]); // Reduce the range by 20px
...
svg_bar.selectAll('rect')
.data(dataset_performance, key)
.enter()
.append('rect')
.attr("class", "bar")
.attr('x', 20) // Move bars to the right by 20px
.attr('width', function (d) { return xScale_h(d.Award); })
.attr('y', function (d) { return yScale_h(d.clean_test); })
.attr('height', yScale_h.bandwidth())
The names of the legend don't fully show.They are cut off, if I increase the width, the bars just grow bigger. How can I accommodate more space for my legend?
I tried appending the legend to the 'svg' tag instead of the 'g' tag but still not the desired results. I even plotted the axis, bars and legend on the 'svg' tag but its still not working.
javascript
const g= svg.append('g')
.attr("transform", `translate(${margin.left},${margin.top})`)
const xAxis= g.append('g')
.call(d3.axisBottom(x).tickSizeOuter(0))
.attr("transform", "translate(0," + innerHeight + ")")
const yAxis= g.append('g')
.call(d3.axisLeft(y).tickSizeOuter(0))
//stack the data? --> stack per subgroup
var stackedData = d3.stack()
.keys(subgroups)
(data)
var legend = g.append('g')
.attr('class', 'legend')
.attr('transform', `translate(${210},${20})`);
legend.selectAll('rect')
.data(subgroups)
.enter()
.append('rect')
.attr('x', 0)
.attr('y', function(d, i){
return i * 20;})
.attr('width', 14)
.attr('height', 14)
.attr('fill', function(d, i){
return color(i);
});
legend.selectAll('text')
.data(subgroups)
.enter()
.append('text')
.text(function(d){
return d;
})
.attr('x',18)
.attr('y', function(d, i){
return i * 18;
})
.attr('text-anchor', 'start')
.attr('alignment-baseline', 'hanging');
//below is my plotted data
telescope,allocated,unallocated
IRSF,61,28
1.9-m,89,0
1.0-m,64,23
// width=300 and heigh=300 for svg
I want to show the full names of the legends just next to the right of the bars for the graph.
Link to the graph is here.
How do I solve the problem?
Your issue is that the SVG's width is too narrow to accomodate showing all of the legend texts.
If your graph follows D3 conventions, space for elements that are auxilliary to graph itself (axis names, ticks, legends, etc.) is made using an margin object. Although I can't see how your graph is made, it looks like yours is setup to use a margin object as well. Following D3 conventions the margin values are fixed, which would explain why changing the width value just makes the bars wider yet still doesn't make space for the legend texts.
Therefore, locate the margin object and change its right value to something higher. Inspecting your example, it looks like doubling it should do it.
Hope this helps!
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
I'm drawing a chart but have trouble how to handle my y scale and that my rectangles are drawn "upon" each over. (I draw with the bars from y-axis)
I calculate how high each bar needs to be and if a bar is higher than my default height the y does not manage to handle this. I'm not shure how to handle this. I draw my rect:
forms.append("rect")
.attr("class", "rectStyle")
.style("fill", function(d) { return color(d.Name); })
.attr("x", function(d){ return x(new Date(d.startDate));})
.attr("height",function(d) { return d.barHeight;})
.attr("y", function(d) { return y(d.RowGroup);})
.attr("width", function(d){ d.w = (x(new Date(d.endDate))-x(new Date(d.startDate)));
return d.w; });
y = d3.scale.linear()
.domain([1,noRows])
.range([0,height -margin.top-margin.bottom-padding*noRows]);
Grateful for how I should solve this problem
I'm working on a slightly modified example of the following linechart: http://bl.ocks.org/mbostock/3883245
Here's my JS Fiddle: http://jsfiddle.net/rQ4xE/
The goal is to have blocks of background color different for periods of time. Changing color will not be difficult. However..
Problem is the gaps of different sizes between rects. I suspect this is because coordinates for the line path are decimal and something gets messed up when calculating the width for rects.
My question is what would be a good way to draw the background rects so that there are no gaps in between?
This is how I add the rects:
svg.append("g").selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("class", "area")
.attr("x", function(d) { return x(d.date) })
.attr("y", 0)
.attr("width", function(d, i) {
not_last = i < data.length - 1;
return not_last ? (x(data[i+1].date)-x(d.date)) : 0;
})
.attr("height", height);
I believe I have found a more elegant solution.
The issue can be solved by specifying to the scale to round the numbers to the nearest integer. That can be done by using scale.rangeRound which also sets the scale's interpolator to d3.interpolateRound.
https://github.com/mbostock/d3/wiki/Quantitative-Scales#wiki-linear_rangeRound
I changed this code:
var x = d3.time.scale().range([0, width]);
to this:
var x = d3.time.scale().rangeRound([0, width]);
JS Fiddle: http://jsfiddle.net/HcC9z/2/
There seems to be a float issue in your x-coordinates and your widths. Try to round both of them.
svg.append("g").selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("class", "area")
.attr("x", function(d) { return Math.floor(x(d.date)) })
.attr("y", 0)
.attr("width", function(d, i) {
not_last = i < data.length - 1;
return not_last ? (Math.floor(x(data[i+1].date))-Math.floor(x(d.date))) : 0;
})
.attr("height", height);
EDIT
Here's a fiddle : http://jsfiddle.net/rQ4xE/3/