Modermo
Modermo

Reputation: 1992

Access parent data when appending

I basically have some nested data like so:

const shapeGroups = [
      {
        title: 'shapeGroup_01',
        color: 'blue',
        shapes: [
          {
            shape:'rect',
            width: 30,
            height: 100,
            x: 250,
            y: 450
          }
        ]
      },
      {
        title: 'shapeGroup_01',
        color: 'blue',
        shapes: [
          {
            shape:'rect',
            width: 10,
            height: 40,
            x: 350,
            y:50
          }
        ]
      }
    ]

What I'm interested in doing is setting the color in each rect as it's defined in the shapeGroup's color property.

const shapeGroups = [{
    title: 'shapeGroup_01',
    color: 'blue',
    shapes: [{
      shape: 'rect',
      width: 30,
      height: 100,
      x: 250,
      y: 450
    }]
  },
  {
    title: 'shapeGroup_01',
    color: 'blue',
    shapes: [{
      shape: 'rect',
      width: 10,
      height: 40,
      x: 350,
      y: 50
    }]
  }
]

const stage = d3.select('#stageContainer')
  .append('svg')
  .attr('id', '#stage')
  .attr('width', 1000)
  .attr('height', 1000)


const groups = stage
  .selectAll('.group')
  .data(shapeGroups)

const groupEnter = groups
  .enter()
  .append('g')
  .attr('class', 'group');

const getGroup = group => group.shapes;
const createShape = shape => document.createElementNS(d3.namespaces.svg, shape.shape)

groupEnter
  .selectAll('.shape')
  .data(getGroup)
  .enter()
  .append(createShape)
  .attr('fill', 'red') // I want the color as defined in the current group
  .attr('width', d => d.width)
  .attr('height', d => d.height)
  .attr('x', d => d.x)
  .attr('y', d => d.y)
<script src="https://d3js.org/d3.v5.min.js"></script>
<div id="stageContainer"></div>

In other words, I need a way to either set color at the group level, or somehow access the parent datum object when I'm appending individual .shapes.

Upvotes: 2

Views: 186

Answers (1)

Gerardo Furtado
Gerardo Furtado

Reputation: 102194

As you can read in my answer here, you cannot access the parent's datum or its index from inside a selection in D3 v4/v5.

A solution here is getting the datum of the parentNode:

.attr('fill', (_, i, n) => d3.select(n[i].parentNode).datum().color)

Here is your code with that change (I'm making the two rectangles with different colours and the SVG smaller, for better visualising it):

const shapeGroups = [{
    title: 'shapeGroup_01',
    color: 'blue',
    shapes: [{
      shape: 'rect',
      width: 30,
      height: 100,
      x: 250,
      y: 50
    }]
  },
  {
    title: 'shapeGroup_01',
    color: 'green',
    shapes: [{
      shape: 'rect',
      width: 10,
      height: 40,
      x: 350,
      y: 20
    }]
  }
];

const stage = d3.select('body')
  .append('svg')
  .attr('width', 400)
  .attr('height', 400)


const groups = stage
  .selectAll('.group')
  .data(shapeGroups)

const groupEnter = groups
  .enter()
  .append('g')
  .attr('class', 'group');

const getGroup = group => group.shapes;
const createShape = shape => document.createElementNS(d3.namespaces.svg, shape.shape)

groupEnter
  .selectAll('.shape')
  .data(getGroup)
  .enter()
  .append(createShape)
  .attr('fill', (_, i, n) => d3.select(n[i].parentNode).datum().color)
  .attr('width', d => d.width)
  .attr('height', d => d.height)
  .attr('x', d => d.x)
  .attr('y', d => d.y)
<script src="https://d3js.org/d3.v5.min.js"></script>

However, the simplest solution by far is just setting the fill to the parent selection itself:

const groupEnter = groups
    .enter()
    .append('g')
    .attr('class', 'group')
    .attr('fill', d => d.color);

Here is the demo:

const shapeGroups = [{
    title: 'shapeGroup_01',
    color: 'blue',
    shapes: [{
      shape: 'rect',
      width: 30,
      height: 100,
      x: 250,
      y: 50
    }]
  },
  {
    title: 'shapeGroup_01',
    color: 'green',
    shapes: [{
      shape: 'rect',
      width: 10,
      height: 40,
      x: 350,
      y: 20
    }]
  }
];

const stage = d3.select('body')
  .append('svg')
  .attr('width', 400)
  .attr('height', 400)


const groups = stage
  .selectAll('.group')
  .data(shapeGroups)

const groupEnter = groups
  .enter()
  .append('g')
  .attr('class', 'group')
  .attr('fill', d=>d.color);

const getGroup = group => group.shapes;
const createShape = shape => document.createElementNS(d3.namespaces.svg, shape.shape)

groupEnter
  .selectAll('.shape')
  .data(getGroup)
  .enter()
  .append(createShape)
  .attr('width', d => d.width)
  .attr('height', d => d.height)
  .attr('x', d => d.x)
  .attr('y', d => d.y)
<script src="https://d3js.org/d3.v5.min.js"></script>

Upvotes: 3

Related Questions