{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "
\n", "
\n", "
Computational Seismology
\n", "
The (nodal) Discontinuous Galerkin Method for the linear advection equation
\n", "
\n", "
\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "

\n", " \n", "\n", "\n", "

\n", "\n", "\n", "---\n", "\n", "This notebook is part of the supplementary material \n", "to [Computational Seismology: A Practical Introduction](https://global.oup.com/academic/product/computational-seismology-9780198717416?cc=de&lang=en&#), \n", "Oxford University Press, 2016.\n", "\n", "\n", "##### Authors:\n", "* Stephanie Wollherr ([@swollherr](https://github.com/swollherr))\n", "* Heiner Igel ([@heinerigel](https://github.com/heinerigel))\n", "---" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The following notebook presents a 1D discontinuous Galerkin code for the advection equation using two different approaches for the integration in time.\n", "\n", "---" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### The discretization of the advection equation\n", "\n", "To keep things simple we want to solve the linear advection equation as a (scalar) hyperbolic equation:\n", "\n", "$$\\partial_t u + \\mu \\ \\partial_x u=0$$\n", "1. We first derive the **weak form** of the equation by multiplying it by an arbitrary test function and integration by parts.\n", "\n", "2. As in the Spectral Element Method, we approximate our unknown variable u(x,t) by a sum over space-dependent basis functions $\\psi_i$ weighted by time-dependent coefficients $u_i(t)$\n", "\n", "$$u(x,t) \\ \\approx \\ \\overline{u}(x,t) \\ = \\ \\sum_{i=1}^{n} u_i(t) \\\n", "\\psi_i(x)$$\n", "\n", "As interpolating functions we choose the **Lagrange polynomials** and use\n", "$\\xi$ as the space variable representing our elemental domain:\n", "\n", "$$\\psi_i \\ \\rightarrow \\ \\ell_i^{(N)} (\\xi) \\ := \\ \\prod_{k = 1, \\ k\n", "\\neq i}^{N+1} \\frac{\\xi - \\xi_k}{\\xi_i-\\xi_k}, \\qquad i = 1, 2, \\dotsc\n", ", N + 1$$\n", "\n", "Finally we get the (local) semi-discrete scheme for the k-th element that we derived in the lecture:\n", "\n", "$$M^k \\cdot \\partial_{t} u_h^k(t) - \\mu \\ (S^k)^T \\cdot u_h^k(t)= -[(\\mu \\ u)^*(t) \\ l_j^k(x)]_{x^{k}_l}^{x^{k}_r}$$\n", "\n", "
\n", "\n", "---\n", "\n", "We will use the following ingredients that we already discussed in previous exercises during the spectral element method:\n", "* Interpolating polynomials (**the Lagrange polynomials**, calculated in the routine 'lagrange.py' )\n", "* Numerical integration (**the Gauss-Lobatto-Legendre approach**, getting the GLL points $\\xi_{i}$ and their corresponding weights in the routine 'gll.py')\n", "* How we can calculate the **mass and stiffness matrix** using some nice properties of our basis functions.\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Exercises ##\n", "\n", "### 1. Examine the code below ###\n", "Look how the mass and stiffness matrix are calculated and compare them to the elemental system matrices for the spectral element code. What are the differences?
\n", "Take a closer look at the stiffness matrix: where is the Jacobian that we use to map our element to the reference element $I=[-1,1]$?
\n", "How is the flux calculated at the boundaries? Try different values for $\\alpha \\in [0,1]$ and see what happens.\n", "What kind of flux do we get for $\\alpha=1$ or $\\alpha=0$?
\n", "Try different orders of approximations.\n", "Try the Euler and the Runge-Kutta scheme for the time extrapolation. What do you notice?\n", "\n", "### 2. Time schemes ###\n", "\n", "Formulate the analytical solution to the advection problem and plot it along with the numerical solution each time step you visualize it during extrapolation.\n", "Formulate an error between analytical and numerical result. \n", "Analyze the solution error as a function of propagation distance for the Euler scheme and the predictor-corrector scheme.
\n", "(You find the implementation already in the code using exercice = 2).\n", "\n", "### 3. The Courant Criterion ###\n", "Using the sampling code below find numerically the Courant limit (for a fixed time extrapolation to $t_{max}$), keeping all other parameters constant, increasing the global spatial order $N$ of the scheme.
\n", "(like in the SEM Code).\n", "\n", "### 4. How discontinuous is the discontinuous Galerkin method? ###\n", "Extract the field values at the element boundaries and calculate the relative amount of the field discontinuity.
\n", "How do the discontinuities compare with the flux values?\n", "(You find the implementation already in the code using exercice = 4).\n", "\n", "
\n", "\n", "---" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### The Code " ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "code_folding": [ 0 ], "lines_to_next_cell": 2 }, "outputs": [], "source": [ "# This is a configuration step. Please run it before the simulation code!\n", "\n", "# Imports etc\n", "import numpy as np\n", "import matplotlib\n", "# Show Plot in The Notebook\n", "matplotlib.use(\"nbagg\")\n", "import matplotlib.pyplot as plt\n", "\n", "# import of DG modules from same directory as the notebook\n", "# we use the same functions that we already know from the spectral element method\n", "from gll import gll\n", "from lagrange import lagrange\n", "from lagrange1st import lagrange1st\n", "from legendre import legendre" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "# ---------------------------------------------------------------\n", "#\n", "# Discontinous galerkin method for the\n", "# 1D advection equation using an upwind flux\n", "#\n", "\n", "# ---------------------------------------------------------------\n", "# Initialization of setup\n", "# ---------------------------------------------------------------\n", "nt = 800 # number of time steps \n", "xmax = 30. # Length of domain\n", "rho = 1 # Density for homogeneous model, if you change the density, take that into account for a analytical solution!\n", "mu = 20 # Speed of the wave, positive=propagating to the right, negative=propagating to the left \n", "N = 6 # Order of Lagrange polynomials\n", "ne = 100 # Number of elements\n", "\n", "\n", "# ----------------------------------------------------------------\n", "\n", "# Initialization of GLL points integration weights\n", "[xi, w] = gll(N) # xi -> N+1 coordinates [-1 1] of GLL points\n", " # w integration weights at GLL locations\n", " \n", "# Space domain\n", "le = xmax / ne # Length of elements for regular grid points\n", "\n", "\n", "# Vector with GLL points, same values for boundary points\n", "k = -1\n", "xg = np.zeros((N +1)* ne)\n", "#xg[k] = 0\n", "for i in range(1, ne + 1):\n", " for j in range(0, N+1):\n", " k += 1\n", " xg[k] = (i - 1) * le + .5 * (xi[j] + 1) * le\n", "\n", "x=np.reshape(xg,(ne,(N+1))) #Matrix containing all GLL points in every element\n", "x=x.T\n", "\n", "\n", "# ---------------------------------------------------------------\n", "# Calculation if time step according to Courant criterion\n", "\n", "dxmin = min(np.diff(xg[0:N]))\n", "eps = 0.1 # Courant value orig 0.1\n", "\n", "dt = eps * dxmin / (np.abs(mu)) # Global time step\n", "\n", "#-----------------------------------------------------------------\n", "#---Exercice 3: Find the courant limit, dt fixed and N decreasing\n", "#dt=0.0004 #or some other max. timestep\n", "#eps= dt*(np.abs(mu)/dxmin)\n", "#print(eps)\n", "#----------------------------------------------------------------\n", "\n", "\n", "J = le / 2 # Mapping - Jacobian, same for every element if the element size is everywhere the same\n", "Ji = 1 / J # Inverse Jacobian\n", "\n", "# Initialization of 1st derivative of Lagrange polynomials used in the stiffness matrix\n", "l1d = lagrange1st(N) # Array with GLL as columns for each N+1 polynomial\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### The system matrices ###\n", "The mass and the stiffness matrix can be precalculated and stored when we transform every element to a reference elemen.
\n", "In the 1D case this is simply the transformation to the interval $I=[-1,1]$ with $dx= J \\ d\\xi$ and hence $J=\\frac{dx}{d\\xi}$. Additionally we also have to transform a spacial derivative with respect to $x$ into a spacial derivative with respect to $\\xi$.
\n", "The Lagrange polynomials have some nice properties that we will use to calculate the mass and stiffness matrix.\n", "At the GLL-points $x_j$ it holds:\n", "$$l_i(x_j) = \\delta_{ij}= \\begin{cases} 1 \\ \\text{ if i=j} \\\\ 0 \\ \\text{ if i \\neq j } \\end{cases}$$\n", "For the numerical integration we use the numerical quadrature, that uses the GLL and their corresponding weights to approximate an integral.\n", "\n", "\n", "$$M_{ij}^k=\\int_{-1}^1 l_i^k(\\xi) l_j^k(\\xi) \\ J \\ d\\xi = \\sum_{m=1}^{N_p} w_m \\ l_i^k (x_m) l_j^k(x_m)\\ J =\\sum_{m=1}^{N_p} w_m \\delta_{im}\\ \\delta_{jm} \\ J= \\begin{cases} w_i \\ J \\ \\ \\text{ if } i=j \\\\ 0 \\ \\ \\ \\ \\ \\ \\ \\text{ if } i \\neq j\\end{cases}$$\n", " **Note** : We have a diagonal mass matrix!\n", " \n", " \n", " $$S_{i,j}= \\int_{-1}^1 l_i^k(\\xi) \\cdot \\partial _x l_j^k(\\xi) \\ d\\xi= \\sum_{m=1}^{N_p} w_m \\ l_i^k(x_m)\\cdot \\partial_x l_j^k(x_m)= \\sum_{m=1}^{N_p} w_m \\delta_{im}\\cdot \\partial_xl_j^k(x_m)= w_i \\cdot \\partial_x l_j^k(x_i)$$\n", "We already calculated the Lagrange polynomials. They will be used to calculate the first derivatives of the Lagrange polynomials in the function \"Lagrange1st\". Now we have all the ingredients to calculate the mass and stiffness matrix:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "# -----------------------------------------------------------------\n", "# Initialization of system matrices\n", "# -----------------------------------------------------------------\n", "\n", "\n", "# variables for elemental matrices\n", "Me = np.zeros(N + 1, dtype=float)\n", "Ke = np.zeros([N + 1, N + 1], dtype=float)\n", "\n", "\n", "# Mass matrix\n", "# Elemental\n", "for i in range(-1, N):\n", " Me[i + 1] = rho * w[i + 1] * J #only a vector since the matrix is diagonal\n", "\n", "\n", "\n", "# Build inverse matrix\n", "Minv = np.identity(N+1)\n", "for i in range(0, N+1):\n", " Minv[i,i] = 1. / Me[i]\n", "\n", "\n", "# ---------------------------------------------------------------\n", "# Stiffness Matrix\n", "# Elemental\n", "\n", "for i in range(-1, N):\n", " for j in range(-1, N):\n", " Ke[i+1,j+1] = mu * w[j + 1] * l1d[i + 1, j + 1] #corrected indices\n", "\n", "\n", "# ---------------------------------------------------------------" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### The numerical flux\n", "\n", "The big difference to the Spectral Element Method is the communication between the element neighbors using a flux term $(\\mu \\ u)^*$.
\n", "For the 1D advection equation we can derive the general formula for a numerical upwind flux\n", "$$(\\mu \\ u )^*= \\frac{\\mu}{2}(u^++u^-) +\\frac{|\\mu|}{2}(\\hat n^+u^++\\hat n^-u^-)$$\n", "with $u^-$ as the interior information, \n", "$u^+$ the exterior information and \n", "$\\hat n$ the corresponding outer pointing normal vectors.
\n", "This leads to the update on the right boundary of an element\n", "$$\\frac{\\mu}{2}\\big(u_h^k(x_r^k)+u_h^{k+1}(x_l^{k+1})\\big )+ \\frac{|\\mu|}{2}\\big(u_h^k(x_r^k)-u_h^{k+1}(x_l^{k+1})\\big )$$\n", "and on the left boundary of an element\n", "$$\\frac{\\mu}{2}\\big(u_h^k(x_l^k)+u_h^{k-1}(x_r^{k-1}) \\big)+ \\frac{|\\mu|}{2}(-u_h^{k}(x_l^{k}+u_h^{k-1}(x_r^{k-1}))\\big)$$\n", "\n", "For a positive travel speed $\\mu$ (-> $\\mu=|\\mu|$) the value on the left boundary is the value of the previous element $k-1$ from where the wave is coming from. On the right boundary it's just the value of the actual element $k$ (and vice versa for $\\mu$ being negative).\n", "Notice that we only consider the first (left boundary) and the last (right boundary) DOF of an element since the Lagrange polynomials are zero for every other points.\n", "This is calculated by the function \"flux\":" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "lines_to_end_of_cell_marker": 2 }, "outputs": [], "source": [ "#calculates the flux between two boundary sides of connected elements for\n", "#element i\n", "#Flux matrix du\n", "def flux(alpha, u, N, ne, mu): #What kind of flux do we get for alpha=1 or alpha=0?\n", " \n", "#impose boundary conditions at x=0 and x=end\n", " ubd1 = 0 \n", " ubd2 = 0 \n", " \n", " du = np.zeros((N+1, ne)) # for every element we have 2 faces to other elements (left and right)\n", " for i in range(0, ne):\n", " \n", "\n", " if i==0: # left boundary of the domain\n", " du[0,i] = -mu / 2 * (u[0,i] + ubd1) - (1 - alpha) * abs(mu) / 2 * (ubd1 - u[0,i]) #left flux\n", " du[N,i] = mu / 2 * (u[N,i] + u[0,i+1]) + (1 - alpha) * abs(mu) / 2 * (u[N,i] - u[0,i+1])#right flux\n", "\n", " elif i==ne-1: # right boundary of the domain\n", " du[0,i] = -mu / 2 * (u[0,i] + u[N,i-1]) - (1-alpha) * abs(mu) / 2 * (-u[0,i] + u[N,i-1])\n", " du[N,i] = mu/2 * (u[N,i] + ubd2) + (1-alpha) * abs(mu) / 2 * (u[N,i] - ubd2)\n", " \n", " else: # in the middle of the domain\n", " du[0,i] = -mu / 2 * (u[0,i] + u[N,i-1]) - (1-alpha)*abs(mu) / 2 * (-u[0,i] + u[N,i-1])\n", " du[N,i] = +mu / 2 * (u[N,i] + u[0,i+1]) + (1-alpha)*abs(mu) / 2 * (u[N,i] - u[0,i+1])\n", "\n", "\n", " return du\n", " return udiff" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### The time extrapolation\n", "\n", "Last but not least we have to solve our semidiscrete scheme that we derived above using an appropriate time extrapolation:\n", "\\begin{eqnarray*}\n", "\\partial_{t} u_h^k(t)= \\underbrace{(M^k)^{-1} \\left( \\mu \\ (S^k) \\cdot u_h^k(t) -\\text{flux}^k \\right )}_{\\text{RHS(u(t))}}\n", "\\end{eqnarray*}\n", "**Note:** If we have a homogenuous medium and a constant advection velocity for every element we can also formulate the equation above as matrix-vector products since $M^k$ and $S^k$ are the same for every element $k$. \n", "In the code below we implemented two different time extrapolation schemes:\n", "\n", "1. The Euler scheme in time with the update\n", "\n", "\\begin{eqnarray*}\n", "u(t+1)= u(t) + dt \\cdot \\text{RHS(u(t))}\n", "\\end{eqnarray*}\n", "\n", "2. and a second-order Runge-Kutta method (also called predictor-corrector scheme) with the follwing steps\n", "\n", "\\begin{eqnarray*} \n", "k1(t)&=&RHS(u(t)) \\\\\n", "k2(t)&=&RHS(u(t)+dt\\cdot k1(t)) \\\\\n", "& & \\\\\n", "u(t+1)&=& u(t) + 0.5 \\cdot dt \\cdot \\left(k1(t)+k2(t)\\right)\n", "\\end{eqnarray*}\n", "\n", "In the Euler method we use for example a loop over every element, in the Runge-Kutta method we use the matrix-vector formulation.
" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "code_folding": [ 85, 127 ], "lines_to_next_cell": 0 }, "outputs": [ { "data": { "application/javascript": [ "/* Put everything inside the global mpl namespace */\n", "window.mpl = {};\n", "\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", " if (mpl.ratio != 1) {\n", " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", " }\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", " fig.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;\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;\n", " this.context = canvas.getContext(\"2d\");\n", "\n", " var backingStore = this.context.backingStorePixelRatio ||\n", "\tthis.context.webkitBackingStorePixelRatio ||\n", "\tthis.context.mozBackingStorePixelRatio ||\n", "\tthis.context.msBackingStorePixelRatio ||\n", "\tthis.context.oBackingStorePixelRatio ||\n", "\tthis.context.backingStorePixelRatio || 1;\n", "\n", " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\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;\n", " this.rubberband_context = rubberband.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 * mpl.ratio);\n", " canvas.attr('height', height * mpl.ratio);\n", " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\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];\n", " var tooltip = mpl.toolbar_items[toolbar_ind];\n", " var image = mpl.toolbar_items[toolbar_ind];\n", " var method_name = mpl.toolbar_items[toolbar_ind];\n", "\n", " if (!name) {\n", " // put a spacer in here.\n", " continue;\n", " }\n", " var button =$('