d3.js: изменить (развернуть) данные, связанные в selection.each () - PullRequest
0 голосов
/ 20 декабря 2018

Я делаю организационную структуру в d3.js (версия 5).Я хочу, чтобы он был максимально динамичным, поэтому, когда есть изменения, я могу легко обновить входные файлы, и он будет перерисован.У меня есть упрощенное представление ниже и в этот CodePen , в котором я жестко закодировал входные данные.

Вот краткое объяснение того, чего я пытаюсь достичь:

  • const tree - это массив объектов, которые представляют иерархическую часть визуализации:

    • a ROOT,
    • 3 менеджера и
    • 6 проектов
  • const staff isмассив объектов, представляющих персонал.

  • searchObj и findIndicesOfMatches работают в .map(), чтобы заменить имена сотрудников в проекте на объект, представляющий их (наряду со свойствами, которые я буду использовать, когда буду продолжать развивать эту организациюдиаграмма)
  • Я определяю макет дерева и отрисовываю дерево.Родительские узлы расширяются, чтобы покрыть своих дочерних элементов, по желанию.
  • Последний шаг, на котором я застрял, заключается в том, что я хочу перебрать листовые узлы, добавить g и отрендерить дополнительные rect s на основе свойства staff.В настоящее время я пытаюсь использовать .each() на узлах, проверяя, чтобы они были листьями (if(!d.children)), добавить g и построить представление персонала в проекте.

В чем я не уверен, так это в том, как изменить связанные данные на свойство staff.Ничто из того, что я пробовал, пока не работает.В настоящее время я получаю один rect в g, даже для проектов без персонала.

Как это выглядит: What it looks like

Как это должно выглядеть: What it should look like

Есть идеи?

index.html:

<!DOCTYPE html>
<html>

<head>
  <script src="https://d3js.org/d3.v5.min.js"></script>
  <script src="./org-chart.js"></script>

  <style>
    body {
      font-family: 'Helvetica';
      color: #666;
      font: 12px sans-serif;
    }
  </style>
</head>

<body>
  <div id="viz"></div>
</body>

</html>

org-chart.js:

const tree = [
  {
    id: 'ROOT',
    parent: null
  },
  {
    id: 'Manager 1',
    parent: 'ROOT'
  },
  {
    id: 'Manager 2',
    parent: 'ROOT'
  },
  {
    id: 'Manager 3',
    parent: 'ROOT'
  },
  {
    id: 'Project 1',
    parent: 'Manager 1',
    staff: ['Staff 1']
  },
  {
    id: 'Project 2',
    parent: 'Manager 1',
    staff: ['Staff 1', 'Staff 2']
  },
  {
    id: 'Project 3',
    parent: 'Manager 2',
    staff: ['Staff 2', 'Staff 3', 'Staff 4']
  },
  {
    id: 'Project 4',
    parent: 'Manager 2',
    staff: ['Staff 2', 'Staff 3', 'Staff 5']
  },
  {
    id: 'Project 5',
    parent: 'Manager 2',
    staff: []
  },
  {
    id: 'Project 6',
    parent: 'Manager 3',
    staff: ['Staff 4', 'Staff 5']
  }
];

const staff = [
  { name: 'Staff 1', office: 'Office 1' },
  { name: 'Staff 2', office: 'Office 2' },
  { name: 'Staff 3', office: 'Office 3' },
  { name: 'Staff 4', office: 'Office 4' },
  { name: 'Staff 5', office: 'Office 5' }
];

function searchObj(obj, query) {
  for (var key in obj) {
    var value = obj[key];
    if (typeof value === 'object') {
      searchObj(value, query);
    }
    if (value === query) {
      return true;
    }
  }
  return false;
}

function findIndicesOfMatches(arrayOfObjects, query) {
  booleanArray = arrayOfObjects.map(el => {
    return searchObj(el, query);
  });
  const reducer = (accumulator, currentValue, index) => {
    return currentValue ? accumulator.concat(index) : accumulator;
  };
  return booleanArray.reduce(reducer, []);
}

// Join tree and staff data
const joinedData = tree.map(el => {
  if ('staff' in el) {
    newStaffArray = el.staff.map(s => {
      const staffIndex = findIndicesOfMatches(staff, s);
      return staff[staffIndex];
    });

    return { ...el, staff: newStaffArray };
  } else {
    return el;
  }
});

console.log('joinedData');
console.log(joinedData);

// Sizing variables
const margin = { top: 50, right: 50, bottom: 90, left: 90 },
  width = 1000,
  height = 200,
  node_height = 25,
  leaf_node_width = 100;

// Draw function
drawOrgChart = data => {
  const svg = d3
    .select('#viz')
    .append('svg')
    .attr('width', width + margin.left + margin.right)
    .attr('height', height + margin.bottom + margin.top);

  const stratify = d3
    .stratify()
    .parentId(d => d.parent)
    .id(d => d.id);

  const tree = d3
    .tree()
    .size([width, height])
    .separation(d => leaf_node_width * 0.5);

  const dataStratified = stratify(data);

  var nodes = d3.hierarchy(dataStratified);

  root_node = tree(nodes);

  const g = svg
    .append('g')
    .attr('transform', `translate(${margin.left}, ${margin.top})`);

  var nodes = g
    .selectAll('.node')
    .data(nodes.descendants(), d => d.id)
    .enter()
    .append('g')
    .attr('class', function(d) {
      return 'node' + (d.children ? ' node--internal' : ' node--leaf');
    })
    .attr('transform', function(d) {
      return 'translate(' + d.x + ',' + d.y + ')';
    });

  var rect_colors = ['grey', 'blue', 'green', 'maroon'];

  nodes
    .append('rect')
    .attr('height', node_height)
    .attr('width', d => {
      const extent = d3.extent(d.leaves().map(d => d.x));
      return extent[1] - extent[0] + leaf_node_width;
    })
    .attr('fill', '#ffffff')
    .attr('stroke', d => {
      return rect_colors[d.depth];
    })
    .attr('transform', d => {
      const first_leaf_x = d.leaves()[0].x;
      return `translate(${-(d.x - first_leaf_x + leaf_node_width / 2)},0)`;
    })
    .attr('rx', 5)
    .attr('ry', 5);

  nodes
    .append('text')
    .attr('dy', '.35em')
    .attr('x', d => 0)
    .attr('y', node_height / 2)
    .style('text-anchor', 'middle')
    .text(function(d) {
      return d.data.data.id;
    });

  // This is the bit I can't figure out:
  // I'd like to append additional elements to
  // the leaf nodes based on the 'staff' property
  console.log(nodes.data());
  nodes.each(function(d, j) {
    if (!d.children) {
      const staff = d.data.data.staff;
      console.log(staff);

      d3.select(this)
        .append('g')
        .selectAll('rect')
        .data([staff])
        .enter()
        .append('rect')
        .attr('x', 0)
        .attr('y', (p, i) => 30 * (i + 1))
        .attr('height', node_height)
        .attr('width', leaf_node_width)
        .attr('transform', `translate(-${leaf_node_width / 2},0)`)
        .attr('stroke', 'red')
        .attr('fill', '#efefef80');
    }
  });
};

document.addEventListener('DOMContentLoaded', function() {
  drawOrgChart(joinedData);
});

1 Ответ

0 голосов
/ 20 декабря 2018

Вам необходимо заменить .data([staff]) на .data(staff).staff это уже массив.Если вы используете [staff], он связывается с массивом одного элемента, который сам является массивом.Вот почему вы видите только один лист для персонала.

d3.select(this)
  .append('g')
  .selectAll('rect')
  // use staff instead of [staff]
  .data(staff)
  ....

См. Этот измененный CodePen

Все еще существует проблема с размером прямоугольников (последнийодин из вне svg), но он должен указать вам правильный путь.

...