【发布时间】:2021-03-04 13:11:21
【问题描述】:
我创建了一个 D3v6 图,我想在其中更改节点之间的关系。我为链接添加了一个上下文菜单,并填充了另一个 JSON 作为选择的选项。我遇到了两个问题:
- 编辑菜单没有显示正确的关系,我想我需要解析或比较这些值。
- 关系的更改不会更新,而是删除现有值,并且 console.log 返回“select-one”。这也坚持一个解析错误。
所以基本上我要做的是,比较来自两个 JSON 的特定值并将它们返回到 HTML 选择,该选择从这些 JSON 之一中获得填充选项。
感谢任何帮助链接或提示。
<!DOCTYPE html>
<html>
<head>
<!-- charset -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>D3v6 v0.2.1</title>
<!-- favcon -->
<link rel="icon" href="https://networkrepository.com/favicon.png">
<!-- call external d3.js framework -->
<script src="https://d3js.org/d3.v6.js"></script>
<!-- import multiselection framework -->
<script src="https://d3js.org/d3-selection-multi.v1.js"></script>
<!-- import "font awesome" stylesheet https://fontawesome.com/ -->
<script src="https://kit.fontawesome.com/39094309d6.js" crossorigin="anonymous"></script>
</head>
<style>
body {
overflow: hidden;
margin: 0px;
}
.canvas {
background-color: rgb(220, 220, 220);
}
.link {
cursor: pointer;
stroke: rgb(0, 0, 0);
display: none;
stroke-width: 3px;
opacity: 30%;
}
.link:hover {
opacity: 100%;
}
.linkPath {
pointer-events: none;
display: none;
}
.node {
stroke: white;
stroke-width: 2px;
cursor: pointer;
}
.icon {
pointer-events: none;
}
#context-menu-link {
font-family: "Open Sans", sans-serif;
position: fixed;
z-index: 10000;
width: 190px;
background: whitesmoke;
border: 2px;
border-radius: 6px;
border-color: white;
border-style: solid;
transform: scale(0);
transform-origin: top left;
}
#context-menu-link.active {
transform: scale(1);
transition: transform 200ms ease-in-out;
}
#context-menu-link .item {
padding: 8px 10px;
font-size: 15px;
color: black;
}
#context-menu-link .item i {
display: inline-block;
margin-right: 5px;
}
#context-menu-link hr {
margin: 5px 0px;
border-color: whitesmoke;
}
#context-menu-link .item:hover {
background: lightblue;
}
#details-link {
font-family: "Open Sans", sans-serif;
box-Shadow: 0px 0px 10px #888888;
position: absolute;
z-index: 10000;
width: 300px;
height: 150px;
background: whitesmoke;
border: 2px;
border-radius: 6px;
border-color: white;
border-style: solid;
transform: scale(0);
transform-origin: top left;
}
#details-link-h3 {
padding-left: 60px;
margin: 10 10 10 10;
cursor: move;
}
#details-link-text {
padding-left: 50px;
margin: 10 10 10 10;
}
#details-link-draggable {
cursor: move;
}
#details-link.active {
transform: scale(1);
transition: transform 200ms ease-in-out;
}
</style>
<body>
<!-- right click context menu link-->
<div id="context-menu-link">
<div id="context-menu-link-details" class="item">
<i class="fa fa-edit"></i> Relationship-Details
</div>
</div>
<!-- editWindow to change link data-->
<div id="details-link">
<div id="details-link-draggable">
<h3 class="fa fa-edit" id="details-link-h3"> Relationship-Details</h3>
</div>
<hr>
<div id="details-link-text" class="item">
<label id="details-link-source">Source --- </label>
<select id="details-link-type-selection" class="item"></select>
<label id="details-link-target"> --- Target</label>
</div>
<input type="button" id="confirmLinkButton" value="OK"></button>
<input type="button" id="abortLinkButton" value="Cancel"></button>
</div>
<!-- create svg root element as a canvas -->
<svg id="svg"></svg>
<!-- call script where the magic is written -->
<script>
var graph = {
"nodes": [{
"id": 1,
"name": "House",
"icon": "\uf1ad",
},
{
"id": 2,
"name": "Cube",
"icon": "\uf7b1",
},
{
"id": 3,
"name": "Rack",
"icon": "\uf233",
},
],
"links": [{
"source": 1,
"target": 2,
"type": "using"
},
{
"source": 2,
"target": 3,
"type": "need"
},
{
"source": 3,
"target": 1,
"type": "using"
},
]
}
var linkTypes = {
"1": "using",
"2": "need"
}
// declare initial variables
var svg = d3.select("svg")
width = window.innerWidth
height = window.innerHeight
// define cavnas area to draw everything
svg = d3.select("svg")
.attr("class", "canvas")
.attr("width", width)
.attr("height", height)
.call(d3.zoom().on("zoom", function(event) {
svg.attr("transform", event.transform)
}))
.append("g")
// remove zoom on dblclick listener
d3.select("svg").on("dblclick.zoom", null)
// append markers to svg
svg.append('defs').append('marker')
.attrs({
'id': 'arrowhead',
'viewBox': '-0 -5 10 10',
'refX': 6,
'refY': 0,
'orient': 'auto',
'markerWidth': 30,
'markerHeight': 30,
'xoverflow': 'visible'
})
.append('svg:path')
.attr('d', 'M 0,-1 L 2 ,0 L 0,1')
.attr('fill', 'black')
.style('stroke', 'none');
var linksContainer = svg.append("g").attr("class", "linksContainer")
var nodesContainer = svg.append("g").attr("class", "nodesContainer")
// iniital force simulation
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) {
return d.id;
}).distance(200))
.force("charge", d3.forceManyBody().strength(-650))
.force("center", d3.forceCenter(width / 2, height / 2))
.force("collision", d3.forceCollide().radius(50))
link = linksContainer.selectAll(".link")
.data(graph.links)
.enter()
.append("line")
.attr("class", "link")
.attr('marker-end', 'url(#arrowhead)')
.style("display", "block")
.on("contextmenu", contextMenuLink)
linkPaths = linksContainer.selectAll(".linkPath")
.data(graph.links)
.enter()
.append('path')
.style("pointer-events", "none")
.attrs({
'class': 'linkPath',
'fill-opacity': 1,
'stroke-opacity': 1,
'id': function(d, i) {
return 'linkPath' + i
}
})
.style("display", "block")
linkLabels = linksContainer.selectAll(".linkLabel")
.data(graph.links)
.enter()
.append('text')
.style("pointer-events", "none")
.attrs({
'class': 'linkLabel',
'id': function(d, i) {
return 'linkLabel' + i
},
'font-size': 16,
'fill': 'black'
})
.style("display", "block")
linkLabels.append('textPath')
.attr('xlink:href', function(d, i) {
return '#linkPath' + i
})
.style("text-anchor", "middle")
.style("pointer-events", "none")
.attr("startOffset", "50%")
.text(function(d) {
return d.type
})
node = nodesContainer.selectAll(".node")
.data(graph.nodes, d => d.id)
.enter()
.append("g")
.attr("class", "node")
.call(d3.drag()
.on("start", dragStarted)
.on("drag", dragged)
.on("end", dragEnded)
)
node.append("circle")
.attr("r", 40)
.style("fill", "whitesmoke")
node.append("text")
.style("class", "icon")
.attr("font-family", "FontAwesome")
.attr("dominant-baseline", "central")
.attr("text-anchor", "middle")
.attr("font-size", 40)
.attr("fill", "black")
.attr("stroke-width", "0px")
.attr("pointer-events", "none")
.text(function(d) {
return d.icon
})
simulation
.nodes(graph.nodes)
.on("tick", ticked);
simulation
.force("link")
.links(graph.links)
populateSelection()
function populateSelection() {
var fillTypeSelection = ""
for (var key in linkTypes) {
fillTypeSelection += "<option value=" + key + ">" + linkTypes[key] + "</option>"
}
document.getElementById("details-link-type-selection").innerHTML = fillTypeSelection;
}
function contextMenuLink(event, d) {
thisElement = d
event.preventDefault()
document.getElementById("context-menu-link").classList.remove("active")
var contextMenuLink = document.getElementById("context-menu-link")
contextMenuLink.style.top = event.clientY + "px"
contextMenuLink.style.left = event.clientX + "px"
contextMenuLink.classList.add("active")
window.addEventListener("click", function() {
contextMenuLink.classList.remove("active")
})
document.getElementById("context-menu-link-details").addEventListener("click", detailsLinkClicked)
}
function detailsLinkClicked() {
detailsLink(thisElement)
}
function detailsLink(d) {
var editLink = document.getElementById("details-link")
editLink.style.left = (window.innerWidth / 2) - 150 + "px"
editLink.style.top = (window.innerHeight / 2) - 75 + "px"
editLink.classList.add("active")
document.getElementById("details-link-source").innerHTML = d.source.name + " --- "
document.getElementById("details-link-target").innerHTML = " --- " + d.target.name
document.getElementById("details-link-type-selection").type = d.type
document.getElementById("confirmLinkButton").addEventListener("click", confirmLinkDetailsClicked)
document.getElementById("abortLinkButton").addEventListener("click", closeLinkDetailsMenu)
}
function confirmLinkDetailsClicked() {
confirmLinkDetails(thisElement)
}
function confirmLinkDetails(d) {
d.type = document.getElementById("details-link-type-selection").type
document.getElementById("details-link").classList.remove("active")
}
function closeLinkDetailsMenu() {
document.getElementById("details-link").classList.remove("active")
}
function ticked() {
// update link positions
link
.attr("x1", function(d) {
return d.source.x;
})
.attr("y1", function(d) {
return d.source.y;
})
.attr("x2", function(d) {
return d.target.x;
})
.attr("y2", function(d) {
return d.target.y;
});
// update node positions
node
.attr("transform", function(d) {
return "translate(" + d.x + ", " + d.y + ")";
});
linkPaths.attr('d', function(d) {
return 'M ' + d.source.x + ' ' + d.source.y + ' L ' + d.target.x + ' ' + d.target.y;
});
linkLabels.attr('transform', function(d) {
if (d.target.x < d.source.x) {
var bbox = this.getBBox();
rx = bbox.x + bbox.width / 2;
ry = bbox.y + bbox.height / 2;
return 'rotate(180 ' + rx + ' ' + ry + ')';
} else {
return 'rotate(0)';
}
});
}
function dragStarted(event, d) {
if (!event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(event, d) {
d.fx = event.x;
d.fy = event.y;
}
function dragEnded(event, d) {
if (!event.active) simulation.alphaTarget(0);
d.fx = undefined;
d.fy = undefined;
}
// make edit-menu draggable
dragElement(document.getElementById("details-link"))
function dragElement(elmnt) {
var pos1 = 0,
pos2 = 0,
pos3 = 0,
pos4 = 0;
if (document.getElementById(elmnt.id + "-draggable")) {
// if present, the header is where you move the DIV from:
document.getElementById(elmnt.id + "-draggable").onmousedown = dragMouseDown;
} else {
// otherwise, move the DIV from anywhere inside the DIV:
elmnt.onmousedown = dragMouseDown;
}
function dragMouseDown(e) {
e = e || window.event;
e.preventDefault();
// get the mouse cursor position at startup:
pos3 = e.clientX;
pos4 = e.clientY;
document.onmouseup = closeDragElement;
// call a function whenever the cursor moves:
document.onmousemove = elementDrag;
}
function elementDrag(e) {
e = e || window.event;
e.preventDefault();
// calculate the new cursor position:
pos1 = pos3 - e.clientX;
pos2 = pos4 - e.clientY;
pos3 = e.clientX;
pos4 = e.clientY;
// set the element's new position:
elmnt.style.top = (elmnt.offsetTop - pos2) + "px";
elmnt.style.left = (elmnt.offsetLeft - pos1) + "px";
}
function closeDragElement() {
// stop moving when mouse button is released:
document.onmouseup = null;
document.onmousemove = null;
}
}
</script>
</body>
</html>
【问题讨论】:
标签: javascript html d3.js