user8569431
user8569431

Reputation:

A more elegant way to bind new elements using data bound to multiple group elements

When working with d3.js I often encounter the situation where I would like to create some groups that contain specific parts of my data set. These groups are then used to bound some other elements to it after transforming the data contained by the groups with some function. I have been searching for an elegant situation for a while, but haven't found one yet. I now use the each function, but I reckon there must be a better way to do this. If anyone knows of a better way to do this, it would be greatly appreciated.

d3.json("example.json").then(data => {
  d3.select("#A")
    .append("svg")
    .attr("id", "A_SVG")
    .attr("width", width)
    .attr("height",height);
  d3.select("#A_SVG")
    .append("g")
    .attr("transform", `translate(${margin.left},${margin.top})`)
    .attr("id", "A_CHART");
  d3.select("#A_CHART")
    .selectAll(".stateProperty")
    .data(Object.entries(data["A"]))
    .enter()
    .append("g")
    .attr("class", "stateProperty")
    .attr("transform", (d, i) => `translate(0,${i * rowSpacing})`);
 d3.select("#A_CHART")
    .selectAll(".stateProperty")
    .each(function(d) {
      d3.select(this)
        .selectAll("rect")
        .data(getNotHoldsRectValues(d[1]))
        .enter()
        .append("rect");
    });
});

function getNotHoldsRectValues(data) {
return [data[0],data[1]]
}

example.json:

{
  "A": {
    "tick_update": [
      { "holds": 1535602060 },
      { "not_holds": 1535602070 },
      { "holds": 1535602120 },
      { "not_holds": 1535602130 }
    ],
    "some_property": []
  },
  "B": {
    "tick_update": [
      { "holds": 1535602060 },
      { "not_holds": 1535602070 },
      { "holds": 1535602120 },
      { "not_holds": 1535602130 },
      { "holds": 1535602180 },
      { "not_holds": 1535602610 }
    ],
    "some_property": []
  }
}

Upvotes: 2

Views: 44

Answers (1)

Gerardo Furtado
Gerardo Furtado

Reputation: 102194

It turns out that the "elegant" way is just the default, idiomatic way.

First, avoid selecting elements all the time. That's a slow process. Instead of that, name your selections and use them.

So, this...

d3.select("#A")
    .append("svg")
    .attr("id", "A_SVG")
    .attr("width", width)
    .attr("height",height);
d3.select("#A_SVG")
    .append("g")
    .attr("transform", `translate(${margin.left},${margin.top})`)
    .attr("id", "A_CHART");

Becomes:

const svg = d3.select("#A")
    .append("svg")
    .attr("id", "A_SVG")
    .attr("width", width)
    .attr("height",height);

svg.append("g")
    .attr("transform", `translate(${margin.left},${margin.top})`)
    .attr("id", "A_CHART");

That way, you just need to use your new const svg whenever you want to refer to that selection. That can be applied to all sub-selections.

Secondly, there is no need for an each here, simply call the enter selection in the group you want.

All that being said, this could be your code:

d3.json("example.json").then(data => {
  const svg = d3.select("#A")
    .append("svg")
    .attr("id", "A_SVG")
    .attr("width", width)
    .attr("height", height);
  const g = svg.append("g")
    .attr("transform", `translate(${margin.left},${margin.top})`)
    .attr("id", "A_CHART");
  const myGroups = g.selectAll(null)
    .data(Object.entries(data["A"]))
    .enter()
    .append("g")
    .attr("class", "stateProperty")
    .attr("transform", (d, i) => `translate(0,${i * rowSpacing})`);
  const rects = myGroups.selectAll(null)
    .data(getNotHoldsRectValues(d[1]))
    .enter()
    .append("rect");
});

Here I'm using selectAll(null), assuming you don't have an update selection.

Finally, it's not clear to me why you are using Object.entries() and that getNotHoldsRectValues function. That might be worth a new question.

Upvotes: 2

Related Questions