我有兩組資料,一組用于上游,一組用于下游。上游和下游都有相同的 John 主節點。
上游資料
var upstreamData = [
{ name: "John", parent: "" },
{ name: "Ann", parent: "John" },
{ name: "Adam", parent: "John" },
{ name: "Chris", parent: "John" },
{ name: "Tina", parent: "Ann" },
{ name: "Sam", parent: "Ann" },
{ name: "Rock", parent: "Chris" },
{ name: "will", parent: "Chris" },
{ name: "Nathan", parent: "Adam" },
{ name: "Roger", parent: "Tina" },
{ name: "Dena", parent: "Tina" },
{ name: "Jim", parent: "Dena" },
{ name: "Liza", parent: "Nathan" }
];
下游資料
var downstreamData = [
{ name: "John", parent: "" },
{ name: "Kat", parent: "John" },
{ name: "Amily", parent: "John" },
{ name: "Summer", parent: "John" },
{ name: "Loki", parent: "Kat" },
{ name: "Liam", parent: "Kat" },
{ name: "Tom", parent: "Amily" }
];
我能夠使用 d3 層次結構和 d3 樹將上游資料表示到主節點的右側,下圖是

如何將下游資料表示到主節點 John 的左側,以便我可以在同一張圖中同時看到 john 的上游和下游資料?
以下是我的代碼框的鏈接

因此,將其翻轉,使下游樹在左側,上游樹在右側(并且根居中):
- 我們需要將上游節點的
y坐標(即它的x)減半,并將innerWidth. 對于根,它放在中心,但對于后代,它按比例將它們放在右側:
Array.from(nodesUpstream).forEach(n => n.y = (n.y * 0.5) innerWidth / 2);
然后,對下游節點y坐標(實際上是x......)進行相同的減半,但*-1它會“鏡像”它們,然后再添加innerWidth / 2回來。根仍將位于中心,但現在后代按比例位于左側并鏡像
Array.from(nodesDownstream).forEach(n => n.y = ((n.y * 0.5) * -1) innerWidth / 2);
請參閱下面的作業片段以及您的 OP 資料:
顯示代碼片段
const nodeRadius = 6;
const width = 600;
const height = 400;
const margin = { top: 24, right: 24, bottom: 24, left: 24 };
const innerWidth = width - margin.left - margin.right;
const innerHeight = height - margin.top - margin.bottom;
const svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`);
const rootName = "John";
const treeLayout = d3.tree().size([innerHeight, innerWidth]);
const stratified = d3.stratify()
.id(function (d) { return d.name; })
.parentId(function (d) { return d.parent; });
const linkPathGenerator = d3.linkHorizontal()
.x((d) => d.y)
.y((d) => d.x);
// create 2x trees
const nodesUpstream = treeLayout(d3.hierarchy(stratified(upstreamData)).data);
const nodesDownstream = treeLayout(d3.hierarchy(stratified(downstreamData)).data);
// align the root node x and y
const nodesUpRoot = Array.from(nodesUpstream).find(n => n.data.name == rootName);
const nodesDownRoot = Array.from(nodesDownstream).find(n => n.data.name == rootName);
nodesDownRoot.x = nodesUpRoot.x;
nodesDownRoot.y = nodesUpRoot.y;
// NOTE - COMMENT OUT THIS STEP TO SEE THE INTEMEDIARY STEP
// for horizontal layout, flip x and y...
// right hand side (upstream): halve and add width / 2 to all y's (which are for x)
Array.from(nodesUpstream).forEach(n => n.y = (n.y / 2) innerWidth / 2);
// left hand side (downstream): halve and negate all y's (which are for x) and add width / 2
Array.from(nodesDownstream).forEach(n => n.y = ((n.y / 2) * -1) innerWidth / 2);
// render both trees
// index allows left hand and right hand side to separately selected and styled
[nodesUpstream, nodesDownstream].forEach(function(nodes, index) {
// adds the links between the nodes
// need to select links based on index to prevent bad rendering
svg.selectAll(`links-${index}`)
.data(nodes.links())
.enter()
.append("path")
.attr("class", `link links-${index}`)
.attr("d", linkPathGenerator);
// adds each node as a group
// need to select nodes based on index to prevent bad rendering
var nodes = svg.selectAll(`.nodes-${index}`)
.data(nodes.descendants())
.enter()
.append("g")
.attr("class", `node nodes-${index}`)
.attr("transform", function(d) {
// x and y flipped here to achieve horizontal placement
return `translate(${d.y},${d.x})`;
});
// adds the circle to the node
nodes.append("circle")
.attr("r", nodeRadius);
// adds the text to the node
nodes.append("text")
.attr("dy", ".35em")
.attr("y", -20)
.style("text-anchor", "middle")
.text(function(d) { return d.data.name; });
});
body {
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
margin: 0;
overflow: hidden;
}
/* upstream */
path.links-0 {
fill: none;
stroke: #ff0000;
}
/* downstream */
path.links-1 {
fill: none;
stroke: #00ff00;
}
text {
text-shadow: -1px -1px 3px white, -1px 1px 3px white, 1px -1px 3px white,
1px 1px 3px white;
pointer-events: none;
font-family: "Playfair Display", serif;
}
circle {
fill: blue;
}
<link href="https://fonts.googleapis.com/css?family=Playfair Display" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.3.0/d3.min.js"></script>
<script>
// Upstream data
var upstreamData = [
{ name: "John", parent: "" },
{ name: "Ann", parent: "John" },
{ name: "Adam", parent: "John" },
{ name: "Chris", parent: "John" },
{ name: "Tina", parent: "Ann" },
{ name: "Sam", parent: "Ann" },
{ name: "Rock", parent: "Chris" },
{ name: "will", parent: "Chris" },
{ name: "Nathan", parent: "Adam" },
{ name: "Roger", parent: "Tina" },
{ name: "Dena", parent: "Tina" },
{ name: "Jim", parent: "Dena" },
{ name: "Liza", parent: "Nathan" }
];
// Downstream data
var downstreamData = [
{ name: "John", parent: "" },
{ name: "Kat", parent: "John" },
{ name: "Amily", parent: "John" },
{ name: "Summer", parent: "John" },
{ name: "Loki", parent: "Kat" },
{ name: "Liam", parent: "Kat" },
{ name: "Tom", parent: "Amily" }
];
</script>
結果是:

y有兩個限制:根被繪制了兩次(我猜你可以跳過其中一個的標簽 John),更重要的是,重新布置坐標時不考慮樹的深度。如果你有一個更深的上游樹,你會看到這一點,因為它仍然會布置在右手邊,并且更加“皺縮”。
編輯
要修復節點寬度(根據深度),您可以使用以下命令:
const depthFactor = 60;
Array.from(nodesUpstream).forEach(n => n.y = (n.depth * depthFactor) innerWidth / 2);
Array.from(nodesDownstream).forEach(n => n.y = (innerWidth / 2) - (n.depth * depthFactor));
例子:
顯示代碼片段
const nodeRadius = 6;
const width = 600;
const height = 400;
const margin = { top: 24, right: 24, bottom: 24, left: 24 };
const innerWidth = width - margin.left - margin.right;
const innerHeight = height - margin.top - margin.bottom;
const svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`);
const rootName = "John";
const treeLayout = d3.tree().size([innerHeight, innerWidth]);
const stratified = d3.stratify()
.id(function (d) { return d.name; })
.parentId(function (d) { return d.parent; });
const linkPathGenerator = d3.linkHorizontal()
.x((d) => d.y)
.y((d) => d.x);
// create 2x trees
const nodesUpstream = treeLayout(d3.hierarchy(stratified(upstreamData)).data);
const nodesDownstream = treeLayout(d3.hierarchy(stratified(downstreamData)).data);
// align the root node x and y
const nodesUpRoot = Array.from(nodesUpstream).find(n => n.data.name == rootName);
const nodesDownRoot = Array.from(nodesDownstream).find(n => n.data.name == rootName);
nodesDownRoot.x = nodesUpRoot.x;
nodesDownRoot.y = nodesUpRoot.y;
// for horizontal layout, flip x and y...
const depthFactor = 60;
Array.from(nodesUpstream).forEach(n => n.y = (n.depth * depthFactor) innerWidth / 2);
Array.from(nodesDownstream).forEach(n => n.y = (innerWidth / 2) - (n.depth * depthFactor));
// render both trees
// index allows left hand and right hand side to separately selected and styled
[nodesUpstream, nodesDownstream].forEach(function(nodes, index) {
// adds the links between the nodes
// need to select links based on index to prevent bad rendering
svg.selectAll(`links-${index}`)
.data(nodes.links())
.enter()
.append("path")
.attr("class", `link links-${index}`)
.attr("d", linkPathGenerator);
// adds each node as a group
// need to select nodes based on index to prevent bad rendering
var nodes = svg.selectAll(`.nodes-${index}`)
.data(nodes.descendants())
.enter()
.append("g")
.attr("class", `node nodes-${index}`)
.attr("transform", function(d) {
// x and y flipped here to achieve horizontal placement
return `translate(${d.y},${d.x})`;
});
// adds the circle to the node
nodes.append("circle")
.attr("r", nodeRadius);
// adds the text to the node
nodes.append("text")
.attr("dy", ".35em")
.attr("y", -20)
.style("text-anchor", "middle")
.text(function(d) { return d.data.name; });
});
body {
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
margin: 0;
overflow: hidden;
}
/* upstream */
path.links-0 {
fill: none;
stroke: #ff0000;
}
/* downstream */
path.links-1 {
fill: none;
stroke: #00ff00;
}
text {
text-shadow: -1px -1px 3px white, -1px 1px 3px white, 1px -1px 3px white,
1px 1px 3px white;
pointer-events: none;
font-family: "Playfair Display", serif;
}
circle {
fill: blue;
}
<link href="https://fonts.googleapis.com/css?family=Playfair Display" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.3.0/d3.min.js"></script>
<script>
// Upstream data
var upstreamData = [
{ name: "John", parent: "" },
{ name: "Ann", parent: "John" },
{ name: "Adam", parent: "John" },
{ name: "Chris", parent: "John" },
{ name: "Tina", parent: "Ann" },
{ name: "Sam", parent: "Ann" },
{ name: "Rock", parent: "Chris" },
{ name: "will", parent: "Chris" },
{ name: "Nathan", parent: "Adam" },
{ name: "Roger", parent: "Tina" },
{ name: "Dena", parent: "Tina" },
{ name: "Jim", parent: "Dena" },
{ name: "Liza", parent: "Nathan" }
];
// Downstream data
var downstreamData = [
{ name: "John", parent: "" },
{ name: "Kat", parent: "John" },
{ name: "Amily", parent: "John" },
{ name: "Summer", parent: "John" },
{ name: "Loki", parent: "Kat" },
{ name: "Liam", parent: "Kat" },
{ name: "Tom", parent: "Amily" }
];
</script>
這使:

轉載請註明出處,本文鏈接:https://www.uj5u.com/net/462632.html
標籤:javascript d3.js
