{ "cells": [ { "cell_type": "markdown", "metadata": { "hideCode": true, "hidePrompt": true }, "source": [ "````{margin}\n", "```{admonition} User types\n", ":class: tip\n", "This section is useful for user type 5.\n", "```\n", "````\n", "\n", "# Interactive plots: plotly\n", "\n", "The use of the [plotly](https://plotly.com/python/getting-started/) package is an example which allows you to create HTML/Javascript widgets from python code without any knowledge on HTML/Javascript." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The example below is taken from the FEM-module by Frans van der Meer.\n", "When using Plotly graphs, make sure the notebook is executed in Jupyter Lab / Jupyter Notebook for the figures to be shown in the book. Running the code in VS code might break the result in the book, although the output is visible in VS code. Alternatively, add the following lines of code to your notebook to have a valid output in the book as well:\n", "\n", "```python\n", "import plotly.io as pio\n", "pio.renderers.default = 'notebook'\n", "```" ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "tags": [ "remove-cell" ] }, "outputs": [], "source": [ "%matplotlib widget\n", "\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "import plotly.subplots as sp\n", "import plotly.graph_objects as go\n", "import plotly.io as pio\n", "pio.renderers.default = 'notebook'" ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "tags": [ "remove-cell" ] }, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "6241088f78f046be86f2e814448e8790", "version_major": 2, "version_minor": 0 }, "image/png": "iVBORw0KGgoAAAANSUhEUgAAAoAAAAHgCAYAAAA10dzkAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8pXeV/AAAACXBIWXMAAA9hAAAPYQGoP6dpAAAIyUlEQVR4nO3WMQEAIAzAMMC/5+ECjiYKenbPzCwAADLO7wAAAN4ygAAAMQYQACDGAAIAxBhAAIAYAwgAEGMAAQBiDCAAQIwBBACIMYAAADEGEAAgxgACAMQYQACAGAMIABBjAAEAYgwgAECMAQQAiDGAAAAxBhAAIMYAAgDEGEAAgBgDCAAQYwABAGIMIABAjAEEAIgxgAAAMQYQACDGAAIAxBhAAIAYAwgAEGMAAQBiDCAAQIwBBACIMYAAADEGEAAgxgACAMQYQACAGAMIABBjAAEAYgwgAECMAQQAiDGAAAAxBhAAIMYAAgDEGEAAgBgDCAAQYwABAGIMIABAjAEEAIgxgAAAMQYQACDGAAIAxBhAAIAYAwgAEGMAAQBiDCAAQIwBBACIMYAAADEGEAAgxgACAMQYQACAGAMIABBjAAEAYgwgAECMAQQAiDGAAAAxBhAAIMYAAgDEGEAAgBgDCAAQYwABAGIMIABAjAEEAIgxgAAAMQYQACDGAAIAxBhAAIAYAwgAEGMAAQBiDCAAQIwBBACIMYAAADEGEAAgxgACAMQYQACAGAMIABBjAAEAYgwgAECMAQQAiDGAAAAxBhAAIMYAAgDEGEAAgBgDCAAQYwABAGIMIABAjAEEAIgxgAAAMQYQACDGAAIAxBhAAIAYAwgAEGMAAQBiDCAAQIwBBACIMYAAADEGEAAgxgACAMQYQACAGAMIABBjAAEAYgwgAECMAQQAiDGAAAAxBhAAIMYAAgDEGEAAgBgDCAAQYwABAGIMIABAjAEEAIgxgAAAMQYQACDGAAIAxBhAAIAYAwgAEGMAAQBiDCAAQIwBBACIMYAAADEGEAAgxgACAMQYQACAGAMIABBjAAEAYgwgAECMAQQAiDGAAAAxBhAAIMYAAgDEGEAAgBgDCAAQYwABAGIMIABAjAEEAIgxgAAAMQYQACDGAAIAxBhAAIAYAwgAEGMAAQBiDCAAQIwBBACIMYAAADEGEAAgxgACAMQYQACAGAMIABBjAAEAYgwgAECMAQQAiDGAAAAxBhAAIMYAAgDEGEAAgBgDCAAQYwABAGIMIABAjAEEAIgxgAAAMQYQACDGAAIAxBhAAIAYAwgAEGMAAQBiDCAAQIwBBACIMYAAADEGEAAgxgACAMQYQACAGAMIABBjAAEAYgwgAECMAQQAiDGAAAAxBhAAIMYAAgDEGEAAgBgDCAAQYwABAGIMIABAjAEEAIgxgAAAMQYQACDGAAIAxBhAAIAYAwgAEGMAAQBiDCAAQIwBBACIMYAAADEGEAAgxgACAMQYQACAGAMIABBjAAEAYgwgAECMAQQAiDGAAAAxBhAAIMYAAgDEGEAAgBgDCAAQYwABAGIMIABAjAEEAIgxgAAAMQYQACDGAAIAxBhAAIAYAwgAEGMAAQBiDCAAQIwBBACIMYAAADEGEAAgxgACAMQYQACAGAMIABBjAAEAYgwgAECMAQQAiDGAAAAxBhAAIMYAAgDEGEAAgBgDCAAQYwABAGIMIABAjAEEAIgxgAAAMQYQACDGAAIAxBhAAIAYAwgAEGMAAQBiDCAAQIwBBACIMYAAADEGEAAgxgACAMQYQACAGAMIABBjAAEAYgwgAECMAQQAiDGAAAAxBhAAIMYAAgDEGEAAgBgDCAAQYwABAGIMIABAjAEEAIgxgAAAMQYQACDGAAIAxBhAAIAYAwgAEGMAAQBiDCAAQIwBBACIMYAAADEGEAAgxgACAMQYQACAGAMIABBjAAEAYgwgAECMAQQAiDGAAAAxBhAAIMYAAgDEGEAAgBgDCAAQYwABAGIMIABAjAEEAIgxgAAAMQYQACDGAAIAxBhAAIAYAwgAEGMAAQBiDCAAQIwBBACIMYAAADEGEAAgxgACAMQYQACAGAMIABBjAAEAYgwgAECMAQQAiDGAAAAxBhAAIMYAAgDEGEAAgBgDCAAQYwABAGIMIABAjAEEAIgxgAAAMQYQACDGAAIAxBhAAIAYAwgAEGMAAQBiDCAAQIwBBACIMYAAADEGEAAgxgACAMQYQACAGAMIABBjAAEAYgwgAECMAQQAiDGAAAAxBhAAIMYAAgDEGEAAgBgDCAAQYwABAGIMIABAjAEEAIgxgAAAMQYQACDGAAIAxBhAAIAYAwgAEGMAAQBiDCAAQIwBBACIMYAAADEGEAAgxgACAMQYQACAGAMIABBjAAEAYgwgAECMAQQAiDGAAAAxBhAAIMYAAgDEGEAAgBgDCAAQYwABAGIMIABAjAEEAIgxgAAAMQYQACDGAAIAxBhAAIAYAwgAEGMAAQBiDCAAQIwBBACIMYAAADEGEAAgxgACAMQYQACAGAMIABBjAAEAYgwgAECMAQQAiDGAAAAxBhAAIMYAAgDEGEAAgBgDCAAQYwABAGIMIABAjAEEAIgxgAAAMQYQACDGAAIAxBhAAIAYAwgAEGMAAQBiDCAAQIwBBACIMYAAADEGEAAgxgACAMQYQACAGAMIABBjAAEAYgwgAECMAQQAiDGAAAAxBhAAIMYAAgDEGEAAgBgDCAAQYwABAGIMIABAjAEEAIgxgAAAMQYQACDGAAIAxBhAAIAYAwgAEGMAAQBiDCAAQIwBBACIMYAAADEGEAAgxgACAMQYQACAGAMIABBjAAEAYgwgAECMAQQAiDGAAAAxBhAAIMYAAgDEGEAAgBgDCAAQYwABAGIMIABAjAEEAIgxgAAAMQYQACDGAAIAxBhAAIAYAwgAEGMAAQBiDCAAQIwBBACIMYAAADEGEAAgxgACAMQYQACAGAMIABBjAAEAYgwgAECMAQQAiDGAAAAxBhAAIMYAAgDEGEAAgBgDCAAQYwABAGIMIABAjAEEAIgxgAAAMQYQACDGAAIAxBhAAIAYAwgAEGMAAQBiDCAAQIwBBACIMYAAADEGEAAgxgACAMQYQACAGAMIABBjAAEAYgwgAECMAQQAiDGAAAAxBhAAIMYAAgDEGEAAgBgDCAAQYwABAGIuJnkHvKensmIAAAAASUVORK5CYII=", "text/html": [ "\n", "
\n", "
\n", " Figure\n", "
\n", " \n", "
\n", " " ], "text/plain": [ "Canvas(toolbar=None)" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "plt.figure()\n", "plt.rcParams.update({'font.size': 12, 'lines.linewidth': 2})\n", "\n", "def shape_functions(total_length, num_nodes, node_x):\n", " l = total_length / (num_nodes - 1)\n", "\n", " if node_x == 0:\n", " x = np.array([node_x, node_x + l])\n", " y = np.array([1.0, 0.0])\n", " elif node_x == total_length:\n", " x = np.array([node_x - l, node_x])\n", " y = np.array([0.0, 1.0])\n", " else:\n", " x = np.array([node_x - l, node_x, node_x + l])\n", " y = np.array([0.0, 1.0, 0.0])\n", "\n", " return x, y\n", "\n", "def shape_functions_for_plot(num_nodes, beam_length = 15):\n", "\n", " x_positions = np.linspace(0, beam_length, num_nodes)\n", "\n", " data = {}\n", "\n", " for node_num, x_coords in enumerate(x_positions):\n", "\n", " x, y = shape_functions(beam_length, num_nodes, x_coords)\n", " data[node_num + 1] = {'x': x, 'y': y}\n", "\n", " return data" ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "tags": [ "remove-cell" ] }, "outputs": [], "source": [ "def linear_lagrange_shape_function(x, nodes_x_values, nodes_displacements):\n", " \"\"\"\n", " Compute the linear Lagrange shape function for a given x.\n", " Args:\n", " x (float): The x-coordinate.\n", " x0 (float): Left node position.\n", " x1 (float): Right node position.\n", " Returns:\n", " float: The shape function value.\n", " \"\"\"\n", "\n", " ratio = (x - nodes_x_values[0]) / (nodes_x_values[1] - nodes_x_values[0])\n", "\n", " return (1 - ratio) * nodes_displacements[0] + ratio * nodes_displacements[1]\n", "\n", "def function(x):\n", " \"\"\"\n", " Define the function to be plotted.\n", " Args:\n", " x (numpy.ndarray): Input values.\n", " Returns:\n", " numpy.ndarray: Corresponding function values.\n", " \"\"\"\n", " return np.sin(x) * 0.14*x\n", "\n", "def shape_functions_applied(num_elements):\n", " \"\"\"\n", " Plot linear Lagrange shape functions on a beam.\n", " Args:\n", " num_elements (int): Number of elements (segments).\n", " \"\"\"\n", "\n", " beam_length = 15\n", "\n", " num_nodes = num_elements + 1\n", " x_at_nodes = np.linspace(0, beam_length, num_nodes)\n", "\n", " y_at_nodes = function(x_at_nodes)\n", "\n", " x_data = np.linspace(0, beam_length, 1000)\n", "\n", " # Use NumPy to compute y_vals efficiently\n", " y_vals = np.zeros_like(x_data)\n", "\n", " for element_number in range(num_elements):\n", " mask = (x_at_nodes[element_number] <= x_data) & (x_data <= x_at_nodes[element_number + 1])\n", " y_vals[mask] = linear_lagrange_shape_function(x_data[mask], x_at_nodes[element_number:element_number + 2], y_at_nodes[element_number:element_number + 2])\n", "\n", " return x_data, function(x_data), y_vals\n" ] }, { "cell_type": "code", "execution_count": 17, "metadata": { "tags": [ "remove-input" ] }, "outputs": [ { "data": { "text/html": [ " \n", " " ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# Colors\n", "grey = '#eeeeee' # light grey fill\n", "\n", "# Prepare all the graphs that are to be plotted\n", "range_elements = np.concatenate((np.arange(1, 7, 1), np.arange(7, 35, 4), np.arange(35, 55, 8)))\n", "\n", "# Create subplots\n", "fig = sp.make_subplots(rows=1, cols=2, subplot_titles=(\"Shape functions\", \"Exact and approximated solution\"))\n", "\n", "base_traces_left = 2 # Number of traces in the left plot that are always visible\n", "base_traces_right = 0 # Number of traces in the right plot that are always visible\n", "\n", "# Add traces to the subplots\n", "x_data, y_data, y_vals = shape_functions_applied(10)\n", "\n", "# Add left plot traces (Cumulative Influx and Cumulative Outflux)\n", "fig.add_trace(\n", " go.Scatter(\n", " visible=True, # Show for the first value of a\n", " x=x_data,\n", " y=y_data,\n", " # line=dict(color='#6a8ba4'),\n", " mode='lines',\n", " name='Exact solution'\n", " ),\n", " row=1,\n", " col=2\n", ")\n", "\n", "\n", "# Add traces to the subplots\n", "for a in range_elements:\n", "\n", " x_data, y_data, y_vals = shape_functions_applied(a)\n", "\n", " fig.add_trace(\n", " go.Scatter(\n", " visible = True if a == range_elements[0] else False, # Show for the first value of a\n", " x=x_data,\n", " y=y_vals,\n", " # line=dict(color='#FFA500'),\n", " mode='lines',\n", " name='Numerical solution'\n", " ),\n", " row=1,\n", " col=2\n", " )\n", "\n", "\n", "# Add traces to the subplots\n", "for a in range_elements:\n", "\n", " data = shape_functions_for_plot(a + 1)\n", "\n", " for i in range(1, a + 2):\n", "\n", " info = data[i]\n", " x, y = info['x'], info['y']\n", "\n", " # Add right plot traces (Cumulative Outflux for each 'a' value)\n", " fig.add_trace(\n", " go.Scatter(\n", " visible = True if a == range_elements[0] else False,\n", " x=x,\n", " y=y,\n", " mode='lines',\n", " name=f'Node shape for node {i}'\n", " ),\n", " row=1,\n", " col=1\n", " )\n", "\n", "# Create and add slider\n", "steps = []\n", "for i in range(0, range_elements.shape[0]):\n", "\n", " a = range_elements[i]\n", " visarray = [False] * len(fig.data)\n", "\n", " visarray[0] = True\n", " visarray[i + 1] = True\n", "\n", " start_of_second_plot = 1 + len(range_elements)\n", "\n", " if i==0:\n", " start_index=start_of_second_plot\n", " else:\n", " start_index = start_of_second_plot + np.sum(range_elements[0:i]) + i\n", "\n", " num_lines = range_elements[i] + 1\n", " end_index = start_index + num_lines\n", "\n", " visarray[start_index:end_index] = [True] * (end_index - start_index)\n", " \n", " step = dict(\n", " method=\"update\",\n", " args=[{\"visible\": visarray}],\n", " label=str(round(range_elements[i], 1))\n", " )\n", " steps.append(step)\n", "\n", "sliders = [dict(\n", " active=0, # Show the first value of 'a' initially\n", " currentvalue={\"prefix\": r\"Number of elements: \"},\n", " steps=steps\n", ")]\n", "\n", "fig.update_layout(\n", " sliders=sliders,\n", " title=\"Applying shape functions\",\n", " legend_title=\"Legend\",\n", " legend=dict(\n", " x=1.05, # Adjust the legend position\n", " y=0.5\n", " )\n", ")\n", "\n", "fig.update_xaxes(title_text='x', row=1, col=1, range=[x_data[0], x_data[-1]])\n", "fig.update_xaxes(title_text='x', row=1, col=2, range=[x_data[0], x_data[-1]])\n", "\n", "# Update y-axis properties\n", "fig.update_yaxes(title_text='Node shape value', row=1, col=1, range=[-0.1, 1.1])\n", "fig.update_yaxes(title_text='y', row=1, col=2, range=[-2.1, 2.1])\n", "\n", "fig.show()" ] } ], "metadata": { "celltoolbar": "Tags", "hide_code_all_hidden": true, "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.18" } }, "nbformat": 4, "nbformat_minor": 4 }