score:6

Accepted answer

Any possible answer for your question is dangerously opinion-based, because it depends on exactly what you want: do you want to treat time as time? Or do you want to treat time as just separate, individual events?

There are actually three options for you here:

  1. Using an ordinal scale (band, point...): in this case, each data entry will be just a categorical variable. The space between the ticks in the axis will be the same.
  2. Using a linear scale: this allows different distances for the ticks in the axis. However, the values won't be time, you'll have to create a math to set the values (the easiest one is probably calculating the number of seconds).
  3. Using a time scale: this has an advantage, which is the fact that you'll treat time as time. The main disadvantage, however, is exactly the previous statement: time is a complicated beast, you'll have leap years, daylight savings, time zone differences etc...

Since the linear scale is quite simple to create (and the ordinal scale is even more simple), here is an answer showing how to do it with a time scale.

Suppose you have this data array:

var data = ["12:32", "21:05", "24:56", "36:30", "45:14", "71:11"];

The first step is parsing the dates:

var specifier = "%M:%S";
var parsedData = data.map(function(d) {
  return d3.timeParse(specifier)(d)
});

And then creating the scale:

var scale = d3.scaleTime()
  .domain(d3.extent(parsedData));

For this to work, we'll have to set the tickValues using our parsed data array, and formatting the ticks for minutes and seconds:

var axis = d3.axisBottom(scale)
    .tickValues(parsedData)
    .tickFormat(function(d){
        return d3.timeFormat(specifier)(d)
    });

Here is the result:

var data = ["12:32", "21:05", "24:56", "36:30", "45:14", "71:11"];
var specifier = "%M:%S";
var svg = d3.select("svg");
var parsedData = data.map(function(d) {
  return d3.timeParse(specifier)(d)
});
var scale = d3.scaleTime()
  .domain(d3.extent(parsedData))
  .range([30, 470]);
var axis = d3.axisBottom(scale)
  .tickValues(parsedData)
  .tickFormat(function(d) {
    return d3.timeFormat(specifier)(d)
  });
var gX = svg.append("g")
  .attr("transform", "translate(0,50)")
  .call(axis)
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg width="500" height="100"></svg>

As you can see, the ticks are correctly spaced here. But we have a problem: those ticks are actual dates (if you inspect the code, you'll see that the year is 1900). Because of that, the last tick, which should be 71 minutes and 11 seconds, is shown as 11:11, because it is the eleventh minute of the next hour.

A simple solutions is referencing the original array of strings:

.tickFormat(function(d,i) {
    return data[i]
});

And here is the result:

var data = ["12:32", "21:05", "24:56", "36:30", "45:14", "71:11"];
var specifier = "%M:%S";
var svg = d3.select("svg");
var parsedData = data.map(function(d) {
  return d3.timeParse(specifier)(d)
});
var scale = d3.scaleTime()
  .domain(d3.extent(parsedData))
  .range([30, 470]);
var axis = d3.axisBottom(scale)
  .tickValues(parsedData)
  .tickFormat(function(d, i) {
    return data[i]
  });
var gX = svg.append("g")
  .attr("transform", "translate(0,50)")
  .call(axis)
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg width="500" height="100"></svg>


Related Query

More Query from same tag