From e6c1e9b5060291fb286721a9ae02d6ff1208cab1 Mon Sep 17 00:00:00 2001 From: YouGuessedMyName Date: Mon, 1 Jul 2024 17:44:36 +0200 Subject: [PATCH] json parsing proof of concept is complete --- notebooks/groups.html | 167 ++++++++++++++++++++++++++++++++ notebooks/layouts.ipynb | 31 +----- notebooks/model.html | 4 +- notebooks/study.ipynb | 45 +++------ stormvogel/layout.py | 76 +++++++-------- stormvogel/layouts/default.json | 11 ++- stormvogel/visualization.py | 12 +-- 7 files changed, 234 insertions(+), 112 deletions(-) create mode 100644 notebooks/groups.html diff --git a/notebooks/groups.html b/notebooks/groups.html new file mode 100644 index 0000000..ca55f3d --- /dev/null +++ b/notebooks/groups.html @@ -0,0 +1,167 @@ + + + + + + + + + +
+

+
+ + + + + + +
+

+
+ + + + + +
+ + +
+
+ + + +
+ + + + + diff --git a/notebooks/layouts.ipynb b/notebooks/layouts.ipynb index 05c575d..2bff512 100644 --- a/notebooks/layouts.ipynb +++ b/notebooks/layouts.ipynb @@ -13,38 +13,15 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 3, "id": "8c98f637-ed9c-430b-b414-09c665b307f8", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{'color': 'red'}\n", - "{'color': 'red'}\n", - "{'color': 'blue'}\n", - "{'color': 'blue'}\n" - ] - } - ], + "outputs": [], "source": [ "# Import custom layout\n", - "l1 = stormvogel.layout.Layout(custom=True, custom_path=\"custom_layout.json\")\n", - "l2 = stormvogel.layout.Layout(custom=True, custom_path=\"/home/ivo/git/stormvogel/notebooks/custom_layout.json\", custom_path_relative=False)\n", - "# Import template layout\n", - "l3 = stormvogel.layout.Layout(custom=False, template_path=\"layouts/default.json\")\n", - "l4 = stormvogel.layout.Layout(custom=False, template_path=DEFAULT)\n", - "l5 = " + "l1 = stormvogel.layout.Layout(\"custom_layout.json\")\n", + "l2 = stormvogel.layout.Layout(path=\"/home/ivo/git/stormvogel/notebooks/custom_layout.json\", path_relative=False)" ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9198a4b4-12cd-47a4-8950-278c18023192", - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { diff --git a/notebooks/model.html b/notebooks/model.html index 03a4d07..e52367d 100644 --- a/notebooks/model.html +++ b/notebooks/model.html @@ -282,7 +282,7 @@

// parsing and collecting nodes and edges from the python - nodes = new vis.DataSet([{"borderWidth": 3, "color": null, "id": 0, "label": "init", "shape": "dot"}, {"borderWidth": 1, "color": null, "id": 1, "label": "studied", "shape": "dot"}, {"borderWidth": 1, "color": null, "id": 2, "label": "didn\u0027t study", "shape": "dot"}, {"borderWidth": 1, "color": null, "id": 3, "label": "pass test", "shape": "dot"}, {"borderWidth": 1, "color": null, "id": 4, "label": "fail test", "shape": "dot"}, {"color": null, "id": 100000000, "label": "study", "shape": "box"}, {"color": null, "id": 100000001, "label": "don\u0027t study", "shape": "box"}]); + nodes = new vis.DataSet([{"borderWidth": 10, "color": null, "id": 0, "label": "init", "shape": "dot"}, {"borderWidth": 1, "color": null, "id": 1, "label": "studied", "shape": "dot"}, {"borderWidth": 1, "color": null, "id": 2, "label": "didn\u0027t study", "shape": "dot"}, {"borderWidth": 1, "color": null, "id": 3, "label": "pass test", "shape": "dot"}, {"borderWidth": 1, "color": null, "id": 4, "label": "fail test", "shape": "dot"}, {"color": null, "id": 100000000, "label": "study", "shape": "box"}, {"color": null, "id": 100000001, "label": "don\u0027t study", "shape": "box"}]); edges = new vis.DataSet([{"arrows": "to", "color": "red", "from": 0, "to": 100000000}, {"arrows": "to", "color": "red", "from": 100000000, "label": "1", "to": 1}, {"arrows": "to", "color": "red", "from": 0, "to": 100000001}, {"arrows": "to", "color": "red", "from": 100000001, "label": "1", "to": 2}, {"arrows": "to", "color": "red", "from": 1, "label": "9/10", "to": 3}, {"arrows": "to", "color": "red", "from": 1, "label": "1/10", "to": 4}, {"arrows": "to", "color": "red", "from": 2, "label": "2/5", "to": 3}, {"arrows": "to", "color": "red", "from": 2, "label": "3/5", "to": 4}, {"arrows": "to", "color": "red", "from": 3, "label": "1", "to": 3}, {"arrows": "to", "color": "red", "from": 4, "label": "1", "to": 4}]); nodeColors = {}; @@ -294,7 +294,7 @@

// adding nodes and edges to the graph data = {nodes: nodes, edges: edges}; - var options = {"nodes": {"color": {"background": "blue", "border": "black"}}}; + var options = {"nodes": {"color": {"background": "white", "border": "black"}}, "init": {"borderWidth": 10, "color": "green"}}; diff --git a/notebooks/study.ipynb b/notebooks/study.ipynb index 03737f4..b02edeb 100644 --- a/notebooks/study.ipynb +++ b/notebooks/study.ipynb @@ -7,41 +7,18 @@ "metadata": {}, "outputs": [ { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "var options = {\n", - " \"nodes\": {\n", - " \"color\": {\n", - " \"background\": \"blue\",\n", - " \"border\": \"black\"\n", - " }\n", - " }\n", - "}\n", - "model.html\n" + "ename": "AttributeError", + "evalue": "'Visualization' object has no attribute 'layout'", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[1], line 33\u001b[0m\n\u001b[1;32m 27\u001b[0m \u001b[38;5;66;03m# If you did not study, then there is only a 40% chance that you pass the test.\u001b[39;00m\n\u001b[1;32m 28\u001b[0m not_studied\u001b[38;5;241m.\u001b[39mset_transitions([\n\u001b[1;32m 29\u001b[0m (\u001b[38;5;241m4\u001b[39m\u001b[38;5;241m/\u001b[39m\u001b[38;5;241m10\u001b[39m, pass_test),\n\u001b[1;32m 30\u001b[0m (\u001b[38;5;241m6\u001b[39m\u001b[38;5;241m/\u001b[39m\u001b[38;5;241m10\u001b[39m, fail_test)\n\u001b[1;32m 31\u001b[0m ])\n\u001b[0;32m---> 33\u001b[0m \u001b[43mstormvogel\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mvisualization\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mshow\u001b[49m\u001b[43m(\u001b[49m\u001b[43mmdp\u001b[49m\u001b[43m)\u001b[49m\n", + "File \u001b[0;32m~/git/stormvogel/.venv/lib/python3.11/site-packages/stormvogel/visualization.py:119\u001b[0m, in \u001b[0;36mshow\u001b[0;34m(model, name, notebook)\u001b[0m\n\u001b[1;32m 111\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mshow\u001b[39m(model: Model, name: \u001b[38;5;28mstr\u001b[39m \u001b[38;5;241m=\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mmodel\u001b[39m\u001b[38;5;124m\"\u001b[39m, notebook: \u001b[38;5;28mbool\u001b[39m \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mTrue\u001b[39;00m):\n\u001b[1;32m 112\u001b[0m \u001b[38;5;250m \u001b[39m\u001b[38;5;124;03m\"\"\"Create and show a visualization of a Model using a pyvis Network\u001b[39;00m\n\u001b[1;32m 113\u001b[0m \n\u001b[1;32m 114\u001b[0m \u001b[38;5;124;03m Args:\u001b[39;00m\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 117\u001b[0m \u001b[38;5;124;03m notebook (bool, optional): Leave to true if you are using in a notebook. Defaults to True.\u001b[39;00m\n\u001b[1;32m 118\u001b[0m \u001b[38;5;124;03m \"\"\"\u001b[39;00m\n\u001b[0;32m--> 119\u001b[0m vis \u001b[38;5;241m=\u001b[39m \u001b[43mVisualization\u001b[49m\u001b[43m(\u001b[49m\u001b[43mmodel\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mname\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mnotebook\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 120\u001b[0m vis\u001b[38;5;241m.\u001b[39mshow()\n", + "File \u001b[0;32m~/git/stormvogel/.venv/lib/python3.11/site-packages/stormvogel/visualization.py:43\u001b[0m, in \u001b[0;36mVisualization.__init__\u001b[0;34m(self, model, name, notebook, cdn_resources, layout)\u001b[0m\n\u001b[1;32m 41\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mname \u001b[38;5;241m=\u001b[39m name\n\u001b[1;32m 42\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mg \u001b[38;5;241m=\u001b[39m Network(notebook\u001b[38;5;241m=\u001b[39mnotebook, directed\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mTrue\u001b[39;00m, cdn_resources\u001b[38;5;241m=\u001b[39mcdn_resources)\n\u001b[0;32m---> 43\u001b[0m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m__add_states\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 44\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m__add_transitions()\n\u001b[1;32m 45\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m layout \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n", + "File \u001b[0;32m~/git/stormvogel/.venv/lib/python3.11/site-packages/stormvogel/visualization.py:57\u001b[0m, in \u001b[0;36mVisualization.__add_states\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 55\u001b[0m borderWidth \u001b[38;5;241m=\u001b[39m \u001b[38;5;241m1\u001b[39m\n\u001b[1;32m 56\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m state \u001b[38;5;241m==\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mmodel\u001b[38;5;241m.\u001b[39mget_initial_state():\n\u001b[0;32m---> 57\u001b[0m borderWidth \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mlayout\u001b[49m[\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124minit\u001b[39m\u001b[38;5;124m\"\u001b[39m][\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mborderWidth\u001b[39m\u001b[38;5;124m\"\u001b[39m]\n\u001b[1;32m 58\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mg\u001b[38;5;241m.\u001b[39madd_node(\n\u001b[1;32m 59\u001b[0m state\u001b[38;5;241m.\u001b[39mid,\n\u001b[1;32m 60\u001b[0m label\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[39mjoin(state\u001b[38;5;241m.\u001b[39mlabels),\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 63\u001b[0m shape\u001b[38;5;241m=\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mdot\u001b[39m\u001b[38;5;124m\"\u001b[39m,\n\u001b[1;32m 64\u001b[0m )\n", + "\u001b[0;31mAttributeError\u001b[0m: 'Visualization' object has no attribute 'layout'" ] - }, - { - "data": { - "text/html": [ - "\n", - " \n", - " " - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" } ], "source": [ diff --git a/stormvogel/layout.py b/stormvogel/layout.py index 0eb3eba..2b16df4 100644 --- a/stormvogel/layout.py +++ b/stormvogel/layout.py @@ -4,62 +4,40 @@ import os import json -DEFAULT = "layouts/default.json" - class Layout: - layout: dict + layout: dict[str, str] - def __init__( - self, - custom: bool, - path: str | None = None, - path_relative: bool = True, - template_path: str = DEFAULT, - ) -> None: + def __init__(self, path: str, path_relative: bool = True) -> None: """Load a new Layout from a json file. Use either a custom or a template file. Args: - custom (bool, optional): If set to true, stormvogel will look for your custom layout.json file. Otherwise a template will be used. path (str, optional): Relavant if custom is true. Path to your custom layout file, relative to the current working directory. Defaults to None. path_relative (bool): Relavant if custom is true. If set to true, then stormvogel will look for a custom layout file relative to the current working directory. - template_path (str, optional): Relavant if custom is false. Path to a template layout files. - These are stored in the folder layouts. For simplicity, we recommed using the constants DEFAULT, etc. - Defaults to DEFAULT (="layouts/default.json"). """ - if custom: - if path is None: - raise Exception( - "If custom is set to true, then the path needs to be set." - ) - cwd = os.getcwd() - if path_relative: - complete_path = os.path.join(cwd, path) - else: - complete_path = path - with open(complete_path) as f: - json_string = f.read() - self.layout = json.loads(json_string) + if path_relative: + complete_path = os.path.join(os.getcwd(), path) else: - package_root_dir = os.path.dirname(os.path.realpath(__file__)) - with open(os.path.join(package_root_dir, template_path)) as f: - json_string = f.read() - self.layout = json.loads(json_string) + complete_path = path + with open(complete_path) as f: + json_string = f.read() + self.layout = json.loads(json_string) def set_nt_layout(self, nt: Network) -> None: """Set the layout of the network passed as the arugment.""" # We here use <> instead of {} because the f-string formatting already uses them. - option_string = f""" -var options = < - "nodes": < - "color": < - "background": "{self.layout["color"]}", - "border": "black" - > - > ->""".replace("<", "{").replace(">", "}") - print(option_string) - nt.set_options(option_string) + # option_string = f""" + # var options = < + # "nodes": < + # "color": < + # "background": "{self.layout["color"]}", + # "border": "black" + # > + # > + # >""".replace("<", "{").replace(">", "}") + options = "var options = " + str(self.layout).replace("'", '"') + print(options) + nt.set_options(options) def save(self) -> None: raise NotImplementedError() @@ -69,3 +47,17 @@ def show_buttons(self) -> None: def __str__(self) -> str: raise NotImplementedError() + + def __getitem__(self, key: str) -> str | None: + try: + return self.layout[key] + except KeyError: + return None + + +package_root_dir = os.path.dirname(os.path.realpath(__file__)) + +# Define template layouts. +DEFAULT = Layout( + os.path.join(package_root_dir, "layouts/default.json"), path_relative=False +) diff --git a/stormvogel/layouts/default.json b/stormvogel/layouts/default.json index 64d7a25..be4715f 100644 --- a/stormvogel/layouts/default.json +++ b/stormvogel/layouts/default.json @@ -1,3 +1,12 @@ { - "color": "blue" + "nodes": { + "color": { + "background": "white", + "border": "black" + } + }, + "init": { + "borderWidth": 10, + "color": "green" + } } diff --git a/stormvogel/visualization.py b/stormvogel/visualization.py index f013c58..152eb98 100644 --- a/stormvogel/visualization.py +++ b/stormvogel/visualization.py @@ -2,7 +2,7 @@ from pyvis.network import Network from stormvogel.model import Model, EmptyAction, Number -from stormvogel.layout import Layout +from stormvogel.layout import Layout, DEFAULT from ipywidgets import interact from IPython.display import display from fractions import Fraction @@ -33,6 +33,10 @@ def __init__( name (str, optional): The name of the resulting html file. May or may not include .html extension. notebook (bool, optional): Leave to true if you are using in a notebook. Defaults to True. """ + if layout is None: + self.layout = DEFAULT + else: + self.layout = layout self.model = model if ( name[-5:] != ".html" @@ -42,10 +46,6 @@ def __init__( self.g = Network(notebook=notebook, directed=True, cdn_resources=cdn_resources) self.__add_states() self.__add_transitions() - if layout is None: - self.layout = Layout(custom=False) - else: - self.layout = layout self.layout.set_nt_layout(self.g) @@ -54,7 +54,7 @@ def __add_states(self): for state in self.model.states.values(): borderWidth = 1 if state == self.model.get_initial_state(): - borderWidth = 3 + borderWidth = self.layout["init"]["borderWidth"] # type: ignore self.g.add_node( state.id, label=",".join(state.labels),