Dalek Dalek - 3 months ago 37
Javascript Question

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.

Answer Source

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/