Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save aflaxman/8377948 to your computer and use it in GitHub Desktop.
Save aflaxman/8377948 to your computer and use it in GitHub Desktop.
prototype of mpld3 plugin for fisheye distortion
Display the source blob
Display the rendered blob
Raw
{
"metadata": {
"name": "2014_01_11b_mpld3_fisheye_plugin_prototype"
},
"nbformat": 3,
"nbformat_minor": 0,
"worksheets": [
{
"cells": [
{
"cell_type": "code",
"collapsed": false,
"input": "!date",
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": "Sat Jan 11 14:34:18 PST 2014\r\n"
}
],
"prompt_number": 1
},
{
"cell_type": "markdown",
"metadata": {},
"source": "# Fisheye Distortion Plugin\n\nmpld3 creater Jake Vanderplas [recently developed a plugin mechanism](http://jakevdp.github.io/blog/2014/01/10/d3-plugins-truly-interactive/) to permit adding custom interactivity to mpld3 plots. I had a little time to give it a try, and put together something I've always wanted: [Cartesian fisheye distortion](http://bost.ocks.org/mike/fisheye/#cartesian) for scatter plots."
},
{
"cell_type": "code",
"collapsed": false,
"input": "%pylab\nimport matplotlib.pyplot as plt, mpld3, mpld3.plugins\nmpld3.enable_notebook(d3_url=\"/files/d3.v3.js\")",
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": "Using matplotlib backend: module://IPython.kernel.zmq.pylab.backend_inline\nPopulating the interactive namespace from numpy and matplotlib\n"
}
],
"prompt_number": 2
},
{
"cell_type": "markdown",
"metadata": {},
"source": "# Here is the result:"
},
{
"cell_type": "code",
"collapsed": false,
"input": "fig = plt.figure()\nxx = round_(randn(100), 2)\nyy = round_(randn(100), 2)\nscatter = plot(xx, yy, 's', color='k', mec='grey', mew=3)\n\nfig.plugins = [mpld3.plugins.PointLabelTooltip(scatter[0]), FishEye()]",
"language": "python",
"metadata": {},
"outputs": [
{
"html": "\n\n\n <script type=\"text/javascript\" src=\"http://d3js.org/d3.v2.min.js?2.9.4\"></script>\n\n <script type=\"text/javascript\">\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 = function() { return this.axes.xdom; }; // changed here, and 3 analogous spots below\n this.class = \"x axis\";\n }else if (position == \"top\"){\n this.transform = \"translate(0,0)\"\n this.scale = function() { return this.axes.xdom; }; // changed\n this.class = \"x axis\";\n }else if (position == \"left\"){\n this.transform = \"translate(0,0)\";\n this.scale = function() { return this.axes.ydom; }; // changed\n this.class = \"y axis\";\n }else{\n this.transform = \"translate(\" + this.axes.width + \",0)\";\n this.scale = function() { return this.axes.ydom; }; // changed\n this.class = \"y axis\";\n }\n }\n\n Axis.prototype.draw = function(){\n\n this.axis = d3.svg.axis()\n .scale(this.scale()) // changed here, now scale is a function \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 gFig = this.axis;\n this.axis.scale(this.scale());\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 </script>\n\n\n <style>\n\n\n div#figure46f4dec5a47e40c797519491beecee6a\n .axesbg{\n fill: #FFFFFF;\n }\n\n\n\ndiv#figure46f4dec5a47e40c797519491beecee6a\n.axis line, .axis path {\n shape-rendering: crispEdges;\n stroke: black;\n fill: none;\n}\n\ndiv#figure46f4dec5a47e40c797519491beecee6a\n.axis text {\n font-family: sans-serif;\n font-size: 10.0px;\n fill: black;\n stroke: none;\n}\n\n\n\ndiv#figure46f4dec5a47e40c797519491beecee6a\n.axis line, .axis path {\n shape-rendering: crispEdges;\n stroke: black;\n fill: none;\n}\n\ndiv#figure46f4dec5a47e40c797519491beecee6a\n.axis text {\n font-family: sans-serif;\n font-size: 10.0px;\n fill: black;\n stroke: none;\n}\n\n\n\ndiv#figure46f4dec5a47e40c797519491beecee6a\n.axes1\npath.line3 {\n stroke: #000000;\n stroke-width: 1.0;\n stroke-dasharray: none;\n fill: none;\n stroke-opacity: 1;\n}\n\ndiv#figure46f4dec5a47e40c797519491beecee6a\n.axes1\npath.points3 {\n stroke-width: 3;\n stroke: #808080;\n fill: #000000;\n fill-opacity: 1;\n stroke-opacity: 1;\n}\n\n\n\ndiv#figure46f4dec5a47e40c797519491beecee6a\ntext.textf7d1540919844cc6b076e750cfe7b978 {\n font-size : 10.0px;\n fill : #000000;\n opacity : 1;\n}\n\n\n\ndiv#figure46f4dec5a47e40c797519491beecee6a\ntext.textaeda81d3de814c52ba5c441e9cca8ea2 {\n font-size : 10.0px;\n fill : #000000;\n opacity : 1;\n}\n\n\n\ndiv#figure46f4dec5a47e40c797519491beecee6a\ntext.text85133adcf7b74c84b350da510e7c9762 {\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='figure46f4dec5a47e40c797519491beecee6a'>\n\n </div>\n <script type=\"text/javascript\">\n var create_fig46f4dec5a47e40c797519491beecee6a = function(){\n var figwidth = 6.0 * 80;\n var figheight = 4.0 * 80;\n var fig = new Figure(\"div#figure46f4dec5a47e40c797519491beecee6a\",\n figwidth, figheight);\n\n\n\n var ax1 = new Axes(fig, [0.125, 0.125, 0.77500000000000002, 0.77500000000000002], [-3.0, 3.0], [-3.0, 3.0],\n \"linear\", \"linear\",\n [-3.0, 3.0], [-3.0, 3.0],\n false, false,\n \"axes1\",\n \"clip46f4dec5a47e40c797519491beecee6a1\", true);\n\n\n\n// Add an Axis element\nax1.add_element(new Axis(ax1, \"bottom\",\n 7, null,\n null));\n\n\n\n// Add an Axis element\nax1.add_element(new Axis(ax1, \"left\",\n 7, null,\n null));\n\n\n\n// Add a Line2D element\nvar linea87cd232377b455493752d7589841c5a = new function(){\n this.data = [[1.1799999999999999, -0.14000000000000001], [1.0900000000000001, -1.3500000000000001], [-1.01, -0.67000000000000004], [-1.4199999999999999, 0.67000000000000004], [-1.3899999999999999, -0.40999999999999998], [2.3999999999999999, -0.77000000000000002], [-0.11, -0.70999999999999996], [0.29999999999999999, -0.76000000000000001], [0.52000000000000002, -0.20999999999999999], [1.02, -0.029999999999999999], [-0.81000000000000005, 0.44], [0.14999999999999999, 0.5], [0.26000000000000001, -0.050000000000000003], [0.40999999999999998, 0.42999999999999999], [2.6200000000000001, 0.84999999999999998], [0.76000000000000001, -1.5], [-0.53000000000000003, 1.6699999999999999], [0.10000000000000001, 0.66000000000000003], [2.4900000000000002, 0.81000000000000005], [-0.95999999999999996, -2.8599999999999999], [-1.6699999999999999, 0.40000000000000002], [-0.83999999999999997, 0.5], [-0.050000000000000003, -0.40999999999999998], [0.82999999999999996, 1.23], [0.40000000000000002, -0.66000000000000003], [0.059999999999999998, 0.51000000000000001], [-1.46, 0.28999999999999998], [0.47999999999999998, 0.47999999999999998], [-1.49, 1.5800000000000001], [0.93999999999999995, 1.6499999999999999], [-1.1399999999999999, 1.0900000000000001], [0.10000000000000001, -1.1599999999999999], [-0.11, 1.71], [-1.9099999999999999, 0.65000000000000002], [0.080000000000000002, -0.0], [-0.47999999999999998, -2.4399999999999999], [0.96999999999999997, -1.1699999999999999], [-0.75, -0.070000000000000007], [0.48999999999999999, 0.5], [1.48, 1.02], [-0.59999999999999998, -1.4099999999999999], [0.31, -0.53000000000000003], [0.35999999999999999, 0.60999999999999999], [-1.23, 1.3100000000000001], [-0.39000000000000001, -2.29], [-0.68000000000000005, 0.17999999999999999], [-0.28000000000000003, 0.14999999999999999], [-1.4199999999999999, -1.8500000000000001], [1.52, -1.3400000000000001], [0.58999999999999997, 0.39000000000000001], [2.2000000000000002, 0.40999999999999998], [-1.0, 0.80000000000000004], [-0.34000000000000002, 0.11], [1.8500000000000001, 1.9099999999999999], [1.51, 0.92000000000000004], [0.34000000000000002, -0.029999999999999999], [1.3999999999999999, -2.04], [-1.4399999999999999, 1.4199999999999999], [0.59999999999999998, -0.080000000000000002], [1.3400000000000001, 0.44], [1.72, 1.77], [-0.53000000000000003, 1.6699999999999999], [0.22, 0.89000000000000001], [0.58999999999999997, -0.02], [1.3999999999999999, -0.10000000000000001], [-0.17000000000000001, 0.02], [2.8300000000000001, 0.64000000000000001], [0.40999999999999998, 2.8500000000000001], [-2.4500000000000002, -0.29999999999999999], [-0.42999999999999999, -0.89000000000000001], [2.6000000000000001, -0.71999999999999997], [-0.58999999999999997, -0.52000000000000002], [0.93000000000000005, 0.70999999999999996], [0.84999999999999998, -0.98999999999999999], [0.88, -0.85999999999999999], [-1.5, 0.73999999999999999], [0.28000000000000003, -1.03], [-1.72, -1.6000000000000001], [0.28999999999999998, 1.3300000000000001], [-0.16, 0.65000000000000002], [0.20000000000000001, -1.24], [-0.87, 0.81999999999999995], [-0.41999999999999998, -0.22], [0.63, -1.8999999999999999], [-2.0600000000000001, -1.5800000000000001], [-1.1499999999999999, -0.46999999999999997], [0.60999999999999999, -1.71], [-0.089999999999999997, 0.059999999999999998], [-1.7, 0.34999999999999998], [-0.81000000000000005, -0.29999999999999999], [-0.56999999999999995, -0.34999999999999998], [-0.26000000000000001, -0.66000000000000003], [-0.12, -1.23], [1.3700000000000001, 1.05], [0.67000000000000004, 1.1799999999999999], [1.9099999999999999, -1.3700000000000001], [-0.46999999999999997, 0.17999999999999999], [0.66000000000000003, -0.029999999999999999], [-0.39000000000000001, 0.80000000000000004], [1.3600000000000001, 0.70999999999999996]];\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\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(\"square\")\n .size(36))\n .attr(\"transform\", this.translate.bind(this));\n\n\n };\n\n this.zoomed = function(){\n\n\n\n this.pointsobj.attr(\"transform\", this.translate.bind(this));\n\n\n }\n};\n\nax1.add_element(linea87cd232377b455493752d7589841c5a);\n\n\n\n\n// Add a text element\nax1.add_element(new function(){\n this.position = [246.0, 0.0];\n this.rotation = -0.0;\n this.ax = ax1;\n this.text = \"\";\n\n this.draw = function(){\n\n this.obj = this.ax.fig.canvas.append(\"text\")\n .attr(\"x\", this.position[0])\n .attr(\"y\", this.ax.fig.height - this.position[1])\n\n .attr(\"transform\", \"rotate(\" + this.rotation + \",\"\n + this.position[0] + \",\"\n + (figheight - this.position[1]) + \")\")\n\n\n .attr(\"class\", \"text\")\n .text(this.text)\n .attr(\"class\", \"textf7d1540919844cc6b076e750cfe7b978\")\n .attr(\"style\", \"text-anchor: middle;\");\n }\n\n this.zoomed = function(){\n\n }\n});\n\n\n\n\n\n// Add a text element\nax1.add_element(new function(){\n this.position = [10.0, 164.0];\n this.rotation = -90.0;\n this.ax = ax1;\n this.text = \"\";\n\n this.draw = function(){\n\n this.obj = this.ax.fig.canvas.append(\"text\")\n .attr(\"x\", this.position[0])\n .attr(\"y\", this.ax.fig.height - this.position[1])\n\n .attr(\"transform\", \"rotate(\" + this.rotation + \",\"\n + this.position[0] + \",\"\n + (figheight - this.position[1]) + \")\")\n\n\n .attr(\"class\", \"text\")\n .text(this.text)\n .attr(\"class\", \"textaeda81d3de814c52ba5c441e9cca8ea2\")\n .attr(\"style\", \"text-anchor: end;\");\n }\n\n this.zoomed = function(){\n\n }\n});\n\n\n\n\n\n// Add a text element\nax1.add_element(new function(){\n this.position = [246.0, 293.55555555555554];\n this.rotation = -0.0;\n this.ax = ax1;\n this.text = \"\";\n\n this.draw = function(){\n\n this.obj = this.ax.fig.canvas.append(\"text\")\n .attr(\"x\", this.position[0])\n .attr(\"y\", this.ax.fig.height - this.position[1])\n\n .attr(\"transform\", \"rotate(\" + this.rotation + \",\"\n + this.position[0] + \",\"\n + (figheight - this.position[1]) + \")\")\n\n\n .attr(\"class\", \"text\")\n .text(this.text)\n .attr(\"class\", \"text85133adcf7b74c84b350da510e7c9762\")\n .attr(\"style\", \"text-anchor: middle;\");\n }\n\n this.zoomed = function(){\n\n }\n});\n\n\n\n\n\n\n\n\n\n\n\n fig.draw();\n\n\n\n\n var tooltip0e3be4abc11e4851b2d88864b8fccfd3 = fig.canvas.append(\"text\")\n .attr(\"class\", \"tooltip-text\")\n .attr(\"x\", 0)\n .attr(\"y\", 0)\n .text(\"\")\n .attr(\"style\", \"text-anchor: middle;\")\n .style(\"visibility\", \"hidden\");\n\n\n\n ax1.axes.selectAll(\".points3\")\n .on(\"mouseover\", function(d, i){\n tooltip0e3be4abc11e4851b2d88864b8fccfd3\n .style(\"visibility\", \"visible\")\n\n .text(\"(\" + d[0] + \", \" + d[1] + \")\")\n ;})\n .on(\"mousemove\", function(d, i){\n // For some reason, this doesn't work in the notebook\n // xy = d3.mouse(fig.canvas.node());\n // use this instead\n var ctm = fig.canvas.node().getScreenCTM();\n tooltip0e3be4abc11e4851b2d88864b8fccfd3\n .attr('x', event.x - ctm.e - 0)\n .attr('y', event.y - ctm.f - 10);})\n .on(\"mouseout\", function(d, i){tooltip0e3be4abc11e4851b2d88864b8fccfd3.style(\"visibility\",\n \"hidden\");});\n\n var a = fig.axes[0],\n xFisheye = d3.fisheye.scale(function() {return a.x;}).focus(360),\n yFisheye = d3.fisheye.scale(function() {return a.y;}).focus(90);\n\n fig.canvas.on(\"mousemove\", function() {\n var mouse = d3.mouse(this);\n xFisheye.focus(mouse[0]);\n yFisheye.focus(mouse[1]);\n a.zoomed(true);\n });\n\n a.xdom = xFisheye;\n a.xmap = xFisheye;\n a.x = xFisheye;\n\n a.ydom = yFisheye;\n a.ymap = yFisheye;\n a.y = yFisheye;\n\n\n return fig\n }\n\n // var fig_46f4dec5a47e40c797519491beecee6a = create_fig46f4dec5a47e40c797519491beecee6a();\n // set a timeout of 0: this makes things work in the IPython notebook\n setTimeout(create_fig46f4dec5a47e40c797519491beecee6a, 0);\n </script>\n\n <script src=\"http://bost.ocks.org/mike/fisheye/fisheye.js?0.0.3\"></script>\n\n\n\n",
"metadata": {},
"output_type": "display_data",
"png": "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\n/g9bN1ux9G5/9AAAAABJRU5ErkJggg==\n",
"text": "<matplotlib.figure.Figure at 0x285c450>"
}
],
"prompt_number": 5
},
{
"cell_type": "markdown",
"metadata": {},
"source": "# Here is the prototype plugin code:"
},
{
"cell_type": "code",
"collapsed": false,
"input": "from mpld3.plugins import PluginBase\nimport jinja2\nimport json\n\n\nclass FishEye(PluginBase):\n \"\"\"A interactive fish eye distortion plugin\"\"\"\n HTML = jinja2.Template(\"\"\"\n <script src=\"files/fisheye.js\"></script>\n \"\"\")\n FIG_JS = jinja2.Template(\"\"\"\n var a = fig.axes[0],\n xFisheye = d3.fisheye.scale(function() {return a.x;}).focus(360),\n yFisheye = d3.fisheye.scale(function() {return a.y;}).focus(90);\n \n fig.canvas.on(\"mousemove\", function() {\n var mouse = d3.mouse(this);\n xFisheye.focus(mouse[0]);\n yFisheye.focus(mouse[1]);\n a.zoomed(true);\n });\n\n a.xdom = xFisheye;\n a.xmap = xFisheye;\n a.x = xFisheye;\n \n a.ydom = yFisheye;\n a.ymap = yFisheye;\n a.y = yFisheye;\n \"\"\")\n\n def __init__(self):\n self.id = self.generate_unique_id()\n\n def _fig_js_args(self):\n return dict(id=self.id)",
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 3
},
{
"cell_type": "markdown",
"metadata": {},
"source": "In order to make the scales update properly, I had to tweak the AXIS_CLASS javascript as well. Maybe there is a better way to do this, though."
},
{
"cell_type": "code",
"collapsed": false,
"input": "# monkey patch AXIS_CLASS to take scale from axes even if it has been changed\nAXIS_CLASS = \"\"\"\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 = function() { return this.axes.xdom; }; // changed here, and 3 analogous spots below\n this.class = \"x axis\";\n }else if (position == \"top\"){\n this.transform = \"translate(0,0)\"\n this.scale = function() { return this.axes.xdom; }; // changed\n this.class = \"x axis\";\n }else if (position == \"left\"){\n this.transform = \"translate(0,0)\";\n this.scale = function() { return this.axes.ydom; }; // changed\n this.class = \"y axis\";\n }else{\n this.transform = \"translate(\" + this.axes.width + \",0)\";\n this.scale = function() { return this.axes.ydom; }; // changed\n this.class = \"y axis\";\n }\n }\n\n Axis.prototype.draw = function(){\n\n this.axis = d3.svg.axis()\n .scale(this.scale()) // changed here, now scale is a function \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 gFig = this.axis;\n this.axis.scale(this.scale());\n this.elem.call(this.axis);\n };\n\"\"\"\n\nmpld3._js.ALL_FUNCTIONS[2] = AXIS_CLASS\n",
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 4
},
{
"cell_type": "markdown",
"metadata": {},
"source": "And to get it to work on nbviewer, I had to edit a few .js urls by hand. I tried to put that in the github gist for my own reference in the future."
}
],
"metadata": {}
}
]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment