Передо мной трудное время при преобразовании иерархической диаграммы d3 из версии 3 в версию 5. Я внес много изменений, но не смог заставить ее работать. Любая помощь будет оценена.
Я использую этот пример https://bl.ocks.org/willzjc/a11626a31c65ba5d319fcf8b8870f281. Это в версии 3. Ниже ссылка на скрипку, в которой я внес изменения. https://jsfiddle.net/vmctq85k/3/
var params = {
selector: "#svgChart",
dataLoadUrl: "https://raw.githubusercontent.com/bumbeishvili/Assets/master/Projects/D3/Organization%20Chart/redesignedChartLongData.json",
chartWidth: window.innerWidth-40,
chartHeight: window.innerHeight - 40,
funcs: {
showMySelf: null,
search: null,
closeSearchBox: null,
clearResult: null,
findInTree: null,
reflectResults: null,
departmentClick: null,
back: null,
toggleFullScreen: null,
locate:null
},
data: null
}
d3.json(params.dataLoadUrl).then(res=>{
params.data = res;
params.pristinaData = JSON.parse(JSON.stringify(res));
drawOrganizationChart(params);
})
function drawOrganizationChart(params) {
listen();
params.funcs.showMySelf = showMySelf;
params.funcs.expandAll = expandAll;
params.funcs.search = searchUsers;
params.funcs.closeSearchBox = closeSearchBox;
params.funcs.findInTree = findInTree;
params.funcs.clearResult = clearResult;
params.funcs.reflectResults = reflectResults;
params.funcs.departmentClick = departmentClick;
params.funcs.back = back;
params.funcs.toggleFullScreen = toggleFullScreen;
params.funcs.locate=locate;
var attrs = {
EXPAND_SYMBOL: '\uf067',
COLLAPSE_SYMBOL: '\uf068',
selector: params.selector,
root: params.data,
width: params.chartWidth,
height: params.chartHeight,
index: 0,
nodePadding: 9,
collapseCircleRadius: 7,
nodeHeight: 80,
nodeWidth: 210,
duration: 750,
rootNodeTopMargin: 20,
minMaxZoomProportions: [0.05, 3],
linkLineSize: 180,
collapsibleFontSize: '10px',
userIcon: '\uf007',
nodeStroke: "#ccc",
nodeStrokeWidth: '1px'
}
var dynamic = {}
dynamic.nodeImageWidth = attrs.nodeHeight * 100 / 140;
dynamic.nodeImageHeight = attrs.nodeHeight - 2 * attrs.nodePadding;
dynamic.nodeTextLeftMargin = attrs.nodePadding * 2 + dynamic.nodeImageWidth
dynamic.rootNodeLeftMargin = attrs.width / 2;
dynamic.nodePositionNameTopMargin = attrs.nodePadding + 8 + dynamic.nodeImageHeight / 4 * 1
dynamic.nodeChildCountTopMargin = attrs.nodePadding + 14 + dynamic.nodeImageHeight / 4 * 3
var tree = d3.tree().nodeSize([attrs.nodeWidth + 40, attrs.nodeHeight]);
var diagonal = d3.linkVertical()
.x(function(d) { return d.y; })
.y(function(d) { return d.x; });
var zoomBehaviours = d3.zoom()
.on("zoom", redraw);
var svg = d3.select(attrs.selector)
.append("svg")
.attr("width", attrs.width)
.attr("height", attrs.height)
.call(zoomBehaviours)
.append("g")
.attr("transform", "translate(" + attrs.width / 2 + "," + 20 + ")");
attrs.root.x0 = 0;
attrs.root.y0 = dynamic.rootNodeLeftMargin;
if (params.mode != 'department') {
var uniq = 1;
addPropertyRecursive('uniqueIdentifier', function(v) {
return uniq++;
}, attrs.root);
}
expand(attrs.root);
if (attrs.root.children) {
attrs.root.children.forEach(collapse);
}
update(attrs.root);
d3.select(attrs.selector).style("height", attrs.height);
var tooltip = d3.select('body')
.append('div')
.attr('class', 'customTooltip-wrapper');
function update(source, param) {
const treeRoot = d3.hierarchy(attrs.root)
tree(treeRoot)
const nodes = treeRoot.descendants()
const links = treeRoot.links()
nodes.forEach(function(d) {
d.y = d.depth * attrs.linkLineSize;
});
var node = svg.selectAll("g.node")
.data(nodes, function(d) {
return d.id || (d.id = ++attrs.index);
});
var nodeEnter = node.enter()
.append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + source.x0 + "," + source.y0 + ")";
})
var nodeGroup = nodeEnter.append("g")
.attr("class", "node-group")
nodeGroup.append("rect")
.attr("width", attrs.nodeWidth)
.attr("height", attrs.nodeHeight)
.attr("data-node-group-id",function(d){
return d.data.uniqueIdentifier;
})
.attr("class", function(d) {
var res = "";
if (d.data.isLoggedUser) res += 'nodeRepresentsCurrentUser ';
res += d._children || d.children ? "nodeHasChildren" : "nodeDoesNotHaveChildren";
return res;
});
var collapsiblesWrapper =
nodeEnter.append('g')
.attr('data-id', function(v) {
return v.data.uniqueIdentifier;
});
var collapsibleRects = collapsiblesWrapper.append("rect")
.attr('class', 'node-collapse-right-rect')
.attr('height', attrs.collapseCircleRadius)
.attr('fill', 'black')
.attr('x', attrs.nodeWidth - attrs.collapseCircleRadius)
.attr('y', attrs.nodeHeight - 7)
.attr("width", function(d) {
if (d.children || d._children) return attrs.collapseCircleRadius;
return 0;
})
var collapsibles =
collapsiblesWrapper.append("circle")
.attr('class', 'node-collapse')
.attr('cx', attrs.nodeWidth - attrs.collapseCircleRadius)
.attr('cy', attrs.nodeHeight - 7)
.attr("", setCollapsibleSymbolProperty);
collapsibles.attr("r", function(d) {
if (d.children || d._children) return attrs.collapseCircleRadius;
return 0;
})
.attr("height", attrs.collapseCircleRadius)
collapsiblesWrapper.append("text")
.attr('class', 'text-collapse')
.attr("x", attrs.nodeWidth - attrs.collapseCircleRadius)
.attr('y', attrs.nodeHeight - 3)
.attr('width', attrs.collapseCircleRadius)
.attr('height', attrs.collapseCircleRadius)
.style('font-size', attrs.collapsibleFontSize)
.attr("text-anchor", "middle")
.style('font-family', 'FontAwesome')
.text(function(d) {
return d.data.collapseText;
})
collapsiblesWrapper.on("click", click);
nodeGroup.append("text")
.attr("x", dynamic.nodeTextLeftMargin)
.attr("y", attrs.nodePadding + 10)
.attr('class', 'emp-name')
.attr("text-anchor", "left")
.text(function(d) {
return d.data.name.trim();
})
.call(wrap, attrs.nodeWidth);
nodeGroup.append("text")
.attr("x", dynamic.nodeTextLeftMargin)
.attr("y", dynamic.nodePositionNameTopMargin)
.attr('class', 'emp-position-name')
.attr("dy", ".35em")
.attr("text-anchor", "left")
.text(function(d) {
var position = d.data.positionName.substring(0,27);
if(position.length<d.data.positionName.length){
position = position.substring(0,24)+'...'
}
return position;
})
nodeGroup.append("text")
.attr("x", dynamic.nodeTextLeftMargin)
.attr("y", attrs.nodePadding + 10 + dynamic.nodeImageHeight / 4 * 2)
.attr('class', 'emp-area')
.attr("dy", ".35em")
.attr("text-anchor", "left")
.text(function(d) {
return d.data.area;
})
nodeGroup.append("text")
.attr("x", dynamic.nodeTextLeftMargin)
.attr("y", dynamic.nodeChildCountTopMargin)
.attr('class', 'emp-count-icon')
.attr("text-anchor", "left")
.style('font-family', 'FontAwesome')
.text(function(d) {
if (d.children || d._children) return attrs.userIcon;
});
nodeGroup.append("text")
.attr("x", dynamic.nodeTextLeftMargin + 13)
.attr("y", dynamic.nodeChildCountTopMargin)
.attr('class', 'emp-count')
.attr("text-anchor", "left")
.text(function(d) {
if (d.data.children != undefined) {
return d.data.children.length
} else {
return d.data._children.length
}
return;
})
nodeGroup.append("defs").append("clipPath")
.attr("id", "clip")
.append("svg:rect")
.attr("id", "clip-rect")
.attr("rx", 3)
.attr('x', attrs.nodePadding)
.attr('y', 2 + attrs.nodePadding)
.attr('width', dynamic.nodeImageWidth)
.attr('fill', 'none')
.attr('height', dynamic.nodeImageHeight - 4)
nodeGroup.append("image")
.attr('y', 2 + attrs.nodePadding)
.attr('x', attrs.nodePadding)
.attr('preserveAspectRatio', 'none')
.attr('width', dynamic.nodeImageWidth)
.attr('height', dynamic.nodeImageHeight - 4)
.attr('clip-path', "url(#clip)")
.attr("xlink:href", function(v) {
return v.data.imageUrl;
})
var nodeUpdate = node.transition()
.duration(attrs.duration)
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
})
nodeUpdate.select("rect")
.attr("width", attrs.nodeWidth)
.attr("height", attrs.nodeHeight)
.attr('rx', 3)
.attr("stroke", function(d){
if(param && d.uniqueIdentifier== param.locate){
return '#a1ceed'
}
return attrs.nodeStroke;
})
.attr('stroke-width', function(d){
if(param && d.uniqueIdentifier== param.locate){
return 6;
}
return attrs.nodeStrokeWidth})
var nodeExit = node.exit().transition()
.duration(attrs.duration)
.attr("transform", function(d) {
return "translate(" + source.x + "," + source.y + ")";
})
.remove();
nodeExit.select("rect")
.attr("width", attrs.nodeWidth)
.attr("height", attrs.nodeHeight)
var link = svg.selectAll("path.link")
.data(links, function(d) {
return d.target.id;
});
link.enter().insert("path", "g")
.attr("class", "link")
.attr("x", attrs.nodeWidth / 2)
.attr("y", attrs.nodeHeight / 2)
.attr("d", function(d) {
var o = {
x: source.x0,
y: source.y0
};
return diagonal({
source: o,
target: o
});
});
link.transition()
.duration(attrs.duration)
.attr("d", diagonal);
link.exit().transition()
.duration(attrs.duration)
.attr("d", function(d) {
var o = {
x: source.x,
y: source.y
};
return diagonal({
source: o,
target: o
});
})
.remove();
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
if(param && param.locate){
var x;
var y;
nodes.forEach(function(d) {
if (d.uniqueIdentifier == param.locate) {
x = d.x;
y = d.y;
}});
var new_x = (-x + (window.innerWidth / 2));
var new_y = (-y + (window.innerHeight / 2));
svg.attr("transform", "translate(" + new_x + "," + new_y + ")")
zoomBehaviours.translate([new_x, new_y]);
zoomBehaviours.scale(1);
}
if (param && param.centerMySelf) {
var x;
var y;
nodes.forEach(function(d) {
if (d.isLoggedUser) {
x = d.x;
y = d.y;
}});
var new_x = (-x + (window.innerWidth / 2));
var new_y = (-y + (window.innerHeight / 2));
svg.attr("transform", "translate(" + new_x + "," + new_y + ")")
zoomBehaviours.translate([new_x, new_y]);
zoomBehaviours.scale(1);
}
function getTagsFromCommaSeparatedStrings(tags) {
return tags.split(',').map(function(v) {
return '<li><div class="tag">' + v + '</div></li> '
}).join('');
}
function tooltipContent(item) {
var strVar = "";
strVar += " <div class=\"customTooltip\">";
strVar += " <!--";
strVar += " <div class=\"tooltip-image-wrapper\"> <img width=\"300\" src=\"https:\/\/raw.githubusercontent.com\/bumbeishvili\/Assets\/master\/Projects\/D3\/Organization%20Chart\/cto.jpg\"> <\/div>";
strVar += "-->";
strVar += " <div class=\"profile-image-wrapper\" style='background-image: url(" + item.imageUrl + ")'>";
strVar += " <\/div>";
strVar += " <div class=\"tooltip-hr\"><\/div>";
strVar += " <div class=\"tooltip-desc\">";
strVar += " <a class=\"name\" href='" + item.profileUrl + "' target=\"_blank\"> " + item.name + "<\/a>";
strVar += " <p class=\"position\">" + item.positionName + " <\/p>";
strVar += " <p class=\"area\">" + item.area + " <\/p>";
strVar += "";
strVar += " <p class=\"office\">" + item.office + "<\/p>";
strVar += " <button class='" + (item.unit.type == 'business' ? " disabled " : "") + " btn btn-tooltip-department' onclick='params.funcs.departmentClick(" + JSON.stringify(item.unit) + ")'>" + item.unit.value + "</button>";
strVar += " <h4 class=\"tags-wrapper\"> <span class=\"title\"><i class=\"fa fa-tags\" aria-hidden=\"true\"><\/i>";
strVar += " ";
strVar += " <\/span> <ul class=\"tags\">" + getTagsFromCommaSeparatedStrings(item.tags) + "<\/ul> <\/h4> <\/div>";
strVar += " <div class=\"bottom-tooltip-hr\"><\/div>";
strVar += " <\/div>";
strVar += "";
return strVar;
}
function equalToEventTarget() {
return this == d3.event.target;
}
d3.select("body").on("click", function() {
var outside = tooltip.filter(equalToEventTarget).empty();
if (outside) {
tooltip.style('opacity', '0').style('display', 'none');
}
});
}
function click(d) {
d3.select(this).select("text").text(function(dv) {
if (dv.collapseText == attrs.EXPAND_SYMBOL) {
dv.collapseText = attrs.COLLAPSE_SYMBOL
} else {
if (dv.children) {
dv.collapseText = attrs.EXPAND_SYMBOL
}
}
return dv.collapseText;
})
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update(d);
}
function redraw() {
svg.attr("transform", d3.event.transform)
}
function wrap(text, width) {
text.each(function() {
var text = d3.select(this),
words = text.text().split(/\s+/).reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 1.1, // ems
x = text.attr("x"),
y = text.attr("y"),
dy = 0, //parseFloat(text.attr("dy")),
tspan = text.text(null)
.append("tspan")
.attr("x", x)
.attr("y", y)
.attr("dy", dy + "em");
while (word = words.pop()) {
line.push(word);
tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(" "));
line = [word];
tspan = text.append("tspan")
.attr("x", x)
.attr("y", y)
.attr("dy", ++lineNumber * lineHeight + dy + "em")
.text(word);
}
}
});
}
function addPropertyRecursive(propertyName, propertyValueFunction, element) {
if (element[propertyName]) {
element[propertyName] = element[propertyName] + ' ' + propertyValueFunction(element);
} else {
element[propertyName] = propertyValueFunction(element);
}
if (element.children) {
element.children.forEach(function(v) {
addPropertyRecursive(propertyName, propertyValueFunction, v)
})
}
if (element._children) {
element._children.forEach(function(v) {
addPropertyRecursive(propertyName, propertyValueFunction, v)
})
}
}
function departmentClick(item) {
hide(['.customTooltip-wrapper']);
if (item.type == 'department' && params.mode != 'department') {
var found = false;
var secondLevelChildren = params.pristinaData.children;
parentLoop:
for (var i = 0; i < secondLevelChildren.length; i++) {
var secondLevelChild = secondLevelChildren[i];
var thirdLevelChildren = secondLevelChild.children ? secondLevelChild.children : secondLevelChild._children;
for (var j = 0; j < thirdLevelChildren.length; j++) {
var thirdLevelChild = thirdLevelChildren[j];
if (thirdLevelChild.unit.value.trim() == item.value.trim()) {
clear(params.selector);
hide(['.btn-action']);
show(['.btn-action.btn-back', '.btn-action.btn-fullscreen', '.department-information']);
set('.dept-name', item.value);
set('.dept-emp-count', "Employees Quantity - " + getEmployeesCount(thirdLevelChild));
set('.dept-description', thirdLevelChild.unit.desc);
params.oldData = params.data;
params.data = deepClone(thirdLevelChild);
found = true;
break parentLoop;
}
}
}
if (found) {
params.mode = "department";
params.funcs.closeSearchBox();
drawOrganizationChart(params);
}
}
}
function getEmployeesCount(node) {
var count = 1;
countChilds(node);
return count;
function countChilds(node) {
var childs = node.children ? node.children : node._children;
if (childs) {
childs.forEach(function(v) {
count++;
countChilds(v);
})
}
}
}
function reflectResults(results) {
var htmlStringArray = results.map(function(result) {
var strVar = "";
strVar += " <div class=\"list-item\">";
strVar += " <a >";
strVar += " <div class=\"image-wrapper\">";
strVar += " <img class=\"image\" src=\"" + result.imageUrl + "\"\/>";
strVar += " <\/div>";
strVar += " <div class=\"description\">";
strVar += " <p class=\"name\">" + result.name + "<\/p>";
strVar += " <p class=\"position-name\">" + result.positionName + "<\/p>";
strVar += " <p class=\"area\">" + result.area + "<\/p>";
strVar += " <\/div>";
strVar += " <div class=\"buttons\">";
strVar += " <a target='_blank' href='"+result.profileUrl+"'><button class='btn-search-box btn-action'>View Profile<\/button><\/a>";
strVar += " <button class='btn-search-box btn-action btn-locate' onclick='params.funcs.locate("+result.uniqueIdentifier+")'>Locate <\/button>";
strVar += " <\/div>";
strVar += " <\/a>";
strVar += " <\/div>";
return strVar;
})
var htmlString = htmlStringArray.join('');
params.funcs.clearResult();
var parentElement = get('.result-list');
var old = parentElement.innerHTML;
var newElement = htmlString + old;
parentElement.innerHTML = newElement;
set('.user-search-box .result-header', "RESULT - " + htmlStringArray.length);
}
function clearResult() {
set('.result-list', '<div class="buffer" ></div>');
set('.user-search-box .result-header', "RESULT");
}
function listen() {
var input = get('.user-search-box .search-input');
input.addEventListener('input', function() {
var value = input.value ? input.value.trim() : '';
if (value.length < 3) {
params.funcs.clearResult();
} else {
var searchResult = params.funcs.findInTree(params.data, value);
params.funcs.reflectResults(searchResult);
}
});
}
function searchUsers() {
d3.selectAll('.user-search-box')
.transition()
.duration(250)
.style('width', '350px')
}
function closeSearchBox() {
d3.selectAll('.user-search-box')
.transition()
.duration(250)
.style('width', '0px')
.each("end", function() {
params.funcs.clearResult();
clear('.search-input');
});
}
function findInTree(rootElement, searchText) {
var result = [];
var regexSearchWord = new RegExp(searchText, "i");
recursivelyFindIn(rootElement, searchText);
return result;
function recursivelyFindIn(user) {
if (user.name.match(regexSearchWord) ||
user.tags.match(regexSearchWord)) {
result.push(user)
}
var childUsers = user.children ? user.children : user._children;
if (childUsers) {
childUsers.forEach(function(childUser) {
recursivelyFindIn(childUser, searchText)
})
}
};
}
function back() {
show(['.btn-action']);
hide(['.customTooltip-wrapper', '.btn-action.btn-back', '.department-information'])
clear(params.selector);
params.mode = "full";
params.data = deepClone(params.pristinaData)
drawOrganizationChart(params);
}
function expandAll() {
expand(root);
update(root);
}
function expand(d) {
if (d.children) {
d.children.forEach(expand);
}
if (d._children) {
d.children = d._children;
d.children.forEach(expand);
d._children = null;
}
if (d.children) {
setToggleSymbol(d, attrs.COLLAPSE_SYMBOL);
}
}
function collapse(d) {
if (d._children) {
d._children.forEach(collapse);
}
if (d.children) {
d._children = d.children;
d._children.forEach(collapse);
d.children = null;
}
if (d._children) {
// if node has children and it's collapsed, then display +
setToggleSymbol(d, attrs.EXPAND_SYMBOL);
}
}
function setCollapsibleSymbolProperty(d) {
if (d._children) {
d.collapseText = attrs.EXPAND_SYMBOL;
} else if (d.children) {
d.collapseText = attrs.COLLAPSE_SYMBOL;
}
}
function setToggleSymbol(d, symbol) {
d.collapseText = symbol;
d3.select("*[data-id='" + d.uniqueIdentifier + "']").select('text').text(symbol);
}
function findmySelf(d) {
if (d.isLoggedUser) {
expandParents(d);
} else if (d._children) {
d._children.forEach(function(ch) {
ch.parent = d;
findmySelf(ch);
})
} else if (d.children) {
d.children.forEach(function(ch) {
ch.parent = d;
findmySelf(ch);
});
};
}
function locateRecursive(d,id) {
if (d.uniqueIdentifier == id) {
expandParents(d);
} else if (d._children) {
d._children.forEach(function(ch) {
ch.parent = d;
locateRecursive(ch,id);
})
} else if (d.children) {
d.children.forEach(function(ch) {
ch.parent = d;
locateRecursive(ch,id);
});
};
}
function expandParents(d) {
while (d.parent) {
d = d.parent;
if (!d.children) {
d.children = d._children;
d._children = null;
setToggleSymbol(d, attrs.COLLAPSE_SYMBOL);
}
}
}
function toggleFullScreen() {
if ((document.fullScreenElement && document.fullScreenElement !== null) ||
(!document.mozFullScreen && !document.webkitIsFullScreen)) {
if (document.documentElement.requestFullScreen) {
document.documentElement.requestFullScreen();
} else if (document.documentElement.mozRequestFullScreen) {
document.documentElement.mozRequestFullScreen();
} else if (document.documentElement.webkitRequestFullScreen) {
document.documentElement.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT);
}
d3.select(params.selector + ' svg').attr('width', screen.width).attr('height', screen.height);
} else {
if (document.cancelFullScreen) {
document.cancelFullScreen();
} else if (document.mozCancelFullScreen) {
document.mozCancelFullScreen();
} else if (document.webkitCancelFullScreen) {
document.webkitCancelFullScreen();
}
d3.select(params.selector + ' svg').attr('width', params.chartWidth).attr('height', params.chartHeight);
}
}
function showMySelf() {
if (!attrs.root.children) {
if (!attrs.root.isLoggedUser) {
attrs.root.children = attrs.root._children;
}
}
if (attrs.root.children) {
attrs.root.children.forEach(collapse);
attrs.root.children.forEach(findmySelf);
}
update(attrs.root, {centerMySelf:true});
}
function locate(id){
if (!attrs.root.children) {
if (!attrs.root.uniqueIdentifier == id) {
attrs.root.children = attrs.root._children;
}
}
if (attrs.root.children) {
attrs.root.children.forEach(collapse);
attrs.root.children.forEach(function(ch){
locateRecursive(ch,id)
});
}
update(attrs.root, {locate:id});
}
function deepClone(item) {
return JSON.parse(JSON.stringify(item));
}
function show(selectors) {
display(selectors, 'initial')
}
function hide(selectors) {
display(selectors, 'none')
}
function display(selectors, displayProp) {
selectors.forEach(function(selector) {
var elements = getAll(selector);
elements.forEach(function(element) {
element.style.display = displayProp;
})
});
}
function set(selector, value) {
var elements = getAll(selector);
elements.forEach(function(element) {
element.innerHTML = value;
element.value = value;
})
}
function clear(selector) {
set(selector, '');
}
function get(selector) {
return document.querySelector(selector);
}
function getAll(selector) {
return document.querySelectorAll(selector);
}
}