Я учусь использовать D3. После исправления кода из этого примера (https://jsfiddle.net/tgsen1bc/) для запуска новейшей версии D3 мне удалось отобразить ссылки, но не узлы или их метки.
EDIT : У меня появятся узлы :) Моя проблема теперь в том, что некоторые дочерние элементы <g>
не отображаются, поэтому не появляются метки для узлов. Например, в Chrome Dev Tools дочерние элементы <foreignObject>
(дочерний элемент <g>
) не отображаются в окне. Я попытался сделать <foreignObject>
очень большим, но дочерние элементы все еще не появляются. Я также попытался заменить <foreignObject>
на <div>
, но <div>
тоже исчез. Я проверил родительские свойства css, и не похоже, что что-то должно блокировать появление дочерних элементов. Единственный дочерний элемент, который появляется, - это кружки <svg>
и <foreignObject>
. Раздел кода для метки:
// Add labels for the nodes
nodeEnter.append('foreignObject')
.attr("y", -30)
.attr("x", -5)
.attr("text-anchor", function (d) {
return d.children || d._children ? "end" : "start";
})
.attr('width', 100)
.attr('height', 50)
.append('div') // doesn't show up on webpage
.attr("class", function (d) {
return "node-label" + " node-" + d.data.type
})
.classed("disabled", function (d) {
return d.enable !== undefined && !d.enable;
})
.append("span") // doesn't show up on webpage
.attr("class", "node-text")
.text(function (d) { // correct label in chrome dev tools
return d.data.name; // but does not show up on webpage
});
var treedata = {
"name": "PublisherNameLongName",
"id": "id1",
"type": "type0",
"addable": false,
"editable": false,
"removable": false,
"enableble": false,
"children": [{
"name": "Landing A",
"id": "id2",
"type": "type1",
"addable": true,
"editable": true,
"removable": true,
"enablable": true,
"enable": false,
"children": null
}]
}
// Set the dimensions and margins of the diagram
var margin = { top: 20, right: 20, bottom: 20, left: 20 },
width = 800 - margin.left - margin.right,
height = 600 - margin.top - margin.bottom,
i = 0,
x = d3.scaleLinear().domain([0, width]).range([0, width]),
y = d3.scaleLinear().domain([0, height]).range([0, height]),
root;
// append the svg object to the body of the page
// appends a 'group' element to 'svg'
// moves the 'group' element to the top left margin
var vis = d3.select("#root")
.append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom)
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
;
vis.append("rect")
.attr("class", "overlay")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom)
.attr("opacity", 0)
var tree = d3.tree().size([height, width]);
// Draws curved diagonal path from parent to child nodes
function diagonal(s, d) {
return `M ${s.y} ${s.x}
C ${(s.y + d.y) / 2} ${s.x},
${(s.y + d.y) / 2} ${d.x},
${d.y} ${d.x}`
}
root = d3.hierarchy(treedata, function (d) {
return d.children;
});
root.x0 = height / 2;
root.y0 = 0;
// open or collaspe children of selected node
function toggleAll(d) {
if (d.children) {
d.children.forEach(toggleAll);
toggle(d);
}
};
// Initialize the display to show a few nodes
// root.children.forEach(toggleAll);
update(root);
function update(source) {
// how long animations last
var duration = d3.event && d3.event.altKey ? 5000 : 500;
// Compute the new tree layout.
var treeObj = tree(root)
var nodes = treeObj.descendants(),
links = treeObj.descendants().slice(1);
// Normalize for fixed-depth.
nodes.forEach(function (d) {
d.y = d.depth * 180;
});
/********************* NODES SECTION *********************/
// Update the nodes...
var node = vis.selectAll("g.node")
.data(nodes, function (d) {
return d.id || (d.id = ++i);
});
// Enter any new nodes at the parent's previous position.
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("id", function (d) {
return "node-" + d.id;
})
.attr("transform", function (d) {
return "translate(" + source.y0 + "," + source.x0 + ")";
})
.on("click", function (d) {
toggle(d);
update(d);
});
// Add Circle for the nodes
nodeEnter.append("circle")
.attr("class", "circle-for-nodes")
.attr("r", 1e-6)
.style("fill", function (d) {
return d._children ? "lightsteelblue" : "#fff";
})
// Add labels for the nodes
nodeEnter.append('foreignObject')
.attr("y", -30)
.attr("x", -5)
.attr("text-anchor", function (d) {
return d.children || d._children ? "end" : "start";
})
.attr('width', 100)
.attr('height', 50)
.append('div')
.attr("class", function (d) {
return "node-label" + " node-" + d.data.type
})
.classed("disabled", function (d) {
return d.enable !== undefined && !d.enable;
})
.append("span")
.attr("class", "node-text")
.text(function (d) {
return d.data.name;
});
// Enable node button if enablable
nodeEnter.filter(function (d) {
return d.enablable;
})
.append("input", ".")
.attr("type", "checkbox")
.property("checked", function (d) {
return d.enable;
})
.on("change", toggleEnable, true)
.on("click", stopPropogation, true);
// Edit node button if editable
nodeEnter.filter(function (d) {
return d.editable;
})
.append("a")
.attr("class", "node-edit")
.on("click", onEditNode, true)
.append("i")
.attr("class", "fa fa-pencil");
// Add node button if addable
nodeEnter.filter(function (d) {
return d.addable;
})
.append("a")
.attr("class", "node-add")
.on("click", onAddNode, true)
.append("i")
.attr("class", "fa fa-plus");
// Remove node button if removable
nodeEnter.filter(function (d) {
return d.removable;
})
.append("a")
.attr("class", "node-remove")
.on("click", onRemoveNode, true)
.append("i")
.attr("class", "fa fa-times");
// UPDATE - merges all transitions together?
// var nodeUpdate = node;
var nodeUpdate = nodeEnter.merge(node);
// Transition nodes to their new position.
nodeUpdate.transition()
.duration(duration)
.attr("transform", function (d) {
return "translate(" + d.y + "," + d.x + ")";
});
// Display node
nodeUpdate.select("circle.circle-for-nodes")
.attr("r", 4.5)
.style("fill", function (d) {
return d._children ? "lightsteelblue" : "#fff";
})
// Display text
nodeUpdate.select(".node-text")
.style("fill-opacity", function (d) {
console.log(d)
return 1;
})
// Transition exiting ndoes to the parent's new position.
var nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function (d) {
return "translate(" + source.y + "," + source.x + ")";
})
.remove();
// On exit reduce the node circles size to 0
nodeExit.select("circle")
.attr("r", 1e-6);
// On exit reduce the opacity of text labels
nodeExit.select("text")
.style("fill-opacity", 1e-6);
/********************* LINKS SECTION *********************/
// Update the links...
var link = vis.selectAll("path.link")
.data(links, function (d) { return d.id; });
// Enter any new links at the parent's previous position
var linkEnter = link.enter().insert("path", "g")
.attr("class", "link")
.attr("d", function (d) {
var o = { x: source.x0, y: source.y0 };
return diagonal(o, o);
})
// UPDATE - merges all transitions together?
var linkUpdate = linkEnter.merge(link);
// Transition back to the parent element position.
linkUpdate.transition()
.duration(duration)
.attr("d", function (d) {
return diagonal(d, d.parent)
});
// Remove exiting links
var linkExit = link.exit().transition()
.duration(duration)
.attr("d", function (d) {
var o = { x: source.x, y: source.y };
return diagonal(o, o);
})
.remove();
// Stash the old positions for transition.
nodes.forEach(function (d) {
d.x0 = d.x;
d.y0 = d.y;
});
// End of function update()
}
// Toggle children
function toggle(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
}
// zoom in / out
function zoom(d) {
//vis.attr("transform", "transl3ate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
var nodes = vis.selectAll("g.node");
nodes.attr("transform", transform);
// Update the links...
var link = vis.selectAll("path.link");
link.attr("d", translate);
// Enter any new links at hte parent's previous position
//link.attr("d", function(d) {
// var o = {x: d.x0, y: d.y0};
// return diagonal({source: o, target: o});
// });
}
function transform(d) {
return "translate(" + x(d.y) + "," + y(d.x) + ")";
}
function translate(d) {
var sourceX = x(d.target.parent.y);
var sourceY = y(d.target.parent.x);
var targetX = x(d.target.y);
var targetY = (sourceX + targetX) / 2;
var linkTargetY = y(d.target.x0);
var result = "M" + sourceX + "," + sourceY + " C" + targetX + "," + sourceY + " " + targetY + "," + y(d.target.x0) + " " + targetX + "," + linkTargetY + "";
return result;
}
function onEditNode(d) {
var length = 9;
var id = Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, length);
addChildNode(d.id, {
"name": "new child node",
"id": id,
"type": "type2"
});
stopPropogation();
}
function onAddNode(d) {
var length = 9;
var id = Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, length);
addChildNode(d.id, {
"name": "new child node",
"id": id,
"type": "type2"
});
stopPropogation();
}
function onRemoveNode(d) {
var index = d.parent.children.indexOf(d);
if (index > -1) {
d.parent.children.splice(index, 1);
}
update(d.parent);
stopPropogation();
}
function addChildNode(parentId, newNode) {
var node = d3.select('#' + 'node-' + parentId);
var nodeData = node.datum();
if (nodeData.children === undefined && nodeData._children === undefined) {
nodeData.children = [newNode];
} else if (nodeData._children != null) {
nodeData._children.push(newNode);
toggle(nodeData);
} else if (nodeData.children != null) {
nodeData.children.push(newNode);
}
update(node);
stopPropogation();
}
function toggleEnable(d) {
d.enable = !d.enable;
var node = d3.select('#' + 'node-' + d.id + " .node-label")
.classed("disabled", !d.enable);
stopPropogation();
}
function stopPropogation() {
d3.event.stopPropagation();
}
body {
height: 100vh;
width: 100vw;
margin: 0;
padding: 0;
}
.node circle {
cursor: pointer;
fill: #fff;
stroke: steelblue;
stroke-width: 1.5px;
}
.node-label {
font-size: 12px;
padding: 3px 5px;
display: inline-block;
word-wrap: break-word;
max-width: 160px;
background: #d0dee7;
border-radius: 5px;
}
.node a:hover {
cursor: pointer;
}
.node a {
font-size: 10px;
margin-left: 5px
}
a.node-remove {
color: red;
}
input+.node-text {
margin-left: 5px;
}
.node-label.node-type1 {
background: coral;
}
.node-label.node-type2 {
background: lightblue;
}
.node-label.node-type3 {
background: yellow;
}
.node-label.disabled {
background: #e9e9e9;
color: #838383
}
.node text {
font-size: 11px;
}
path.link {
fill: none;
stroke: #ccc;
stroke-width: 1.5px;
}
<!DOCTYPE html>
<meta charset="utf-8" />
<script src="https://d3js.org/d3.v5.js"></script>
<body>
<div id="root"></div>
</body>