🏠 back to Observable

Using D3 selection.join to create variable tags

This is more of a D3 question, so if this isn’t the right place to ask, do let me know.

I’m creating a data-driven table in D3, along the lines of:

  const container = d3.select(DOM.element("div"));

  const tbl = container
    .append("table");

  const thead = tbl
    .append("thead")
    .append("tr")
    .selectAll("th")
    .data(Object.keys(thisData[1]))
    .join("th")
    .text(d => d);

  const tbody = tbl
    .append("tbody")
    .selectAll("tr")
    .data(Object.values(thisData))
    .join("tr");

  tbody
    .selectAll("td")
    .data(function(d, i) {
      return Object.values(d);
    })
    .join("td")
    .text(function(d) {
        return d["Name"];
    });

The thing I’m wondering: is there a way to create both td's and th's in the last join call based on the value of d, where there’s information in the data that would tell me that a given column is actually a header and so should be created with a th tag?

If not, are there other ways to create th and td tags together in a D3-generated table?

Thanks in advance for any help!

Yes, but it’s a little cumbersome. You can pass a enter function to selection.join, and then a create function to selection.append.

{
  const table = d3.create("table");
  const tbody = table.append("tbody");

  tbody
    .selectAll("td")
    .data([
      {header: true, name: "Alice"}, 
      {header: false, name: "Bob"}
    ])
    .join(
      enter => enter.append(d => document.createElement(d.header ? "th" : "td")),
      update => update,
      exit => exit.remove()
    )
      .text(d => d.name);

  return table.node();
}
4 Likes

That works perfectly. Thank you!