cancel
Showing results for 
Search instead for 
Did you mean: 

d3.js forcelayout challenges with retrieving json object from python on flask

Propel
Node Link

Hello there ... I tried to mirror the python bolt example with my own graph data - to render it on a forcelayout so that the output looks similar to what we see on the neo4j console. https://github.com/neo4j-examples/movies-python-bolt is where i got started. The challenge i ran into was - while trying to replicate an observable graph using d3.js - forcelayout in particular. Am new to both javascript so i wasn't even clear about the sequence in which the calls happen and hence the console log messages as well. Any help is much appreciated. Thanks. 

<script>
var width = 960,
    height = 500;
	
//force = d3.layout.force().nodes(d3.values(ailments)).links(rels).size([width, height]).linkDistance(60).charge(-300).on("tick", tick).start();

var nodes = {};
var ailments = {} ; 
var rels = []; 
var force = d3.layout.force().size([width, height]).linkDistance(60).charge(-300).on("tick",tick); 
console.log('initiated force!! be with you !!'); 
var kinks = [] ; 

d3.json("/getA", function(error, dataset){
	console.log('getA the function gets called now ... '); 
	ailments = dataset.nodes; 
	//ailments = dataset.nodes['nodes']; 
	//rels = dataset.links['links'] ; 
	rels = dataset.links; 
	console.log(ailments); 
	//console.log(nodes);
	console.log('relationships coming up..'); 
	console.log(rels); 
	force.start(); 
}); 

// http://blog.thomsonreuters.com/index.php/mobile-patent-suits-graphic-of-the-day/
var links = [
  {source: "Microsoft", target: "Amazon", type: "licensing"},
  {source: "Microsoft", target: "HTC", type: "licensing"},
  {source: "Samsung", target: "Apple", type: "suit"},
  {source: "Motorola", target: "Apple", type: "suit"},
  {source: "Nokia", target: "Apple", type: "resolved"},
  {source: "Nokia", target: "Qualcomm", type: "suit"}
];

// Compute the distinct nodes from the links.
links.forEach(function(link) {
  link.source = nodes[link.source] || (nodes[link.source] = {name: link.source});
  link.target = nodes[link.target] || (nodes[link.target] = {name: link.target});
  //console.log('this pain gets called now'); 
});


function tick() {
  path.attr("d", linkArc);
  circle.attr("transform", transform);
  text.attr("transform", transform);
}

force = d3.layout.force().nodes(d3.values(ailments)).links(rels).size([width, height]).linkDistance(60).charge(-300).on("tick", tick).start();
console.log("now i threw the graph out there"); 


var svg = d3.select("body").append("svg")
    .attr("width", width)
    .attr("height", height);
svg.append("defs").selectAll("marker")
    .data(["suit", "licensing", "resolved"])
  .enter().append("marker")
    .attr("id", function(d) { return d; })
    .attr("viewBox", "0 -5 10 10")
    .attr("refX", 15)
    .attr("refY", -1.5)
    .attr("markerWidth", 6)
    .attr("markerHeight", 6)
    .attr("orient", "auto")
	.attr("stroke", "black")
  .append("path")
    .attr("d", "M0,-5L10,0L0,5");
	

// Per-type markers, as they don't inherit styles.


// Use elliptical arc path segments to doubly-encode directionality.
var path = svg.append("g").selectAll("path")
    .data(force.links())
  .enter().append("path")
    .attr("class", function(d) { return "link " + d.type; })
    .attr("marker-end", function(d) { return "url(#" + d.type + ")"; });
var circle = svg.append("g").selectAll("circle")
    .data(force.nodes())
  .enter().append("circle")
    .attr("r", 6)
    .call(force.drag);

var text = svg.append("g").selectAll("text")
    .data(force.nodes())
  .enter().append("text")
    .attr("x", 8)
    .attr("y", ".31em")
    .text(function(d) { return d.name; });	
	
function linkArc(d) {
  var dx = d.target.x - d.source.x,
      dy = d.target.y - d.source.y,
      dr = Math.sqrt(dx * dx + dy * dy);
  return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y;
}

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

</script>

The python function that makes this /getA possible - i've already filtered to unique nodes and the links. 

@app.route("/getA")
def getA():
    print('get A gets called ...')
    db = get_db()    
    nodes = []
    rels = []
    ailment = []
    cure = []
    jlinks = {"links":[]}
    uniqueNodes = {"nodes":[]}
    #results = db.read_transaction(work, request.args.get("limit", 100))
    #results = db.read_transaction(work,request.args.get("limit", 5))
    sql    = "MATCH (a:Ailment) -[:SOLVED_BY]->(theCURE) return a as ailment, theCURE limit 5"
    with db as graphDB_Session:
        nodes = graphDB_Session.run(sql)
        print("output:")
        for node in nodes:
            #print(node)
            prepare_links(node, ailment, cure, uniqueNodes)

    for idx, value in enumerate(ailment):
        #print (ailment[idx], cure[idx])
        source = ailment[idx]['title']
        target = cure[idx]['title']
        y = {"source":source, "target":target, "type":"SOLVED_BY"}
        jlinks["links"].append(y)
        if (source not in (uniqueNodes["nodes"])):
            uniqueNodes["nodes"].append(source)
        if (target not in (uniqueNodes["nodes"])):
            uniqueNodes["nodes"].append(target)

    print(jlinks)
    print(uniqueNodes)
    
    return Response(dumps({"nodes": uniqueNodes, "links": jlinks}),mimetype="application/json")

def prepare_links(node, ailment, cure):
    ailment.append(node.value("ailment"))
    cure.append(node.value("theCURE"))

If you observe the console log on the browser does show up the json values coming through but as i said i dont think am doing it right with respect to when this data needs to get loaded and populated and refreshed. So, more a javascript question here. I do have this query on stackoverflow as well so bear with the cross post. 

console log output on the browserconsole log output on the browser

1 ACCEPTED SOLUTION

Propel
Node Link

Two mistakes ... 

  1. The sequence of calling - as i mentioned above had to be fixed by rolling the forcelayout calls from within the .json function

  2. The approach to getting an array from the API call into another array object here in Javascript was also messed up. Specifically this line -

    rels = dataset.links["links"];

looks very obvious but somehow i'd messed this up as well. The updated script that works on my own data finally goes like this -

var svg = d3.select("body").append("svg")
    .attr("width", width)
    .attr("height", height);
svg.append("defs").selectAll("marker")
    .data(["suit", "licensing", "resolved"])
  .enter().append("marker")
    .attr("id", function(d) { return d; })
    .attr("viewBox", "0 -5 10 10")
    .attr("refX", 15)
    .attr("refY", -1.5)
    .attr("markerWidth", 6)
    .attr("markerHeight", 6)
    .attr("orient", "auto")
    .attr("stroke", "black")
  .append("path")
    .attr("d", "M0,-5L10,0L0,5");   

var force = d3.layout.force().nodes(d3.values(nodes)).links(rels).size([width, height]).linkDistance(60).charge(-300).on("tick", tick).start();
console.log('graph goes up only now'); 

force.on("tick", function(e) {
  path.attr("d", linkArc);
  circle.attr("transform", transform);
  text.attr("transform", transform);
}); 
var text = svg.append("g").selectAll("text")
    .data(force.nodes())
  .enter().append("text")
    .attr("x", 8)
    .attr("y", ".31em")
    .text(function(d) { return d.name; });

var path = svg.append("g").selectAll("path")
    .data(force.links())
  .enter().append("path")
    .attr("class", function(d) { return "link " + d.type; })
    .attr("marker-end", function(d) { return "url(#" + d.type + ")"; });
var circle = svg.append("g").selectAll("circle")
    .data(force.nodes())
  .enter().append("circle")
    .attr("r", 6)
    .call(force.drag);
circle.attr("transform", function(d) {
    return "translate(" + d.x + "," + d.y + ")";
}); 

And the python code that had to be cleaned up to serve this data - 

@app.route("/getA")
def getA():
    print('get A gets called ...')
    db = get_db()
    
    nodes = []
    ailment = []
    cure = []
    jlinks = {"links":[]}
    uniqueNodes = {"nodes":[]}
    sql    = "MATCH (a:Ailment) -[:SOLVED_BY]->(theCURE) return a as ailment, theCURE limit 5"
    with db as graphDB_Session:
        nodes = graphDB_Session.run(sql)
        print("output:")
        for node in nodes:
            #print(node)
            prepare_links(node, ailment, cure)

    counter = 0 
    for idx, value in enumerate(ailment):
        source = ailment[idx]['title']
        target = cure[idx]['title']
        y = {"source":str(source), "target":str(target), "type":"SOLVED_BY"}
        jlinks["links"].append(y)
        
        if source not in uniqueNodes["nodes"]:
            uniqueNodes["nodes"].append(source)
        if target not in uniqueNodes["nodes"]: 
            uniqueNodes["nodes"].append(target)
            
    print(jlinks)    
    print(uniqueNodes)
    
    return Response(dumps({"nodes": uniqueNodes, "links": jlinks}),mimetype="application/json")

thanks for reading. Hopefully it helps others that want to extend the code examples to their own UI. 

View solution in original post

2 REPLIES 2

Propel
Node Link

Two mistakes ... 

  1. The sequence of calling - as i mentioned above had to be fixed by rolling the forcelayout calls from within the .json function

  2. The approach to getting an array from the API call into another array object here in Javascript was also messed up. Specifically this line -

    rels = dataset.links["links"];

looks very obvious but somehow i'd messed this up as well. The updated script that works on my own data finally goes like this -

var svg = d3.select("body").append("svg")
    .attr("width", width)
    .attr("height", height);
svg.append("defs").selectAll("marker")
    .data(["suit", "licensing", "resolved"])
  .enter().append("marker")
    .attr("id", function(d) { return d; })
    .attr("viewBox", "0 -5 10 10")
    .attr("refX", 15)
    .attr("refY", -1.5)
    .attr("markerWidth", 6)
    .attr("markerHeight", 6)
    .attr("orient", "auto")
    .attr("stroke", "black")
  .append("path")
    .attr("d", "M0,-5L10,0L0,5");   

var force = d3.layout.force().nodes(d3.values(nodes)).links(rels).size([width, height]).linkDistance(60).charge(-300).on("tick", tick).start();
console.log('graph goes up only now'); 

force.on("tick", function(e) {
  path.attr("d", linkArc);
  circle.attr("transform", transform);
  text.attr("transform", transform);
}); 
var text = svg.append("g").selectAll("text")
    .data(force.nodes())
  .enter().append("text")
    .attr("x", 8)
    .attr("y", ".31em")
    .text(function(d) { return d.name; });

var path = svg.append("g").selectAll("path")
    .data(force.links())
  .enter().append("path")
    .attr("class", function(d) { return "link " + d.type; })
    .attr("marker-end", function(d) { return "url(#" + d.type + ")"; });
var circle = svg.append("g").selectAll("circle")
    .data(force.nodes())
  .enter().append("circle")
    .attr("r", 6)
    .call(force.drag);
circle.attr("transform", function(d) {
    return "translate(" + d.x + "," + d.y + ")";
}); 

And the python code that had to be cleaned up to serve this data - 

@app.route("/getA")
def getA():
    print('get A gets called ...')
    db = get_db()
    
    nodes = []
    ailment = []
    cure = []
    jlinks = {"links":[]}
    uniqueNodes = {"nodes":[]}
    sql    = "MATCH (a:Ailment) -[:SOLVED_BY]->(theCURE) return a as ailment, theCURE limit 5"
    with db as graphDB_Session:
        nodes = graphDB_Session.run(sql)
        print("output:")
        for node in nodes:
            #print(node)
            prepare_links(node, ailment, cure)

    counter = 0 
    for idx, value in enumerate(ailment):
        source = ailment[idx]['title']
        target = cure[idx]['title']
        y = {"source":str(source), "target":str(target), "type":"SOLVED_BY"}
        jlinks["links"].append(y)
        
        if source not in uniqueNodes["nodes"]:
            uniqueNodes["nodes"].append(source)
        if target not in uniqueNodes["nodes"]: 
            uniqueNodes["nodes"].append(target)
            
    print(jlinks)    
    print(uniqueNodes)
    
    return Response(dumps({"nodes": uniqueNodes, "links": jlinks}),mimetype="application/json")

thanks for reading. Hopefully it helps others that want to extend the code examples to their own UI. 

Propel
Node Link

The javascript didnt get pasted right ... sorry about that. 

 

d3.json("/getA", function(error, dataset){
	console.log('getA the function gets called now ... '); 
	ailments = dataset.nodes; 
	rels = dataset.links["links"]; 
	console.log(ailments); 
	//console.log(nodes);
	console.log('relationships coming up..'); 
	console.log(rels); 
	//links = [dataset.links]; 
	
	rels.forEach(function(dink) {
		dink.source = nodes[dink.source] || (nodes[dink.source] = {name: dink.source});
		dink.target = nodes[dink.target] || (nodes[dink.target] = {name: dink.target});
		console.log('creating unique nodes gets called again'); 
	});
	console.log('relationships again ..'); 
	console.log(rels); 

	var svg = d3.select("body").append("svg")
		.attr("width", width)
		.attr("height", height);
	svg.append("defs").selectAll("marker")
		.data(["suit", "licensing", "resolved"])
	  .enter().append("marker")
		.attr("id", function(d) { return d; })
		.attr("viewBox", "0 -5 10 10")
		.attr("refX", 15)
		.attr("refY", -1.5)
		.attr("markerWidth", 6)
		.attr("markerHeight", 6)
		.attr("orient", "auto")
		.attr("stroke", "black")
	  .append("path")
		.attr("d", "M0,-5L10,0L0,5");	
	
	var force = d3.layout.force().nodes(d3.values(nodes)).links(rels).size([width, height]).linkDistance(60).charge(-300).on("tick", tick).start();
	console.log('graph goes up only now'); 
	
	force.on("tick", function(e) {
	  path.attr("d", linkArc);
	  circle.attr("transform", transform);
	  text.attr("transform", transform);
	}); 
	var text = svg.append("g").selectAll("text")
		.data(force.nodes())
	  .enter().append("text")
		.attr("x", 8)
		.attr("y", ".31em")
		.text(function(d) { return d.name; });

	var path = svg.append("g").selectAll("path")
		.data(force.links())
	  .enter().append("path")
		.attr("class", function(d) { return "link " + d.type; })
		.attr("marker-end", function(d) { return "url(#" + d.type + ")"; });
	var circle = svg.append("g").selectAll("circle")
		.data(force.nodes())
	  .enter().append("circle")
		.attr("r", 6)
		.call(force.drag);
	circle.attr("transform", function(d) {
		return "translate(" + d.x + "," + d.y + ")";
	}); 
	
});