How to wrap long tick text in Observablehq using the Plot.plot function?

How to wrap long tick text in Observable using the Plot.plot function? Can I use tickFormat for this?

With D3 I solved it like this. How can I solve this with the Plot.plot function?

svg.select(".x_axis")
      .call(xAxis.tickFormat(d3.timeFormat("%d-%m-%Y %H:%M:%S")))
      .selectAll("text")
      .call(function (t) {
        t.each(function (d) {
          var self = d3.select(this);
          var s = self.text().split(' ');
          self.text(null);
          self.append("tspan")
            .attr("x", 0)
            .attr("dy", ".8em")
            .text(s[0]);
          self.append("tspan")
            .attr("x", 0)
            .attr("dy", "1.2em")
            .text(s[1]);
        })
      });

Edit: Link to notebook

Thank you.

1 Like

I think you can use similar regex commands in Plot to introduce a line break where there is a comma (e.g. similar to where you used self.text().split(' ') in the D3 example)… and removing rotation is right there on tickRotate. As your current tickFormat code shows, plot is able to accept regular JavaScript to control outputs.

Would you be able to share your notebook rather than a view of a partial code snippet so that others can make direct suggestions?

We have been discussing this at Multi-line tick labels ¡ Issue #460 ¡ observablehq/plot ¡ GitHub but there is no official support yet. The idea would be to consider newlines in the tick format to, well, create a new line, as we do in Plot.text. Drafts are available at multiline ticks labels, draft by Fil ¡ Pull Request #609 ¡ observablehq/plot ¡ GitHub and multiline tick labels by Fil ¡ Pull Request #83 ¡ d3/d3-axis ¡ GitHub

(duplicated answer from d3.js - How to wrap long tick text in Observablehq using the Plot.plot function? - Stack Overflow)

1 Like

Will that be released at some point? Any workaround?

Yes I hope we’ll eventually get to it! Feedback is precious in that regard.

As for a workaround, well, it’s always possible to do some kind of post processing on the svg that is returned by Plot, going through the ticks and adding tspan nodes. I’m sorry I don’t have an example at hand, but it would be very similar to the way you do it with plain D3.

In the meantime, if you want to share your notebook, please publish it (“unlisted”).

With the hint from @Fil and some D3 code, I was now able to label the x-axis markers in two lines. Please take a look at my notebook Data plot. Feedback welcome.

3 Likes

Just checking whether you republished the updated notebook? I don’t see the 2 lines…

Thanks for the hint. I have now rebublished it.

That’s great! I think it’s worth mentioning that you can combine the plot generation and modification into one cell. The advantage is that you can avoid the use of a side-effect.

The combined cell might look something like this:

plot = {
  let dataPlot = Plot.plot({
    grid: true,
    x: {
      ticks: 10,
      tickFormat: (d) => new Date(d * 1000).toLocaleString("de-DE"),
      tickRotate: 0,
      label: "Date and time →"
    },
    y: {
      ticks: 8,
      label: "↑ Temperature  (°C)",
      domain: [14, 32]
    },
    marks: [
      Plot.ruleY([0]),
      Plot.lineY(curveData, {
        x: "date",
        y: "temperature",
        curve: "catmull-rom",
        marker: "circle"
      })
    ],
    marginTop: 20,
    marginBottom: 50
  });

  //Add Multi-line tick labels to the chart
  d3
    .select(dataPlot)
    .select('[aria-label="x-axis"]')
    .selectAll("text")
    .call(function (t) {
      t.each(function (d) {
        var self = d3.select(this);
        var s = new Date(d * 1000).toLocaleString("de-DE").split(",");
        self.text(null);
        self.append("tspan").attr("x", 0).attr("dy", ".8em").text(s[0]);
        self.append("tspan").attr("x", 0).attr("dy", "1.2em").text(s[1]);
      });
    });

  return dataPlot
}
4 Likes

Thank you for sharing this.

@dergroncki The added line breaks in your notebook didn’t work anymore. Probably due to some changes in the generated svg in a later version of observable plot (?).

I slightly modified your notebook here to make it work.

The current version of Plot generates multi-line time axes by default, so you shouldn’t have to do any additional work to get pretty axes.

Plot.plot({
  grid: true,
  y: {
    label: "↑ Temperature  (°C)",
    domain: [14, 32]
  },
  marks: [
    Plot.ruleY([0]),
    Plot.lineY(curveData, {
      x: (d) => new Date(d.date * 1000),
      y: "temperature",
      curve: "catmull-rom",
      marker: "circle",
      stroke: "red"
    })
  ]
})

(And really, you should say x: "date" instead of x: (d) => new Date(d.date * 1000), but you’ll need to fix the data so that the date field is declared as Date objects instead of numbers.)

If you want to customize the format, you can return a string with newlines (\n) from the tickFormat option. For example:

Plot.plot({
  grid: true,
  x: {
    tickFormat(d) {
      const [a, b] = d.toLocaleString("de-DE").split(",");
      return `${a}\n${b}`;
    }
  },
  y: {
    label: "↑ Temperature  (°C)",
    domain: [14, 32]
  },
  marks: [
    Plot.ruleY([0]),
    Plot.lineY(curveData, {
      x: (d) => new Date(d.date * 1000),
      y: "temperature",
      curve: "catmull-rom",
      marker: "circle",
      stroke: "red"
    })
  ]
})

I wouldn’t use the DOM modification (d3.select) approach unless you really need it, as it’s much lower level than using Plot’s built-in features.

2 Likes