mardi 18 mai 2021

logical mistake with existing interval condition

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