1

I got the a D3 v4 forced graph in place. Usually I load data from an external JSON file but I simplified the code for easier jfiddle import. The graph adds an node each time a node is clicked. Further the node will be added to the data_node array. So far so good.

My problem is, I can´t figure out how to store the new data into an local JSON file. The API´s from node.js seems not to work or I am simply blind. As I want to store the changes about the nodes permanent.

Any hint?

<!DOCTYPE html>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<html>
    <head>
        <title>Index jFiddle</title>
        <!-- favcon -->
        <link rel="icon" href="https://networkrepository.com/favicon.png">
        <!-- call external d3.js framework -->
        <script src="https://d3js.org/d3.v4.js"></script>
        <!-- load "font awesome" stylesheet https://fontawesome.com/ -->
        <script src="https://kit.fontawesome.com/39094309d6.js" crossorigin="anonymous"></script>   
    </head>

    <style>
        body {
            overflow: hidden;
            margin: 0px;
            font-family: "Open Sans", sans-serif;
        }

        .canvas {
            background-color: rgb(220,220,220);
        }

        .link {
            stroke: rgb(0,0,0);
            stroke-width: 2px;
        }

        .node {
            stroke: rgb(255,255,255);
            stroke-width: 2px;    
        }

        .icon {
            fill: rgb(0,0,0);
            pointer-events: none;    
        }       

        .node:hover{
            cursor: pointer;
        }
    </style>

    <body>
         <!-- SVG area for the whole graph -->
        <svg id="svg"> </svg>

        <!-- call app.js where the application is written -->
        <script>
            
// define different variables
var width = window.innerWidth
    height = window.innerHeight
    boolColor = true
    boolOpacity = true
    color = null
    nodes = null
    
// define cavnas area to draw everything
var svg = d3.select("svg")
            .attr("class", "canvas")
            .attr("width", width)
            .attr("height", height)
            .call(d3.zoom().on("zoom", function() {
                svg.attr("transform", d3.event.transform)
            }))
            .append("g")

// Removes zoom on doubleclick listener
d3.select("svg").on("dblclick.zoom", null)

var simulation = d3.forceSimulation()
                .force("link", d3.forceLink().id(function(d) { return d.id; }).distance(100))
                .force("charge", d3.forceManyBody().strength(-400))
                .force("center", d3.forceCenter(width / 2, height / 2))
                .force("attraceForce",d3.forceManyBody().strength(70));

// load data from json file
var data_nodes = [
    {
        "id": "00000", 
        "type": "company", 
        "name": "Test", 
        "context": "",
        "icon": "\uf1ad"
    },
    {
        "id": "00100",
        "type": "software",
        "name": "Jira",
        "context":"Jira",
        "icon": "\uf7b1",
        "parent" : "00000"
    },
    {
        "id": "00200",
        "type": "software",
        "name": "Confluence",
        "context":"Confluence",
        "icon": "\uf78d",
        "parent" : "00000"
    },
    {
        "id": "00300",
        "type": "software",
        "name": "IVIS",
        "context":"IVIS",
        "icon": "\ue084",
        "parent" : "00000"
    },
    {
        "id": "00400",
        "type": "software",
        "name": "IPOS",
        "context":"IPOS",
        "icon": "\ue084",
        "parent" : "00000"
    },
    {
        "id": "00500",
        "type": "software",
        "name": "IDAS",
        "context":"IDAS",
        "icon": "\ue084",
        "parent" : "00000"
    },
    {
        "id": "99997",
        "type": "hardware",
        "name": "power-plug",
        "context": "power-plug",
        "icon": "\uf1e6",
        "parent" : "00000"
    },
    {
        "id": "99998",
        "type": "hardware",
        "name": "usv",
        "context": "usv",
        "icon": "\uf5df",
        "parent" : "00000"
    },
]


var data_links = [ 
   
    {"source": "99998", "target": "00000"},
    {"source": "99997", "target": "00000"},
    {"source": "00100", "target": "00000"},
    {"source": "00200", "target": "00000"},
    {"source": "00300", "target": "00000"},
    {"source": "00400", "target": "00000"},
    {"source": "00500", "target": "00000"},
    
]


    // create links which visualize relationships
    var links = svg.selectAll("svg")
                .data(data_links) 
                .enter()
                    .append("line")
                    .attr("class", "link")
                    .style("stroke-width", 3)
                    .style("stroke-linecap", "round")

    var nodes = svg.selectAll("svg")
                .data(data_nodes)
                .enter()
                    .append("circle")
                    .attr("r", 30)
                    .attr("class", "node")
                    .attr("fill", initialColor)
                    .call(d3.drag()
                        .on("start", dragStarted)
                        .on("drag", dragged)
                        .on("end", dragEnded)
                    )
                    .on("click", click)
    
       
    var icons = svg.selectAll("svg")
                .data(data_nodes)
                .enter()
                    //.append("g")
                    .append("text")
                    .attr("class", "icon")
                    .attr("text-anchor", "middle")
                    .attr("dominant-baseline", "central")
                    .style("font-family","FontAwesome")
                    .style("font-size","30px")
                    .text(function (d) {return d.icon;})
                    .call(d3.drag()
                        .on("start", dragStarted)
                        .on("drag", dragged)
                        .on("end", dragEnded)
                    )
                
    simulation.nodes(data_nodes).on("tick", ticked);
        
    simulation.force("link").links(data_links);

    

    function ticked() {
        // update link positions
        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; });
        
        // update node positions
        nodes
            .attr("cx", function(d) { return d.x; })
            .attr("cy", function(d) { return d.y; })
        
        // update icon positions
        icons
            .attr("x", function(d) {return d.x})
            .attr("y", function(d) {return d.y})

    }


    function click(d) {
        addNode(d)
    }

    function addNode(d) {
        var id = Math.round(Math.random()*10000);

        var newLink = {source: id, target: d.id } 
            data_links.push(newLink);

        var obj = {
            "id": id, 
            "type": "company", 
            "name": "Test1", 
            "context": "",
            "icon": "\uf1ad"
        }
        data_nodes.push(obj)
        // changes:
        
        var newLink = {source: id, target: d.id } 
          data_links.push(newLink);
          
          
          links = svg.selectAll("line")
          .data(data_links) 
          .enter()
          .append("line")
          .attr("class", "link")
          .style("stroke-width", 3)
          .style("stroke-linecap", "round")
          // disable browser context menu on link       
          .merge(links);

        //append the new object:
        nodes = svg.selectAll("circle")
             .data(data_nodes)
             .enter()
                .append("circle")
                .attr("r", 30)
                .attr("class", "node")
                .attr("fill", initialColor)
                .call(d3.drag()
                    .on("start", dragStarted)
                    .on("drag", dragged)
                    .on("end", dragEnded)
              )
              .on("click", click)      
              .merge(nodes);
        
         icons = svg.selectAll("text")
                .data(data_nodes)
                .enter()
                    .append("text")
                    .attr("class", "icon")
                    .attr("text-anchor", "middle")
                    .attr("dominant-baseline", "central")
                    .style("font-family","FontAwesome")
                    .style("font-size","30px")
                    .text(function (d) {return d.icon;})
                    .call(d3.drag()
                        .on("start", dragStarted)
                        .on("drag", dragged)
                        .on("end", dragEnded)
                    )
                    .merge(icons);
                    
          
          // re-initialize the simulation:
          simulation.nodes(data_nodes);
          simulation.force("link").links(data_links);

    }


/*
    Set the color of each node in dependency of their d.name attribute.
*/
function initialColor(d) {
    switch (d.name) {
        case "power-plug":
            return "lightgreen"
        case "usv":
            return "orange" 
        default:
            return "whitesmoke"
    }
}


// Context menu


/*
    dragStarted() / dragged() and dragEnded() controlling the drag behaviour of each
    object. In case all drag events are not desired, simple comment out the .call(d3.drag())
    execution during the object(node) creation
*/
function dragStarted(d) {
    if (!d3.event.active) simulation.alphaTarget(0.3).restart();
    d.fx = d.x;
    d.fy = d.y;
}
    
function dragged(d) {
    d.fx = d3.event.x;
    d.fy = d3.event.y;
}
    
function dragEnded(d) {
    if (!d3.event.active) simulation.alphaTarget(0);
        d.fx = null;
        d.fy = null;
}

        </script>
    </body>
</html>
ICoded
  • 319
  • 3
  • 18
  • Does this answer your question? [Save json string to client pc (using HTML5 API)](https://stackoverflow.com/questions/16329293/save-json-string-to-client-pc-using-html5-api) – Robin Mackenzie Feb 01 '21 at 08:47
  • You could either download and upload a file or use something like [localStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) or for larger amount of data [indexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API) – Reyno Feb 01 '21 at 08:49
  • @RobinMackenzie thanks for the link. I forgot to mention it should be done automatically. Without any user interaction. A new node is added and the local json file, which is in the same root folder, will ideally be updated in the background. So next time the app is started the new data is already there. – ICoded Feb 01 '21 at 09:22

0 Answers0