frank
frank

Reputation: 141

d3 v4 scaleBand ticks

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?

Upvotes: 14

Views: 18675

Answers (2)

Aaron Eads
Aaron Eads

Reputation: 61

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.

Upvotes: 3

Gerardo Furtado
Gerardo Furtado

Reputation: 102219

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

Upvotes: 34

Related Questions