d3.js forcelayout not showing up with data from python on flask

Viewed 87

I was trying to retrieve nodes and links from a python code that already has them unique and in the form of a JSON but while the console.log shows up the data on the browser am unable to view them as a forcelayout graph on the screen. Presumably i dont understand the sequence of calling here in javascript so bear with my lack of javascript knowledge. Any help here is much appreciated.

This is a borrowed example from the gallery of course with me tampering the d3.json piece.

<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 !!'); 

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(); 
}); 



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 browser console output

  • as i said i am still unclear on the sequence here as this is my first ever javascript attempt!

Noticed that python and flask tags were removed - which i think is fine because i dont think there is an issue with the below python code that's sending this data. just FYI

@app.route("/getA")
def getA():
    print('get A gets called ...')
    db = get_db()    
    nodes = []
    rels = []
    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)

    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 serialize_ailment(ailment):
    return {
        "title":ailment["title"]
    }
def serialize_cure(cure):
    return {
        "title":cure["title"]
    }


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

Bear with some unwanted code, comments as i've basically tried out a few variants.

1 Answers

Two mistakes ... i figured ...

  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 -

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 + ")";
}); 

And the python code that "serves up" this data goes like this ...

@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")

Technicalily i haven't yet used up the unique nodes coming from the python function but that's for another day :) thanks for reading.

Related