I'm completely new to Flask and am trying to figure out how to display networkx graph data with d3js force layouts. Here is the relevant Python code:
@app.route("/")
def index():
"""
When you request the root path, you'll get the index.html template.
"""
return flask.render_template("index.html")
@app.route("/thread")
def get_graph_data(thread_id: int=3532967):
"""
returns json of a network graph for the specified thread
:param thread_id:
:return:
"""
pqdict, userdict = graphs.get_post_quote_dict(thread_id)
G = graphs.create_graph(pqdict)
s = graphs.graph_to_node_link(G, remove_singlets=True) # returns dict
return flask.jsonify(s)
And here is the index.html file:
<!DOCTYPE html>
<html>
<head>
<title>Index thing</title>
<script type="text/javascript" src="http://d3js.org/d3.v2.js"></script>
<link type="text/css" rel="stylesheet" href="templates/graph.css"/>
</head>
<body>
<div id="chart"></div>
<script>
var w = 1500,
h = 1500,
fill = d3.scale.category20();
var vis = d3.select("#chart")
.append("svg:svg")
.attr("width", w)
.attr("height", h);
d3.json("/thread", function (json) {
var force = d3.layout.force()
.charge(-120)
.linkDistance(30)
.nodes(json.nodes)
.links(json.links)
.size([w, h])
.start();
var link = vis.selectAll("line.link")
.data(json.links)
.enter().append("svg:line")
.attr("class", "link")
.style("stroke-width", function (d) {
return Math.sqrt(d.value);
})
.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;
});
var node = vis.selectAll("circle.node")
.data(json.nodes)
.enter().append("svg:circle")
.attr("class", "node")
.attr("cx", function (d) {
return d.x;
})
.attr("cy", function (d) {
return d.y;
})
.attr("r", 5)
.style("fill", function (d) {
return fill(d.group);
})
.call(force.drag);
vis.style("opacity", 1e-6)
.transition()
.duration(1000)
.style("opacity", 1);
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("cx", function (d) {
return d.x;
})
.attr("cy", function (d) {
return d.y;
});
});
});
</script>
</body>
</html>
So clearly the d3.json() function wants a location of a static JSON file, which in this case I'm trying to generate dynamically based on the request URL.
I've tried about a dozen approaches that I've found around here. Per the suggestion below, I tried:
@app.route("/")
def index():
"""
When you request the root path, you'll get the index.html template.
"""
return flask.render_template("index.html")
@app.route("/thread")
def get_graph_data():
"""
returns json of a network graph for the specified thread
:param thread_id:
:return:
"""
thread_id = request.args.get("thread_id", 3532967, type=int)
pqdict, userdict = graphs.get_post_quote_dict(thread_id)
G = graphs.create_graph(pqdict)
s = graphs.graph_to_node_link(G, remove_singlets=True)
return jsonify(s)
With the template index.html unchanged, and navigated to "http://localhost/thread?thread_id=12345" but this failed because it was printing the JSON for ID 12345 on the page instead of rendering the javascript.
So to sum up, my current goal is to specify a parameter in the Python method from the URL (".../showgraph?threadid=whatever..."), generate a json in the Python code, and pass it back to the html/js. How do I accomplish this?
You are really close!
First off, the statement: "clearly the d3.json() function wants a location of a static JSON file" is not correct.
d3.json() is part of the d3-request
library and, as such, is an XHR method (e.g. expects a URL, which could be a static JSON like data.json
, but not literal JSON data).
I would adjust your Flask route to accept a GET
parameter as the function will only accept a parameter via the URL:
@app.route("/thread")
def get_graph_data():
thread_id = request.args.get("thread_id", 3532967, type=int)
pqdict, userdict = graphs.get_post_quote_dict(thread_id)
G = graphs.create_graph(pqdict)
s = graphs.graph_to_node_link(G, remove_singlets=True) # returns dict
return flask.jsonify(s)
What I mean here is if you want to use the function parameter you'd need to do something like this:
@app.route("/thread/<int:thread_id>")
def get_graph_data(thread_id):
...
Then, adjust your XHR call a bit to send the GET parameter:
var url = "/thread?thread_id=" + id.toString();
d3.json(url, function (json) {
var force = d3.layout.force()
.charge(-120)
.linkDistance(30)
.nodes(json.nodes)
.links(json.links)
.size([w, h])
.start();
// a bunch more js that i copied and pasted from a tutorial
});
And it should be fine.
Also, FYI, if you ever want to use Jinja2 to "read" an object into a Javascript Object you need to use 2 filters: {{ data|tojson|safe }}
The solution was:
Python:
@app.route("/thread")
def get_graph_data():
"""
returns json of a network graph for the specified thread
:param thread_id:
:return:
"""
thread_id = request.args.get("thread_id", 3532967, type=int)
pqdict, userdict = graphs.get_post_quote_dict(thread_id)
G = graphs.create_graph(pqdict)
s = graphs.graph_to_node_link(G, remove_singlets=True)
return json.dumps(s)
@app.route("/showgraph")
def showgraph():
thread_id = request.args.get("thread_id", 3532967, type=int)
return render_template("index.html", threadid=thread_id)
HTML/Jinja2:
<!DOCTYPE html>
<html>
<head>
<title>Index thing</title>
<script type="text/javascript" src="http://d3js.org/d3.v2.js"></script>
<link type="text/css" rel="stylesheet" href="templates/graph.css"/>
</head>
<body>
<div id="chart"></div>
<script>
var w = 1500,
h = 1500,
fill = d3.scale.category20();
var vis = d3.select("#chart")
.append("svg:svg")
.attr("width", w)
.attr("height", h);
d3.json("/thread?thread_id={{ threadid }}", function (json) {
var force = d3.layout.force()
.charge(-120)
.linkDistance(30)
.nodes(json.nodes)
.links(json.links)
.size([w, h])
.start();
// irrelevant stuff
});
</script>
</body>
</html>
Needed separate methods to return the JSON and render the page. Still seems silly to have to parse the thread_id arg twice but whatever. Accepted PJ's answer since it was 99% of the issue!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With