Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Combining angularJS and d3.js: Refreshing a plot after submitting new input parameters

I would like that after I click on the submit button with new values inside input fields, my network d3.js plot would get updated based on the new graph generated by new input values. Here in the following, you can find my example code:

GenerateGraph.js This file contains a bunch of functions which generate a graph (randomGraph) based on the submitted input values. Then the graph is needed to get refreshed in the browser.

function degree(node,list){
  var deg=new Array();
  for (var i=0; i<node.length; i++){
    var count=0;
    for (var j=0; j<list.length; j++){
        if (node[i]==list[j][0] || node[i]==list[j][1]){
          count++;
        }
    }
    deg.push(count);
  }
  return deg;
}
function randomGraph (n, m) { //creates a random graph on n nodes and m links
  var graph={};
  var nodes = d3.range(n).map(Object),
      list  = randomChoose(unorderedPairs(d3.range(n)), m),
      links = list.map(function (a) { return {source: a[0], target: a[1]} });
  graph={
  Node:nodes,
  ListEdges:list,
  Links:links
  }
  return graph;
}

function randomChoose (s, k) { // returns a random k element subset of s
  var a = [], i = -1, j;
  while (++i < k) {
    j = Math.floor(Math.random() * s.length);
    a.push(s.splice(j, 1)[0]);
  };
  return a;
}

function unorderedPairs (s) { // returns the list of all unordered pairs from s
  var i = -1, a = [], j;
  while (++i < s.length) {
    j = i;
    while (++j < s.length) a.push([s[i],s[j]])
  };
  return a;
}

network.html

!DOCTYPE html>
<html>
<head>
     <meta charset="utf-8">     
     <meta name="viewport" content="width=device-width, initial-scale=1">
     <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Tangerine">
     <link rel="stylesheet" type="text/css" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
     <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.min.js"></script>
     <title>graph</title>

     <script src='http://d3js.org/d3.v3.min.js'></script>     
     <link rel="stylesheet" type="text/css" href="style.css">

</head>
<body ng-app="myApp">
     <script src="GenerateGraph.js" type="text/javascript"></script>
     <script src="svgGraph.js" type="text/javascript"></script>
     <h1 class="title">Simulating a network</h1>
     <div id="outer" ng-controller="MainCtrl" class="col-md-6">
        <network-inputs inputs="networkInputs" submit="submit(inputs)"></network-inputs>
     </div> 
    <!--test -->
     <script type="text/javascript">

        //get the input parameters for plotting
         angular.module("myApp", [])

         .directive('networkInputs', function() {

         return {
                restrict: 'E',                
                scope: {
                   inputs: '<',
                   submit: '&'
                },
                link : link,               
                template: 
              '<h3 >Initialise new parameters to generate a network </h3>'+
                    '<form ng-submit="submit({inputs: inputs})" class="form-inline">'+
                      '<div class="form-group">'+
                         '<label>Number of nodes</label>'+
                         '<input type="number" min="10" class="form-control" ng-model="inputs.N" ng-required="true">'+
                      '</div>'+
                      '<div class="form-group">'+
                         '<label>Number of links</label>'+
                          '<input type="number" min="0.1" class="form-control" ng-model="inputs.m" ng-required="true">'+
                      '</div>'+
                      '<button style="color:black; margin: 1rem 4rem;" type="submit">Generate</button>' +
                   '</form>'};
         })
         .factory("initialiseNetwork",function(){
            var data = {
                            N: 20,
                            m: 50,

                       };

            return {
                networkInputs:data
            };      

         })

         .controller("MainCtrl",  ['$scope','initialiseNetwork' ,function($scope,initialiseNetwork) {

               $scope.networkInputs={};
               $scope.mySVG=function(){
                         var graph=randomGraph($scope.networkInputs.N, $scope.networkInputs.m);

                };

               function init(){
                  $scope.networkInputs=initialiseNetwork.networkInputs;
                   //Run the function which generates the graph and plot it 

               }
               init();

               $scope.submit = function(inputs) {

                   var dataObject = {
                       N: inputs.N,
                       m: inputs.m
                   };
                   //lets simply log them but you can plot or smth other 
                   console.log($scope.networkInputs); 

               }

         }]);

    </script>
</body>

</html>

svgGraph.js

function link(scope,element, attrs){
    //SVG size
    var width = 1800,
    height = 1100;

    // We only need to specify the dimensions for this container.

    var vis = d3.select(element[0]).append('svg')
              .attr('width', width)
              .attr('height', height);
    var force = d3.layout.force()
                      .gravity(.05)
                      .distance(100)
                      .charge(-100)
                      .size([width, height]);
        // Extract the nodes and links from the data.
     scope.$watch('val',function(newVal,oldVal){
                   vis.selectAll('*').remove();
                   if (!newVal){
                       return;
                   }

        var Glinks = newVal.links;
        var W=degree(newVal.nodes,newVal.list);

        var Gnodes = [];
        var obj=newVal.nodes;
        Object.keys(obj).forEach(function(key) {
           Gnodes.push({"name":key, "count":W[key]});
        });

        //Creates the graph data structure 
        force.nodes(Gnodes)
             .links(Glinks)
             .linkDistance(function(d) { 
              return(0.1*Glinks.length); 
             })//link length
             .start();
         //Create all the line svgs but without locations yet
        var link = vis.selectAll(".link")
           .data(Glinks)
           .enter().append("line")
           .attr("class", "link")
           .style("stroke-width","0.3px");

        //Do the same with the circles for the nodes - no 
        var node = vis.selectAll(".node")
            .data(Gnodes)
            .enter().append("g")
            .attr("class", "node")
            .call(force.drag);
        node.append("circle")
             .attr("r", function(d){
              return d.count*0.5;   
            })
            .style("opacity", .3)
            .style("fill", "red");
        //add degree of node as text 
        node.append("text")
            .attr("text-anchor", "middle")
            .text(function(d) { return d.count })
            .attr("font-family",'Raleway',"Tangerine");
        //Now we are giving the SVGs co-ordinates - the force layout is generating the co-ordinates which this code is using to update the attributes of the SVG elements
        force.on("tick", function () {
             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;
                 });

              node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
        });      
    });
}

The link in the plunker is here.

like image 232
Dalek Avatar asked Jun 29 '17 14:06

Dalek


1 Answers

1 - In the directive, you watch val, which in controller is $scope.data, so I guess you need it for every submitted form? then just assign the data to $scope.data every submit:

$scope.submit = function(inputs) {
    var dataObject = {
      N: inputs.N,
      m: inputs.m
    };
    $scope.data = randomGraph(dataObject.N, dataObject.m);
}

2 - Then, in sgvGraph.js, in the scope.watch, you use var newVal.nodes and newVal.list anf newVal.link which are all undefined because you build your object with {Node:.., Links:..., ListEdges:...}

3 - Should add novalidate in form and manage the error manually, because I cant submit with the min="0.1" with chrome

This is the working plunkr: http://embed.plnkr.co/PbynuNCPM4Jv4lPmK8eW/

like image 89
Fetrarij Avatar answered Nov 05 '22 16:11

Fetrarij