In this part, we will see several options to create interactive plots in the notebook.
We import NumPy and matplotlib as usual. We also need to activate matplotlib's inline backend in the notebook.
In [1]:
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
Now, we import mpld3. If this fails, type in a terminal: pip install mpld3
In [2]:
import mpld3
mpld3.enable_notebook()
All we have to do now is create a normal matplotlib plot. It will show up directly in the notebook as an interactive figure.
In [3]:
N = 100
scatter = plt.scatter(np.random.normal(size=N),
np.random.normal(size=N),
c=np.random.random(size=N),
s=1000 * np.random.random(size=N),
alpha=0.3,
cmap=plt.cm.jet)
Click on the small arrow on the bottom left to pan and zoom in the figure.
d3.js is a popular visualization library in JavaScript. In principle, it has nothing to do with Python. Yet, we will see here how to mix Python and d3.js in the IPython notebook.
First, let's show three discs with SVG. To show SVG figures in the IPython notebook, we can use the %%SVG
cell magic.
SVG is a XML-based vector image format understood by all modern browsers.
In [4]:
%%SVG
<svg id="svg" height="60">
<circle cx="40" cy="30" r="10" />
<circle cx="80" cy="30" r="10" />
<circle cx="120" cy="30" r="10" />
</svg>
Now, let's show how to manipulate dynamically these objects with d3.js. This all happens client-side, in the browser. At this point, Python knows nothing about what we're doing here.
In [5]:
%%javascript
require.config({paths: {d3: "http://d3js.org/d3.v3.min"}});
require(["d3"], function(d3) {
// We select the #svg element.
var svg = d3.select("#svg");
// We select all circles...
var circle = svg.selectAll("circle")
// and we assign them some data.
.data(["red", "green", "blue"]);
// Now, we assign the "fill" attribute of each circle to
// the value associated to it, and the "r" attribute
// to some value depending on the data.
circle.transition().duration(1000).
attr("fill", function(d) { return d; }).
attr("r", function(d) { return 4*d.length; });
});
In d3.js, the central idea is to bind data values to visual objects. Here, we assign a string to each circle. Then, we can assign graphical attributes of the objects as a function of the data items associated to them.
Animations can be easily added. Here, we just need to add:
transition().duration(1000).
after circle.
.
Now, we will load a graph with the networkX package, display it with matplotlib first, and then d3.js.
In [6]:
import json
import networkx as nx
We will load an example graph, representing relations between members of a Karate club. Involved in a dispute, these members are split into two groups.
In [7]:
g = nx.karate_club_graph()
nx.draw(g)
Displaying this graph as a dynamic object in d3.js is not straightforward. We first need to export it in a language-independent representation (JSON). Then, we will load this JSON document and render the graph with d3.js.
Let's export the graph in an adequate format. NetworkX integrates a convenient function for this.
In [8]:
from networkx.readwrite import json_graph
In [9]:
json_graph.node_link_data(g)
Out[9]:
We export this dictionary to a JSON file, understandable by d3.js.
In [10]:
with open('graph.json', 'w') as f:
json.dump(_, f, indent=1)
Now, let's create the SVG element containing the graph, along with a couple of CSS styles for the nodes and edges. It is empty for now.
In [11]:
%%html
<svg id="graph" width="600" height="300"></svg>
<style>
.node {stroke: #fff; stroke-width: 1.5px;}
.link {stroke: #999; stroke-opacity: .6;}
</style>
The JavaScript code to load and display the graph is below.
In [12]:
%%javascript
require(["d3"], function(d3) {
// We select the SVG element.
var svg = d3.select("#graph");
// We create a color scale.
var color = d3.scale.category10();
// We create a force-directed dynamic graph layout.
var force = d3.layout.force()
.charge(-120)
.linkDistance(30)
.size([600, 300]);
// We load the JSON file.
d3.json("graph.json", function(error, graph) {
// We initialize the force field.
force.nodes(graph.nodes)
.links(graph.links)
.start();
// We create the links.
var link = svg.selectAll(".link")
.data(graph.links)
.enter().append("line") // adding a <line/> element for every graph link
.attr("class", "link");
// We create the nodes.
var node = svg.selectAll(".node")
.data(graph.nodes)
.enter().append("circle") // adding a <circle/> element for every graph node
.attr("class", "node")
.attr("r", 5) // radius
.style("fill", function(d) { // color depending on the person's group
return color(d.club);
})
.call(force.drag);
// Binding the force field to the graph.
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; });
});
});
});