Reputation: 11
Note: My first StackOverFlow post, please bear with me.
Aim of my code:
Take a connected graph as an input in a user friendly way ( I mean that user should be able to add a node on say, a double click. And when the user wants to connect two nodes, he/she may just select two nodes and there should be a way for the user to input distance between the two selected nodes).
After successfully taking the graph as input, I want to perform various operations on the graph like telling what is the shortest path from node ‘A’ to node ‘B’, for example.
Approach:
I have decided to use D3.js for the purpose. I am a newbie in JavaScript/jQuery but still I learnt D3.js from https://www.dashingd3js.com/table-of-contents . I have seen people create amazingly beautiful projects involving graph using D3.js but I am kind of struggling even in the basics.
Attempt:
I wrote the following code to connect two nodes:
<!DOCTYPE html>
<html>
<head>
<title>D3 Try</title>
<script type="text/javascript" src="d3.min.js"></script>
</head>
<style>
.node {
fill: #ccc;
stroke: #fff;
stroke-width: 2px;
}
.link {
stroke: #777;
stroke-width: 2px;
}
</style>
<body style="background-color: red">
<h2>Hello world</h2>
<script type="text/javascript">
var height=500,width=960;
// If we leave out the x and y coordinates of the visualization, d3 selects
// random positions for the nodes
// nodes are arbitrary objects
var nodes = [ {x: width/3, y: height/2 } , {x: 2*width/3, y: height/2} ];
// Define the array which contains information about the links
var links= [ {source: 0, target: 1}];
// container to hold the visualisation
var svg=d3.select('body').append('svg').attr('width',width).attr('height',height);
// Create force layout object which contains dimensions of the container and
// the arrays of nodes and links
var force=d3.layout.force().size([width,height]).nodes(nodes).links(links);
// Documentation says that define a function in place of width/2 but fir bhi , how?
force.linkDistance(width/2);
var link=svg.selectAll('.link').data(links).enter().append('line').attr('class','link');
var node=svg.selectAll('.node').data(nodes).enter().append('circle').attr('class','node');
force.on('end',function(){
node.attr('r', width/25)
.attr('cx', function(d) { return d.x; })
.attr('cy', function(d) { return d.y; });
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; });
});
force.start();
</script>
</body>
</html>
Problems I faced and what I really want to ask:
The above code connects two nodes, okay great, but how do I dynamically create such nodes on user's double click ? It may here be noted that not only a node has to be drawn on SVG, but the nodes
and links
arrays are to be updated too. How do I update those hard-coded arrays dynamically so that they store the most recent information in them?
The distance between the two nodes must be entered by the user, here it is a constant width/2
. I checked out the API on github, they say that define a function instead of a constant.I searched but couldn't find any example of that. Even if I use the D3 supplied variable d
, its of no use since it has to be user dependent.
Any help? Thank You.
Upvotes: 1
Views: 4179
Reputation: 7687
To do this, you can rely on the magic of the d3 .enter()
method. This is a data that operates on a selection, and returns a placeholder for every piece of data assigned to the selection that doesn't currently map to an SVG element in the DOM.
What this means is that if you modify the data, you can then append that data to a selection and have any changes in the data represented by changes in the selection. If you want to add a node to your data, it would work like this:
//Modify data
nodes.push({x : d3.mouse(svg)[0], y : d3.mouse(svg)[1]});
//Modify data of node selection
node.data(nodes);
//Operate on new nodes
node.enter()
.append('circle')
.attr('class','node')
.attr('r', width/25)
.attr('cx', function(d) { return d.x; })
.attr('cy', function(d) { return d.y; });
.call(force.end);
Since your first two data points already have DOM nodes assigned to them from when you first created them, this will only append a new circle
element for the new data point.
All of this would be called from within an event handler for mousedown
or click
, and would take user input in the form of mouse position.
Upvotes: 1