Я делаю организационную структуру в 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
, даже для проектов без персонала.
Как это выглядит:
Как это должно выглядеть:
Есть идеи?
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);
});