From b2d1c75891e628316e678a20def171c275a009b1 Mon Sep 17 00:00:00 2001 From: YouGuessedMyName Date: Tue, 10 Sep 2024 19:54:29 +0200 Subject: [PATCH] minor fix --- notebooks/bond.ipynb | 80 +++++++++++--- notebooks/layouts/borange.json | 131 +++++++++++++++++++++++ notebooks/study.ipynb | 111 ++++++++++++++++++- notebooks/test.ipynb | 182 ++++++++++++++++++-------------- stormvogel/html_templates.py | 32 +++++- stormvogel/layout_editor.py | 29 ++++- stormvogel/layouts/default.json | 5 +- stormvogel/layouts/schema.json | 4 + stormvogel/visjs.py | 29 ++++- stormvogel/visualization.py | 8 +- 10 files changed, 496 insertions(+), 115 deletions(-) create mode 100644 notebooks/layouts/borange.json diff --git a/notebooks/bond.ipynb b/notebooks/bond.ipynb index f74dd15..952efb2 100644 --- a/notebooks/bond.ipynb +++ b/notebooks/bond.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 38, + "execution_count": 39, "id": "ecbc3f07-50a9-4f4a-9df2-4a95527e6db6", "metadata": {}, "outputs": [ @@ -52,24 +52,46 @@ " nodes: nodes,\n", " edges: edges,\n", " };\n", - " var options = {\"physics\": false, \"fixed\": true};\n", + " var options = {\"physics\": true};\n", " var network = new vis.Network(container, data, options);\n", "\n", " network.on( 'click', function(properties) {\n", - " var nodeId = network.getNodeAt({x:properties.event.srcEvent.offsetX, y:properties.event.srcEvent.offsetY});\n", - " var update_array = [{id: nodeId, hidden: true}]\n", - " nodes.update(update_arrvay)\n", + " var nodeId = network.getNodeAt({x:properties.event.srcEvent.offsetX, y:properties.event.srcEvent.offsetY});\n", + " makeNeighborsVisible(nodeId);\n", " });\n", "\n", " function myFunction() {\n", " var new_options = {\"nodes\": {\"color\": {\"background\": \"red\"}}}\n", - " var update_array = [{id: 1, hidden: false}]\n", + " var update_array = [{id: 2, hidden: false}]\n", " nodes.update(update_array)\n", " network.setOptions(new_options)\n", " }\n", - " alert(JSON.stringify(network.getPositions(2)))\n", + " function disappear() {\n", + " ids = nodes.getIds();\n", + " for (let i = 0; i < ids.length; i++) {\n", + " var nodeId = ids[i];\n", + " var node = nodes.get(nodeId);\n", + " node[\"hidden\"] = true;\n", + " nodes.update(node);\n", + " } \n", + " }\n", + " function makeVisible(nodeId) {\n", + " var node = nodes.get(nodeId);\n", + " node[\"hidden\"] = false;\n", + " nodes.update(node);\n", + " }\n", + " \n", + " function makeNeighborsVisible(myNode) {\n", + " var ids = network.getConnectedNodes(myNode, 'to');\n", + " for (let i = 0; i < ids.length; i++) {\n", + " var nodeId = ids[i];\n", + " makeVisible(nodeId);\n", + " }\n", + " }\n", + " disappear();\n", + " makeVisible(1);\n", " \n", - " \n", + " \n", " \n", "" ], @@ -129,24 +151,46 @@ " nodes: nodes,\n", " edges: edges,\n", " };\n", - " var options = {\"physics\": false, \"fixed\": true};\n", + " var options = {\"physics\": true};\n", " var network = new vis.Network(container, data, options);\n", "\n", " network.on( 'click', function(properties) {\n", - " var nodeId = network.getNodeAt({x:properties.event.srcEvent.offsetX, y:properties.event.srcEvent.offsetY});\n", - " var update_array = [{id: nodeId, hidden: true}]\n", - " nodes.update(update_arrvay)\n", + " var nodeId = network.getNodeAt({x:properties.event.srcEvent.offsetX, y:properties.event.srcEvent.offsetY});\n", + " makeNeighborsVisible(nodeId);\n", " });\n", "\n", " function myFunction() {\n", " var new_options = {\"nodes\": {\"color\": {\"background\": \"red\"}}}\n", - " var update_array = [{id: 1, hidden: false}]\n", + " var update_array = [{id: 2, hidden: false}]\n", " nodes.update(update_array)\n", " network.setOptions(new_options)\n", " }\n", - " alert(JSON.stringify(network.getPositions(2)))\n", + " function disappear() {\n", + " ids = nodes.getIds();\n", + " for (let i = 0; i < ids.length; i++) {\n", + " var nodeId = ids[i];\n", + " var node = nodes.get(nodeId);\n", + " node[\"hidden\"] = true;\n", + " nodes.update(node);\n", + " } \n", + " }\n", + " function makeVisible(nodeId) {\n", + " var node = nodes.get(nodeId);\n", + " node[\"hidden\"] = false;\n", + " nodes.update(node);\n", + " }\n", + " \n", + " function makeNeighborsVisible(myNode) {\n", + " var ids = network.getConnectedNodes(myNode, 'to');\n", + " for (let i = 0; i < ids.length; i++) {\n", + " var nodeId = ids[i];\n", + " makeVisible(nodeId);\n", + " }\n", + " }\n", + " disappear();\n", + " makeVisible(1);\n", " \n", - " \n", + " \n", " \n", "\"\"\"\n", "\n", @@ -206,7 +250,11 @@ "output_type": "display_data" } ], - "source": [] + "source": [ + " \n", + " \n", + " " + ] }, { "cell_type": "code", diff --git a/notebooks/layouts/borange.json b/notebooks/layouts/borange.json new file mode 100644 index 0000000..13f447f --- /dev/null +++ b/notebooks/layouts/borange.json @@ -0,0 +1,131 @@ +{ + "__fake_macros": { + "__group_macro": { + "borderWidth": 1, + "color": { + "background": "white", + "border": "black", + "highlight": { + "background": "white", + "border": "red" + } + }, + "shape": "ellipse", + "mass": 1, + "font": { + "color": "black", + "size": 14 + } + } + }, + "groups": { + "states": { + "borderWidth": 1, + "color": { + "background": "#ffa348", + "border": "#9141ac", + "highlight": { + "background": "white", + "border": "red" + } + }, + "shape": "ellipse", + "mass": 1, + "font": { + "color": "black", + "size": 14 + } + }, + "actions": { + "borderWidth": 1, + "color": { + "background": "lightblue", + "border": "black", + "highlight": { + "background": "white", + "border": "red" + } + }, + "shape": "box", + "mass": 1, + "font": { + "color": "black", + "size": 14 + } + }, + "scheduled_actions": { + "borderWidth": 1, + "color": { + "background": "pink", + "border": "black", + "highlight": { + "background": "white", + "border": "red" + } + }, + "shape": "box", + "mass": 1, + "font": { + "color": "black", + "size": 14 + }, + "schedColor": false + }, + "init": { + "borderWidth": 1, + "color": { + "background": "#613583", + "border": "black", + "highlight": { + "background": "white", + "border": "red" + } + }, + "shape": "ellipse", + "mass": 1, + "font": { + "color": "black", + "size": 14 + } + } + }, + "reload_button": false, + "edges": { + "arrows": "to", + "font": { + "color": "black", + "size": 14 + }, + "color": { + "color": "#000000" + } + }, + "numbers": { + "fractions": true, + "digits": 5 + }, + "results_and_rewards": { + "show_results": true, + "resultSymbol": "\u2606", + "show_rewards": true + }, + "layout": { + "randomSeed": 5 + }, + "misc": { + "enable_physics": true, + "width": 800, + "height": 600, + "explore": true + }, + "saving": { + "relative_path": true, + "filename": "layouts/borange.json", + "save_button": false, + "load_button": false + }, + "positions": {}, + "width": 800, + "height": 600, + "physics": true +} diff --git a/notebooks/study.ipynb b/notebooks/study.ipynb index b4190ba..d9aa111 100644 --- a/notebooks/study.ipynb +++ b/notebooks/study.ipynb @@ -9,7 +9,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "ef13107015784678ba65ec0488f6e259", + "model_id": "83f1bd6e0acd434cbcda022039d362b0", "version_major": 2, "version_minor": 0 }, @@ -26,7 +26,7 @@ "import ipywidgets as widgets\n", "import IPython.display as ipd\n", "\n", - "#logging.basicConfig(level=logging.INFO)\n", + "logging.basicConfig(level=logging.ERROR)\n", "\n", "debug_output = widgets.Output()\n", "ipd.display(debug_output)" @@ -43,7 +43,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "5ae1bd6903c7480a91050c3fd18dd531", + "model_id": "25ae527906ea428bab1945ba93c99eee", "version_major": 2, "version_minor": 0 }, @@ -154,6 +154,111 @@ "source": [ "stormvogel.communication_server.awaiting" ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "467262db-4633-4b4d-a0aa-c2d595c2a866", + "metadata": {}, + "outputs": [], + "source": [ + "vis.layout.layout\n" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "9faef680-3785-4ecc-b832-fba2635a87ba", + "metadata": {}, + "outputs": [], + "source": [ + "vis.update()" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "90562c37-6c99-4178-b57b-716c09d1d5f5", + "metadata": {}, + "outputs": [], + "source": [ + "vis.nt.update_options(\"{}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "76925e3b-83b2-434b-9cbb-e45a6847f8df", + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "document.getElementById('studyOOCZXVecop').contentWindow.network.setOptions({'nodes':{'color': 'red'}});" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "\"document.getElementById('studyOOCZXVecop').contentWindow.network.setOptions({'nodes':{'color': 'red'}});\"" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "options = '''{'nodes':{'color': 'red'}}'''\n", + "js = f\"\"\"document.getElementById('{vis.nt.name}').contentWindow.network.setOptions({options});\"\"\"\n", + "ipd.display(ipd.Javascript(js))\n", + "js" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "974ce10c-8da1-4a46-9237-48cc406cba9d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "\"{'__fake_macros': {'__group_macro': {'borderWidth': 1, 'color': {'background': 'white', 'border': 'black', 'highlight': {'background': 'white', 'border': 'red'}}, 'shape': 'ellipse', 'mass': 1, 'font': {'color': 'black', 'size': 14}}}, 'groups': {'states': {'borderWidth': 57, 'color': {'background': 'white', 'border': 'black', 'highlight': {'background': 'white', 'border': 'red'}}, 'shape': 'ellipse', 'mass': 1, 'font': {'color': 'black', 'size': 14}}, 'actions': {'borderWidth': 1, 'color': {'background': 'lightblue', 'border': 'black', 'highlight': {'background': 'white', 'border': 'red'}}, 'shape': 'box', 'mass': 1, 'font': {'color': 'black', 'size': 14}}, 'scheduled_actions': {'borderWidth': 1, 'color': {'background': 'pink', 'border': 'black', 'highlight': {'background': 'white', 'border': 'red'}}, 'shape': 'box', 'mass': 1, 'font': {'color': 'black', 'size': 14}, 'schedColor': False}, 'init': {'borderWidth': 1, 'color': {'background': 'white', 'border': 'black', 'highlight': {'background': 'white', 'border': 'red'}}, 'shape': 'ellipse', 'mass': 1, 'font': {'color': 'black', 'size': 14}}}, 'reload_button': False, 'edges': {'arrows': 'to', 'font': {'color': '#613583', 'size': 14}, 'color': {'color': '#b5835a'}}, 'numbers': {'fractions': True, 'digits': 5}, 'results_and_rewards': {'show_results': True, 'resultSymbol': '\u2606', 'show_rewards': True}, 'layout': {'randomSeed': 5}, 'misc': {'enable_physics': True, 'width': 800, 'height': 600, 'explore': False}, 'saving': {'relative_path': True, 'filename': 'layouts/NAME.json', 'save_button': False, 'load_button': False}, 'positions': {}, 'width': 800, 'height': 600, 'physics': True}\"" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "str(vis.layout.layout)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "c72d4a81-dbb1-446b-99af-3ada9e6578b1", + "metadata": {}, + "outputs": [], + "source": [ + "vis.nt.set_options('{groups: {nodes: {color: {background: \"red\"}}}}')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "10c6dd06-1e9a-480f-93cf-bc55c1405fcd", + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { diff --git a/notebooks/test.ipynb b/notebooks/test.ipynb index 32f7a8f..bcfefeb 100644 --- a/notebooks/test.ipynb +++ b/notebooks/test.ipynb @@ -9,12 +9,12 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "f54454cde93f4446bc04a3e5d08b29bf", + "model_id": "d35d7d42c6304da2bd6f780d835cc2d6", "version_major": 2, "version_minor": 0 }, "text/plain": [ - "Output()" + "Output(outputs=({'output_type': 'display_data', 'data': {'text/plain': '', '\u2026" ] }, "metadata": {}, @@ -25,6 +25,7 @@ "from stormvogel.visjs import Network\n", "\n", "nt = Network(name=\"neem\")\n", + "nt.enable_exploration_mode(0)\n", "nt.add_node(id=0)\n", "nt.add_node(id=1, label=\"lol\\n hi\")\n", "nt.add_node(id=2, label=\"lol\")\n", @@ -32,23 +33,103 @@ "nt.add_node(id=5, label=\"do' not\")\n", "nt.add_edge(0, 1, label=\"hi\")\n", "nt.add_edge(0, 2)\n", - "nt.set_options('{}')\n", - "nt.show()\n" + "nt.set_options('{nodes: {color: {background: \"red\"}}}')\n", + "nt.show()\n", + "\n" ] }, { "cell_type": "code", - "execution_count": 4, - "id": "2f92467b-9937-47e3-8837-f9e322b0bfe9", + "execution_count": 6, + "id": "b6be18cc-b5d9-4a39-92a6-70c649b35352", "metadata": {}, "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n", + "\n", + " \n", + " Network\n", + " \n", + " \n", + " \n", + " \n", + "
\n", + " \n", + " \n", + "\n", + "\n" + ] + }, { "data": { - "text/html": [ - "" + "application/javascript": [ + "document.getElementById('neem').contentWindow.makeAllNodesInvisible()" ], "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -56,90 +137,29 @@ }, { "data": { + "application/javascript": [ + "document.getElementById('neem').contentWindow.makeNodeVisible(0)" + ], "text/plain": [ - "'6'" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "import stormvogel.communication_server as serv\n", - "\n", - "\n", - "\n", - "serv.server_running\n", - "serv.server\n", - "serv.server.request(\"String(6)\")" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "74486ec8-a0ae-4724-bff2-bc7200dc1241", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{}" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "import stormvogel\n", - "stormvogel.communication_server.awaiting" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "b49e07e5-bbe3-49ec-ae19-7091ba1d95d6", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" + "" ] }, - "execution_count": 4, "metadata": {}, - "output_type": "execute_result" + "output_type": "display_data" } ], "source": [ - "serv.server_running" + "print(nt.generate_html())\n", + "nt.update_exploration_mode(0)" ] }, { "cell_type": "code", - "execution_count": 6, - "id": "dda68ca7-94d7-4549-884a-d4c8f1686c06", + "execution_count": null, + "id": "8e80054e-dd5e-4a0f-b0f8-4faf34786a9c", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "import stormvogel.communication_server as serv\n", - "\n", - "serv.initialize_server()" - ] + "outputs": [], + "source": [] } ], "metadata": { diff --git a/stormvogel/html_templates.py b/stormvogel/html_templates.py index 0915940..19a3360 100644 --- a/stormvogel/html_templates.py +++ b/stormvogel/html_templates.py @@ -26,10 +26,38 @@ """ # Javascript code for finding the container and initializing the network -NETWORK_JS = """ +NETWORK_JS = """//js var container = document.getElementById("mynetwork"); var data = { nodes: nodes, edges: edges, }; -var network = new vis.Network(container, data, options);""" +var network = new vis.Network(container, data, options); +function makeAllNodesInvisible() { + ids = nodes.getIds(); + for (let i = 0; i < ids.length; i++) { + var nodeId = ids[i]; + var node = nodes.get(nodeId); + node["hidden"] = true; + nodes.update(node); + } +}; +function makeNeighborsVisible(myNode) { + var ids = network.getConnectedNodes(myNode, 'to'); + for (let i = 0; i < ids.length; i++) { + var nodeId = ids[i]; + var node = nodes.get(nodeId); + node["hidden"] = false; + nodes.update(node); + } +}; +function makeNodeVisible(nodeId) { + var node = nodes.get(nodeId); + node["hidden"] = false; + nodes.update(node); +}; +network.on( 'click', function(properties) { + var nodeId = network.getNodeAt({x:properties.event.srcEvent.offsetX, y:properties.event.srcEvent.offsetY}); + makeNeighborsVisible(nodeId); +}); +""" diff --git a/stormvogel/layout_editor.py b/stormvogel/layout_editor.py index 1451163..01d4b03 100644 --- a/stormvogel/layout_editor.py +++ b/stormvogel/layout_editor.py @@ -30,9 +30,14 @@ def __init__( do_display=False, ) - def try_update(self): - if not self.loaded: - return + def copy_settings(self): + """Copy some settings from one place in the layout to another place in the layout. + They differ because visjs requires for them to be arranged a certain way which is not nice for an editor.""" + self.layout.layout["physics"] = self.layout.layout["misc"]["enable_physics"] + self.layout.layout["width"] = self.layout.layout["misc"]["width"] + self.layout.layout["height"] = self.layout.layout["misc"]["height"] + + def process_save_button(self): if self.layout.layout["saving"]["save_button"]: # Save iff the save button was pressed. self.layout.layout["saving"]["save_button"] = False @@ -49,6 +54,8 @@ def try_update(self): self.layout.layout["saving"]["filename"], path_relative=self.layout.layout["saving"]["relative_path"], ) + + def process_load_button(self): if self.layout.layout["saving"]["load_button"]: # Load iff the load button was pressed. self.layout.layout["saving"]["load_button"] = False @@ -59,13 +66,29 @@ def try_update(self): self.show() # TODO replace this with simply setting the button values so that the entire menu doesn't have to reload (looks weird). if self.vis is not None: self.vis.show() + + def process_reload_button(self): if self.layout.layout["reload_button"] and self.vis is not None: # Call show again iff the reload button was pressed. self.layout.layout["reload_button"] = False with self.debug_output: logging.info("Received reload button request.") self.vis.show() + + def try_update(self): + """Process the updates from the layout editor where required.""" + with self.debug_output: + logging.error("try_update called") + self.copy_settings() + + if not self.loaded: + return + self.process_save_button() + self.process_load_button() + self.process_reload_button() if self.vis is not None: + with self.debug_output: + logging.error("vis update called") self.vis.update() def try_show(self): diff --git a/stormvogel/layouts/default.json b/stormvogel/layouts/default.json index 8cd3745..f514c6c 100644 --- a/stormvogel/layouts/default.json +++ b/stormvogel/layouts/default.json @@ -98,7 +98,8 @@ "misc": { "enable_physics": true, "width": 800, - "height": 600 + "height": 600, + "explore": false }, "saving": { "relative_path": true, @@ -107,5 +108,7 @@ "load_button": false }, "positions": {}, + "width": 800, + "height": 600, "physics": true } diff --git a/stormvogel/layouts/schema.json b/stormvogel/layouts/schema.json index e62572b..363c76e 100644 --- a/stormvogel/layouts/schema.json +++ b/stormvogel/layouts/schema.json @@ -135,6 +135,10 @@ "min": 0, "max": 2000 } + }, + "explore": { + "__description": "explore", + "__widget": "Checkbox" } }, "saving": { diff --git a/stormvogel/visjs.py b/stormvogel/visjs.py index 12b78c5..62b924f 100644 --- a/stormvogel/visjs.py +++ b/stormvogel/visjs.py @@ -45,11 +45,32 @@ def __init__( self.nodes_js: str = "" self.edges_js: str = "" self.options_js: str = "{}" + self.new_nodes_hidden: bool = False self.server: stormvogel.communication_server.CommunicationServer = ( stormvogel.communication_server.initialize_server() ) # Note that this refers to the same server as the global variable in stormvogel.communication_server. + def enable_exploration_mode(self, initial_node_id: int): + """Every node becomes invisible. You can then click any node to reveal all of its successors. Call before adding any nodes to the network.""" + self.new_nodes_hidden = True + self.initial_node_id = initial_node_id + + def update_exploration_mode(self, initial_node_id: int): + # Make all nodes invisible. + ipd.display( + ipd.Javascript( + f"document.getElementById('{self.name}').contentWindow.makeAllNodesInvisible()" + ) + ) + # Make the initial state visible. + ipd.display( + ipd.Javascript( + f"document.getElementById('{self.name}').contentWindow.makeNodeVisible({initial_node_id})" + ) + ) + # All future nodes to be added will be hidden as well. + def get_positions(self) -> dict: """Get the current positions of the nodes on the canvas. Returns empty dict if unsucessful. Example result: {"0": {"x": 5, "y": 10}}""" @@ -88,6 +109,8 @@ def add_node( current += ( f', x: {position_dict[str(id)]["x"]}, y: {position_dict[str(id)]["y"]}' ) + if self.new_nodes_hidden and id != self.initial_node_id: + current += ", hidden: true" current += " },\n" self.nodes_js += current @@ -165,9 +188,9 @@ def reload(self) -> None: def update_options(self, options: str): """Update the options. The string DOES NOT WORK if it starts with 'var options = '""" self.set_options(options) - html = f"""""" - with spam: - ipd.display(ipd.HTML(html)) + js = f"""document.getElementById('{self.name}').contentWindow.network.setOptions({options});""" + with self.debug_output: + ipd.display(ipd.Javascript(js)) with self.debug_output: print("Called Network.update_options") diff --git a/stormvogel/visualization.py b/stormvogel/visualization.py index 03c4311..44e2f0b 100644 --- a/stormvogel/visualization.py +++ b/stormvogel/visualization.py @@ -83,18 +83,14 @@ def show(self) -> None: debug_output=self.debug_output, do_display=False, ) - with self.debug_output: - logging.info("Init network") + if self.layout.layout["misc"]["explore"]: + self.nt.enable_exploration_mode(self.model.get_initial_state().id) self.layout.set_groups(self.separate_labels) self.__add_states() self.__add_transitions() self.__update_physics_enabled() self.nt.set_options(str(self.layout)) - with self.debug_output: - logging.info("Set options") self.nt.show() - with self.debug_output: - logging.info("nt show") self.maybe_display_output() def update(self) -> None: