Skip to content

Save port positions in the elk graph - #20

Merged
LudoBroche merged 10 commits into
mainfrom
19-feature-save-port-positions-in-the-elk-graph
Aug 28, 2026
Merged

Save port positions in the elk graph#20
LudoBroche merged 10 commits into
mainfrom
19-feature-save-port-positions-in-the-elk-graph

Conversation

@LudoBroche

@LudoBroche LudoBroche commented Jul 24, 2026

Copy link
Copy Markdown
Member

Tip

The Feature

  • As a: Developer integrating ewoksdraw with pyelk for automatic graph layout
  • I want to: Include computed task input/output positions in the ELK graph
  • So that: SVG links can use the actual I/O anchors instead of generic box edges

Note

Scope & Boundaries

  • In Scope: Extracting TaskIOPosition values and converting them into fixed WEST input and EAST output ports with x/y coordinates
  • Out of Scope: map_all_data, mappings without source_output, layout algorithm changes, and manual port repositioning

Important

Acceptance Criteria

  • Every task input/output becomes an ELK port with its computed x/y position
  • Supported data-mapping edges connect the corresponding output and input ports
  • Unsupported mappings emit a warning and are not drawn
  • SVG and ELK conversion tests pass

@LudoBroche LudoBroche added this to the ewoksdraw 1.0 release milestone Jul 24, 2026
@LudoBroche LudoBroche self-assigned this Jul 24, 2026
@codecov

codecov Bot commented Jul 24, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.73418% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/ewoksdraw/svg/svg_task_io.py 83.33% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@LudoBroche
LudoBroche force-pushed the 19-feature-save-port-positions-in-the-elk-graph branch from 7f0ce2c to 0e8326c Compare July 29, 2026 12:56
Base automatically changed from 17-feature-flat-converter-from-ewoks-execution-graph-to-elk-layout-graph to main August 5, 2026 12:55
@LudoBroche
LudoBroche force-pushed the 19-feature-save-port-positions-in-the-elk-graph branch from 0e8326c to 9752500 Compare August 5, 2026 14:33
@LudoBroche LudoBroche linked an issue Aug 6, 2026 that may be closed by this pull request
Comment thread examples/convert_ewoks_to_elk_graph.py Outdated
Comment on lines 18 to 27
task_io_positions: TaskIOPositions = svg_task_group.extract_io_positions()
elk_graph: ElkGraph = convert_ewoks_to_elk_graph(
ewoks_graph, task_sizes, task_io_positions
)

pprint(dict(ewoks_graph.graph.nodes(data=True)))
pprint(list(ewoks_graph.graph.edges(data=True)))
pprint(task_sizes)
pprint(task_io_positions)
pprint(elk_graph)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New method to extract io_positions returning a TaskIOPositions.
Example:

{
'task1': [
           TaskIOPosition(name='a', io_type='input', x=0.0, y=20.0),
           TaskIOPosition(name='b', io_type='input', x=0.0, y=28.0),
           TaskIOPosition(name='delay', io_type='input', x=0.0, y=36.0),
           TaskIOPosition(name='result', io_type='output', x=39.56, y=47.0)],
 'task2': [
           TaskIOPosition(name='a', io_type='input', x=0.0, y=20.0),
           TaskIOPosition(name='b', io_type='input', x=0.0, y=28.0),
           TaskIOPosition(name='delay', io_type='input', x=0.0, y=36.0),
           TaskIOPosition(name='result', io_type='output', x=39.56, y=47.0)]
}

The elk_graph representation now includes the TaskIOPositions (called "Ports" in ELK semantic).
Example:

{'children': [{'height': 55.0,
               'id': 'task1',
               'layoutOptions': {'org.eclipse.elk.portConstraints': 'FIXED_POS'},
               'ports': [{'height': 0,
                          'id': 'task1.input.a',
                          'layoutOptions': {'org.eclipse.elk.port.borderOffset': 0,
                                            'org.eclipse.elk.port.index': 0,
                                            'org.eclipse.elk.port.side': 'WEST'},
                          'width': 0,
                          'x': 0.0,
                          'y': 20.0},

                          ....


                         {'height': 0,
                          'id': 'task1.output.result',
                          'layoutOptions': {'org.eclipse.elk.port.borderOffset': 0,
                                            'org.eclipse.elk.port.index': 0,
                                            'org.eclipse.elk.port.side': 'EAST'},
                          'width': 0,
                          'x': 39.56,
                          'y': 47.0}],
  ....
 'edges': [{'id': 'edge_0_task1_task3',
            'sources': ['task1.output.result'],
            'targets': ['task3.input.a']},
           {'id': 'edge_1_task2_task4',
            'sources': ['task2.output.result'],
            'targets': ['task4.input.a']},
           {'id': 'edge_2_task3_task5',
            'sources': ['task3.output.result'],
            'targets': ['task5.input.a']},
           {'id': 'edge_3_task4_task5',
            'sources': ['task4.output.result'],
            'targets': ['task5.input.b']},
           {'id': 'edge_4_task5_task6',
            'sources': ['task5.output.result'],
            'targets': ['task6.input.a']}],

Comment thread src/ewoksdraw/layout/elk_converter.py
Comment thread src/ewoksdraw/layout/elk_converter.py
for mapping in link_attrs.get("data_mapping", []):
source_output = mapping.get("source_output")
if not source_output:
warnings.warn(

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm also ignoring links that have source_input but no source_output in the mapping.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is source_input? Did you mean target_input?

How is it possible to have a link that does not have a target and a source?

@LudoBroche LudoBroche Aug 10, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I meant the mapping has a target_input but no source_ouput
From the doc : ewokscore :

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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gotcha. Didn't even know this was a thing

Comment thread src/ewoksdraw/layout/elk_converter.py Outdated
Comment thread src/ewoksdraw/layout/elk_converter.py
Comment thread src/ewoksdraw/svg/svg_group.py
Comment thread src/ewoksdraw/svg/svg_task.py Outdated
Comment thread src/ewoksdraw/svg/svg_task_io.py
Comment thread src/ewoksdraw/tests/test_svg_task.py
Comment thread CHANGELOG.md Outdated
@LudoBroche
LudoBroche marked this pull request as ready for review August 6, 2026 11:57
@LudoBroche
LudoBroche requested a review from a team August 6, 2026 11:59

@loichuder loichuder left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like some code would read better if we deal with inputs and outputs separately (rather than keeping a io_type flag). I identified already one place where it would simplify the code.

What do think?

Comment thread src/ewoksdraw/layout/elk_converter.py Outdated
for mapping in link_attrs.get("data_mapping", []):
source_output = mapping.get("source_output")
if not source_output:
warnings.warn(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is source_input? Did you mean target_input?

How is it possible to have a link that does not have a target and a source?

Comment thread src/ewoksdraw/layout/elk_converter.py
Comment thread src/ewoksdraw/layout/elk_converter.py Outdated
Comment thread src/ewoksdraw/layout/elk_converter.py
Comment thread src/ewoksdraw/layout/elk_converter.py Outdated
Comment thread CHANGELOG.md Outdated
Comment on lines 134 to +151
def _convert_io_positions_to_elk_ports(
task_id: str, io_positions: list[TaskIOPosition]
task_id: str, io_positions: IOPositions
) -> list[ElkPort]:
ports = _convert_positions_to_elk_ports(
io_positions.inputs,
id_prefix=f"{task_id}.input",
elk_port_side="WEST",
)
ports.extend(
_convert_positions_to_elk_ports(
io_positions.outputs,
id_prefix=f"{task_id}.output",
elk_port_side="EAST",
)
)
return ports


@LudoBroche LudoBroche Aug 10, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The 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

Comment thread src/ewoksdraw/svg/svg_task.py Outdated
y: float


class IOPositions(NamedTuple):

@LudoBroche LudoBroche Aug 10, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of having a flag for input/output, we just contain both directly in a single structure

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally, I would not used a tuple but instead passed inputs and outputs separately down the line.

But I understand this is up to personal preference.

Comment thread src/ewoksdraw/svg/svg_task.py Outdated
Comment on lines +147 to +161
def get_io_positions(self) -> IOPositions:
"""Return the task-relative positions of the task inputs and outputs."""
return IOPositions(
inputs=self._get_group_io_positions(self._inputs),
outputs=self._get_group_io_positions(self._outputs),
)

@staticmethod
def _get_group_io_positions(group: SvgTaskIOGroup) -> list[TaskIOPosition]:
positions: list[TaskIOPosition] = []
for io in group.elements:
x = group.translation.x + io.translation.x
y = group.translation.y + io.translation.y
positions.append(TaskIOPosition(name=io.name, x=x, y=y))
return positions

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New version of the get_io_position with the I/O splits.
Two simple method calls.

@LudoBroche
LudoBroche requested a review from loichuder August 10, 2026 11:45
Comment thread src/ewoksdraw/layout/elk_converter.py
Comment thread src/ewoksdraw/layout/elk_converter.py Outdated
Comment thread src/ewoksdraw/layout/elk_converter.py Outdated
for mapping in link_attrs.get("data_mapping", []):
source_output = mapping.get("source_output")
if not source_output:
warnings.warn(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gotcha. Didn't even know this was a thing

Comment thread src/ewoksdraw/svg/svg_task.py Outdated
y: float


class IOPositions(NamedTuple):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally, I would not used a tuple but instead passed inputs and outputs separately down the line.

But I understand this is up to personal preference.

LudoBroche and others added 6 commits August 28, 2026 15:11
Co-authored-by: Loïc Huder <42204205+loichuder@users.noreply.github.com>
Co-authored-by: Loïc Huder <42204205+loichuder@users.noreply.github.com>
@LudoBroche
LudoBroche force-pushed the 19-feature-save-port-positions-in-the-elk-graph branch from fd98b9b to 61b59cf Compare August 28, 2026 13:11
@LudoBroche

Copy link
Copy Markdown
Member Author

I applied your suggestions.
Mainly splitting inputs / outputs.

@LudoBroche
LudoBroche merged commit 14f12ab into main Aug 28, 2026
6 checks passed
@LudoBroche
LudoBroche deleted the 19-feature-save-port-positions-in-the-elk-graph branch August 28, 2026 13:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Save port positions in the elk graph

2 participants