简体   繁体   中英

Forced-Directed Graph and localstorage

I'm trying to store nodes positions after a drag and drop in localStorage, but when I reload the page, the links are where I saved them, nodes aswell but links and nodes are not linked so the nodes just go away from their initial positions.

Here my code, i'm using angular.

angular.module('core').controller('HomeController', ['$scope', 
function($scope) {
    $scope.graph = {
        width : 500,
        height : 400,
        color : d3.scale.category20(),
        force : '',
        drag : '',
        dragstart : function(d) {
            d.x = d3.event.x;
            d.y = d3.event.y;
        },
        dragend : function(d) {
            var graphTmp = { "nodes" : $scope.graph.node.data(), "links" : $scope.graph.link.data()};
            localStorage.setItem('graph',JSON.stringify(graphTmp));
        },
        link : [],
        node : [],
        links : [],
        nodes : []
    };
    $scope.svg = d3.select("body").append("svg")
            .attr("width", $scope.graph.width)
            .attr("height", $scope.graph.height);
    $scope.savedGraph = {};

    $scope.draw = function(){
        $scope.graph.force = d3.layout.force()
                .charge(-120)
                .linkDistance(30)
                .size([$scope.graph.width, $scope.graph.height]);

        $scope.graph.force
              .nodes($scope.graph.nodes)
              .links($scope.graph.links)
              .start();

        $scope.graph.link = $scope.svg.selectAll(".link")
            .data($scope.graph.links)
            .enter().append("line")
            .attr("class", "link")
            .style({'stroke' : 'gray', 'stroke-width' : '1px'});

        $scope.graph.drag = $scope.graph.force.drag()
            .on("dragstart", $scope.graph.dragstart)
            .on("dragend", $scope.graph.dragend);

        $scope.graph.node = $scope.svg.selectAll(".node")
            .data($scope.graph.nodes)
            .enter().append("circle")
            .attr("class", "node")
            .attr("r", 5)
            .style("fill", function(d) { return $scope.graph.color(d.group); })
            .call($scope.graph.drag);

        $scope.graph.node
            .append("title")
            .text(function(d) { return d.name; });

        $scope.graph.force.on("tick", function() {
            $scope.graph.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; });

            $scope.graph.node
                .attr("cx", function(d) { return d.x; })
                .attr("cy", function(d) { return d.y; });
        });
    };

    if(localStorage.getItem('graph') === null){

        $scope.graph.nodes = [
            {"name":"Myriel","group":1},
            {"name":"Napoleon","group":1},
            {"name":"Mlle.Baptistine","group":1},
            {"name":"Mme.Magloire","group":1},
            {"name":"CountessdeLo","group":1},
            {"name":"Geborand","group":1},
            {"name":"Champtercier","group":1},
            {"name":"Cravatte","group":1},
            {"name":"Count","group":1}
        ];

        $scope.graph.links = [
            {"source":1,"target":0,"value":1},
            {"source":2,"target":0,"value":8},
            {"source":3,"target":0,"value":10},
            {"source":3,"target":2,"value":6},
            {"source":4,"target":0,"value":1}
        ];

        $scope.draw();
    }
    else {
        var graphTmp = $.parseJSON(localStorage.getItem('graph'));
        $scope.graph.links = graphTmp.links;
        $scope.graph.nodes = graphTmp.nodes;
        $scope.draw();

    }

Does someone know why ? I think it's because nodes and links are not able to be linked together with only node.data() and link.data() datas. May I store more datas ?

Thanks !

The problem is that initially when you load the links(there is no data in localstorage) it has this structure:

 $scope.graph.links = [{
            "source": 1,
            "target": 0,
            "value": 1
        }, {
            "source": 2,
            "target": 0,
            "value": 8
        }, {
            "source": 3,
            "target": 0,
            "value": 10
        }, {
            "source": 3,
            "target": 2,
            "value": 6
        }, {
            "source": 4,
            "target": 0,
            "value": 1
        }];

Now the force layout will replace the source index with the source object from nodes.

When you drag you save the nodes and the links(the links object will hold node object for the source and target).

Now when you load from the local storage you parse the links, the links source/target will be having a different object from the nodes object(because its deep cloned).

Thus drag changes on node will not be reflected on the links source/target object.Hence the links go detached.

Solution for the problem is that you always store the indexe form of links in localstorage. I am doing this here by keeping another object initLinks

$scope.graph.nodes = [{
    "name": "Myriel",
    "group": 1
}, {
    "name": "Napoleon",
    "group": 1
}, {
    "name": "Mlle.Baptistine",
    "group": 1
}, {
    "name": "Mme.Magloire",
    "group": 1
}, {
    "name": "CountessdeLo",
    "group": 1
}, {
    "name": "Geborand",
    "group": 1
}, {
    "name": "Champtercier",
    "group": 1
}, {
    "name": "Cravatte",
    "group": 1
}, {
    "name": "Count",
    "group": 1
}];

$scope.graph.links = [{
    "source": 1,
    "target": 0,
    "value": 1
}, {
    "source": 2,
    "target": 0,
    "value": 8
}, {
    "source": 3,
    "target": 0,
    "value": 10
}, {
    "source": 3,
    "target": 2,
    "value": 6
}, {
    "source": 4,
    "target": 0,
    "value": 1
}];
$scope.graph.initLinks = angular.copy($scope.graph.links);

On saving to localstorage i do this

    var graphTmp = {
        "nodes": $scope.graph.nodes,
        "links": $scope.graph.initLinks
    };

    localStorage.setItem('graph', JSON.stringify(graphTmp));

While loading from localstorage i do this

var graphTmp = JSON.parse(localStorage.getItem('graph'));
$scope.graph.links =  graphTmp.links;
$scope.graph.initLinks = angular.copy(graphTmp.links);
$scope.graph.nodes = graphTmp.nodes;
$scope.draw();

Working code here

Hope this helps!

to expand on cyrils answer, if you have complex data in links. you might want to substitute the object reference itself with its index. all other data is preserved. Note that in my application I save link data separately in localstorage . Nodes are saved with another key.

store(key: string, data: any) {
if (key === 'links') {
  // replace all targets and source with indexes, we do not need to serialize the values.
  localStorage.setItem(key, JSON.stringify(data, (k, v) => {
    if (k === 'source' || k === 'target') {
      return v.index;
    } else {
      return v;
    }

  }));

} else {
  localStorage.setItem(key, JSON.stringify(data));
}
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM