diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..8f2b9a90 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +.idea/ +.ipynb_checkpoints/ +.vscode/ +node_modules/ +pyironflow/static/ +.coverage +.DS_Store +.pre-commit-config.yaml +pyiron.log +*.pyc +*.zip +*Zone.Identifier +pyiron_nodes/ +.ipynb +Untitled.ipynb diff --git a/Do_heron_step_While_diff_above_With_B2T_half2x_x_n2y_B2B_half2x_n/recovery.pckl b/Do_heron_step_While_diff_above_With_B2T_half2x_x_n2y_B2B_half2x_n/recovery.pckl new file mode 100644 index 00000000..470ed8e4 Binary files /dev/null and b/Do_heron_step_While_diff_above_With_B2T_half2x_x_n2y_B2B_half2x_n/recovery.pckl differ diff --git a/Untitled1.ipynb b/Untitled1.ipynb new file mode 100644 index 00000000..f79df96f --- /dev/null +++ b/Untitled1.ipynb @@ -0,0 +1,197 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "4925e4b4-b37a-4b3c-aadf-78be8457a1b5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[1G\u001b[0K⠙\u001b[1G\u001b[0K⠹\u001b[1G\u001b[0K⠸\u001b[1G\u001b[0K⠼\u001b[1G\u001b[0K⠴\u001b[1G\u001b[0K⠦\u001b[1G\u001b[0K⠧\u001b[1G\u001b[0K⠇\u001b[1G\u001b[0K⠏\u001b[1G\u001b[0K⠋\u001b[1G\u001b[0K⠙\u001b[1G\u001b[0K⠹\u001b[1G\u001b[0K⠸\u001b[1G\u001b[0K⠼\u001b[1G\u001b[0K⠴\u001b[1G\u001b[0K⠦\u001b[1G\u001b[0K⠧\u001b[1G\u001b[0K⠇\u001b[1G\u001b[0K⠏\u001b[1G\u001b[0K⠋\u001b[1G\u001b[0K⠙\u001b[1G\u001b[0K⠹\u001b[1G\u001b[0K⠸\u001b[1G\u001b[0K⠼\u001b[1G\u001b[0K⠴\u001b[1G\u001b[0K⠦\u001b[1G\u001b[0K⠧\u001b[1G\u001b[0K⠇\u001b[1G\u001b[0K⠏\u001b[1G\u001b[0K⠋\u001b[1G\u001b[0K⠙\u001b[1G\u001b[0K⠹\u001b[1G\u001b[0K⠸\u001b[1G\u001b[0K⠼\u001b[1G\u001b[0K⠴\u001b[1G\u001b[0K⠦\u001b[1G\u001b[0K⠧\u001b[1G\u001b[0K⠇\u001b[1G\u001b[0K⠏\u001b[1G\u001b[0K⠋\u001b[1G\u001b[0K⠙\u001b[1G\u001b[0K⠹\u001b[1G\u001b[0K⠸\u001b[1G\u001b[0K⠼\u001b[1G\u001b[0K\n", + "up to date, audited 94 packages in 4s\n", + "\u001b[1G\u001b[0K⠼\u001b[1G\u001b[0K\n", + "\u001b[1G\u001b[0K⠼\u001b[1G\u001b[0K7 packages are looking for funding\n", + "\u001b[1G\u001b[0K⠼\u001b[1G\u001b[0K run `npm fund` for details\n", + "\u001b[1G\u001b[0K⠼\u001b[1G\u001b[0K\n", + "\u001b[31m\u001b[1m5\u001b[22m\u001b[39m vulnerabilities (4 \u001b[33m\u001b[1mmoderate\u001b[22m\u001b[39m, 1 \u001b[31m\u001b[1mhigh\u001b[22m\u001b[39m)\n", + "\n", + "To address issues that do not require attention, run:\n", + " npm audit fix\n", + "\n", + "To address all issues (including breaking changes), run:\n", + " npm audit fix --force\n", + "\n", + "Run `npm audit` for details.\n", + "\u001b[1G\u001b[0K⠼\u001b[1G\u001b[0K" + ] + } + ], + "source": [ + "!npm install react react-dom @xyflow/react @anywidget/react" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "d07b9901-3395-4a1e-8125-329aa3e4787c", + "metadata": {}, + "outputs": [], + "source": [ + "import sys\n", + "from pathlib import Path\n", + "\n", + "sys.path.insert(0, str(Path(Path.cwd())))" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "7fd7a9da-c9ca-41a5-8dbb-7fcc1ef02f0e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[1G\u001b[0K⠙\u001b[1G\u001b[0K\n", + " \u001b[37mpyironflow/static/\u001b[0m\u001b[1mwidget.js\u001b[0m \u001b[33m1.8mb ⚠️\u001b[0m\n", + " \u001b[37mpyironflow/static/\u001b[0m\u001b[1mwidget.css\u001b[0m \u001b[36m16.6kb\u001b[0m\n", + "\n", + "⚡ \u001b[32mDone in 359ms\u001b[0m\n", + "\u001b[1G\u001b[0K⠙\u001b[1G\u001b[0K" + ] + } + ], + "source": [ + "!npx esbuild js/widget.jsx --minify --format=esm --bundle --outdir=pyironflow/static" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "72f313da-7e59-4b1e-99bd-1ba873a0b77c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[1G\u001b[0K⠙\u001b[1G\u001b[0K\n", + " \u001b[37mpyironflow/nodeeditor_static/\u001b[0m\u001b[1mwidget.js\u001b[0m \u001b[33m1.8mb ⚠️\u001b[0m\n", + " \u001b[37mpyironflow/nodeeditor_static/\u001b[0m\u001b[1mwidget.css\u001b[0m \u001b[36m16.6kb\u001b[0m\n", + "\n", + "⚡ \u001b[32mDone in 357ms\u001b[0m\n", + "\u001b[1G\u001b[0K⠙\u001b[1G\u001b[0K" + ] + } + ], + "source": [ + "!npx esbuild js/widget.jsx --minify --format=esm --bundle --outdir=pyironflow/nodeeditor_static" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "844ac6b5-fcde-45c7-a414-8e8478a42707", + "metadata": {}, + "outputs": [], + "source": [ + "from pyiron_workflow import Workflow\n", + "#from typing import Optional\n", + "from pyironflow.pyironflow import PyironFlow\n", + "from pyironflow.nodeeditor import NodeEditor\n", + "from pyironflow.wf_extensions import get_nodes\n", + "#import pyiron_nodes\n", + "#from pyironflow.macro import get_node_instance_type\n", + "\n", + "#from pyiron_workflow import Workflow\n", + "#from pyiron_workflow.channels import NotData\n", + "#from pyironflow.themes import get_color\n", + "#from pyironflow.macro import get_node_instance_type\n", + "#from pyironflow.wf_extensions import get_import_path, create_macro\n", + "\n", + "import anywidget\n", + "import pathlib\n", + "import traitlets\n", + "import os\n", + "import json\n", + "import importlib\n", + "import typing\n", + "import ipywidgets as widgets\n", + "from pyiron_workflow.nodes.while_loop import while_node_factory\n", + "from pyiron_workflow.nodes.for_loop import for_node_factory\n", + "from pyiron_workflow.nodes.for_loop import for_node\n", + "import pyiron_workflow as pwf" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "38e4f2e0-880f-437f-a365-2ca1f26d5a6b", + "metadata": {}, + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'fib' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[10], line 11\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[38;5;129m@as_function_node\u001b[39m()\n\u001b[1;32m 6\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21madd_1\u001b[39m(a: Optional[\u001b[38;5;28mfloat\u001b[39m \u001b[38;5;241m|\u001b[39m \u001b[38;5;28mint\u001b[39m] \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m):\n\u001b[1;32m 7\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m a\u001b[38;5;241m+\u001b[39m\u001b[38;5;241m1\u001b[39m\n\u001b[1;32m 9\u001b[0m fibonacci_loop \u001b[38;5;241m=\u001b[39m while_node_factory(\n\u001b[1;32m 10\u001b[0m pwf\u001b[38;5;241m.\u001b[39mstd\u001b[38;5;241m.\u001b[39mLessThan,\n\u001b[0;32m---> 11\u001b[0m \u001b[43mfib\u001b[49m\u001b[38;5;241m.\u001b[39mFibonacci,\n\u001b[1;32m 12\u001b[0m [(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mnext\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mobj\u001b[39m\u001b[38;5;124m\"\u001b[39m)],\n\u001b[1;32m 13\u001b[0m [(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mnext\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mthis\u001b[39m\u001b[38;5;124m\"\u001b[39m),(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mnow\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mlast\u001b[39m\u001b[38;5;124m\"\u001b[39m)],\n\u001b[1;32m 14\u001b[0m \u001b[38;5;28;01mTrue\u001b[39;00m,\n\u001b[1;32m 15\u001b[0m \u001b[38;5;28;01mFalse\u001b[39;00m,\n\u001b[1;32m 16\u001b[0m )\n", + "\u001b[0;31mNameError\u001b[0m: name 'fib' is not defined" + ] + } + ], + "source": [ + "from pyiron_workflow import as_function_node, as_macro_node\n", + "import typing\n", + "from typing import Optional\n", + "\n", + "@as_function_node()\n", + "def add_1(a: Optional[float | int] = None):\n", + " return a+1\n", + "\n", + "fibonacci_loop = while_node_factory(\n", + " pwf.std.LessThan,\n", + " fib.Fibonacci,\n", + " [(\"next\", \"obj\")],\n", + " [(\"next\", \"this\"),(\"now\", \"last\")],\n", + " True,\n", + " False,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ae574756-596c-44a6-b335-52eb4b51ec9d", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "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.12.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/Untitled2.ipynb b/Untitled2.ipynb new file mode 100644 index 00000000..deedcb84 --- /dev/null +++ b/Untitled2.ipynb @@ -0,0 +1,1325 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 15, + "id": "98eb9b46-7d81-4cc2-97c2-3bc46047f2ca", + "metadata": {}, + "outputs": [], + "source": [ + "#!npm install react react-dom @xyflow/react @anywidget/react\n", + "\n", + "import sys\n", + "from pathlib import Path\n", + "\n", + "sys.path.insert(0, str(Path(Path.cwd())))" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "25e0a71b-4233-4bdb-b38a-99569c85bf65", + "metadata": {}, + "outputs": [], + "source": [ + "import sys\n", + "from pathlib import Path\n", + "\n", + "sys.path.insert(0, str(Path(Path.cwd())))" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "c90150e0-99e6-40ae-b77f-5c24f486272a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[1G\u001b[0K⠙\u001b[1G\u001b[0K\n", + " \u001b[37mpyironflow/static/\u001b[0m\u001b[1mwidget.js\u001b[0m \u001b[33m1.8mb ⚠️\u001b[0m\n", + " \u001b[37mpyironflow/static/\u001b[0m\u001b[1mwidget.css\u001b[0m \u001b[36m16.6kb\u001b[0m\n", + "\n", + "⚡ \u001b[32mDone in 351ms\u001b[0m\n", + "\u001b[1G\u001b[0K⠙\u001b[1G\u001b[0K" + ] + } + ], + "source": [ + "!npx esbuild js/widget.jsx --minify --format=esm --bundle --outdir=pyironflow/static" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "8cd1cb9d-5c3f-4932-88ee-fb3d97a4afb1", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[1G\u001b[0K⠙\u001b[1G\u001b[0K\n", + " \u001b[37mpyironflow/nodeeditor_static/\u001b[0m\u001b[1mwidget.js\u001b[0m \u001b[33m1.8mb ⚠️\u001b[0m\n", + " \u001b[37mpyironflow/nodeeditor_static/\u001b[0m\u001b[1mwidget.css\u001b[0m \u001b[36m16.6kb\u001b[0m\n", + "\n", + "⚡ \u001b[32mDone in 354ms\u001b[0m\n", + "\u001b[1G\u001b[0K⠙\u001b[1G\u001b[0K" + ] + } + ], + "source": [ + "!npx esbuild js/widget.jsx --minify --format=esm --bundle --outdir=pyironflow/nodeeditor_static" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "ba5b416e-678b-490f-ad19-38ceadb4788c", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/jmittler/miniconda3/envs/pyironflow-clone2/lib/python3.12/site-packages/numpy/_core/getlimits.py:551: UserWarning: Signature b'\\x00\\xd0\\xcc\\xcc\\xcc\\xcc\\xcc\\xcc\\xfb\\xbf\\x00\\x00\\x00\\x00\\x00\\x00' for does not match any known type: falling back to type probe function.\n", + "This warnings indicates broken support for the dtype!\n", + " machar = _get_machar(dtype)\n", + "/home/jmittler/git/pyironFlow/pyironflow/nodeeditor_reactflow.py:49: SyntaxWarning: invalid escape sequence '\\.'\n", + " _CHANNEL_CONNECTION_REGEX = '.*/[^/]+/(.*)\\.type_hint = (.*); /[^/]+/(.*)\\.type_hint = (.*)$'\n", + "/home/jmittler/git/pyironFlow/pyironflow/nodeeditor_reactflow.py:51: SyntaxWarning: invalid escape sequence '\\w'\n", + " \"^The channel /[^/]/([^\\w]+) cannot take the value .* not compliant with the type hint (.*)$\"\n" + ] + } + ], + "source": [ + "from pyiron_workflow import Workflow\n", + "#from typing import Optional\n", + "from pyironflow.pyironflow import PyironFlow\n", + "from pyironflow.nodeeditor import NodeEditor\n", + "from pyironflow.wf_extensions import get_nodes\n", + "from pyironflow.wf_extensions import create_macro\n", + "from pyironflow.wf_extensions import get_input_types_from_hint\n", + "from pyiron_workflow.nodes.while_loop import while_node_factory\n", + "#import pyiron_nodes\n", + "#from pyironflow.macro import get_node_instance_type\n", + "\n", + "#from pyiron_workflow import Workflow\n", + "#from pyiron_workflow.channels import NotData\n", + "#from pyironflow.themes import get_color\n", + "#from pyironflow.macro import get_node_instance_type\n", + "#from pyironflow.wf_extensions import get_import_path, create_macro\n", + "\n", + "import pyiron_workflow\n", + "import anywidget\n", + "import pathlib\n", + "import traitlets\n", + "import os\n", + "import json\n", + "import importlib\n", + "import typing\n", + "import ipywidgets as widgets\n", + "from pyiron_workflow import as_function_node, as_macro_node\n", + "from pyiron_workflow.nodes.for_loop import for_node_factory\n", + "from pyiron_workflow.nodes.for_loop import for_node" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "f4a61a76-b54e-4aab-a8e0-132839a20fab", + "metadata": {}, + "outputs": [], + "source": [ + "@as_function_node()\n", + "def add(a: float | int = 0, b: float | int = 0):\n", + " added = a+b\n", + " return added\n", + "\n", + "\n", + "@as_function_node()\n", + "def divide(x: float | int = 0, y: float | int = 0):\n", + " out = x/y\n", + " return out\n", + "\n", + "@as_function_node()\n", + "def multiply(x: float | int = 0, y: float | int = 0):\n", + " out = x*y\n", + " return out\n", + "\n", + "@as_function_node()\n", + "def half(x: float | int = 0):\n", + " x_halfed = x/2\n", + " return x_halfed\n", + "\n", + "@as_macro_node()\n", + "def heron_step(self, S: float | int = 0, x_n: float | int = 0):\n", + " self.divide = divide(S, x_n) \n", + " self.add = add(x_n, self.divide)\n", + " self.half = half(self.add)\n", + "\n", + " return self.half, x_n\n", + "\n", + "@as_function_node()\n", + "def diff_above(x: float | int = 0, y: float | int = 0, max_diff: float | int = 0):\n", + " \n", + " diff = abs(x-y)\n", + " if diff > max_diff:\n", + " diff_above = True\n", + " else:\n", + " diff_above = False\n", + " \n", + " return diff_above\n", + "\n", + "\n", + "@as_macro_node()\n", + "def short_macro(self, a: float | int = 0):\n", + " self.half_one = half(a) \n", + " self.half_two = half(self.half_one)\n", + "\n", + " return self.half_two\n", + "\n", + "@as_macro_node()\n", + "def shorter_macro(self, b: float | int = 0):\n", + " self.half = half(b) \n", + "\n", + " return self.half\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "bb27c8b2-3df9-42fa-ba05-8db14b3344cb", + "metadata": {}, + "outputs": [], + "source": [ + "heron_while_loop = while_node_factory(\n", + " diff_above,\n", + " heron_step,\n", + " [(\"half\", \"x\"),(\"x_n\", \"y\")],\n", + " [(\"half\", \"x_n\"),],\n", + " True,\n", + " False,\n", + ")\n", + "\n", + "heron_while_loop.__qualname__=heron_while_loop.__name__\n" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "7d3ad90f-9a67-40b4-bde9-49253b0161ae", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'\\n@as_macro_node()\\ndef sub_loop_body(self, n):\\n\\n self.make_list = to_list(n)\\n self.loop = macro_for_loop(self.make_list)\\n self.max = list_max(self.loop.outputs.add2)\\n\\n return self.max\\n'" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "heron_while_loop = while_node_factory(\n", + " diff_above,\n", + " heron_step,\n", + " [(\"half\", \"x\"),(\"x_n\", \"y\")],\n", + " [(\"half\", \"x_n\"),],\n", + " True,\n", + " False,\n", + ")\n", + "\n", + "\n", + "@as_macro_node()\n", + "def heron_calc(self, S, diff):\n", + " self.half = half(S)\n", + " self.heron_step = heron_while_loop()\n", + "\n", + " return self.heron_step\n", + "\n", + "'''\n", + "@as_macro_node()\n", + "def sub_loop_body(self, n):\n", + "\n", + " self.make_list = to_list(n)\n", + " self.loop = macro_for_loop(self.make_list)\n", + " self.max = list_max(self.loop.outputs.add2)\n", + "\n", + " return self.max\n", + "'''" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d314bb7d-8e69-49b4-9ea1-577a6d7f4186", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "a7b386e0-4f54-44e0-a143-05b38bdedb23", + "metadata": {}, + "outputs": [], + "source": [ + "@as_function_node()\n", + "def add_1(a: float | int = 0):\n", + " return a+1\n", + "\n", + "\n", + "@as_function_node()\n", + "def square(n):\n", + " n_squared = n*n\n", + " return n_squared\n", + "\n", + "@as_macro_node()\n", + "def add_and_square_macro(self, n):\n", + "\n", + " self.add_1 = add_1(n)\n", + " self.square = square(self.add_1)\n", + " #self.append = pwf.std.AppendToList(n_list, self.touple)\n", + "\n", + " \n", + " \n", + " return self.square\n", + " \n", + "square_for_loop = for_node_factory(\n", + " add_and_square_macro,\n", + " (\"n\"),\n", + ")\n", + "\n", + "from pyiron_nodes.loop_nodes import fib\n", + "import pyiron_workflow as pwf\n", + "\n", + "@as_macro_node()\n", + "def Fibonacci(self, this, last):\n", + " self.next = pwf.std.Add(this, last)\n", + " self.now = pwf.std.UserInput(this)\n", + " return self.next, self.now\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "093e357b-3f69-4947-8bf6-15c3386ca598", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "e39b419a-b305-4da8-9675-a6ce2dbb8717", + "metadata": {}, + "outputs": [], + "source": [ + "square_for_loop.__qualname__=square_for_loop.__name__" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "44c0a3dc-7b5e-4762-8d1c-5705ef2f543e", + "metadata": {}, + "outputs": [], + "source": [ + "wf = Workflow('heron')\n", + "\n", + "#wf.add = add()\n", + "#wf.divide = divide()\n", + "#wf.multiply = multiply()\n", + "#wf. half = half()\n", + "\n", + "#wf.heron = heron_step()\n", + "#wf.long = short_macro()\n", + "#wf.short = shorter_macro()\n", + "\n", + "#wf.heron2 = heron_step()\n", + "#wf.diff = diff_above()\n", + "#wf.half = half()\n", + "#wf.for_step = heron_calc()\n", + "\n", + "wf.heron_loop = heron_while_loop()\n", + "\n", + "wf.ans_macro = add_and_square_macro()\n", + "wf.fib = Fibonacci()\n", + "\n", + "#wf.for_loop = square_for_loop()" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "c1956649-4da3-4a61-a58c-720440ffc72c", + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "327f8f47bb8b4061a9b0bb1c609e9620", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "HBox(children=(Accordion(children=(VBox(children=(Button(button_style='info', description='Refresh', style=But…" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "pf = NodeEditor([wf])\n", + "pf.gui" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "047db101-e452-4084-a133-b0ff3b72b7fc", + "metadata": {}, + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'loop_other_body_id' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[14], line 10\u001b[0m\n\u001b[1;32m 3\u001b[0m temp_wf\u001b[38;5;241m.\u001b[39mbody \u001b[38;5;241m=\u001b[39m v\u001b[38;5;241m.\u001b[39m_body_node_class()\n\u001b[1;32m 6\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m out_port \u001b[38;5;129;01min\u001b[39;00m v\u001b[38;5;241m.\u001b[39moutputs: \n\u001b[0;32m---> 10\u001b[0m edge_id \u001b[38;5;241m=\u001b[39m \u001b[43mloop_other_body_id\u001b[49m \u001b[38;5;241m+\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m__\u001b[39m\u001b[38;5;124m\"\u001b[39m \u001b[38;5;241m+\u001b[39m out_port\u001b[38;5;241m.\u001b[39mlabel \u001b[38;5;241m+\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m-\u001b[39m\u001b[38;5;124m\"\u001b[39m \u001b[38;5;241m+\u001b[39m v\u001b[38;5;241m.\u001b[39mlabel \u001b[38;5;241m+\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m_output__\u001b[39m\u001b[38;5;124m\"\u001b[39m \u001b[38;5;241m+\u001b[39m out_port\u001b[38;5;241m.\u001b[39mlabel\n\u001b[1;32m 12\u001b[0m edge_dict \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mdict\u001b[39m()\n\u001b[1;32m 13\u001b[0m edge_dict[\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124msource\u001b[39m\u001b[38;5;124m\"\u001b[39m] \u001b[38;5;241m=\u001b[39m loop_other_body_id\n", + "\u001b[0;31mNameError\u001b[0m: name 'loop_other_body_id' is not defined" + ] + } + ], + "source": [ + "v = wf.new_for_loop\n", + "temp_wf = Workflow('temp')\n", + "temp_wf.body = v._body_node_class()\n", + "\n", + "\n", + "for in_port in v.outputs: \n", + " for out_port in temp_wf.body.outputs:\n", + " if in_port.label == out_port.label: \n", + "\n", + " \n", + " edge_id = loop_body_id + \"__\" + out_port.label + \"-\" + v.label + \"_output__\" + in_port.label\n", + " \n", + " edge_dict = dict()\n", + " edge_dict[\"source\"] = loop_body_id\n", + " edge_dict[\"sourceHandle\"] = out_port.label\n", + " edge_dict[\"target\"] = v.label + \"_output\"\n", + " edge_dict[\"targetHandle\"] = in_port.label\n", + " edge_dict[\"id\"] = edge_id\n", + " edge_dict[\"type\"] = \"macroSubEdge\"\n", + " edge_dict[\"layer\"] = 1\n", + " edge_dict[\"parent\"] = v.label\n", + " edge_dict[\"style\"] = {\"stroke\": \"darkgray\", \"strokeWidth\": 2}\n", + " edges.append(edge_dict)" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "a064dbab-9021-457f-bc82-814566352a64", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "square\n" + ] + } + ], + "source": [ + "v = wf.new_for_loop\n", + "temp_wf = Workflow('temp')\n", + "temp_wf.body = v._body_node_class()\n", + "\n", + "for in_port in v.outputs: \n", + " for out_port in temp_wf.body.outputs:\n", + " if in_port.label == out_port.label:\n", + " print(out_port.label)" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "3eec8573-ebdd-43df-a435-b158c9e8318a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "square\n" + ] + } + ], + "source": [ + "for n in temp_wf.body.outputs:\n", + " print(n.label)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c03e3bcd-5c3b-4e76-830c-72ab17e0fa3c", + "metadata": {}, + "outputs": [], + "source": [ + "#wf.heron_loop_new = Do_heron_step_While_diff_above_With_B2T_half2x_x_n2y_B2B_half2x_n()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2b3aaa29-5622-4fee-8118-bc36d522a534", + "metadata": {}, + "outputs": [], + "source": [ + "pf.wf_widgets[0].update_status()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d115714a-bc8c-4498-a840-96520d85f5cb", + "metadata": {}, + "outputs": [], + "source": [ + "rest_list = []\n", + "for items in list(wf.outputs.channel_dict.keys()):\n", + " print(items)\n", + " rest, n = items.rsplit('__', 1)\n", + " rest_list.append(rest)\n", + "print(list(dict.fromkeys(rest_list)))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "232d56af-1558-4bf6-ba58-ea0ddc62728f", + "metadata": {}, + "outputs": [], + "source": [ + "create_macro(wf, \"heron_steps\", \"./pyiron_nodes\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c856de4a-19b9-4ac7-aa8e-5f75f57d3f12", + "metadata": {}, + "outputs": [], + "source": [ + "#def create_macro(wf = dict, name = str, root_path='../pyiron_nodes/pyiron_nodes'):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1b782ebc-c780-4500-883a-3e89f9820987", + "metadata": {}, + "outputs": [], + "source": [ + "for n in wf.long.inputs:\n", + "\n", + " target_node, target_handle = n._value_receiver.full_label.split('/')[-1].split('.', 1)\n", + " edge_id = wf.long.label + \"_inEdge_\" + n.label\n", + "\n", + " print(target_node, target_handle, edge_id)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3286b75e-e3a1-4e52-a95b-b3a06faca52e", + "metadata": {}, + "outputs": [], + "source": [ + " for ic2, (out, inp) in enumerate(temp_wf.body.graph_as_dict[\"edges\"][\"data\"].keys()):\n", + " out_node, out_port = out.split('/')[-1].split('.', 1)\n", + " inp_node, inp_port = inp.split('/')[-1].split('.', 1)\n", + " \n", + " \n", + " edge_dict = dict()\n", + " edge_dict[\"source\"] = loop_body_id + \"_\" + out_node\n", + " edge_dict[\"sourceHandle\"] =out_port\n", + " edge_dict[\"target\"] = loop_body_id + \"_\" + inp_node\n", + " edge_dict[\"targetHandle\"] =inp_port\n", + " edge_dict[\"id\"] = out_node + out_port + \"-\" + inp_node + inp_port \n", + " edge_dict[\"type\"] = \"macroSubEdge\"\n", + " edge_dict[\"layer\"] = 1\n", + " edge_dict[\"parent\"] = loop_body_id\n", + " edge_dict[\"style\"] = {\"stroke\": \"black\", \"strokeWidth\": 2}\n", + "\n", + " edges.append(edge_dict) \n" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "542beac8-7a14-4363-9504-1a9e6ff8e97f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[{'id': 'heron_loop',\n", + " 'data': {'label': 'heron_loop',\n", + " 'source_labels': ['half', 'x_n'],\n", + " 'target_labels': ['test_x',\n", + " 'test_y',\n", + " 'test_max_diff',\n", + " 'body_S',\n", + " 'body_x_n',\n", + " 'max_iterations'],\n", + " 'import_path': 'pyiron_workflow.nodes.while_loop.Do_heron_step_While_diff_above_With_B2T_half2x_x_n2y_B2B_half2x_n',\n", + " 'target_values': [0, 0, 0, 0, 0, None],\n", + " 'target_types': ['int-float',\n", + " 'int-float',\n", + " 'int-float',\n", + " 'int-float',\n", + " 'int-float',\n", + " 'int'],\n", + " 'target_types_raw': [['float', 'int'],\n", + " ['float', 'int'],\n", + " ['float', 'int'],\n", + " ['float', 'int'],\n", + " ['float', 'int'],\n", + " ['int', 'NoneType']],\n", + " 'target_literal_values': [None, None, None, None, None, None],\n", + " 'target_literal_types': [None, None, None, None, None, None],\n", + " 'source_values': ['NotData', 'NotData'],\n", + " 'source_types': ['None', 'None'],\n", + " 'source_types_raw': ['Not Explicitly Defined', 'Not Explicitly Defined'],\n", + " 'failed': 'False',\n", + " 'running': 'False',\n", + " 'ready': 'False',\n", + " 'cache_hit': 'False',\n", + " 'python_object_id': 140355774088016},\n", + " 'position': {'x': 12, 'y': 164},\n", + " 'type': 'loopNodeExpanded',\n", + " 'layer': 0,\n", + " 'style': {'padding': 5,\n", + " 'background': 'rgba(140, 86, 75, 0.7)',\n", + " 'borderRadius': '10px',\n", + " 'width': '1800PX',\n", + " 'width_unitless': 1800,\n", + " 'height': '530px',\n", + " 'height_unitless': 530},\n", + " 'targetPosition': 'left',\n", + " 'sourcePosition': 'right',\n", + " 'measured': {'width': 1800, 'height': 530}},\n", + " {'id': 'heron_loop_diff_above',\n", + " 'data': {'label': 'diff_above',\n", + " 'source_labels': ['diff_above'],\n", + " 'target_labels': ['x', 'y', 'max_diff'],\n", + " 'import_path': '__main__.diff_above',\n", + " 'target_values': [0, 0, 0],\n", + " 'target_types': ['int-float', 'int-float', 'int-float'],\n", + " 'target_types_raw': [['float', 'int'], ['float', 'int'], ['float', 'int']],\n", + " 'target_literal_values': [None, None, None],\n", + " 'target_literal_types': [None, None, None],\n", + " 'source_values': ['NotData'],\n", + " 'source_types': ['None'],\n", + " 'source_types_raw': ['Not Explicitly Defined'],\n", + " 'failed': 'False',\n", + " 'running': 'False',\n", + " 'ready': 'False',\n", + " 'cache_hit': 'False',\n", + " 'python_object_id': 140352779819616},\n", + " 'position': {'x': 1450, 'y': 50},\n", + " 'type': 'customNode',\n", + " 'layer': 1,\n", + " 'style': {'padding': 5,\n", + " 'background': '#a2ea9f',\n", + " 'borderRadius': '10px',\n", + " 'width': '150PX',\n", + " 'width_unitless': 150,\n", + " 'height': '88px',\n", + " 'height_unitless': 88},\n", + " 'targetPosition': 'left',\n", + " 'sourcePosition': 'right',\n", + " 'parentId': 'heron_loop',\n", + " 'extent': 'parent',\n", + " 'draggable': False,\n", + " 'measured': {'width': 150, 'height': 88}},\n", + " {'id': 'heron_loop_heron_step_0',\n", + " 'data': {'label': 'heron_step_0',\n", + " 'source_labels': ['half', 'x_n'],\n", + " 'target_labels': ['S', 'x_n'],\n", + " 'import_path': '__main__.heron_step',\n", + " 'target_values': [0, 0],\n", + " 'target_types': ['int-float', 'int-float'],\n", + " 'target_types_raw': [['float', 'int'], ['float', 'int']],\n", + " 'target_literal_values': [None, None],\n", + " 'target_literal_types': [None, None],\n", + " 'source_values': ['NotData', 'NotData'],\n", + " 'source_types': ['None', 'None'],\n", + " 'source_types_raw': ['Not Explicitly Defined', 'Not Explicitly Defined'],\n", + " 'failed': 'False',\n", + " 'running': 'False',\n", + " 'ready': 'False',\n", + " 'cache_hit': 'False',\n", + " 'python_object_id': 140352779827824,\n", + " 'edges': [{'source': 'heron_loop_input',\n", + " 'sourceHandle': 'test_x',\n", + " 'target': 'heron_loop_diff_above',\n", + " 'targetHandle': 'x',\n", + " 'id': 'heron_loop_inEdge_x',\n", + " 'type': 'macroSubEdge',\n", + " 'layer': 1,\n", + " 'parent': 'heron_loop',\n", + " 'style': {'stroke': 'darkgray', 'strokeWidth': 2}},\n", + " {'source': 'heron_loop_input',\n", + " 'sourceHandle': 'test_y',\n", + " 'target': 'heron_loop_diff_above',\n", + " 'targetHandle': 'y',\n", + " 'id': 'heron_loop_inEdge_y',\n", + " 'type': 'macroSubEdge',\n", + " 'layer': 1,\n", + " 'parent': 'heron_loop',\n", + " 'style': {'stroke': 'darkgray', 'strokeWidth': 2}},\n", + " {'source': 'heron_loop_input',\n", + " 'sourceHandle': 'test_max_diff',\n", + " 'target': 'heron_loop_diff_above',\n", + " 'targetHandle': 'max_diff',\n", + " 'id': 'heron_loop_inEdge_max_diff',\n", + " 'type': 'macroSubEdge',\n", + " 'layer': 1,\n", + " 'parent': 'heron_loop',\n", + " 'style': {'stroke': 'darkgray', 'strokeWidth': 2}},\n", + " {'source': 'heron_loop_input',\n", + " 'sourceHandle': 'body_S',\n", + " 'target': 'heron_loop_heron_step_0',\n", + " 'targetHandle': 'S',\n", + " 'id': 'heron_loop_inEdge_S',\n", + " 'type': 'macroSubEdge',\n", + " 'layer': 1,\n", + " 'parent': 'heron_loop',\n", + " 'style': {'stroke': 'darkgray', 'strokeWidth': 2}},\n", + " {'source': 'heron_loop_input',\n", + " 'sourceHandle': 'body_x_n',\n", + " 'target': 'heron_loop_heron_step_0',\n", + " 'targetHandle': 'x_n',\n", + " 'id': 'heron_loop_inEdge_x_n',\n", + " 'type': 'macroSubEdge',\n", + " 'layer': 1,\n", + " 'parent': 'heron_loop',\n", + " 'style': {'stroke': 'darkgray', 'strokeWidth': 2}},\n", + " {'source': 'heron_loop_heron_step_0',\n", + " 'sourceHandle': 'half',\n", + " 'target': 'heron_loop_diff_above',\n", + " 'targetHandle': 'x',\n", + " 'id': 'heron_loop_heron_step_0__half-heron_loop_diff_above__x',\n", + " 'type': 'macroSubEdge',\n", + " 'layer': 1,\n", + " 'parent': 'heron_loop',\n", + " 'style': {'stroke': 'black', 'strokeWidth': 2}},\n", + " {'source': 'heron_loop_heron_step_0',\n", + " 'sourceHandle': 'x_n',\n", + " 'target': 'heron_loop_diff_above',\n", + " 'targetHandle': 'y',\n", + " 'id': 'heron_loop_heron_step_0__x_n-heron_loop_diff_above__y',\n", + " 'type': 'macroSubEdge',\n", + " 'layer': 1,\n", + " 'parent': 'heron_loop',\n", + " 'style': {'stroke': 'black', 'strokeWidth': 2}},\n", + " {'source': 'heron_loop_heron_step_0',\n", + " 'sourceHandle': 'half',\n", + " 'target': 'heron_loop_heron_step_1',\n", + " 'targetHandle': 'x_n',\n", + " 'id': 'heron_loop_heron_step_0__half-heron_loop_heron_step_1__x_n',\n", + " 'type': 'macroSubEdge',\n", + " 'layer': 1,\n", + " 'parent': 'heron_loop',\n", + " 'style': {'stroke': 'black', 'strokeWidth': 2}},\n", + " {'source': 'heron_loop_heron_step_1',\n", + " 'sourceHandle': 'half',\n", + " 'target': 'heron_loop_output',\n", + " 'targetHandle': 'half',\n", + " 'id': 'heron_loop_heron_step_1__half-heron_loop_output__half',\n", + " 'type': 'macroSubEdge',\n", + " 'layer': 1,\n", + " 'parent': 'heron_loop',\n", + " 'style': {'stroke': 'darkgray', 'strokeWidth': 2}},\n", + " {'source': 'heron_loop_heron_step_1',\n", + " 'sourceHandle': 'x_n',\n", + " 'target': 'heron_loop_output',\n", + " 'targetHandle': 'x_n',\n", + " 'id': 'heron_loop_heron_step_1__x_n-heron_loop_output__x_n',\n", + " 'type': 'macroSubEdge',\n", + " 'layer': 1,\n", + " 'parent': 'heron_loop',\n", + " 'style': {'stroke': 'darkgray', 'strokeWidth': 2}},\n", + " {'source': 'heron_loop_heron_step_0_x_n',\n", + " 'sourceHandle': 'user_input',\n", + " 'target': 'heron_loop_heron_step_0_add',\n", + " 'targetHandle': 'a',\n", + " 'id': 'x_nuser_input-adda',\n", + " 'type': 'macroSubEdge',\n", + " 'layer': 1,\n", + " 'parent': 'heron_loop_heron_step_0',\n", + " 'style': {'stroke': 'black', 'strokeWidth': 2}},\n", + " {'source': 'heron_loop_heron_step_0_x_n',\n", + " 'sourceHandle': 'user_input',\n", + " 'target': 'heron_loop_heron_step_0_divide',\n", + " 'targetHandle': 'y',\n", + " 'id': 'x_nuser_input-dividey',\n", + " 'type': 'macroSubEdge',\n", + " 'layer': 1,\n", + " 'parent': 'heron_loop_heron_step_0',\n", + " 'style': {'stroke': 'black', 'strokeWidth': 2}},\n", + " {'source': 'heron_loop_heron_step_0_divide',\n", + " 'sourceHandle': 'out',\n", + " 'target': 'heron_loop_heron_step_0_add',\n", + " 'targetHandle': 'b',\n", + " 'id': 'divideout-addb',\n", + " 'type': 'macroSubEdge',\n", + " 'layer': 1,\n", + " 'parent': 'heron_loop_heron_step_0',\n", + " 'style': {'stroke': 'black', 'strokeWidth': 2}},\n", + " {'source': 'heron_loop_heron_step_0_add',\n", + " 'sourceHandle': 'added',\n", + " 'target': 'heron_loop_heron_step_0_half',\n", + " 'targetHandle': 'x',\n", + " 'id': 'addadded-halfx',\n", + " 'type': 'macroSubEdge',\n", + " 'layer': 1,\n", + " 'parent': 'heron_loop_heron_step_0',\n", + " 'style': {'stroke': 'black', 'strokeWidth': 2}},\n", + " {'source': 'heron_loop_heron_step_0_input',\n", + " 'sourceHandle': 'S',\n", + " 'target': 'heron_loop_heron_step_0_divide',\n", + " 'targetHandle': 'x',\n", + " 'id': 'heron_loop_heron_step_0inEdge_S',\n", + " 'type': 'macroSubEdge',\n", + " 'layer': 1,\n", + " 'parent': 'heron_loop_heron_step_0',\n", + " 'style': {'stroke': 'darkgray', 'strokeWidth': 2}},\n", + " {'source': 'heron_loop_heron_step_0_input',\n", + " 'sourceHandle': 'x_n',\n", + " 'target': 'heron_loop_heron_step_0_x_n',\n", + " 'targetHandle': 'user_input',\n", + " 'id': 'heron_loop_heron_step_0inEdge_x_n',\n", + " 'type': 'macroSubEdge',\n", + " 'layer': 1,\n", + " 'parent': 'heron_loop_heron_step_0',\n", + " 'style': {'stroke': 'darkgray', 'strokeWidth': 2}},\n", + " {'source': 'heron_loop_heron_step_0_half',\n", + " 'sourceHandle': 'x_halfed',\n", + " 'target': 'heron_loop_heron_step_0_output',\n", + " 'targetHandle': 'half',\n", + " 'id': 'heron_loop_heron_step_0_outEdge_x_halfed',\n", + " 'type': 'macroSubEdge',\n", + " 'layer': 1,\n", + " 'parent': 'heron_loop_heron_step_0',\n", + " 'style': {'stroke': 'darkgray', 'strokeWidth': 2}},\n", + " {'source': 'heron_loop_heron_step_0_x_n',\n", + " 'sourceHandle': 'user_input',\n", + " 'target': 'heron_loop_heron_step_0_output',\n", + " 'targetHandle': 'x_n',\n", + " 'id': 'heron_loop_heron_step_0_outEdge_user_input',\n", + " 'type': 'macroSubEdge',\n", + " 'layer': 1,\n", + " 'parent': 'heron_loop_heron_step_0',\n", + " 'style': {'stroke': 'darkgray', 'strokeWidth': 2}}]},\n", + " 'position': {'x': 150, 'y': 150},\n", + " 'type': 'dummyMacroExpanded',\n", + " 'layer': 1,\n", + " 'style': {'padding': 5,\n", + " 'background': 'rgba(234, 207, 159, 0.7)',\n", + " 'borderRadius': '10px',\n", + " 'width': '1190PX',\n", + " 'width_unitless': 1190,\n", + " 'height': '280px',\n", + " 'height_unitless': 280},\n", + " 'targetPosition': 'left',\n", + " 'sourcePosition': 'right',\n", + " 'parentId': 'heron_loop',\n", + " 'extent': 'parent',\n", + " 'draggable': False,\n", + " 'measured': {'width': 1190, 'height': 280}},\n", + " {'id': 'heron_loop_heron_step_0_add',\n", + " 'data': {'label': 'add',\n", + " 'source_labels': ['added'],\n", + " 'target_labels': ['a', 'b'],\n", + " 'import_path': '__main__.add',\n", + " 'target_values': [0, 0],\n", + " 'target_types': ['int-float', 'int-float'],\n", + " 'target_types_raw': [['float', 'int'], ['float', 'int']],\n", + " 'target_literal_values': [None, None],\n", + " 'target_literal_types': [None, None],\n", + " 'source_values': ['NotData'],\n", + " 'source_types': ['None'],\n", + " 'source_types_raw': ['Not Explicitly Defined'],\n", + " 'failed': 'False',\n", + " 'running': 'False',\n", + " 'ready': 'False',\n", + " 'cache_hit': 'False',\n", + " 'python_object_id': 140352780484048},\n", + " 'position': {'x': 622, 'y': 117.5},\n", + " 'type': 'subNode',\n", + " 'layer': 1,\n", + " 'style': {'padding': 5,\n", + " 'background': '#a2ea9f',\n", + " 'borderRadius': '10px',\n", + " 'width': '150px',\n", + " 'width_unitless': 150,\n", + " 'height': '72px',\n", + " 'height_unitless': 72},\n", + " 'targetPosition': 'left',\n", + " 'sourcePosition': 'right',\n", + " 'parentId': 'heron_loop_heron_step_0',\n", + " 'extent': 'parent',\n", + " 'measured': {'width': 150, 'height': 72}},\n", + " {'id': 'heron_loop_heron_step_0_divide',\n", + " 'data': {'label': 'divide',\n", + " 'source_labels': ['out'],\n", + " 'target_labels': ['x', 'y'],\n", + " 'import_path': '__main__.divide',\n", + " 'target_values': [0, 0],\n", + " 'target_types': ['int-float', 'int-float'],\n", + " 'target_types_raw': [['float', 'int'], ['float', 'int']],\n", + " 'target_literal_values': [None, None],\n", + " 'target_literal_types': [None, None],\n", + " 'source_values': ['NotData'],\n", + " 'source_types': ['None'],\n", + " 'source_types_raw': ['Not Explicitly Defined'],\n", + " 'failed': 'False',\n", + " 'running': 'False',\n", + " 'ready': 'False',\n", + " 'cache_hit': 'False',\n", + " 'python_object_id': 140352779813520},\n", + " 'position': {'x': 392, 'y': 123},\n", + " 'type': 'subNode',\n", + " 'layer': 1,\n", + " 'style': {'padding': 5,\n", + " 'background': '#a2ea9f',\n", + " 'borderRadius': '10px',\n", + " 'width': '150px',\n", + " 'width_unitless': 150,\n", + " 'height': '72px',\n", + " 'height_unitless': 72},\n", + " 'targetPosition': 'left',\n", + " 'sourcePosition': 'right',\n", + " 'parentId': 'heron_loop_heron_step_0',\n", + " 'extent': 'parent',\n", + " 'measured': {'width': 150, 'height': 72}},\n", + " {'id': 'heron_loop_heron_step_0_half',\n", + " 'data': {'label': 'half',\n", + " 'source_labels': ['x_halfed'],\n", + " 'target_labels': ['x'],\n", + " 'import_path': '__main__.half',\n", + " 'target_values': [0],\n", + " 'target_types': ['int-float'],\n", + " 'target_types_raw': [['float', 'int']],\n", + " 'target_literal_values': [None],\n", + " 'target_literal_types': [None],\n", + " 'source_values': ['NotData'],\n", + " 'source_types': ['None'],\n", + " 'source_types_raw': ['Not Explicitly Defined'],\n", + " 'failed': 'False',\n", + " 'running': 'False',\n", + " 'ready': 'False',\n", + " 'cache_hit': 'False',\n", + " 'python_object_id': 140352780475408},\n", + " 'position': {'x': 792, 'y': 125.5},\n", + " 'type': 'subNode',\n", + " 'layer': 1,\n", + " 'style': {'padding': 5,\n", + " 'background': '#a2ea9f',\n", + " 'borderRadius': '10px',\n", + " 'width': '150px',\n", + " 'width_unitless': 150,\n", + " 'height': '56px',\n", + " 'height_unitless': 56},\n", + " 'targetPosition': 'left',\n", + " 'sourcePosition': 'right',\n", + " 'parentId': 'heron_loop_heron_step_0',\n", + " 'extent': 'parent',\n", + " 'measured': {'width': 150, 'height': 56}},\n", + " {'id': 'heron_loop_heron_step_0_input',\n", + " 'data': {'label': 'heron_step_0_input',\n", + " 'source_labels': ['S', 'x_n'],\n", + " 'target_labels': [],\n", + " 'import_path': '',\n", + " 'target_values': [],\n", + " 'target_types': [],\n", + " 'target_types_raw': [],\n", + " 'target_literal_values': [],\n", + " 'target_literal_types': [],\n", + " 'source_values': [],\n", + " 'source_types': [],\n", + " 'source_types_raw': [],\n", + " 'failed': 'False',\n", + " 'running': 'False',\n", + " 'ready': 'False',\n", + " 'cache_hit': 'False',\n", + " 'python_object_id': 140352779827824},\n", + " 'position': {'x': 8, 'y': 22},\n", + " 'type': 'inNode',\n", + " 'layer': 1,\n", + " 'style': {'padding': 0,\n", + " 'background': 'rgba(171, 190, 209, 1)',\n", + " 'borderRadius': '2px',\n", + " 'width': '100px',\n", + " 'width_unitless': 100,\n", + " 'height': '40px',\n", + " 'height_unitless': 40},\n", + " 'targetPosition': 'left',\n", + " 'sourcePosition': 'right',\n", + " 'parentId': 'heron_loop_heron_step_0',\n", + " 'extent': 'parent',\n", + " 'measured': {'width': 100, 'height': 40}},\n", + " {'id': 'heron_loop_heron_step_0_output',\n", + " 'data': {'label': 'heron_step_0_output',\n", + " 'source_labels': [],\n", + " 'target_labels': ['half', 'x_n'],\n", + " 'import_path': '',\n", + " 'target_values': [],\n", + " 'target_types': [],\n", + " 'target_types_raw': [],\n", + " 'target_literal_values': [],\n", + " 'target_literal_types': [],\n", + " 'source_values': [],\n", + " 'source_types': [],\n", + " 'source_types_raw': [],\n", + " 'failed': 'False',\n", + " 'running': 'False',\n", + " 'ready': 'False',\n", + " 'cache_hit': 'False',\n", + " 'python_object_id': 140352779827824},\n", + " 'position': {'x': 1082, 'y': 22},\n", + " 'type': 'inNode',\n", + " 'layer': 1,\n", + " 'style': {'padding': 0,\n", + " 'background': 'rgba(171, 190, 209, 1)',\n", + " 'borderRadius': '2px',\n", + " 'width': '100px',\n", + " 'width_unitless': 100,\n", + " 'height': '40px',\n", + " 'height_unitless': 40},\n", + " 'targetPosition': 'left',\n", + " 'sourcePosition': 'right',\n", + " 'draggable': False,\n", + " 'parentId': 'heron_loop_heron_step_0',\n", + " 'extent': 'parent',\n", + " 'measured': {'width': 100, 'height': 40}},\n", + " {'id': 'heron_loop_heron_step_0_x_n',\n", + " 'data': {'label': 'x_n',\n", + " 'source_labels': ['user_input'],\n", + " 'target_labels': ['user_input'],\n", + " 'import_path': 'pyiron_workflow.nodes.standard.UserInput',\n", + " 'target_values': [0],\n", + " 'target_types': ['int-float'],\n", + " 'target_types_raw': [['float', 'int']],\n", + " 'target_literal_values': [None],\n", + " 'target_literal_types': [None],\n", + " 'source_values': ['NotData'],\n", + " 'source_types': ['None'],\n", + " 'source_types_raw': ['Not Explicitly Defined'],\n", + " 'failed': 'False',\n", + " 'running': 'False',\n", + " 'ready': 'False',\n", + " 'cache_hit': 'False',\n", + " 'python_object_id': 140352779824608},\n", + " 'position': {'x': 162, 'y': 125.5},\n", + " 'type': 'subNode',\n", + " 'layer': 1,\n", + " 'style': {'padding': 5,\n", + " 'background': '#a2ea9f',\n", + " 'borderRadius': '10px',\n", + " 'width': '150px',\n", + " 'width_unitless': 150,\n", + " 'height': '56px',\n", + " 'height_unitless': 56},\n", + " 'targetPosition': 'left',\n", + " 'sourcePosition': 'right',\n", + " 'parentId': 'heron_loop_heron_step_0',\n", + " 'extent': 'parent',\n", + " 'measured': {'width': 150, 'height': 56},\n", + " 'selected': False},\n", + " {'id': 'heron_loop_heron_step_1',\n", + " 'data': {'label': 'heron_step_1',\n", + " 'source_labels': ['half', 'x_n'],\n", + " 'target_labels': ['S', 'x_n'],\n", + " 'import_path': '__main__.heron_step',\n", + " 'target_values': [0, 0],\n", + " 'target_types': ['int-float', 'int-float'],\n", + " 'target_types_raw': [['float', 'int'], ['float', 'int']],\n", + " 'target_literal_values': [None, None],\n", + " 'target_literal_types': [None, None],\n", + " 'source_values': ['NotData', 'NotData'],\n", + " 'source_types': ['None', 'None'],\n", + " 'source_types_raw': ['Not Explicitly Defined', 'Not Explicitly Defined'],\n", + " 'failed': 'False',\n", + " 'running': 'False',\n", + " 'ready': 'False',\n", + " 'cache_hit': 'False',\n", + " 'python_object_id': 140352780468496},\n", + " 'position': {'x': 1450, 'y': 150},\n", + " 'type': 'macroNode',\n", + " 'layer': 1,\n", + " 'style': {'padding': 5,\n", + " 'background': 'rgba(234, 207, 159, 1)',\n", + " 'borderRadius': '10px',\n", + " 'width': '150PX',\n", + " 'width_unitless': 150,\n", + " 'height': '72px',\n", + " 'height_unitless': 72},\n", + " 'targetPosition': 'left',\n", + " 'sourcePosition': 'right',\n", + " 'parentId': 'heron_loop',\n", + " 'extent': 'parent',\n", + " 'draggable': False,\n", + " 'measured': {'width': 150, 'height': 72}},\n", + " {'id': 'heron_loop_input',\n", + " 'data': {'label': 'heron_loop_input',\n", + " 'source_labels': ['test_x',\n", + " 'test_y',\n", + " 'test_max_diff',\n", + " 'body_S',\n", + " 'body_x_n',\n", + " 'max_iterations'],\n", + " 'target_labels': [],\n", + " 'import_path': '',\n", + " 'target_values': [],\n", + " 'target_types': [],\n", + " 'target_types_raw': [],\n", + " 'target_literal_values': [],\n", + " 'target_literal_types': [],\n", + " 'source_values': [],\n", + " 'source_types': [],\n", + " 'source_types_raw': [],\n", + " 'failed': 'False',\n", + " 'running': 'False',\n", + " 'ready': 'False',\n", + " 'cache_hit': 'False',\n", + " 'python_object_id': 140355774088016},\n", + " 'position': {'x': 8, 'y': 22},\n", + " 'type': 'inNode',\n", + " 'layer': 1,\n", + " 'style': {'padding': 0,\n", + " 'background': 'rgba(171, 190, 209, 1)',\n", + " 'borderRadius': '2px',\n", + " 'width': '100px',\n", + " 'width_unitless': 100,\n", + " 'height': '104px',\n", + " 'height_unitless': 104},\n", + " 'targetPosition': 'left',\n", + " 'sourcePosition': 'right',\n", + " 'parentId': 'heron_loop',\n", + " 'extent': 'parent',\n", + " 'measured': {'width': 100, 'height': 104}},\n", + " {'id': 'heron_loop_output',\n", + " 'data': {'label': 'heron_loop_output',\n", + " 'source_labels': [],\n", + " 'target_labels': ['half', 'x_n'],\n", + " 'import_path': '',\n", + " 'target_values': [],\n", + " 'target_types': [],\n", + " 'target_types_raw': [],\n", + " 'target_literal_values': [],\n", + " 'target_literal_types': [],\n", + " 'source_values': [],\n", + " 'source_types': [],\n", + " 'source_types_raw': [],\n", + " 'failed': 'False',\n", + " 'running': 'False',\n", + " 'ready': 'False',\n", + " 'cache_hit': 'False',\n", + " 'python_object_id': 140355774088016},\n", + " 'position': {'x': 1692, 'y': 22},\n", + " 'type': 'inNode',\n", + " 'layer': 1,\n", + " 'style': {'padding': 0,\n", + " 'background': 'rgba(171, 190, 209, 1)',\n", + " 'borderRadius': '2px',\n", + " 'width': '100px',\n", + " 'width_unitless': 100,\n", + " 'height': '40px',\n", + " 'height_unitless': 40},\n", + " 'targetPosition': 'left',\n", + " 'sourcePosition': 'right',\n", + " 'draggable': False,\n", + " 'parentId': 'heron_loop',\n", + " 'extent': 'parent',\n", + " 'measured': {'width': 100, 'height': 40}},\n", + " {'id': 'int_input',\n", + " 'data': {'label': 'int_input',\n", + " 'source_labels': ['a'],\n", + " 'target_labels': ['a'],\n", + " 'import_path': 'pyiron_nodes.loop_demo.input.int_input',\n", + " 'target_values': [0],\n", + " 'target_types': ['int'],\n", + " 'target_types_raw': ['int'],\n", + " 'target_literal_values': [[]],\n", + " 'target_literal_types': [[]],\n", + " 'source_values': ['NotData'],\n", + " 'source_types': ['None'],\n", + " 'source_types_raw': ['Not Explicitly Defined'],\n", + " 'failed': 'False',\n", + " 'running': 'False',\n", + " 'ready': 'False',\n", + " 'cache_hit': 'False',\n", + " 'python_object_id': 140352781543824},\n", + " 'position': {'x': 12, 'y': 12},\n", + " 'type': 'customNode',\n", + " 'layer': 0,\n", + " 'style': {'padding': 5,\n", + " 'background': '#a2ea9f',\n", + " 'borderRadius': '10px',\n", + " 'width': '240PX',\n", + " 'width_unitless': 240,\n", + " 'height': '56px',\n", + " 'height_unitless': 56},\n", + " 'targetPosition': 'left',\n", + " 'sourcePosition': 'right',\n", + " 'measured': {'width': 240, 'height': 56}},\n", + " {'id': 'int_input0',\n", + " 'data': {'label': 'int_input0',\n", + " 'source_labels': ['a'],\n", + " 'target_labels': ['a'],\n", + " 'import_path': 'pyiron_nodes.loop_demo.input.int_input',\n", + " 'target_values': [0],\n", + " 'target_types': ['int'],\n", + " 'target_types_raw': ['int'],\n", + " 'target_literal_values': [[]],\n", + " 'target_literal_types': [[]],\n", + " 'source_values': ['NotData'],\n", + " 'source_types': ['None'],\n", + " 'source_types_raw': ['Not Explicitly Defined'],\n", + " 'failed': 'False',\n", + " 'running': 'False',\n", + " 'ready': 'False',\n", + " 'cache_hit': 'False',\n", + " 'python_object_id': 140352781305760},\n", + " 'position': {'x': 12, 'y': 88},\n", + " 'type': 'customNode',\n", + " 'layer': 0,\n", + " 'style': {'padding': 5,\n", + " 'background': '#a2ea9f',\n", + " 'borderRadius': '10px',\n", + " 'width': '240PX',\n", + " 'width_unitless': 240,\n", + " 'height': '56px',\n", + " 'height_unitless': 56},\n", + " 'targetPosition': 'left',\n", + " 'sourcePosition': 'right',\n", + " 'measured': {'width': 240, 'height': 56}}]" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "json.loads(pf.wf_widgets[0].gui.nodes)" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "77d10da9-09dc-45f1-aa25-85ff085471af", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[{'source': 'new_for_loop_start',\n", + " 'target': 'new_for_loop__in_range',\n", + " 'id': 'new_for_loop_start-new_for_loop__in_range',\n", + " 'type': 'macroSubEdge',\n", + " 'layer': 1,\n", + " 'parent': 'new_for_loop',\n", + " 'style': {'stroke': 'blue', 'strokeWidth': 2},\n", + " 'markerEnd': {'type': 'arrowclosed', 'color': 'blue'}},\n", + " {'source': 'new_for_loop__in_range',\n", + " 'sourceHandle': 'true',\n", + " 'target': 'new_for_loop_add_and_square_macro_cf',\n", + " 'id': 'new_for_loop__in_range_true-new_for_loop_add_and_square_macro_cf',\n", + " 'type': 'macroSubEdge',\n", + " 'layer': 1,\n", + " 'parent': 'new_for_loop',\n", + " 'style': {'stroke': 'blue', 'strokeWidth': 2},\n", + " 'markerEnd': {'type': 'arrowclosed', 'color': 'blue'}},\n", + " {'source': 'new_for_loop__in_range',\n", + " 'sourceHandle': 'false',\n", + " 'target': 'new_for_loop_end',\n", + " 'id': 'new_for_loop__in_range-new_for_loop_end',\n", + " 'type': 'macroSubEdge',\n", + " 'layer': 1,\n", + " 'parent': 'new_for_loop',\n", + " 'style': {'stroke': 'blue', 'strokeWidth': 2},\n", + " 'markerEnd': {'type': 'arrowclosed', 'color': 'blue'}},\n", + " {'source': 'new_for_loop_add_and_square_macro_cf',\n", + " 'target': 'new_for_loop__increment',\n", + " 'id': 'new_for_loop_add_and_square_macro_cf-new_for_loop__increment',\n", + " 'type': 'macroSubEdge',\n", + " 'layer': 1,\n", + " 'parent': 'new_for_loop',\n", + " 'style': {'stroke': 'blue', 'strokeWidth': 2},\n", + " 'markerEnd': {'type': 'arrowclosed', 'color': 'blue'}},\n", + " {'source': 'new_for_loop__increment',\n", + " 'target': 'new_for_loop__in_range',\n", + " 'id': 'new_for_loop__increment-new_for_loop__in_range',\n", + " 'type': 'loopEdge',\n", + " 'layer': 1,\n", + " 'parent': 'new_for_loop',\n", + " 'style': {'stroke': 'blue', 'strokeWidth': 2},\n", + " 'markerEnd': {'type': 'arrowclosed', 'color': 'blue'}}]" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "json.loads(pf.wf_widgets[0].gui.edges)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cf81c0f9-6c41-4d56-930f-40ce888714a6", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "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.12.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/Untitled3.ipynb b/Untitled3.ipynb new file mode 100644 index 00000000..2574314d --- /dev/null +++ b/Untitled3.ipynb @@ -0,0 +1,360 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "ca1c7aff-d64c-4710-9708-2748527c4ad9", + "metadata": {}, + "outputs": [], + "source": [ + "import sys\n", + "from pathlib import Path\n", + "\n", + "sys.path.insert(0, str(Path(Path.cwd())))" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "743059eb-00e7-4896-83e0-f98de3f54332", + "metadata": {}, + "outputs": [], + "source": [ + "from pyiron_workflow import Workflow\n", + "from pyironflow.pyironflow import PyironFlow\n", + "from pyironflow.nodeeditor import NodeEditor\n", + "from pyiron_workflow.nodes.while_loop import while_node\n", + "from pyiron_workflow.nodes.for_loop import for_node\n", + "import pyiron_workflow as pwf\n", + "import pathlib\n", + "from pyiron_workflow import as_function_node, as_macro_node\n" + ] + }, + { + "cell_type": "markdown", + "id": "6ab784f0-955b-4f5f-b858-4cbc32988906", + "metadata": {}, + "source": [ + "### while-loop in the command line\n", + "\n", + "To get started, we first have a look at the command line implementation of while-loops. Below we can see the basic nodes, that our loop will consist of. We will recreate Herons's Algorithm for the calculation of square roots. This algorithm repeats three simple steps. We have our number S, which we want to find the square root of, as well as a value x_n, which at the start is any number between S and 0. The formula for one step of this algorithm is x_n+1 = ((S/x_n) + x_n)/2. Below you can see the corresponding function nodes for our workflow, as well as the macro \"heron_step\" which represents one step of the algorithm." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "4fb8d20c-3283-445d-b6c2-d556547a5e43", + "metadata": {}, + "outputs": [], + "source": [ + "@as_function_node()\n", + "def add(a: float | int = 0, b: float | int = 0):\n", + " added = a+b\n", + " return added\n", + "\n", + "\n", + "@as_function_node()\n", + "def divide(x: float | int = 0, y: float | int = 0):\n", + " out = x/y\n", + " return out\n", + "\n", + "@as_function_node()\n", + "def half(x: float | int = 0):\n", + " x_halfed = x/2\n", + " return x_halfed\n", + "\n", + "@as_macro_node()\n", + "def heron_step(self, S: float | int = 0, x_n: float | int = 0):\n", + " self.divide = divide(S, x_n) \n", + " self.add = add(x_n, self.divide)\n", + " self.half = half(self.add)\n", + "\n", + " return self.half, x_n" + ] + }, + { + "cell_type": "markdown", + "id": "07f83bbc-19eb-4510-ad76-467b74bce2de", + "metadata": {}, + "source": [ + "With each iteration, our x_n gets closer to the real square root of S. A fitting ending point would be, if our results from one step to the next only differ by a small amount. For this, we have a test node, wich compares two values and returns true, as long as the difference between the two exceeds a given value." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2144a9a5-0383-4b1e-972e-66a2db856e68", + "metadata": {}, + "outputs": [], + "source": [ + "@as_function_node()\n", + "def diff_above(x: float | int = 0, y: float | int = 0, max_diff: float | int = 0):\n", + " \n", + " diff = abs(x-y)\n", + " if diff > max_diff:\n", + " diff_above = True\n", + " else:\n", + " diff_above = False\n", + " \n", + " return diff_above" + ] + }, + { + "cell_type": "markdown", + "id": "6c252eb1-77a7-4135-be69-8c6c4656e6a9", + "metadata": {}, + "source": [ + "With these things combined, we can create our heron loop. \n", + "test_node_class defines, which kind of test determines, if the loop continues.\n", + "body_node_class defines the macro, which gets executed for each step.\n", + "body_to_test connections describes what outputs of the body node get connected to the test node. Since we want to check the difference between x_n and x_n+1 we need to connect both outputs of our macro to the test nodes x and y inputs to get them tested each step.\n", + "body_to_body connections describe how the data gets passed between calculation steps. Since we want to keep our original starting number S but want to pass on our newly calculated \"half\", it gets connected to input \"x_n\".\n", + "At last we define starting values for our loop to run. After that it can be executed." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "4bd1ab0e-0e7f-4112-b45b-49dffe51a124", + "metadata": {}, + "outputs": [], + "source": [ + "heron_loop = pwf.while_node(\n", + " test_node_class = diff_above,\n", + " body_node_class = heron_step,\n", + " body_to_test_connections = ((\"half\", \"x\"), (\"x_n\", \"y\"),),\n", + " body_to_body_connections = (\"half\",\"x_n\"),\n", + " body_S= 100,\n", + " body_x_n= 95,\n", + " test_x= 1,\n", + " test_y= 2,\n", + " test_max_diff= 0.01,\n", + " strict_condition_hint = False)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "296ce64c-3858-41d7-b86c-3b291d15c6ed", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'half': 10.000000000035843, 'x_n': 10.00002677482755}" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "heron_loop.run()" + ] + }, + { + "cell_type": "markdown", + "id": "ef8149cc-9931-4055-81cb-f20b3f523cdd", + "metadata": {}, + "source": [ + "### while-loop in pyironFlow\n", + "\n", + "The three necessary nodes are already added to the workflow. Should we wish to add more, they can be found in the loop_demo folder in the node library on the left hand side. Simply navigate through the tree and click the nodes you would like to add.\n", + "To create a while node from the three selected nodes, we must first establish the connection between them. We pick heron1 for step one of the loop and heron2 for step two. After each step, we want to check for our condition. In this case, this means comparing the value of x_n to the result of the macro computation half. for this, we connect both outputs (half, x_n) to our test node (x,y). Should the condition hold, we want to use our result \"half\" as the new x_n in the next loop step. To achieve this, we connect output \"half\" of \"heron1\" to input \"x_n\" of \"heron2\". Now we have made all the needed connections and we can create the loop.\n", + "For this, navigate to the dropdown menu on the left and expand the field \"Loop\". Now we select \"while\" as out loop type and enter the name \"heron1\" into the field \"body\" and \"heron2\" into the field \"body2\". Then we enter \"diff\" as the name of our test. We can then simply click the button below and our new loop node should appear. We can then remove the old nodes, as they are no longer needed. You can select the loop node and click \"expand\" to get a more detailed view of the node. Here we can switch between the \"blueprint\" view, which shows the dataflow connections inside of the loop, or select \"control flow\" which shows us the simplified control flow representation of our loop. If we want to run our loop, all we need to do is connect a few input nodes (also in the node library) and then select \"pull\"" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "c689c408-7c2a-4322-ae28-f3f41197c23e", + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "b2162851aeb24643ade81371fc9b7766", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "HBox(children=(Accordion(children=(VBox(children=(Button(button_style='info', description='Refresh', style=But…" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "wf_h = Workflow('heron')\n", + "\n", + "wf_h.heron1 = heron_step()\n", + "wf_h.heron2 = heron_step()\n", + "wf_h.diff = diff_above()\n", + "\n", + "\n", + "pf = NodeEditor([wf_h])\n", + "pf.gui" + ] + }, + { + "cell_type": "markdown", + "id": "e41e97fc-aed9-472f-afa2-e1b1cb7919d9", + "metadata": {}, + "source": [ + "### for-loop in the command line\n", + "We start again with the neccessary nodes. For this example, we have a simple algorithm. It takes a set of given numbers, adds one and then squares them. For the for loop we dont need a seperate test node. The loop ends once it has iterated over all the given values." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "580327c4-e84c-408c-a1bb-e739764b1240", + "metadata": {}, + "outputs": [], + "source": [ + "@as_function_node()\n", + "def add_1(a: float | int = 0):\n", + " return a+1\n", + "\n", + "\n", + "@as_function_node()\n", + "def square(n):\n", + " n_squared = n*n\n", + " return n_squared\n", + "\n", + "@as_macro_node()\n", + "def add_and_square_macro(self, n):\n", + "\n", + " self.add_1 = add_1(n)\n", + " self.square = square(self.add_1)\n", + " return self.square" + ] + }, + { + "cell_type": "markdown", + "id": "15251efc-f39f-4ccc-9dc9-47cd93900fe5", + "metadata": {}, + "source": [ + "body_node_class again declares, which macro shall be executed each step of the loop.\n", + "iter_on defines, which input the node iterates over\n", + "Since we dont want a dataframe as output, it gets set to false. At the end, we give a set of numbers for the input n to iterate over. After that, the loop can be executed." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "50ec039c-53b2-42e2-a96e-dd0158488933", + "metadata": {}, + "outputs": [], + "source": [ + "for_add_and_square = pwf.for_node(\n", + " body_node_class = add_and_square_macro,\n", + " iter_on = \"n\",\n", + " output_as_dataframe = False,\n", + " n= [0,1,2,3,4],\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "88c25c41-dc8d-4aca-a69c-46256b1fe1d7", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'n': [0, 1, 2, 3, 4], 'square': [1, 4, 9, 16, 25]}" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "for_add_and_square.run()" + ] + }, + { + "cell_type": "markdown", + "id": "9e47dc04-e838-42de-bd2b-b53f377213ee", + "metadata": {}, + "source": [ + "### for-loop in pyironFlow\n", + "The necessary node is already added to the Workflow. All we need to to is got to the \"Loop\" dropdown again. This time, we select \"for\" as loop type. We declare \"macro\" as the node we want to loop. Then we just entwer which inputs we would like to iterate over, seperated by spaces. Since we only have the one, we enter n (without the \"\"). Then we can click the create button and the new loop node gets added. We have same option of expanding the loop and viewing the \"blueprint\", which shows the data flow connections, and \"control flow\" which shows the control flow representation. All we need to do now is connect an input node. There is a fitting one called \"list_0_to_4\" in the node library-" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "3decf246-d8cc-4053-8064-cbdf395a1deb", + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "42327466af4a43c28693317cc9f6c7f7", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "HBox(children=(Accordion(children=(VBox(children=(Button(button_style='info', description='Refresh', style=But…" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "wf_ans = Workflow('add_and_square')\n", + "\n", + "wf_ans.macro = add_and_square_macro()\n", + "\n", + "pf2 = NodeEditor([wf_ans])\n", + "pf2.gui" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ad6f83a2-5578-41dc-89a1-794327056a9c", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0789d02a-5824-40f3-a540-bce429535c21", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "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.12.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/demo.ipynb b/demo.ipynb deleted file mode 100644 index 71fb228a..00000000 --- a/demo.ipynb +++ /dev/null @@ -1,1128 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "8e8fc580-81a6-436d-a00c-cdd3f78b8c00", - "metadata": {}, - "source": [ - "# PyironFlow: a visual programming environment for pyiron_workflows" - ] - }, - { - "cell_type": "markdown", - "id": "002c5334-fc90-4032-8f1f-7ad698f8e3c9", - "metadata": {}, - "source": [ - "Author: Jörg Neugebauer\n", - "\n", - "Date: Aug. 16, 2024" - ] - }, - { - "cell_type": "markdown", - "id": "34b15840-ecf6-4d42-9269-ed235181a08a", - "metadata": {}, - "source": [ - "Key concepts:\n", - "- Strict separartion between logical/programmatic part (handled by pyiron_workflows) and gui (handled by a combination of reactflow, a react javascript tool, and ipywidgets to connect to the jupyter ecosystem)\n", - "- The communication between python and .jsx is handled via traitlets\n", - "- Graphical and programmatic representation of a workflow, described by its nodes and edges, is fully echangable. The two representations can be seemlessly interchanged, i.e. you can start writing the workflow as code and then transfer it to the gui to extend or run it " - ] - }, - { - "cell_type": "markdown", - "id": "a8427ed6-22f8-4e03-aeb9-9c57b6eb6724", - "metadata": {}, - "source": [ - "### Setup configuration" - ] - }, - { - "cell_type": "markdown", - "id": "83efff2c-20a2-487f-9fe8-ca3cb158f725", - "metadata": {}, - "source": [ - "#### Include the pyiron_node library into PYTHONPATH (modules need to be importable)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3be83a1e-f2b0-4d54-91b2-34b39a903c65", - "metadata": {}, - "outputs": [], - "source": [ - "%config IPCompleter.evaluation='unsafe'\n", - "\n", - "import sys\n", - "from pathlib import Path\n", - "sys.path.insert(0, str(Path(Path.cwd()).parent) + '/pyiron_nodes')" - ] - }, - { - "cell_type": "markdown", - "id": "a7248af3-afa1-4163-90fe-482f868522c6", - "metadata": {}, - "source": [ - "#### When creating a new conda environment or react widget install the following tools " - ] - }, - { - "cell_type": "markdown", - "id": "9a37829a-086a-458b-8d7d-5723900ba5ff", - "metadata": {}, - "source": [ - "Note: You have to install via conda-forge: \n", - "- nodejs\n", - "- esbuild\n", - "- anywidget\n", - "\n", - "Uncomment the line below to install the following modules (.jsx side)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "e0f2617d-b647-45eb-9688-6ad3da6401f7", - "metadata": { - "scrolled": true - }, - "outputs": [], - "source": [ - "# !npm install react react-dom @xyflow/react @anywidget/react" - ] - }, - { - "cell_type": "code", - "execution_count": 44, - "id": "4c9f6660-678a-4f08-9239-56d1cbfe904d", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001b[1G\u001b[0K⠙\u001b[1G\u001b[0K⠹\u001b[1G\u001b[0K⠸\u001b[1G\u001b[0K⠼\u001b[1G\u001b[0K⠴\u001b[1G\u001b[0K⠦\u001b[1G\u001b[0K⠧\u001b[1G\u001b[0K⠇\u001b[1G\u001b[0K⠏\u001b[1G\u001b[0K⠋\u001b[1G\u001b[0K\n", - "added 1 package, and audited 98 packages in 1s\n", - "\u001b[1G\u001b[0K⠋\u001b[1G\u001b[0K\n", - "\u001b[1G\u001b[0K⠋\u001b[1G\u001b[0K6 packages are looking for funding\n", - "\u001b[1G\u001b[0K⠋\u001b[1G\u001b[0K run `npm fund` for details\n", - "\u001b[1G\u001b[0K⠋\u001b[1G\u001b[0K\n", - "found \u001b[32m\u001b[1m0\u001b[22m\u001b[39m vulnerabilities\n", - "\u001b[1G\u001b[0K⠋\u001b[1G\u001b[0K" - ] - } - ], - "source": [ - "!npm install elkjs" - ] - }, - { - "cell_type": "markdown", - "id": "1bc17fad-9a0f-4a30-bf7b-f89c2d6e9c21", - "metadata": {}, - "source": [ - "#### Run the following line whenever you modify a file in the js/ (javascript) directory " - ] - }, - { - "cell_type": "code", - "execution_count": 45, - "id": "7a1876e6-3d07-46da-a618-381a65e177b8", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001b[1G\u001b[0K⠙\u001b[1G\u001b[0K\n", - " \u001b[37mstatic/\u001b[0m\u001b[1mwidget.js\u001b[0m \u001b[33m1.7mb ⚠️\u001b[0m\n", - " \u001b[37mstatic/\u001b[0m\u001b[1mwidget.css\u001b[0m \u001b[36m15.1kb\u001b[0m\n", - "\n", - "⚡ \u001b[32mDone in 176ms\u001b[0m\n", - "\u001b[1G\u001b[0K⠙\u001b[1G\u001b[0K" - ] - } - ], - "source": [ - "!npx esbuild js/widget.jsx --minify --format=esm --bundle --outdir=static" - ] - }, - { - "cell_type": "markdown", - "id": "58989109-da22-4424-87a0-02b44be5a4a1", - "metadata": {}, - "source": [ - "## Simple Demonstrator (How to use *anywidget* to build a reactflow widget)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "bacdd7c5-db0b-45b9-b5c9-960157b3265c", - "metadata": {}, - "outputs": [], - "source": [ - "import anywidget\n", - "import pathlib\n", - "import traitlets\n", - "import os\n", - "import json" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c43f096b-3435-40b7-84f5-d21281af4622", - "metadata": {}, - "outputs": [], - "source": [ - "class ReactFlowWidget(anywidget.AnyWidget):\n", - " path = pathlib.Path(os.getcwd()) / 'static' \n", - " _esm = path / \"widget.js\"\n", - " _css = path / \"widget.css\"\n", - " nodes = traitlets.Unicode('[]').tag(sync=True)\n", - " edges = traitlets.Unicode('[]').tag(sync=True)\n", - " commands = traitlets.Unicode('[]').tag(sync=True)" - ] - }, - { - "cell_type": "markdown", - "id": "cddb7083-b7de-4e5d-b6a3-ff85da9d3180", - "metadata": {}, - "source": [ - "### Create an empty reactflow window" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "86260af7-a89c-4716-b1e2-bd87169a5f8c", - "metadata": {}, - "outputs": [], - "source": [ - "w = ReactFlowWidget()\n", - "w" - ] - }, - { - "cell_type": "markdown", - "id": "705c599b-2bf3-46ed-bccd-144ee901cb0d", - "metadata": {}, - "source": [ - "#### Create some nodes and load them into the widget (to see the effect go back the the frame above)" - ] - }, - { - "cell_type": "markdown", - "id": "6a411836-aca8-4ebf-83f3-03077ce317ab", - "metadata": {}, - "source": [ - "Note: We have to convert the nodes dictionary to json to load it" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "23222809-34df-4d42-b81c-a82bee6e86e3", - "metadata": {}, - "outputs": [], - "source": [ - "nodes = [\n", - " {\n", - " 'id': '1',\n", - " 'data': { 'label': 'Hello' },\n", - " 'position': { 'x': 0, 'y': 0 },\n", - " 'type': 'input',\n", - " 'sourcePosition': 'right',\n", - " },\n", - " {\n", - " 'id': '2',\n", - " 'data': { 'label': 'message' },\n", - " 'position': { 'x': 250, 'y': 100 },\n", - " 'type': 'output',\n", - " 'targetPosition': 'left', \n", - " },\n", - " {\n", - " 'id': '3',\n", - " 'data': { 'label': 'my_node_1', \n", - " 'source_labels': ['a', 'b'],\n", - " 'target_labels': ['in1', 'in_2', 'in_3'],\n", - " 'target_values': ['1', None, 2],\n", - " },\n", - " 'position': { 'x': 0, 'y': 100 },\n", - " 'type': 'customNode',\n", - " 'style': {\n", - " 'border': '1px black solid',\n", - " 'padding': 5,\n", - " 'background': '#999',\n", - " 'borderRadius': '10px',\n", - " 'width': '200px',\n", - " }, \n", - " 'targetPosition': 'left', \n", - " 'sourcePosition': 'right', \n", - " },\n", - " {\n", - " 'id': '4',\n", - " 'data': { 'label': 'my_node_2', \n", - " 'source_labels': ['a', 'c', '12'],\n", - " 'target_labels': ['in1', 'in_2'],\n", - " 'target_values': ['1', None],\n", - " },\n", - " 'position': { 'x': 300, 'y': 160 },\n", - " 'type': 'customNode',\n", - " 'style': {\n", - " 'border': '1px black solid',\n", - " 'padding': 20,\n", - " 'background': '#1999',\n", - " }, \n", - " 'targetPosition': 'left', \n", - " 'sourcePosition': 'right', \n", - " }\n", - "]\n", - "\n", - "nodes_json = json.dumps(nodes)\n", - "w.nodes = nodes_json" - ] - }, - { - "cell_type": "markdown", - "id": "df1b3652-820b-4067-b066-1fe3327bebef", - "metadata": {}, - "source": [ - "Play a bit with the widget. Move the nodes, connect the node handles. You can get all the changes you did graphicall via the nodes and edges property of the widget:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3e89757b-58ec-4751-ab36-3c2754214142", - "metadata": {}, - "outputs": [], - "source": [ - "json.loads(w.edges)" - ] - }, - { - "cell_type": "markdown", - "id": "717f068f-a6b9-45d1-8d4a-34188df8f089", - "metadata": {}, - "source": [ - "## PyironFlow Gui" - ] - }, - { - "cell_type": "markdown", - "id": "a9788019-fbc8-4612-9bc6-7b03b2df0304", - "metadata": {}, - "source": [ - "#### Definition via workflow" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f4d7c441-ce54-4e1a-b87c-f765de711289", - "metadata": {}, - "outputs": [], - "source": [ - "!npx esbuild js/widget.jsx --format=esm --bundle --outdir=static" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "5d9dfc7b-00eb-44e2-8f64-94ca9fa1187a", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001b[1G\u001b[0K⠙\u001b[1G\u001b[0K\n", - " \u001b[37mstatic/\u001b[0m\u001b[1mwidget.js\u001b[0m \u001b[36m308.5kb\u001b[0m\n", - " \u001b[37mstatic/\u001b[0m\u001b[1mwidget.css\u001b[0m \u001b[36m15.1kb\u001b[0m\n", - "\n", - "⚡ \u001b[32mDone in 38ms\u001b[0m\n", - "\u001b[1G\u001b[0K⠙\u001b[1G\u001b[0K" - ] - } - ], - "source": [ - "!npx esbuild js/widget.jsx --minify --format=esm --bundle --outdir=static" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "2d384fd8-5a61-4a4e-ba9e-8343db77d392", - "metadata": {}, - "outputs": [], - "source": [ - "%config IPCompleter.evaluation='unsafe'\n", - "\n", - "import sys\n", - "from pathlib import Path\n", - "sys.path.insert(0, str(Path(Path.cwd()).parent) + '/pyiron_nodes')" - ] - }, - { - "cell_type": "markdown", - "id": "01664ecc-b47e-47fa-a21c-a1b9531efd99", - "metadata": {}, - "source": [ - "### Create two example workflows (to compute elastic constants and an empty one)" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "4f923d3b-3ede-428e-8d6d-fac62264f78c", - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "8f9bf38a1f1d4719b323f0e8b7a7d18a", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "from pyiron_workflow import Workflow \n", - "\n", - "Workflow.register(\"node_library.atomistic\", \"atomistic\") \n", - "\n", - "wf = Workflow('compute_elastic_constants')\n", - "wf.engine = wf.create.atomistic.engine.ase.M3GNet()\n", - "wf.bulk = wf.create.atomistic.structure.build.bulk('Al', cubic=True)\n", - "wf.elastic = wf.create.atomistic.property.elastic.elastic_constants(structure=wf.bulk, engine=wf.engine) #, parameters=parameters)\n", - "# out = elastic.pull()\n", - "\n", - "wf_new = Workflow('new_workflow')" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "005814b6-b152-407d-a130-928d32e51741", - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "0f3a4de0b2124f999e3ea0855503ddbd", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "HBox(children=(Accordion(children=(Tree(nodes=(Node(icon_style='warning', name='databases'), Node(icon_style='…" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "from python.pyironflow import PyironFlow\n", - "\n", - "pf = PyironFlow([wf])\n", - "pf.gui" - ] - }, - { - "cell_type": "markdown", - "id": "527a4315-32e3-45a3-bec5-7efd12163e6b", - "metadata": {}, - "source": [ - "### Get the grapically build or modified workflow" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9d618ec7-9b51-45bd-b9f9-ede1bdcfc0ee", - "metadata": {}, - "outputs": [], - "source": [ - "wf = pf.get_workflow()\n", - "wf.bulk.outputs.channel_dict['structure'].value" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5c6aeaf5-4694-4498-9bb5-7a83e555e08d", - "metadata": {}, - "outputs": [], - "source": [ - "pf.wf_widgets[0].gui.nodes" - ] - }, - { - "cell_type": "markdown", - "id": "c46db326-daf7-453c-919d-e882b8e8df8b", - "metadata": {}, - "source": [ - "### You can return the workflow back to the gui " - ] - }, - { - "cell_type": "markdown", - "id": "99101524-fa8a-4ea2-ada8-be2c9e3708e0", - "metadata": {}, - "source": [ - "Note: \n", - "- all the nodes and connections are reproduced\n", - "- the node positions are presently not stored, you have to place them manually" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c9b1cdf2-374f-4752-9a61-7762b61e64a4", - "metadata": {}, - "outputs": [], - "source": [ - "wf = pf.get_workflow()\n", - "pf2 = PyironFlow([wf])\n", - "pf2.gui" - ] - }, - { - "cell_type": "markdown", - "id": "b9b8b084-6c81-42e6-85fd-1b9fe60b296b", - "metadata": {}, - "source": [ - "#### Get the nodes of the graphically represented workflow (remove the semicolon)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "94c103f6-37f0-44da-8fda-19b88d3a4c54", - "metadata": {}, - "outputs": [], - "source": [ - "import json\n", - "json.loads(pf.wf_widgets[0].gui.nodes);" - ] - }, - { - "cell_type": "markdown", - "id": "31b94459-6a73-4fcf-9d61-7ccdec3170d3", - "metadata": {}, - "source": [ - "#### Run the workflow manually (you can also do this in the gui)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "49605a78-999f-4b5f-b519-6691fdf36b6e", - "metadata": {}, - "outputs": [], - "source": [ - "out = wf.run()" - ] - }, - { - "cell_type": "markdown", - "id": "0fed749e-8285-428b-8f5d-a6c47d981556", - "metadata": {}, - "source": [ - "#### Open the log window for debugging" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ae6cee08-34b5-4f88-b963-d857dc15ef20", - "metadata": {}, - "outputs": [], - "source": [ - "pf.out_log" - ] - }, - { - "cell_type": "markdown", - "id": "9813abeb-7b4a-4fe6-beab-4b45251e75df", - "metadata": {}, - "source": [ - "TODO:\n", - " - create new label if same node is included multiple times in the same workflow (feature should be provided on the pyiron_workflow side)\n", - " - Include values at ports (if simple data structures)\n", - " - Tree view for new nodes\n", - " - add top buttons: 'run' (entire workflow)\n", - " - automatic arrangement of nodes\n", - " - Allow editing input channels (e.g. chemical element)\n", - " - Keep state of a node persistent, i.e., if had had run the output etc. should be available without rerunning (keep workflow instance if no changes have been applied)\n", - " - Bugs:\n", - " - node with multiple outputs cannot be jsonified (get_import_path(node) fails)" - ] - }, - { - "cell_type": "markdown", - "id": "94f7c453-f7d2-47c4-b4f8-804a766af71f", - "metadata": {}, - "source": [ - "### Ideas, concepts" - ] - }, - { - "cell_type": "markdown", - "id": "d1afacbd-b8eb-444d-ae28-e3bdcae7a0a2", - "metadata": {}, - "source": [ - "The follwoing ipywidget may be useful (e.g. to replace the tabs)" - ] - }, - { - "cell_type": "code", - "execution_count": 26, - "id": "37f1b1f6-0cfa-43cd-840a-67f00672060b", - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "c7969df5ac8d461ba636a61d6ed1c15b", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "TagsInput(value=['pizza', 'fries'], allow_duplicates=False, tabbable=False)" - ] - }, - "execution_count": 26, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "from ipywidgets import TagsInput\n", - "tabs = TagsInput(\n", - " value=['pizza', 'fries'],\n", - " # allowed_tags=['pizza', 'fries', 'tomatoes', 'steak'],\n", - " allow_duplicates=False\n", - ")\n", - "tabs" - ] - }, - { - "cell_type": "code", - "execution_count": 27, - "id": "12912768-6c3e-4101-a8ab-c68d5cfc3594", - "metadata": {}, - "outputs": [], - "source": [ - "import ipywidgets as widgets\n", - "from traitlets import Tuple, Any, Unicode, Enum, Int, observe, validate, link\n", - "\n", - "class InputTags(widgets.HBox, widgets.ValueWidget):\n", - " allowed_tags = Tuple(allow_none=True)\n", - " max_tags = Int(allow_none=True, default_value=None, min=1)\n", - " options = Tuple()\n", - " value = Any()\n", - " description = Unicode()\n", - " allowed_type = Enum((str,float,None))\n", - " _suggestions = Tuple()\n", - " def __init__(self, value=None, description=None, allowed_tags=None, allowed_type=str, max_tags=None, color=None, options=None, **kwargs):\n", - " '''value=None, description=None, allowed_tags=None, max_tags=None, color=None'''\n", - " self._cbox = widgets.Combobox(layout=widgets.Layout(min_width='110px', flex='1', margin = '2px -5px 0px 2px'), \n", - " placeholder = \"type or select\")\n", - " super().__init__(**kwargs)\n", - " if description is None:\n", - " description=''\n", - " self.max_tags = max_tags\n", - " self._btn_color = color\n", - " self._btns_cont = widgets.HBox([self._cbox], layout=dict(flex='1', min_width='220px'))\n", - " self.slct_all_btn = widgets.Button(icon='angle-double-left', tooltip='select all', button_style='info', \n", - " layout=dict(min_width='32px', width='auto', margin='2px -2px 0px 2px'))\n", - " self.clear_all_btn = widgets.Button(icon='times', tooltip='clear all', button_style='warning', \n", - " layout=dict(min_width='32px', width='auto', margin='2px 0px 0px 2px'))\n", - " self.validate_btn = widgets.Button(icon='check', tooltip='add to queue', button_style='success', \n", - " layout=dict(min_width='32px', width='auto', margin='2px 0px 0px 2px'))\n", - " self.validate_btn.on_click(lambda b: self.submit())\n", - " self.validate_btn_container = widgets.HBox(layout=dict(margin='0px -2px -2px 0px'))\n", - " self.slct_all_btn.on_click(lambda b: self.select_all_startwith(self._cbox.value))\n", - " self.clear_all_btn.on_click(lambda b: self.clear_cbox())\n", - " self._cbox.observe(self.on_T_change, names='value')\n", - " self._cbox.on_submit(lambda change: self.submit())\n", - " self.buttons = widgets.HBox([self._btns_cont], layout=dict(flex='1', flex_flow='wrap', margin='1px'))\n", - " self._description_widget = widgets.HTML()\n", - " self.children = [self._description_widget, self.buttons]\n", - " self.description = description\n", - " self.allowed_type = allowed_type\n", - " self.allowed_tags = allowed_tags\n", - " link((self._cbox, 'options'), (self, '_suggestions'))\n", - " self.options = options if options is not None else ()\n", - " self.value = value if value is not None else ()\n", - " \n", - " def _isvalid(self, obj):\n", - " #allowed_type = (float, int) if self.allowed_type==float else self.allowed_type\n", - " if obj is None or isinstance(obj, (list,tuple)):\n", - " return True\n", - " #elif obj and isinstance(obj, (list, tuple)):\n", - " # return all(isinstance(s, allowed_type) for s in obj)\n", - " else:\n", - " return False\n", - "\n", - " @observe('description')\n", - " def _observe_description(self, change):\n", - " desc = change.new\n", - " value = f'''{desc}'''\n", - " width = '86px' if desc!='' else '0px'\n", - " self._description_widget.value = value\n", - " self._description_widget.layout.width = width\n", - " \n", - " def _validate_value_type(self, val, allowed_type, allowed_tags=None):\n", - " if val is None:\n", - " val = ()\n", - " val_new = []\n", - " for v in val:\n", - " if str(v).strip()!='':\n", - " if allowed_tags is None or \\\n", - " (allowed_tags is not None and v in allowed_tags):\n", - " try:\n", - " v = allowed_type(v)\n", - " val_new.append(v)\n", - " except ValueError:\n", - " pass\n", - " return tuple(val_new)\n", - "\n", - "\n", - " @validate('allowed_tags')\n", - " def _valid_allowed_tags(self, proposal):\n", - " val = proposal['value']\n", - " if all(isinstance(v,(float,int)) for v in val):\n", - " val = tuple(float(v) for v in val)\n", - " else:\n", - " val = tuple(str(v) for v in val)\n", - " return val\n", - "\n", - " @observe('allowed_tags')\n", - " def _observe_allowed_tags(self, change):\n", - " val = change.new\n", - " if val is not None:\n", - " if val and isinstance(val[0], float):\n", - " self.allowed_type = float\n", - " else:\n", - " self.allowed_type = str\n", - " self.options = val\n", - " if self.value is not None:\n", - " self.value = tuple(v for v in self.value if v in val)\n", - " \n", - " @observe('allowed_type')\n", - " def _observe_allowed_type(self, change):\n", - " self.value = self._validate_value_type(self.value, allowed_type=change.new)\n", - " if self.allowed_tags is not None:\n", - " at = self._validate_value_type(self.allowed_tags, allowed_type=change.new)\n", - " if not at:\n", - " at = None\n", - " self.allowed_tags = at\n", - "\n", - " @validate('options')\n", - " def _valid_options(self, proposal):\n", - " val = proposal['value']\n", - " opts = self._validate_value_type(val, allowed_type=self.allowed_type, allowed_tags=self.allowed_tags)\n", - " if self.allowed_tags is not None:\n", - " opts = tuple(sorted(set(opts+self.allowed_tags)))\n", - " return opts\n", - "\n", - " @observe('value','options')\n", - " def _update_suggestions(self, change):\n", - " opts = set(self.options).difference(set(self.value))\n", - " self._suggestions = sorted(tuple(str(o) for o in opts))\n", - " \n", - " @validate('value')\n", - " def _valid_value(self, proposal):\n", - " val = proposal['value']\n", - " assert self._isvalid(val), f'{val} of type {type(val)} must be a list or tuple of {self.allowed_type}'\n", - " newval = self._validate_value_type(val, allowed_type=self.allowed_type, allowed_tags=self.allowed_tags)\n", - " return tuple(sorted(self.allowed_type(v) for v in newval))[:self.max_tags]\n", - " \n", - " @observe('value')\n", - " def _observe_value(self, change):\n", - " btns = [b for b in self.buttons.children[:-1] if b.description in change.new]\n", - " oldval = [] if change.old is None else change.old\n", - " for v in change.new:\n", - " if v not in oldval:\n", - " desc = f\"'{v}'\" if self.allowed_type is str else f\"{v}\"\n", - " button = widgets.Button(description = desc, tooltip=str(v), disabled=True,\n", - " layout=dict(width='auto'),\n", - " style=dict(button_color=self._btn_color))\n", - " close_btn = widgets.Button(icon='times', \n", - " layout=dict(width='32px', margin='0px -5px 0px 0px'),\n", - " style=dict(button_color=self._btn_color))\n", - " close_btn.on_click(self._on_close_button_click)\n", - " bbox=widgets.HBox([close_btn,button], layout=dict(align_items='center', width='auto'))\n", - " bbox.description = close_btn.tag = v\n", - " bbox.tag = 'entry'\n", - " btns.append(bbox)\n", - " self._cbox.value = ''\n", - " opts = set(self.options+self.value)\n", - " self.options = tuple(opts)\n", - " if self.allowed_tags is None or len(self.allowed_tags) != len(btns):\n", - " self._btns_cont.children = (self._cbox, self.validate_btn_container, self.slct_all_btn, self.clear_all_btn)\n", - " elif not self.value:\n", - " self._btns_cont.children = (self._cbox, self.clear_all_btn,)\n", - " else:\n", - " self._btns_cont.children = (self.clear_all_btn,)\n", - " self.buttons.children = btns + [self._btns_cont]\n", - " \n", - " @observe('_suggestions')\n", - " def _update_placeholder(self, change):\n", - " opts = self._suggestions\n", - " suff = ''\n", - " if not opts:\n", - " opts = ('type or select',)\n", - " elif len(opts)>5:\n", - " suff = '...'\n", - " opts = opts[:5]\n", - " opts_str = f'{opts}'\n", - " if self.allowed_tags is None:\n", - " pref = 'suggestions'\n", - " else:\n", - " pref = 'allowed tags'\n", - " if self.allowed_type is float:\n", - " typ = 'number'\n", - " else:\n", - " typ = 'string'\n", - " self._cbox.placeholder = f'{pref} ({typ}): {opts_str[1:-1]}{suff}'\n", - "\n", - " def _on_close_button_click(self, b):\n", - " self.value = [child.description for child in self.buttons.children[:-1] if child.description!=b.tag]\n", - " \n", - " def on_T_change(self, change=None):\n", - " if change.new.strip() == '':\n", - " self.validate_btn_container.children = []\n", - " else:\n", - " self.validate_btn_container.children = [self.validate_btn]\n", - " self.slct_all_btn.tooltip = f\"select all starting with {change.new}\" if change.new!='' else \"select all\"\n", - " if change.new!='' and self.allowed_tags is not None:\n", - " val = [v for v in self._cbox.options if v.startswith(change.new)]\n", - " if len(val)==1:\n", - " self.submit(val[0])\n", - " if change.new in self._cbox.options and len(val)==1:\n", - " self.submit()\n", - " \n", - " def submit(self, val=None):\n", - " val = self._cbox.value.strip() if val is None else val\n", - " if val != '':\n", - " try:\n", - " val = self.allowed_type(val)\n", - " except ValueError:\n", - " pass\n", - " if self.max_tags is None or len(self.value),\n", - " '_model_module': ,\n", - " '_model_module_version': ,\n", - " '_model_name': ,\n", - " '_msg_callbacks': ,\n", - " '_property_lock': ,\n", - " '_states_to_send': ,\n", - " '_suggestions': ,\n", - " '_view_count': ,\n", - " '_view_module': ,\n", - " '_view_module_version': ,\n", - " '_view_name': ,\n", - " 'allowed_tags': ,\n", - " 'allowed_type': ,\n", - " 'box_style': ,\n", - " 'children': ,\n", - " 'comm': ,\n", - " 'description': ,\n", - " 'keys': ,\n", - " 'layout': ,\n", - " 'log': ,\n", - " 'max_tags': ,\n", - " 'options': ,\n", - " 'tabbable': ,\n", - " 'tooltip': ,\n", - " 'value': }" - ] - }, - "execution_count": 41, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "it.traits()" - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "id": "205e47c2-3250-4c73-b847-e0724c0ba4ef", - "metadata": {}, - "outputs": [], - "source": [ - "tabs.tabbable" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "1e123ebc-800d-4257-ad51-732aaa051925", - "metadata": {}, - "outputs": [], - "source": [ - "from python.pyironflow import PyironFlow\n", - "\n", - "pf = PyironFlow([])\n", - "pf.gui" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "fcb19a81-29a2-4241-81d9-ecd91da77731", - "metadata": {}, - "outputs": [], - "source": [ - "from pyiron_workflow import Workflow \n", - "\n", - "Workflow.register(\"node_library.databases\", 'databases') \n", - "\n", - "wf = Workflow('database')\n", - "wf.database = wf.create.databases.elasticity.de_jong()\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "cad9115f-95b9-471d-8183-2ec72e879f9f", - "metadata": {}, - "outputs": [], - "source": [ - "df = wf.run()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "1fd76521-f24b-4beb-8d6b-128c73f19881", - "metadata": {}, - "outputs": [], - "source": [ - "wf.database.run()" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "a16ebb77-f89a-487c-8c90-9495b76b8b40", - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "4317ab2821fe4f6c80485c2f0a2349d3", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "from pyiron_workflow import Workflow \n", - "\n", - "Workflow.register(\"node_library.atomistic\", \"atomistic\") \n", - "\n", - "wf = Workflow('database')\n", - "wf.bulk = wf.create.atomistic.structure.build.cubic_bulk_cell('Al', cell_size=3)\n", - "wf.phonopy = wf.create.atomistic.property.phonons.create_phonopy(structure=wf.bulk)\n", - "wf.dyn_matrix = wf.create.atomistic.property.phonons.get_dynamical_matrix(phonopy=wf.phonopy.outputs.phonopy)\n", - "wf.eigenvalues = wf.create.atomistic.property.phonons.get_eigenvalues(matrix=wf.dyn_matrix)" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "5b3c291a-78f7-40fa-995c-58702218e33d", - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "c3417e4a0af7493b86fae9322b9b524b", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "HBox(children=(Accordion(children=(Tree(nodes=(Node(icon_style='warning', name='databases'), Node(icon_style='…" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "from python.pyironflow import PyironFlow\n", - "\n", - "pf = PyironFlow([wf])\n", - "pf.gui" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "b27a3e6f-0956-43ac-9ba3-796896cc9c6f", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAh8AAAGdCAYAAACyzRGfAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjkuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/TGe4hAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAedUlEQVR4nO3df0xd9eH/8dextLdQAWdt7y0ptrhdrR3+pI6Wbh/YJriuM1tYdA5n6nRLHXUWm4WU8ceuZrtUEgnb0C5tGsQYWuP8mXVWMJtoxjppbTNDjbqJLZu9I3UI2OJltu/vH/1y7O2l1Qv3vuHQ5yM5ifec9z2879uT9JnDvVzHGGMEAABgyXmTPQEAAHBuIT4AAIBVxAcAALCK+AAAAFYRHwAAwCriAwAAWEV8AAAAq4gPAABgVdpkT+B0J06c0HvvvafMzEw5jjPZ0wEAAJ+BMUZDQ0PKycnReeed/d7GlIuP9957T7m5uZM9DQAAMA69vb1auHDhWcdMufjIzMyUdHLyWVlZkzwbAADwWQwODio3N9f9d/xsplx8jP6qJSsri/gAAMBjPstbJnjDKQAAsIr4AAAAVhEfAADAKuIDAABYRXwAAACriA8AAGAV8QEAAKwiPgAAgFXEBwAAsIr4AAAAVhEfAADAKuIDAABYRXwAAACriA8AAGBV2mRPANPT4o07J3sKCXt30+rJngIAnBO48wEAAKwiPgAAgFXEBwAAsIr4AAAAVhEfAADAqoTiY/HixXIcJ25bt26dJMkYo1AopJycHKWnp6ukpETd3d0pmTgAAPCmhOKjq6tLhw8fdrf29nZJ0k033SRJqq+vV0NDg5qamtTV1aVAIKDS0lINDQ0lf+YAAMCTEoqPefPmKRAIuNsf/vAHff7zn1dxcbGMMWpsbFRtba3Ky8uVn5+vlpYWHTt2TK2tramaPwAA8Jhxv+djZGREjz32mO644w45jqOenh5FIhGVlZW5Y3w+n4qLi9XZ2XnG80SjUQ0ODsZsAABg+hp3fDzzzDP64IMPdPvtt0uSIpGIJMnv98eM8/v97rGx1NXVKTs7291yc3PHOyUAAOAB446Pbdu2adWqVcrJyYnZ7zhOzGNjTNy+U9XU1GhgYMDdent7xzslAADgAeP6bpeDBw/qxRdf1FNPPeXuCwQCkk7eAVmwYIG7v6+vL+5uyKl8Pp98Pt94pgEAADxoXHc+mpubNX/+fK1e/ckXceXl5SkQCLifgJFOvi+ko6NDRUVFE58pAACYFhK+83HixAk1NzdrzZo1Skv75OmO46iqqkrhcFjBYFDBYFDhcFgZGRmqqKhI6qQBAIB3JRwfL774og4dOqQ77rgj7lh1dbWGh4dVWVmp/v5+FRYWqq2tTZmZmUmZLAAA8D7HGGMmexKnGhwcVHZ2tgYGBpSVlTXZ08E4Ld64c7KnkLB3N63+9EEAgDEl8u833+0CAACsIj4AAIBVxAcAALCK+AAAAFYRHwAAwCriAwAAWEV8AAAAq4gPAABgFfEBAACsIj4AAIBVxAcAALCK+AAAAFYRHwAAwCriAwAAWEV8AAAAq4gPAABgFfEBAACsIj4AAIBVxAcAALCK+AAAAFYRHwAAwCriAwAAWEV8AAAAq4gPAABgFfEBAACsIj4AAIBVxAcAALCK+AAAAFYRHwAAwCriAwAAWEV8AAAAq4gPAABgFfEBAACsIj4AAIBVxAcAALCK+AAAAFYRHwAAwCriAwAAWJVwfPz73//WD37wA82dO1cZGRm6+uqrtXfvXve4MUahUEg5OTlKT09XSUmJuru7kzppAADgXQnFR39/v1auXKmZM2fq+eef14EDB/Tggw/qggsucMfU19eroaFBTU1N6urqUiAQUGlpqYaGhpI9dwAA4EFpiQx+4IEHlJubq+bmZnff4sWL3f82xqixsVG1tbUqLy+XJLW0tMjv96u1tVVr165NzqwBAIBnJXTn47nnntOyZct00003af78+brmmmu0detW93hPT48ikYjKysrcfT6fT8XFxers7BzznNFoVIODgzEbAACYvhKKj3feeUebN29WMBjUCy+8oLvuukv33HOPHn30UUlSJBKRJPn9/pjn+f1+99jp6urqlJ2d7W65ubnjeR0AAMAjEoqPEydO6Nprr1U4HNY111yjtWvX6sc//rE2b94cM85xnJjHxpi4faNqamo0MDDgbr29vQm+BAAA4CUJxceCBQu0dOnSmH2XX365Dh06JEkKBAKSFHeXo6+vL+5uyCifz6esrKyYDQAATF8JxcfKlSv15ptvxux76623tGjRIklSXl6eAoGA2tvb3eMjIyPq6OhQUVFREqYLAAC8LqFPu9x7770qKipSOBzWzTffrFdffVVbtmzRli1bJJ38dUtVVZXC4bCCwaCCwaDC4bAyMjJUUVGRkhcAAAC8JaH4uO666/T000+rpqZG999/v/Ly8tTY2Khbb73VHVNdXa3h4WFVVlaqv79fhYWFamtrU2ZmZtInDwAAvMcxxpjJnsSpBgcHlZ2drYGBAd7/4WGLN+6c7Ckk7N1Nqyd7CgDgWYn8+813uwAAAKuIDwAAYBXxAQAArCI+AACAVcQHAACwivgAAABWER8AAMAq4gMAAFhFfAAAAKuIDwAAYBXxAQAArCI+AACAVcQHAACwKm2yJwAASA2+XRpTFXc+AACAVcQHAACwivgAAABWER8AAMAq4gMAAFhFfAAAAKuIDwAAYBXxAQAArOKPjAEexh+RAuBF3PkAAABWER8AAMAq4gMAAFhFfAAAAKuIDwAAYBXxAQAArCI+AACAVcQHAACwivgAAABWER8AAMAq4gMAAFhFfAAAAKuIDwAAYBXxAQAArCI+AACAVQnFRygUkuM4MVsgEHCPG2MUCoWUk5Oj9PR0lZSUqLu7O+mTBgAA3pXwnY8vfvGLOnz4sLu9/vrr7rH6+no1NDSoqalJXV1dCgQCKi0t1dDQUFInDQAAvCvh+EhLS1MgEHC3efPmSTp516OxsVG1tbUqLy9Xfn6+WlpadOzYMbW2tiZ94gAAwJsSjo+3335bOTk5ysvL0y233KJ33nlHktTT06NIJKKysjJ3rM/nU3FxsTo7O894vmg0qsHBwZgNAABMXwnFR2FhoR599FG98MIL2rp1qyKRiIqKivT+++8rEolIkvx+f8xz/H6/e2wsdXV1ys7Odrfc3NxxvAwAAOAVCcXHqlWr9N3vfldXXHGFrr/+eu3cuVOS1NLS4o5xHCfmOcaYuH2nqqmp0cDAgLv19vYmMiUAAOAxE/qo7Zw5c3TFFVfo7bffdj/1cvpdjr6+vri7Iafy+XzKysqK2QAAwPQ1ofiIRqN64403tGDBAuXl5SkQCKi9vd09PjIyoo6ODhUVFU14ogAAYHpIS2Twz372M9144426+OKL1dfXp1/+8pcaHBzUmjVr5DiOqqqqFA6HFQwGFQwGFQ6HlZGRoYqKilTNHwAAeExC8fGvf/1L3//+93XkyBHNmzdPy5cv1+7du7Vo0SJJUnV1tYaHh1VZWan+/n4VFhaqra1NmZmZKZk8AADwnoTiY8eOHWc97jiOQqGQQqHQROYEAACmMb7bBQAAWEV8AAAAq4gPAABgFfEBAACsIj4AAIBVxAcAALCK+AAAAFYRHwAAwCriAwAAWEV8AAAAq4gPAABgFfEBAACsIj4AAIBVxAcAALCK+AAAAFYRHwAAwCriAwAAWEV8AAAAq4gPAABgFfEBAACsIj4AAIBVxAcAALCK+AAAAFYRHwAAwCriAwAAWEV8AAAAq4gPAABgFfEBAACsIj4AAIBVxAcAALCK+AAAAFYRHwAAwCriAwAAWEV8AAAAq4gPAABgFfEBAACsIj4AAIBVxAcAALBqQvFRV1cnx3FUVVXl7jPGKBQKKScnR+np6SopKVF3d/dE5wkAAKaJccdHV1eXtmzZoiuvvDJmf319vRoaGtTU1KSuri4FAgGVlpZqaGhowpMFAADeN674+PDDD3Xrrbdq69at+tznPufuN8aosbFRtbW1Ki8vV35+vlpaWnTs2DG1trYmbdIAAMC7xhUf69at0+rVq3X99dfH7O/p6VEkElFZWZm7z+fzqbi4WJ2dnWOeKxqNanBwMGYDAADTV1qiT9ixY4dee+01dXV1xR2LRCKSJL/fH7Pf7/fr4MGDY56vrq5O9913X6LTAAAAHpXQnY/e3l6tX79ejz32mGbPnn3GcY7jxDw2xsTtG1VTU6OBgQF36+3tTWRKAADAYxK687F371719fWpoKDA3Xf8+HG9/PLLampq0ptvvinp5B2QBQsWuGP6+vri7oaM8vl88vl845k7AADwoITufHz961/X66+/rv3797vbsmXLdOutt2r//v265JJLFAgE1N7e7j5nZGREHR0dKioqSvrkAQCA9yR05yMzM1P5+fkx++bMmaO5c+e6+6uqqhQOhxUMBhUMBhUOh5WRkaGKiorkzRoAAHhWwm84/TTV1dUaHh5WZWWl+vv7VVhYqLa2NmVmZib7RwEAAA+acHy89NJLMY8dx1EoFFIoFJroqQEAwDTEd7sAAACriA8AAGAV8QEAAKwiPgAAgFXEBwAAsIr4AAAAVhEfAADAKuIDAABYRXwAAACriA8AAGAV8QEAAKwiPgAAgFVJ/1ZbwKsWb9w52VMAgHMCdz4AAIBVxAcAALCK+AAAAFYRHwAAwCriAwAAWEV8AAAAq4gPAABgFfEBAACsIj4AAIBV/IVTD+AvbwIAphPufAAAAKuIDwAAYBXxAQAArCI+AACAVcQHAACwivgAAABWER8AAMAq4gMAAFhFfAAAAKuIDwAAYBXxAQAArCI+AACAVcQHAACwivgAAABWJRQfmzdv1pVXXqmsrCxlZWVpxYoVev75593jxhiFQiHl5OQoPT1dJSUl6u7uTvqkAQCAdyUUHwsXLtSmTZu0Z88e7dmzR1/72tf07W9/2w2M+vp6NTQ0qKmpSV1dXQoEAiotLdXQ0FBKJg8AALwnofi48cYb9c1vflOXXnqpLr30Uv3qV7/S+eefr927d8sYo8bGRtXW1qq8vFz5+flqaWnRsWPH1Nramqr5AwAAjxn3ez6OHz+uHTt26OjRo1qxYoV6enoUiURUVlbmjvH5fCouLlZnZ2dSJgsAALwvLdEnvP7661qxYoU++ugjnX/++Xr66ae1dOlSNzD8fn/MeL/fr4MHD57xfNFoVNFo1H08ODiY6JQAAICHJHzn47LLLtP+/fu1e/du/eQnP9GaNWt04MAB97jjODHjjTFx+05VV1en7Oxsd8vNzU10SgAAwEMSjo9Zs2bpC1/4gpYtW6a6ujpdddVV+vWvf61AICBJikQiMeP7+vri7oacqqamRgMDA+7W29ub6JQAAICHTPjvfBhjFI1GlZeXp0AgoPb2dvfYyMiIOjo6VFRUdMbn+3w+96O7oxsAAJi+EnrPx89//nOtWrVKubm5Ghoa0o4dO/TSSy9p165dchxHVVVVCofDCgaDCgaDCofDysjIUEVFRarmDwAAPCah+PjPf/6j2267TYcPH1Z2drauvPJK7dq1S6WlpZKk6upqDQ8Pq7KyUv39/SosLFRbW5syMzNTMnkAAOA9CcXHtm3bznrccRyFQiGFQqGJzAkAAExjfLcLAACwivgAAABWER8AAMAq4gMAAFhFfAAAAKuIDwAAYBXxAQAArCI+AACAVQn9kTEAABBr8cadkz2FhL27afWk/nzufAAAAKuIDwAAYBXxAQAArCI+AACAVcQHAACwivgAAABWER8AAMAq4gMAAFhFfAAAAKuIDwAAYBXxAQAArCI+AACAVcQHAACwivgAAABWER8AAMAq4gMAAFhFfAAAAKuIDwAAYBXxAQAArCI+AACAVcQHAACwivgAAABWER8AAMAq4gMAAFhFfAAAAKuIDwAAYBXxAQAArCI+AACAVcQHAACwKqH4qKur03XXXafMzEzNnz9f3/nOd/Tmm2/GjDHGKBQKKScnR+np6SopKVF3d3dSJw0AALwrofjo6OjQunXrtHv3brW3t+vjjz9WWVmZjh496o6pr69XQ0ODmpqa1NXVpUAgoNLSUg0NDSV98gAAwHvSEhm8a9eumMfNzc2aP3++9u7dq//7v/+TMUaNjY2qra1VeXm5JKmlpUV+v1+tra1au3Zt8mYOAAA8aULv+RgYGJAkXXjhhZKknp4eRSIRlZWVuWN8Pp+Ki4vV2dk5kR8FAACmiYTufJzKGKMNGzboy1/+svLz8yVJkUhEkuT3+2PG+v1+HTx4cMzzRKNRRaNR9/Hg4OB4pwQAADxg3Hc+7r77bv3973/X9u3b4445jhPz2BgTt29UXV2dsrOz3S03N3e8UwIAAB4wrvj46U9/queee05//vOftXDhQnd/IBCQ9MkdkFF9fX1xd0NG1dTUaGBgwN16e3vHMyUAAOARCcWHMUZ33323nnrqKf3pT39SXl5ezPG8vDwFAgG1t7e7+0ZGRtTR0aGioqIxz+nz+ZSVlRWzAQCA6Suh93ysW7dOra2tevbZZ5WZmene4cjOzlZ6erocx1FVVZXC4bCCwaCCwaDC4bAyMjJUUVGRkhcAAAC8JaH42Lx5sySppKQkZn9zc7Nuv/12SVJ1dbWGh4dVWVmp/v5+FRYWqq2tTZmZmUmZMAAA8LaE4sMY86ljHMdRKBRSKBQa75wAAMA0xne7AAAAq4gPAABgFfEBAACsIj4AAIBVxAcAALCK+AAAAFYRHwAAwCriAwAAWEV8AAAAq4gPAABgFfEBAACsIj4AAIBVxAcAALCK+AAAAFYRHwAAwCriAwAAWEV8AAAAq4gPAABgVdpkT8C2xRt3TvYUAAA4p3HnAwAAWEV8AAAAq4gPAABgFfEBAACsIj4AAIBVxAcAALCK+AAAAFYRHwAAwCriAwAAWEV8AAAAq4gPAABgFfEBAACsIj4AAIBVxAcAALAqbbInAODcsnjjzsmewri8u2n1ZE/hnODV6wOJ4c4HAACwivgAAABWER8AAMAq4gMAAFiVcHy8/PLLuvHGG5WTkyPHcfTMM8/EHDfGKBQKKScnR+np6SopKVF3d3ey5gsAADwu4fg4evSorrrqKjU1NY15vL6+Xg0NDWpqalJXV5cCgYBKS0s1NDQ04ckCAADvS/ijtqtWrdKqVavGPGaMUWNjo2pra1VeXi5Jamlpkd/vV2trq9auXTux2QIAAM9L6ns+enp6FIlEVFZW5u7z+XwqLi5WZ2fnmM+JRqMaHByM2QAAwPSV1PiIRCKSJL/fH7Pf7/e7x05XV1en7Oxsd8vNzU3mlAAAwBSTkk+7OI4T89gYE7dvVE1NjQYGBtytt7c3FVMCAABTRFL/vHogEJB08g7IggUL3P19fX1xd0NG+Xw++Xy+ZE4DAABMYUm985GXl6dAIKD29nZ338jIiDo6OlRUVJTMHwUAADwq4TsfH374of7xj3+4j3t6erR//35deOGFuvjii1VVVaVwOKxgMKhgMKhwOKyMjAxVVFQkdeIAAMCbEo6PPXv26Ktf/ar7eMOGDZKkNWvW6JFHHlF1dbWGh4dVWVmp/v5+FRYWqq2tTZmZmcmbNQAA8KyE46OkpETGmDMedxxHoVBIoVBoIvMCAADTFN/tAgAArCI+AACAVcQHAACwivgAAABWER8AAMAq4gMAAFhFfAAAAKuIDwAAYBXxAQAArCI+AACAVcQHAACwivgAAABWER8AAMCqhL/VFgDORYs37pzsKQDTBnc+AACAVcQHAACwivgAAABWER8AAMAq4gMAAFhFfAAAAKuIDwAAYBXxAQAArCI+AACAVcQHAACwivgAAABWER8AAMAq4gMAAFhFfAAAAKuIDwAAYBXxAQAArCI+AACAVcQHAACwivgAAABWER8AAMAq4gMAAFhFfAAAAKuIDwAAYBXxAQAArEpZfDz88MPKy8vT7NmzVVBQoFdeeSVVPwoAAHhISuLj8ccfV1VVlWpra7Vv3z595Stf0apVq3To0KFU/DgAAOAhKYmPhoYG3XnnnfrRj36kyy+/XI2NjcrNzdXmzZtT8eMAAICHpCX7hCMjI9q7d682btwYs7+srEydnZ1x46PRqKLRqPt4YGBAkjQ4OJjsqUmSTkSPpeS8AAB4RSr+jR09pzHmU8cmPT6OHDmi48ePy+/3x+z3+/2KRCJx4+vq6nTffffF7c/NzU321AAAgKTsxtSde2hoSNnZ2Wcdk/T4GOU4TsxjY0zcPkmqqanRhg0b3McnTpzQf//7X82dO3fM8RMxODio3Nxc9fb2KisrK6nnPpexrqnD2qYG65oarGtqeGVdjTEaGhpSTk7Op45NenxcdNFFmjFjRtxdjr6+vri7IZLk8/nk8/li9l1wwQXJnlaMrKysKf0/0KtY19RhbVODdU0N1jU1vLCun3bHY1TS33A6a9YsFRQUqL29PWZ/e3u7ioqKkv3jAACAx6Tk1y4bNmzQbbfdpmXLlmnFihXasmWLDh06pLvuuisVPw4AAHhISuLje9/7nt5//33df//9Onz4sPLz8/XHP/5RixYtSsWP+8x8Pp9+8YtfxP2aBxPDuqYOa5sarGtqsK6pMR3X1TGf5TMxAAAAScJ3uwAAAKuIDwAAYBXxAQAArCI+AACAVZ6Oj4cfflh5eXmaPXu2CgoK9Morr5x1fEdHhwoKCjR79mxdcskl+t3vfhc35sknn9TSpUvl8/m0dOlSPf3006ma/pSW7LV95JFH5DhO3PbRRx+l8mVMOYms6+HDh1VRUaHLLrtM5513nqqqqsYcxzWb/HXlev1EImv71FNPqbS0VPPmzVNWVpZWrFihF154IW4c12zy19Vz16zxqB07dpiZM2earVu3mgMHDpj169ebOXPmmIMHD445/p133jEZGRlm/fr15sCBA2br1q1m5syZ5ve//707prOz08yYMcOEw2HzxhtvmHA4bNLS0szu3bttvawpIRVr29zcbLKysszhw4djtnNJouva09Nj7rnnHtPS0mKuvvpqs379+rgxXLOpWVeu15MSXdv169ebBx54wLz66qvmrbfeMjU1NWbmzJnmtddec8dwzaZmXb12zXo2Pr70pS+Zu+66K2bfkiVLzMaNG8ccX11dbZYsWRKzb+3atWb58uXu45tvvtl84xvfiBlzww03mFtuuSVJs/aGVKxtc3Ozyc7OTvpcvSTRdT1VcXHxmP9Ics2mZl25Xk+ayNqOWrp0qbnvvvvcx1yzqVlXr12znvy1y8jIiPbu3auysrKY/WVlZers7BzzOX/961/jxt9www3as2eP/ve//511zJnOOR2lam0l6cMPP9SiRYu0cOFCfetb39K+ffuS/wKmqPGs62dxrl+zqVpX6dy+XqXkrO2JEyc0NDSkCy+80N3HNZuadZW8dc16Mj6OHDmi48ePx31Rnd/vj/tCu1GRSGTM8R9//LGOHDly1jFnOud0lKq1XbJkiR555BE999xz2r59u2bPnq2VK1fq7bffTs0LmWLGs66fxbl+zaZqXc/161VKzto++OCDOnr0qG6++WZ3H9dsatbVa9dsSv68ui2O48Q8NsbE7fu08afvT/Sc01Wy13b58uVavny5e3zlypW69tpr9dvf/la/+c1vkjXtKS8V1xfXbPLXgOv1E+Nd2+3btysUCunZZ5/V/Pnzk3LO6STZ6+q1a9aT8XHRRRdpxowZcZXY19cXV5OjAoHAmOPT0tI0d+7cs4450zmno1St7enOO+88XXfddVO2ypNtPOv6WZzr12yq1vV059r1Kk1sbR9//HHdeeedeuKJJ3T99dfHHOOaTc26nm6qX7Oe/LXLrFmzVFBQoPb29pj97e3tKioqGvM5K1asiBvf1tamZcuWaebMmWcdc6ZzTkepWtvTGWO0f/9+LViwIDkTn+LGs66fxbl+zaZqXU93rl2v0vjXdvv27br99tvV2tqq1atXxx3nmk3Nup5uyl+zk/Eu12QY/ajStm3bzIEDB0xVVZWZM2eOeffdd40xxmzcuNHcdttt7vjRj4Pee++95sCBA2bbtm1xHwf9y1/+YmbMmGE2bdpk3njjDbNp06Zz7iNgxqRmbUOhkNm1a5f55z//afbt22d++MMfmrS0NPO3v/3N+uubLImuqzHG7Nu3z+zbt88UFBSYiooKs2/fPtPd3e0e55pNzbpyvZ6U6Nq2traatLQ089BDD8V83PODDz5wx3DNpmZdvXbNejY+jDHmoYceMosWLTKzZs0y1157reno6HCPrVmzxhQXF8eMf+mll8w111xjZs2aZRYvXmw2b94cd84nnnjCXHbZZWbmzJlmyZIl5sknn0z1y5iSkr22VVVV5uKLLzazZs0y8+bNM2VlZaazs9PGS5lSEl1XSXHbokWLYsZwzSZ/XbleP5HI2hYXF4+5tmvWrIk5J9ds8tfVa9esY8z/f2cgAACABZ58zwcAAPAu4gMAAFhFfAAAAKuIDwAAYBXxAQAArCI+AACAVcQHAACwivgAAABWER8AAMAq4gMAAFhFfAAAAKuIDwAAYNX/A5fXVXmJBV7ZAAAAAElFTkSuQmCC", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "from matplotlib import pyplot as plt\n", - "\n", - "\n", - "pic = plt.hist(wf.eigenvalues.pull(), bins=10)" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "id": "128bb28a-27cd-4208-b73b-698c828ba7e6", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "tuple" - ] - }, - "execution_count": 17, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "f39cfe0f-e3c0-407b-827b-d2508933c794", - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "93f4ae7364844ffc92a6b051e4eed766", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "HBox(children=(Accordion(children=(Tree(nodes=(Node(icon_style='warning', name='databases'), Node(icon_style='…" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "from python.pyironflow import PyironFlow\n", - "\n", - "pf = PyironFlow([])\n", - "pf.gui" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f225be2a-90a0-4e5c-b6ed-07518bc1fa6c", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "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.12.4" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/environment.yml b/environment.yml deleted file mode 100644 index 8ab2c5e5..00000000 --- a/environment.yml +++ /dev/null @@ -1,11 +0,0 @@ -channels: -- conda-forge -dependencies: -- anywidget -- esbuild -- ipython -- ipywidgets -- nodejs -- traitlets -- pyiron_workflow -- ipytree diff --git a/js/DummyMacroExpanded.jsx b/js/DummyMacroExpanded.jsx new file mode 100644 index 00000000..b88e0729 --- /dev/null +++ b/js/DummyMacroExpanded.jsx @@ -0,0 +1,327 @@ +import React, { memo, useEffect, useState, useRef } from "react"; +import { Handle, useUpdateNodeInternals, NodeToolbar, useNodesState, Panel} from "@xyflow/react"; +import { useModel } from "@anywidget/react"; +import { UpdateDataContext } from './widget.jsx'; // import the context + +/** + * Author: Joerg Neugebauer + * Copyright: Copyright 2024, Max-Planck-Institut for Sustainable Materials GmbH - Computational Materials Design (CM) Department + * Version: 0.2 + * Maintainer: + * Email: + * Status: development + * Date: Aug 1, 2024 + */ + +export default memo(({ data, node_status, id, position }) => { + const updateNodeInternals = useUpdateNodeInternals(); +// const [nodes, setNodes, onNodesChange] = useNodesState([]); + + const num_handles = Math.max(data.source_labels.length, data.target_labels.length); + const [handles, setHandles] = useState(Array(num_handles).fill({})); + + const model = useModel(); + const context = React.useContext(UpdateDataContext); + + const edgesRef = useRef(data.edges); + +// console.log('nodes', nodes) + + + useEffect(() => { + handles.map((_, index) => { + updateNodeInternals(`handle-${index}`); + }); + }, [handles]); + + + + useEffect(() => { + edgesRef.current = data.edges; // Update the ref when the prop changes + }, [data.edges]); + + useEffect(() => { + const interval = setInterval(() => { + console.log('Running logic every 0,2s', edgesRef.current); + console.log('only edges', data.edges); + const parents = [ + ...new Set( + edgesRef.current + .filter((edge) => edge.type == "macroSubEdge") + .map ((edge) => edge.parent) + ) + ]; + console.log('Parents: ', parents); + if (data.onMessage) { + if (parents.includes(id)) { + data.onMessage(id); + clearInterval(interval); + return; + } + else { + + }; + model.set("timestamp", Date.now()); + }; + + }, 200); + + return () => clearInterval(interval); + + }, []); + + + + + + + const pullFunction = () => { + // pull on the node + console.log('pull: ', data.label) + model.set("commands", `pull: ${data.label} - ${new Date().getTime()}`); + model.save_changes(); + } + + const pushFunction = () => { + // push from the node + console.log('push: ', data.label) + model.set("commands", `push: ${data.label} - ${new Date().getTime()}`); + model.save_changes(); + } + + // outputFunction and sourceFunction lifted to widget.jsx to be used by ContextMenu.jsx + + const resetFunction = () => { + // reset state and cache of node + console.log('reset: ', data.label) + model.set("commands", `reset: ${data.label}`); + model.save_changes(); + } + + const collapseFunction = () => { + // show source code of node + console.log('collapse ', data.label) + model.set("commands", `collapse: ${data.label}`); + model.save_changes(); + } + + const sortFunction = () => { + // reset state and cache of node + console.log('sort: ', data.label) + data.onSort(id); + } + + + + + + const renderLabel = (label, failed, running, ready, cache_hit) => { + let status = ''; + + if (failed === "True") { + status = '🟥 '; + } else if (running === "True") { + status = '🟨 '; + } else if ((ready === "True") && (cache_hit === "False")) { + status = '🟦 '; + } else if ((ready === "True") && (cache_hit === "True")) { + status = '🟩 '; + } else { + status = '⬜ '; + } + + return ( +
+ {status + label} +
+ ); + } + + + const renderCustomHandle = (position, type, index, label) => { + return ( + + ); + } + + const renderInputHandle = (data, index, editValue = false) => { + const label = data.target_labels[index] + const inp_type = data.target_types[index] + const literal_type = data.target_literal_types[index] + const value = data.target_values[index] + const [inputValue, setInputValue] = useState(value); + const context = React.useContext(UpdateDataContext); + // console.log('input type: ', data) + + const inputTypeMap = { + 'str': 'text', + 'int': 'text', + 'float': 'text', + 'bool': 'checkbox', + '_LiteralGenericAlias': 'dropdown' + }; + + const convertInput = (value, inp_type) => { + switch(inp_type) { + case 'int': + // Check if value can be converted to an integer + const intValue = parseInt(value, 10); + return isNaN(intValue) ? value : intValue; + case 'float': + // Check if value can be converted to a float + const floatValue = parseFloat(value); + return isNaN(floatValue) ? value : floatValue; + case 'bool': + return value; + default: + return value; // if inp_type === 'str' or anything else unexpected, returns the original string + } + } + + const currentInputType = inputTypeMap[inp_type] || 'text'; + + if (inp_type === 'NonPrimitive' || inp_type === 'None') { + editValue = false; + } + + const getBackgroundColor = (value, inp_type) => { + if (value === null) { + return 'grey'; + } else if (value === 'NotData') { + return '#FFD740' + } else { + return 'white'; + } + } + + return ( + <> +
+ {`${label}`} + {editValue && (currentInputType === 'dropdown' + ? ( + + ) : ( + { + const newValue = currentInputType === 'checkbox' ? e.target.checked : e.target.value; + console.log('onChange', value, e, inputValue, newValue, index, data.label); + // Always update the input value + setInputValue(newValue); + context(data.label, index, newValue); + }} + onKeyDown={e => { + if(e.keyCode === 13) { + // When Enter key is pressed, convert the input + const convertedValue = convertInput(inputValue, inp_type); + console.log('onKeyDown', value, e, inputValue, convertedValue, index, data.label); + context(data.label, index, convertedValue); + } + }} + onBlur={() => { + // When the mouse leaves the textbox, convert the input + const convertedValue = convertInput(inputValue, inp_type); + context(data.label, index, convertedValue); + }} + style={{ + width: '40px', + height: '10px', + fontSize: '6px', + backgroundColor: getBackgroundColor(value, inp_type) + }} + /> + ))} +
+ {renderCustomHandle('left', 'target', index, label)} + + ); + } + + const renderOutputHandle = (data, index) => { + const label = data.source_labels[index] + + return ( + <> + {renderCustomHandle('right', 'source', index, label)} + + ); + } + + const onChange = (evt) => { + setSimpleOption(evt.target.value); // without type assertions + }; + + return ( +
+ + {renderLabel(data.label, data.failed, data.running, data.ready, data.cache_hit)} + +
+ {handles.map((_, index) => ( +
+
+ {index < data.target_labels.length && + renderInputHandle(data, index, true)} +
+ +
+ {index < data.source_labels.length && + renderOutputHandle(data, index)} +
+
+ ))} +
+ + + + + + +
+ ); +}); diff --git a/js/InNode.jsx b/js/InNode.jsx new file mode 100644 index 00000000..3909e9f4 --- /dev/null +++ b/js/InNode.jsx @@ -0,0 +1,129 @@ +import React, { memo, useEffect, useState } from "react"; +import { Handle, useUpdateNodeInternals, NodeToolbar, useNodesState, Panel} from "@xyflow/react"; +import { useModel } from "@anywidget/react"; +import { UpdateDataContext } from './widget.jsx'; // import the context + +/** + * Author: Joerg Neugebauer + * Copyright: Copyright 2024, Max-Planck-Institut for Sustainable Materials GmbH - Computational Materials Design (CM) Department + * Version: 0.2 + * Maintainer: + * Email: + * Status: development + * Date: Aug 1, 2024 + */ + +export default memo(({ data, node_status }) => { + const updateNodeInternals = useUpdateNodeInternals(); +// const [nodes, setNodes, onNodesChange] = useNodesState([]); + + const num_handles = Math.max(data.source_labels.length, data.target_labels.length); + const [handles, setHandles] = useState(Array(num_handles).fill({})); + + const model = useModel(); + const context = React.useContext(UpdateDataContext); + +// console.log('nodes', nodes) + + + useEffect(() => { + handles.map((_, index) => { + updateNodeInternals(`handle-${index}`); + }); + }, [handles]); + + const pullFunction = () => { + // pull on the node + console.log('pull: ', data.label) + model.set("commands", `pull: ${data.label} - ${new Date().getTime()}`); + model.save_changes(); + } + + const pushFunction = () => { + // push from the node + console.log('push: ', data.label) + model.set("commands", `push: ${data.label} - ${new Date().getTime()}`); + model.save_changes(); + } + + // outputFunction and sourceFunction lifted to widget.jsx to be used by ContextMenu.jsx + + const resetFunction = () => { + // reset state and cache of node + console.log('reset: ', data.label) + model.set("commands", `reset: ${data.label}`); + model.save_changes(); + } + + const renderLabel = (label) => { + + return ( +
+ {label} +
+ ); + } + + + const renderCustomHandle = (position, type, index, label) => { + return ( + + ); + } + + const renderInputHandle = (data, index) => { + const label = data.target_labels[index] + + return ( + <> +
+ {`${label}`} + +
+ {renderCustomHandle('left', 'target', index, label)} + + ); + } + + const renderOutputHandle = (data, index) => { + const label = data.source_labels[index] + + return ( + <> +
+ {`${label}`} +
+ {renderCustomHandle('right', 'source', index, label)} + + ); + } + + + const onChange = (evt) => { + setSimpleOption(evt.target.value); // without type assertions + }; + + return ( +
+ {handles.map((_, index) => ( +
+
+ {index < data.target_labels.length && + renderInputHandle(data, index)} +
+ +
+ {index < data.source_labels.length && + renderOutputHandle(data, index)} +
+
+ ))} +
+ ); +}); \ No newline at end of file diff --git a/js/LoopEdge.jsx b/js/LoopEdge.jsx new file mode 100644 index 00000000..f552219a --- /dev/null +++ b/js/LoopEdge.jsx @@ -0,0 +1,24 @@ +import React from 'react'; +import { BaseEdge, BezierEdge, EdgeLabelRenderer, MarkerType} from '@xyflow/react'; + +export default function SelfConnecting({ id, sourceX, sourceY, targetX, targetY, ...props}) { + + const radiusX = (sourceX - targetX) * 0.6; + const radiusY = 50; + const edgePath = `M ${sourceX - 2} ${sourceY} A ${radiusX} ${radiusY} 0 1 0 ${ + targetX + 2 + } ${targetY}`; + // transform: `translate(-50%, -50%) translate(${radiusX}px,${radiusY}px)`, + //return ; + return ( + <> + + + ); +} diff --git a/js/LoopNode.jsx b/js/LoopNode.jsx new file mode 100644 index 00000000..ad651978 --- /dev/null +++ b/js/LoopNode.jsx @@ -0,0 +1,279 @@ +import React, { memo, useEffect, useState } from "react"; +import { Handle, useUpdateNodeInternals, NodeToolbar, useNodesState, Panel} from "@xyflow/react"; +import { useModel } from "@anywidget/react"; +import { UpdateDataContext } from './widget.jsx'; // import the context + +/** + * Author: Joerg Neugebauer + * Copyright: Copyright 2024, Max-Planck-Institut for Sustainable Materials GmbH - Computational Materials Design (CM) Department + * Version: 0.2 + * Maintainer: + * Email: + * Status: development + * Date: Aug 1, 2024 + */ + +export default memo(({ data, node_status }) => { + const updateNodeInternals = useUpdateNodeInternals(); +// const [nodes, setNodes, onNodesChange] = useNodesState([]); + + const num_handles = Math.max(data.source_labels.length, data.target_labels.length); + const [handles, setHandles] = useState(Array(num_handles).fill({})); + + const model = useModel(); + const context = React.useContext(UpdateDataContext); + +// console.log('nodes', nodes) + + + useEffect(() => { + handles.map((_, index) => { + updateNodeInternals(`handle-${index}`); + }); + }, [handles]); + + const pullFunction = () => { + // pull on the node + console.log('pull: ', data.label) + model.set("commands", `pull: ${data.label} - ${new Date().getTime()}`); + model.save_changes(); + } + + const pushFunction = () => { + // push from the node + console.log('push: ', data.label) + model.set("commands", `push: ${data.label} - ${new Date().getTime()}`); + model.save_changes(); + } + + // outputFunction and sourceFunction lifted to widget.jsx to be used by ContextMenu.jsx + + const resetFunction = () => { + // reset state and cache of node + console.log('reset: ', data.label) + model.set("commands", `reset: ${data.label}`); + model.save_changes(); + } + + const expandFunction = () => { + // show source code of node + console.log('expand ', data.label) + model.set("commands", `expand: ${data.label}`); + model.save_changes(); + } + + + + const renderLabel = (label, failed, running, ready, cache_hit) => { + let status = ''; + + if (failed === "True") { + status = '🟥 '; + } else if (running === "True") { + status = '🟨 '; + } else if ((ready === "True") && (cache_hit === "False")) { + status = '🟦 '; + } else if ((ready === "True") && (cache_hit === "True")) { + status = '🟩 '; + } else { + status = '⬜ '; + } + + return ( +
+ {status + label} +
+ ); + } + + + const renderCustomHandle = (position, type, index, label) => { + return ( + + ); + } + + const renderInputHandle = (data, index, editValue = false) => { + const label = data.target_labels[index] + const inp_type = data.target_types[index] + const literal_type = data.target_literal_types[index] + const value = data.target_values[index] + const [inputValue, setInputValue] = useState(value); + const context = React.useContext(UpdateDataContext); + // console.log('input type: ', data) + + const inputTypeMap = { + 'str': 'text', + 'int': 'text', + 'float': 'text', + 'bool': 'checkbox', + '_LiteralGenericAlias': 'dropdown' + }; + + const convertInput = (value, inp_type) => { + switch(inp_type) { + case 'int': + // Check if value can be converted to an integer + const intValue = parseInt(value, 10); + return isNaN(intValue) ? value : intValue; + case 'float': + // Check if value can be converted to a float + const floatValue = parseFloat(value); + return isNaN(floatValue) ? value : floatValue; + case 'bool': + return value; + default: + return value; // if inp_type === 'str' or anything else unexpected, returns the original string + } + } + + const currentInputType = inputTypeMap[inp_type] || 'text'; + + if (inp_type === 'NonPrimitive' || inp_type === 'None') { + editValue = false; + } + + const getBackgroundColor = (value, inp_type) => { + if (value === null) { + return 'grey'; + } else if (value === 'NotData') { + return '#FFD740' + } else { + return 'white'; + } + } + + return ( + <> +
+ {`${label}`} + {editValue && (currentInputType === 'dropdown' + ? ( + + ) : ( + { + const newValue = currentInputType === 'checkbox' ? e.target.checked : e.target.value; + console.log('onChange', value, e, inputValue, newValue, index, data.label); + // Always update the input value + setInputValue(newValue); + context(data.label, index, newValue); + }} + onKeyDown={e => { + if(e.keyCode === 13) { + // When Enter key is pressed, convert the input + const convertedValue = convertInput(inputValue, inp_type); + console.log('onKeyDown', value, e, inputValue, convertedValue, index, data.label); + context(data.label, index, convertedValue); + } + }} + onBlur={() => { + // When the mouse leaves the textbox, convert the input + const convertedValue = convertInput(inputValue, inp_type); + context(data.label, index, convertedValue); + }} + style={{ + width: '40px', + height: '10px', + fontSize: '6px', + backgroundColor: getBackgroundColor(value, inp_type) + }} + /> + ))} +
+ {renderCustomHandle('left', 'target', index, label)} + + ); + } + + const renderOutputHandle = (data, index) => { + const label = data.source_labels[index] + + return ( + <> +
+ {`${label}`} +
+ {renderCustomHandle('right', 'source', index, label)} + + ); + } + + const onChange = (evt) => { + setSimpleOption(evt.target.value); // without type assertions + }; + + return ( +
+ + {renderLabel(data.label, data.failed, data.running, data.ready, data.cache_hit)} + +
+ {handles.map((_, index) => ( +
+
+ {index < data.target_labels.length && + renderInputHandle(data, index, true)} +
+ +
+ {index < data.source_labels.length && + renderOutputHandle(data, index)} +
+
+ ))} +
+ + + + + + +
+ ); +}); diff --git a/js/LoopNodeExpanded.jsx b/js/LoopNodeExpanded.jsx new file mode 100644 index 00000000..372db869 --- /dev/null +++ b/js/LoopNodeExpanded.jsx @@ -0,0 +1,268 @@ +import React, { memo, useEffect, useState, useRef } from "react"; +import { Handle, useUpdateNodeInternals, NodeToolbar, useNodesState, Panel} from "@xyflow/react"; +import { useModel } from "@anywidget/react"; +import { UpdateDataContext } from './widget.jsx'; // import the context + +/** + * Author: Joerg Neugebauer + * Copyright: Copyright 2024, Max-Planck-Institut for Sustainable Materials GmbH - Computational Materials Design (CM) Department + * Version: 0.2 + * Maintainer: + * Email: + * Status: development + * Date: Aug 1, 2024 + */ + +export default memo(({ data, node_status, id, position }) => { + const updateNodeInternals = useUpdateNodeInternals(); +// const [nodes, setNodes, onNodesChange] = useNodesState([]); + + const num_handles = Math.max(data.source_labels.length, data.target_labels.length); + const [handles, setHandles] = useState(Array(num_handles).fill({})); + + const model = useModel(); + const context = React.useContext(UpdateDataContext); + + const edgesRef = useRef(data.edges); + +// console.log('nodes', nodes) + + + useEffect(() => { + handles.map((_, index) => { + updateNodeInternals(`handle-${index}`); + }); + }, [handles]); + + useEffect(() => { + edgesRef.current = data.edges; // Update the ref when the prop changes + }, [data.edges]); + +/* + useEffect(() => { + const interval = setInterval(() => { + console.log('Running logic every 0,2s', edgesRef.current); + console.log('only edges', data.edges); + const parents = [ + ...new Set( + edgesRef.current + .filter((edge) => edge.type == "macroSubEdge") + .map ((edge) => edge.parent) + ) + ]; + console.log('Parents: ', parents); + if (data.onMessage) { + if (parents.includes(id)) { + data.onMessage(id); + clearInterval(interval); + return; + } + else { + + }; + model.set("timestamp", Date.now()); + }; + + }, 200); + + return () => clearInterval(interval); + + }, []); + +*/ + + + + const pullFunction = () => { + // pull on the node + console.log('pull: ', data.label) + model.set("commands", `pull: ${data.label} - ${new Date().getTime()}`); + model.save_changes(); + } + + const pushFunction = () => { + // push from the node + console.log('push: ', data.label) + model.set("commands", `push: ${data.label} - ${new Date().getTime()}`); + model.save_changes(); + } + + // outputFunction and sourceFunction lifted to widget.jsx to be used by ContextMenu.jsx + + const resetFunction = () => { + // reset state and cache of node + console.log('reset: ', data.label) + model.set("commands", `reset: ${data.label}`); + model.save_changes(); + } + + const collapseFunction = () => { + // show source code of node + console.log('collapse ', data.label) + model.set("commands", `collapse: ${data.label}`); + model.save_changes(); + } + + const sortFunction = () => { + // reset state and cache of node + console.log('sort: ', data.label) + data.onSort(id); + } + + const buildViewFunction = () => { + // show source code of node + console.log('build_view ', data.label) + model.set("commands", `build_view: ${data.label}`); + model.save_changes(); + } + + const loopViewFunction = () => { + // show source code of node + console.log('loop_view ', data.label) + model.set("commands", `loop_view: ${data.label}`); + model.save_changes(); + } + + + + const renderLabel = (label, failed, running, ready, cache_hit) => { + let status = ''; + + if (failed === "True") { + status = '🟥 '; + } else if (running === "True") { + status = '🟨 '; + } else if ((ready === "True") && (cache_hit === "False")) { + status = '🟦 '; + } else if ((ready === "True") && (cache_hit === "True")) { + status = '🟩 '; + } else { + status = '⬜ '; + } + + return ( +
+ {status + label} +
+ ); + } + + + const renderCustomHandle = (position, type, index, label) => { + return ( + + ); + } + + const renderInputHandle = (data, index, editValue = false) => { + const label = data.target_labels[index] + const inp_type = data.target_types[index] + const literal_type = data.target_literal_types[index] + const value = data.target_values[index] + const [inputValue, setInputValue] = useState(value); + const context = React.useContext(UpdateDataContext); + // console.log('input type: ', data) + + const inputTypeMap = { + 'str': 'text', + 'int': 'text', + 'float': 'text', + 'bool': 'checkbox', + '_LiteralGenericAlias': 'dropdown' + }; + + const convertInput = (value, inp_type) => { + switch(inp_type) { + case 'int': + // Check if value can be converted to an integer + const intValue = parseInt(value, 10); + return isNaN(intValue) ? value : intValue; + case 'float': + // Check if value can be converted to a float + const floatValue = parseFloat(value); + return isNaN(floatValue) ? value : floatValue; + case 'bool': + return value; + default: + return value; // if inp_type === 'str' or anything else unexpected, returns the original string + } + } + + const currentInputType = inputTypeMap[inp_type] || 'text'; + + if (inp_type === 'NonPrimitive' || inp_type === 'None') { + editValue = false; + } + + const getBackgroundColor = (value, inp_type) => { + if (value === null) { + return 'grey'; + } else if (value === 'NotData') { + return '#FFD740' + } else { + return 'white'; + } + } + + return ( + <> + + {renderCustomHandle('left', 'target', index, label)} + + ); + } + + const renderOutputHandle = (data, index) => { + const label = data.source_labels[index] + + return ( + <> + {renderCustomHandle('right', 'source', index, label)} + + ); + } + + const onChange = (evt) => { + setSimpleOption(evt.target.value); // without type assertions + }; + + return ( +
+ + {renderLabel(data.label, data.failed, data.running, data.ready, data.cache_hit)} + +
+ {handles.map((_, index) => ( +
+
+ {index < data.target_labels.length && + renderInputHandle(data, index, true)} +
+ +
+ {index < data.source_labels.length && + renderOutputHandle(data, index)} +
+
+ ))} +
+ + + + + + + + +
+ ); +}); diff --git a/js/MacroInOutNode.jsx b/js/MacroInOutNode.jsx new file mode 100644 index 00000000..6305464d --- /dev/null +++ b/js/MacroInOutNode.jsx @@ -0,0 +1,179 @@ +import React, { memo, useEffect, useState } from "react"; +import { Handle, useUpdateNodeInternals, NodeToolbar, useNodesState, Panel} from "@xyflow/react"; +import { useModel } from "@anywidget/react"; +import { UpdateDataContext } from './widget.jsx'; // import the context + +/** + * Author: Joerg Neugebauer + * Copyright: Copyright 2024, Max-Planck-Institut for Sustainable Materials GmbH - Computational Materials Design (CM) Department + * Version: 0.2 + * Maintainer: + * Email: + * Status: development + * Date: Aug 1, 2024 + */ + +export default memo(({ data, node_status }) => { + const updateNodeInternals = useUpdateNodeInternals(); +// const [nodes, setNodes, onNodesChange] = useNodesState([]); + + const num_handles = Math.max(data.source_labels.length, data.target_labels.length); + const [handles, setHandles] = useState(Array(num_handles).fill({})); + + const model = useModel(); + const context = React.useContext(UpdateDataContext); + +// console.log('nodes', nodes) + + + useEffect(() => { + handles.map((_, index) => { + updateNodeInternals(`handle-${index}`); + }); + }, [handles]); + + const pullFunction = () => { + // pull on the node + console.log('pull: ', data.label) + model.set("commands", `pull: ${data.label} - ${new Date().getTime()}`); + model.save_changes(); + } + + const pushFunction = () => { + // push from the node + console.log('push: ', data.label) + model.set("commands", `push: ${data.label} - ${new Date().getTime()}`); + model.save_changes(); + } + + // outputFunction and sourceFunction lifted to widget.jsx to be used by ContextMenu.jsx + + const resetFunction = () => { + // reset state and cache of node + console.log('reset: ', data.label) + model.set("commands", `reset: ${data.label}`); + model.save_changes(); + } + + const renderLabel = (label, failed, running, ready, cache_hit) => { + return ( +
+ {label} +
+ ); + } + + + const renderCustomHandle = (position, type, index, label) => { + return ( + + ); + } + + const renderInputHandle = (data, index, editValue = false) => { + const label = data.target_labels[index] + const inp_type = data.target_types[index] + const literal_type = data.target_literal_types[index] + const value = data.target_values[index] + const [inputValue, setInputValue] = useState(value); + const context = React.useContext(UpdateDataContext); + // console.log('input type: ', data) + + const inputTypeMap = { + 'str': 'text', + 'int': 'text', + 'float': 'text', + 'bool': 'checkbox', + '_LiteralGenericAlias': 'dropdown' + }; + + const convertInput = (value, inp_type) => { + switch(inp_type) { + case 'int': + // Check if value can be converted to an integer + const intValue = parseInt(value, 10); + return isNaN(intValue) ? value : intValue; + case 'float': + // Check if value can be converted to a float + const floatValue = parseFloat(value); + return isNaN(floatValue) ? value : floatValue; + case 'bool': + return value; + default: + return value; // if inp_type === 'str' or anything else unexpected, returns the original string + } + } + + const currentInputType = inputTypeMap[inp_type] || 'text'; + + if (inp_type === 'NonPrimitive' || inp_type === 'None') { + editValue = false; + } + + const getBackgroundColor = (value, inp_type) => { + if (value === null) { + return 'grey'; + } else if (value === 'NotData') { + return '#FFD740' + } else { + return 'white'; + } + } + + return ( + <> +
+ {`${label}`} + +
+ {renderCustomHandle('left', 'target', index, label)} + + ); + } + + const renderOutputHandle = (data, index) => { + const label = data.source_labels[index] + + return ( + <> +
+ {`${label}`} +
+ {renderCustomHandle('right', 'source', index, label)} + + ); + } + + const onChange = (evt) => { + setSimpleOption(evt.target.value); // without type assertions + }; + + return ( +
+ + {renderLabel(data.label, data.failed, data.running, data.ready, data.cache_hit)} + +
+ {handles.map((_, index) => ( +
+
+ {index < data.target_labels.length && + renderInputHandle(data, index, true)} +
+ +
+ {index < data.source_labels.length && + renderOutputHandle(data, index)} +
+
+ ))} +
+
+ ); +}); diff --git a/js/MacroNode.jsx b/js/MacroNode.jsx new file mode 100644 index 00000000..ad651978 --- /dev/null +++ b/js/MacroNode.jsx @@ -0,0 +1,279 @@ +import React, { memo, useEffect, useState } from "react"; +import { Handle, useUpdateNodeInternals, NodeToolbar, useNodesState, Panel} from "@xyflow/react"; +import { useModel } from "@anywidget/react"; +import { UpdateDataContext } from './widget.jsx'; // import the context + +/** + * Author: Joerg Neugebauer + * Copyright: Copyright 2024, Max-Planck-Institut for Sustainable Materials GmbH - Computational Materials Design (CM) Department + * Version: 0.2 + * Maintainer: + * Email: + * Status: development + * Date: Aug 1, 2024 + */ + +export default memo(({ data, node_status }) => { + const updateNodeInternals = useUpdateNodeInternals(); +// const [nodes, setNodes, onNodesChange] = useNodesState([]); + + const num_handles = Math.max(data.source_labels.length, data.target_labels.length); + const [handles, setHandles] = useState(Array(num_handles).fill({})); + + const model = useModel(); + const context = React.useContext(UpdateDataContext); + +// console.log('nodes', nodes) + + + useEffect(() => { + handles.map((_, index) => { + updateNodeInternals(`handle-${index}`); + }); + }, [handles]); + + const pullFunction = () => { + // pull on the node + console.log('pull: ', data.label) + model.set("commands", `pull: ${data.label} - ${new Date().getTime()}`); + model.save_changes(); + } + + const pushFunction = () => { + // push from the node + console.log('push: ', data.label) + model.set("commands", `push: ${data.label} - ${new Date().getTime()}`); + model.save_changes(); + } + + // outputFunction and sourceFunction lifted to widget.jsx to be used by ContextMenu.jsx + + const resetFunction = () => { + // reset state and cache of node + console.log('reset: ', data.label) + model.set("commands", `reset: ${data.label}`); + model.save_changes(); + } + + const expandFunction = () => { + // show source code of node + console.log('expand ', data.label) + model.set("commands", `expand: ${data.label}`); + model.save_changes(); + } + + + + const renderLabel = (label, failed, running, ready, cache_hit) => { + let status = ''; + + if (failed === "True") { + status = '🟥 '; + } else if (running === "True") { + status = '🟨 '; + } else if ((ready === "True") && (cache_hit === "False")) { + status = '🟦 '; + } else if ((ready === "True") && (cache_hit === "True")) { + status = '🟩 '; + } else { + status = '⬜ '; + } + + return ( +
+ {status + label} +
+ ); + } + + + const renderCustomHandle = (position, type, index, label) => { + return ( + + ); + } + + const renderInputHandle = (data, index, editValue = false) => { + const label = data.target_labels[index] + const inp_type = data.target_types[index] + const literal_type = data.target_literal_types[index] + const value = data.target_values[index] + const [inputValue, setInputValue] = useState(value); + const context = React.useContext(UpdateDataContext); + // console.log('input type: ', data) + + const inputTypeMap = { + 'str': 'text', + 'int': 'text', + 'float': 'text', + 'bool': 'checkbox', + '_LiteralGenericAlias': 'dropdown' + }; + + const convertInput = (value, inp_type) => { + switch(inp_type) { + case 'int': + // Check if value can be converted to an integer + const intValue = parseInt(value, 10); + return isNaN(intValue) ? value : intValue; + case 'float': + // Check if value can be converted to a float + const floatValue = parseFloat(value); + return isNaN(floatValue) ? value : floatValue; + case 'bool': + return value; + default: + return value; // if inp_type === 'str' or anything else unexpected, returns the original string + } + } + + const currentInputType = inputTypeMap[inp_type] || 'text'; + + if (inp_type === 'NonPrimitive' || inp_type === 'None') { + editValue = false; + } + + const getBackgroundColor = (value, inp_type) => { + if (value === null) { + return 'grey'; + } else if (value === 'NotData') { + return '#FFD740' + } else { + return 'white'; + } + } + + return ( + <> +
+ {`${label}`} + {editValue && (currentInputType === 'dropdown' + ? ( + + ) : ( + { + const newValue = currentInputType === 'checkbox' ? e.target.checked : e.target.value; + console.log('onChange', value, e, inputValue, newValue, index, data.label); + // Always update the input value + setInputValue(newValue); + context(data.label, index, newValue); + }} + onKeyDown={e => { + if(e.keyCode === 13) { + // When Enter key is pressed, convert the input + const convertedValue = convertInput(inputValue, inp_type); + console.log('onKeyDown', value, e, inputValue, convertedValue, index, data.label); + context(data.label, index, convertedValue); + } + }} + onBlur={() => { + // When the mouse leaves the textbox, convert the input + const convertedValue = convertInput(inputValue, inp_type); + context(data.label, index, convertedValue); + }} + style={{ + width: '40px', + height: '10px', + fontSize: '6px', + backgroundColor: getBackgroundColor(value, inp_type) + }} + /> + ))} +
+ {renderCustomHandle('left', 'target', index, label)} + + ); + } + + const renderOutputHandle = (data, index) => { + const label = data.source_labels[index] + + return ( + <> +
+ {`${label}`} +
+ {renderCustomHandle('right', 'source', index, label)} + + ); + } + + const onChange = (evt) => { + setSimpleOption(evt.target.value); // without type assertions + }; + + return ( +
+ + {renderLabel(data.label, data.failed, data.running, data.ready, data.cache_hit)} + +
+ {handles.map((_, index) => ( +
+
+ {index < data.target_labels.length && + renderInputHandle(data, index, true)} +
+ +
+ {index < data.source_labels.length && + renderOutputHandle(data, index)} +
+
+ ))} +
+ + + + + + +
+ ); +}); diff --git a/js/MacroNodeExpanded.jsx b/js/MacroNodeExpanded.jsx new file mode 100644 index 00000000..b71e022a --- /dev/null +++ b/js/MacroNodeExpanded.jsx @@ -0,0 +1,324 @@ +import React, { memo, useEffect, useState, useRef } from "react"; +import { Handle, useUpdateNodeInternals, NodeToolbar, useNodesState, Panel} from "@xyflow/react"; +import { useModel } from "@anywidget/react"; +import { UpdateDataContext } from './widget.jsx'; // import the context + +/** + * Author: Joerg Neugebauer + * Copyright: Copyright 2024, Max-Planck-Institut for Sustainable Materials GmbH - Computational Materials Design (CM) Department + * Version: 0.2 + * Maintainer: + * Email: + * Status: development + * Date: Aug 1, 2024 + */ + +export default memo(({ data, node_status, id, position }) => { + const updateNodeInternals = useUpdateNodeInternals(); +// const [nodes, setNodes, onNodesChange] = useNodesState([]); + + const num_handles = Math.max(data.source_labels.length, data.target_labels.length); + const [handles, setHandles] = useState(Array(num_handles).fill({})); + + const model = useModel(); + const context = React.useContext(UpdateDataContext); + + const edgesRef = useRef(data.edges); + +// console.log('nodes', nodes) + + + useEffect(() => { + handles.map((_, index) => { + updateNodeInternals(`handle-${index}`); + }); + }, [handles]); + + useEffect(() => { + edgesRef.current = data.edges; // Update the ref when the prop changes + }, [data.edges]); + + useEffect(() => { + const interval = setInterval(() => { + console.log('Running logic every 0,2s', edgesRef.current); + console.log('only edges', data.edges); + const parents = [ + ...new Set( + edgesRef.current + .filter((edge) => edge.type == "macroSubEdge") + .map ((edge) => edge.parent) + ) + ]; + console.log('Parents: ', parents); + if (data.onMessage) { + if (parents.includes(id)) { + data.onMessage(id); + clearInterval(interval); + return; + } + else { + + }; + model.set("timestamp", Date.now()); + }; + + }, 200); + + return () => clearInterval(interval); + + }, []); + + + + + + const pullFunction = () => { + // pull on the node + console.log('pull: ', data.label) + model.set("commands", `pull: ${data.label} - ${new Date().getTime()}`); + model.save_changes(); + } + + const pushFunction = () => { + // push from the node + console.log('push: ', data.label) + model.set("commands", `push: ${data.label} - ${new Date().getTime()}`); + model.save_changes(); + } + + // outputFunction and sourceFunction lifted to widget.jsx to be used by ContextMenu.jsx + + const resetFunction = () => { + // reset state and cache of node + console.log('reset: ', data.label) + model.set("commands", `reset: ${data.label}`); + model.save_changes(); + } + + const collapseFunction = () => { + // show source code of node + console.log('collapse ', data.label) + model.set("commands", `collapse: ${data.label}`); + model.save_changes(); + } + + const sortFunction = () => { + // reset state and cache of node + console.log('sort: ', data.label) + data.onSort(id); + } + + + + + + const renderLabel = (label, failed, running, ready, cache_hit) => { + let status = ''; + + if (failed === "True") { + status = '🟥 '; + } else if (running === "True") { + status = '🟨 '; + } else if ((ready === "True") && (cache_hit === "False")) { + status = '🟦 '; + } else if ((ready === "True") && (cache_hit === "True")) { + status = '🟩 '; + } else { + status = '⬜ '; + } + + return ( +
+ {status + label} +
+ ); + } + + + const renderCustomHandle = (position, type, index, label) => { + return ( + + ); + } + + const renderInputHandle = (data, index, editValue = false) => { + const label = data.target_labels[index] + const inp_type = data.target_types[index] + const literal_type = data.target_literal_types[index] + const value = data.target_values[index] + const [inputValue, setInputValue] = useState(value); + const context = React.useContext(UpdateDataContext); + // console.log('input type: ', data) + + const inputTypeMap = { + 'str': 'text', + 'int': 'text', + 'float': 'text', + 'bool': 'checkbox', + '_LiteralGenericAlias': 'dropdown' + }; + + const convertInput = (value, inp_type) => { + switch(inp_type) { + case 'int': + // Check if value can be converted to an integer + const intValue = parseInt(value, 10); + return isNaN(intValue) ? value : intValue; + case 'float': + // Check if value can be converted to a float + const floatValue = parseFloat(value); + return isNaN(floatValue) ? value : floatValue; + case 'bool': + return value; + default: + return value; // if inp_type === 'str' or anything else unexpected, returns the original string + } + } + + const currentInputType = inputTypeMap[inp_type] || 'text'; + + if (inp_type === 'NonPrimitive' || inp_type === 'None') { + editValue = false; + } + + const getBackgroundColor = (value, inp_type) => { + if (value === null) { + return 'grey'; + } else if (value === 'NotData') { + return '#FFD740' + } else { + return 'white'; + } + } + + return ( + <> +
+ {`${label}`} + {editValue && (currentInputType === 'dropdown' + ? ( + + ) : ( + { + const newValue = currentInputType === 'checkbox' ? e.target.checked : e.target.value; + console.log('onChange', value, e, inputValue, newValue, index, data.label); + // Always update the input value + setInputValue(newValue); + context(data.label, index, newValue); + }} + onKeyDown={e => { + if(e.keyCode === 13) { + // When Enter key is pressed, convert the input + const convertedValue = convertInput(inputValue, inp_type); + console.log('onKeyDown', value, e, inputValue, convertedValue, index, data.label); + context(data.label, index, convertedValue); + } + }} + onBlur={() => { + // When the mouse leaves the textbox, convert the input + const convertedValue = convertInput(inputValue, inp_type); + context(data.label, index, convertedValue); + }} + style={{ + width: '40px', + height: '10px', + fontSize: '6px', + backgroundColor: getBackgroundColor(value, inp_type) + }} + /> + ))} +
+ {renderCustomHandle('left', 'target', index, label)} + + ); + } + + const renderOutputHandle = (data, index) => { + const label = data.source_labels[index] + + return ( + <> + {renderCustomHandle('right', 'source', index, label)} + + ); + } + + const onChange = (evt) => { + setSimpleOption(evt.target.value); // without type assertions + }; + + return ( +
+ + {renderLabel(data.label, data.failed, data.running, data.ready, data.cache_hit)} + +
+ {handles.map((_, index) => ( +
+
+ {index < data.target_labels.length && + renderInputHandle(data, index, true)} +
+ +
+ {index < data.source_labels.length && + renderOutputHandle(data, index)} +
+
+ ))} +
+ + + + + + +
+ ); +}); diff --git a/js/OutNode.jsx b/js/OutNode.jsx new file mode 100644 index 00000000..6305464d --- /dev/null +++ b/js/OutNode.jsx @@ -0,0 +1,179 @@ +import React, { memo, useEffect, useState } from "react"; +import { Handle, useUpdateNodeInternals, NodeToolbar, useNodesState, Panel} from "@xyflow/react"; +import { useModel } from "@anywidget/react"; +import { UpdateDataContext } from './widget.jsx'; // import the context + +/** + * Author: Joerg Neugebauer + * Copyright: Copyright 2024, Max-Planck-Institut for Sustainable Materials GmbH - Computational Materials Design (CM) Department + * Version: 0.2 + * Maintainer: + * Email: + * Status: development + * Date: Aug 1, 2024 + */ + +export default memo(({ data, node_status }) => { + const updateNodeInternals = useUpdateNodeInternals(); +// const [nodes, setNodes, onNodesChange] = useNodesState([]); + + const num_handles = Math.max(data.source_labels.length, data.target_labels.length); + const [handles, setHandles] = useState(Array(num_handles).fill({})); + + const model = useModel(); + const context = React.useContext(UpdateDataContext); + +// console.log('nodes', nodes) + + + useEffect(() => { + handles.map((_, index) => { + updateNodeInternals(`handle-${index}`); + }); + }, [handles]); + + const pullFunction = () => { + // pull on the node + console.log('pull: ', data.label) + model.set("commands", `pull: ${data.label} - ${new Date().getTime()}`); + model.save_changes(); + } + + const pushFunction = () => { + // push from the node + console.log('push: ', data.label) + model.set("commands", `push: ${data.label} - ${new Date().getTime()}`); + model.save_changes(); + } + + // outputFunction and sourceFunction lifted to widget.jsx to be used by ContextMenu.jsx + + const resetFunction = () => { + // reset state and cache of node + console.log('reset: ', data.label) + model.set("commands", `reset: ${data.label}`); + model.save_changes(); + } + + const renderLabel = (label, failed, running, ready, cache_hit) => { + return ( +
+ {label} +
+ ); + } + + + const renderCustomHandle = (position, type, index, label) => { + return ( + + ); + } + + const renderInputHandle = (data, index, editValue = false) => { + const label = data.target_labels[index] + const inp_type = data.target_types[index] + const literal_type = data.target_literal_types[index] + const value = data.target_values[index] + const [inputValue, setInputValue] = useState(value); + const context = React.useContext(UpdateDataContext); + // console.log('input type: ', data) + + const inputTypeMap = { + 'str': 'text', + 'int': 'text', + 'float': 'text', + 'bool': 'checkbox', + '_LiteralGenericAlias': 'dropdown' + }; + + const convertInput = (value, inp_type) => { + switch(inp_type) { + case 'int': + // Check if value can be converted to an integer + const intValue = parseInt(value, 10); + return isNaN(intValue) ? value : intValue; + case 'float': + // Check if value can be converted to a float + const floatValue = parseFloat(value); + return isNaN(floatValue) ? value : floatValue; + case 'bool': + return value; + default: + return value; // if inp_type === 'str' or anything else unexpected, returns the original string + } + } + + const currentInputType = inputTypeMap[inp_type] || 'text'; + + if (inp_type === 'NonPrimitive' || inp_type === 'None') { + editValue = false; + } + + const getBackgroundColor = (value, inp_type) => { + if (value === null) { + return 'grey'; + } else if (value === 'NotData') { + return '#FFD740' + } else { + return 'white'; + } + } + + return ( + <> +
+ {`${label}`} + +
+ {renderCustomHandle('left', 'target', index, label)} + + ); + } + + const renderOutputHandle = (data, index) => { + const label = data.source_labels[index] + + return ( + <> +
+ {`${label}`} +
+ {renderCustomHandle('right', 'source', index, label)} + + ); + } + + const onChange = (evt) => { + setSimpleOption(evt.target.value); // without type assertions + }; + + return ( +
+ + {renderLabel(data.label, data.failed, data.running, data.ready, data.cache_hit)} + +
+ {handles.map((_, index) => ( +
+
+ {index < data.target_labels.length && + renderInputHandle(data, index, true)} +
+ +
+ {index < data.source_labels.length && + renderOutputHandle(data, index)} +
+
+ ))} +
+
+ ); +}); diff --git a/js/ReverseSubNode.jsx b/js/ReverseSubNode.jsx new file mode 100644 index 00000000..cd0c78ee --- /dev/null +++ b/js/ReverseSubNode.jsx @@ -0,0 +1,193 @@ +import React, { memo, useEffect, useState } from "react"; +import { Handle, useUpdateNodeInternals, NodeToolbar, useNodesState, Panel} from "@xyflow/react"; +import { useModel } from "@anywidget/react"; +import { UpdateDataContext } from './widget.jsx'; // import the context + +/** + * Author: Joerg Neugebauer + * Copyright: Copyright 2024, Max-Planck-Institut for Sustainable Materials GmbH - Computational Materials Design (CM) Department + * Version: 0.2 + * Maintainer: + * Email: + * Status: development + * Date: Aug 1, 2024 + */ + +export default memo(({ data, node_status }) => { + const updateNodeInternals = useUpdateNodeInternals(); +// const [nodes, setNodes, onNodesChange] = useNodesState([]); + + const num_handles = Math.max(data.source_labels.length, data.target_labels.length); + const [handles, setHandles] = useState(Array(num_handles).fill({})); + + const model = useModel(); + const context = React.useContext(UpdateDataContext); + +// console.log('nodes', nodes) + + + useEffect(() => { + handles.map((_, index) => { + updateNodeInternals(`handle-${index}`); + }); + }, [handles]); + + const pullFunction = () => { + // pull on the node + console.log('pull: ', data.label) + model.set("commands", `pull: ${data.label} - ${new Date().getTime()}`); + model.save_changes(); + } + + const pushFunction = () => { + // push from the node + console.log('push: ', data.label) + model.set("commands", `push: ${data.label} - ${new Date().getTime()}`); + model.save_changes(); + } + + // outputFunction and sourceFunction lifted to widget.jsx to be used by ContextMenu.jsx + + const resetFunction = () => { + // reset state and cache of node + console.log('reset: ', data.label) + model.set("commands", `reset: ${data.label}`); + model.save_changes(); + } + + const renderLabel = (label, failed, running, ready, cache_hit) => { + let status = ''; + + if (failed === "True") { + status = '🟥 '; + } else if (running === "True") { + status = '🟨 '; + } else if ((ready === "True") && (cache_hit === "False")) { + status = '🟦 '; + } else if ((ready === "True") && (cache_hit === "True")) { + status = '🟩 '; + } else { + status = '⬜ '; + } + + return ( +
+ {status + label} +
+ ); + } + + + const renderCustomHandle = (position, type, index, label) => { + return ( + + ); + } + + const renderInputHandle = (data, index, editValue = false) => { + const label = data.target_labels[index] + const inp_type = data.target_types[index] + const literal_type = data.target_literal_types[index] + const value = data.target_values[index] + const [inputValue, setInputValue] = useState(value); + const context = React.useContext(UpdateDataContext); + // console.log('input type: ', data) + + const inputTypeMap = { + 'str': 'text', + 'int': 'text', + 'float': 'text', + 'bool': 'checkbox', + '_LiteralGenericAlias': 'dropdown' + }; + + const convertInput = (value, inp_type) => { + switch(inp_type) { + case 'int': + // Check if value can be converted to an integer + const intValue = parseInt(value, 10); + return isNaN(intValue) ? value : intValue; + case 'float': + // Check if value can be converted to a float + const floatValue = parseFloat(value); + return isNaN(floatValue) ? value : floatValue; + case 'bool': + return value; + default: + return value; // if inp_type === 'str' or anything else unexpected, returns the original string + } + } + + const currentInputType = inputTypeMap[inp_type] || 'text'; + + if (inp_type === 'NonPrimitive' || inp_type === 'None') { + editValue = false; + } + + const getBackgroundColor = (value, inp_type) => { + if (value === null) { + return 'grey'; + } else if (value === 'NotData') { + return '#FFD740' + } else { + return 'white'; + } + } + + return ( + <> +
+ {`${label}`} + +
+ {renderCustomHandle('left', 'source', index, label)} + + ); + } + + const renderOutputHandle = (data, index) => { + const label = data.source_labels[index] + + return ( + <> +
+ {`${label}`} +
+ {renderCustomHandle('right', 'target', index, label)} + + ); + } + + const onChange = (evt) => { + setSimpleOption(evt.target.value); // without type assertions + }; + + return ( +
+ + {renderLabel(data.label, data.failed, data.running, data.ready, data.cache_hit)} + +
+ {handles.map((_, index) => ( +
+
+ {index < data.target_labels.length && + renderInputHandle(data, index, true)} +
+ +
+ {index < data.source_labels.length && + renderOutputHandle(data, index)} +
+
+ ))} +
+
+ ); +}); diff --git a/js/SubNode.jsx b/js/SubNode.jsx new file mode 100644 index 00000000..f03d182c --- /dev/null +++ b/js/SubNode.jsx @@ -0,0 +1,193 @@ +import React, { memo, useEffect, useState } from "react"; +import { Handle, useUpdateNodeInternals, NodeToolbar, useNodesState, Panel} from "@xyflow/react"; +import { useModel } from "@anywidget/react"; +import { UpdateDataContext } from './widget.jsx'; // import the context + +/** + * Author: Joerg Neugebauer + * Copyright: Copyright 2024, Max-Planck-Institut for Sustainable Materials GmbH - Computational Materials Design (CM) Department + * Version: 0.2 + * Maintainer: + * Email: + * Status: development + * Date: Aug 1, 2024 + */ + +export default memo(({ data, node_status }) => { + const updateNodeInternals = useUpdateNodeInternals(); +// const [nodes, setNodes, onNodesChange] = useNodesState([]); + + const num_handles = Math.max(data.source_labels.length, data.target_labels.length); + const [handles, setHandles] = useState(Array(num_handles).fill({})); + + const model = useModel(); + const context = React.useContext(UpdateDataContext); + +// console.log('nodes', nodes) + + + useEffect(() => { + handles.map((_, index) => { + updateNodeInternals(`handle-${index}`); + }); + }, [handles]); + + const pullFunction = () => { + // pull on the node + console.log('pull: ', data.label) + model.set("commands", `pull: ${data.label} - ${new Date().getTime()}`); + model.save_changes(); + } + + const pushFunction = () => { + // push from the node + console.log('push: ', data.label) + model.set("commands", `push: ${data.label} - ${new Date().getTime()}`); + model.save_changes(); + } + + // outputFunction and sourceFunction lifted to widget.jsx to be used by ContextMenu.jsx + + const resetFunction = () => { + // reset state and cache of node + console.log('reset: ', data.label) + model.set("commands", `reset: ${data.label}`); + model.save_changes(); + } + + const renderLabel = (label, failed, running, ready, cache_hit) => { + let status = ''; + + if (failed === "True") { + status = '🟥 '; + } else if (running === "True") { + status = '🟨 '; + } else if ((ready === "True") && (cache_hit === "False")) { + status = '🟦 '; + } else if ((ready === "True") && (cache_hit === "True")) { + status = '🟩 '; + } else { + status = '⬜ '; + } + + return ( +
+ {status + label} +
+ ); + } + + + const renderCustomHandle = (position, type, index, label) => { + return ( + + ); + } + + const renderInputHandle = (data, index, editValue = false) => { + const label = data.target_labels[index] + const inp_type = data.target_types[index] + const literal_type = data.target_literal_types[index] + const value = data.target_values[index] + const [inputValue, setInputValue] = useState(value); + const context = React.useContext(UpdateDataContext); + // console.log('input type: ', data) + + const inputTypeMap = { + 'str': 'text', + 'int': 'text', + 'float': 'text', + 'bool': 'checkbox', + '_LiteralGenericAlias': 'dropdown' + }; + + const convertInput = (value, inp_type) => { + switch(inp_type) { + case 'int': + // Check if value can be converted to an integer + const intValue = parseInt(value, 10); + return isNaN(intValue) ? value : intValue; + case 'float': + // Check if value can be converted to a float + const floatValue = parseFloat(value); + return isNaN(floatValue) ? value : floatValue; + case 'bool': + return value; + default: + return value; // if inp_type === 'str' or anything else unexpected, returns the original string + } + } + + const currentInputType = inputTypeMap[inp_type] || 'text'; + + if (inp_type === 'NonPrimitive' || inp_type === 'None') { + editValue = false; + } + + const getBackgroundColor = (value, inp_type) => { + if (value === null) { + return 'grey'; + } else if (value === 'NotData') { + return '#FFD740' + } else { + return 'white'; + } + } + + return ( + <> +
+ {`${label}`} + +
+ {renderCustomHandle('left', 'target', index, label)} + + ); + } + + const renderOutputHandle = (data, index) => { + const label = data.source_labels[index] + + return ( + <> +
+ {`${label}`} +
+ {renderCustomHandle('right', 'source', index, label)} + + ); + } + + const onChange = (evt) => { + setSimpleOption(evt.target.value); // without type assertions + }; + + return ( +
+ + {renderLabel(data.label, data.failed, data.running, data.ready, data.cache_hit)} + +
+ {handles.map((_, index) => ( +
+
+ {index < data.target_labels.length && + renderInputHandle(data, index, true)} +
+ +
+ {index < data.source_labels.length && + renderOutputHandle(data, index)} +
+
+ ))} +
+
+ ); +}); diff --git a/js/TempMacroNode.jsx b/js/TempMacroNode.jsx new file mode 100644 index 00000000..ad651978 --- /dev/null +++ b/js/TempMacroNode.jsx @@ -0,0 +1,279 @@ +import React, { memo, useEffect, useState } from "react"; +import { Handle, useUpdateNodeInternals, NodeToolbar, useNodesState, Panel} from "@xyflow/react"; +import { useModel } from "@anywidget/react"; +import { UpdateDataContext } from './widget.jsx'; // import the context + +/** + * Author: Joerg Neugebauer + * Copyright: Copyright 2024, Max-Planck-Institut for Sustainable Materials GmbH - Computational Materials Design (CM) Department + * Version: 0.2 + * Maintainer: + * Email: + * Status: development + * Date: Aug 1, 2024 + */ + +export default memo(({ data, node_status }) => { + const updateNodeInternals = useUpdateNodeInternals(); +// const [nodes, setNodes, onNodesChange] = useNodesState([]); + + const num_handles = Math.max(data.source_labels.length, data.target_labels.length); + const [handles, setHandles] = useState(Array(num_handles).fill({})); + + const model = useModel(); + const context = React.useContext(UpdateDataContext); + +// console.log('nodes', nodes) + + + useEffect(() => { + handles.map((_, index) => { + updateNodeInternals(`handle-${index}`); + }); + }, [handles]); + + const pullFunction = () => { + // pull on the node + console.log('pull: ', data.label) + model.set("commands", `pull: ${data.label} - ${new Date().getTime()}`); + model.save_changes(); + } + + const pushFunction = () => { + // push from the node + console.log('push: ', data.label) + model.set("commands", `push: ${data.label} - ${new Date().getTime()}`); + model.save_changes(); + } + + // outputFunction and sourceFunction lifted to widget.jsx to be used by ContextMenu.jsx + + const resetFunction = () => { + // reset state and cache of node + console.log('reset: ', data.label) + model.set("commands", `reset: ${data.label}`); + model.save_changes(); + } + + const expandFunction = () => { + // show source code of node + console.log('expand ', data.label) + model.set("commands", `expand: ${data.label}`); + model.save_changes(); + } + + + + const renderLabel = (label, failed, running, ready, cache_hit) => { + let status = ''; + + if (failed === "True") { + status = '🟥 '; + } else if (running === "True") { + status = '🟨 '; + } else if ((ready === "True") && (cache_hit === "False")) { + status = '🟦 '; + } else if ((ready === "True") && (cache_hit === "True")) { + status = '🟩 '; + } else { + status = '⬜ '; + } + + return ( +
+ {status + label} +
+ ); + } + + + const renderCustomHandle = (position, type, index, label) => { + return ( + + ); + } + + const renderInputHandle = (data, index, editValue = false) => { + const label = data.target_labels[index] + const inp_type = data.target_types[index] + const literal_type = data.target_literal_types[index] + const value = data.target_values[index] + const [inputValue, setInputValue] = useState(value); + const context = React.useContext(UpdateDataContext); + // console.log('input type: ', data) + + const inputTypeMap = { + 'str': 'text', + 'int': 'text', + 'float': 'text', + 'bool': 'checkbox', + '_LiteralGenericAlias': 'dropdown' + }; + + const convertInput = (value, inp_type) => { + switch(inp_type) { + case 'int': + // Check if value can be converted to an integer + const intValue = parseInt(value, 10); + return isNaN(intValue) ? value : intValue; + case 'float': + // Check if value can be converted to a float + const floatValue = parseFloat(value); + return isNaN(floatValue) ? value : floatValue; + case 'bool': + return value; + default: + return value; // if inp_type === 'str' or anything else unexpected, returns the original string + } + } + + const currentInputType = inputTypeMap[inp_type] || 'text'; + + if (inp_type === 'NonPrimitive' || inp_type === 'None') { + editValue = false; + } + + const getBackgroundColor = (value, inp_type) => { + if (value === null) { + return 'grey'; + } else if (value === 'NotData') { + return '#FFD740' + } else { + return 'white'; + } + } + + return ( + <> +
+ {`${label}`} + {editValue && (currentInputType === 'dropdown' + ? ( + + ) : ( + { + const newValue = currentInputType === 'checkbox' ? e.target.checked : e.target.value; + console.log('onChange', value, e, inputValue, newValue, index, data.label); + // Always update the input value + setInputValue(newValue); + context(data.label, index, newValue); + }} + onKeyDown={e => { + if(e.keyCode === 13) { + // When Enter key is pressed, convert the input + const convertedValue = convertInput(inputValue, inp_type); + console.log('onKeyDown', value, e, inputValue, convertedValue, index, data.label); + context(data.label, index, convertedValue); + } + }} + onBlur={() => { + // When the mouse leaves the textbox, convert the input + const convertedValue = convertInput(inputValue, inp_type); + context(data.label, index, convertedValue); + }} + style={{ + width: '40px', + height: '10px', + fontSize: '6px', + backgroundColor: getBackgroundColor(value, inp_type) + }} + /> + ))} +
+ {renderCustomHandle('left', 'target', index, label)} + + ); + } + + const renderOutputHandle = (data, index) => { + const label = data.source_labels[index] + + return ( + <> +
+ {`${label}`} +
+ {renderCustomHandle('right', 'source', index, label)} + + ); + } + + const onChange = (evt) => { + setSimpleOption(evt.target.value); // without type assertions + }; + + return ( +
+ + {renderLabel(data.label, data.failed, data.running, data.ready, data.cache_hit)} + +
+ {handles.map((_, index) => ( +
+
+ {index < data.target_labels.length && + renderInputHandle(data, index, true)} +
+ +
+ {index < data.source_labels.length && + renderOutputHandle(data, index)} +
+
+ ))} +
+ + + + + + +
+ ); +}); diff --git a/js/TempMacroNodeExpanded.jsx b/js/TempMacroNodeExpanded.jsx new file mode 100644 index 00000000..b71e022a --- /dev/null +++ b/js/TempMacroNodeExpanded.jsx @@ -0,0 +1,324 @@ +import React, { memo, useEffect, useState, useRef } from "react"; +import { Handle, useUpdateNodeInternals, NodeToolbar, useNodesState, Panel} from "@xyflow/react"; +import { useModel } from "@anywidget/react"; +import { UpdateDataContext } from './widget.jsx'; // import the context + +/** + * Author: Joerg Neugebauer + * Copyright: Copyright 2024, Max-Planck-Institut for Sustainable Materials GmbH - Computational Materials Design (CM) Department + * Version: 0.2 + * Maintainer: + * Email: + * Status: development + * Date: Aug 1, 2024 + */ + +export default memo(({ data, node_status, id, position }) => { + const updateNodeInternals = useUpdateNodeInternals(); +// const [nodes, setNodes, onNodesChange] = useNodesState([]); + + const num_handles = Math.max(data.source_labels.length, data.target_labels.length); + const [handles, setHandles] = useState(Array(num_handles).fill({})); + + const model = useModel(); + const context = React.useContext(UpdateDataContext); + + const edgesRef = useRef(data.edges); + +// console.log('nodes', nodes) + + + useEffect(() => { + handles.map((_, index) => { + updateNodeInternals(`handle-${index}`); + }); + }, [handles]); + + useEffect(() => { + edgesRef.current = data.edges; // Update the ref when the prop changes + }, [data.edges]); + + useEffect(() => { + const interval = setInterval(() => { + console.log('Running logic every 0,2s', edgesRef.current); + console.log('only edges', data.edges); + const parents = [ + ...new Set( + edgesRef.current + .filter((edge) => edge.type == "macroSubEdge") + .map ((edge) => edge.parent) + ) + ]; + console.log('Parents: ', parents); + if (data.onMessage) { + if (parents.includes(id)) { + data.onMessage(id); + clearInterval(interval); + return; + } + else { + + }; + model.set("timestamp", Date.now()); + }; + + }, 200); + + return () => clearInterval(interval); + + }, []); + + + + + + const pullFunction = () => { + // pull on the node + console.log('pull: ', data.label) + model.set("commands", `pull: ${data.label} - ${new Date().getTime()}`); + model.save_changes(); + } + + const pushFunction = () => { + // push from the node + console.log('push: ', data.label) + model.set("commands", `push: ${data.label} - ${new Date().getTime()}`); + model.save_changes(); + } + + // outputFunction and sourceFunction lifted to widget.jsx to be used by ContextMenu.jsx + + const resetFunction = () => { + // reset state and cache of node + console.log('reset: ', data.label) + model.set("commands", `reset: ${data.label}`); + model.save_changes(); + } + + const collapseFunction = () => { + // show source code of node + console.log('collapse ', data.label) + model.set("commands", `collapse: ${data.label}`); + model.save_changes(); + } + + const sortFunction = () => { + // reset state and cache of node + console.log('sort: ', data.label) + data.onSort(id); + } + + + + + + const renderLabel = (label, failed, running, ready, cache_hit) => { + let status = ''; + + if (failed === "True") { + status = '🟥 '; + } else if (running === "True") { + status = '🟨 '; + } else if ((ready === "True") && (cache_hit === "False")) { + status = '🟦 '; + } else if ((ready === "True") && (cache_hit === "True")) { + status = '🟩 '; + } else { + status = '⬜ '; + } + + return ( +
+ {status + label} +
+ ); + } + + + const renderCustomHandle = (position, type, index, label) => { + return ( + + ); + } + + const renderInputHandle = (data, index, editValue = false) => { + const label = data.target_labels[index] + const inp_type = data.target_types[index] + const literal_type = data.target_literal_types[index] + const value = data.target_values[index] + const [inputValue, setInputValue] = useState(value); + const context = React.useContext(UpdateDataContext); + // console.log('input type: ', data) + + const inputTypeMap = { + 'str': 'text', + 'int': 'text', + 'float': 'text', + 'bool': 'checkbox', + '_LiteralGenericAlias': 'dropdown' + }; + + const convertInput = (value, inp_type) => { + switch(inp_type) { + case 'int': + // Check if value can be converted to an integer + const intValue = parseInt(value, 10); + return isNaN(intValue) ? value : intValue; + case 'float': + // Check if value can be converted to a float + const floatValue = parseFloat(value); + return isNaN(floatValue) ? value : floatValue; + case 'bool': + return value; + default: + return value; // if inp_type === 'str' or anything else unexpected, returns the original string + } + } + + const currentInputType = inputTypeMap[inp_type] || 'text'; + + if (inp_type === 'NonPrimitive' || inp_type === 'None') { + editValue = false; + } + + const getBackgroundColor = (value, inp_type) => { + if (value === null) { + return 'grey'; + } else if (value === 'NotData') { + return '#FFD740' + } else { + return 'white'; + } + } + + return ( + <> +
+ {`${label}`} + {editValue && (currentInputType === 'dropdown' + ? ( + + ) : ( + { + const newValue = currentInputType === 'checkbox' ? e.target.checked : e.target.value; + console.log('onChange', value, e, inputValue, newValue, index, data.label); + // Always update the input value + setInputValue(newValue); + context(data.label, index, newValue); + }} + onKeyDown={e => { + if(e.keyCode === 13) { + // When Enter key is pressed, convert the input + const convertedValue = convertInput(inputValue, inp_type); + console.log('onKeyDown', value, e, inputValue, convertedValue, index, data.label); + context(data.label, index, convertedValue); + } + }} + onBlur={() => { + // When the mouse leaves the textbox, convert the input + const convertedValue = convertInput(inputValue, inp_type); + context(data.label, index, convertedValue); + }} + style={{ + width: '40px', + height: '10px', + fontSize: '6px', + backgroundColor: getBackgroundColor(value, inp_type) + }} + /> + ))} +
+ {renderCustomHandle('left', 'target', index, label)} + + ); + } + + const renderOutputHandle = (data, index) => { + const label = data.source_labels[index] + + return ( + <> + {renderCustomHandle('right', 'source', index, label)} + + ); + } + + const onChange = (evt) => { + setSimpleOption(evt.target.value); // without type assertions + }; + + return ( +
+ + {renderLabel(data.label, data.failed, data.running, data.ready, data.cache_hit)} + +
+ {handles.map((_, index) => ( +
+
+ {index < data.target_labels.length && + renderInputHandle(data, index, true)} +
+ +
+ {index < data.source_labels.length && + renderOutputHandle(data, index)} +
+
+ ))} +
+ + + + + + +
+ ); +}); diff --git a/js/useElkLayout.jsx b/js/useElkLayout.jsx index 345bb9ff..0b85ad01 100644 --- a/js/useElkLayout.jsx +++ b/js/useElkLayout.jsx @@ -1,112 +1,112 @@ -import { useEffect } from 'react'; -import ELK from 'elkjs/lib/elk.bundled.js'; -import { useNodesInitialized, useReactFlow } from '@xyflow/react'; -// import { saveAs } from 'file-saver'; - - -// elk layouting options can be found here: -// https://www.eclipse.org/elk/reference/algorithms/org-eclipse-elk-layered.html - - -// uses elkjs to give each node a layouted position -export const getLayoutedNodes2 = async (nodes, edges) => { - const layoutOptions = { - 'elk.algorithm': 'layered', - 'elk.direction': 'RIGHT', - 'elk.layered.spacing.edgeNodeBetweenLayers': '40', - 'elk.spacing.nodeNode': '40', - 'elk.layered.nodePlacement.strategy': 'SIMPLE', - }; - - console.log("nodes layout: ", nodes); - console.log("edges layout: ", edges); - const elk = new ELK(); - - const graph = { - id: 'root', - layoutOptions, - children: nodes.map((n) => { - const targetPorts = n.data.target_labels.map((label) => ({ - id: `${n.id}_in_${label}`, - - // ⚠️ it's important to let elk know on which side the port is - // in this example targets are on the left (WEST) and sources on the right (EAST) - properties: { - side: 'WEST', - }, - })); - - const sourcePorts = n.data.source_labels.map((label) => ({ - id: `${n.id}_out_${label}`, - properties: { - side: 'EAST', - }, - })); - - return { - id: n.id, - width: n.style.width_unitless ?? 240, - height: n.style.height_unitless ?? 100, - // ⚠️ we need to tell elk that the ports are fixed, in order to reduce edge crossings - properties: { - 'org.eclipse.elk.portConstraints': 'FIXED_ORDER', - // 'org.eclipse.elk.layered.portSortingStrategy': 'UP_DOWN', - }, - // we are also passing the id, so we can also handle edges without a sourceHandle or targetHandle option - ports: [ ...targetPorts.reverse(), ...sourcePorts.reverse()], - }; - }), - edges: edges.map((e) => ({ - id: e.id, - sources: [`${e.source}_out_${e.sourceHandle}`], - targets: [`${e.target}_in_${e.targetHandle}`], - })), - }; - - // const blob = new Blob([JSON.stringify(graph)], {type: "text/plain;charset=utf-8"}); - // saveAs(blob, 'output.json'); - - console.log("Graph: ", graph); - const layoutedGraph = await elk.layout(graph); - console.log("layoutedGraph: ", layoutedGraph); - - const layoutedNodes = nodes.map((node) => { - const layoutedNode = layoutedGraph.children?.find( - (lgNode) => lgNode.id === node.id, - ); - - return { - ...node, - position: { - x: layoutedNode?.x ?? 0, - y: layoutedNode?.y ?? 0, - }, - }; - }); - console.log("layoutedGraphNodes: ", layoutedNodes); - - return layoutedNodes; -}; - -export default function useLayoutNodes() { - const nodesInitialized = useNodesInitialized(); - const { getNodes, getEdges, setNodes, fitView } = useReactFlow(); - - useEffect(() => { - if (nodesInitialized) { - const layoutNodes = async () => { - const layoutedNodes = await getLayoutedNodes( - getNodes(), - getEdges(), - ); - - setNodes(layoutedNodes); - setTimeout(() => fitView(), 0); - }; - - layoutNodes(); - } - }, [nodesInitialized, getNodes, getEdges, setNodes, fitView]); - - return null; +import { useEffect } from 'react'; +import ELK from 'elkjs/lib/elk.bundled.js'; +import { useNodesInitialized, useReactFlow } from '@xyflow/react'; +// import { saveAs } from 'file-saver'; + + +// elk layouting options can be found here: +// https://www.eclipse.org/elk/reference/algorithms/org-eclipse-elk-layered.html + + +// uses elkjs to give each node a layouted position +export const getLayoutedNodes2 = async (nodes, edges) => { + const layoutOptions = { + 'elk.algorithm': 'layered', + 'elk.direction': 'RIGHT', + 'elk.layered.spacing.edgeNodeBetweenLayers': '40', + 'elk.spacing.nodeNode': '40', + 'elk.layered.nodePlacement.strategy': 'SIMPLE', + }; + + console.log("nodes layout: ", nodes); + console.log("edges layout: ", edges); + const elk = new ELK(); + + const graph = { + id: 'root', + layoutOptions, + children: nodes.map((n) => { + const targetPorts = n.data.target_labels.map((label) => ({ + id: `${n.id}_in_${label}`, + + // ⚠️ it's important to let elk know on which side the port is + // in this example targets are on the left (WEST) and sources on the right (EAST) + properties: { + side: 'WEST', + }, + })); + + const sourcePorts = n.data.source_labels.map((label) => ({ + id: `${n.id}_out_${label}`, + properties: { + side: 'EAST', + }, + })); + + return { + id: n.id, + width: n.style.width_unitless ?? 240, + height: n.style.height_unitless ?? 100, + // ⚠️ we need to tell elk that the ports are fixed, in order to reduce edge crossings + properties: { + 'org.eclipse.elk.portConstraints': 'FIXED_ORDER', + // 'org.eclipse.elk.layered.portSortingStrategy': 'UP_DOWN', + }, + // we are also passing the id, so we can also handle edges without a sourceHandle or targetHandle option + ports: [ ...targetPorts.reverse(), ...sourcePorts.reverse()], + }; + }), + edges: edges.map((e) => ({ + id: e.id, + sources: [`${e.source}_out_${e.sourceHandle}`], + targets: [`${e.target}_in_${e.targetHandle}`], + })), + }; + + // const blob = new Blob([JSON.stringify(graph)], {type: "text/plain;charset=utf-8"}); + // saveAs(blob, 'output.json'); + + console.log("Graph: ", graph); + const layoutedGraph = await elk.layout(graph); + console.log("layoutedGraph: ", layoutedGraph); + + const layoutedNodes = nodes.map((node) => { + const layoutedNode = layoutedGraph.children?.find( + (lgNode) => lgNode.id === node.id, + ); + + return { + ...node, + position: { + x: layoutedNode?.x ?? 0, + y: layoutedNode?.y ?? 0, + }, + }; + }); + console.log("layoutedGraphNodes: ", layoutedNodes); + + return layoutedNodes; +}; + +export default function useLayoutNodes() { + const nodesInitialized = useNodesInitialized(); + const { getNodes, getEdges, setNodes, fitView } = useReactFlow(); + + useEffect(() => { + if (nodesInitialized) { + const layoutNodes = async () => { + const layoutedNodes = await getLayoutedNodes( + getNodes(), + getEdges(), + ); + + setNodes(layoutedNodes); + setTimeout(() => fitView(), 0); + }; + + layoutNodes(); + } + }, [nodesInitialized, getNodes, getEdges, setNodes, fitView]); + + return null; } \ No newline at end of file diff --git a/js/widget.css b/js/widget.css index bbc271cf..7c271fa7 100644 --- a/js/widget.css +++ b/js/widget.css @@ -17,5 +17,16 @@ html, body, #root { } .react-flow__edge-path { - stroke-width: 2px + stroke-width: 2px; } + + + +.edge-label-renderer__loop-edge { + position: absolute; + background-color: #8f8f8f; + padding: 10px; + border-radius: 5px; + font-size: 12px; + font-weight: 700; +} \ No newline at end of file diff --git a/js/widget.jsx b/js/widget.jsx index 93bcab38..7b755ed8 100644 --- a/js/widget.jsx +++ b/js/widget.jsx @@ -10,6 +10,7 @@ import { applyNodeChanges, addEdge, useOnSelectionChange, + MarkerType, } from '@xyflow/react'; import '@xyflow/react/dist/style.css'; import { ReactFlowProvider } from '@xyflow/react'; @@ -17,8 +18,20 @@ import { ReactFlowProvider } from '@xyflow/react'; import TextUpdaterNode from './TextUpdaterNode.jsx'; import CustomNode from './CustomNode.jsx'; +import MacroNode from './MacroNode.jsx'; +import SubNode from './SubNode.jsx'; +import ReverseSubNode from './ReverseSubNode.jsx'; +import MacroInOutNode from './MacroInOutNode.jsx'; +import MacroNodeExpanded from './MacroNodeExpanded.jsx'; +import DummyMacroExpanded from './DummyMacroExpanded.jsx'; +import LoopNode from './LoopNode.jsx'; +import LoopNodeExpanded from './LoopNodeExpanded.jsx'; +import InNode from './InNode.jsx'; +import OutNode from './OutNode.jsx'; import {getLayoutedNodes2} from './useElkLayout'; +import LoopEdge from './LoopEdge.jsx'; + import './text-updater-node.css'; import './widget.css'; import './ContextMenu.css'; @@ -82,20 +95,152 @@ const render = createRender(() => { const selectedNodes = []; const selectedEdges = []; + const currentTimestamp = []; + const [menu, setMenu] = useState(null); const ref = useRef(null); const nodeTypes = { textUpdater: TextUpdaterNode, customNode: CustomNode, + macroNode: MacroNode, + macroNodeExpanded: MacroNodeExpanded, + dummyMacroExpanded: DummyMacroExpanded, + subNode: SubNode, + reverseSubNode: ReverseSubNode, + macroInOutNode: MacroInOutNode, + loopNode: LoopNode, + loopNodeExpanded: LoopNodeExpanded, + inNode: InNode, + outNode: OutNode, }; + const edgeTypes = { + loopEdge: LoopEdge, + }; + const layoutNodes = async () => { const layoutedNodes = await getLayoutedNodes2(nodes, edges); setNodes(layoutedNodes); // setTimeout(() => fitView(), 0); }; + + const layoutNodesExclusive = async () => { + const nodes = JSON.parse(model.get("nodes")); + const edges = JSON.parse(model.get("edges")); + const filteredSubNodes = nodes.filter(node => (node.type != 'subNode')); + const filteredNodes = filteredSubnodes.filter(node => (node.type != 'inNode')); + const restNodes = nodes.filter(node => (node.type == 'subNode' || node.type == 'inNode')); + const filteredEdges = edges.filter(edge => edge.type != "macroSubEdge"); + const layoutedNodes = await getLayoutedNodes2(filteredNodes, filteredEdges); + const allNodes = layoutedNodes.concat(restNodes); + setNodes(allNodes); + // setTimeout(() => fitView(), 0); + }; + + const layoutOne = async (id) => { + if (id != "") { + const nodes = JSON.parse(model.get("nodes")); + const allSubNodes = nodes.filter(node => node.parentId == id); + const subNodes = allSubNodes.filter(node => node.type != "inNode"); + console.log('Node to Layout', id); + console.log('Subnodes to Layout:', subNodes); + const edges = JSON.parse(model.get("edges")); + const allSubEdges = edges.filter(edge => edge.parent == id); + const subEdges = allSubEdges.filter(edge => edge.style.stroke != "darkgray"); + console.log('SubEdges to Layout:', subEdges); + const restNodes = nodes.filter(node => (node.parentId != id || node.type == "inNode")); + const restEdges = edges.filter(edge => (edge.parent != id || edge.style.stroke == "darkgray")); + const layoutedMacroNodes = await getLayoutedNodes2(subNodes, subEdges); + + layoutedMacroNodes.forEach(node => { + node.position.x = node.position.x + 150 ; + node.position.y = node.position.y + 100 ; + }); + + layoutedMacroNodes.forEach(node => { + if (node.data.label == "inputs" || node.data.label == "outputs") + node.position.y = 10 ; + }); + + console.log('Macro Layout Data changed:', layoutedMacroNodes); + console.log('restNodes:', restNodes); + + const untouchedNodes = restNodes.filter(node => (node.type == "subNode" || node.type == 'inNode')); + console.log('untouchedNodes:', untouchedNodes); + const customNodes = restNodes.filter(node => (node.type != "subNode" && node.type != 'inNode')); + console.log('customNodes:', customNodes); + const customEdges = restEdges.filter(edge => edge.type != "macroSubEdge"); + console.log('customEdges:', customEdges); + + const layoutedCustomNodes = await getLayoutedNodes2(customNodes, customEdges); + + const allNodes = layoutedCustomNodes.concat(layoutedMacroNodes, untouchedNodes); + const allNodesSorted = allNodes.sort((a, b) => a.id.localeCompare(b.id)); + setNodes(allNodesSorted); + } + }; + + const layoutSub = async (id) => { + if (id != "") { + const nodes = JSON.parse(model.get("nodes")); + const allSubNodes = nodes.filter(node => node.parentId == id); + const subNodes = allSubNodes.filter(node => node.type != "inNode"); + console.log('Node to Layout', id); + console.log('Subnodes to Layout:', subNodes); + const edges = JSON.parse(model.get("edges")); + const allSubEdges = edges.filter(edge => edge.parent == id); + const subEdges = allSubEdges.filter(edge => edge.style.stroke != "darkgray"); + console.log('SubEdges to Layout:', subEdges); + const restNodes = nodes.filter(node => (node.parentId != id || node.type == "inNode")); + const restNodesSorted = restNodes.sort((a, b) => a.id.localeCompare(b.id)); + const restEdges = edges.filter(edge => (edge.parent != id || edge.style.stroke == "darkgray")); + const layoutedMacroNodes = await getLayoutedNodes2(subNodes, subEdges); + + layoutedMacroNodes.forEach(node => { + node.position.x = node.position.x + 150 ; + node.position.y = node.position.y + 100 ; + }); + + console.log('Macro Layout Data changed:', layoutedMacroNodes); + console.log('restNodes:', restNodesSorted); + + + const allNodes = restNodesSorted.concat(layoutedMacroNodes); + setNodes(allNodes); + } + }; + + + + + + + + + + const wiggleNode = (id, nodesRef) => { + const allNodes = nodesRef; + allNodes.forEach(node => { + if (node.id == id) { + node.position.x += 1; + } + }); + setNodes(allNodes); + }; + + const sortMessageFromNode = useCallback((nodeId, message) => { + console.log(`Node ${nodeId} sorts`); + layoutOne(nodeId); + }, []); + + const sortMessageFromDummy = useCallback((nodeId, message) => { + console.log(`Node ${nodeId} sorts`); + layoutSub(nodeId); + }, []); + + const outputFunction = (data) => { // direct output of node to output widget console.log('output: ', data.label) @@ -153,7 +298,6 @@ const sourceFunction = (data) => { const updateData = (nodeLabel, handleIndex, newValue) => { setNodes(prevNodes => prevNodes.map((node, idx) => { - console.log('updatedDataNodes: ', nodeLabel, handleIndex, newValue, node.id); if (node.id !== nodeLabel) { return node; } @@ -176,51 +320,81 @@ const sourceFunction = (data) => { // for test only, can be later removed useEffect(() => { - console.log('nodes_test:', nodes); + // console.log('nodes_test:', nodes); model.set("nodes", JSON.stringify(nodes)); // TODO: maybe better do it via command changeValue(nodeID, handleID, value) model.save_changes() }, [nodes]); model.on("change:nodes", () => { - const new_nodes = model.get("nodes") - setNodes(JSON.parse(new_nodes)); + const new_nodes = JSON.parse(model.get("nodes")); + const expandedMacros = new_nodes.filter(node => node.type == "macroNodeExpanded"); + const expandedDummies = new_nodes.filter(node => node.type == "dummyMacroExpanded"); + const restNodes = new_nodes.filter(node => ((node.type != "macroNodeExpanded") && (node.type != "dummyMacroExpanded"))); + const updatedMacroNodes = expandedMacros.map(node => ({ + ...node, + data: { ...node.data, onMessage: sortMessageFromNode, wiggle: wiggleNode, onSort: layoutOne, edges,}, + })); + const updatedDummyNodes = expandedDummies.map(node => ({ + ...node, + data: { ...node.data, onMessage: sortMessageFromDummy, wiggle: wiggleNode, onSort: layoutOne, edges,}, + })); + const allNodes = updatedMacroNodes.concat(updatedDummyNodes, restNodes); + const allNodesSorted = allNodes.sort((a, b) => a.id.localeCompare(b.id)); + setNodes(allNodesSorted); }); model.on("change:edges", () => { - const new_edges = model.get("edges") - setEdges(JSON.parse(new_edges)); - }); + const new_edges = JSON.parse(model.get("edges")); + setEdges(new_edges); + }); + + model.on("change:timestamp", () => { + const newTimestamp = model.get("timestamp"); + console.log('current Timestamp: ', currentTimestamp); + console.log('new Timestamp', newTimestamp); + if (newTimestamp != currentTimestamp) { + console.log('Timestamp changed!'); + const new_nodes = JSON.parse(model.get("nodes")); + const expandedMacros = new_nodes.filter(node => node.type == "macroNodeExpanded"); + const expandedDummies = new_nodes.filter(node => node.type == "dummyMacroExpanded"); + const restNodes = new_nodes.filter(node => ((node.type != "macroNodeExpanded") && (node.type != "dummyMacroExpanded"))); + const updatedMacroNodes = expandedMacros.map(node => ({ + ...node, + data: { ...node.data, onMessage: sortMessageFromNode, wiggle: wiggleNode, onSort: layoutOne, edges,}, + })); + const updatedDummyNodes = expandedDummies.map(node => ({ + ...node, + data: { ...node.data, onMessage: sortMessageFromDummy, wiggle: wiggleNode, onSort: layoutOne, edges,}, + })); + const allNodes = updatedMacroNodes.concat(updatedDummyNodes, restNodes); + const allNodesSorted = allNodes.sort((a, b) => a.id.localeCompare(b.id)); + setNodes(allNodesSorted); + }; + }); + + const nodeSelection = useCallback( + (nodes) => { + const selectedNodes = nodes.filter(node => node.selected === true); + // console.log('nodes where selection == true: ', selectedNodes); + return selectedNodes; + }); + + const edgeSelection = useCallback( + (edges) => { + const selectedEdges = edges.filter(edges => edges.selected === true); + // console.log('edges where selection == true: ', selectedEdges); + return selectedEdges; + }); + const onNodesChange = useCallback( (changes) => { setNodes((nds) => { const new_nodes = applyNodeChanges(changes, nds); - var selectionChanged = false; - for (const i in changes) { - if (Object.hasOwn(changes[i], 'selected')) { - if (changes[i].selected){ - for (const k in new_nodes){ - if (new_nodes[k].id == changes[i].id) { - selectedNodes.push(new_nodes[k]); - selectionChanged = true; - } - } - } - else{ - for (const j in selectedNodes){ - if (selectedNodes[j].id == changes[i].id) { - selectedNodes.splice(j, 1); - selectionChanged = true; - } - } - } - } - } - if (selectionChanged) { - console.log('selectedNodes:', selectedNodes); - model.set("selected_nodes", JSON.stringify(selectedNodes)); - model.save_changes() - } + // console.log('nodes:', nodes); + model.set("nodes", JSON.stringify(new_nodes)); + model.set("selected_nodes", JSON.stringify(nodeSelection(new_nodes))); + model.save_changes(); return new_nodes; }); }, @@ -241,34 +415,9 @@ const sourceFunction = (data) => { (changes) => { setEdges((eds) => { const new_edges = applyEdgeChanges(changes, eds); - for (const i in changes) { - if (Object.hasOwn(changes[i], 'selected')) { - if (changes[i].selected){ - for (const k in new_edges){ - if (new_edges[k].id == changes[i].id) { - selectedEdges.push(new_edges[k]); - } - } - } - else{ - for (const j in selectedEdges){ - if (selectedEdges[j].id == changes[i].id) { - selectedEdges.splice(j, 1); - } - } - } - } - } - for (const n in selectedEdges){ - var filterResult = new_edges.filter((edge) => edge.id === selectedEdges[n].id); - if (filterResult == []){ - selectedEdges.splice(n, 1); - } - } - console.log('selectedEdges:', selectedEdges); console.log('edges:', new_edges); model.set("edges", JSON.stringify(new_edges)); - model.set("selected_edges", JSON.stringify(selectedEdges)); + model.set("selected_edges", JSON.stringify(edgeSelection(new_edges))); model.save_changes(); return new_edges; }); @@ -431,6 +580,7 @@ const sourceFunction = (data) => { onNodesDelete={onNodesDelete} onMoveEnd={onMoveEnd} nodeTypes={nodeTypes} + edgeTypes={edgeTypes} onPaneClick={onPaneClick} onNodeContextMenu={onNodeContextMenu} fitView @@ -478,13 +628,23 @@ const sourceFunction = (data) => { > Load + + + + + {