Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

d3 v4 scaleBand ticks

Tags:

d3.js

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?

like image 229
frank Avatar asked Oct 23 '16 03:10

frank


People also ask

What is scaleBand d3?

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.

What is a tick in d3?

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.

What is band scale?

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.


2 Answers

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)
}));
like image 91
Gerardo Furtado Avatar answered Nov 16 '22 03:11

Gerardo Furtado


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.

like image 32
Aaron Eads Avatar answered Nov 16 '22 04:11

Aaron Eads