Home > Software engineering >  D3 scaleTime() axis tick misalignment
D3 scaleTime() axis tick misalignment

Time:07-04

I am drawing an x axis with d3.axisBottom() and d3.scaleTime(). However, the axis ticks are not aligned with the output from the scale.

Example: see this observable notebook.

const width = 300;
const content = svg.append("g").attr("class", "content");

const xScale = d3.scaleTime().domain(dateExtent).range([0, 600]);
const xAxis = d3.axisBottom(xScale).ticks(10);

content
  .append("g")
  .attr("class", "x-axis-group")
  .attr("transform", `translate(0, ${width - 35})`)
  .call(xAxis);

content
  .selectAll("circle.temp-circle")
  .data(dateData)
  .join("circle")
  .attr("class", "temp-circle")
  .attr("cx", (d) => xScale(d.date))
  .attr("cy", width - 45)
  .attr("r", 3)
  .style("fill", "steelBlue");

Any suggestions are appreciated on how to resolve this?

CodePudding user response:

When you use scaleTime and you are in the east coast of Australia, you will be somewhere between 9-11 hours ahead of Greenwich Mean Time (e.g. London, UK). new Date will use your local time and the for example, the first date will no longer be March 1st at midnight but February 28th at maybe 2pm.

Use scaleUtc instead so that differences between the dates in your data and the local date of your users is not an issue (unless it's important).

d3.scaleUtc([[domain, ]range]) · Source, Examples

Equivalent to scaleTime, but the returned time scale operates in Coordinated Universal Time rather than local time.

const svg = d3.select("body").append("svg").attr("width", 500).attr("height", 300);
const content = svg.append("g").attr("class", "content");

const xScale = d3.scaleTime().domain(dateExtent).range([0, 400]);
const xAxis = d3.axisBottom(xScale).ticks(10);

// your problem
content
  .append("g")
  .attr("class", "x-axis-group")
  .attr("transform", `translate(0, ${45})`)
  .call(xAxis);

content
  .selectAll("circle.temp-circle")
  .data(dateData)
  .join("circle")
  .attr("class", "temp-circle")
  .attr("cx", (d) => xScale(d.date))
  .attr("cy", 45)
  .attr("r", 4)
  .style("fill", "steelBlue");

// using scaleUtc
const xScale2 = d3.scaleUtc().domain(dateExtent).range([0, 400]);
const xAxis2 = d3.axisBottom(xScale2).ticks(10);

content
  .append("g")
  .attr("class", "x-axis-group2")
  .attr("transform", `translate(0, ${105})`)
  .call(xAxis2);

content
  .selectAll("circle.temp-circle2")
  .data(dateData)
  .join("circle")
  .attr("class", "temp-circle2")
  .attr("cx", (d) => xScale2(d.date))
  .attr("cy", 105)
  .attr("r", 4)
  .style("fill", "steelBlue");
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.5.0/d3.min.js"></script>
<script>
const timelineData = [
  [20220301, 100],
  [20220302, 100],
  [20220303, 100],
  [20220304, 100],
  [20220305, 100],
  [20220306, 100],
  [20220307, 100],
  [20220308, 100],
  [20220309, 100],
  [20220310, 100],
  [20220311, 100],
  [20220312, 100],
  [20220313, 100],
  [20220314, 100],
  [20220315, 100],
  [20220316, 100]
];

const dateData = timelineData
  .map((d) => {
    const datePattern = /(\d{4})(\d{2})(\d{2})/;
    return {
      date: new Date(String(d[0]).replace(datePattern, "$1-$2-$3")),
      count: d[1]
    };
  })
  .sort((a, b) => (a.date < b.date ? -1 : 1));

const dateExtent = d3.extent(dateData, (x) => x.date);
dateExtent[0] = new Date(
  new Date(dateExtent[0].valueOf()).setDate(dateExtent[0].getDate() - 1)
);
dateExtent[1] = new Date(
  new Date(dateExtent[1].valueOf()).setDate(dateExtent[1].getDate()   1)
);
</script>

  • Related