Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
{
"metadata": {
"name": ""
},
"nbformat": 3,
"nbformat_minor": 0,
"worksheets": [
{
"cells": [
{
"cell_type": "code",
"collapsed": false,
"input": [
"!date"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"Thu Jan 30 21:27:42 PST 2014\r\n"
]
}
],
"prompt_number": 1
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"%matplotlib inline\n",
"import matplotlib.pyplot as plt, mpld3, jinja2\n",
"from mpld3 import plugins"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 15
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"mpld3.enable_notebook(\"//cdnjs.cloudflare.com/ajax/libs/d3/3.4.1/d3.min.js\")"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 16
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"PointHTMLTooltips are currently implemented by adding a div to the top-level DOM element, and then using absolute positioning:\n",
"\n",
" FIG_JS = jinja2.Template(\"\"\"\n",
" var tooltip = d3.select(\"body\").append(\"div\")\n",
" .attr(\"class\", \"mpld3-tooltip\")\n",
" .style(\"position\", \"absolute\")\n",
" .style(\"z-index\", \"10\")\n",
" .style(\"visibility\", \"hidden\");\n",
"\n",
" var labels = {{ labels }};\n",
"\n",
"\n",
" ax{{ axid }}.axes.selectAll(\".{{ pointclass }}{{ elid }}\")\n",
" .on(\"mouseover\", function(d, i){\n",
" tooltip\n",
" .html(labels[i])\n",
" .style(\"visibility\", \"visible\");})\n",
" .on(\"mousemove\", function(d, i){\n",
" tooltip\n",
" .style(\"top\", (event.pageY+{{ voffset }})+\"px\")\n",
" .style(\"left\",(event.pageX+{{ hoffset }})+\"px\");})\n",
" .on(\"mouseout\", function(d, i){\n",
" tooltip\n",
" .style(\"visibility\", \"hidden\");});\n",
" \"\"\")\n"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"fig, ax = plt.subplots()\n",
"points = ax.plot(range(10), 'o', color='grey', ms=20, mew=5)\n",
"labels = ['<h1>Point #{title}</h1>'.format(title=i) for i in range(10)]\n",
"plugins.connect(fig, plugins.PointHTMLTooltip(points[0], labels, 25, -25))\n",
"plugins.connect(fig, plugins.ResetButton())"
],
"language": "python",
"metadata": {},
"outputs": [
{
"html": [
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
" <style>\n",
"\n",
"\n",
" div#figure9d4bbc51570b4c1d93713cab554bc55d\n",
" .axesbg{\n",
" fill: #FFFFFF;\n",
" }\n",
"\n",
"\n",
"\n",
"div#figure9d4bbc51570b4c1d93713cab554bc55d\n",
".axis line, .axis path {\n",
" shape-rendering: crispEdges;\n",
" stroke: black;\n",
" fill: none;\n",
"}\n",
"\n",
"div#figure9d4bbc51570b4c1d93713cab554bc55d\n",
".axis text {\n",
" font-family: sans-serif;\n",
" font-size: 10.0px;\n",
" fill: black;\n",
" stroke: none;\n",
"}\n",
"\n",
"\n",
"\n",
"div#figure9d4bbc51570b4c1d93713cab554bc55d\n",
".axis line, .axis path {\n",
" shape-rendering: crispEdges;\n",
" stroke: black;\n",
" fill: none;\n",
"}\n",
"\n",
"div#figure9d4bbc51570b4c1d93713cab554bc55d\n",
".axis text {\n",
" font-family: sans-serif;\n",
" font-size: 10.0px;\n",
" fill: black;\n",
" stroke: none;\n",
"}\n",
"\n",
"\n",
"\n",
"div#figure9d4bbc51570b4c1d93713cab554bc55d\n",
".axes1\n",
"path.line3 {\n",
" stroke: #808080;\n",
" stroke-width: 1.0;\n",
" stroke-dasharray: none;\n",
" fill: none;\n",
" stroke-opacity: 1;\n",
"}\n",
"\n",
"div#figure9d4bbc51570b4c1d93713cab554bc55d\n",
".axes1\n",
"path.points3 {\n",
" stroke-width: 5;\n",
" stroke: #000000;\n",
" fill: #808080;\n",
" fill-opacity: 1;\n",
" stroke-opacity: 1;\n",
"}\n",
"\n",
"\n",
"\n",
"div#figure9d4bbc51570b4c1d93713cab554bc55d\n",
"text.textff8957acd8ae48eca4865785eed73102 {\n",
" font-size : 10.0px;\n",
" fill : #000000;\n",
" opacity : 1;\n",
"}\n",
"\n",
"\n",
"\n",
"div#figure9d4bbc51570b4c1d93713cab554bc55d\n",
"text.texte7233033a26e476abc1d41659d1b5af6 {\n",
" font-size : 10.0px;\n",
" fill : #000000;\n",
" opacity : 1;\n",
"}\n",
"\n",
"\n",
"\n",
"div#figure9d4bbc51570b4c1d93713cab554bc55d\n",
"text.textec7258429fb44659a3b471aad63fde71 {\n",
" font-size : 12.0px;\n",
" fill : #000000;\n",
" opacity : 1;\n",
"}\n",
"\n",
"\n",
"\n",
"\n",
"\n",
" </style>\n",
"\n",
"\n",
"\n",
" <div id='figure9d4bbc51570b4c1d93713cab554bc55d'>\n",
" </div>\n",
" <script type=\"text/javascript\">\n",
" var create_fig9d4bbc51570b4c1d93713cab554bc55d = function(d3, undefined){\n",
"\n",
"\n",
" function Figure(figid, width, height){\n",
" this.figid = figid;\n",
" this.root = d3.select(figid);\n",
" this.width = width;\n",
" this.height = height;\n",
" this.axes = [];\n",
" }\n",
"\n",
" Figure.prototype.draw = function(){\n",
" this.canvas = this.root.append('svg:svg')\n",
" .attr('class', 'figure')\n",
" .attr('width', this.width)\n",
" .attr('height', this.height);\n",
" for (var i=0; i<this.axes.length; i++){\n",
" this.axes[i].draw();\n",
" }\n",
" };\n",
"\n",
" Figure.prototype.reset = function(duration){\n",
" duration = (typeof duration !== 'undefined') ? duration : 750;\n",
" for (var i=0; i<this.axes.length; i++){\n",
" this.axes[i].prep_reset();\n",
" }\n",
"\n",
" var transition = function(t){\n",
" for (var i=0; i<this.axes.length; i++){\n",
" this.axes[i].xdom(this.axes[i].xdom.domain(this.axes[i].ix(t)));\n",
" this.axes[i].ydom(this.axes[i].ydom.domain(this.axes[i].iy(t)));\n",
"\n",
" // don't propagate: this will be done as part of the loop.\n",
" this.axes[i].zoomed(false);\n",
" }\n",
" }.bind(this)\n",
"\n",
" d3.transition().duration(duration)\n",
" .tween(\"zoom\", function(){return transition;});\n",
"\n",
" for (var i=0; i<this.axes.length; i++){\n",
" this.axes[i].finalize_reset();\n",
" }\n",
" };\n",
"\n",
"\n",
"\n",
" function Axes(fig, bbox,\n",
" xlim, ylim,\n",
" xscale, yscale,\n",
" xdomain, ydomain,\n",
" xgridOn, ygridOn,\n",
" axclass, clipid,\n",
" zoomable){\n",
" this.axnum = fig.axes.length;\n",
" fig.axes.push(this);\n",
"\n",
" this.fig = fig;\n",
" this.bbox = bbox;\n",
" this.xlim = xlim;\n",
" this.ylim = ylim;\n",
" this.xdomain = xdomain;\n",
" this.ydomain = ydomain;\n",
" this.xscale = xscale;\n",
" this.yscale = yscale;\n",
" this.xgridOn = xgridOn;\n",
" this.ygridOn = ygridOn;\n",
" this.axclass = (typeof axclass !== 'undefined') ? axclass : \"axes\";\n",
" this.clipid = (typeof clipid != 'undefined') ? clipid : \"clip\";\n",
" this.zoomable = zoomable;\n",
"\n",
" this.sharex = [];\n",
" this.sharey = [];\n",
" this.elements = [];\n",
"\n",
" this.position = [this.bbox[0] * this.fig.width,\n",
" (1 - this.bbox[1] - this.bbox[3]) * this.fig.height];\n",
" this.width = bbox[2] * this.fig.width;\n",
" this.height = bbox[3] * this.fig.height;\n",
"\n",
" if(this.xscale === 'log'){\n",
" this.xdom = d3.scale.log();\n",
" }else if(this.xscale === 'date'){\n",
" this.xdom = d3.time.scale();\n",
" }else{\n",
" this.xdom = d3.scale.linear();\n",
" }\n",
"\n",
" if(this.yscale === 'log'){\n",
" this.ydom = d3.scale.log();\n",
" }else if(this.yscale === 'date'){\n",
" this.ydom = d3.time.scale();\n",
" }else{\n",
" this.ydom = d3.scale.linear();\n",
" }\n",
"\n",
" this.xdom.domain(this.xdomain)\n",
" .range([0, this.width]);\n",
"\n",
" this.ydom.domain(this.ydomain)\n",
" .range([this.height, 0]);\n",
"\n",
" if(this.xscale === 'date'){\n",
" this.xmap = d3.time.scale()\n",
" .domain(this.xdomain)\n",
" .range(this.xlim);\n",
" this.x = function(x){return this.xdom(this.xmap.invert(x));}\n",
" }else if(this.xscale === 'log'){\n",
" this.xmap = this.xdom;\n",
" this.x = this.xdom;\n",
" }else{\n",
" this.xmap = this.xdom;\n",
" this.x = this.xdom;\n",
" }\n",
"\n",
" if(this.yscale === 'date'){\n",
" this.ymap = d3.time.scale()\n",
" .domain(this.ydomain)\n",
" .range(this.ylim);\n",
" this.y = function(y){return this.ydom(this.ymap.invert(y));}\n",
" }else if(this.xscale === 'log'){\n",
" this.ymap = this.ydom;\n",
" this.y = this.ydom;\n",
" }else{\n",
" this.ymap = this.ydom;\n",
" this.y = this.ydom;\n",
" }\n",
" }\n",
"\n",
" Axes.prototype.draw = function(){\n",
" this.zoom = d3.behavior.zoom()\n",
" .x(this.xdom)\n",
" .y(this.ydom)\n",
" .on(\"zoom\", this.zoomed.bind(this));\n",
"\n",
" this.baseaxes = this.fig.canvas.append(\"g\")\n",
" .attr('transform', 'translate('\n",
" + this.position[0] + ','\n",
" + this.position[1] + ')')\n",
" .attr('width', this.width)\n",
" .attr('height', this.height)\n",
" .attr('class', \"baseaxes\");\n",
"\n",
" if(this.zoomable){\n",
" this.baseaxes.call(this.zoom);\n",
" }\n",
"\n",
" this.axesbg = this.baseaxes.append(\"svg:rect\")\n",
" .attr(\"width\", this.width)\n",
" .attr(\"height\", this.height)\n",
" .attr(\"class\", \"axesbg\");\n",
"\n",
" this.clip = this.baseaxes.append(\"svg:clipPath\")\n",
" .attr(\"id\", this.clipid)\n",
" .append(\"svg:rect\")\n",
" .attr(\"x\", 0)\n",
" .attr(\"y\", 0)\n",
" .attr(\"width\", this.width)\n",
" .attr(\"height\", this.height)\n",
"\n",
" this.axes = this.baseaxes.append(\"g\")\n",
" .attr(\"class\", this.axclass)\n",
" .attr(\"clip-path\", \"url(#\" + this.clipid + \")\");\n",
"\n",
" for(var i=0; i<this.elements.length; i++){\n",
" this.elements[i].draw();\n",
" }\n",
" };\n",
"\n",
" Axes.prototype.zoomed = function(propagate){\n",
" // propagate is a boolean specifying whether to propagate movements\n",
" // to shared axes, specified by sharex and sharey. Default is true.\n",
" propagate = (typeof propagate == 'undefined') ? true : propagate;\n",
"\n",
" //console.log(this.zoom.translate());\n",
" //console.log(this.zoom.scale());\n",
" //console.log(this.zoom.x().domain());\n",
" //console.log(this.zoom.y().domain());\n",
"\n",
" for(var i=0; i<this.elements.length; i++){\n",
" this.elements[i].zoomed();\n",
" }\n",
"\n",
" if(propagate){\n",
" // update shared x axes\n",
" for(var i=0; i<this.sharex.length; i++){\n",
" this.sharex[i].zoom.x().domain(this.zoom.x().domain());\n",
" this.sharex[i].zoomed(false);\n",
" }\n",
" // update shared y axes\n",
" for(var i=0; i<this.sharey.length; i++){\n",
" this.sharey[i].zoom.y().domain(this.zoom.y().domain());\n",
" this.sharey[i].zoomed(false);\n",
" }\n",
" }\n",
" };\n",
"\n",
" Axes.prototype.add_element = function(element){\n",
" this.elements.push(element);\n",
" };\n",
"\n",
" Axes.prototype.prep_reset = function(){\n",
" // interpolate() does not work on dates, so we map dates to numbers,\n",
" // interpolate the numbers, and then invert the map.\n",
" // we use the same strategy for log, so the interpolation will be smooth.\n",
" // There probably is a cleaner approach...\n",
"\n",
" if (this.xscale === 'date'){\n",
" var start = this.xdom.domain();\n",
" var end = this.xdomain;\n",
" var interp = d3.interpolate(\n",
" [this.xmap(start[0]), this.xmap(start[1])],\n",
" [this.xmap(end[0]), this.xmap(end[1])]);\n",
" this.ix = function(t){\n",
" return [this.xmap.invert(interp(t)[0]),\n",
" this.xmap.invert(interp(t)[1])];\n",
" }\n",
" }else{\n",
" this.ix = d3.interpolate(this.xdom.domain(), this.xlim);\n",
" }\n",
"\n",
" if (this.yscale === 'date'){\n",
" var start = this.ydom.domain();\n",
" var end = this.ydomain;\n",
" var interp = d3.interpolate(\n",
" [this.ymap(start[0]), this.ymap(start[1])],\n",
" [this.ymap(end[0]), this.ymap(end[1])]);\n",
" this.iy = function(t){\n",
" return [this.ymap.invert(interp(t)[0]),\n",
" this.ymap.invert(interp(t)[1])];\n",
" }\n",
" }else{\n",
" this.iy = d3.interpolate(this.ydom.domain(), this.ylim);\n",
" }\n",
" }\n",
"\n",
" Axes.prototype.finalize_reset = function(){\n",
" this.zoom.scale(1).translate([0, 0]);\n",
" }\n",
"\n",
" Axes.prototype.reset = function(){\n",
" this.prep_reset();\n",
" d3.transition().duration(750).tween(\"zoom\", function() {\n",
" return function(t) {\n",
" this.zoom.x(this.xdom.domain(this.ix(t)))\n",
" .y(this.ydom.domain(this.iy(t)));\n",
" this.zoomed();\n",
" };\n",
" });\n",
" this.finalize_reset();\n",
" };\n",
"\n",
"\n",
"\n",
" function Axis(axes, position, nticks, tickvalues, tickformat){\n",
" this.axes = axes;\n",
" this.position = position;\n",
" this.nticks = nticks;\n",
" this.tickvalues = tickvalues;\n",
" this.tickformat = tickformat;\n",
" if (position == \"bottom\"){\n",
" this.transform = \"translate(0,\" + this.axes.height + \")\";\n",
" this.scale = this.axes.xdom;\n",
" this.class = \"x axis\";\n",
" }else if (position == \"top\"){\n",
" this.transform = \"translate(0,0)\"\n",
" this.scale = this.axes.xdom;\n",
" this.class = \"x axis\";\n",
" }else if (position == \"left\"){\n",
" this.transform = \"translate(0,0)\";\n",
" this.scale = this.axes.ydom;\n",
" this.class = \"y axis\";\n",
" }else{\n",
" this.transform = \"translate(\" + this.axes.width + \",0)\";\n",
" this.scale = this.axes.ydom;\n",
" this.class = \"y axis\";\n",
" }\n",
" }\n",
"\n",
" Axis.prototype.draw = function(){\n",
" this.axis = d3.svg.axis()\n",
" .scale(this.scale)\n",
" .orient(this.position)\n",
" .ticks(this.nticks)\n",
" .tickValues(this.tickvalues)\n",
" .tickFormat(this.tickformat);\n",
" this.elem = this.axes.baseaxes.append('g')\n",
" .attr(\"transform\", this.transform)\n",
" .attr(\"class\", this.class)\n",
" .call(this.axis);\n",
" };\n",
"\n",
" Axis.prototype.zoomed = function(){\n",
" this.elem.call(this.axis);\n",
" };\n",
"\n",
"\n",
"\n",
" function Grid(axes, xy){\n",
" this.axes = axes;\n",
" this.class = xy + \" grid\"\n",
" if(xy == \"x\"){\n",
" this.transform = \"translate(0,\" + this.axes.height + \")\";\n",
" this.position = \"bottom\";\n",
" this.scale = this.axes.xdom;\n",
" this.tickSize = -this.axes.height;\n",
" }else{\n",
" this.transform = \"translate(0,0)\";\n",
" this.position = \"left\";\n",
" this.scale = this.axes.ydom;\n",
" this.tickSize = -this.axes.width;\n",
" }\n",
" }\n",
"\n",
" Grid.prototype.draw = function(){\n",
" this.grid = d3.svg.axis()\n",
" .scale(this.scale)\n",
" .orient(this.position)\n",
" .tickSize(this.tickSize, 0, 0)\n",
" .tickFormat(\"\");\n",
" this.elem = this.axes.axes.append(\"g\")\n",
" .attr(\"class\", this.class)\n",
" .attr(\"transform\", this.transform)\n",
" .call(this.grid);\n",
" };\n",
"\n",
" Grid.prototype.zoomed = function(){\n",
" this.elem.call(this.grid);\n",
" };\n",
"\n",
"\n",
"\n",
" // This function constructs a mapped SVG path\n",
" // from an input data array\n",
" var construct_SVG_path = function(data, xmap, ymap){\n",
" var result = \"\";\n",
" for (var i=0;i<data.length;i++){\n",
" result += data[i][0];\n",
" if(data[i][0] == 'Z'){\n",
" continue;\n",
" }\n",
" for (var j=0;j<data[i][1].length;j++){\n",
" if(j % 2 == 0){\n",
" result += \" \" + xmap(data[i][1][j]);\n",
" }else{\n",
" result += \" \" + ymap(data[i][1][j]);\n",
" }\n",
" }\n",
" result += \" \";\n",
" }\n",
" return result;\n",
" };\n",
"\n",
"\n",
" var figwidth = 6.0 * 80;\n",
" var figheight = 4.0 * 80;\n",
" var fig = new Figure(\"div#figure9d4bbc51570b4c1d93713cab554bc55d\",\n",
" figwidth, figheight);\n",
"\n",
"\n",
"\n",
" var ax1 = new Axes(fig, [0.125, 0.125, 0.77500000000000002, 0.77500000000000002], [0.0, 9.0], [0.0, 9.0],\n",
" \"linear\", \"linear\",\n",
" [0.0, 9.0], [0.0, 9.0],\n",
" false, false,\n",
" \"axes1\",\n",
" \"clip9d4bbc51570b4c1d93713cab554bc55d1\", true);\n",
"\n",
"\n",
"\n",
"// Add an Axis element\n",
"ax1.add_element(new Axis(ax1, \"bottom\",\n",
" 10, null,\n",
" null));\n",
"\n",
"\n",
"\n",
"// Add an Axis element\n",
"ax1.add_element(new Axis(ax1, \"left\",\n",
" 10, null,\n",
" null));\n",
"\n",
"\n",
"\n",
"// Add a Line2D element\n",
"var line98de01a5186e4bfa9be04bdc831dd004 = new function(){\n",
" this.data = [[0.0, 0.0], [1.0, 1.0], [2.0, 2.0], [3.0, 3.0], [4.0, 4.0], [5.0, 5.0], [6.0, 6.0], [7.0, 7.0], [8.0, 8.0], [9.0, 9.0]];\n",
" this.ax = ax1;\n",
"\n",
" this.translate = function(d)\n",
" { return \"translate(\" + this.ax.x(d[0]) + \",\"\n",
" + this.ax.y(d[1]) + \")\"; };\n",
"\n",
" this.draw = function(){\n",
"\n",
"\n",
" this.pointsobj = this.ax.axes.append(\"svg:g\")\n",
" .selectAll(\"scatter-dots-3\")\n",
" .data(this.data.filter(\n",
" function(d){return !isNaN(d[0]) && !isNaN(d[1]); }))\n",
" .enter().append(\"svg:path\")\n",
" .attr('class', 'points3')\n",
" .attr(\"d\", d3.svg.symbol()\n",
" .type(\"circle\")\n",
" .size(400))\n",
" .attr(\"transform\", this.translate.bind(this));\n",
"\n",
" };\n",
"\n",
" this.zoomed = function(){\n",
"\n",
"\n",
"\n",
" this.pointsobj.attr(\"transform\", this.translate.bind(this));\n",
"\n",
"\n",
" }\n",
"};\n",
"\n",
"ax1.add_element(line98de01a5186e4bfa9be04bdc831dd004);\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
" fig.draw();\n",
"\n",
"\n",
" var tooltip = d3.select(\"body\").append(\"div\")\n",
" .attr(\"class\", \"mpld3-tooltip\")\n",
" .style(\"position\", \"absolute\")\n",
" .style(\"z-index\", \"10\")\n",
" .style(\"visibility\", \"hidden\");\n",
"\n",
" var labels = ['<h1>Point #0</h1>', '<h1>Point #1</h1>', '<h1>Point #2</h1>', '<h1>Point #3</h1>', '<h1>Point #4</h1>', '<h1>Point #5</h1>', '<h1>Point #6</h1>', '<h1>Point #7</h1>', '<h1>Point #8</h1>', '<h1>Point #9</h1>'];\n",
"\n",
"\n",
" ax1.axes.selectAll(\".points3\")\n",
" .on(\"mouseover\", function(d, i){\n",
" tooltip\n",
" .html(labels[i])\n",
" .style(\"visibility\", \"visible\");})\n",
" .on(\"mousemove\", function(d, i){\n",
" tooltip\n",
" .style(\"top\", (event.pageY+-25)+\"px\")\n",
" .style(\"left\",(event.pageX+25)+\"px\");})\n",
" .on(\"mouseout\", function(d, i){\n",
" tooltip\n",
" .style(\"visibility\", \"hidden\");});\n",
"\n",
" fig.root.append(\"div\")\n",
" .append(\"button\")\n",
" .text(\"Reset\")\n",
" .on(\"click\", fig.reset.bind(fig));\n",
"\n",
"\n",
" return fig\n",
" }\n",
"\n",
" // set a timeout of 0: this makes things work in the IPython notebook\n",
" setTimeout(function(){\n",
" // we need to call the function, making sure d3 is defined appropriately\n",
" if(typeof define === \"function\" && define.amd){\n",
" // If require.js is available, use it to load d3\n",
" require.config({paths: {d3: \"//cdnjs.cloudflare.com/ajax/libs/d3/3.4.1/d3.min\"}});\n",
" require([\"d3\"], create_fig9d4bbc51570b4c1d93713cab554bc55d);\n",
" }else if(typeof d3 === \"undefined\"){\n",
" // No require.js: dynamically load d3\n",
" var s = document.createElement('script');\n",
" s.src = \"//cdnjs.cloudflare.com/ajax/libs/d3/3.4.1/d3.min.js\";\n",
" s.async = true;\n",
" s.onreadystatechange = s.onload = s.onerror = function() {\n",
" if(typeof d3 === \"undefined\"){\n",
" document.getElementById(\"figure9d4bbc51570b4c1d93713cab554bc55d\").innerHTML =\n",
" \"<p style='color:red;'>(d3 failed to load)</p>\";\n",
" }else{\n",
" create_fig9d4bbc51570b4c1d93713cab554bc55d(d3);\n",
" }\n",
" };\n",
" document.getElementsByTagName(\"head\")[0].appendChild(s);\n",
" }else{\n",
" // d3 is already globally loaded\n",
" create_fig9d4bbc51570b4c1d93713cab554bc55d(d3);\n",
" }\n",
" }, 0);\n",
"\n",
" </script>\n",
"\n",
" <style>\n",
" None\n",
" </style>\n",
"\n",
"\n",
"\n"
],
"metadata": {},
"output_type": "display_data",
"png": "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\nrkJggg==\n",
"text": [
"<matplotlib.figure.Figure at 0x3e0f9d0>"
]
}
],
"prompt_number": 17
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"It is more organizationally appealing to add the tooltip div to the figure root div, but this requires a more complicated approach to getting the positioning correct."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"plugins.PointHTMLTooltip.FIG_JS = jinja2.Template(\"\"\"\n",
"var tooltip = fig.root.append(\"div\")\n",
" .attr(\"class\", \"mpld3-tooltip\")\n",
" .style(\"position\", \"absolute\")\n",
" .style(\"z-index\", \"10\")\n",
" .style(\"visibility\", \"hidden\");\n",
"\n",
"var labels = {{ labels }};\n",
"\n",
"\n",
"ax{{ axid }}.axes.selectAll(\".{{ pointclass }}{{ elid }}\")\n",
" .on(\"mouseover\", function(d, i){\n",
" tooltip\n",
" .html(labels[i])\n",
" .style(\"visibility\", \"visible\");})\n",
" .on(\"mousemove\", function(d, i){\n",
" var ctm = fig.canvas.node().getScreenCTM();\n",
" tooltip\n",
" .style(\"left\", (event.x - ctm.e +{{ voffset }})+\"px\")\n",
" .style(\"top\", (event.y - ctm.f +{{ hoffset }})+\"px\");})\n",
" .on(\"mouseout\", function(d, i){\n",
" tooltip\n",
" .style(\"visibility\", \"hidden\");});\n",
"\"\"\")\n"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 38
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"fig, ax = plt.subplots()\n",
"points = ax.plot(range(10), 'o', color='grey', ms=20, mew=5)\n",
"labels = ['<h1>Point #{title}</h1>'.format(title=i) for i in range(10)]\n",
"plugins.connect(fig, plugins.PointHTMLTooltip(points[0], labels, 25, -25))\n",
"plugins.connect(fig, plugins.ResetButton())"
],
"language": "python",
"metadata": {},
"outputs": [
{
"html": [
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
" <style>\n",
"\n",
"\n",
" div#figure1e506e680cd8499189f1bf22f68a79d1\n",
" .axesbg{\n",
" fill: #FFFFFF;\n",
" }\n",
"\n",
"\n",
"\n",
"div#figure1e506e680cd8499189f1bf22f68a79d1\n",
".axis line, .axis path {\n",
" shape-rendering: crispEdges;\n",
" stroke: black;\n",
" fill: none;\n",
"}\n",
"\n",
"div#figure1e506e680cd8499189f1bf22f68a79d1\n",
".axis text {\n",
" font-family: sans-serif;\n",
" font-size: 10.0px;\n",
" fill: black;\n",
" stroke: none;\n",
"}\n",
"\n",
"\n",
"\n",
"div#figure1e506e680cd8499189f1bf22f68a79d1\n",
".axis line, .axis path {\n",
" shape-rendering: crispEdges;\n",
" stroke: black;\n",
" fill: none;\n",
"}\n",
"\n",
"div#figure1e506e680cd8499189f1bf22f68a79d1\n",
".axis text {\n",
" font-family: sans-serif;\n",
" font-size: 10.0px;\n",
" fill: black;\n",
" stroke: none;\n",
"}\n",
"\n",
"\n",
"\n",
"div#figure1e506e680cd8499189f1bf22f68a79d1\n",
".axes1\n",
"path.line3 {\n",
" stroke: #808080;\n",
" stroke-width: 1.0;\n",
" stroke-dasharray: none;\n",
" fill: none;\n",
" stroke-opacity: 1;\n",
"}\n",
"\n",
"div#figure1e506e680cd8499189f1bf22f68a79d1\n",
".axes1\n",
"path.points3 {\n",
" stroke-width: 5;\n",
" stroke: #000000;\n",
" fill: #808080;\n",
" fill-opacity: 1;\n",
" stroke-opacity: 1;\n",
"}\n",
"\n",
"\n",
"\n",
"div#figure1e506e680cd8499189f1bf22f68a79d1\n",
"text.textc4e124048e8c479ebc67c5e1efd0f3c1 {\n",
" font-size : 10.0px;\n",
" fill : #000000;\n",
" opacity : 1;\n",
"}\n",
"\n",
"\n",
"\n",
"div#figure1e506e680cd8499189f1bf22f68a79d1\n",
"text.textf77b2bdc6a864e70aee3e85a4e098478 {\n",
" font-size : 10.0px;\n",
" fill : #000000;\n",
" opacity : 1;\n",
"}\n",
"\n",
"\n",
"\n",
"div#figure1e506e680cd8499189f1bf22f68a79d1\n",
"text.text25595a7629474c62a78d4828b7cc8b0b {\n",
" font-size : 12.0px;\n",
" fill : #000000;\n",
" opacity : 1;\n",
"}\n",
"\n",
"\n",
"\n",
"\n",
"\n",
" </style>\n",
"\n",
"\n",
"\n",
" <div id='figure1e506e680cd8499189f1bf22f68a79d1'>\n",
" </div>\n",
" <script type=\"text/javascript\">\n",
" var create_fig1e506e680cd8499189f1bf22f68a79d1 = function(d3, undefined){\n",
"\n",
"\n",
" function Figure(figid, width, height){\n",
" this.figid = figid;\n",
" this.root = d3.select(figid);\n",
" this.width = width;\n",
" this.height = height;\n",
" this.axes = [];\n",
" }\n",
"\n",
" Figure.prototype.draw = function(){\n",
" this.canvas = this.root.append('svg:svg')\n",
" .attr('class', 'figure')\n",
" .attr('width', this.width)\n",
" .attr('height', this.height);\n",
" for (var i=0; i<this.axes.length; i++){\n",
" this.axes[i].draw();\n",
" }\n",
" };\n",
"\n",
" Figure.prototype.reset = function(duration){\n",
" duration = (typeof duration !== 'undefined') ? duration : 750;\n",
" for (var i=0; i<this.axes.length; i++){\n",
" this.axes[i].prep_reset();\n",
" }\n",
"\n",
" var transition = function(t){\n",
" for (var i=0; i<this.axes.length; i++){\n",
" this.axes[i].xdom(this.axes[i].xdom.domain(this.axes[i].ix(t)));\n",
" this.axes[i].ydom(this.axes[i].ydom.domain(this.axes[i].iy(t)));\n",
"\n",
" // don't propagate: this will be done as part of the loop.\n",
" this.axes[i].zoomed(false);\n",
" }\n",
" }.bind(this)\n",
"\n",
" d3.transition().duration(duration)\n",
" .tween(\"zoom\", function(){return transition;});\n",
"\n",
" for (var i=0; i<this.axes.length; i++){\n",
" this.axes[i].finalize_reset();\n",
" }\n",
" };\n",
"\n",
"\n",
"\n",
" function Axes(fig, bbox,\n",
" xlim, ylim,\n",
" xscale, yscale,\n",
" xdomain, ydomain,\n",
" xgridOn, ygridOn,\n",
" axclass, clipid,\n",
" zoomable){\n",
" this.axnum = fig.axes.length;\n",
" fig.axes.push(this);\n",
"\n",
" this.fig = fig;\n",
" this.bbox = bbox;\n",
" this.xlim = xlim;\n",
" this.ylim = ylim;\n",
" this.xdomain = xdomain;\n",
" this.ydomain = ydomain;\n",
" this.xscale = xscale;\n",
" this.yscale = yscale;\n",
" this.xgridOn = xgridOn;\n",
" this.ygridOn = ygridOn;\n",
" this.axclass = (typeof axclass !== 'undefined') ? axclass : \"axes\";\n",
" this.clipid = (typeof clipid != 'undefined') ? clipid : \"clip\";\n",
" this.zoomable = zoomable;\n",
"\n",
" this.sharex = [];\n",
" this.sharey = [];\n",
" this.elements = [];\n",
"\n",
" this.position = [this.bbox[0] * this.fig.width,\n",
" (1 - this.bbox[1] - this.bbox[3]) * this.fig.height];\n",
" this.width = bbox[2] * this.fig.width;\n",
" this.height = bbox[3] * this.fig.height;\n",
"\n",
" if(this.xscale === 'log'){\n",
" this.xdom = d3.scale.log();\n",
" }else if(this.xscale === 'date'){\n",
" this.xdom = d3.time.scale();\n",
" }else{\n",
" this.xdom = d3.scale.linear();\n",
" }\n",
"\n",
" if(this.yscale === 'log'){\n",
" this.ydom = d3.scale.log();\n",
" }else if(this.yscale === 'date'){\n",
" this.ydom = d3.time.scale();\n",
" }else{\n",
" this.ydom = d3.scale.linear();\n",
" }\n",
"\n",
" this.xdom.domain(this.xdomain)\n",
" .range([0, this.width]);\n",
"\n",
" this.ydom.domain(this.ydomain)\n",
" .range([this.height, 0]);\n",
"\n",
" if(this.xscale === 'date'){\n",
" this.xmap = d3.time.scale()\n",
" .domain(this.xdomain)\n",
" .range(this.xlim);\n",
" this.x = function(x){return this.xdom(this.xmap.invert(x));}\n",
" }else if(this.xscale === 'log'){\n",
" this.xmap = this.xdom;\n",
" this.x = this.xdom;\n",
" }else{\n",
" this.xmap = this.xdom;\n",
" this.x = this.xdom;\n",
" }\n",
"\n",
" if(this.yscale === 'date'){\n",
" this.ymap = d3.time.scale()\n",
" .domain(this.ydomain)\n",
" .range(this.ylim);\n",
" this.y = function(y){return this.ydom(this.ymap.invert(y));}\n",
" }else if(this.xscale === 'log'){\n",
" this.ymap = this.ydom;\n",
" this.y = this.ydom;\n",
" }else{\n",
" this.ymap = this.ydom;\n",
" this.y = this.ydom;\n",
" }\n",
" }\n",
"\n",
" Axes.prototype.draw = function(){\n",
" this.zoom = d3.behavior.zoom()\n",
" .x(this.xdom)\n",
" .y(this.ydom)\n",
" .on(\"zoom\", this.zoomed.bind(this));\n",
"\n",
" this.baseaxes = this.fig.canvas.append(\"g\")\n",
" .attr('transform', 'translate('\n",
" + this.position[0] + ','\n",
" + this.position[1] + ')')\n",
" .attr('width', this.width)\n",
" .attr('height', this.height)\n",
" .attr('class', \"baseaxes\");\n",
"\n",
" if(this.zoomable){\n",
" this.baseaxes.call(this.zoom);\n",
" }\n",
"\n",
" this.axesbg = this.baseaxes.append(\"svg:rect\")\n",
" .attr(\"width\", this.width)\n",
" .attr(\"height\", this.height)\n",
" .attr(\"class\", \"axesbg\");\n",
"\n",
" this.clip = this.baseaxes.append(\"svg:clipPath\")\n",
" .attr(\"id\", this.clipid)\n",
" .append(\"svg:rect\")\n",
" .attr(\"x\", 0)\n",
" .attr(\"y\", 0)\n",
" .attr(\"width\", this.width)\n",
" .attr(\"height\", this.height)\n",
"\n",
" this.axes = this.baseaxes.append(\"g\")\n",
" .attr(\"class\", this.axclass)\n",
" .attr(\"clip-path\", \"url(#\" + this.clipid + \")\");\n",
"\n",
" for(var i=0; i<this.elements.length; i++){\n",
" this.elements[i].draw();\n",
" }\n",
" };\n",
"\n",
" Axes.prototype.zoomed = function(propagate){\n",
" // propagate is a boolean specifying whether to propagate movements\n",
" // to shared axes, specified by sharex and sharey. Default is true.\n",
" propagate = (typeof propagate == 'undefined') ? true : propagate;\n",
"\n",
" //console.log(this.zoom.translate());\n",
" //console.log(this.zoom.scale());\n",
" //console.log(this.zoom.x().domain());\n",
" //console.log(this.zoom.y().domain());\n",
"\n",
" for(var i=0; i<this.elements.length; i++){\n",
" this.elements[i].zoomed();\n",
" }\n",
"\n",
" if(propagate){\n",
" // update shared x axes\n",
" for(var i=0; i<this.sharex.length; i++){\n",
" this.sharex[i].zoom.x().domain(this.zoom.x().domain());\n",
" this.sharex[i].zoomed(false);\n",
" }\n",
" // update shared y axes\n",
" for(var i=0; i<this.sharey.length; i++){\n",
" this.sharey[i].zoom.y().domain(this.zoom.y().domain());\n",
" this.sharey[i].zoomed(false);\n",
" }\n",
" }\n",
" };\n",
"\n",
" Axes.prototype.add_element = function(element){\n",
" this.elements.push(element);\n",
" };\n",
"\n",
" Axes.prototype.prep_reset = function(){\n",
" // interpolate() does not work on dates, so we map dates to numbers,\n",
" // interpolate the numbers, and then invert the map.\n",
" // we use the same strategy for log, so the interpolation will be smooth.\n",
" // There probably is a cleaner approach...\n",
"\n",
" if (this.xscale === 'date'){\n",
" var start = this.xdom.domain();\n",
" var end = this.xdomain;\n",
" var interp = d3.interpolate(\n",
" [this.xmap(start[0]), this.xmap(start[1])],\n",
" [this.xmap(end[0]), this.xmap(end[1])]);\n",
" this.ix = function(t){\n",
" return [this.xmap.invert(interp(t)[0]),\n",
" this.xmap.invert(interp(t)[1])];\n",
" }\n",
" }else{\n",
" this.ix = d3.interpolate(this.xdom.domain(), this.xlim);\n",
" }\n",
"\n",
" if (this.yscale === 'date'){\n",
" var start = this.ydom.domain();\n",
" var end = this.ydomain;\n",
" var interp = d3.interpolate(\n",
" [this.ymap(start[0]), this.ymap(start[1])],\n",
" [this.ymap(end[0]), this.ymap(end[1])]);\n",
" this.iy = function(t){\n",
" return [this.ymap.invert(interp(t)[0]),\n",
" this.ymap.invert(interp(t)[1])];\n",
" }\n",
" }else{\n",
" this.iy = d3.interpolate(this.ydom.domain(), this.ylim);\n",
" }\n",
" }\n",
"\n",
" Axes.prototype.finalize_reset = function(){\n",
" this.zoom.scale(1).translate([0, 0]);\n",
" }\n",
"\n",
" Axes.prototype.reset = function(){\n",
" this.prep_reset();\n",
" d3.transition().duration(750).tween(\"zoom\", function() {\n",
" return function(t) {\n",
" this.zoom.x(this.xdom.domain(this.ix(t)))\n",
" .y(this.ydom.domain(this.iy(t)));\n",
" this.zoomed();\n",
" };\n",
" });\n",
" this.finalize_reset();\n",
" };\n",
"\n",
"\n",
"\n",
" function Axis(axes, position, nticks, tickvalues, tickformat){\n",
" this.axes = axes;\n",
" this.position = position;\n",
" this.nticks = nticks;\n",
" this.tickvalues = tickvalues;\n",
" this.tickformat = tickformat;\n",
" if (position == \"bottom\"){\n",
" this.transform = \"translate(0,\" + this.axes.height + \")\";\n",
" this.scale = this.axes.xdom;\n",
" this.class = \"x axis\";\n",
" }else if (position == \"top\"){\n",
" this.transform = \"translate(0,0)\"\n",
" this.scale = this.axes.xdom;\n",
" this.class = \"x axis\";\n",
" }else if (position == \"left\"){\n",
" this.transform = \"translate(0,0)\";\n",
" this.scale = this.axes.ydom;\n",
" this.class = \"y axis\";\n",
" }else{\n",
" this.transform = \"translate(\" + this.axes.width + \",0)\";\n",
" this.scale = this.axes.ydom;\n",
" this.class = \"y axis\";\n",
" }\n",
" }\n",
"\n",
" Axis.prototype.draw = function(){\n",
" this.axis = d3.svg.axis()\n",
" .scale(this.scale)\n",
" .orient(this.position)\n",
" .ticks(this.nticks)\n",
" .tickValues(this.tickvalues)\n",
" .tickFormat(this.tickformat);\n",
" this.elem = this.axes.baseaxes.append('g')\n",
" .attr(\"transform\", this.transform)\n",
" .attr(\"class\", this.class)\n",
" .call(this.axis);\n",
" };\n",
"\n",
" Axis.prototype.zoomed = function(){\n",
" this.elem.call(this.axis);\n",
" };\n",
"\n",
"\n",
"\n",
" function Grid(axes, xy){\n",
" this.axes = axes;\n",
" this.class = xy + \" grid\"\n",
" if(xy == \"x\"){\n",
" this.transform = \"translate(0,\" + this.axes.height + \")\";\n",
" this.position = \"bottom\";\n",
" this.scale = this.axes.xdom;\n",
" this.tickSize = -this.axes.height;\n",
" }else{\n",
" this.transform = \"translate(0,0)\";\n",
" this.position = \"left\";\n",
" this.scale = this.axes.ydom;\n",
" this.tickSize = -this.axes.width;\n",
" }\n",
" }\n",
"\n",
" Grid.prototype.draw = function(){\n",
" this.grid = d3.svg.axis()\n",
" .scale(this.scale)\n",
" .orient(this.position)\n",
" .tickSize(this.tickSize, 0, 0)\n",
" .tickFormat(\"\");\n",
" this.elem = this.axes.axes.append(\"g\")\n",
" .attr(\"class\", this.class)\n",
" .attr(\"transform\", this.transform)\n",
" .call(this.grid);\n",
" };\n",
"\n",
" Grid.prototype.zoomed = function(){\n",
" this.elem.call(this.grid);\n",
" };\n",
"\n",
"\n",
"\n",
" // This function constructs a mapped SVG path\n",
" // from an input data array\n",
" var construct_SVG_path = function(data, xmap, ymap){\n",
" var result = \"\";\n",
" for (var i=0;i<data.length;i++){\n",
" result += data[i][0];\n",
" if(data[i][0] == 'Z'){\n",
" continue;\n",
" }\n",
" for (var j=0;j<data[i][1].length;j++){\n",
" if(j % 2 == 0){\n",
" result += \" \" + xmap(data[i][1][j]);\n",
" }else{\n",
" result += \" \" + ymap(data[i][1][j]);\n",
" }\n",
" }\n",
" result += \" \";\n",
" }\n",
" return result;\n",
" };\n",
"\n",
"\n",
" var figwidth = 6.0 * 80;\n",
" var figheight = 4.0 * 80;\n",
" var fig = new Figure(\"div#figure1e506e680cd8499189f1bf22f68a79d1\",\n",
" figwidth, figheight);\n",
"\n",
"\n",
"\n",
" var ax1 = new Axes(fig, [0.125, 0.125, 0.77500000000000002, 0.77500000000000002], [0.0, 9.0], [0.0, 9.0],\n",
" \"linear\", \"linear\",\n",
" [0.0, 9.0], [0.0, 9.0],\n",
" false, false,\n",
" \"axes1\",\n",
" \"clip1e506e680cd8499189f1bf22f68a79d11\", true);\n",
"\n",
"\n",
"\n",
"// Add an Axis element\n",
"ax1.add_element(new Axis(ax1, \"bottom\",\n",
" 10, null,\n",
" null));\n",
"\n",
"\n",
"\n",
"// Add an Axis element\n",
"ax1.add_element(new Axis(ax1, \"left\",\n",
" 10, null,\n",
" null));\n",
"\n",
"\n",
"\n",
"// Add a Line2D element\n",
"var line91f1d415dd9b459bbf0149f0f6f43f47 = new function(){\n",
" this.data = [[0.0, 0.0], [1.0, 1.0], [2.0, 2.0], [3.0, 3.0], [4.0, 4.0], [5.0, 5.0], [6.0, 6.0], [7.0, 7.0], [8.0, 8.0], [9.0, 9.0]];\n",
" this.ax = ax1;\n",
"\n",
" this.translate = function(d)\n",
" { return \"translate(\" + this.ax.x(d[0]) + \",\"\n",
" + this.ax.y(d[1]) + \")\"; };\n",
"\n",
" this.draw = function(){\n",
"\n",
"\n",
" this.pointsobj = this.ax.axes.append(\"svg:g\")\n",
" .selectAll(\"scatter-dots-3\")\n",
" .data(this.data.filter(\n",
" function(d){return !isNaN(d[0]) && !isNaN(d[1]); }))\n",
" .enter().append(\"svg:path\")\n",
" .attr('class', 'points3')\n",
" .attr(\"d\", d3.svg.symbol()\n",
" .type(\"circle\")\n",
" .size(400))\n",
" .attr(\"transform\", this.translate.bind(this));\n",
"\n",
" };\n",
"\n",
" this.zoomed = function(){\n",
"\n",
"\n",
"\n",
" this.pointsobj.attr(\"transform\", this.translate.bind(this));\n",
"\n",
"\n",
" }\n",
"};\n",
"\n",
"ax1.add_element(line91f1d415dd9b459bbf0149f0f6f43f47);\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
" fig.draw();\n",
"\n",
"\n",
"var tooltip = fig.root.append(\"div\")\n",
" .attr(\"class\", \"mpld3-tooltip\")\n",
" .style(\"position\", \"absolute\")\n",
" .style(\"z-index\", \"10\")\n",
" .style(\"visibility\", \"hidden\");\n",
"\n",
"var labels = ['<h1>Point #0</h1>', '<h1>Point #1</h1>', '<h1>Point #2</h1>', '<h1>Point #3</h1>', '<h1>Point #4</h1>', '<h1>Point #5</h1>', '<h1>Point #6</h1>', '<h1>Point #7</h1>', '<h1>Point #8</h1>', '<h1>Point #9</h1>'];\n",
"\n",
"\n",
"ax1.axes.selectAll(\".points3\")\n",
" .on(\"mouseover\", function(d, i){\n",
" tooltip\n",
" .html(labels[i])\n",
" .style(\"visibility\", \"visible\");})\n",
" .on(\"mousemove\", function(d, i){\n",
" var ctm = fig.canvas.node().getScreenCTM();\n",
" tooltip\n",
" .style(\"left\", (event.x - ctm.e +-25)+\"px\")\n",
" .style(\"top\", (event.y - ctm.f +25)+\"px\");})\n",
" .on(\"mouseout\", function(d, i){\n",
" tooltip\n",
" .style(\"visibility\", \"hidden\");});\n",
" fig.root.append(\"div\")\n",
" .append(\"button\")\n",
" .text(\"Reset\")\n",
" .on(\"click\", fig.reset.bind(fig));\n",
"\n",
"\n",
" return fig\n",
" }\n",
"\n",
" // set a timeout of 0: this makes things work in the IPython notebook\n",
" setTimeout(function(){\n",
" // we need to call the function, making sure d3 is defined appropriately\n",
" if(typeof define === \"function\" && define.amd){\n",
" // If require.js is available, use it to load d3\n",
" require.config({paths: {d3: \"//cdnjs.cloudflare.com/ajax/libs/d3/3.4.1/d3.min\"}});\n",
" require([\"d3\"], create_fig1e506e680cd8499189f1bf22f68a79d1);\n",
" }else if(typeof d3 === \"undefined\"){\n",
" // No require.js: dynamically load d3\n",
" var s = document.createElement('script');\n",
" s.src = \"//cdnjs.cloudflare.com/ajax/libs/d3/3.4.1/d3.min.js\";\n",
" s.async = true;\n",
" s.onreadystatechange = s.onload = s.onerror = function() {\n",
" if(typeof d3 === \"undefined\"){\n",
" document.getElementById(\"figure1e506e680cd8499189f1bf22f68a79d1\").innerHTML =\n",
" \"<p style='color:red;'>(d3 failed to load)</p>\";\n",
" }else{\n",
" create_fig1e506e680cd8499189f1bf22f68a79d1(d3);\n",
" }\n",
" };\n",
" document.getElementsByTagName(\"head\")[0].appendChild(s);\n",
" }else{\n",
" // d3 is already globally loaded\n",
" create_fig1e506e680cd8499189f1bf22f68a79d1(d3);\n",
" }\n",
" }, 0);\n",
"\n",
" </script>\n",
"\n",
" <style>\n",
" None\n",
" </style>\n",
"\n",
"\n",
"\n"
],
"metadata": {},
"output_type": "display_data",
"png": "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\nrkJggg==\n",
"text": [
"<matplotlib.figure.Figure at 0x3cb6110>"
]
}
],
"prompt_number": 40
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"I thought I had that working better before... Try `d3.mouse(fig.canvas.node())` approach."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"plugins.PointHTMLTooltip.FIG_JS = jinja2.Template(\"\"\"\n",
"var tooltip = fig.root.append(\"div\")\n",
" .attr(\"class\", \"mpld3-tooltip\")\n",
" .style(\"position\", \"absolute\")\n",
" .style(\"z-index\", \"10\")\n",
" .style(\"visibility\", \"hidden\");\n",
"\n",
"var labels = {{ labels }};\n",
"\n",
"\n",
"ax{{ axid }}.axes.selectAll(\".{{ pointclass }}{{ elid }}\")\n",
" .on(\"mouseover\", function(d, i){\n",
" tooltip\n",
" .html(labels[i])\n",
" .style(\"visibility\", \"visible\");})\n",
" .on(\"mousemove\", function(d, i){\n",
" var xy = d3.mouse(fig.canvas.node());\n",
" tooltip\n",
" .style(\"left\", (xy[0] +{{ voffset }})+\"px\")\n",
" .style(\"top\", (xy[1] +{{ hoffset }})+\"px\");})\n",
" .on(\"mouseout\", function(d, i){\n",
" tooltip\n",
" .style(\"visibility\", \"hidden\");});\n",
"\"\"\")\n"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 41
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"fig, ax = plt.subplots()\n",
"points = ax.plot(range(10), 'o', color='grey', ms=20, mew=5)\n",
"labels = ['<h1>Point #{title}</h1>'.format(title=i) for i in range(10)]\n",
"plugins.connect(fig, plugins.PointHTMLTooltip(points[0], labels, 25, -25))\n",
"plugins.connect(fig, plugins.ResetButton())"
],
"language": "python",
"metadata": {},
"outputs": [
{
"html": [
"\n",
"\n",
"\n",
"\n",
"\n",
"\n",
" <style>\n",
"\n",
"\n",
" div#figureea16e463114e4a769c868a07182d0aed\n",
" .axesbg{\n",
" fill: #FFFFFF;\n",
" }\n",
"\n",
"\n",
"\n",
"div#figureea16e463114e4a769c868a07182d0aed\n",
".axis line, .axis path {\n",
" shape-rendering: crispEdges;\n",
" stroke: black;\n",
" fill: none;\n",
"}\n",
"\n",
"div#figureea16e463114e4a769c868a07182d0aed\n",
".axis text {\n",
" font-family: sans-serif;\n",
" font-size: 10.0px;\n",
" fill: black;\n",
" stroke: none;\n",
"}\n",
"\n",
"\n",
"\n",
"div#figureea16e463114e4a769c868a07182d0aed\n",
".axis line, .axis path {\n",
" shape-rendering: crispEdges;\n",
" stroke: black;\n",
" fill: none;\n",
"}\n",
"\n",
"div#figureea16e463114e4a769c868a07182d0aed\n",
".axis text {\n",
" font-family: sans-serif;\n",
" font-size: 10.0px;\n",
" fill: black;\n",
" stroke: none;\n",
"}\n",
"\n",
"\n",
"\n",
"div#figureea16e463114e4a769c868a07182d0aed\n",
".axes1\n",
"path.line3 {\n",
" stroke: #808080;\n",
" stroke-width: 1.0;\n",
" stroke-dasharray: none;\n",
" fill: none;\n",
" stroke-opacity: 1;\n",
"}\n",
"\n",
"div#figureea16e463114e4a769c868a07182d0aed\n",
".axes1\n",
"path.points3 {\n",
" stroke-width: 5;\n",
" stroke: #000000;\n",
" fill: #808080;\n",
" fill-opacity: 1;\n",
" stroke-opacity: 1;\n",
"}\n",
"\n",
"\n",
"\n",
"div#figureea16e463114e4a769c868a07182d0aed\n",
"text.textbf0c954711da48ea94c2e881373fb07d {\n",
" font-size : 10.0px;\n",
" fill : #000000;\n",
" opacity : 1;\n",
"}\n",
"\n",
"\n",
"\n",
"div#figureea16e463114e4a769c868a07182d0aed\n",
"text.text12c637ec96e144e1b4130618dd5c2fbe {\n",
" font-size : 10.0px;\n",
" fill : #000000;\n",
" opacity : 1;\n",
"}\n",
"\n",
"\n",
"\n",
"div#figureea16e463114e4a769c868a07182d0aed\n",
"text.textaff89213cfcf43ee8df06d90603e20ce {\n",
" font-size : 12.0px;\n",
" fill : #000000;\n",
" opacity : 1;\n",
"}\n",
"\n",
"\n",
"\n",
"\n",
"\n",
" </style>\n",
"\n",
"\n",
"\n",
" <div id='figureea16e463114e4a769c868a07182d0aed'>\n",
" </div>\n",
" <script type=\"text/javascript\">\n",
" var create_figea16e463114e4a769c868a07182d0aed = function(d3, undefined){\n",
"\n",
"\n",
" function Figure(figid, width, height){\n",
" this.figid = figid;\n",
" this.root = d3.select(figid);\n",
" this.width = width;\n",
" this.height = height;\n",
" this.axes = [];\n",
" }\n",
"\n",
" Figure.prototype.draw = function(){\n",
" this.canvas = this.root.append('svg:svg')\n",
" .attr('class', 'figure')\n",
" .attr('width', this.width)\n",
" .attr('height', this.height);\n",
" for (var i=0; i<this.axes.length; i++){\n",
" this.axes[i].draw();\n",
" }\n",
" };\n",
"\n",
" Figure.prototype.reset = function(duration){\n",
" duration = (typeof duration !== 'undefined') ? duration : 750;\n",
" for (var i=0; i<this.axes.length; i++){\n",
" this.axes[i].prep_reset();\n",
" }\n",
"\n",
" var transition = function(t){\n",
" for (var i=0; i<this.axes.length; i++){\n",
" this.axes[i].xdom(this.axes[i].xdom.domain(this.axes[i].ix(t)));\n",
" this.axes[i].ydom(this.axes[i].ydom.domain(this.axes[i].iy(t)));\n",
"\n",
" // don't propagate: this will be done as part of the loop.\n",
" this.axes[i].zoomed(false);\n",
" }\n",
" }.bind(this)\n",
"\n",
" d3.transition().duration(duration)\n",
" .tween(\"zoom\", function(){return transition;});\n",
"\n",
" for (var i=0; i<this.axes.length; i++){\n",
" this.axes[i].finalize_reset();\n",
" }\n",
" };\n",
"\n",
"\n",
"\n",
" function Axes(fig, bbox,\n",
" xlim, ylim,\n",
" xscale, yscale,\n",
" xdomain, ydomain,\n",
" xgridOn, ygridOn,\n",
" axclass, clipid,\n",
" zoomable){\n",
" this.axnum = fig.axes.length;\n",
" fig.axes.push(this);\n",
"\n",
" this.fig = fig;\n",
" this.bbox = bbox;\n",
" this.xlim = xlim;\n",
" this.ylim = ylim;\n",
" this.xdomain = xdomain;\n",
" this.ydomain = ydomain;\n",
" this.xscale = xscale;\n",
" this.yscale = yscale;\n",
" this.xgridOn = xgridOn;\n",
" this.ygridOn = ygridOn;\n",
" this.axclass = (typeof axclass !== 'undefined') ? axclass : \"axes\";\n",
" this.clipid = (typeof clipid != 'undefined') ? clipid : \"clip\";\n",
" this.zoomable = zoomable;\n",
"\n",
" this.sharex = [];\n",
" this.sharey = [];\n",
" this.elements = [];\n",
"\n",
" this.position = [this.bbox[0] * this.fig.width,\n",
" (1 - this.bbox[1] - this.bbox[3]) * this.fig.height];\n",
" this.width = bbox[2] * this.fig.width;\n",
" this.height = bbox[3] * this.fig.height;\n",
"\n",
" if(this.xscale === 'log'){\n",
" this.xdom = d3.scale.log();\n",
" }else if(this.xscale === 'date'){\n",
" this.xdom = d3.time.scale();\n",
" }else{\n",
" this.xdom = d3.scale.linear();\n",
" }\n",
"\n",
" if(this.yscale === 'log'){\n",
" this.ydom = d3.scale.log();\n",
" }else if(this.yscale === 'date'){\n",
" this.ydom = d3.time.scale();\n",
" }else{\n",
" this.ydom = d3.scale.linear();\n",
" }\n",
"\n",
" this.xdom.domain(this.xdomain)\n",
" .range([0, this.width]);\n",
"\n",
" this.ydom.domain(this.ydomain)\n",
" .range([this.height, 0]);\n",
"\n",
" if(this.xscale === 'date'){\n",
" this.xmap = d3.time.scale()\n",
" .domain(this.xdomain)\n",
" .range(this.xlim);\n",
" this.x = function(x){return this.xdom(this.xmap.invert(x));}\n",
" }else if(this.xscale === 'log'){\n",
" this.xmap = this.xdom;\n",
" this.x = this.xdom;\n",
" }else{\n",
" this.xmap = this.xdom;\n",
" this.x = this.xdom;\n",
" }\n",
"\n",
" if(this.yscale === 'date'){\n",
" this.ymap = d3.time.scale()\n",
" .domain(this.ydomain)\n",
" .range(this.ylim);\n",
" this.y = function(y){return this.ydom(this.ymap.invert(y));}\n",
" }else if(this.xscale === 'log'){\n",
" this.ymap = this.ydom;\n",
" this.y = this.ydom;\n",
" }else{\n",
" this.ymap = this.ydom;\n",
" this.y = this.ydom;\n",
" }\n",
" }\n",
"\n",
" Axes.prototype.draw = function(){\n",
" this.zoom = d3.behavior.zoom()\n",
" .x(this.xdom)\n",
" .y(this.ydom)\n",
" .on(\"zoom\", this.zoomed.bind(this));\n",
"\n",
" this.baseaxes = this.fig.canvas.append(\"g\")\n",
" .attr('transform', 'translate('\n",
" + this.position[0] + ','\n",
" + this.position[1] + ')')\n",
" .attr('width', this.width)\n",
" .attr('height', this.height)\n",
" .attr('class', \"baseaxes\");\n",
"\n",
" if(this.zoomable){\n",
" this.baseaxes.call(this.zoom);\n",
" }\n",
"\n",
" this.axesbg = this.baseaxes.append(\"svg:rect\")\n",
" .attr(\"width\", this.width)\n",
" .attr(\"height\", this.height)\n",
" .attr(\"class\", \"axesbg\");\n",
"\n",
" this.clip = this.baseaxes.append(\"svg:clipPath\")\n",
" .attr(\"id\", this.clipid)\n",
" .append(\"svg:rect\")\n",
" .attr(\"x\", 0)\n",
" .attr(\"y\", 0)\n",
" .attr(\"width\", this.width)\n",
" .attr(\"height\", this.height)\n",
"\n",
" this.axes = this.baseaxes.append(\"g\")\n",
" .attr(\"class\", this.axclass)\n",
" .attr(\"clip-path\", \"url(#\" + this.clipid + \")\");\n",
"\n",
" for(var i=0; i<this.elements.length; i++){\n",
" this.elements[i].draw();\n",
" }\n",
" };\n",
"\n",
" Axes.prototype.zoomed = function(propagate){\n",
" // propagate is a boolean specifying whether to propagate movements\n",
" // to shared axes, specified by sharex and sharey. Default is true.\n",
" propagate = (typeof propagate == 'undefined') ? true : propagate;\n",
"\n",
" //console.log(this.zoom.translate());\n",
" //console.log(this.zoom.scale());\n",
" //console.log(this.zoom.x().domain());\n",
" //console.log(this.zoom.y().domain());\n",
"\n",
" for(var i=0; i<this.elements.length; i++){\n",
" this.elements[i].zoomed();\n",
" }\n",
"\n",
" if(propagate){\n",
" // update shared x axes\n",
" for(var i=0; i<this.sharex.length; i++){\n",
" this.sharex[i].zoom.x().domain(this.zoom.x().domain());\n",
" this.sharex[i].zoomed(false);\n",
" }\n",
" // update shared y axes\n",
" for(var i=0; i<this.sharey.length; i++){\n",
" this.sharey[i].zoom.y().domain(this.zoom.y().domain());\n",
" this.sharey[i].zoomed(false);\n",
" }\n",
" }\n",
" };\n",
"\n",
" Axes.prototype.add_element = function(element){\n",
" this.elements.push(element);\n",
" };\n",
"\n",
" Axes.prototype.prep_reset = function(){\n",
" // interpolate() does not work on dates, so we map dates to numbers,\n",
" // interpolate the numbers, and then invert the map.\n",
" // we use the same strategy for log, so the interpolation will be smooth.\n",
" // There probably is a cleaner approach...\n",
"\n",
" if (this.xscale === 'date'){\n",
" var start = this.xdom.domain();\n",
" var end = this.xdomain;\n",
" var interp = d3.interpolate(\n",
" [this.xmap(start[0]), this.xmap(start[1])],\n",
" [this.xmap(end[0]), this.xmap(end[1])]);\n",
" this.ix = function(t){\n",
" return [this.xmap.invert(interp(t)[0]),\n",
" this.xmap.invert(interp(t)[1])];\n",
" }\n",
" }else{\n",
" this.ix = d3.interpolate(this.xdom.domain(), this.xlim);\n",
" }\n",
"\n",
" if (this.yscale === 'date'){\n",
" var start = this.ydom.domain();\n",
" var end = this.ydomain;\n",
" var interp = d3.interpolate(\n",
" [this.ymap(start[0]), this.ymap(start[1])],\n",
" [this.ymap(end[0]), this.ymap(end[1])]);\n",
" this.iy = function(t){\n",
" return [this.ymap.invert(interp(t)[0]),\n",
" this.ymap.invert(interp(t)[1])];\n",
" }\n",
" }else{\n",
" this.iy = d3.interpolate(this.ydom.domain(), this.ylim);\n",
" }\n",
" }\n",
"\n",
" Axes.prototype.finalize_reset = function(){\n",
" this.zoom.scale(1).translate([0, 0]);\n",
" }\n",
"\n",
" Axes.prototype.reset = function(){\n",
" this.prep_reset();\n",
" d3.transition().duration(750).tween(\"zoom\", function() {\n",
" return function(t) {\n",
" this.zoom.x(this.xdom.domain(this.ix(t)))\n",
" .y(this.ydom.domain(this.iy(t)));\n",
" this.zoomed();\n",
" };\n",
" });\n",
" this.finalize_reset();\n",
" };\n",
"\n",
"\n",
"\n",
" function Axis(axes, position, nticks, tickvalues, tickformat){\n",
" this.axes = axes;\n",
" this.position = position;\n",
" this.nticks = nticks;\n",
" this.tickvalues = tickvalues;\n",
" this.tickformat = tickformat;\n",
" if (position == \"bottom\"){\n",
" this.transform = \"translate(0,\" + this.axes.height + \")\";\n",
" this.scale = this.axes.xdom;\n",
" this.class = \"x axis\";\n",
" }else if (position == \"top\"){\n",
" this.transform = \"translate(0,0)\"\n",
" this.scale = this.axes.xdom;\n",
" this.class = \"x axis\";\n",
" }else if (position == \"left\"){\n",
" this.transform = \"translate(0,0)\";\n",
" this.scale = this.axes.ydom;\n",
" this.class = \"y axis\";\n",
" }else{\n",
" this.transform = \"translate(\" + this.axes.width + \",0)\";\n",
" this.scale = this.axes.ydom;\n",
" this.class = \"y axis\";\n",
" }\n",
" }\n",
"\n",
" Axis.prototype.draw = function(){\n",
" this.axis = d3.svg.axis()\n",
" .scale(this.scale)\n",
" .orient(this.position)\n",
" .ticks(this.nticks)\n",
" .tickValues(this.tickvalues)\n",
" .tickFormat(this.tickformat);\n",
" this.elem = this.axes.baseaxes.append('g')\n",
" .attr(\"transform\", this.transform)\n",
" .attr(\"class\", this.class)\n",
" .call(this.axis);\n",
" };\n",
"\n",
" Axis.prototype.zoomed = function(){\n",
" this.elem.call(this.axis);\n",
" };\n",
"\n",
"\n",
"\n",
" function Grid(axes, xy){\n",
" this.axes = axes;\n",
" this.class = xy + \" grid\"\n",
" if(xy == \"x\"){\n",
" this.transform = \"translate(0,\" + this.axes.height + \")\";\n",
" this.position = \"bottom\";\n",
" this.scale = this.axes.xdom;\n",
" this.tickSize = -this.axes.height;\n",
" }else{\n",
" this.transform = \"translate(0,0)\";\n",
" this.position = \"left\";\n",
" this.scale = this.axes.ydom;\n",
" this.tickSize = -this.axes.width;\n",
" }\n",
" }\n",
"\n",
" Grid.prototype.draw = function(){\n",
" this.grid = d3.svg.axis()\n",
" .scale(this.scale)\n",
" .orient(this.position)\n",
" .tickSize(this.tickSize, 0, 0)\n",
" .tickFormat(\"\");\n",
" this.elem = this.axes.axes.append(\"g\")\n",
" .attr(\"class\", this.class)\n",
" .attr(\"transform\", this.transform)\n",
" .call(this.grid);\n",
" };\n",
"\n",
" Grid.prototype.zoomed = function(){\n",
" this.elem.call(this.grid);\n",
" };\n",
"\n",
"\n",
"\n",
" // This function constructs a mapped SVG path\n",
" // from an input data array\n",
" var construct_SVG_path = function(data, xmap, ymap){\n",
" var result = \"\";\n",
" for (var i=0;i<data.length;i++){\n",
" result += data[i][0];\n",
" if(data[i][0] == 'Z'){\n",
" continue;\n",
" }\n",
" for (var j=0;j<data[i][1].length;j++){\n",
" if(j % 2 == 0){\n",
" result += \" \" + xmap(data[i][1][j]);\n",
" }else{\n",
" result += \" \" + ymap(data[i][1][j]);\n",
" }\n",
" }\n",
" result += \" \";\n",
" }\n",
" return result;\n",
" };\n",
"\n",
"\n",
" var figwidth = 6.0 * 80;\n",
" var figheight = 4.0 * 80;\n",
" var fig = new Figure(\"div#figureea16e463114e4a769c868a07182d0aed\",\n",
" figwidth, figheight);\n",
"\n",
"\n",
"\n",
" var ax1 = new Axes(fig, [0.125, 0.125, 0.77500000000000002, 0.77500000000000002], [0.0, 9.0], [0.0, 9.0],\n",
" \"linear\", \"linear\",\n",
" [0.0, 9.0], [0.0, 9.0],\n",
" false, false,\n",
" \"axes1\",\n",
" \"clipea16e463114e4a769c868a07182d0aed1\", true);\n",
"\n",
"\n",
"\n",
"// Add an Axis element\n",
"ax1.add_element(new Axis(ax1, \"bottom\",\n",
" 10, null,\n",
" null));\n",
"\n",
"\n",
"\n",
"// Add an Axis element\n",
"ax1.add_element(new Axis(ax1, \"left\",\n",
" 10, null,\n",
" null));\n",
"\n",
"\n",
"\n",
"// Add a Line2D element\n",
"var line71bba1ad8ee74e75a46e73f125e0ee9b = new function(){\n",
" this.data = [[0.0, 0.0], [1.0, 1.0], [2.0, 2.0], [3.0, 3.0], [4.0, 4.0], [5.0, 5.0], [6.0, 6.0], [7.0, 7.0], [8.0, 8.0], [9.0, 9.0]];\n&