I have data like the following
date,values 2016-10-01,10 2016-10-02,20 2016-10-03,30 2016-10-04,5 2016-10-05,50 2016-10-06,2 2016-10-07,7 2016-10-08,17
and am generating a bar chart using the following code
var margin = {top: 20, right: 20, bottom: 70, left: 40},
width = 800 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
var parseDate = d3.timeParse("%Y-%m-%d");
var x = d3.scaleBand().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
var xAxis = d3.axisBottom(x);
var yAxis = d3.axisLeft(y);
var tip = d3.tip()
.attr('class', 'd3-tip')
.offset([-10, 0])
.html(function(d) {
return "<strong>Month of " + d.date + ":</strong> <span style='color:red'>" + d.value + " sales</span>";
})
var svg = d3.select("#barg").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.call(tip);
data = d3.csvParse(d3.select("pre#data2").text());
data.forEach(function(d) {
d.date = parseDate(d.date);
d.value = +d.value;
});
x.domain(data.map(function(d) { return d.date; }));
y.domain([0, d3.max(data, function(d) { return d.value; })]);
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", "-.55em")
.attr("transform", "rotate(-90)" )
svg.append("g")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Value ($)");
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(d) { return x(d.date); })
.attr("width", x.bandwidth() - 5)
.attr("y", function(d) { return y(d.value); })
.attr("height", function(d) { return height - y(d.value); })
.on('mouseover', tip.show)
.on('mouseout', tip.hide)
So the problem I am having is that I have ordinal data, but for large cardinality (for instance, 120 data points) The x axis has way too many ticks. I have tried a few things like tickValues
, but when I use this, my x axis tick points all show up on top of each other. Ideally I would like 10 tick points or so, when the cardinality is high. Any ideas?
scaleBand() function in D3. js is used to construct a new band scale with the domain specified as an array of values and the range as the minimum and maximum extents of the bands. This function splits the range into n bands where n is the number of values in the domain array.
d3. ticks generates an array of nicely-rounded numbers inside an interval [start, stop]. The third argument indicates the approximate count of values needed. For example, to cover the extent [0, 10] with about 100 values, d3.ticks will return these ticks: start = 0.
An arrangement by which colliers are paid an agreed sum for removing a dirt band, in addition to the usual tonnage rate. The payment varies with the thickness of the band.
This can be done using tickValues
indeed. For instance, in this demo, we have 200 values, so the axis is absolutely crowded:
var svg = d3.select("body")
.append("svg")
.attr("width", 500)
.attr("height", 100);
var data = d3.range(200);
var xScale = d3.scaleBand()
.domain(data.map(function(d){ return d}))
.range([10, 490]);
var xAxis = d3.axisBottom(xScale);
var gX = svg.append("g").call(xAxis);
<script src="https://d3js.org/d3.v4.min.js"></script>
Now, the same code using tickValues
:
var svg = d3.select("body")
.append("svg")
.attr("width", 500)
.attr("height", 100);
var data = d3.range(200);
var xScale = d3.scaleBand()
.domain(data.map(function(d){ return d}))
.range([10, 490]);
var xAxis = d3.axisBottom(xScale)
.tickValues(xScale.domain().filter(function(d,i){ return !(i%10)}));
var gX = svg.append("g").call(xAxis);
<script src="https://d3js.org/d3.v4.min.js"></script>
In this last snippet, tickValues
uses the remainder operator to show only 1 in every 10 ticks:
.tickValues(xScale.domain().filter(function(d,i){
return !(i%10)
}));
Here is a general solution to this problem using tickFormat(...)
. We can define a minimum acceptable width for our ticks, then skip every nth tick based on this minimum.
d3
.axisBottom(xScale)
.tickFormat((t, i) => {
const MIN_WIDTH = 30;
let skip = Math.round(MIN_WIDTH * data.length / chartWidth);
skip = Math.max(1, skip);
return (i % skip === 0) ? t : null;
});
let skip = ...
is a rearrangement of the inequality ChartWidth / (NumTicks / N) > MinWidth
. Here N
represents the tick "step size", so we are asserting that the width of every nth tick is greater than the minimum acceptable width. If we rearrange the inequality to solve for N
, we can determine how many ticks to skip to achieve our desired width.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With