diff --git a/Dockerfile b/Dockerfile
index a87e00bc27f8b00d367658953f919713e718fa05..c7af1913699ee4e0240f14174ddca850e20e5c54 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -2,5 +2,5 @@ ARG BASE_IMAGE=registry.git.rwth-aachen.de/jupyter/profiles/rwth-courses:latest
 FROM ${BASE_IMAGE}
 
 RUN conda install --quiet --yes \
-		'scipy==1.7.1' && \
+	'scipy==1.7.1' && \
 	conda clean --all
diff --git a/GDET3 Diskrete Fourier-Transformation GUI.ipynb b/GDET3 Diskrete Fourier-Transformation GUI.ipynb
new file mode 100644
index 0000000000000000000000000000000000000000..d0af393fabca77eaa9ea2e2b81c5a05a4edead53
--- /dev/null
+++ b/GDET3 Diskrete Fourier-Transformation GUI.ipynb	
@@ -0,0 +1,248 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "jupyter": {
+     "source_hidden": true
+    }
+   },
+   "outputs": [],
+   "source": [
+    "# Copyright 2020 Institut für Nachrichtentechnik, RWTH Aachen University\n",
+    "%matplotlib widget\n",
+    "\n",
+    "from ipywidgets import interact, interactive, fixed, HBox, VBox\n",
+    "import ipywidgets as widgets\n",
+    "from IPython.display import clear_output, display, HTML\n",
+    "\n",
+    "import matplotlib.pyplot as plt\n",
+    "from rwth_nb.plots import colors\n",
+    "import rwth_nb.plots.mpl_decorations as rwth_plt\n",
+    "from rwth_nb.misc.signals import *\n",
+    "from rwth_nb.misc.transforms import *\n",
+    "\n",
+    "t,deltat = np.linspace(-10,10,50001,  retstep=True) # t-axis\n",
+    "f,deltaf = np.linspace(-10,10,len(t), retstep=True) # f-axis\n",
+    "kMax = 16 # number of k values in sum for Sa(f)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "<div>\n",
+    "    <img src=\"figures/rwth_ient_logo@2x.png\" style=\"float: right;height: 5em;\">\n",
+    "</div>\n",
+    "\n",
+    "# Demonstrator Diskrete Fourier-Transformation\n",
+    "\n",
+    "Zum Starten: Im Menü: Run <span class=\"fa-chevron-right fa\"></span> Run All Cells auswählen.\n",
+    "\n",
+    "## Einleitung\n",
+    "\n",
+    "$s(t)$ wird ideal mit Abtastrate $r$ abgetastet.\n",
+    "\n",
+    "Diskrete Fourier-Transformation mit $F = \\frac{1}{M}$ resultiert in diskreter Fourier-Transformierten\n",
+    "$$\n",
+    "S_\\mathrm{d}(k) \n",
+    "= \n",
+    "\\sum_{n=0}^{M-1} s_\\mathrm{d}(n) \\mathrm{e}^{-\\mathrm{j}2\\pi kFn} \\quad k=0,\\ldots,M-1 \\text{ ,}\n",
+    "$$\n",
+    "wobei $s_\\mathrm{d}(n)$ um $M$ periodisches, diskretes Zeitsignal mit $t = n \\cdot \\frac{1}{r}$.\n",
+    "\n",
+    "Für die Rücktransformation in den Zeitbereich gilt dann\n",
+    "$$\n",
+    "s_\\mathrm{d}(n)\n",
+    "=\n",
+    "\\frac{1}{M} \\sum_{k=0}^{M-1}S_\\mathrm{d}(k) \\mathrm{e}^{\\mathrm{j}2\\pi kFn} \\quad n=0,\\ldots,M-1 \\text{ ,}\n",
+    "$$\n",
+    "wobei\n",
+    "$f = k \\cdot \\frac{r}{M}$.\n",
+    "\n",
+    "## Beispiel\n",
+    "\n",
+    "Zeige eine Periode mit $M=24$ Werten"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "M = 24; # period of discrete signal\n",
+    "r = 4; # sampling rate\n",
+    "\n",
+    "n = np.arange(M) # discrete time axis\n",
+    "sd = tri(n/r) + tri((n-M)/r) # discrete signal sd(n)\n",
+    "\n",
+    "F = 1/M \n",
+    "\n",
+    "# Very slow DFT computation for teaching purposes!\n",
+    "# Fasten your seatbelts with FFF by uncommenting the line with \"fft.fft\" below\n",
+    "Sd = np.zeros_like(sd, dtype=complex)\n",
+    "for k in np.arange(M): \n",
+    "    Sd[k] = np.sum(sd * np.exp(-2j*np.pi*k*F*n))\n",
+    "#Sd = np.fft.fft(sd) # this would be so much faster!\n",
+    "Sd = np.real(Sd) # discard super small imaginary part (1e-16), works \"only\" for examples like this\n",
+    "\n",
+    "# Plot\n",
+    "fig,axs = plt.subplots(2,1)\n",
+    "\n",
+    "## Plot discrete time domain\n",
+    "ax = axs[0]; rwth_plt.stem(ax, n, sd); \n",
+    "ax.set_xlabel(r'$\\rightarrow n$'); ax.set_ylabel(r'$\\uparrow s_\\mathrm{d}(n)$')\n",
+    "rwth_plt.axis(ax);\n",
+    "\n",
+    "# Plot discrete Fourier domain\n",
+    "ax = axs[1]; rwth_plt.stem(ax, np.arange(M), Sd/r); \n",
+    "ax.set_xlabel(r'$\\rightarrow k$'); ax.set_ylabel(r'$\\uparrow S_\\mathrm{d}(k)/r$')\n",
+    "rwth_plt.axis(ax); fig.tight_layout()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## Demonstrator\n",
+    "\n",
+    "Signal $s(t) = \\Lambda(t)$ mit zugehörigem Spektrum $S(f) = \\mathrm{si}^2(\\pi f)$ kann zunächst mit Rate $r$ abgetastet werden. Weiter lässt sich das diskrete Signal periodisch fortsetzen mit Periode $M$."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "jupyter": {
+     "source_hidden": true
+    },
+    "tags": []
+   },
+   "outputs": [],
+   "source": [
+    "def decorate_second_axis(ax):\n",
+    "    ax.spines['top'].set_position(('axes',.9)); ax.spines['right'].set_color('none'); ax.spines['left'].set_color('none'); ax.spines['bottom'].set_color('none');\n",
+    "    ax.xaxis.set_label_coords(.98,1); ax.xaxis.label.set_horizontalalignment('right') \n",
+    "\n",
+    "fig, axs = plt.subplots(2, 1, **rwth_plt.landscape); #fig.tight_layout()\n",
+    "highlight_rect_style = {'facecolor': \"none\", 'edgecolor': 'rwth:orange', 'linestyle':'--', 'linewidth':2.0}\n",
+    "\n",
+    "@widgets.interact(  r = widgets.IntSlider(min=0, max=10, step=2, value=0, description='$r$ [Hz]'),\n",
+    "                  Mbr = widgets.IntSlider(min=0, max=10, step=2, value=0, description='$M/r$'))\n",
+    "def update_plots(r, Mbr):\n",
+    "    M = r*Mbr\n",
+    "    global n, snT, k, SkF\n",
+    "    # Continuous functions s(t) and S(f)\n",
+    "    s = tri\n",
+    "    S = lambda f: si(np.pi*f) ** 2\n",
+    "    \n",
+    "    # Ideal sampling\n",
+    "    Sa_f = np.zeros_like(S(f))\n",
+    "    nT = []; snT = nT; n = nT;\n",
+    "    if r > 0:\n",
+    "        T = 1/r\n",
+    "        \n",
+    "        # Sampled function s(n)\n",
+    "        nT, snT = sample(t, s(t), T); n = nT/T;\n",
+    "        \n",
+    "        # Sampled continuous spectrum S_a(f)\n",
+    "        for k in np.arange(-kMax, kMax+1): # evaluate infinite sum only for 2*kMax+1 elements \n",
+    "            Sa_f += S(f-k/T)\n",
+    "        #Sa_f = Sa_f/T # normalize here\n",
+    "        \n",
+    "        # Periodic copies of s(n)\n",
+    "        kF = []; SkF = kF; k = kF;\n",
+    "        sd = np.zeros_like(snT)\n",
+    "        if M > 0:\n",
+    "            F = 1/M # in Hz\n",
+    "            kF, SkF = sample(f, Sa_f, r*F); k = kF/F/r\n",
+    "            for d in np.arange(-kMax, kMax+1): # evaluate infinite sum only for 2*kMax+1 elements \n",
+    "                sd += s((n-d*M)*T)\n",
+    "    display('M={}'.format(M))\n",
+    "    \n",
+    "    # Plot\n",
+    "    if not axs[0].lines: # Call plot() and decorate axes. Usually, these functions take some processing time\n",
+    "        ax = axs[0]; ax.set_title('Zeitbereich');\n",
+    "        ax.plot(t, s(t), 'k', label='$s(t)$');\n",
+    "        ax.set_xlabel(r'$\\rightarrow t $ [s]')\n",
+    "        ax.set_xlim([-5,10]); ax.set_ylim([-.09, 1.19]); ax.legend(loc='upper right', bbox_to_anchor=(1.0, 0.8)); rwth_plt.axis(ax);\n",
+    "        \n",
+    "        axn = ax.twiny(); rwth_plt.stem(axn, [1], [1], color='rwth:blue', label=r'$s(n)$'); \n",
+    "        axn.set_xlabel(r'$\\rightarrow n$ [Samples]'); decorate_second_axis(axn);\n",
+    "        axn.legend(loc='upper right', bbox_to_anchor=(1.0, 0.6));\n",
+    "        axn.fill_between([-.1, M-1+.1], -.05, 1.05, **highlight_rect_style)  \n",
+    "               \n",
+    "        ax = axs[1]; ax.set_title('Frequenzbereich');\n",
+    "        ax.plot(f, S(f), 'k', label='$S(f)$')\n",
+    "        ax.plot(f, Sa_f, 'rwth:red', label='$S_\\mathrm{a}(f)/r$')\n",
+    "        ax.set_xlabel(r'$\\rightarrow f $ [Hz]')\n",
+    "        ax.set_xlim([-5,10]);  ax.set_ylim([-.09, 1.19]); ax.legend(loc='upper right', bbox_to_anchor=(1.0, 0.8)); rwth_plt.axis(ax);\n",
+    "        \n",
+    "        axk = ax.twiny(); rwth_plt.stem(axk, [1], [1], label=r'$S_\\mathrm{d}(k)/r$');\n",
+    "        axk.set_xlabel(r'$\\rightarrow k$'); decorate_second_axis(axk);\n",
+    "        axk.legend(loc='upper right', bbox_to_anchor=(1.0, 0.5));\n",
+    "        axk.fill_between([-.1, M-1+.1], -.05, 1.05, **highlight_rect_style) \n",
+    "        \n",
+    "    else: # Update only the plot lines themselves. Should not take too much processing time\n",
+    "        ax = axs[0] # time domain\n",
+    "        axn = fig.axes[2];\n",
+    "        if r > 0:\n",
+    "            if len(axn.collections) > 1: axn.collections[1].remove()\n",
+    "            if M == 0:\n",
+    "                rwth_plt.stem_set_data(axn.containers[0], n, snT); #axn.containers[0].set_label(r'$s(n)$')\n",
+    "            else:\n",
+    "                rwth_plt.stem_set_data(axn.containers[0], n, sd);  #axn.containers[0].set_label(r'$s_\\mathrm{d}(n)$')\n",
+    "                axn.fill_between([-.1, M-1+.1], -.05, 1.05, **highlight_rect_style)  \n",
+    "                        \n",
+    "        ax = axs[1] # frequency domain\n",
+    "        ax.lines[1].set_ydata(Sa_f)\n",
+    "        axk = fig.axes[3];\n",
+    "        if M > 0 and r > 0:\n",
+    "            if len(axk.collections) > 1: axk.collections[1].remove()\n",
+    "            axk.fill_between([-.1, M-1+.1], -.05, 1.05, **highlight_rect_style)  \n",
+    "            rwth_plt.stem_set_data(axk.containers[0], k, SkF)\n",
+    "            \n",
+    "    axn = fig.axes[2]; axn.set_visible(r > 0)\n",
+    "    axk = fig.axes[3]; axk.set_visible(M > 0 and r > 0)\n",
+    "    if r > 0:\n",
+    "        axn.set_xlim((ax.get_xlim()[0]*r,ax.get_xlim()[1]*r))\n",
+    "        if M > 0:\n",
+    "            axk.set_xlim((ax.get_xlim()[0]*M/r,ax.get_xlim()[1]*M/r))"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "---\n",
+    "This notebook is provided as [Open Educational Resource](https://en.wikipedia.org/wiki/Open_educational_resources) (OER). Feel free to use the notebook for your own purposes. The code is licensed under the [MIT license](https://opensource.org/licenses/MIT). \n",
+    "\n",
+    "Please attribute the work as follows: \n",
+    "*Christian Rohlfing, Emin Kosar, Übungsbeispiele zur Vorlesung \"Grundgebiete der Elektrotechnik 3 - Signale und Systeme\"*, gehalten von Jens-Rainer Ohm, 2021, Institut für Nachrichtentechnik, RWTH Aachen University."
+   ]
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3 (ipykernel)",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.9.7"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/GDET3 Faltung GUI.ipynb b/GDET3 Faltung GUI.ipynb
index 63a06540f59b5577ce9200e1e9b47a58830a39f4..536377d252aa62894f15d5014b3c481c790566da 100644
--- a/GDET3 Faltung GUI.ipynb	
+++ b/GDET3 Faltung GUI.ipynb	
@@ -138,7 +138,12 @@
     "            \n",
     "    except: pass\n",
     "    rwth_plots.update_ylim(axs0[0], s(tau), 0.19, 5);  rwth_plots.update_ylim(axs0[1], h(tau), 0.19, 5);\n",
-    "    \n",
+    "\n",
+    "def swap_signals(b):\n",
+    "    s_type = w_s_type.value; h_type = w_h_type.value; w_s_type.value = h_type; w_h_type.value = s_type;\n",
+    "    s_T = w_s_T.value; h_T = w_h_T.value; w_s_T.value = h_T; w_h_T.value = s_T;\n",
+    "    s_t0 = w_s_t0.value; h_t0 = w_h_t0.value; w_s_t0.value = h_t0; w_h_t0.value = s_t0;\n",
+    "\n",
     "# Widgets\n",
     "w_s_type = widgets.Dropdown(options=list(signal_types.keys()), description=r'Wähle $s(t)$:')\n",
     "w_s_T = widgets.FloatSlider(min=0.5, max=4, value=1, step=.1, description=r'Dehnung T', style=rwth_plots.wdgtl_style)\n",
@@ -147,7 +152,10 @@
     "w_h_T = widgets.FloatSlider(min=0.5, max=4, value=1, step=.1, description=r'Dehnung T', style=rwth_plots.wdgtl_style)\n",
     "w_h_t0 = s_t0=widgets.FloatSlider(min=-2, max=2, value=0, step=.1, description=r'Verschiebung $t_0$', style=rwth_plots.wdgtl_style)\n",
     "w = widgets.interactive(update_signals, s_type=w_s_type, s_T=w_s_T, s_t0=w_s_t0, h_type=w_h_type, h_T=w_h_T, h_t0 = w_h_t0)\n",
-    "display(widgets.HBox((widgets.VBox((w_s_type, w_s_T, w_s_t0)), widgets.VBox((w_h_type, w_h_T, w_h_t0), layout=Layout(margin='0 0 0 100px'))))); w.update();"
+    "swap_s_h = widgets.Button(icon='arrows-h', description='Austauschen'); swap_s_h.on_click(swap_signals)\n",
+    "display(widgets.HBox((widgets.VBox((w_s_type, w_s_T, w_s_t0), layout=Layout(margin='0 50px 0 0')), \n",
+    "                      swap_s_h, \n",
+    "                      widgets.VBox((w_h_type, w_h_T, w_h_t0), layout=Layout(margin='0 0 0 50px'))))); w.update();"
    ]
   },
   {
@@ -219,13 +227,6 @@
     "update_plot_intervals()"
    ]
   },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "Verschiebung $t$ kann automatisch abgespielt werden. Eine schrittweise Betrachtung ist ebenfalls möglich."
-   ]
-  },
   {
    "cell_type": "code",
    "execution_count": null,
@@ -283,6 +284,13 @@
     "widgets.HBox([play, stepwise, reset, intslider])#, status"
    ]
   },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Verschiebung $t$ kann automatisch abgespielt werden. Eine schrittweise Betrachtung ist ebenfalls möglich."
+   ]
+  },
   {
    "cell_type": "markdown",
    "metadata": {},
@@ -327,7 +335,7 @@
    "name": "python",
    "nbconvert_exporter": "python",
    "pygments_lexer": "ipython3",
-   "version": "3.8.1"
+   "version": "3.8.3"
   }
  },
  "nbformat": 4,
diff --git a/GDET3 Faltungsbeispiel.ipynb b/GDET3 Faltungsbeispiel.ipynb
index ed333d3c84458207c48466d01ff2ec280768a734..6c79b19452dbe962f0d640ba35677d1e12bece02 100644
--- a/GDET3 Faltungsbeispiel.ipynb	
+++ b/GDET3 Faltungsbeispiel.ipynb	
@@ -313,7 +313,7 @@
    },
    "outputs": [],
    "source": [
-    "fig, axs = plt.subplots(2, 1, figsize=(8,6))\n",
+    "fig, axs = plt.subplots(2, 1, figsize=(8,6)); fig.tight_layout();\n",
     "gt = g(t)\n",
     "\n",
     "@widgets.interact(t=widgets.FloatSlider(min=-5, max=15, step=.2, value=5,description='$t$', continuous_update=True))\n",
@@ -383,7 +383,7 @@
    "name": "python",
    "nbconvert_exporter": "python",
    "pygments_lexer": "ipython3",
-   "version": "3.8.2"
+   "version": "3.8.3"
   }
  },
  "nbformat": 4,
diff --git a/GDET3 RLC-System.ipynb b/GDET3 RLC-System.ipynb
index 8f999ca4ed345b2507c9c4a3a626a08b2ad6813a..9ef543fbcabf6c21892db285f637ecdcd1fb8bb5 100644
--- a/GDET3 RLC-System.ipynb	
+++ b/GDET3 RLC-System.ipynb	
@@ -6,7 +6,8 @@
    "metadata": {
     "jupyter": {
      "source_hidden": true
-    }
+    },
+    "tags": []
    },
    "outputs": [],
    "source": [
@@ -56,7 +57,7 @@
    "outputs": [],
    "source": [
     "# Exemplary values\n",
-    "R = 16 # Ohm\n",
+    "R = 16 # Ohm, Ω\n",
     "L = 1.5E-3  # Henry, mH\n",
     "C = 1E-6 # Farad, myF"
    ]
@@ -245,7 +246,8 @@
    "metadata": {
     "jupyter": {
      "source_hidden": true
-    }
+    },
+    "tags": []
    },
    "outputs": [],
    "source": [
@@ -276,6 +278,30 @@
     "    display(HTML('{}<br />{}'.format('a={0:.3f}'.format(a), 'b={0:.3f}'.format(b))))"
    ]
   },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### Experiment\n",
+    "\n",
+    "Mit $R=0 \\,\\Omega$, $L=47\\,\\mathrm{mH}$ und $C=0,68\\,\\mu\\mathrm{F}$ liegt die entsprechende Grenzfrequenz bei $f_0 \\approx 900\\,Hz$. Rauchentwicklung ab Sekunde 20 ;)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "jupyter": {
+     "source_hidden": true
+    },
+    "tags": []
+   },
+   "outputs": [],
+   "source": [
+    "from IPython.display import Video\n",
+    "Video(\"figures/RLC_Experiment.mp4\", width=480, height=270 )"
+   ]
+  },
   {
    "cell_type": "markdown",
    "metadata": {},
@@ -284,13 +310,13 @@
     "This notebook is provided as [Open Educational Resource](https://en.wikipedia.org/wiki/Open_educational_resources) (OER). Feel free to use the notebook for your own purposes. The code is licensed under the [MIT license](https://opensource.org/licenses/MIT). \n",
     "\n",
     "Please attribute the work as follows: \n",
-    "*Christian Rohlfing, Übungsbeispiele zur Vorlesung \"Grundgebiete der Elektrotechnik 3 - Signale und Systeme\"*, gehalten von Jens-Rainer Ohm, 2020, Institut für Nachrichtentechnik, RWTH Aachen University."
+    "*Christian Rohlfing, Übungsbeispiele zur Vorlesung \"Grundgebiete der Elektrotechnik 3 - Signale und Systeme\"*, gehalten von Jens-Rainer Ohm, 2021, Institut für Nachrichtentechnik, RWTH Aachen University."
    ]
   }
  ],
  "metadata": {
   "kernelspec": {
-   "display_name": "Python 3",
+   "display_name": "Python 3 (ipykernel)",
    "language": "python",
    "name": "python3"
   },
@@ -304,7 +330,7 @@
    "name": "python",
    "nbconvert_exporter": "python",
    "pygments_lexer": "ipython3",
-   "version": "3.8.1"
+   "version": "3.9.7"
   }
  },
  "nbformat": 4,
diff --git a/README.md b/README.md
index 3f09480a40d33fbac9ef9407eff50cae6d77b5e6..a440a3669969c1bea3a6b514a0bd82073a073206 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,7 @@
 
 # Übungsbeispiele zur Vorlesung Grundgebiete der Elektrotechnik 3 &ndash; Signale und Systeme
 
+[![RWTHjupyter](https://jupyter.pages.rwth-aachen.de/documentation/images/badge-launch-rwth-jupyter.svg)](https://jupyter.rwth-aachen.de/hub/spawn?profile=gdet3&next=/user-redirect/lab/tree/gdet3%2Findex.ipynb) 
 [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/git/https%3A%2F%2Fgit.rwth-aachen.de%2FIENT%2Fgdet3-demos/master?urlpath=lab/tree/index.ipynb)
 
 ## Introduction
@@ -12,10 +13,10 @@ Visit the notebook [index.ipynb](index.ipynb) for a table of contents.
 
 ## Quick Start
 
-Run the notebooks directly online [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/git/https%3A%2F%2Fgit.rwth-aachen.de%2FIENT%2Fgdet3-demos/master?urlpath=lab/tree/index.ipynb). Please note the following limitations:
+Run the notebooks directly online with either [![RWTHjupyter](https://jupyter.pages.rwth-aachen.de/documentation/images/badge-launch-rwth-jupyter.svg)](https://jupyter.rwth-aachen.de/hub/spawn?profile=gdet3&next=/user-redirect/lab/tree/gdet3%2Findex.ipynb) or [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/git/https%3A%2F%2Fgit.rwth-aachen.de%2FIENT%2Fgdet3-demos/master?urlpath=lab/tree/index.ipynb). Please note the following limitations:
 
 * The starting process of the session may take up to one minute.
-* Please note that the session will be cancelled after 10 minutes of user inactivity.
+* Please note that the Binder session will be cancelled after 10 minutes of user inactivity.
 
 ## Usage
 
@@ -48,14 +49,16 @@ To run the notebooks on your local machine, you may use [Anaconda](https://www.a
 
 ### Docker
 
-For advanced users only: If you happen to have Docker installed, you can start a local dockerized JupyterLab with enabled GDET3-Demos with
+For advanced users only: If you happen to have Docker installed, you can start a local dockerized JupyterLab
 
 ```bash
-docker run --name='gdet3-demos' --rm -it -p 8888:8888 -e JUPYTER_ENABLE_LAB=yes registry.git.rwth-aachen.de/ient/gdet3-demos:master
+git clone --recurse-submodules https://git.rwth-aachen.de/IENT/gdet3-demos.git
+docker run --name='gdet3-demos' --rm -it -p 8888:8888 -v ${pwd}/gdet3-demos:/home/jovyan/work registry.git.rwth-aachen.de/ient/gdet3-demos:master
 ```
 
 Copy and paste the displayed link to your favorite browser.
 
+
 ## Contact
 
 * If you found a bug, please use the [issue tracker](https://git.rwth-aachen.de/IENT/gdet3-demos/issues).
diff --git a/figures/RLC_Experiment.mp4 b/figures/RLC_Experiment.mp4
new file mode 100755
index 0000000000000000000000000000000000000000..a541af0aa4954849d204cf568798f7b3a961fd84
Binary files /dev/null and b/figures/RLC_Experiment.mp4 differ
diff --git a/index.ipynb b/index.ipynb
index e55871b886c8f07b22896022277e540a464109d6..9131cb350a153b82bdf36c2b293b3810f37dfe0c 100644
--- a/index.ipynb
+++ b/index.ipynb
@@ -44,6 +44,7 @@
     "    * [Reale Abtastung](GDET3%20Reale%20Abtastung.ipynb)\n",
     "    * [Demo Diskretes Faltungsintegral](GDET3%20Diskrete%20Faltung%20GUI.ipynb)\n",
     "    * [Diskrete Fourier-Transformation](GDET3%20Diskrete%20Fourier-Transformation.ipynb)\n",
+    "    * [Demo Diskrete Fourier-Transformation](GDET3%20Diskrete%20Fourier-Transformation%20GUI.ipynb)\n",
     "    * [Demo $z$-Transformation](GDET3%20z-Transformation%20GUI.ipynb)\n",
     "5. Systemtheorie der Tiefpass- und Bandpasssysteme\n",
     "    * [Äquivalenter Tiefpass](GDET3%20Äquivalenter%20Tiefpass.ipynb)\n",
diff --git a/src/laplace/laplace_plot.py b/src/laplace/laplace_plot.py
index cd1f5612c661e379bec7d677662022a3973bbc04..b77af2acf2cb4e64a4a984788ed789228dae5a76 100644
--- a/src/laplace/laplace_plot.py
+++ b/src/laplace/laplace_plot.py
@@ -25,7 +25,7 @@ class pzPoint():
     def clear(self):
         if self.h_point is not None: self.h_point.remove(); self.h_point = None
         if self.h_point_conj is not None: self.h_point_conj.remove(); self.h_point_conj = None
-        if self.h_order is not None: self.h_order.remove(); self.h_order = None
+        if self.h_order is not None: self.h_order[0].remove(); self.h_order[1].remove(); self.h_order = None
 
 
 class pzPlot():
@@ -106,26 +106,96 @@ class pzPlot():
         self.ax.set_title('Pol- /Nullstellen Diagramm', fontsize='12')
         self.ax.set_aspect('equal', adjustable='datalim')
 
-        def onclick(event):
-            if self.action == 'add' and event.key == 'shift':
-                self.action = 'del'
+        # * move indicates whether a drag is happening
+        # * selected_coords indicates selected coords when button is pressed
+        # * ghost_hpoint, ghost_hpoint_conj are the half transparent poles/zeroes to indicate where the newly created will be
+        self.move = False
+        self.selected_coords = None
+        self.ghost_hpoint = None
+        self.ghost_hpoint_conj = None
 
+        def on_btn_press(event):
             if event.inaxes != self.ax: return
             if self.filter != 'man': return
+
+            # button press, reset move
+            self.move = False
+
+            # find closest point and set selected if there are any valid points nearby
             p = event.xdata + 1j * event.ydata
-            self.update_point(p, self.mode)
+            self.selected_coords = p
+
+        def on_btn_release(event):
+            if event.inaxes != self.ax:
+                if self.ghost_hpoint is not None:
+                    self.ghost_hpoint.remove()
+                    self.ghost_hpoint_conj.remove()
+                    self.ghost_hpoint = None
+                    self.ghost_hpoint_conj = None
+                return
+            if self.filter != 'man': return
+
+            # press + no movement + release = click
+            if not self.move:
+                on_click(event)
+
+            # press + movement + release = drag
+            # if dragging process is finished
+            if self.move and self.find_closest_point(self.selected_coords, self.mode)[0] is not None:
+                # release move
+                self.move = False
+
+                # remove any ghosts from the figure and clear variables
+                if self.ghost_hpoint is not None:
+                    self.ghost_hpoint.remove()
+                    self.ghost_hpoint_conj.remove()
+                    self.ghost_hpoint = None
+                    self.ghost_hpoint_conj = None
+
+                # delete at press selected point
+                tmp_action = self.action
+                self.action = 'del'
+                self.update_point(self.selected_coords, self.mode)
+
+                # add new point at drag point
+                self.action = 'add'
+                on_click(event)
+                self.action = tmp_action
 
-        def onkeypress(event):
-            self.action_locked = True if self.action == 'del' else False
-            self.w_action_type.children[0].value = 'Löschen'
+        def on_motion(event):
+            if event.inaxes != self.ax: return
+            if self.filter != 'man': return
+
+            # if button is pressed
+            if event.button == 1:
+                # lock move
+                self.move = True
+
+                # if a point was selected
+                if self.find_closest_point(self.selected_coords, self.mode)[0] is not None:
+                    if self.ghost_hpoint is None:
+                        # draw ghost point
+                        if self.mode == 'p':
+                            plotstyle = rwth_plots.style_poles
+                        else:
+                            plotstyle = rwth_plots.style_zeros
+
+                        self.ghost_hpoint, = self.ax.plot(event.xdata, event.ydata, **plotstyle, alpha=.5)
 
-        def onkeyrelease(event):
-            if not self.action_locked:
-                self.w_action_type.children[0].value = 'Hinzufügen'
+                        if np.abs(event.ydata) > 0:  # plot complex conjugated poles/zeroes
+                            self.ghost_hpoint_conj, = self.ax.plot(event.xdata, -event.ydata, **plotstyle, alpha=.5)
+                    else:
+                        self.ghost_hpoint.set_data(event.xdata, event.ydata)
+                        self.ghost_hpoint_conj.set_data(event.xdata, -event.ydata)
+
+        def on_click(event):
+            # update point
+            p = event.xdata + 1j * event.ydata
+            self.update_point(p, self.mode)
 
-        self.fig.canvas.mpl_connect('button_press_event', onclick)
-        self.fig.canvas.mpl_connect('key_press_event', onkeypress)
-        self.fig.canvas.mpl_connect('key_release_event', onkeyrelease)
+        self.fig.canvas.mpl_connect('button_press_event', on_btn_press)
+        self.fig.canvas.mpl_connect('button_release_event', on_btn_release)
+        self.fig.canvas.mpl_connect('motion_notify_event', on_motion)
 
         self.handles['axh'] = plt.subplot(gs[0, 1])
         self.handles['axh'].set_title('Impulsantwort', fontsize='12')
@@ -433,7 +503,7 @@ class pzPlot():
         if not (roc[0] <= 0 <= roc[1]):
             S_f = np.full(self.f.shape, np.NaN)
         else:
-            S_f = rwth_transforms.ilaplace_Hf(self.f, self.H0, poles, zeroes, poles_order, zeroes_order, dB=True)
+            S_f = rwth_transforms.ilaplace_Hf(self.f, self.H0, poles, zeroes, poles_order, zeroes_order, dB=True).round(8)
 
         # process signals
         # delete existing dirac
diff --git a/src/z_transform/z_transform.py b/src/z_transform/z_transform.py
index 91449076817400576ad339d0831cc383279e6f8f..3fcee1a63777a290f3d65686cd04f24d1ea46e47 100644
--- a/src/z_transform/z_transform.py
+++ b/src/z_transform/z_transform.py
@@ -25,7 +25,7 @@ class pzPoint():
     def clear(self):
         if self.h_point is not None: self.h_point.remove(); self.h_point = None
         if self.h_point_conj is not None: self.h_point_conj.remove(); self.h_point_conj = None
-        if self.h_order is not None: self.h_order.remove(); self.h_order = None
+        if self.h_order is not None: self.h_order[0].remove(); self.h_order[1].remove(); self.h_order = None
 
 
 class zPlot():
@@ -103,26 +103,99 @@ class zPlot():
         self.ax.set_title('Pol- /Nullstellen Diagramm', fontsize='12')
         self.ax.set_aspect('equal')
 
-        def onclick(event):
-            if self.action == 'add' and event.key == 'shift':
-                self.action = 'del'
+        # * move indicates whether a drag is happening
+        # * selected_coords indicates selected coords when button is pressed
+        # * ghost_hpoint, ghost_hpoint_conj are the half transparent poles/zeroes to indicate where the newly created will be
+        self.move = False
+        self.selected_coords = None
+        self.ghost_hpoint = None
+        self.ghost_hpoint_conj = None
 
+        def on_btn_press(event):
             if event.inaxes != self.ax: return
             if self.filter != 'man': return
+
+            # button press, reset move
+            self.move = False
+
+            # find closest point and set selected if there are any valid points nearby
             p = event.xdata + 1j * event.ydata
-            self.update_point(p, self.mode)
+            self.selected_coords = p
+
+        def on_btn_release(event):
+            if event.inaxes != self.ax:
+                if self.ghost_hpoint is not None:
+                    self.ghost_hpoint.remove()
+                    self.ghost_hpoint_conj.remove()
+                    self.ghost_hpoint = None
+                    self.ghost_hpoint_conj = None
+                return
+            if self.filter != 'man': return
 
-        def onkeypress(event):
-            self.action_locked = self.action == 'del'
-            self.w_action_type.children[0].value = 'Löschen'
+            # press + no movement + release = click
+            if not self.move:
+                on_click(event)
+
+            # press + movement + release = drag
+            # if dragging process is finished
+            if self.move and self.find_closest_point(self.selected_coords, self.mode)[0] is not None:
+                # release move
+                self.move = False
+
+                # remove any ghosts from the figure and clear variables
+                if self.ghost_hpoint is not None:
+                    self.ghost_hpoint.remove()
+                    self.ghost_hpoint_conj.remove()
+                    self.ghost_hpoint = None
+                    self.ghost_hpoint_conj = None
+
+                # delete at press selected point
+                tmp_action = self.action
+                self.action = 'del'
+                self.update_point(self.selected_coords, self.mode)
+
+                # add new point at drag point
+                self.action = 'add'
+                on_click(event)
+                self.action = tmp_action
+
+        def on_motion(event):
+            if event.inaxes != self.ax: return
+            if self.filter != 'man': return
 
-        def onkeyrelease(event):
-            if not self.action_locked:
-                self.w_action_type.children[0].value = 'Hinzufügen'
+            # if button is pressed
+            if event.button == 1:
+                # lock move
+                self.move = True
+
+                # if a point was selected
+                if self.find_closest_point(self.selected_coords, self.mode)[0] is not None:
+                    if self.ghost_hpoint is None:
+                        # draw ghost point
+                        if self.mode == 'p':
+                            plotstyle = rwth_plots.style_poles
+                        else:
+                            plotstyle = rwth_plots.style_zeros
+
+                        self.ghost_hpoint, = self.ax.plot(event.xdata, event.ydata, **plotstyle, alpha=.5)
+
+                        if np.abs(event.ydata) > 0:  # plot complex conjugated poles/zeroes
+                            self.ghost_hpoint_conj, = self.ax.plot(event.xdata, -event.ydata, **plotstyle, alpha=.5)
+                    else:
+                        self.ghost_hpoint.set_data(event.xdata, event.ydata)
+                        self.ghost_hpoint_conj.set_data(event.xdata, -event.ydata)
+
+        def on_click(event):
+            if self.filter != 'man': return
+            if event.inaxes != self.ax: return
+
+            # update point
+            p = event.xdata + 1j * event.ydata
+            self.update_point(p, self.mode)
 
-        self.fig.canvas.mpl_connect('button_press_event', onclick)
-        self.fig.canvas.mpl_connect('key_press_event', onkeypress)
-        self.fig.canvas.mpl_connect('key_release_event', onkeyrelease)
+        self.fig.canvas.mpl_connect('button_press_event', on_btn_press)
+        self.fig.canvas.mpl_connect('button_release_event', on_btn_release)
+        self.fig.canvas.mpl_connect('motion_notify_event', on_motion)
 
         self.handles['axh'] = plt.subplot(gs[0, 1])
         self.handles['axh'].set_xlim(-15, 15);
@@ -442,11 +515,13 @@ class zPlot():
 
         # update f-domain
         if self.systemIsStable:
-            H_f = np.abs(rwth_transforms.iz_Hf(self.f, self.H0, poles, zeroes, poles_order, zeroes_order, True))
+            H_f = np.abs(rwth_transforms.iz_Hf(self.f, self.H0, poles, zeroes, poles_order, zeroes_order, True)).round(8)
+            rwth_plots.update_ylim(self.handles['axH'], H_f, 1.9, ymax=1e5)
         else:
-            H_f = np.ones(self.f.shape) * -.5
+            H_f = np.empty(self.f.shape)
+            H_f[:] = np.nan
+            rwth_plots.update_ylim(self.handles['axH'], [-0.5, 0.5], 0.1)
 
         self.stabilitytxt.set_visible(not self.systemIsStable)
 
         self.handles['lineH'].set_ydata(H_f)
-        rwth_plots.update_ylim(self.handles['axH'], H_f, 1.9, ymax=1e5)