Package by Jake Vanderplas (hey that's me!)
Reads the object structure of matplotlib figures and converts them to D3js
An extensible plugin system for adding new interactivity
Because it uses mpl, it is automatically compatible with PrettyPlotLib, ggplot, and Seaborn
In [1]:
#%run talktools
%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np
from IPython.display import HTML
# CSS formatting
from IPython.display import HTML
HTML("""
<style>
h1 {text-align:center; color:#111133; font-size: 220%;}
h2 {text-align:center; color:#111155; font-size: 150%;}
h3 {text-align:center; font-size: 140%;}
</style>
""")
Out[1]:
In [2]:
x, y = np.random.normal(0, 1, [2, 100])
c, s = 800 * np.random.random([2, 100])
fig = plt.figure()
points = plt.scatter(x, y, c=c, s=s, alpha=0.3)
plt.grid(color="lightgray");
In [3]:
import mpld3
mpld3.enable_notebook()
fig
Out[3]:
In [4]:
mpld3.fig_to_dict(fig)
Out[4]:
In [5]:
from mpld3 import plugins
labels = ['Point {0}'.format(i) for i in range(100)]
tooltips = plugins.PointLabelTooltip(points, labels)
plugins.connect(fig, tooltips)
fig
Out[5]:
In [6]:
from sklearn.datasets import load_iris
iris = load_iris()
# dither the data for clearer plotting
iris.data += 0.1 * np.random.random(iris.data.shape)
fig, ax = plt.subplots(4, 4, sharex="col", sharey="row", figsize=(8, 8))
fig.subplots_adjust(left=0.05, right=0.95, bottom=0.05, top=0.95,
hspace=0.1, wspace=0.1)
for i in range(4):
for j in range(4):
points = ax[3 - i, j].scatter(iris.data[:, j], iris.data[:, i],
c=iris.target, s=40, alpha=0.6)
# remove tick labels
for axi in ax.flat:
for axis in [axi.xaxis, axi.yaxis]:
axis.set_major_formatter(plt.NullFormatter())
# Here we connect the linked brush plugin
plugins.connect(fig, plugins.LinkedBrush(points))
In [7]:
from mpld3 import plugins, utils
class HighlightLines(plugins.PluginBase):
"""A plugin to highlight lines on hover"""
JAVASCRIPT = """
mpld3.register_plugin("linehighlight", LineHighlightPlugin);
LineHighlightPlugin.prototype = Object.create(mpld3.Plugin.prototype);
LineHighlightPlugin.prototype.constructor = LineHighlightPlugin;
LineHighlightPlugin.prototype.requiredProps = ["line_ids"];
LineHighlightPlugin.prototype.defaultProps = {alpha_bg:0.3, alpha_fg:1.0}
function LineHighlightPlugin(fig, props){
mpld3.Plugin.call(this, fig, props);
};
LineHighlightPlugin.prototype.draw = function(){
for(var i=0; i<this.props.line_ids.length; i++){
var obj = mpld3.get_element(this.props.line_ids[i]),
alpha_fg = this.props.alpha_fg;
alpha_bg = this.props.alpha_bg;
obj.elements()
.on("mouseover", function(d, i){
d3.select(this).transition().duration(50)
.style("stroke-opacity", alpha_fg); })
.on("mouseout", function(d, i){
d3.select(this).transition().duration(200)
.style("stroke-opacity", alpha_bg); });
}
};
"""
def __init__(self, lines):
self.lines = lines
self.dict_ = {"type": "linehighlight",
"line_ids": [utils.get_id(line) for line in lines],
"alpha_bg": lines[0].get_alpha(),
"alpha_fg": 1.0}
x = np.linspace(0, 10, 100)
y = 0.1 * (np.random.random((50, 100)) - 0.5)
y = y.cumsum(1)
fig, ax = plt.subplots(subplot_kw={'xticks': [], 'yticks': []})
lines = ax.plot(x, y.T, color='blue', lw=4, alpha=0.1)
plugins.connect(fig, HighlightLines(lines))
In [8]:
# from http://mpld3.github.io/examples/heart_path.html
import matplotlib as mpl
import matplotlib.path as mpath
import matplotlib.patches as mpatches
class LinkedDragPlugin(plugins.PluginBase):
JAVASCRIPT = r"""
mpld3.register_plugin("drag", DragPlugin);
DragPlugin.prototype = Object.create(mpld3.Plugin.prototype);
DragPlugin.prototype.constructor = DragPlugin;
DragPlugin.prototype.requiredProps = ["idpts", "idline", "idpatch"];
DragPlugin.prototype.defaultProps = {}
function DragPlugin(fig, props){
mpld3.Plugin.call(this, fig, props);
};
DragPlugin.prototype.draw = function(){
var patchobj = mpld3.get_element(this.props.idpatch, this.fig);
var ptsobj = mpld3.get_element(this.props.idpts, this.fig);
var lineobj = mpld3.get_element(this.props.idline, this.fig);
var drag = d3.behavior.drag()
.origin(function(d) { return {x:ptsobj.ax.x(d[0]),
y:ptsobj.ax.y(d[1])}; })
.on("dragstart", dragstarted)
.on("drag", dragged)
.on("dragend", dragended);
lineobj.path.attr("d", lineobj.datafunc(ptsobj.offsets));
patchobj.path.attr("d", patchobj.datafunc(ptsobj.offsets,
patchobj.pathcodes));
lineobj.data = ptsobj.offsets;
patchobj.data = ptsobj.offsets;
ptsobj.elements()
.data(ptsobj.offsets)
.style("cursor", "default")
.call(drag);
function dragstarted(d) {
d3.event.sourceEvent.stopPropagation();
d3.select(this).classed("dragging", true);
}
function dragged(d, i) {
d[0] = ptsobj.ax.x.invert(d3.event.x);
d[1] = ptsobj.ax.y.invert(d3.event.y);
d3.select(this)
.attr("transform", "translate(" + [d3.event.x,d3.event.y] + ")");
lineobj.path.attr("d", lineobj.datafunc(ptsobj.offsets));
patchobj.path.attr("d", patchobj.datafunc(ptsobj.offsets,
patchobj.pathcodes));
}
function dragended(d, i) {
d3.select(this).classed("dragging", false);
}
}
mpld3.register_plugin("drag", DragPlugin);
"""
def __init__(self, points, line, patch):
if isinstance(points, mpl.lines.Line2D):
suffix = "pts"
else:
suffix = None
self.dict_ = {"type": "drag",
"idpts": utils.get_id(points, suffix),
"idline": utils.get_id(line),
"idpatch": utils.get_id(patch)}
fig, ax = plt.subplots(figsize=(8, 6))
Path = mpath.Path
path_data = [
(Path.MOVETO, (1.58, -2.57)),
(Path.CURVE4, (0.35, -1.1)),
(Path.CURVE4, (-1.75, 2.0)),
(Path.CURVE4, (0.375, 2.0)),
(Path.LINETO, (0.85, 1.15)),
(Path.CURVE4, (2.2, 3.2)),
(Path.CURVE4, (3, 0.05)),
(Path.CURVE4, (2.0, -0.5)),
(Path.CLOSEPOLY, (1.58, -2.57)),
]
codes, verts = zip(*path_data)
path = mpath.Path(verts, codes)
patch = mpatches.PathPatch(path, facecolor='r', alpha=0.5)
ax.add_patch(patch)
# plot control points and connecting lines
x, y = zip(*path.vertices[:-1])
points = ax.plot(x, y, 'go', ms=10)
line = ax.plot(x, y, '-k')
ax.grid(True, color='gray', alpha=0.5)
ax.axis('equal')
ax.set_title("Drag Points to Change Path", fontsize=18)
plugins.connect(fig, LinkedDragPlugin(points[0], line[0], patch))