From d87927f5b24fd8b297a662f7d9a295ae6352693c Mon Sep 17 00:00:00 2001 From: Dane Freeman Date: Wed, 20 Jan 2021 12:59:20 -0500 Subject: [PATCH 01/11] starting python api reference docs --- docs/index.md | 7 +++++++ docs/reference/index.md | 8 ++++++++ docs/reference/transformers.md | 17 +++++++++++++++++ docs/reference/widgets.md | 14 ++++++++++++++ py_src/ipyelk/app.py | 6 +++++- py_src/ipyelk/diagram/elk_widget.py | 8 +++++++- py_src/ipyelk/layouting/elkjs.py | 10 ++++------ py_src/ipyelk/nx/transformer.py | 2 +- 8 files changed, 63 insertions(+), 9 deletions(-) create mode 100644 docs/reference/index.md create mode 100644 docs/reference/transformers.md create mode 100644 docs/reference/widgets.md diff --git a/docs/index.md b/docs/index.md index 691383a4..a3c3a018 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,3 +1,10 @@ ```{include} ../README.md ``` + +```{toctree} +:maxdepth: 1 +:hidden: +:titlesonly: +reference/index +``` \ No newline at end of file diff --git a/docs/reference/index.md b/docs/reference/index.md new file mode 100644 index 00000000..1c57863d --- /dev/null +++ b/docs/reference/index.md @@ -0,0 +1,8 @@ +# API Reference +This page describe the overall API for ipyelk. + +```{toctree} +:maxdepth: 2 +widgets +transformers +``` diff --git a/docs/reference/transformers.md b/docs/reference/transformers.md new file mode 100644 index 00000000..53a2ff45 --- /dev/null +++ b/docs/reference/transformers.md @@ -0,0 +1,17 @@ +# Transformers +The goal of transformers it to take some generalized objects and + +## Transformer + +```{eval-rst} +.. autoclass:: ipyelk.transform.ElkTransformer + :members: +``` + +## NetworkX Transformer + +```{eval-rst} +.. autoclass:: ipyelk.nx.transformer.XELK + :show-inheritance: + :members: +``` diff --git a/docs/reference/widgets.md b/docs/reference/widgets.md new file mode 100644 index 00000000..b6c8cb15 --- /dev/null +++ b/docs/reference/widgets.md @@ -0,0 +1,14 @@ +# Widgets + +## Diagram Widget + +```{eval-rst} +.. autoclass:: ipyelk.diagram.elk_widget.ElkDiagram + :members: +``` + +## App Widget +```{eval-rst} +.. autoclass:: ipyelk.app.Elk + :members: +``` diff --git a/py_src/ipyelk/app.py b/py_src/ipyelk/app.py index 6a1b2a05..488112cc 100644 --- a/py_src/ipyelk/app.py +++ b/py_src/ipyelk/app.py @@ -11,7 +11,11 @@ class Elk(W.VBox, StyledWidget): - """ An Elk diagramming widget """ + """An Elk diagramming widget to help coordinate the + :py:class:`~ipyelk.diagram.elk_widget.ElkDiagram` and + :py:class:`~ipyelk.transform.ElkTransformer` + + """ transformer: ElkTransformer = T.Instance(ElkTransformer) diagram: ElkDiagram = T.Instance(ElkDiagram) diff --git a/py_src/ipyelk/diagram/elk_widget.py b/py_src/ipyelk/diagram/elk_widget.py index 5589fe37..37448263 100644 --- a/py_src/ipyelk/diagram/elk_widget.py +++ b/py_src/ipyelk/diagram/elk_widget.py @@ -16,7 +16,13 @@ class ElkDiagram(DOMWidget): - """Jupyterlab widget for interacting with ELK diagrams""" + """Jupyterlab widget for interacting with ELK diagrams. + + Setting the instance's `value` traitlet to valid `elk json + `_ + will call the `elkjs layout method `_ and + display the returned `mark_layout` using `sprotty `_. + """ _model_name = T.Unicode("ELKDiagramModel").tag(sync=True) _model_module = T.Unicode(EXTENSION_NAME).tag(sync=True) diff --git a/py_src/ipyelk/layouting/elkjs.py b/py_src/ipyelk/layouting/elkjs.py index 4bd34766..16f5110f 100644 --- a/py_src/ipyelk/layouting/elkjs.py +++ b/py_src/ipyelk/layouting/elkjs.py @@ -26,7 +26,8 @@ def message(self) -> Dict: class ElkJS(DOMWidget): - """Jupyterlab widget for interacting with ELK diagrams""" + """Jupyterlab widget for calling [elkjs](https://github.com/kieler/elkjs) + layout given a valid elkjson dictionary""" _model_name = T.Unicode("ELKLayoutModel").tag(sync=True) _model_module = T.Unicode(EXTENSION_NAME).tag(sync=True) @@ -44,11 +45,8 @@ def __init__(self, *args, **kwargs): asyncio.create_task(self._process_requests()) asyncio.create_task(self._process_responses()) - @T.default("_value") - def _default_value(self): - return {"id": "root"} - - async def layout(self, value): + async def layout(self, value:Dict): + """Pass the value to [elkjs](https://github.com/kieler/elkjs) layout""" future = asyncio.Future() request = LayoutRequest(payload=value) self._futures[request.id] = future diff --git a/py_src/ipyelk/nx/transformer.py b/py_src/ipyelk/nx/transformer.py index e6742847..140fc6e8 100644 --- a/py_src/ipyelk/nx/transformer.py +++ b/py_src/ipyelk/nx/transformer.py @@ -41,7 +41,7 @@ class XELK(ElkTransformer): - """NetworkX DiGraphs to ELK dictionary structure""" + """NetworkX source graphs to a valid ELK JSON dictionary structure""" HIDDEN_ATTR = "hidden" hoist_hidden_edges: bool = True From f6393a874345c8abd5a63044179100af377a6194 Mon Sep 17 00:00:00 2001 From: Dane Freeman Date: Wed, 20 Jan 2021 13:20:19 -0500 Subject: [PATCH 02/11] attempt to get ipyelk installed for autodocing --- docs/rtd.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/rtd.yml b/docs/rtd.yml index eaaaf2f3..58f66778 100644 --- a/docs/rtd.yml +++ b/docs/rtd.yml @@ -10,3 +10,5 @@ dependencies: - python >=3.7,<3.8 - sphinx - sphinx-autodoc-typehints + - pip: + - ../. \ No newline at end of file From d71172d5cac004ed128f9c0978e7fba9a38b3771 Mon Sep 17 00:00:00 2001 From: Dane Freeman Date: Wed, 20 Jan 2021 13:32:28 -0500 Subject: [PATCH 03/11] another attempt to get ipyelk installed for autodocing --- .readthedocs.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.readthedocs.yml b/.readthedocs.yml index 71f48d50..181f5ee4 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -2,7 +2,9 @@ version: 2 sphinx: configuration: docs/conf.py - +python: + version: 3.7 + pip_install: true conda: environment: docs/rtd.yml From 3eec64f7de90de709b4b6347286760d2ae812766 Mon Sep 17 00:00:00 2001 From: Dane Freeman Date: Wed, 20 Jan 2021 13:51:57 -0500 Subject: [PATCH 04/11] yet another attempt to get ipyelk installed for autodocing --- .readthedocs.yml | 3 --- docs/rtd.yml | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/.readthedocs.yml b/.readthedocs.yml index 181f5ee4..cd84d48f 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -2,9 +2,6 @@ version: 2 sphinx: configuration: docs/conf.py -python: - version: 3.7 - pip_install: true conda: environment: docs/rtd.yml diff --git a/docs/rtd.yml b/docs/rtd.yml index 58f66778..ca128963 100644 --- a/docs/rtd.yml +++ b/docs/rtd.yml @@ -11,4 +11,4 @@ dependencies: - sphinx - sphinx-autodoc-typehints - pip: - - ../. \ No newline at end of file + - -e .. \ No newline at end of file From 49599c324a9b911c8d0ce32b6243f680ea94806f Mon Sep 17 00:00:00 2001 From: Dane Freeman Date: Wed, 20 Jan 2021 14:56:24 -0500 Subject: [PATCH 05/11] check if environment is readthedocs for elk json schema validation --- py_src/ipyelk/schema/validator.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/py_src/ipyelk/schema/validator.py b/py_src/ipyelk/schema/validator.py index e14c4584..6e2ce02f 100644 --- a/py_src/ipyelk/schema/validator.py +++ b/py_src/ipyelk/schema/validator.py @@ -8,9 +8,17 @@ import jsonschema -HERE = Path(__file__).parent -SCHEMA = json.loads((HERE / "elkschema.json").read_text(encoding="utf-8")) -SCHEMA["$ref"] = "#/definitions/AnyElkNode" +try: + HERE = Path(__file__).parent + SCHEMA = json.loads((HERE / "elkschema.json").read_text(encoding="utf-8")) + SCHEMA["$ref"] = "#/definitions/AnyElkNode" +except FileNotFoundError as E: + import os + if os.environ.get("READTHEDOCS", False): + SCHEMA = {} # use dummy schema while building the docs + else: + raise E + ElkSchemaValidator = jsonschema.Draft7Validator(SCHEMA) From b8d90133ff79f69b32f843be506b958636596f16 Mon Sep 17 00:00:00 2001 From: Dane Freeman Date: Wed, 20 Jan 2021 15:05:57 -0500 Subject: [PATCH 06/11] including build json schema for doc purposes --- .gitignore | 3 - py_src/ipyelk/schema/elkschema.json | 746 ++++++++++++++++++++++++++++ py_src/ipyelk/schema/validator.py | 13 +- 3 files changed, 749 insertions(+), 13 deletions(-) create mode 100644 py_src/ipyelk/schema/elkschema.json diff --git a/.gitignore b/.gitignore index ca94dbaf..e56aa353 100644 --- a/.gitignore +++ b/.gitignore @@ -157,9 +157,6 @@ py_src/ipyelk/labextension/*.tgz # ------------- **/coverage/ -# generated files -py_src/ipyelk/schema/*.json - # editors .idea/ .vscode/ diff --git a/py_src/ipyelk/schema/elkschema.json b/py_src/ipyelk/schema/elkschema.json new file mode 100644 index 00000000..1b9ea278 --- /dev/null +++ b/py_src/ipyelk/schema/elkschema.json @@ -0,0 +1,746 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "AnyElkEdge": { + "anyOf": [ + { + "$ref": "#/definitions/ElkEdge" + }, + { + "$ref": "#/definitions/ElkExtendedEdge" + }, + { + "$ref": "#/definitions/ElkPrimitiveEdge" + }, + { + "$ref": "#/definitions/LazyElkEdge" + } + ] + }, + "AnyElkEdgeWithProperties": { + "anyOf": [ + { + "additionalProperties": false, + "properties": { + "id": { + "type": "string" + }, + "junctionPoints": { + "items": { + "$ref": "#/definitions/ElkPoint" + }, + "type": "array" + }, + "labels": { + "items": { + "$ref": "#/definitions/ElkLabel" + }, + "type": "array" + }, + "layoutOptions": { + "$ref": "#/definitions/LayoutOptions" + }, + "properties": { + "$ref": "#/definitions/ElkProperties" + } + }, + "required": ["id"], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "id": { + "type": "string" + }, + "junctionPoints": { + "items": { + "$ref": "#/definitions/ElkPoint" + }, + "type": "array" + }, + "labels": { + "items": { + "$ref": "#/definitions/ElkLabel" + }, + "type": "array" + }, + "layoutOptions": { + "$ref": "#/definitions/LayoutOptions" + }, + "properties": { + "$ref": "#/definitions/ElkProperties" + }, + "sections": { + "items": { + "$ref": "#/definitions/ElkEdgeSection" + }, + "type": "array" + }, + "sources": { + "items": { + "type": "string" + }, + "type": "array" + }, + "targets": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": ["id", "sections", "sources", "targets"], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "bendPoints": { + "items": { + "$ref": "#/definitions/ElkPoint" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "junctionPoints": { + "items": { + "$ref": "#/definitions/ElkPoint" + }, + "type": "array" + }, + "labels": { + "items": { + "$ref": "#/definitions/ElkLabel" + }, + "type": "array" + }, + "layoutOptions": { + "$ref": "#/definitions/LayoutOptions" + }, + "properties": { + "$ref": "#/definitions/ElkProperties" + }, + "source": { + "type": "string" + }, + "sourcePoint": { + "$ref": "#/definitions/ElkPoint" + }, + "sourcePort": { + "type": "string" + }, + "target": { + "type": "string" + }, + "targetPoint": { + "$ref": "#/definitions/ElkPoint" + }, + "targetPort": { + "type": "string" + } + }, + "required": ["id", "source", "target"], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "id": { + "type": "string" + }, + "junctionPoints": { + "items": { + "$ref": "#/definitions/ElkPoint" + }, + "type": "array" + }, + "labels": { + "items": { + "$ref": "#/definitions/AnyElkLabelWithProperties" + }, + "type": "array" + }, + "layoutOptions": { + "$ref": "#/definitions/LayoutOptions" + }, + "properties": { + "$ref": "#/definitions/ElkProperties" + }, + "sources": { + "items": { + "type": "string" + }, + "type": "array" + }, + "targets": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": ["id", "sources", "targets"], + "type": "object" + } + ] + }, + "AnyElkLabelWithProperties": { + "additionalProperties": false, + "properties": { + "height": { + "type": "number" + }, + "id": { + "type": "string" + }, + "labels": { + "items": { + "$ref": "#/definitions/ElkLabel" + }, + "type": "array" + }, + "layoutOptions": { + "$ref": "#/definitions/LayoutOptions" + }, + "properties": { + "$ref": "#/definitions/ElkProperties" + }, + "text": { + "type": "string" + }, + "width": { + "type": "number" + }, + "x": { + "type": "number" + }, + "y": { + "type": "number" + } + }, + "required": ["id", "text"], + "type": "object" + }, + "AnyElkNode": { + "additionalProperties": false, + "properties": { + "children": { + "items": { + "$ref": "#/definitions/AnyElkNode" + }, + "type": "array" + }, + "edges": { + "items": { + "$ref": "#/definitions/AnyElkEdgeWithProperties" + }, + "type": "array" + }, + "height": { + "type": "number" + }, + "id": { + "type": "string" + }, + "labels": { + "items": { + "$ref": "#/definitions/AnyElkLabelWithProperties" + }, + "type": "array" + }, + "layoutOptions": { + "$ref": "#/definitions/LayoutOptions" + }, + "ports": { + "items": { + "$ref": "#/definitions/AnyElkPort" + }, + "type": "array" + }, + "properties": { + "$ref": "#/definitions/ElkProperties" + }, + "width": { + "type": "number" + }, + "x": { + "type": "number" + }, + "y": { + "type": "number" + } + }, + "required": ["id"], + "type": "object" + }, + "AnyElkPort": { + "additionalProperties": false, + "properties": { + "height": { + "type": "number" + }, + "id": { + "type": "string" + }, + "labels": { + "items": { + "$ref": "#/definitions/AnyElkLabelWithProperties" + }, + "type": "array" + }, + "layoutOptions": { + "$ref": "#/definitions/LayoutOptions" + }, + "properties": { + "$ref": "#/definitions/ElkProperties" + }, + "width": { + "type": "number" + }, + "x": { + "type": "number" + }, + "y": { + "type": "number" + } + }, + "required": ["id"], + "type": "object" + }, + "ElkEdge": { + "additionalProperties": false, + "properties": { + "id": { + "type": "string" + }, + "junctionPoints": { + "items": { + "$ref": "#/definitions/ElkPoint" + }, + "type": "array" + }, + "labels": { + "items": { + "$ref": "#/definitions/ElkLabel" + }, + "type": "array" + }, + "layoutOptions": { + "$ref": "#/definitions/LayoutOptions" + } + }, + "required": ["id"], + "type": "object" + }, + "ElkEdgeSection": { + "additionalProperties": false, + "properties": { + "bendPoints": { + "items": { + "$ref": "#/definitions/ElkPoint" + }, + "type": "array" + }, + "endPoint": { + "$ref": "#/definitions/ElkPoint" + }, + "id": { + "type": "string" + }, + "incomingSections": { + "items": { + "type": "string" + }, + "type": "array" + }, + "incomingShape": { + "type": "string" + }, + "labels": { + "items": { + "$ref": "#/definitions/ElkLabel" + }, + "type": "array" + }, + "layoutOptions": { + "$ref": "#/definitions/LayoutOptions" + }, + "outgoingSections": { + "items": { + "type": "string" + }, + "type": "array" + }, + "outgoingShape": { + "type": "string" + }, + "startPoint": { + "$ref": "#/definitions/ElkPoint" + } + }, + "required": ["endPoint", "id", "startPoint"], + "type": "object" + }, + "ElkExtendedEdge": { + "additionalProperties": false, + "properties": { + "id": { + "type": "string" + }, + "junctionPoints": { + "items": { + "$ref": "#/definitions/ElkPoint" + }, + "type": "array" + }, + "labels": { + "items": { + "$ref": "#/definitions/ElkLabel" + }, + "type": "array" + }, + "layoutOptions": { + "$ref": "#/definitions/LayoutOptions" + }, + "sections": { + "items": { + "$ref": "#/definitions/ElkEdgeSection" + }, + "type": "array" + }, + "sources": { + "items": { + "type": "string" + }, + "type": "array" + }, + "targets": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": ["id", "sections", "sources", "targets"], + "type": "object" + }, + "ElkGraphElement": { + "additionalProperties": false, + "properties": { + "id": { + "type": "string" + }, + "labels": { + "items": { + "$ref": "#/definitions/ElkLabel" + }, + "type": "array" + }, + "layoutOptions": { + "$ref": "#/definitions/LayoutOptions" + } + }, + "required": ["id"], + "type": "object" + }, + "ElkLabel": { + "additionalProperties": false, + "properties": { + "height": { + "type": "number" + }, + "id": { + "type": "string" + }, + "labels": { + "items": { + "$ref": "#/definitions/ElkLabel" + }, + "type": "array" + }, + "layoutOptions": { + "$ref": "#/definitions/LayoutOptions" + }, + "text": { + "type": "string" + }, + "width": { + "type": "number" + }, + "x": { + "type": "number" + }, + "y": { + "type": "number" + } + }, + "required": ["id", "text"], + "type": "object" + }, + "ElkNode": { + "additionalProperties": false, + "properties": { + "children": { + "items": { + "$ref": "#/definitions/ElkNode" + }, + "type": "array" + }, + "edges": { + "items": { + "$ref": "#/definitions/ElkEdge" + }, + "type": "array" + }, + "height": { + "type": "number" + }, + "id": { + "type": "string" + }, + "labels": { + "items": { + "$ref": "#/definitions/ElkLabel" + }, + "type": "array" + }, + "layoutOptions": { + "$ref": "#/definitions/LayoutOptions" + }, + "ports": { + "items": { + "$ref": "#/definitions/ElkPort" + }, + "type": "array" + }, + "width": { + "type": "number" + }, + "x": { + "type": "number" + }, + "y": { + "type": "number" + } + }, + "required": ["id"], + "type": "object" + }, + "ElkPoint": { + "additionalProperties": false, + "properties": { + "x": { + "type": "number" + }, + "y": { + "type": "number" + } + }, + "required": ["x", "y"], + "type": "object" + }, + "ElkPort": { + "additionalProperties": false, + "properties": { + "height": { + "type": "number" + }, + "id": { + "type": "string" + }, + "labels": { + "items": { + "$ref": "#/definitions/ElkLabel" + }, + "type": "array" + }, + "layoutOptions": { + "$ref": "#/definitions/LayoutOptions" + }, + "width": { + "type": "number" + }, + "x": { + "type": "number" + }, + "y": { + "type": "number" + } + }, + "required": ["id"], + "type": "object" + }, + "ElkPrimitiveEdge": { + "additionalProperties": false, + "properties": { + "bendPoints": { + "items": { + "$ref": "#/definitions/ElkPoint" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "junctionPoints": { + "items": { + "$ref": "#/definitions/ElkPoint" + }, + "type": "array" + }, + "labels": { + "items": { + "$ref": "#/definitions/ElkLabel" + }, + "type": "array" + }, + "layoutOptions": { + "$ref": "#/definitions/LayoutOptions" + }, + "source": { + "type": "string" + }, + "sourcePoint": { + "$ref": "#/definitions/ElkPoint" + }, + "sourcePort": { + "type": "string" + }, + "target": { + "type": "string" + }, + "targetPoint": { + "$ref": "#/definitions/ElkPoint" + }, + "targetPort": { + "type": "string" + } + }, + "required": ["id", "source", "target"], + "type": "object" + }, + "ElkProperties": { + "additionalProperties": false, + "properties": { + "cssClasses": { + "type": "string" + }, + "isDef": { + "type": "boolean" + }, + "shape": { + "$ref": "#/definitions/Shape" + }, + "type": { + "type": "string" + } + }, + "type": "object" + }, + "ElkShape": { + "additionalProperties": false, + "properties": { + "height": { + "type": "number" + }, + "id": { + "type": "string" + }, + "labels": { + "items": { + "$ref": "#/definitions/ElkLabel" + }, + "type": "array" + }, + "layoutOptions": { + "$ref": "#/definitions/LayoutOptions" + }, + "width": { + "type": "number" + }, + "x": { + "type": "number" + }, + "y": { + "type": "number" + } + }, + "required": ["id"], + "type": "object" + }, + "LayoutOptions": { + "additionalProperties": { + "type": "string" + }, + "description": "***************************************************************************** Copyright (c) 2019 TypeFox and others. All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse Public License v1.0 which accompanies this distribution, and is available at http://www.eclipse.org/legal/epl-v10.html *****************************************************************************", + "type": "object" + }, + "LazyElkEdge": { + "additionalProperties": false, + "properties": { + "id": { + "type": "string" + }, + "junctionPoints": { + "items": { + "$ref": "#/definitions/ElkPoint" + }, + "type": "array" + }, + "labels": { + "items": { + "$ref": "#/definitions/AnyElkLabelWithProperties" + }, + "type": "array" + }, + "layoutOptions": { + "$ref": "#/definitions/LayoutOptions" + }, + "sources": { + "items": { + "type": "string" + }, + "type": "array" + }, + "targets": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": ["id", "sources", "targets"], + "type": "object" + }, + "Shape": { + "additionalProperties": false, + "properties": { + "end": { + "type": "string" + }, + "height": { + "type": "number" + }, + "start": { + "type": "string" + }, + "use": { + "type": "string" + }, + "width": { + "type": "number" + }, + "x": { + "type": "number" + }, + "y": { + "type": "number" + } + }, + "type": "object" + } + } +} diff --git a/py_src/ipyelk/schema/validator.py b/py_src/ipyelk/schema/validator.py index 6e2ce02f..83ddc31e 100644 --- a/py_src/ipyelk/schema/validator.py +++ b/py_src/ipyelk/schema/validator.py @@ -8,16 +8,9 @@ import jsonschema -try: - HERE = Path(__file__).parent - SCHEMA = json.loads((HERE / "elkschema.json").read_text(encoding="utf-8")) - SCHEMA["$ref"] = "#/definitions/AnyElkNode" -except FileNotFoundError as E: - import os - if os.environ.get("READTHEDOCS", False): - SCHEMA = {} # use dummy schema while building the docs - else: - raise E +HERE = Path(__file__).parent +SCHEMA = json.loads((HERE / "elkschema.json").read_text(encoding="utf-8")) +SCHEMA["$ref"] = "#/definitions/AnyElkNode" ElkSchemaValidator = jsonschema.Draft7Validator(SCHEMA) From e423c2b5e1021f5164ac588622b633dc27e868e7 Mon Sep 17 00:00:00 2001 From: Dane Freeman Date: Thu, 21 Jan 2021 14:30:04 -0500 Subject: [PATCH 07/11] cleaning up function annotations --- docs/reference/transformers.md | 3 +- docs/reference/widgets.md | 4 +- py_src/ipyelk/app.py | 8 +++ py_src/ipyelk/layouting/elkjs.py | 2 +- py_src/ipyelk/nx/transformer.py | 92 ++++++++++++++++---------------- py_src/ipyelk/transform.py | 13 ++++- 6 files changed, 70 insertions(+), 52 deletions(-) diff --git a/docs/reference/transformers.md b/docs/reference/transformers.md index 53a2ff45..167def96 100644 --- a/docs/reference/transformers.md +++ b/docs/reference/transformers.md @@ -1,5 +1,6 @@ # Transformers -The goal of transformers it to take some generalized objects and +The goal of transformers is to take some generalized source and generate valid +elk json to pass to an ElkDiagram widget for layout. ## Transformer diff --git a/docs/reference/widgets.md b/docs/reference/widgets.md index b6c8cb15..56152da6 100644 --- a/docs/reference/widgets.md +++ b/docs/reference/widgets.md @@ -1,14 +1,16 @@ # Widgets ## Diagram Widget - ```{eval-rst} +.. currentmodule:: ipyelk.diagram.elk_widget .. autoclass:: ipyelk.diagram.elk_widget.ElkDiagram :members: ``` ## App Widget ```{eval-rst} +.. currentmodule:: ipyelk.app .. autoclass:: ipyelk.app.Elk :members: + :autoattribute: hovered ``` diff --git a/py_src/ipyelk/app.py b/py_src/ipyelk/app.py index 488112cc..ddfb7308 100644 --- a/py_src/ipyelk/app.py +++ b/py_src/ipyelk/app.py @@ -15,6 +15,14 @@ class Elk(W.VBox, StyledWidget): :py:class:`~ipyelk.diagram.elk_widget.ElkDiagram` and :py:class:`~ipyelk.transform.ElkTransformer` + Attributes + ---------- + + transformer: :py:class:`~ipyelk.diagram.elk_widget.ElkDiagram` + Transformer to convert source objects into valid elk json value + diagram: :py:class:`~ipyelk.diagram.elk_widget.ElkDiagram` + + :param toolbar: Toolar for widget """ transformer: ElkTransformer = T.Instance(ElkTransformer) diff --git a/py_src/ipyelk/layouting/elkjs.py b/py_src/ipyelk/layouting/elkjs.py index 16f5110f..4afdb91c 100644 --- a/py_src/ipyelk/layouting/elkjs.py +++ b/py_src/ipyelk/layouting/elkjs.py @@ -45,7 +45,7 @@ def __init__(self, *args, **kwargs): asyncio.create_task(self._process_requests()) asyncio.create_task(self._process_responses()) - async def layout(self, value:Dict): + async def layout(self, value: Dict): """Pass the value to [elkjs](https://github.com/kieler/elkjs) layout""" future = asyncio.Future() request = LayoutRequest(payload=value) diff --git a/py_src/ipyelk/nx/transformer.py b/py_src/ipyelk/nx/transformer.py index 140fc6e8..90137843 100644 --- a/py_src/ipyelk/nx/transformer.py +++ b/py_src/ipyelk/nx/transformer.py @@ -41,13 +41,23 @@ class XELK(ElkTransformer): - """NetworkX source graphs to a valid ELK JSON dictionary structure""" + """NetworkX source graphs to a valid ELK JSON dictionary structure + + :param source: Tuple of NetworkX graphs. The first graph contains + node/port/edge information while the second graph contains the node + hierarchy. + + :param value: Output elk json + + """ HIDDEN_ATTR = "hidden" hoist_hidden_edges: bool = True - source = T.Tuple(T.Instance(nx.Graph), T.Instance(nx.DiGraph, allow_none=True)) - layouts = T.Dict() # keys: networkx nodes {ElkElements: {layout options}} + source: Tuple[nx.Graph, nx.DiGraph] = T.Tuple( + T.Instance(nx.Graph), T.Instance(nx.DiGraph, allow_none=True) + ) + layouts = T.Dict() # keys: NetworkX nodes {ElkElements: {layout options}} css_classes = T.Dict() port_scale = T.Int(default_value=5) @@ -87,9 +97,7 @@ def node_id(self, node: Hashable) -> str: """Get the element id for a node in the main graph for use in elk :param node: Node in main graph - :type node: Hashable :return: Element ID - :rtype: str """ g, tree = self.source if node is ElkRoot: @@ -101,12 +109,9 @@ def node_id(self, node: Hashable) -> str: def port_id(self, node: Hashable, port: Optional[Hashable] = None) -> str: """Get a suitable Elk identifier from the node and port - :param node: Node from the incoming networkx graph - :type node: Hashable + :param node: Node from the incoming NetworkX graph :param port: Port identifier, defaults to None - :type port: Optional[Hashable], optional :return: If no port is provided will refer to the node - :rtype: str """ if port is None: return self.node_id(node) @@ -122,14 +127,13 @@ def edge_id(self, edge: Edge): async def transform(self) -> ElkNode: """Generate ELK dictionary structure :return: Root Elk node - :rtype: ElkNode """ # TODO decide behavior for nodes that exist in the tree but not g g, tree = self.source self.clear_registry() visible_edges, hidden_edges = self.collect_edges() - # Process visible networkx nodes into elknodes + # Process visible NetworkX nodes into elknodes visible_nodes = [ n for n in g.nodes() if not is_hidden(tree, n, self.HIDDEN_ATTR) ] @@ -187,7 +191,12 @@ async def transform(self) -> ElkNode: return top - async def make_elknode(self, node) -> Tuple[ElkNode, PortMap]: + async def make_elknode(self, node: Hashable) -> Tuple[ElkNode, PortMap]: + """Get the elknode and ports associated with given NetworkX node + + :param node: Incoming NetworkX node + :return: ElkNode and Ports + """ # merge layout options defined on the node data with default layout # options node_data = self.get_node_data(node) @@ -197,7 +206,7 @@ async def make_elknode(self, node) -> Tuple[ElkNode, PortMap]: ) labels = await self.make_labels(node) - # update port map with declared ports in the networkx node data + # update port map with declared ports in the NetworkX node data node_ports = await self.collect_ports(node) properties = self.get_properties(node, self.get_css(node, ElkNode)) or None @@ -215,15 +224,12 @@ async def make_elknode(self, node) -> Tuple[ElkNode, PortMap]: def get_layout( self, node: Hashable, elk_type: Type[ElkGraphElement] ) -> Optional[Dict]: - """Get the Elk Layout Options appropriate for given networkx node and + """Get the Elk Layout Options appropriate for given NetworkX node and filter by given elk_type - :param node: [description] - :type node: Hashable - :param elk_type: [description] - :type elk_type: Type[ElkGraphElement] - :return: [description] - :rtype: [type] + :param node: NetworkX node + :param elk_type: Elk Graph Element to apply layouts to + :return: Dictionary of relevant layout options """ # TODO look at self.source hierarchy and resolve layout with added # infomation. until then use root node `None` for layout options @@ -242,13 +248,10 @@ def get_properties( ) -> Dict: """Get the properties for a graph element - :param element: Networkx node or edge - :type node: Hashable + :param element: NetworkX node or edge :param dom_classes: Set of base CSS DOM classes to merge, defaults to Set[str]=None - :type dom_classes: [type], optional :return: Set of CSS Classes to apply - :rtype: Set[str] """ g, tree = self.source @@ -285,18 +288,14 @@ def get_css( elk_type: Type[ElkGraphElement], dom_classes: Set[str] = None, ) -> Set[str]: - """Get the CSS Classes appropriate for given networkx node given + """Get the CSS Classes appropriate for given NetworkX node given elk_type - :param node: Networkx node - :type node: Hashable + :param node: NetworkX node :param elk_type: ElkGraphElement to get appropriate css classes - :type elk_type: Type[ElkGraphElement] :param dom_classes: Set of base CSS DOM classes to merge, defaults to Set[str]=None - :type dom_classes: [type], optional :return: Set of CSS Classes to apply - :rtype: Set[str] """ typed_css = self.css_classes.get(node, {}) css_classes = set(typed_css.get(elk_type, [])) @@ -341,11 +340,8 @@ async def make_edge( """Make the associated Elk edge for the given Edge :param edge: Edge object to wrap - :type edge: Edge :param styles: List of css classes to add to given Elk edge, defaults to None - :type styles: Optional[List[str]], optional :return: Elk edge - :rtype: ElkExtendedEdge """ labels = [] @@ -383,14 +379,10 @@ async def make_port( ) -> Port: """Make the associated elk port for the given owner node and port - :param owner: [description] - :type owner: Hashable - :param port: [description] - :type port: Hashable + :param owner: NetworkX node + :param port: NetworkX port name :param styles: list of css classes to apply to given ElkPort - :type styles: List[str] - :return: [description] - :rtype: ElkPort + :return: Elk port """ port_id = self.port_id(owner, port) properties = self.get_properties(port, styles) or None @@ -409,6 +401,10 @@ def get_node_data(self, node: Hashable) -> Dict: return g.nodes.get(node, {}) async def collect_ports(self, *nodes) -> PortMap: + """Get port map for list of incoming NetworkX nodes + + :return: ElkPort objects mapped to their + """ ports: PortMap = {} for node in nodes: values = self.get_node_data(node).get(self.port_key, []) @@ -438,7 +434,12 @@ async def collect_ports(self, *nodes) -> PortMap: ports[elkport.id] = Port(node=node, elkport=elkport) return ports - async def make_labels(self, node: Hashable) -> Optional[List[ElkLabel]]: + async def make_labels(self, node: Hashable) -> List[ElkLabel]: + """Get associated ElkLabels for a given input NetworkX node. + + :param node: Input NetworkX node + :return: List of ElkLabels + """ labels = [] g = self.source[0] data = g.nodes.get(node, {}) @@ -478,13 +479,10 @@ async def make_labels(self, node: Hashable) -> Optional[List[ElkLabel]]: return labels def collect_edges(self) -> Tuple[EdgeMap, EdgeMap]: - """[summary] + """Method to process edges in the NetworkX source graph and separate + visible edges and hidden edges. - :return: [description] - :rtype: Tuple[ - Dict[Hashable, List[ElkExtendedEdge]], - Dict[Hashable, List[ElkExtendedEdge]] - ] + :return: Visible edge mapping and hidden edge mapping. """ visible: EdgeMap = defaultdict( list diff --git a/py_src/ipyelk/transform.py b/py_src/ipyelk/transform.py index b64314d3..d3e7ae82 100644 --- a/py_src/ipyelk/transform.py +++ b/py_src/ipyelk/transform.py @@ -106,14 +106,23 @@ def from_id(self, element_id: str) -> Hashable: ) from E def to_id(self, item: Hashable) -> str: - """Use original objects to find elk id""" + """Use original objects to find elk id + + :param item: item to convert to the elk identifier + :raises ElkRegistryError: If unable to find item in the registry + :return: elk identifier + """ try: return self._item_to_elk[item] except KeyError as E: raise ElkRegistryError(f"Item `{item}` not in elk id registry.") from E def connect(self, view: ElkDiagram) -> T.link: - """Connect the output value of this transformer to a diagram""" + """Connect the output elk json value of this transformer to a diagram + + :param view: Elk diagram to link elk json values. + :return: traitlet link + """ return T.dlink((self, "value"), (view, "value")) def register(self, element: Union[str, ElkGraphElement], item: Hashable): From e1ff5806e3cc02eec4deead79e61c5c74816b769 Mon Sep 17 00:00:00 2001 From: Dane Freeman Date: Thu, 21 Jan 2021 14:32:04 -0500 Subject: [PATCH 08/11] add long term 0.3.x branch as target in ci --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5b46d7bd..f7a71394 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,9 +4,11 @@ on: push: branches: - master + - 0.3.x pull_request: branches: - master + - 0.3.x env: ATEST_RETRIES: 2 From 0effa131722f0a85d4824d95daa2438d3e7e77ff Mon Sep 17 00:00:00 2001 From: Dane Freeman Date: Thu, 21 Jan 2021 14:40:46 -0500 Subject: [PATCH 09/11] bump to development version 0.3.1 --- CHANGELOG.md | 6 ++++++ package.json | 2 +- py_src/ipyelk/_version.py | 4 ++-- src/index.ts | 2 +- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ae6c9c0..bb906b0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## @jupyrdf/jupyter-elk 0.3.1 + +## ipyelk 0.3.1 + +--- + ## @jupyrdf/jupyter-elk 0.3.0 ## ipyelk 0.3.0 diff --git a/package.json b/package.json index 29c8d45f..b317fd13 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@jupyrdf/jupyter-elk", - "version": "0.3.0", + "version": "0.3.1", "description": "ElkJS widget for Jupyter", "keywords": [ "jupyter", diff --git a/py_src/ipyelk/_version.py b/py_src/ipyelk/_version.py index 0aa2c4ce..0f7b4963 100644 --- a/py_src/ipyelk/_version.py +++ b/py_src/ipyelk/_version.py @@ -1,7 +1,7 @@ # Copyright (c) 2021 Dane Freeman. # Distributed under the terms of the Modified BSD License. -__version__ = "0.3.0" +__version__ = "0.3.1" # The version of the attribute spec that this package # implements. This is the value used in @@ -10,4 +10,4 @@ # Update this value when attributes are added/removed from # your models, or serialized format changes. EXTENSION_NAME = "@jupyrdf/jupyter-elk" -EXTENSION_SPEC_VERSION = "0.3.0" +EXTENSION_SPEC_VERSION = "0.3.1" diff --git a/src/index.ts b/src/index.ts index 2aef8850..4ffc0069 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,7 +3,7 @@ * Distributed under the terms of the Modified BSD License. */ export const NAME = '@jupyrdf/jupyter-elk'; -export const VERSION = '0.3.0'; +export const VERSION = '0.3.1'; export const ELK_DEBUG = window.location.hash.indexOf('ELK_DEBUG') > -1; From 22b90b9cae6f175b2dcd104dc8e4bb906556193d Mon Sep 17 00:00:00 2001 From: Dane Freeman Date: Fri, 22 Jan 2021 09:47:53 -0500 Subject: [PATCH 10/11] cleanup --- docs/index.md | 1 + docs/reference/index.md | 1 + docs/reference/transformers.md | 14 ++++++++---- docs/reference/widgets.md | 11 +++++++++- docs/rtd.yml | 2 +- py_src/ipyelk/diagram/elk_widget.py | 33 +++++++++++++++++++++-------- py_src/ipyelk/transform.py | 2 ++ 7 files changed, 49 insertions(+), 15 deletions(-) diff --git a/docs/index.md b/docs/index.md index 52f6dd35..abf63fb0 100644 --- a/docs/index.md +++ b/docs/index.md @@ -12,3 +12,4 @@ contributing :hidden: :titlesonly: reference/index +``` diff --git a/docs/reference/index.md b/docs/reference/index.md index 1c57863d..6199eac5 100644 --- a/docs/reference/index.md +++ b/docs/reference/index.md @@ -1,4 +1,5 @@ # API Reference + This page describe the overall API for ipyelk. ```{toctree} diff --git a/docs/reference/transformers.md b/docs/reference/transformers.md index 167def96..42c34a86 100644 --- a/docs/reference/transformers.md +++ b/docs/reference/transformers.md @@ -1,18 +1,24 @@ # Transformers -The goal of transformers is to take some generalized source and generate valid -elk json to pass to an ElkDiagram widget for layout. + +The goal of transformers is to take some generalized source and generate valid elk json +to pass to an ElkDiagram widget for layout. ## Transformer ```{eval-rst} -.. autoclass:: ipyelk.transform.ElkTransformer +.. currentmodule:: ipyelk.transform +.. autoclass:: EdgeMap +.. autoclass:: ElkTransformer :members: ``` ## NetworkX Transformer ```{eval-rst} -.. autoclass:: ipyelk.nx.transformer.XELK +.. currentmodule:: ipyelk.nx.transformer + + +.. autoclass:: XELK :show-inheritance: :members: ``` diff --git a/docs/reference/widgets.md b/docs/reference/widgets.md index 56152da6..e08373c0 100644 --- a/docs/reference/widgets.md +++ b/docs/reference/widgets.md @@ -1,6 +1,7 @@ # Widgets ## Diagram Widget + ```{eval-rst} .. currentmodule:: ipyelk.diagram.elk_widget .. autoclass:: ipyelk.diagram.elk_widget.ElkDiagram @@ -8,9 +9,17 @@ ``` ## App Widget + ```{eval-rst} .. currentmodule:: ipyelk.app .. autoclass:: ipyelk.app.Elk :members: - :autoattribute: hovered +``` + +## Layout Widget + +```{eval-rst} +.. currentmodule:: ipyelk.layouting.elkjs +.. autoclass:: ipyelk.layouting.elkjs.ElkJS + :members: ``` diff --git a/docs/rtd.yml b/docs/rtd.yml index ca128963..b93336f7 100644 --- a/docs/rtd.yml +++ b/docs/rtd.yml @@ -11,4 +11,4 @@ dependencies: - sphinx - sphinx-autodoc-typehints - pip: - - -e .. \ No newline at end of file + - -e .. diff --git a/py_src/ipyelk/diagram/elk_widget.py b/py_src/ipyelk/diagram/elk_widget.py index 37448263..309741ad 100644 --- a/py_src/ipyelk/diagram/elk_widget.py +++ b/py_src/ipyelk/diagram/elk_widget.py @@ -3,7 +3,7 @@ # Copyright (c) 2021 Dane Freeman. # Distributed under the terms of the Modified BSD License. -from typing import List +from typing import Dict, List, Tuple import traitlets as T from ipywidgets import CallbackDispatcher, DOMWidget, widget_serialization @@ -16,12 +16,27 @@ class ElkDiagram(DOMWidget): - """Jupyterlab widget for interacting with ELK diagrams. + """Jupyterlab widget for displaying and interacting with diagrams generated + from elk json. Setting the instance's `value` traitlet to valid `elk json - `_ - will call the `elkjs layout method `_ and - display the returned `mark_layout` using `sprotty `_. + `_ will call the `elkjs layout method + `_ and display the returned `mark_layout` + using `sprotty `_. + + :ivar value: Input elk json + :vartype value: Dict + :ivar mark_layout: Resulting layout from current layouter e.g. elkjs + :vartype mark_layout: Dict + :ivar selected: elk ids of selected marks + :vartype selected: Tuple[str] + :ivar hovered: elk id of currently hovered mark + :vartype hovered: str + :ivar layouter: A layouter to add position and sizes to marks in the incoming + elk json + :vartype layouter: ElkJS + """ _model_name = T.Unicode("ELKDiagramModel").tag(sync=True) @@ -35,10 +50,10 @@ class ElkDiagram(DOMWidget): defs = T.Dict(value_trait=T.Instance(Def), kw={}).tag( sync=True, **def_serialization ) - mark_layout = T.Dict().tag(sync=True) - selected = T.Tuple().tag(sync=True) - hovered = T.Unicode(allow_none=True, default_value=None).tag(sync=True) - layouter = T.Instance(ElkJS, kw={}).tag(sync=True, **widget_serialization) + mark_layout: Dict = T.Dict().tag(sync=True) + selected: Tuple = T.Tuple().tag(sync=True) + hovered: str = T.Unicode(allow_none=True, default_value=None).tag(sync=True) + layouter: ElkJS = T.Instance(ElkJS, kw={}).tag(sync=True, **widget_serialization) def __init__(self, *value, **kwargs): if value: diff --git a/py_src/ipyelk/transform.py b/py_src/ipyelk/transform.py index d3e7ae82..8c1b31b8 100644 --- a/py_src/ipyelk/transform.py +++ b/py_src/ipyelk/transform.py @@ -37,6 +37,8 @@ def __hash__(self): return hash(tuple([hash(self.node), hash(self.elkport.id)])) +# TODO investigating following pattern for various map +# https://github.com/pandas-dev/pandas/issues/33025#issuecomment-699636759 NodeMap = Dict[Hashable, ElkNode] EdgeMap = Dict[Hashable, List[Edge]] PortMap = Dict[Hashable, Port] From 7c91b41f3ec726be2da4988fbea2675d04ad4d5a Mon Sep 17 00:00:00 2001 From: Dane Freeman Date: Sun, 31 Jan 2021 19:15:52 -0500 Subject: [PATCH 11/11] adding in pip install --- dodo.py | 19 ++++++++++++++++--- scripts/project.py | 3 ++- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/dodo.py b/dodo.py index bd390c63..ff26bf5b 100644 --- a/dodo.py +++ b/dodo.py @@ -142,7 +142,7 @@ def task_binder(): def task_env(): """prepare project envs""" - envs = ["default", "atest"] + envs = ["default", "atest", "docs"] for i, env in enumerate(envs): file_dep = [P.PROJ_LOCK, P.OK_PREFLIGHT_CONDA] if P.FORCE_SERIAL_ENV_PREP and i: @@ -231,6 +231,19 @@ def task_setup(): P.OK_LABEXT, ) +def task_setup_docs(): + _install = ["--no-deps", "--ignore-installed", "-vv", "-e", "."] + yield _ok( + dict( + name="docs py setup", + file_dep=[P.OK_ENV["docs"]], + actions=[ + [*P.APR, "docs", *P.PIP, "install", *_install], + [*P.APR, "docs", *P.PIP, "check"], + ], + ), + P.OK_DOCS_PIP_INSTALL + ) if not P.TESTING_IN_CI: @@ -583,7 +596,7 @@ def task_docs(): """build the docs (mostly as readthedocs would)""" yield dict( name="sphinx", - file_dep=[P.DOCS_CONF, *P.ALL_PY_SRC, *P.ALL_MD], + file_dep=[P.DOCS_CONF, *P.ALL_PY_SRC, *P.ALL_MD, P.OK_DOCS_PIP_INSTALL], targets=[P.DOCS_BUILDINFO], actions=[[*P.APR_DOCS, "docs"]], ) @@ -594,7 +607,7 @@ def task_watch_docs(): yield dict( uptodate=[lambda: False], name="sphinx-autobuild", - file_dep=[P.DOCS_BUILDINFO, *P.ALL_MD], + file_dep=[P.DOCS_BUILDINFO, *P.ALL_MD, P.OK_DOCS_PIP_INSTALL], actions=[ LongRunning( [*P.APR_DOCS, "sphinx-autobuild", P.DOCS, P.DOCS_BUILD], shell=False diff --git a/scripts/project.py b/scripts/project.py index 1dad6549..f8bf1510 100644 --- a/scripts/project.py +++ b/scripts/project.py @@ -102,7 +102,7 @@ ] # env stuff -OK_ENV = {env: BUILD / f"prep_{env}.ok" for env in ["default", "atest"]} +OK_ENV = {env: BUILD / f"prep_{env}.ok" for env in ["default", "atest", "docs"]} # python stuff PY_SRC = ROOT / "py_src" / PY_PKG @@ -167,6 +167,7 @@ OK_PYFLAKES = BUILD / "pyflakes.ok" OK_NBLINT = {nb.name: BUILD / f"nblint.{nb.name}.ok" for nb in EXAMPLE_IPYNB} OK_PIP_INSTALL = BUILD / "pip_install.ok" +OK_DOCS_PIP_INSTALL = BUILD / "docs_pip_install.ok" OK_PRETTIER = BUILD / "prettier.ok" OK_INDEX = BUILD / "index.ok" OK_LABEXT = BUILD / "labext.ok"