oluckyman
oluckyman

Reputation: 3886

d3.js: Align text labels between ticks on the axis

Is there an easy way to align text labels between ticks?

Here is my time axis with labels above the ticks:
enter image description here

I would like to place these labels like here:
enter image description here

Upvotes: 43

Views: 24430

Answers (7)

oluckyman
oluckyman

Reputation: 3886

I ended up with one of the Lars Kotthoff's advices.

Every time when I call(axis) I also adjust text labels.
Here is simplified code:

function renderAxis() {
    axisContainer
        .transition().duration(300)
        .call(axis)                  // draw the standart d3 axis
        .call(adjustTextLabels);     // adjusts text labels on the axis 
}

function adjustTextLabels(selection) {
    selection.selectAll('.major text')
        .attr('transform', 'translate(' + daysToPixels(1) / 2 + ',0)');
}

// calculate the width of the days in the timeScale
function daysToPixels(days, timeScale) {
    var d1 = new Date();
    timeScale || (timeScale = Global.timeScale);
    return timeScale(d3.time.day.offset(d1, days)) - timeScale(d1);
}

Update:
BTW, here is a calendar demo with I ended up: http://bl.ocks.org/oluckyman/6199145

enter image description here

Upvotes: 54

ColinE
ColinE

Reputation: 70122

You might want to consider using D3FC, which has a drop-in replacement for the D3 axis component that supports this feature.

Here's an example which substitutes the D3 axis d3.axisBottom, for the D3FC equivalent fc.axisBottom:

const axis = fc.axisBottom(linear)
  .tickCenterLabel(true);

The tickCenterLabel centres the axis labels as requested.

Here's what the axis looks like with tickCenterLabel = false:

enter image description here

And here with the tickCenterLabel = true:

enter image description here

Full disclosure - I'm a maintainer and contributor to D3FC

Upvotes: 3

VINOTH KUMAR
VINOTH KUMAR

Reputation: 33

        svg.append("g")
          .attr("class", "axis axis-years")
          .attr("transform", "translate(0," + (height + 1) + ")")
          .call(xAxis)
        .selectAll("text")

     .attr("x", "-1.8em")
     .attr("y", ".00em")
     .attr("transform", function (d) {
         return "rotate(-90)"});

Upvotes: 0

Union find
Union find

Reputation: 8150

Already a few good replies but just to add one more. Note the use of text-anchor.

Same idea: After your call, select the text, reposition.

.call(xAxis)                
.selectAll(".tick text")
.style("text-anchor", "start")
.attr("x", axisTextAdjust)

Upvotes: 1

Idan Gazit
Idan Gazit

Reputation: 2560

I often do this by stacking multiple axes, each with a custom .tickFormat().

If I'm placing labels in between dates, I'll often do something like this:

@timeDaysAxisLabels = d3.svg.axis()
    .scale(@timescale)
    .orient('bottom')
    .ticks(d3.time.hour.utc, 12)  # I want ticks at noon, easiest to just get them ever 12 hours
    .tickFormat((d) =>
        # only draw labels at noon, between the date boundaries
        if d.getUTCHours() == 12
            # draw the label!
            formatter = d3.time.format.utc('%a %d %b')  # "Mon 12 Apr"
            return formatter(d)
        else
            # not noon, don't draw anything
            return null)
    .tickSize(0)
    .tickPadding(30)

I'll also create a separate axis with no labels at all, and a non-zero .tickSize() to actually draw ticks, but this block above positions date labels in the center of the "column".

Upvotes: 1

ckersch
ckersch

Reputation: 7687

You can do this by using axis.tickSize(major[[,minor],end]) and .tickSubdivide(). Your ticks are set to line up with the major ticks, but if you set the height of these ticks to 0, and set some height for minor ticks, and specify that there is one minor tick between each pair of major ticks, you will end up with tick labels between your ticks. Your code would look like this:

var myAxis = d3.svg.axis()
    .ticks(15)
    .tickSubdivide(1)
    .tickSize(0, 6, 0);

Note that you need to explicitly set an end size. If you only provide two numbers, they will be interpreted as major and end and minor will default to 0.

Here's a fiddle.

Upvotes: 2

Lars Kotthoff
Lars Kotthoff

Reputation: 109232

There is no easy (i.e. built-in) way of doing this, but you can still achieve it. There are a few options. The most straightforward one is probably to use the tickFormat function to specify a format with a suitable number of spaces in front/after the numbers. This would need to be hand-tuned for each application though.

Alternatively, you could select the label elements after they have been drawn and add a suitable transform attribute that shifts them accordingly. Again, this would have to be hand-tuned.

Your third option is to have two different axes, one for the ticks and one for the labels. The idea is that the axis that provides the ticks has no labels and the other one no ticks. You would need to set the tick values appropriately, but at least you wouldn't have to guess the right offset.

Upvotes: 7

Related Questions