-
Notifications
You must be signed in to change notification settings - Fork 0
Save port positions in the elk graph #20
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
0d7b7d3
8a07ae2
e489bee
b7fcc35
1b36d1f
6ce3ac2
7be6249
88f9aae
61b59cf
2692a68
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,17 +1,32 @@ | ||
| import warnings | ||
| from typing import Any | ||
| from typing import TypedDict | ||
|
|
||
| from ewokscore.graph import TaskGraph | ||
|
|
||
| from ewoksdraw.config.constants import ELK_LAYOUT_OPTIONS | ||
|
|
||
| from ..svg.svg_task import TaskIOPosition | ||
| from ..svg.svg_task_group import TaskInputPositions | ||
| from ..svg.svg_task_group import TaskOutputPositions | ||
| from ..svg.svg_task_group import TaskSizes | ||
|
|
||
|
|
||
| class ElkPort(TypedDict): | ||
| id: str | ||
| x: float | ||
| y: float | ||
| width: float | ||
| height: float | ||
| layoutOptions: dict[str, Any] | ||
|
|
||
|
|
||
| class ElkChild(TypedDict): | ||
| id: str | ||
| width: float | ||
| height: float | ||
| layoutOptions: dict[str, Any] | ||
| ports: list[ElkPort] | ||
|
|
||
|
|
||
| class ElkEdge(TypedDict): | ||
|
|
@@ -28,13 +43,17 @@ class ElkGraph(TypedDict): | |
|
|
||
|
|
||
| def convert_ewoks_to_elk_graph( | ||
| ewoks_graph: TaskGraph, task_sizes: TaskSizes | ||
| ewoks_graph: TaskGraph, | ||
| task_sizes: TaskSizes, | ||
| task_input_positions: TaskInputPositions, | ||
| task_output_positions: TaskOutputPositions, | ||
| ) -> ElkGraph: | ||
| """Convert an Ewoks task graph into an ELK layout graph. | ||
|
|
||
| :param ewoks_graph: the task graph to convert, e.g. from ``ewokscore.load_graph``. | ||
| :param task_sizes: ``(width, height)`` per task in ``ewoks_graph``, and no | ||
| other task id, e.g. ``{"task1": (39.56, 55.0), ...}``. | ||
| :param task_sizes: width and height of each task. | ||
| :param task_input_positions: input positions of each task. | ||
| :param task_output_positions: output positions of each task. | ||
| """ | ||
| node_ids = set(ewoks_graph.graph.nodes) | ||
| if node_ids != task_sizes.keys(): | ||
|
|
@@ -43,30 +62,135 @@ def convert_ewoks_to_elk_graph( | |
| f"{sorted(node_ids)}" | ||
| ) | ||
|
|
||
| children: list[ElkChild] = [ | ||
| { | ||
| "id": task_id, | ||
| "width": task_sizes[task_id].width, | ||
| "height": task_sizes[task_id].height, | ||
| } | ||
| for task_id in ewoks_graph.graph.nodes | ||
| ] | ||
| if node_ids != task_input_positions.keys(): | ||
| raise ValueError( | ||
| "task_input_positions " | ||
| f"{sorted(task_input_positions)} do not match ewoks_graph task ids " | ||
| f"{sorted(node_ids)}" | ||
| ) | ||
|
|
||
| if node_ids != task_output_positions.keys(): | ||
| raise ValueError( | ||
| "task_output_positions " | ||
| f"{sorted(task_output_positions)} do not match ewoks_graph task ids " | ||
| f"{sorted(node_ids)}" | ||
| ) | ||
|
|
||
| children: list[ElkChild] = [] | ||
| used_ids: set[str] = set() | ||
| for task_id in ewoks_graph.graph.nodes: | ||
|
LudoBroche marked this conversation as resolved.
|
||
| ports = _convert_io_positions_to_elk_ports( | ||
| task_id, | ||
| task_input_positions[task_id], | ||
| task_output_positions[task_id], | ||
| ) | ||
| children.append( | ||
| { | ||
| "id": task_id, | ||
| "width": task_sizes[task_id].width, | ||
| "height": task_sizes[task_id].height, | ||
| "layoutOptions": { | ||
| "org.eclipse.elk.portConstraints": "FIXED_POS", | ||
| }, | ||
| "ports": ports, | ||
| } | ||
| ) | ||
| used_ids.add(task_id) | ||
| used_ids.update(port["id"] for port in ports) | ||
|
|
||
| root_id = _available_elk_id("__ewoksdraw_root__", used_ids) | ||
| used_ids.add(root_id) | ||
|
|
||
| edges: list[ElkEdge] = [] | ||
| for source, target, link_attrs in ewoks_graph.graph.edges(data=True): | ||
| n_mappings = len(link_attrs.get("data_mapping", [])) or 1 | ||
| for index in range(n_mappings): | ||
| if link_attrs.get("map_all_data", False): | ||
| warnings.warn( | ||
|
LudoBroche marked this conversation as resolved.
|
||
| f"Ewoks link {source!r} -> {target!r} uses 'map_all_data', which " | ||
| "is not yet supported.", | ||
| UserWarning, | ||
| stacklevel=2, | ||
| ) | ||
|
|
||
| for mapping in link_attrs.get("data_mapping", []): | ||
| source_output = mapping.get("source_output") | ||
| if source_output is None: | ||
| warnings.warn( | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm also ignoring links that have source_input but no
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is How is it possible to have a link that does not have a target and a source?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry, I meant the mapping has a data_mapping (optional): Describe data transfer from source outputs to target input arguments. For example: {
"data_mapping": [{"source_output": "result",
"target_input": "a"}]
}If "source_output" is None or missing, the complete output of the source will be passed to the corresponding "target_input" or the target.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Gotcha. Didn't even know this was a thing |
||
| f"Data mapping on Ewoks link {source!r} -> {target!r} has no " | ||
| "'source_output', which is not yet supported.", | ||
| UserWarning, | ||
| stacklevel=2, | ||
| ) | ||
| continue | ||
|
|
||
| edge_id = _available_elk_id( | ||
| f"edge_{len(edges)}_{source}_{target}", used_ids | ||
| ) | ||
|
|
||
| edges.append( | ||
| { | ||
| "id": f"edge_{source}_{target}_{index}", | ||
| "sources": [source], | ||
| "targets": [target], | ||
| "id": edge_id, | ||
| "sources": [f"{source}.output.{source_output}"], | ||
| "targets": [f"{target}.input.{mapping['target_input']}"], | ||
| } | ||
| ) | ||
| used_ids.add(edge_id) | ||
|
|
||
| return { | ||
| "id": "root", | ||
| "id": root_id, | ||
| "layoutOptions": ELK_LAYOUT_OPTIONS, | ||
| "children": children, | ||
| "edges": edges, | ||
| } | ||
|
|
||
|
|
||
| def _convert_io_positions_to_elk_ports( | ||
| task_id: str, | ||
| input_positions: list[TaskIOPosition], | ||
| output_positions: list[TaskIOPosition], | ||
| ) -> list[ElkPort]: | ||
| ports = _convert_positions_to_elk_ports( | ||
| input_positions, | ||
| id_prefix=f"{task_id}.input", | ||
| elk_port_side="WEST", | ||
| ) | ||
| ports.extend( | ||
| _convert_positions_to_elk_ports( | ||
| output_positions, | ||
| id_prefix=f"{task_id}.output", | ||
| elk_port_side="EAST", | ||
| ) | ||
| ) | ||
| return ports | ||
|
|
||
|
|
||
|
Comment on lines
146
to
+165
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A new split between input/output allows us to separate the two calls and avoid the constants |
||
| def _convert_positions_to_elk_ports( | ||
| positions: list[TaskIOPosition], id_prefix: str, elk_port_side: str | ||
| ) -> list[ElkPort]: | ||
| ports: list[ElkPort] = [] | ||
|
|
||
| for index, position in enumerate(positions): | ||
| ports.append( | ||
| { | ||
| "id": f"{id_prefix}.{position.name}", | ||
| "x": position.x, | ||
| "y": position.y, | ||
| "width": 0, | ||
| "height": 0, | ||
| "layoutOptions": { | ||
| "org.eclipse.elk.port.side": elk_port_side, | ||
| "org.eclipse.elk.port.index": index, | ||
| "org.eclipse.elk.port.borderOffset": 0, | ||
| }, | ||
| } | ||
| ) | ||
|
|
||
| return ports | ||
|
|
||
|
|
||
| def _available_elk_id(preferred_id: str, used_ids: set[str]) -> str: | ||
|
LudoBroche marked this conversation as resolved.
|
||
| element_id = preferred_id | ||
| index = 1 | ||
| while element_id in used_ids: | ||
| element_id = f"{preferred_id}_{index}" | ||
| index += 1 | ||
| return element_id | ||
Uh oh!
There was an error while loading. Please reload this page.