I am trying to create a small dependency simulation, with the help of the existing graph links. To do so I thought about different states and an active value which simulates an on/off switch. Each link contains a type, either "moderate" or "critical", to define the urgency of this connection.
A click on a node turns the clicked node off and sets active = false and state = "off".
States
- "on" - if active is true
- "off"- if active is false
- "limited" - if an existing link connection is "moderate"
- "error" - if an existing link connection is "critical"
To frequently prove the current states I created an interval function, as you can see in the code snippet and wrote several comments for easier understanding.
Problem: The dependency chain is working for critical connections but seems to have a logical error for limited connections.
Test Cases:
- Turn off node 6 -> which automatically set node 1 to limited -> which again set node 0 to error. If you turn node 6 on again, the simulated dependency issues disappear. CHECK, working.
- Turn off node 7 -> which automatically set node 1 to error -> which again set node 0 to error. If you turn node 7 on again, the simulated dependency issues disappear. CHECK, working.
- Turn off node 3 -> which automatically set node 2 to error .. now node 0 should be on limited. FAILED, not working.
- Turn off node 4 -> which should set node 2 to limited .. FAILED, not working.
If appreciate if anybody would point on my thinking issue.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>stackoverflow dependency demo</title>
<script src="https://d3js.org/d3.v6.min.js"></script>
</head>
<style>
body {
background-color: #e6e7ee;
}
circle {
stroke: black;
stroke-width: 1px;
cursor: pointer;
}
</style>
<body>
<script>
var svg = d3.select("body").append("svg")
.attr("width", window.innerWidth)
.attr("height", window.innerHeight)
.attr("class", "svg")
.call(d3.zoom().on("zoom", function (event) {
svg.attr("transform", event.transform)
}))
.append("g")
d3.select("svg").on("dblclick.zoom", null)
// append markers to svg
svg.append("defs").append("marker")
.attr("id", "arrowhead")
.attr("viewBox", "-0 -5 10 10")
.attr("refX", 6)
.attr("refY", 0)
.attr("orient", "auto")
.attr("markerWidth", 100)
.attr("markerHeight", 100)
.attr("xoverflow", "visible")
.append("svg:path")
.attr("d", "M 0,-1 L 2 ,0 L 0,1")
.attr("fill", "red")
.style("stroke", "none")
var linkContainer = svg.append("g").attr("class", "linkContainer")
var nodeContainer = svg.append("g").attr("class", "nodeContainer")
var graph = {
"nodes": [
{ "id": 0, "active": true, "state": "on" },
{ "id": 1, "active": true, "state": "on" },
{ "id": 2, "active": true, "state": "on" },
{ "id": 3, "active": true, "state": "on" },
{ "id": 4, "active": true, "state": "on" },
{ "id": 5, "active": true, "state": "on" },
{ "id": 6, "active": true, "state": "on" },
{ "id": 7, "active": true, "state": "on" },
],
"links": [
{ "source": 0, "target": 1, "type": "critical" },
{ "source": 0, "target": 2, "type": "moderate" },
{ "source": 0, "target": 3, "type": "critical" },
{ "source": 1, "target": 7, "type": "critical" },
{ "source": 1, "target": 6, "type": "moderate" },
{ "source": 2, "target": 4, "type": "moderate" },
{ "source": 2, "target": 5, "type": "critical" },
]
}
var forceLayout = d3.forceSimulation()
.force("link", d3.forceLink().id(function (d) {
return d.id;
}).distance(125))
.force("charge", d3.forceManyBody().strength(-1250))
.force("center", d3.forceCenter(window.innerWidth / 2, window.innerHeight / 2))
.force("collision", d3.forceCollide().radius(50))
//###############################################
//################## initialize #################
//###############################################
function init() {
links = linkContainer.selectAll(".link")
.data(graph.links)
.join("line")
.attr("class", "link")
.attr('marker-end', 'url(#arrowhead)')
.style("stroke", "black")
nodes = nodeContainer.selectAll(".node")
.data(graph.nodes, function (d) { return d.id; })
.join("g")
.attr("class", "node")
.attr("id", function (d) { return "node" + d.id; })
.call(d3.drag()
.on("start", dragStarted)
.on("drag", dragged)
.on("end", dragEnded)
)
nodes.selectAll("circle")
.data(d => [d])
.join("circle")
.attr("r", 40)
.style("fill", setColor)
.on("click", switchState)
nodes.selectAll("text")
.data(d => [d])
.join("text")
.attr("dominant-baseline", "central")
.attr("text-anchor", "middle")
.attr("id", function (d) { return "text" + d.id })
.attr("pointer-events", "none")
.text(function (d) {
return d.id + " - " + d.state
})
forceLayout
.nodes(graph.nodes)
.on("tick", outerTick)
forceLayout
.force("link")
.links(graph.links)
}
//###############################################
//##### set color in relation of the state ######
//###############################################
function setColor(d) {
switch (d.state) {
case "on":
return "greenyellow"
case "limited":
return "yellow"
case "error":
return "red"
case "off":
return "grey"
default:
return "greenyellow"
}
}
//###############################################
//######## switch state - turn off & on #########
//###############################################
function switchState(event, d) {
if (d.active == true) {
d.active = false
d.state = "off"
} else if (d.active == false) {
d.active = true
d.state = "on"
}
}
function outerTick() {
links
.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;
});
nodes.attr("transform", function (d) {
return "translate(" + d.x + "," + d.y + ")";
});
}
function dragStarted(event, d) {
if (!event.active) forceLayout.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) forceLayout.alphaTarget(0);
d.fx = undefined;
d.fy = undefined;
}
//###############################################
//########### dependency simulation ############
//###############################################
window.setInterval(function () {
for (var i = 0; i < graph.nodes.length; i++) {
/* isModerate is used as a proof boolean. To keep the current node on state limited,
in case more than one limited connections exist. Otherwise the current node switch
to on if an "non-affected" node is not faulty
*/
isModerate = false
if (graph.nodes[i].active == false) {
graph.nodes[i].state = "off"
} else if (graph.nodes[i].active == true) {
for (var z = 0; z < graph.links.length; z++) {
/*
if the current node.id exist as a source.id than continue checking. Further a
ciritcal connection will always break the loop as it cause the current node to
be on state error.
*/
if (graph.nodes[i].id === graph.links[z].source.id) {
if (graph.links[z].type === "critical") {
if (graph.nodes[graph.links[z].target.id].state === "on") {
graph.nodes[i].state = "on"
} else if (graph.nodes[graph.links[z].target.id].state === "error") {
graph.nodes[i].state = "error"
break
} else if (graph.nodes[graph.links[z].target.id].state === "limited") {
graph.nodes[i].state = "error"
break
} else if (graph.nodes[graph.links[z].target.id].active === false) {
graph.nodes[i].state = "error"
break
}
} else if (!isModerate && graph.links[z].type === "moderate") {
if (graph.nodes[graph.links[z].target.id].state === "on") {
graph.nodes[i].state = "on"
} else if (graph.nodes[graph.links[z].target.id].state === "error") {
graph.nodes[i].state = "limited"
isModerate = true
} else if (graph.nodes[graph.links[z].target.id].state === "limited") {
graph.nodes[i].state = "limited"
isModerate = true
} else if (graph.nodes[graph.links[z].target.id].active === false) {
graph.nodes[i].state = "limited"
isModerate = true
}
}
}
}
}
}
init()
}, 500)
</script>
</body>
</html>
Aucun commentaire:
Enregistrer un commentaire