Skip to content

_elkjs

ELK data model implemented as typings.TypedDicts and subprocess callers to check if elkjs can be installed via npm. The high level function is call_elkjs.

ELKOutputChild = t.Union[ELKOutputEdge, ELKOutputJunction, ELKOutputLabel, ELKOutputNode, ELKOutputPort] module-attribute 🔗

Type alias for ELK output.

LABEL_LAYOUT_OPTIONS = {'nodeLabels.placement': 'OUTSIDE, V_BOTTOM, H_CENTER'} module-attribute 🔗

Options for labels to configure ELK layouting.

LAYOUT_OPTIONS: ImmutableLayoutOptions = {'algorithm': 'layered', 'edgeRouting': 'ORTHOGONAL', 'elk.direction': 'RIGHT', 'hierarchyHandling': 'INCLUDE_CHILDREN', 'layered.edgeLabels.sideSelection': 'ALWAYS_DOWN', 'layered.nodePlacement.strategy': 'BRANDES_KOEPF', 'layered.considerModelOrder.strategy': 'NODES_AND_EDGES', 'spacing.labelNode': '0.0'} module-attribute 🔗

Available (and possibly useful) Global Options to configure ELK layouting.

See Also🔗

get_global_layered_layout_options : A function that instantiates this class with well-tested settings.

REQUIRED_NPM_PKG_VERSIONS: t.Dict[str, str] = {'elkjs': '0.9.2'} module-attribute 🔗

npm package names and versions required by this Python module.

ELKInputChild 🔗

Bases: ELKInputData

Children of either ELKInputData or ELKInputChild.

Source code in capellambse_context_diagrams/_elkjs.py
101
102
103
104
105
106
107
108
class ELKInputChild(ELKInputData, total=False):
    """Children of either `ELKInputData` or `ELKInputChild`."""

    labels: cabc.MutableSequence[ELKInputLabel]
    ports: cabc.MutableSequence[ELKInputPort]

    width: t.Union[int, float]
    height: t.Union[int, float]

ELKInputData 🔗

Bases: TypedDict

Data that can be fed to ELK.

Source code in capellambse_context_diagrams/_elkjs.py
92
93
94
95
96
97
98
class ELKInputData(te.TypedDict, total=False):
    """Data that can be fed to ELK."""

    id: te.Required[str]
    layoutOptions: LayoutOptions
    children: cabc.MutableSequence[ELKInputChild]  # type: ignore
    edges: cabc.MutableSequence[ELKInputEdge]

ELKInputEdge 🔗

Bases: TypedDict

Exchange data that can be fed to ELK.

Source code in capellambse_context_diagrams/_elkjs.py
130
131
132
133
134
135
136
class ELKInputEdge(te.TypedDict):
    """Exchange data that can be fed to ELK."""

    id: str
    sources: cabc.MutableSequence[str]
    targets: cabc.MutableSequence[str]
    labels: te.NotRequired[cabc.MutableSequence[ELKInputLabel]]

ELKInputLabel 🔗

Bases: TypedDict

Label data that can be fed to ELK.

Source code in capellambse_context_diagrams/_elkjs.py
111
112
113
114
115
116
117
class ELKInputLabel(te.TypedDict, total=False):
    """Label data that can be fed to ELK."""

    text: te.Required[str]
    layoutOptions: LayoutOptions
    width: t.Union[int, float]
    height: t.Union[int, float]

ELKInputPort 🔗

Bases: TypedDict

Connector data that can be fed to ELK.

Source code in capellambse_context_diagrams/_elkjs.py
120
121
122
123
124
125
126
127
class ELKInputPort(t.TypedDict):
    """Connector data that can be fed to ELK."""

    id: str
    width: t.Union[int, float]
    height: t.Union[int, float]

    layoutOptions: te.NotRequired[cabc.MutableMapping[str, t.Any]]

ELKOutputData 🔗

Bases: ELKOutputElement

Data that comes from ELK.

Source code in capellambse_context_diagrams/_elkjs.py
161
162
163
164
165
class ELKOutputData(ELKOutputElement):
    """Data that comes from ELK."""

    type: t.Literal["graph"]
    children: cabc.MutableSequence[ELKOutputChild]  # type: ignore

ELKOutputEdge 🔗

Bases: ELKOutputElement

Edge that comes out of ELK.

Source code in capellambse_context_diagrams/_elkjs.py
208
209
210
211
212
213
214
215
216
class ELKOutputEdge(ELKOutputElement):
    """Edge that comes out of ELK."""

    type: t.Literal["edge"]

    sourceId: str
    targetId: str
    routingPoints: cabc.MutableSequence[ELKPoint]
    children: cabc.MutableSequence[ELKOutputLabel]

ELKOutputElement 🔗

Bases: TypedDict

Base class for all elements that comes out of ELK.

Source code in capellambse_context_diagrams/_elkjs.py
153
154
155
156
157
158
class ELKOutputElement(t.TypedDict):
    """Base class for all elements that comes out of ELK."""

    id: str

    style: dict[str, t.Any]

ELKOutputJunction 🔗

Bases: ELKOutputElement

Exchange-Junction that comes out of ELK.

Source code in capellambse_context_diagrams/_elkjs.py
178
179
180
181
182
183
184
185
class ELKOutputJunction(ELKOutputElement):
    """Exchange-Junction that comes out of ELK."""

    type: t.Literal["junction"]
    children: cabc.MutableSequence[ELKOutputLabel]

    position: ELKPoint
    size: ELKSize

ELKOutputLabel 🔗

Bases: ELKOutputElement

Label that comes out of ELK.

Source code in capellambse_context_diagrams/_elkjs.py
198
199
200
201
202
203
204
205
class ELKOutputLabel(ELKOutputElement):
    """Label that comes out of ELK."""

    type: t.Literal["label"]
    text: str

    position: ELKPoint
    size: ELKSize

ELKOutputNode 🔗

Bases: ELKOutputElement

Node that comes out of ELK.

Source code in capellambse_context_diagrams/_elkjs.py
168
169
170
171
172
173
174
175
class ELKOutputNode(ELKOutputElement):
    """Node that comes out of ELK."""

    type: t.Literal["node"]
    children: cabc.MutableSequence[ELKOutputChild]  # type: ignore

    position: ELKPoint
    size: ELKSize

ELKOutputPort 🔗

Bases: ELKOutputElement

Port that comes out of ELK.

Source code in capellambse_context_diagrams/_elkjs.py
188
189
190
191
192
193
194
195
class ELKOutputPort(ELKOutputElement):
    """Port that comes out of ELK."""

    type: t.Literal["port"]
    children: cabc.MutableSequence[ELKOutputLabel]

    position: ELKPoint
    size: ELKSize

ELKPoint 🔗

Bases: TypedDict

Point data in ELK.

Source code in capellambse_context_diagrams/_elkjs.py
139
140
141
142
143
class ELKPoint(t.TypedDict):
    """Point data in ELK."""

    x: t.Union[int, float]
    y: t.Union[int, float]

ELKSize 🔗

Bases: TypedDict

Size data in ELK.

Source code in capellambse_context_diagrams/_elkjs.py
146
147
148
149
150
class ELKSize(t.TypedDict):
    """Size data in ELK."""

    width: t.Union[int, float]
    height: t.Union[int, float]

ExecutableNotFoundError 🔗

Bases: NodeJSError, FileNotFoundError

The required executable could not be found in the PATH.

Source code in capellambse_context_diagrams/_elkjs.py
233
234
class ExecutableNotFoundError(NodeJSError, FileNotFoundError):
    """The required executable could not be found in the PATH."""

NodeInstallationError 🔗

Bases: NodeJSError

Installation of the node.js package failed.

Source code in capellambse_context_diagrams/_elkjs.py
237
238
class NodeInstallationError(NodeJSError):
    """Installation of the node.js package failed."""

NodeJSError 🔗

Bases: RuntimeError

An error happened during node execution.

Source code in capellambse_context_diagrams/_elkjs.py
229
230
class NodeJSError(RuntimeError):
    """An error happened during node execution."""

call_elkjs(elk_dict) 🔗

Call into elk.js to auto-layout the diagram.

Parameters🔗

elk_dict The diagram data, sans layouting information

Returns🔗

layouted_diagram The diagram data, augmented with layouting information

Source code in capellambse_context_diagrams/_elkjs.py
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
def call_elkjs(elk_dict: ELKInputData) -> ELKOutputData:
    """Call into elk.js to auto-layout the ``diagram``.

    Parameters
    ----------
    elk_dict
        The diagram data, sans layouting information

    Returns
    -------
    layouted_diagram
        The diagram data, augmented with layouting information
    """
    _find_node_and_npm()
    _install_required_npm_pkg_versions()

    proc = subprocess.run(
        ["node", str(PATH_TO_ELK_JS)],
        executable=shutil.which("node"),
        capture_output=True,
        check=False,
        input=json.dumps(elk_dict),
        text=True,
        env={**os.environ, "NODE_PATH": str(NODE_HOME)},
    )
    if proc.returncode:
        log.getChild("node").error("%s", proc.stderr)
        raise NodeJSError("elk.js process failed")

    return json.loads(proc.stdout)

get_global_layered_layout_options() 🔗

Return optimal ELKLayered configuration.

Source code in capellambse_context_diagrams/_elkjs.py
358
359
360
def get_global_layered_layout_options() -> LayoutOptions:
    """Return optimal ELKLayered configuration."""
    return copy.deepcopy(LAYOUT_OPTIONS)  # type: ignore[arg-type]