How to control the spacings of the x Axis label positions in a stacked bar chart? - d3.js

I took bits from this and this solutions.
I then do the following to show my x axis label ticks:
var x = d3.scaleBand().range([0, width]).padding(.1).domain(d3.range(1993, 2002));
svg.append("g")
.attr('class', 'axis')
.attr('transform', 'translate(-10,' + (height - 20) + ')')
.call(d3.axisBottom(x));
However, I then get the following output where x axis ticks are shifted further to the right each time like if their gap doesn't match the bar's.

The following snippet shows how the rects and axis's ticks line up correctly.
Need to be careful on how d3.range generates arrays, in that d3.range(1993, 2002) will create [1993, 1994, ... , 2001].
Also, I'm not sure why the g element that contains the X axis was shifted left, so that is set to 0
var width = 800
var height = 100
//this makes an array 1993 to 2001. Is that what you want?
var data = d3.range(1993, 2002)
var x = d3.scaleBand()
.range([0, width])
.padding(.1)
.domain(data);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
svg.append("g")
.attr('class', 'axis')
//.attr('transform', 'translate(-10,' + (height - 20) + ')')
.attr('transform', 'translate(0,' + (height - 20) + ')')
.call(d3.axisBottom(x));
svg.selectAll('rect')
.data(data)
.enter()
.append('rect')
.attr('x', d => x(d))
.attr('y', 0)
.attr('width', x.bandwidth())
.attr('height', x.bandwidth())
<script src="https://d3js.org/d3.v4.min.js"></script>

I don't have enough S.O to 'comment' but I think it may be helpful to use the xScale:
-in the xAxis (something like...
xAxis = d3.axisBottom()
.scale(xScale)
)
-in the x position of each bar ( something like...
'x', d => xScale(year)
)
-in the width of each bar, seems like you might need to use something like...
('width', x.bandwidth())
looks like the xScale in the (1)axis and (2)bars are not working as expected together.
Maybe this helps?!

Related

D3 version 6 - compute bar width for bar chart

I'm using D3 version 6 to make a bar chart. But I'm hard coding the bar width and it's resulting in weird-looking bars that aren't evenly spaced:
My x scale is a time scale (d3.scaleTime()), used in my x axis:
const parseTime = d3.timeParse("%Y");
const x = d3.scaleTime()
.rangeRound([0, width])
.domain([parseTime('1930'),parseTime('2025')]);
const xAxis = g => g
.attr("transform", `translate(0, ${height})`)
.call(d3.axisBottom(x));
const group = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
group.append("g").call(xAxis);
Is there a way to set the bar width instead of a hard-coded 4?
group.selectAll('bar')
.data(data)
.enter()
.append('rect')
.attr("class", "ref-count")
.attr("y", d => y(d.count) )
.attr("x", d => x(parseTime(d.year)) )
.style("fill", color)
.attr("width", 4)
.attr("height", d => height - y(d.count));

D3JS: unable to set only 5 ticks in x Axis

I've been unable to set only 5 ticks on my x Axis, by default it set 10 ticks, multiples of 5, I tried to use ´.ticks(5)´ but it's not working for me. If if visualize the chart on mobile, 10 ticks becomes almost unreadable. But as I mentioned ´.ticks(5)´ it's not working
this is the snippet which draws the x axis:
var xAxis = svg.append("g")
.style("font-size", textSetter())
.attr("class", "xAxis", "axis")
.attr("transform", "translate(" + 0 + "," + heightTwo + ")")
.call(d3.axisBottom(xScale).ticks(5)
.tickPadding(5).tickFormat(d => d + "%"))
How can I solve this? Thanks
You could set all the text in the axis to a particular class and then hide the ticks based on the number for example.
//Create the xAxis and assign x-axisticks class to the tick text
var xAxis = svg.append("g")
.style("font-size", textSetter())
.attr("class", "xAxis", "axis")
.attr("transform", "translate(" + 0 + "," + heightTwo + ")")
.call(d3.axisBottom(xScale)
.selectAll('text')
.attr('class', 'x-axisticks');
//remove the ticks which based on the multiples
var xticks = d3.selectAll('.x-axisticks');
xticks.attr('class', function (d, i) {
i = i + 1;
if (i % 5 !== 0) d3.select(this).remove();
});
Hope this helps. A more specific use-case answer would probably require some more code to be posted from your end so that I can tailor an answer, but this should work.

Cut off gridline at the end in d3.js

I'd like to cut off the grid line at the end of the graph. If I remove the padding in xScale, then the end tick "2016"'s "16" will be cut off.
Which part should I fix, yScale, xScale, or the gridline?
//Define X axis
xAxis = d3.axisBottom()
.scale(xScale)
.ticks(10)
.tickFormat(formatTime);
//Define Y axis
yAxis = d3.axisLeft()
.scale(yScale)
.ticks(5);
//Define Y axis gridlines
function make_y_gridlines() {
return d3.axisLeft(yScale)
}
//Create axes
svg.append("g")
.attr("class", "axis x")
.attr("transform", "translate(0," + (h - padding) + ")")
.call(xAxis);
svg.append("g")
.attr("class", "axis y")
.attr("transform", "translate(" + padding + ",0)")
.call(yAxis);
// add the Y gridlines
svg.append("g")
.attr("class", "grid")
.attr("transform", "translate(" + padding + ",0)")
.call(make_y_gridlines()
.tickSize(-w)
.tickFormat("")
)
As the x-axis is defined with:
.range([padding, w - padding * 2])
This means, the graph begins at 1 * padding from the left of the svg container and ends at width - 2 * padding from the left. Which means the width of the graph (as opposed to the width of the svg container) is width - 3 * padding.
Thus the actual size of the y grid lines should be width - 3 * padding.
In the code, this means that you can just replace the ticksize in the definition of the y gridlines:
.tickSize(-w)
with:
.tickSize(-w + 3*padding)
It's -(w-3*padding) since the ticks by default are going to the left of the y axis and here we want them to the right.

D3.js - why is this histogram generating a negative width and throwing an error?

I'm trying to start from Mike Bostock's histogram example:
http://bl.ocks.org/mbostock/3048450
Initially I'm just trying to change the data and domain to get an understanding of how it works and get closer to what I need. But in doing that, my script throws an error due to negative widths on the rects.
What is this line doing exactly and why does it generate a negative value?
.attr("width", x(data[0].dx) - 1)
My fiddle is here: http://jsfiddle.net/rolfsf/p96dH/1/
and my script currently is this:
//generate some data with a median of 75
var values = d3.range(1000).map(d3.random.logNormal(Math.log(75), 0.4));
// A formatter for counts.
var formatCount = d3.format(",.0f");
var margin = {top: 10, right: 30, bottom: 30, left: 30},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.linear()
.domain([60, 95])
.range([0, width]);
// Generate a histogram using twenty uniformly-spaced bins.
var data = d3.layout.histogram()
.bins(x.ticks(7))
(values);
var y = d3.scale.linear()
.domain([0, d3.max(data, function(d) { return d.y; })])
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
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 + ")");
var bar = svg.selectAll(".bar")
.data(data)
.enter().append("g")
.attr("class", "bar")
.attr("transform", function(d) { return "translate(" + x(d.x) + "," + y(d.y) + ")"; });
bar.append("rect")
.attr("x", 1)
.attr("width", x(data[0].dx) - 1)
.attr("height", function(d) { return height - y(d.y); });
bar.append("text")
.attr("dy", ".75em")
.attr("y", 6)
.attr("x", x(data[0].dx) / 2)
.attr("text-anchor", "middle")
.text(function(d) { return formatCount(d.y); });
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
UPDATE: The answer in this question: D3 Histogram with negative values actually gives me the chart layout I wanted, though Lars is correct that my domain function is causing problems.
an updated fiddle is here (note that due to the domain issue, the first and last bars have incorrect heights) http://jsfiddle.net/rolfsf/p96dH/3/
I replaced my width function with the barWidth variable:
var numbins = data.length;
var barWidth = width/numbins - 1;
It's simply the way you've set up your x scale. You're assuming that all values are going to be in the interval (60,95), which they are not. For values smaller than 60, you get negative numbers.
You can fix this easily by getting the actual extent of the data for the domain:
var x = d3.scale.linear()
.domain(d3.extent(data))
.range([0, width]);
You're right, it is due to the domain; the reason it works in Mike's example is that the domain minimum he uses is 0.
A better approach would be to do the following, replace every occurrence of
x(data[0].dx) - 1
with
x(60 + data[0].dx) - 1
More, generally, you can define your x scale with:
var x = d3.scale.linear()
.domain(d3.extent(data))
.range([0, width]);
And then the above snippets (setting bar width), become:
x(d3.min(datasetBars) + data[0].dx) - 1)

d3 axis tick alignment

I have a d3 bar chart with, and the axis tick marks are centered below the bars (as I would expect). In this case, I would actually like to left align the axis ticks and have the ticks under the left edge of the bar. Is that possible?
EDIT: Javascript for constructing the axis
// note width is just the width of the chart
var xScale = d3.scale.ordinal().rangeRoundBands([0,width], .1);
var xAxis = d3.svg.axis().scale(xScale).orient("bottom");
here is an example of left aligning the ticks.
http://bl.ocks.org/mbostock/6186172
the idea is that after you create the ticks you collect them and aligne them:
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text")
.attr("y", 6)
.attr("x", 6)
.style("text-anchor", "start");
Why not just create a new scale?
var padding = .1;
var axisScale = d3.scale.linear()
.domain([0, n])
.range([0 + padding, WIDTH + padding]);
Where n is the number of bars in your histogram.
I think that this should be adjusted when you add the axis to your graph.
Try (assuming your graph is called svg):
var bandSize = xScale.rangeBand();
svg.append("svg:g")
.attr("transform", "translate(-" + bandSize + "," + h + ")")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "start")
.attr("transform", "translate(" + bandSize + ", 0)");
In my experiments, this shifts the ticks to the start of each band, and retains the text centered under each band.

Resources