{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Axially symmetric modeling\n", "\n", "*Prof. dr.ir. T.N.Olsthoorn*\n", "\n", "*Heemstede, Okt. 2016, 24 May 2017*" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Theory" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "def inpoly(...)\n" ] } ], "source": [ "myModules = '/Users/Theo/GRWMODELS/python/modules/fdm/'\n", "\n", "import sys\n", "if not myModules in sys.path:\n", " sys.path.append(myModules)\n", "\n", "import mfgrid" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Often it is useful to simulate groundwater flow in an axial symmetric using only the coordinates x and z, where x stands for the radius r and y is not used. The thickness in y direction is no longer constant but varies with x as $y = 2 \\pi x^2$. Note that here $x>0$. Columns in the grid are now, in fact, cylinders with thickness equal to that of the corresponding column.\n", "\n", "To alow this, we have to adapt the computation of the cell resistances. Because in axial flow we have\n", "\n", "$\\Delta \\phi = \\frac {Q} {2 \\pi k_x \\Delta z} \\ln \\frac {r_1} {r_2} $\n", "\n", "The resistance $R_x = \\frac {\\Delta \\phi} {Q} = \\frac 1 {2 \\pi k_x \\Delta z} \\ln \\frac {r_2} {r_1} $, with $r_2 > r_1$\n", "\n", "We can thus write out the resistance against horitonzal axial flow between the innermost cell face at $x=r_1$ and the cell center at $x=r_m$ as well as the resistance between the cell center and the outer most cell face at $x=r_2$." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " Rx1 = 1 / (2 * pi * kx * dz) * log(xm / x[:-1])\n", " Rx2 = 1 / (2 * pi * kx * dz) * log(x[1:] / xm)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In vertical direction we have the resistance due to vertical flow through the rings\n", "\n", "$R_z = \\frac {\\Delta z} { k_z \\pi (r_2^2 - r_1^2) }$\n", "\n", "These resistances thus become:" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ " Rz = dz / (kz * pi * (x[1:]**2 - x[:-1]**2))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "And the cell conductances now become" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ " Cx = 1 / (Rx1[1:] + Rx2[:-1])\n", " Cy = np.zeros(sz)\n", " Cz = 0.5 / (Rz[:,:,1:] + Rz[:,:,:-1])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Notice that the conductance in the y direction is left in and set to zeros. This implies that there will be no flow between adjacent model rows. When we now use a model with more than one row, we can look at it as a set of simultaneously computed independent axisymmetric models, because these models all share the same distance from zero by their x-coordinate and have no mutual interaction in the y-direction. Different axisymmetric models can thus be simulated simultaneously, which may be useful for comparisons, sensitivity compuatations and for calibration." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "These are the only changes we have to make to convert our 3D groundwater model to an axisymmetric model.\n", "\n", "We can build this into our existing model and use a switch to tell the model to run the rectangular or the axisymmetric case.\n", "\n", "In Python this is easily done using a named input parameter `axial=false`.\n", "\n", "The call would then look as follows" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " Phi = fdm3(x, y, z, kx, ky, kz, FQ, HI, IBOUND, axial=true)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The y coordinate vector is irrelevant here. It can be left empty or set to any value, e.g. `none`. Whatever it is, it will be ignored in the axisymmetrical case. Notice that the size of the model is obtained from the IBOUND array." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Implementation; the adepted module to include axial symmetry" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Overwriting fdm_d.py\n" ] } ], "source": [ "%%writefile fdm_d.py\n", "\n", "import numpy as np\n", "import pdb\n", "import scipy.sparse as sp\n", "from scipy.sparse.linalg import spsolve # to use its short name\n", "from collections import namedtuple\n", "\n", "class InputError(Exception):\n", " pass\n", "\n", "def quivdata(Out, gr, iz=0):\n", " \"\"\"Returns coordinates and velocity components to show velocity with quiver\n", " \n", " Compute arrays to display velocity vectors using matplotlib's quiver.\n", " The quiver is always drawn in the xy-plane for a specific layer. Default iz=0\n", " \n", " Parameters\n", " ----------\n", " `Out` : namedtuple holding arrays `Qx`, `Qy`, `Qz` as defined in `fdm3`\n", " `Qx` : ndarray, shape: (Nz, Ny, Nx-1), [L3/T]\n", " Interfacial flows in finite difference model in x-direction from `fdm3'\n", " `Qy` : ndarray, shape: (Nz, Ny-1, Nx), [L3/T]\n", " Interfacial flows in finite difference model in y-direction from `fdm3`\n", " `Qz` : ndarray, shape: (Nz-1, Ny, Nx), [L3/T]\n", " Interfacial flows in finite difference model in z-direction from `fdm3` \n", " `gr` : `grid_object` generated by Grid\n", " `iz` : int [-]\n", " iz is the number of the layer for which the data are requested,\n", " and all output arrays will be 2D for that layer.\n", " if iz==None, then all outputs will be full 3D arrays and cover all layers\n", " simultaneously\n", "\n", " Returns\n", " -------\n", " `Xm` : ndarray, shape: (Nz, Ny, Nx), [L]\n", " x-coordinates of cell centers\n", " `Ym` : ndarray, shape: (Nz, Ny, Nx), [L]\n", " y-coodinates of cell centers\n", " `ZM` : ndarray, shape: (Nz, Ny, Nx), [L]\n", " `z`-coordinates at cell centers\n", " `U` : ndarray, shape: (Nz, Ny, Nx), [L3/d]\n", " Flow in `x`-direction at cell centers\n", " `V` : ndarray, shape: (Nz, Ny, Nx), [L3/T]\n", " Flow in `y`-direction at cell centers\n", " `W` : ndarray, shape: (Nz, Ny, Nx), [L3/T]\n", " Flow in `z`-direction at cell centers.\n", " \n", " \"\"\"\n", " \n", " X, Y = np.meshgrid(gr.xm, gr.ym) # coordinates of cell centers\n", " \n", " shp = (gr.Ny, gr.Nx) # 2D tuple to select a single layer\n", " \n", " # Flows at cell centers\n", " U = np.concatenate((Out.Qx[iz, :, 0].reshape((1, gr.Ny, 1)), \\\n", " 0.5 * (Out.Qx[iz, :, :-1].reshape((1, gr.Ny, gr.Nx-2)) +\\\n", " Out.Qx[iz, :, 1: ].reshape((1, gr.Ny, gr.Nx-2))), \\\n", " Out.Qx[iz, :, -1].reshape((1, gr.Ny, 1))), axis=2).reshape(shp)\n", " V = np.concatenate((Out.Qy[iz, 0, :].reshape((1, 1, gr.Nx)), \\\n", " 0.5 * (Out.Qy[iz, :-1, :].reshape((1, gr.Ny-2, gr.Nx)) +\\\n", " Out.Qy[iz, 1:, :].reshape((1, gr.Ny-2, gr.Nx))), \\\n", " Out.Qy[iz, -1, :].reshape((1, 1, gr.Nx))), axis=1).reshape(shp)\n", " return X, Y, U, V\n", "\n", "\n", "def unique(x, tol=0.0001):\n", " \"\"\"return sorted unique values of x, keeping ascending or descending direction\"\"\"\n", " if x[0]>x[-1]: # vector is reversed\n", " x = np.sort(x)[::-1] # sort and reverse\n", " return x[np.hstack((np.diff(x) < -tol, True))]\n", " else:\n", " x = np.sort(x)\n", " return x[np.hstack((np.diff(x) > +tol, True))]\n", "\n", " \n", "def fdm3(gr, kx, ky, kz, FQ, HI, IBOUND):\n", " '''Steady state 3D Finite Difference Model returning computed heads and flows.\n", " \n", " Heads and flows are returned as 3D arrays as specified under output parmeters.\n", " \n", " Parameters\n", " ----------\n", " 'gr' : `grid_object`, generated by gr = Grid(x, y, z, ..)\n", " if `gr.axial`==True, then the model is run in axially symmetric model\n", " `kx`, `ky`, `kz` : ndarray, shape: (Nz, Ny, Nx), [L/T]\n", " hydraulic conductivities along the three axes, 3D arrays.\n", " `FQ` : ndarray, shape: (Nz, Ny, Nx), [L3/T]\n", " prescrived cell flows (injection positive, zero of no inflow/outflow)\n", " `IH` : ndarray, shape: (Nz, Ny, Nx), [L]\n", " initial heads. `IH` has the prescribed heads for the cells with prescribed head.\n", " `IBOUND` : ndarray, shape: (Nz, Ny, Nx) of int\n", " boundary array like in MODFLOW with values denoting\n", " * IBOUND>0 the head in the corresponding cells will be computed\n", " * IBOUND=0 cells are inactive, will be given value NaN\n", " * IBOUND<0 coresponding cells have prescribed head\n", " \n", " outputs\n", " ------- \n", " `Out` : namedtuple containing heads and flows:\n", " `Out.Phi` : ndarray, shape: (Nz, Ny, Nx), [L3/T] \n", " computed heads. Inactive cells will have NaNs\n", " `Out.Q` : ndarray, shape: (Nz, Ny, Nx), [L3/T]\n", " net inflow in all cells, inactive cells have 0\n", " `Out.Qx : ndarray, shape: (Nz, Ny, Nx-1), [L3/T] \n", " intercell flows in x-direction (parallel to the rows)\n", " `Out.Qy` : ndarray, shape: (Nz, Ny-1, Nx), [L3/T] \n", " intercell flows in y-direction (parallel to the columns)\n", " `Out.Qz` : ndarray, shape: (Nz-1, Ny, Nx), [L3/T] \n", " intercell flows in z-direction (vertially upward postitive)\n", " the 3D array with the final heads with `NaN` at inactive cells.\n", " \n", " TO 160905\n", " '''\n", "\n", " import pdb\n", " \n", " # define the named tuple to hold all the output of the model fdm3\n", " Out = namedtuple('Out',['Phi', 'Q', 'Qx', 'Qy', 'Qz'])\n", " Out.__doc__ = \"\"\"fdm3 output, , containing fields Phi, Qx, Qy and Qz\\n \\\n", " Use Out.Phi, Out.Q, Out.Qx, Out.Qy and Out.Qz\"\"\" \n", " \n", " if gr.axial:\n", " print('Running in axial mode, y-values are ignored.')\n", "\n", " if kx.shape != gr.shape:\n", " raise AssertionError(\"shape of kx {0} differs from that of model {1}\".format(kx.shape,SHP))\n", " if ky.shape != gr.shape:\n", " raise AssertionError(\"shape of ky {0} differs from that of model {1}\".format(ky.shape,SHP))\n", " if kz.shape != gr.shape:\n", " raise AssertionError(\"shape of kz {0} differs from that of model {1}\".format(kz.shape,SHP))\n", " \n", " active = (IBOUND>0).reshape(gr.Nod,) # boolean vector denoting the active cells\n", " inact = (IBOUND==0).reshape(gr.Nod,) # boolean vector denoting inacive cells\n", " fxhd = (IBOUND<0).reshape(gr.Nod,) # boolean vector denoting fixed-head cells\n", "\n", " # reshaping shorthands\n", " dx = np.reshape(gr.dx, (1, 1, gr.Nx))\n", " dy = np.reshape(gr.dy, (1, gr.Ny, 1))\n", "\n", " # half cell flow resistances\n", " if not gr.axial:\n", " Rx1 = 0.5 * dx / ( dy * gr.DZ) / kx\n", " Rx2 = Rx1\n", " Ry1 = 0.5 * dy / (gr.DZ * dx) / ky\n", " Rz1 = 0.5 * gr.DZ / ( dx * dy) / kz\n", " else:\n", " min_dx = 0.000001\n", " x = gr.x; x[0] = max(0.1 * x[1], x[0]) # preventd division by zero x[0]\n", " Rx1 = 1 / (2 * np.pi * kx * gr.DZ) * np.log(x[1:] / gr.xm).reshape((1, 1, gr.Nx))\n", " Rx2 = 1 / (2 * np.pi * kx * gr.DZ) * np.log(gr.xm / x[:-1]).reshape((1, 1, gr.Nx))\n", " Ry1 = np.inf * np.ones(gr.shape)\n", " Rz1 = 0.5 * gr.DZ / (np.pi * (gr.x[1:]**2 - gr.x[:-1]**2).reshape((1, 1, gr.Nx)) * kz)\n", " \n", " # set flow resistance in inactive cells to infinite\n", " Rx1[inact.reshape(gr.shape)] = np.inf\n", " Rx2[inact.reshape(gr.shape)] = np.inf\n", " Ry1[inact.reshape(gr.shape)] = np.inf\n", " Ry2 = Ry1\n", " Rz1[inact.reshape(gr.shape)] = np.inf\n", " Rz2 = Rz1\n", " \n", " # conductances between adjacent cells\n", " Cx = 1 / (Rx1[ :, :, 1:] + Rx2[:, :, :-1]) \n", " Cy = 1 / (Ry1[ :, :-1,:] + Ry2[:, 1:, :])\n", " Cz = 1 / (Rz1[:-1, :, :] + Rz2[1:, :, :])\n", " \n", " #pdb.set_trace()\n", " \n", " IE = gr.NOD[:, :, 1:] # east neighbor cell numbers\n", " IW = gr.NOD[:, :, :-1] # west neighbor cell numbers\n", " IN = gr.NOD[:, :-1, :] # north neighbor cell numbers\n", " IS = gr.NOD[:, 1:, :] # south neighbor cell numbers\n", " IT = gr.NOD[:-1, :, :] # top neighbor cell numbers\n", " IB = gr.NOD[1:, :, :] # bottom neighbor cell numbers\n", " \n", " R = lambda x : x.ravel() # generate anonymous function R(x) as shorthand for x.ravel()\n", "\n", " # notice the call csc_matrix( (data, (rowind, coind) ), (M,N)) tuple within tupple\n", " # also notice that Cij = negative but that Cii will be postive, namely -sum(Cij)\n", " A = sp.csc_matrix(( -np.concatenate(( R(Cx), R(Cx), R(Cy), R(Cy), R(Cz), R(Cz)) ),\\\n", " (np.concatenate(( R(IE), R(IW), R(IN), R(IS), R(IB), R(IT)) ),\\\n", " np.concatenate(( R(IW), R(IE), R(IS), R(IN), R(IT), R(IB)) ),\\\n", " )),(gr.Nod,gr.Nod))\n", " \n", " # to use the vector of diagonal values in a call of sp.diags() we need to have it aa a \n", " # standard nondimensional numpy vector.\n", " # To get this:\n", " # - first turn the matrix obtained by A.sum(axis=1) into a np.array by np.array( .. )\n", " # - then take the whole column to loose the array orientation (to get a dimensionless numpy vector)\n", " adiag = np.array(-A.sum(axis=1))[:,0]\n", " \n", " Adiag = sp.diags(adiag) # diagonal matrix with a[i,i]\n", " \n", " #pdb.set_trace()\n", "\n", " RHS = FQ.reshape((gr.Nod,1)) - A[:,fxhd].dot(HI.reshape((gr.Nod,1))[fxhd]) # Right-hand side vector\n", " \n", " Out.Phi = HI.flatten() # allocate space to store heads\n", " \n", " Out.Phi[active] = spsolve( (A+Adiag)[active][:,active] ,RHS[active] ) # solve heads at active locations\n", " \n", " # net cell inflow\n", " Out.Q = (A+Adiag).dot(Out.Phi).reshape(gr.shape)\n", " \n", " # reshape Phi to shape of grid\n", " Out.Phi = Out.Phi.reshape(gr.shape)\n", " \n", " #Flows across cell faces\n", " Out.Qx = -np.diff( Out.Phi, axis=2) * Cx\n", " Out.Qy = +np.diff( Out.Phi, axis=1) * Cy\n", " Out.Qz = +np.diff( Out.Phi, axis=0) * Cz\n", " \n", " # set inactive cells to NaN\n", " Out.Phi[inact.reshape(gr.shape)] = np.NaN # put NaN at inactive locations\n", "\n", " \n", " return Out # all outputs in a named tuple for easy access" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Examples\n", "\n", "Here we'll work out a few axially symmetric examples to verify this model using analytical solutions.\n", "\n", "\n", "We will also compute the darwdown in a multi-layer aquifer system.\n", "\n", "We'll keep truly axially symmetric 3D flow for the next chapter, after we introduced the stream function." ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "def inpoly(...)\n" ] }, { "data": { "text/plain": [ "" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "%matplotlib notebook\n", "\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "from importlib import reload\n", "import fdm_d # from current directory\n", "import mfgrid # path has been added to sys.path above\n", "\n", "# when we have been editing files, make sure to reload\n", "reload(fdm_d)\n", "reload(mfgrid)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Circular island\n", "\n", "The first example is flow in a circular island with recharge, like we did in a previous chapter using a large-scale 2D or 3D model.\n", "\n", "We use a single layer, which, in axially symmetric cases becomes a single row of cells, in which `x` should be read as `r`, the distance to the center of the island." ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Running in axial mode, y-values are ignored.\n" ] }, { "data": { "application/javascript": [ "/* Put everything inside the global mpl namespace */\n", "window.mpl = {};\n", "\n", "mpl.get_websocket_type = function() {\n", " if (typeof(WebSocket) !== 'undefined') {\n", " return WebSocket;\n", " } else if (typeof(MozWebSocket) !== 'undefined') {\n", " return MozWebSocket;\n", " } else {\n", " alert('Your browser does not have WebSocket support.' +\n", " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", " 'Firefox 4 and 5 are also supported but you ' +\n", " 'have to enable WebSockets in about:config.');\n", " };\n", "}\n", "\n", "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", " this.id = figure_id;\n", "\n", " this.ws = websocket;\n", "\n", " this.supports_binary = (this.ws.binaryType != undefined);\n", "\n", " if (!this.supports_binary) {\n", " var warnings = document.getElementById(\"mpl-warnings\");\n", " if (warnings) {\n", " warnings.style.display = 'block';\n", " warnings.textContent = (\n", " \"This browser does not support binary websocket messages. \" +\n", " \"Performance may be slow.\");\n", " }\n", " }\n", "\n", " this.imageObj = new Image();\n", "\n", " this.context = undefined;\n", " this.message = undefined;\n", " this.canvas = undefined;\n", " this.rubberband_canvas = undefined;\n", " this.rubberband_context = undefined;\n", " this.format_dropdown = undefined;\n", "\n", " this.image_mode = 'full';\n", "\n", " this.root = $('
');\n", " this._root_extra_style(this.root)\n", " this.root.attr('style', 'display: inline-block');\n", "\n", " $(parent_element).append(this.root);\n", "\n", " this._init_header(this);\n", " this._init_canvas(this);\n", " this._init_toolbar(this);\n", "\n", " var fig = this;\n", "\n", " this.waiting = false;\n", "\n", " this.ws.onopen = function () {\n", " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", " fig.send_message(\"send_image_mode\", {});\n", " fig.send_message(\"refresh\", {});\n", " }\n", "\n", " this.imageObj.onload = function() {\n", " if (fig.image_mode == 'full') {\n", " // Full images could contain transparency (where diff images\n", " // almost always do), so we need to clear the canvas so that\n", " // there is no ghosting.\n", " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", " }\n", " fig.context.drawImage(fig.imageObj, 0, 0);\n", " };\n", "\n", " this.imageObj.onunload = function() {\n", " this.ws.close();\n", " }\n", "\n", " this.ws.onmessage = this._make_on_message_function(this);\n", "\n", " this.ondownload = ondownload;\n", "}\n", "\n", "mpl.figure.prototype._init_header = function() {\n", " var titlebar = $(\n", " '
');\n", " var titletext = $(\n", " '
');\n", " titlebar.append(titletext)\n", " this.root.append(titlebar);\n", " this.header = titletext[0];\n", "}\n", "\n", "\n", "\n", "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", "\n", "}\n", "\n", "\n", "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", "\n", "}\n", "\n", "mpl.figure.prototype._init_canvas = function() {\n", " var fig = this;\n", "\n", " var canvas_div = $('
');\n", "\n", " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", "\n", " function canvas_keyboard_event(event) {\n", " return fig.key_event(event, event['data']);\n", " }\n", "\n", " canvas_div.keydown('key_press', canvas_keyboard_event);\n", " canvas_div.keyup('key_release', canvas_keyboard_event);\n", " this.canvas_div = canvas_div\n", " this._canvas_extra_style(canvas_div)\n", " this.root.append(canvas_div);\n", "\n", " var canvas = $('');\n", " canvas.addClass('mpl-canvas');\n", " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", "\n", " this.canvas = canvas[0];\n", " this.context = canvas[0].getContext(\"2d\");\n", "\n", " var rubberband = $('');\n", " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", "\n", " var pass_mouse_events = true;\n", "\n", " canvas_div.resizable({\n", " start: function(event, ui) {\n", " pass_mouse_events = false;\n", " },\n", " resize: function(event, ui) {\n", " fig.request_resize(ui.size.width, ui.size.height);\n", " },\n", " stop: function(event, ui) {\n", " pass_mouse_events = true;\n", " fig.request_resize(ui.size.width, ui.size.height);\n", " },\n", " });\n", "\n", " function mouse_event_fn(event) {\n", " if (pass_mouse_events)\n", " return fig.mouse_event(event, event['data']);\n", " }\n", "\n", " rubberband.mousedown('button_press', mouse_event_fn);\n", " rubberband.mouseup('button_release', mouse_event_fn);\n", " // Throttle sequential mouse events to 1 every 20ms.\n", " rubberband.mousemove('motion_notify', mouse_event_fn);\n", "\n", " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", "\n", " canvas_div.on(\"wheel\", function (event) {\n", " event = event.originalEvent;\n", " event['data'] = 'scroll'\n", " if (event.deltaY < 0) {\n", " event.step = 1;\n", " } else {\n", " event.step = -1;\n", " }\n", " mouse_event_fn(event);\n", " });\n", "\n", " canvas_div.append(canvas);\n", " canvas_div.append(rubberband);\n", "\n", " this.rubberband = rubberband;\n", " this.rubberband_canvas = rubberband[0];\n", " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", " this.rubberband_context.strokeStyle = \"#000000\";\n", "\n", " this._resize_canvas = function(width, height) {\n", " // Keep the size of the canvas, canvas container, and rubber band\n", " // canvas in synch.\n", " canvas_div.css('width', width)\n", " canvas_div.css('height', height)\n", "\n", " canvas.attr('width', width);\n", " canvas.attr('height', height);\n", "\n", " rubberband.attr('width', width);\n", " rubberband.attr('height', height);\n", " }\n", "\n", " // Set the figure to an initial 600x600px, this will subsequently be updated\n", " // upon first draw.\n", " this._resize_canvas(600, 600);\n", "\n", " // Disable right mouse context menu.\n", " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", " return false;\n", " });\n", "\n", " function set_focus () {\n", " canvas.focus();\n", " canvas_div.focus();\n", " }\n", "\n", " window.setTimeout(set_focus, 100);\n", "}\n", "\n", "mpl.figure.prototype._init_toolbar = function() {\n", " var fig = this;\n", "\n", " var nav_element = $('
')\n", " nav_element.attr('style', 'width: 100%');\n", " this.root.append(nav_element);\n", "\n", " // Define a callback function for later on.\n", " function toolbar_event(event) {\n", " return fig.toolbar_button_onclick(event['data']);\n", " }\n", " function toolbar_mouse_event(event) {\n", " return fig.toolbar_button_onmouseover(event['data']);\n", " }\n", "\n", " for(var toolbar_ind in mpl.toolbar_items) {\n", " var name = mpl.toolbar_items[toolbar_ind][0];\n", " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", " var image = mpl.toolbar_items[toolbar_ind][2];\n", " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", "\n", " if (!name) {\n", " // put a spacer in here.\n", " continue;\n", " }\n", " var button = $('');\n", " button.click(method_name, toolbar_event);\n", " button.mouseover(tooltip, toolbar_mouse_event);\n", " nav_element.append(button);\n", " }\n", "\n", " // Add the status bar.\n", " var status_bar = $('');\n", " nav_element.append(status_bar);\n", " this.message = status_bar[0];\n", "\n", " // Add the close button to the window.\n", " var buttongrp = $('
');\n", " var button = $('');\n", " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", " buttongrp.append(button);\n", " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", " titlebar.prepend(buttongrp);\n", "}\n", "\n", "mpl.figure.prototype._root_extra_style = function(el){\n", " var fig = this\n", " el.on(\"remove\", function(){\n", "\tfig.close_ws(fig, {});\n", " });\n", "}\n", "\n", "mpl.figure.prototype._canvas_extra_style = function(el){\n", " // this is important to make the div 'focusable\n", " el.attr('tabindex', 0)\n", " // reach out to IPython and tell the keyboard manager to turn it's self\n", " // off when our div gets focus\n", "\n", " // location in version 3\n", " if (IPython.notebook.keyboard_manager) {\n", " IPython.notebook.keyboard_manager.register_events(el);\n", " }\n", " else {\n", " // location in version 2\n", " IPython.keyboard_manager.register_events(el);\n", " }\n", "\n", "}\n", "\n", "mpl.figure.prototype._key_event_extra = function(event, name) {\n", " var manager = IPython.notebook.keyboard_manager;\n", " if (!manager)\n", " manager = IPython.keyboard_manager;\n", "\n", " // Check for shift+enter\n", " if (event.shiftKey && event.which == 13) {\n", " this.canvas_div.blur();\n", " // select the cell after this one\n", " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", " IPython.notebook.select(index + 1);\n", " }\n", "}\n", "\n", "mpl.figure.prototype.handle_save = function(fig, msg) {\n", " fig.ondownload(fig, null);\n", "}\n", "\n", "\n", "mpl.find_output_cell = function(html_output) {\n", " // Return the cell and output element which can be found *uniquely* in the notebook.\n", " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", " // IPython event is triggered only after the cells have been serialised, which for\n", " // our purposes (turning an active figure into a static one), is too late.\n", " var cells = IPython.notebook.get_cells();\n", " var ncells = cells.length;\n", " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", " data = data.data;\n", " }\n", " if (data['text/html'] == html_output) {\n", " return [cell, data, j];\n", " }\n", " }\n", " }\n", " }\n", "}\n", "\n", "// Register the function which deals with the matplotlib target/channel.\n", "// The kernel may be null if the page has been refreshed.\n", "if (IPython.notebook.kernel != null) {\n", " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", "}\n" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "application/javascript": [ "/* Put everything inside the global mpl namespace */\n", "window.mpl = {};\n", "\n", "mpl.get_websocket_type = function() {\n", " if (typeof(WebSocket) !== 'undefined') {\n", " return WebSocket;\n", " } else if (typeof(MozWebSocket) !== 'undefined') {\n", " return MozWebSocket;\n", " } else {\n", " alert('Your browser does not have WebSocket support.' +\n", " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", " 'Firefox 4 and 5 are also supported but you ' +\n", " 'have to enable WebSockets in about:config.');\n", " };\n", "}\n", "\n", "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", " this.id = figure_id;\n", "\n", " this.ws = websocket;\n", "\n", " this.supports_binary = (this.ws.binaryType != undefined);\n", "\n", " if (!this.supports_binary) {\n", " var warnings = document.getElementById(\"mpl-warnings\");\n", " if (warnings) {\n", " warnings.style.display = 'block';\n", " warnings.textContent = (\n", " \"This browser does not support binary websocket messages. \" +\n", " \"Performance may be slow.\");\n", " }\n", " }\n", "\n", " this.imageObj = new Image();\n", "\n", " this.context = undefined;\n", " this.message = undefined;\n", " this.canvas = undefined;\n", " this.rubberband_canvas = undefined;\n", " this.rubberband_context = undefined;\n", " this.format_dropdown = undefined;\n", "\n", " this.image_mode = 'full';\n", "\n", " this.root = $('
');\n", " this._root_extra_style(this.root)\n", " this.root.attr('style', 'display: inline-block');\n", "\n", " $(parent_element).append(this.root);\n", "\n", " this._init_header(this);\n", " this._init_canvas(this);\n", " this._init_toolbar(this);\n", "\n", " var fig = this;\n", "\n", " this.waiting = false;\n", "\n", " this.ws.onopen = function () {\n", " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", " fig.send_message(\"send_image_mode\", {});\n", " fig.send_message(\"refresh\", {});\n", " }\n", "\n", " this.imageObj.onload = function() {\n", " if (fig.image_mode == 'full') {\n", " // Full images could contain transparency (where diff images\n", " // almost always do), so we need to clear the canvas so that\n", " // there is no ghosting.\n", " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", " }\n", " fig.context.drawImage(fig.imageObj, 0, 0);\n", " };\n", "\n", " this.imageObj.onunload = function() {\n", " this.ws.close();\n", " }\n", "\n", " this.ws.onmessage = this._make_on_message_function(this);\n", "\n", " this.ondownload = ondownload;\n", "}\n", "\n", "mpl.figure.prototype._init_header = function() {\n", " var titlebar = $(\n", " '
');\n", " var titletext = $(\n", " '
');\n", " titlebar.append(titletext)\n", " this.root.append(titlebar);\n", " this.header = titletext[0];\n", "}\n", "\n", "\n", "\n", "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", "\n", "}\n", "\n", "\n", "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", "\n", "}\n", "\n", "mpl.figure.prototype._init_canvas = function() {\n", " var fig = this;\n", "\n", " var canvas_div = $('
');\n", "\n", " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", "\n", " function canvas_keyboard_event(event) {\n", " return fig.key_event(event, event['data']);\n", " }\n", "\n", " canvas_div.keydown('key_press', canvas_keyboard_event);\n", " canvas_div.keyup('key_release', canvas_keyboard_event);\n", " this.canvas_div = canvas_div\n", " this._canvas_extra_style(canvas_div)\n", " this.root.append(canvas_div);\n", "\n", " var canvas = $('');\n", " canvas.addClass('mpl-canvas');\n", " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", "\n", " this.canvas = canvas[0];\n", " this.context = canvas[0].getContext(\"2d\");\n", "\n", " var rubberband = $('');\n", " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", "\n", " var pass_mouse_events = true;\n", "\n", " canvas_div.resizable({\n", " start: function(event, ui) {\n", " pass_mouse_events = false;\n", " },\n", " resize: function(event, ui) {\n", " fig.request_resize(ui.size.width, ui.size.height);\n", " },\n", " stop: function(event, ui) {\n", " pass_mouse_events = true;\n", " fig.request_resize(ui.size.width, ui.size.height);\n", " },\n", " });\n", "\n", " function mouse_event_fn(event) {\n", " if (pass_mouse_events)\n", " return fig.mouse_event(event, event['data']);\n", " }\n", "\n", " rubberband.mousedown('button_press', mouse_event_fn);\n", " rubberband.mouseup('button_release', mouse_event_fn);\n", " // Throttle sequential mouse events to 1 every 20ms.\n", " rubberband.mousemove('motion_notify', mouse_event_fn);\n", "\n", " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", "\n", " canvas_div.on(\"wheel\", function (event) {\n", " event = event.originalEvent;\n", " event['data'] = 'scroll'\n", " if (event.deltaY < 0) {\n", " event.step = 1;\n", " } else {\n", " event.step = -1;\n", " }\n", " mouse_event_fn(event);\n", " });\n", "\n", " canvas_div.append(canvas);\n", " canvas_div.append(rubberband);\n", "\n", " this.rubberband = rubberband;\n", " this.rubberband_canvas = rubberband[0];\n", " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", " this.rubberband_context.strokeStyle = \"#000000\";\n", "\n", " this._resize_canvas = function(width, height) {\n", " // Keep the size of the canvas, canvas container, and rubber band\n", " // canvas in synch.\n", " canvas_div.css('width', width)\n", " canvas_div.css('height', height)\n", "\n", " canvas.attr('width', width);\n", " canvas.attr('height', height);\n", "\n", " rubberband.attr('width', width);\n", " rubberband.attr('height', height);\n", " }\n", "\n", " // Set the figure to an initial 600x600px, this will subsequently be updated\n", " // upon first draw.\n", " this._resize_canvas(600, 600);\n", "\n", " // Disable right mouse context menu.\n", " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", " return false;\n", " });\n", "\n", " function set_focus () {\n", " canvas.focus();\n", " canvas_div.focus();\n", " }\n", "\n", " window.setTimeout(set_focus, 100);\n", "}\n", "\n", "mpl.figure.prototype._init_toolbar = function() {\n", " var fig = this;\n", "\n", " var nav_element = $('
')\n", " nav_element.attr('style', 'width: 100%');\n", " this.root.append(nav_element);\n", "\n", " // Define a callback function for later on.\n", " function toolbar_event(event) {\n", " return fig.toolbar_button_onclick(event['data']);\n", " }\n", " function toolbar_mouse_event(event) {\n", " return fig.toolbar_button_onmouseover(event['data']);\n", " }\n", "\n", " for(var toolbar_ind in mpl.toolbar_items) {\n", " var name = mpl.toolbar_items[toolbar_ind][0];\n", " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", " var image = mpl.toolbar_items[toolbar_ind][2];\n", " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", "\n", " if (!name) {\n", " // put a spacer in here.\n", " continue;\n", " }\n", " var button = $('