score:0

Accepted answer

To make them appear in the middle of the range, you would need to add an offset (dx) to the `text` elements. This offset would be equal to half the size of an interval, which you can compute dynamically.

Here's an example that does something very similar.

score:0

I had the same problem and solved it by using another scale for the X-axis that is shifted 15 days.

``````// scale is your usual x scale. change dates on the domain by 15 days
var d0 = scale.domain()[0];
d0.setDate(d0.getDate() - 15);
var d1 = scale.domain()[1];
d1.setDate(d1.getDate() - 15);

// use this new x scale for the axis only
new_scale = d3.time.scale().domain([d0, d1]).range(scale.range());
d3.svg.axis().scale(new_scale);
``````

This way you have no awkwardly shifted texts and still leave all the heavy lifting to d3. For showing ticks you'd use a second x-axis with the normal scale.

score:1

(2019) Using latest d3v5, other stackoverflow responses, 1, and mbostock trick to select and edit the labels text property, axis styling.

First giving a `x_scale` and number of `ticks` you can calculate the distance in rendered pixels of all ticks, including non linear scales. The function below returns an array with ticks distances / spaces.

``````// ticksDistance is constant for a specific x_scale
// apply scale to ticks and calcule distance
const getTicksDistance = (scale) => {
const ticks = scale.ticks(); // return array with ticks
const spaces = []
for(let i=0; i < ticks.length - 1; i++){
spaces.push(scale(ticks[i+1]) - scale(ticks[i]))
}
return spaces;
};
const ticksSpacingTime = getTicksDistance(x_scale_time);
``````

Then use that distance to shift the labels from its current position, the index `i` gives you the position d3 is iterating over the text labels.

``````svg.append("g")
.attr("class", "x-axis-shifted")
.attr("transform", "translate(0,100)")
.call(x_axis)
.selectAll("text")
.attr("x", (d,i) => ticksSpacingTime[i]/2)
``````

``````const x_scale_time = d3.scaleTime()
.domain([new Date(2017,12,1),new Date()])
.range([0, 960]);

const x_axis_time = d3.axisBottom()
.scale(x_scale_time)
.ticks(d3.timeMonth.every(1))

const x_scale_pow = d3.scalePow().exponent(2)
.domain([0,20000])
.range([0, 960]);

const x_axis_pow = d3.axisBottom()
.scale(x_scale_pow)
.ticks(10)

// ticksDistance is constant for a specific x_scale
const getTicksDistance = (scale) => {
const ticks = scale.ticks();
const spaces = []
for(let i=0; i < ticks.length - 1; i++){
spaces.push(scale(ticks[i+1]) - scale(ticks[i]))
}
return spaces;
};

//you have to recalculate when x_scale or ticks change
const ticksSpacingTime = getTicksDistance(x_scale_time);
const ticksSpacingPow = getTicksDistance(x_scale_pow);

const svg = d3.select("body").append("svg")
.attr("width", "500px")
.attr("height","350px")
.style("width", "100%")
.style("height", "auto");

// normal
svg.append("g")
.attr("class", "x-axis-time")
.attr("transform", "translate(0,0)")
.call(x_axis_time)

// shift labels to half of the ticks distance
svg.append("g")
.attr("class", "x-axis-time-shifted")
.attr("transform", "translate(0,40)")
.call(x_axis_time)
.selectAll("text")
.attr("x", (d,i) => ticksSpacingTime[i]/2)

// normal
svg.append("g")
.attr("class", "x-axis")
.attr("transform", "translate(0,110)")
.call(x_axis_pow)

// shift labels to half of the ticks distance
svg.append("g")
.attr("class", "x-axis-shifted")
.attr("transform", "translate(0,150)")
.call(x_axis_pow)
.selectAll("text")
.attr("x", (d,i) => ticksSpacingPow[i]/2)``````
``<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>``