Interactive data visualization in the notebook

In this part, we will see several options to create interactive plots in the notebook.

Making matplotlib figures interactive in the notebook

The mpld3 library allows you to make your matplotlib figures interactive in the notebook. It leverages the JavaScript visualization library d3.js by converting a matplotlib figure into an abstract representation that is understood by d3.js.

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.

A crash course on d3.js

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..

Showing a networkX graph with d3.js

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


Couldn't import dot_parser, loading of dot files will not be possible.

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]:
{'directed': False,
 'graph': [('name', "Zachary's Karate Club")],
 'links': [{'source': 0, 'target': 1},
  {'source': 0, 'target': 2},
  {'source': 0, 'target': 3},
  {'source': 0, 'target': 4},
  {'source': 0, 'target': 5},
  {'source': 0, 'target': 6},
  {'source': 0, 'target': 7},
  {'source': 0, 'target': 8},
  {'source': 0, 'target': 10},
  {'source': 0, 'target': 11},
  {'source': 0, 'target': 12},
  {'source': 0, 'target': 13},
  {'source': 0, 'target': 17},
  {'source': 0, 'target': 19},
  {'source': 0, 'target': 21},
  {'source': 0, 'target': 31},
  {'source': 1, 'target': 2},
  {'source': 1, 'target': 3},
  {'source': 1, 'target': 7},
  {'source': 1, 'target': 13},
  {'source': 1, 'target': 17},
  {'source': 1, 'target': 19},
  {'source': 1, 'target': 21},
  {'source': 1, 'target': 30},
  {'source': 2, 'target': 3},
  {'source': 2, 'target': 32},
  {'source': 2, 'target': 7},
  {'source': 2, 'target': 8},
  {'source': 2, 'target': 9},
  {'source': 2, 'target': 13},
  {'source': 2, 'target': 27},
  {'source': 2, 'target': 28},
  {'source': 3, 'target': 7},
  {'source': 3, 'target': 12},
  {'source': 3, 'target': 13},
  {'source': 4, 'target': 10},
  {'source': 4, 'target': 6},
  {'source': 5, 'target': 16},
  {'source': 5, 'target': 10},
  {'source': 5, 'target': 6},
  {'source': 6, 'target': 16},
  {'source': 8, 'target': 32},
  {'source': 8, 'target': 30},
  {'source': 8, 'target': 33},
  {'source': 9, 'target': 33},
  {'source': 13, 'target': 33},
  {'source': 14, 'target': 32},
  {'source': 14, 'target': 33},
  {'source': 15, 'target': 32},
  {'source': 15, 'target': 33},
  {'source': 18, 'target': 32},
  {'source': 18, 'target': 33},
  {'source': 19, 'target': 33},
  {'source': 20, 'target': 32},
  {'source': 20, 'target': 33},
  {'source': 22, 'target': 32},
  {'source': 22, 'target': 33},
  {'source': 23, 'target': 32},
  {'source': 23, 'target': 25},
  {'source': 23, 'target': 27},
  {'source': 23, 'target': 29},
  {'source': 23, 'target': 33},
  {'source': 24, 'target': 25},
  {'source': 24, 'target': 27},
  {'source': 24, 'target': 31},
  {'source': 25, 'target': 31},
  {'source': 26, 'target': 33},
  {'source': 26, 'target': 29},
  {'source': 27, 'target': 33},
  {'source': 28, 'target': 33},
  {'source': 28, 'target': 31},
  {'source': 29, 'target': 32},
  {'source': 29, 'target': 33},
  {'source': 30, 'target': 33},
  {'source': 30, 'target': 32},
  {'source': 31, 'target': 33},
  {'source': 31, 'target': 32},
  {'source': 32, 'target': 33}],
 'multigraph': False,
 'nodes': [{'club': 'Mr. Hi', 'id': 0},
  {'club': 'Mr. Hi', 'id': 1},
  {'club': 'Mr. Hi', 'id': 2},
  {'club': 'Mr. Hi', 'id': 3},
  {'club': 'Mr. Hi', 'id': 4},
  {'club': 'Mr. Hi', 'id': 5},
  {'club': 'Mr. Hi', 'id': 6},
  {'club': 'Mr. Hi', 'id': 7},
  {'club': 'Mr. Hi', 'id': 8},
  {'club': 'Officer', 'id': 9},
  {'club': 'Mr. Hi', 'id': 10},
  {'club': 'Mr. Hi', 'id': 11},
  {'club': 'Mr. Hi', 'id': 12},
  {'club': 'Mr. Hi', 'id': 13},
  {'club': 'Officer', 'id': 14},
  {'club': 'Officer', 'id': 15},
  {'club': 'Mr. Hi', 'id': 16},
  {'club': 'Mr. Hi', 'id': 17},
  {'club': 'Officer', 'id': 18},
  {'club': 'Mr. Hi', 'id': 19},
  {'club': 'Officer', 'id': 20},
  {'club': 'Mr. Hi', 'id': 21},
  {'club': 'Officer', 'id': 22},
  {'club': 'Officer', 'id': 23},
  {'club': 'Officer', 'id': 24},
  {'club': 'Officer', 'id': 25},
  {'club': 'Officer', 'id': 26},
  {'club': 'Officer', 'id': 27},
  {'club': 'Officer', 'id': 28},
  {'club': 'Officer', 'id': 29},
  {'club': 'Officer', 'id': 30},
  {'club': 'Officer', 'id': 31},
  {'club': 'Officer', 'id': 32},
  {'club': 'Officer', 'id': 33}]}

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

});