How can I split an ordinal axis in d3? - d3.js

The following is my draw axis code:
var seasons = ["summer", "winter", "fall", "spring"];
var margin = {top:80, right:30, bottom:30, left:30},
width = 1200 - margin.right - margin.left,
height = 800 - margin.top - margin.bottom;
var x = d3.scale.ordinal()
.domain(seasons)
.rangeRoundBands([0, width], 0.9);
xAxis = d3.svg.axis()
.scale(x)
.tickSize(4, 6)
.tickPadding(6)
.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 + ")");
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
</script>
However, the tickPadding function does now introduce a space between the ordinal axis categories.
More specifically, I want that each of the summer, winter, fall and spring parts of the axis are separate from each other, sort of like dashed line. How can I get this?

I don't know of any way built into the d3 axis to accomplish this, but you can remove the path it draws and replace it with a dashed line, like so:
// Draw the axis, as you currently are
var axisElem = svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Remove the line
axisElem.selectAll("path.domain").remove();
// Figure out how big each dash should be
var gapFraction = 0.1; // The portion of the line that should be a gap
var total = x(seasons[1]) - x(seasons[0]);
var dash = total * (1 - gapFraction);
var gap = total * gapFraction;
// Draw the dashed line
axisElem.append("line")
.classed("domain", true)
.attr("x1", x(seasons[0]) - dash / 2 + gap / 2)
.attr("x2", x(seasons[seasons.length - 1]) + dash / 2 - gap / 2)
.attr("y1", 0)
.attr("y2", 0)
.attr("stroke", "black")
.attr("stroke-dasharray", dash + "," + gap);

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));

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.

Add brush on top of d3 barchart

I am trying to add a brush on top of a barchart in d3. Currently I am using barchart for my example:
https://bl.ocks.org/mbostock/1134768
This is the example of the brush that I want to implement: http://bl.ocks.org/mbostock/34f08d5e11952a80609169b7917d4172
I can only find examples where the bush is below the chart and not on the main chart itself. Can anyone explain to me how to put the brush on top of the barchart or point me in the direction of an example that I could follow?
In the Brush & Zoom example you are referring to, there are following lines:
var focus = svg.append("g")
.attr("class", "focus")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var context = svg.append("g")
.attr("class", "context")
.attr("transform", "translate(" + margin2.left + "," + margin2.top + ")");
...
svg.append("rect")
.attr("class", "zoom")
.attr("width", width)
.attr("height", height)
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(zoom);
These are specifying positions of the chart area, brush area and zoom area. You can just tweak margin and margin2 values which are specified at the beginning of the script, or explicitly set up a different transformation here.
You need to have margin.top > margin2.top, and tweak bottom margins too.

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