diff --git a/animatplot/_version.py b/animatplot/_version.py index 3daa956..6df05e3 100644 --- a/animatplot/_version.py +++ b/animatplot/_version.py @@ -1,3 +1,3 @@ version = (0, 2, 0) -info = '.dev2' +info = '' __version__ = '.'.join(map(str, version))+info diff --git a/animatplot/blocks/image_like.py b/animatplot/blocks/image_like.py index 7841016..2cd1539 100644 --- a/animatplot/blocks/image_like.py +++ b/animatplot/blocks/image_like.py @@ -8,9 +8,14 @@ class Pcolormesh(Block): Parameters ---------- - X, Y : 2D np.ndarray, optional - C : 3D np.ndarray - axis : matplotlib axis + X : 2D np.ndarray, optional + Y : 2D np.ndarray, optional + C : list of 2D np.ndarray or a 3D np.ndarray + axis : matplotlib axis, optional + an axis to attach the block to. + t_axis : int, optional + The axis of the array that represents time. Defaults to 0. + No effect if C is a list. Notes ----- @@ -29,24 +34,34 @@ def __init__(self, *args, axis=None, t_axis=0, **kwargs): else: raise TypeError( 'Illegal arguments to pcolormesh; see help(pcolormesh)') - if len(self.C.shape) != 3: - raise TypeError('C must be a 3D array') - super().__init__(axis) + super().__init__(axis, t_axis) + + self._is_list = isinstance(self.C, list) + self.C = np.asanyarray(self.C) + Slice = self._make_slice(0, 3) if self._arg_len == 1: - self.quad = self.ax.pcolormesh(self.C[:, :, 0], **kwargs) + self.quad = self.ax.pcolormesh(self.C[Slice], **kwargs) elif self._arg_len == 3: - self.quad = self.ax.pcolormesh(self.X, self.Y, self.C[:, :, 0], + self.quad = self.ax.pcolormesh(self.X, self.Y, self.C[Slice], **kwargs) def _update(self, i): - self.quad.set_array(self.C[:-1, :-1, i].ravel()) + Slice = self._make_pcolormesh_slice(i, 3) + self.quad.set_array(self.C[Slice].ravel()) return self.quad def __len__(self): return self.C.shape[2] + def _make_pcolormesh_slice(self, i, dim): + if self._is_list: + return i + Slice = [slice(-1)]*3 # weird thing to make animation work + Slice[self.t_axis] = i + return tuple(Slice) + class Imshow(Block): """Animates a series of images @@ -64,6 +79,7 @@ class Imshow(Block): t_axis : int, optional The axis of the array that represents time. Defaults to 0. No effect if images is a list. + Notes ----- This block accepts additional keyword arguments to be passed to diff --git a/animatplot/blocks/vectors.py b/animatplot/blocks/vectors.py index a565a5b..20b970c 100644 --- a/animatplot/blocks/vectors.py +++ b/animatplot/blocks/vectors.py @@ -1,4 +1,5 @@ from .base import Block +import numpy as np class Quiver(Block): @@ -7,48 +8,53 @@ class Quiver(Block): Parameters ---------- - X, Y : 2D or 3D numpy array - U, V : 3D numpy array + X : 1D or 2D numpy array + The x positions of the arrows. Cannot be animated. + Y : 1D or 2D numpy array + The y positions of the arrows. Cannot be animated. + U : 2D or 3D numpy array + The U displacement of the arrows. 1 dimension + higher than the X, Y arrays. + V : 2D or 3D numpy array + The V displcement of the arrows. 1 dimension + higher than the X, Y arrays. axis : matplotlib axis, optional The axis to the block to + t_axis : int, optional + The axis of the array that represents time. Defaults to 0. + No effect if U, V are lists. + Notes ----- This block accepts additional keyword arguments to be passed to :meth:`matplotlib.axes.Axes.quiver` """ - def __init__(self, X, Y, U, V, axis=None, **kwargs): + def __init__(self, X, Y, U, V, axis=None, t_axis=0, **kwargs): + self.X = X + self.Y = Y + self.U = np.asanyarray(U) + self.V = np.asanyarray(V) if X.shape != Y.shape: raise ValueError("X, Y must have the same shape") - if U.shape != V.shape: + if self.U.shape != self.V.shape: raise ValueError("U, V must have the same shape") - # lots of features removed b/c quiver plots can't set_XY - # self.animate_XY = len(X.shape) == 3 - # self.animate_UV = len(U.shape) == 3 - - self.x = X - self.y = Y - self.u = U - self.v = V - self.ax = axis + super().__init__(axis, t_axis) - xy_slice = [slice(None)]*2 + ([slice(1)] if len(X.shape) == 3 else []) - # uv_slice = [slice(None)]*2 +([slice(1)] if len(U.shape) == 3 else []) + self._dim = len(self.U.shape) + self._is_list = isinstance(U, list) - self.Q = self.ax.quiver(X[tuple(xy_slice)].squeeze(), - Y[tuple(xy_slice)].squeeze(), - U[:, :, 0], V[:, :, 0], + Slice = self._make_slice(0, self._dim) + self.Q = self.ax.quiver(self.X, self.Y, + self.U[Slice], self.V[Slice], **kwargs) def _update(self, i): - # if self.animate_UV: - self.Q.set_UVC(self.u[:, :, i], self.v[:, :, i]) - # if self.animate_XY: - # pass # self.Q.set_XYC(self.x[:, :, i], self.y[:, :, i]) - return self.Q, + Slice = self._make_slice(i, self._dim) + self.Q.set_UVC(self.U[Slice], self.V[Slice]) + return self.Q def __len__(self): - # if len(self.x.shape) == 3: - # return self.x.shape[2] - # else: - return self.u.shape[2] + if self._is_list: + return self.U.shape[0] + return self.U.shape[self.t_axis] diff --git a/docs/source/gallery/Nuke.ipynb b/docs/source/gallery/Nuke.ipynb index f08d4ea..c48bc0f 100644 --- a/docs/source/gallery/Nuke.ipynb +++ b/docs/source/gallery/Nuke.ipynb @@ -844,7 +844,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -879,7 +879,7 @@ "anim = amp.Animation([block], timeline)\n", "\n", "anim.controls()\n", - "anim.save_gif('polarization')\n", + "anim.save_gif('nuke')\n", "plt.show()" ] }, @@ -889,7 +889,7 @@ "raw_mimetype": "text/restructuredtext" }, "source": [ - ".. image:: polarization.gif" + ".. image:: nuke.gif" ] } ], diff --git a/docs/source/gallery/nuke.gif b/docs/source/gallery/nuke.gif new file mode 100644 index 0000000..20538a9 Binary files /dev/null and b/docs/source/gallery/nuke.gif differ diff --git a/docs/source/gallery/pcolormesh.gif b/docs/source/gallery/pcolormesh.gif index 803b806..fc3b474 100644 Binary files a/docs/source/gallery/pcolormesh.gif and b/docs/source/gallery/pcolormesh.gif differ diff --git a/docs/source/gallery/pcolormesh.ipynb b/docs/source/gallery/pcolormesh.ipynb index 7fcf008..1baa7c6 100644 --- a/docs/source/gallery/pcolormesh.ipynb +++ b/docs/source/gallery/pcolormesh.ipynb @@ -38,7 +38,7 @@ "\n", "Z = np.sin(X*X+Y*Y-T)\n", "\n", - "block = amp.blocks.Pcolormesh(X[:,:,0], Y[:,:,0], Z, cmap='RdBu')\n", + "block = amp.blocks.Pcolormesh(X[:,:,0], Y[:,:,0], Z, t_axis=2, cmap='RdBu')\n", "plt.colorbar(block.quad)\n", "plt.gca().set_aspect('equal')\n", "\n", diff --git a/docs/source/gallery/polarization.gif b/docs/source/gallery/polarization.gif index 20538a9..80103e3 100644 Binary files a/docs/source/gallery/polarization.gif and b/docs/source/gallery/polarization.gif differ diff --git a/docs/source/gallery/polarization_quiver.ipynb b/docs/source/gallery/polarization_quiver.ipynb new file mode 100644 index 0000000..2bd9f98 --- /dev/null +++ b/docs/source/gallery/polarization_quiver.ipynb @@ -0,0 +1,908 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Polarization" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib notebook\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "import animatplot as amp" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's create the data." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "E0 = np.array([1, 2])\n", + "E0 = E0 / np.linalg.norm(E0)\n", + "\n", + "phi = np.array([0, np.pi/7])\n", + "\n", + "f = 3\n", + "t = np.linspace(0,2*np.pi,50)\n", + "\n", + "# The Electric Field\n", + "E = E0[:, np.newaxis]*np.exp(1j*(t+phi[:, np.newaxis])) # fancy array boardcasting\n", + "\n", + "# Converting the Electric field into animatable arrows.\n", + "X = np.zeros(3) # x location of the arrow tails\n", + "Y = np.zeros(3) # y location of the arrow tails\n", + "\n", + "zeros = np.zeros_like(E[0,:]) # padding\n", + "U = np.array([E[0,:], zeros, E[0,:]]).real\n", + "V = np.array([zeros, E[1,:], E[1,:]]).real" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now to animate it." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "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[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 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[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 * 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][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 = $('