Skip to content

raesl.plot

RaESL plotting and visualization module.

diagrams

Graphviz diagrams of an ESL specification.

function_chain_diagram

function_chain_diagram(
    graph: Graph,
    start_points: List[Tuple[Node, List[Node]]],
    end_points: List[Tuple[Node, List[Node]]],
    levels: int = 1,
    style: Style = Style(),
) -> Digraph

Draw a function chain diagram using Graphviz.

Parameters:

Name Type Description Default
graph Graph

Instantiated ESL graph.

required
start_points List[Tuple[Node, List[Node]]]

List of tuples that contain the component node and list of function nodes that serve as the starting point of the function chains.

required
end_points List[Tuple[Node, List[Node]]]

List of tuples that contain the component node and list of function nodes that serve as the end point of the function chains.

required
levels int

Number of levels to decompose intermediate components into. This number is relative to the depth of the start nodes and end nodes.

1
style Style

RaESL style options.

Style()

Returns:

Type Description
Digraph

Graphviz Digraph object of the function chain diagram.

Source code in src/raesl/plot/diagrams.py
def function_chain_diagram(
    graph: Graph,
    start_points: List[Tuple[Node, List[Node]]],
    end_points: List[Tuple[Node, List[Node]]],
    levels: int = 1,
    style: Style = Style(),
) -> Digraph:
    """Draw a function chain diagram using Graphviz.

    Arguments:
        graph: Instantiated ESL graph.
        start_points: List of tuples that contain the component node and list of
            function nodes that serve as the starting point of the function chains.
        end_points: List of tuples that contain the component node and list of
            function nodes that serve as the end point of the function chains.
        levels: Number of levels to decompose intermediate components into.
            This number is relative to the depth of the start nodes and end nodes.
        style: RaESL style options.

    Returns:
        Graphviz Digraph object of the function chain diagram.
    """
    utils.check_start_and_end_points(start_points=start_points, end_points=end_points)

    view = GraphView(
        graph,
        view_func=view_funcs.function_chain,
        view_kwargs=dict(
            start_points=start_points, end_points=end_points, levels=levels, style=style
        ),
    )

    components = [n for n in view.nodes if n.kind == "component"]
    if style.diagram.show_hierarchy:
        ancestors = set([a for c in components for a in c.ancestors])
        roots = [n for n in ancestors if not n.parent]
    else:
        roots = components

    dot = _draw_migrated_graph(view, roots, style)

    return dot

function_traceability_diagram

function_traceability_diagram(
    graph: Graph,
    root: Node,
    levels: int,
    style: Style = Style(),
) -> Digraph

Draw a functional traceability diagram using Graphviz.

Parameters:

Name Type Description Default
graph Graph

Instantiated ESL graph.

required
root Node

Node that serves a the root of the traceability tree. Must be a transformation specification.

required
levels int

Number of levels to go down into the traceability tree.

required
style Style

RaESL Style options.

Style()

Returns Graphviz Digraph object of the functional traceability diagram.

Source code in src/raesl/plot/diagrams.py
def function_traceability_diagram(
    graph: Graph, root: Node, levels: int, style: Style = Style()
) -> Digraph:
    """Draw a functional traceability diagram using Graphviz.

    Arguments:
        graph: Instantiated ESL graph.
        root: Node that serves a the root of the traceability tree. Must be a
            transformation specification.
        levels: Number of levels to go down into the traceability tree.
        style: RaESL Style options.

    Returns
        Graphviz Digraph object of the functional traceability diagram.
    """

    # Input checks.
    if root.kind != "function_spec":
        raise ValueError(
            "Root node must be of kind 'function_spec' not of kind '{}'".format(root.kind)
        )

    if root.annotations.esl_info["sub_kind"] != "transformation":
        raise ValueError(
            """Root node must be of sub-kind 'transformation'
            not of sub-kind '{}'""".format(
                root.annotations.esl_info["sub_kind"]
            )
        )

    view = GraphView(
        graph,
        view_func=view_funcs.traceability,
        view_kwargs=dict(root=root, levels=levels, style=style),
    )

    components = [n for n in view.nodes if n.kind == "component"]
    dot = _draw_migrated_graph(view, components, style)

    return dot

functional_context_diagram

functional_context_diagram(
    graph: Graph,
    root: Node,
    degree: int = 1,
    style: Style = Style(),
) -> Digraph

Draw a functional context diagram using Graphviz.

Parameters:

Name Type Description Default
graph Graph

Instantiated ESL graph.

required
root Node

Root node for which the functional context is drawn.

required
degree int

The degree up to which neighbors must be collected (neighbors of neighbors). Defaults to 1.

1
style Style

RaESL style options.

Style()

Returns:

Type Description
Digraph

Graphviz Digraph object of the functional context diagram.

Source code in src/raesl/plot/diagrams.py
def functional_context_diagram(
    graph: Graph, root: Node, degree: int = 1, style: Style = Style()
) -> Digraph:
    """Draw a functional context diagram using Graphviz.

    Arguments:
        graph: Instantiated ESL graph.
        root: Root node for which the functional context is drawn.
        degree: The degree up to which neighbors must be collected (neighbors of
            neighbors). Defaults to 1.
        style: RaESL style options.

    Returns:
        Graphviz Digraph object of the functional context diagram.
    """
    if root.kind != "component":
        raise ValueError(
            "Root node is of kind '{}' while it must be of kind 'component'.".format(root.kind)
        )

    view = GraphView(
        graph,
        view_func=view_funcs.functional_context,
        view_kwargs={"root": root, "degree": degree, "style": style},
    )

    components = [n for n in view.nodes if n.kind == "component"]
    if style.diagram.show_root_children or style.diagram.show_neighbor_children:
        roots = [c for c in components if c.depth <= root.depth]
        roots.extend([c.parent for c in components if c.parent.depth == root.depth])
    else:
        roots = components

    dot = _draw_migrated_graph(view, roots, style=style)

    return dot

functional_dependency_diagram

functional_dependency_diagram(
    graph: Graph,
    root: Node,
    levels: int,
    style: Style = Style(),
) -> Digraph

Draw a functional dependency diagram using Graphviz.

Parameters:

Name Type Description Default
graph Graph

Instantiated ESL graph.

required
root Node

Root node for which the dependency structure must be drawn.

required
levels int

Number of levels to include in the tree.

required
style Style

RaESL style options.

Style()

Returns:

Type Description
Digraph

Graphviz Digraph object of the functional dependency diagram.

Source code in src/raesl/plot/diagrams.py
def functional_dependency_diagram(
    graph: Graph, root: Node, levels: int, style: Style = Style()
) -> Digraph:
    """Draw a functional dependency diagram using Graphviz.

    Arguments:
        graph: Instantiated ESL graph.
        root: Root node for which the dependency structure must be drawn.
        levels: Number of levels to include in the tree.
        style: RaESL style options.

    Returns:
        Graphviz Digraph object of the functional dependency diagram.
    """
    if root.kind != "component":
        raise ValueError(
            "Root node is of kind '{}' while it must be of kind 'component'.".format(root.kind)
        )

    view = GraphView(
        graph,
        view_func=view_funcs.functional_dependency,
        view_kwargs={"root": root, "levels": levels},
    )

    if style.diagram.show_hierarchy:
        roots = [root]
    else:
        roots = [n for n in view.nodes if n.kind == "component"]

    dot = _draw_migrated_graph(view, roots, style)

    return dot

hierarchy_diagram

hierarchy_diagram(
    graph: Graph,
    roots: List[Node],
    levels: int,
    style: Style = Style(),
) -> Digraph

Draw a hierarchical decomposition tree using Graphviz.

Parameters:

Name Type Description Default
graph Graph

Instantiated ESL graph.

required
roots List[Node]

List of root nodes for which a tree must be drawn.

required
levels int

Number of levels to include in the tree.

required
style Style

RaESL style options.

Style()

Returns:

Type Description
Digraph

Graphviz Digraph object of the decomposition tree.

Source code in src/raesl/plot/diagrams.py
def hierarchy_diagram(
    graph: Graph, roots: List[Node], levels: int, style: Style = Style()
) -> Digraph:
    """Draw a hierarchical decomposition tree using Graphviz.

    Arguments:
        graph: Instantiated ESL graph.
        roots: List of root nodes for which a tree must be drawn.
        levels: Number of levels to include in the tree.
        style: RaESL style options.

    Returns:
        Graphviz Digraph object of the decomposition tree.
    """
    view = GraphView(
        graph=graph,
        view_func=view_funcs.hierarchy,
        view_kwargs={"roots": roots, "levels": levels},
    )

    dot = _draw_migrated_graph(view, view.nodes, style)

    return dot

generic

RaESL GraphViz diagram plotting module.

DiagramStyle

DiagramStyle(
    digraph: Optional[Dict[str, Any]] = None,
    orientation: Optional[str] = None,
    node_shapes: Optional[Dict[str, str]] = None,
    edge_styles: Optional[Dict[str, str]] = None,
    show_hierarchy: Optional[bool] = None,
    list_variables: Optional[bool] = None,
    show_root_children: Optional[bool] = None,
    show_neighbor_children: Optional[bool] = None,
    show_function_dependencies: Optional[bool] = None,
)

Bases: Mapping

RaESL Graphviz diagram style mapping.

Source code in src/raesl/plot/generic.py
def __init__(
    self,
    digraph: Optional[Dict[str, Any]] = None,
    orientation: Optional[str] = None,
    node_shapes: Optional[Dict[str, str]] = None,
    edge_styles: Optional[Dict[str, str]] = None,
    show_hierarchy: Optional[bool] = None,
    list_variables: Optional[bool] = None,
    show_root_children: Optional[bool] = None,
    show_neighbor_children: Optional[bool] = None,
    show_function_dependencies: Optional[bool] = None,
):
    super().__init__(
        digraph=digraph,
        orientation=orientation,
        node_shapes=node_shapes,
        edge_styles=edge_styles,
        show_hierarchy=show_hierarchy,
        list_variables=list_variables,
        show_root_children=show_root_children,
        show_neighbor_children=show_neighbor_children,
        show_function_dependencies=show_function_dependencies,
    )

digraph

digraph() -> Dict[str, Any]

Options for the :obj:graphviz.Digraph object.

Source code in src/raesl/plot/generic.py
@field
def digraph(self) -> Dict[str, Any]:
    """Options for the :obj:`graphviz.Digraph` object."""

edge_styles

edge_styles() -> Dict[str, str]

Dictionary of edge kind to Graphviz edge styles.

Source code in src/raesl/plot/generic.py
@field
def edge_styles(self) -> Dict[str, str]:
    """Dictionary of edge kind to Graphviz edge styles."""

list_variables

list_variables() -> bool

Whether to list the variables of goal- and transformation specifications.

Source code in src/raesl/plot/generic.py
@field
def list_variables(self) -> bool:
    """Whether to list the variables of goal- and transformation specifications."""

node_shapes

node_shapes() -> Dict[str, str]

Dictionary of node kinds to Graphviz node shapes.

Source code in src/raesl/plot/generic.py
@field
def node_shapes(self) -> Dict[str, str]:
    """Dictionary of node kinds to Graphviz node shapes."""

orientation

orientation() -> str

Orientation of the layout of the graph. One of 'LR' (left-to-right) or 'TD' (top-down).

Source code in src/raesl/plot/generic.py
@field
def orientation(self) -> str:
    """Orientation of the layout of the graph. One of 'LR' (left-to-right) or
    'TD' (top-down)."""

show_function_dependencies

show_function_dependencies() -> bool

Whether to display dependencies between functions within a traceability diagram.

Source code in src/raesl/plot/generic.py
@field
def show_function_dependencies(self) -> bool:
    """Whether to display dependencies between functions within a traceability
    diagram."""

show_hierarchy

show_hierarchy() -> bool

Whether to draw the nested hierarchical structure.

Source code in src/raesl/plot/generic.py
@field
def show_hierarchy(self) -> bool:
    """Whether to draw the nested hierarchical structure."""

show_neighbor_children

show_neighbor_children() -> bool

Whether to display the children of the neighbor components within a functional context diagram.

Source code in src/raesl/plot/generic.py
@field
def show_neighbor_children(self) -> bool:
    """Whether to display the children of the neighbor components within a
    functional context diagram."""

show_root_children

show_root_children() -> bool

Whether to display the children of the root component within a functional context diagram.

Source code in src/raesl/plot/generic.py
@field
def show_root_children(self) -> bool:
    """Whether to display the children of the root component within a functional
    context diagram."""

Style

Style(
    diagram: Optional[
        Union[DiagramStyle, Dict[str, Any]]
    ] = None,
    ragraph: Optional[Union[Style, Dict[str, Any]]] = None,
)

Bases: Mapping

RaESL plotting style mapping.

Source code in src/raesl/plot/generic.py
def __init__(
    self,
    diagram: Optional[Union[DiagramStyle, Dict[str, Any]]] = None,
    ragraph: Optional[Union[RaGraphStyle, Dict[str, Any]]] = None,
):
    super().__init__(
        diagram=diagram,
        ragraph=ragraph,
    )

diagram

diagram() -> DiagramStyle

Graphviz diagram style.

Source code in src/raesl/plot/generic.py
@field
def diagram(self) -> DiagramStyle:
    """Graphviz diagram style."""

ragraph

ragraph() -> Style

RaGraph style options, used for Multi-Domain matrices.

Source code in src/raesl/plot/generic.py
@field
def ragraph(self) -> RaGraphStyle:
    """RaGraph style options, used for Multi-Domain matrices."""

matrix

Matrix based views on an ESL specification.

mdm

mdm(
    graph: Graph,
    node_kinds: Optional[List[str]] = None,
    edge_kinds: Optional[List[str]] = None,
    edge_labels: Optional[List[str]] = None,
    edge_weights: Optional[List[str]] = None,
    lead_components: Optional[List[Node]] = None,
    depth: Optional[int] = 2,
    style: Style = Style(),
) -> Figure

Create a Multi-Domain Matrix plot using Plotly.

Parameters:

Name Type Description Default
node_kinds Optional[List[str]]

The node kinds to display.

None
edge_kinds Optional[List[str]]

The edge kinds to display.

None
edge_labels Optional[List[str]]

The edge labels to display.

None
edge_weights Optional[List[str]]

The edge weights to display.

None
lead_components Optional[List[Node]]

The lead components to be used in node selection.

None
depth Optional[int]

The depth up to which components and related nodes must be included.

2
style Style

RaESL style options.

Style()

Returns:

Name Type Description
Plotly Figure
Source code in src/raesl/plot/matrix.py
def mdm(
    graph: Graph,
    node_kinds: Optional[List[str]] = None,
    edge_kinds: Optional[List[str]] = None,
    edge_labels: Optional[List[str]] = None,
    edge_weights: Optional[List[str]] = None,
    lead_components: Optional[List[Node]] = None,
    depth: Optional[int] = 2,
    style: Style = Style(),
) -> go.Figure:
    """Create a Multi-Domain Matrix plot using Plotly.

    Arguments:
        node_kinds: The node kinds to display.
        edge_kinds: The edge kinds to display.
        edge_labels: The edge labels to display.
        edge_weights: The edge weights to display.
        lead_components: The lead components to be used in node selection.
        depth: The depth up to which components and related nodes must be included.
        style: RaESL style options.

    Returns:
       Plotly :obj:`go.Figure` object of the Multi-Domain Matrix.
    """
    view = GraphView(
        graph,
        view_func=view_funcs.multi_domain,
        view_kwargs={
            "node_kinds": node_kinds,
            "edge_kinds": edge_kinds,
            "edge_labels": edge_labels,
            "edge_weights": edge_weights,
            "lead_components": lead_components,
            "depth": depth,
        },
    )

    style = style.ragraph
    if style.piemap.display == "labels":
        if not style.piemap.fields:
            style.piemap.fields = edge_labels

        if not style.palettes.get("fields"):
            style.palettes = {
                "fields": {
                    field: color
                    for field, color in zip(
                        graph.edge_labels,
                        get_categorical(n_colors=len(graph.edge_labels)),
                    )
                }
            }

    elif style.piemap.display == "kinds":
        if not style.piemap.fields:
            style.piemap.fields = edge_kinds
    elif style.piemap.display in ["weights", "weight labels"]:
        if not style.piemap.fields:
            style.piemap.fields = edge_weights

    return ragraph.plot.mdm(leafs=view.nodes, edges=view.edges, show=False, style=style, sort=False)

utils

RaESL plotting utility functions.

add_level

add_level(graph: Graph, sliced: Graph, parents: Set[Node])

Add a level to the sliced graph based on the hierarchy in the source graph.

Parameters:

Name Type Description Default
graph Graph

Instantiated ESL graph.

required
sliced Graph

Sliced data graph.

required
Note

Creates deepcopies of child nodes to preserve the original data structure.

Source code in src/raesl/plot/utils.py
def add_level(graph: Graph, sliced: Graph, parents: Set[Node]):
    """Add a level to the sliced graph based on the hierarchy in the source graph.

    Arguments:
        graph: Instantiated ESL graph.
        sliced: Sliced data graph.

    Note:
        Creates deepcopies of child nodes to preserve the original data structure.
    """
    comp_names = set([c.name for c in sliced.get_nodes_by_kind(kind="component")])

    for p in parents:
        children = comp_names.intersection(set([child.name for child in p.children]))
        if children:
            n = deepcopy(p)
            n.children = [sliced[child] for child in children]
            sliced.add_node(n)

check_comp_func_pairs

check_comp_func_pairs(
    points: List[Tuple[Node, List[Node]]]
)

Check of for all points the provided components functions pairs are consistent.

Parameters:

Name Type Description Default
points List[Tuple[Node, List[Node]]]

List of points for which the consistency must be checked.

required

Raises:

Type Description
ValueError

If the active component of the given functions does not match.

Source code in src/raesl/plot/utils.py
def check_comp_func_pairs(points: List[Tuple[Node, List[Node]]]):
    """Check of for all points the provided components functions pairs are consistent.

    Arguments:
        points: List of points for which the consistency must be checked.

    Raises:
        ValueError: If the active component of the given functions does not match.
    """
    for point in points:
        comp = point[0]
        functions = point[1]
        for f in functions:
            if comp.name != f.annotations.esl_info["body"]["active"]:
                msg = "Active component of '{}' is not equal to start component '{}'."
                raise ValueError(msg.format(comp.name, f.annotations.esl_info["body"]["active"]))

check_start_and_end_points

check_start_and_end_points(
    start_points: List[Tuple[Node, List[Node]]],
    end_points: List[Tuple[Node, List[Node]]],
)

Check start and end points for consistency.

Parameters:

Name Type Description Default
start_points List[Tuple[Node, List[Node]]]

List of tuples that contain the component node and list of function nodes that serve as starting points of the function chains.

required
end_points List[Tuple[Node, List[Node]]]

List of tuples that contain the component node and list of functions nodes that serve as end points of the function chains.

required

Raises:

Type Description
ValueError

If the component-function pairs do not have a corresponding active component.

ValueError

If any of the components are a descendant of another component.

Source code in src/raesl/plot/utils.py
def check_start_and_end_points(
    start_points: List[Tuple[Node, List[Node]]],
    end_points: List[Tuple[Node, List[Node]]],
):
    """Check start and end points for consistency.

    Arguments:
        start_points: List of tuples that contain the component node and list of
            function nodes that serve as starting points of the function chains.
        end_points: List of tuples that contain the component node and list of
            functions nodes that serve as end points of the function chains.

    Raises:
        ValueError: If the component-function pairs do not have a corresponding active
            component.
        ValueError: If any of the components are a descendant of another component.
    """
    points = start_points + end_points
    check_comp_func_pairs(points)
    check_tree_disjunction([p[0] for p in points])

check_tree_disjunction

check_tree_disjunction(nodes: List[Node])

Check if list of nodes are not part of each others descendants.

Raises:

Type Description
ValueError

If node is in the descendants of another node that was provided.

Source code in src/raesl/plot/utils.py
def check_tree_disjunction(nodes: List[Node]):
    """Check if list of nodes are not part of each others descendants.

    Raises:
        ValueError: If node is in the descendants of another node that was provided.
    """
    decendants = get_all_descendants(nodes)
    for n in nodes:
        if n in decendants:
            raise ValueError("Node {} is a descendant of another provided node.".format(n.name))

crawl_descendants

crawl_descendants(
    node: Node, max_depth: int
) -> Tuple[List[Node], List[Edge]]

Walk down the decomposition tree of a node, add its children and create composition dependencies accordingly.

Parameters:

Name Type Description Default
node Node

The parent node to unfold.

required
max_depth int

Max depth to unfold nodes to (absolute depth w.r.t. root).

required

Returns:

Type Description
Tuple[List[Node], List[Edge]]

List of (additionally) crawled nodes and "composition_dependency" edges.

Note

Since we explicitly need to figure out which (partial) hierarchies to display using Graphviz, we recreate those parts of the hierarchy using explicit "composition dependency" :obj:Edge objects in the preprocessing step using :obj:ragraph.graph.GraphView objects.

Source code in src/raesl/plot/utils.py
def crawl_descendants(node: Node, max_depth: int) -> Tuple[List[Node], List[Edge]]:
    """Walk down the decomposition tree of a node, add its children and create
    composition dependencies accordingly.

    Arguments:
        node: The parent node to unfold.
        max_depth: Max depth to unfold nodes to (absolute depth w.r.t. root).

    Returns:
        List of (additionally) crawled nodes and "composition_dependency" edges.

    Note:
        Since we explicitly need to figure out which (partial) hierarchies to display
        using Graphviz, we recreate those parts of the hierarchy using explicit
        "composition dependency" :obj:`Edge` objects in the preprocessing step using
        :obj:`ragraph.graph.GraphView` objects.
    """
    nodes, edges = [], []

    for child in node.children:
        nodes.append(child)
        edges.append(Edge(node, child, kind="composition_dependency"))

        if child.children and child.depth < max_depth:
            cnodes, cedges = crawl_descendants(child, max_depth)
            nodes.extend(cnodes)
            edges.extend(cedges)

    return nodes, edges

filter_nodes

filter_nodes(
    graph: Graph,
    lead_components: List[Node],
    node_kinds: List[str],
) -> List[Node]

Filter nodes for displaying a multi-domain-matrix.

Parameters:

Name Type Description Default
graph Graph

Instantiated ESL graph.

required
lead_components List[Node]

List of nodes of kind 'component' that are leading when filtering the other nodes.

required
node_kinds List[str]

List of node kinds to be included.

required

Returns:

Type Description
List[Node]

List of filtered nodes.

Note

Node of kind 'component' are always leading in filtering the nodes to ensure the consistency within the network of shown dependencies. Since the component hierarchy forms the central structure of an ESL specification.

Source code in src/raesl/plot/utils.py
def filter_nodes(graph: Graph, lead_components: List[Node], node_kinds: List[str]) -> List[Node]:
    """Filter nodes for displaying a multi-domain-matrix.

    Arguments:
        graph: Instantiated ESL graph.
        lead_components: List of nodes of kind 'component' that are leading
            when filtering the other nodes.
        node_kinds: List of node kinds to be included.

    Returns:
        List of filtered nodes.

    Note:
        Node of kind 'component' are always leading in filtering the nodes to ensure the consistency
        within the network of shown dependencies. Since the component hierarchy forms the central
        structure of an ESL specification.
    """
    nodes = []
    for kind in node_kinds:
        if kind == "component":
            nodes.extend(lead_components)
        else:
            domain_nodes = []
            for n in graph.get_nodes_by_kind(kind=kind):
                if has_mapping_dependency(graph, n, lead_components + nodes):
                    domain_nodes.append(n)

            nodes.extend(get_axis_sequence(nodes=domain_nodes, kinds=[kind]))

    return nodes

get_all_descendants

get_all_descendants(nodes: List[Node]) -> Set[Node]

Get all descendants from a list of provided nodes.

Parameters:

Name Type Description Default
nodes List[Node]

List of nodes for which the descendants must be found.

required

Returns:

Type Description
Set[Node]

Set of nodes containing all descendants of the provided nodes.

Source code in src/raesl/plot/utils.py
def get_all_descendants(nodes: List[Node]) -> Set[Node]:
    """Get all descendants from a list of provided nodes.

    Arguments:
        nodes: List of nodes for which the descendants must be found.

    Returns:
        Set of nodes containing all descendants of the provided nodes.
    """
    all_descendants: Set[Node] = set()
    for n in nodes:
        all_descendants.union(set(n.descendants))

    return all_descendants

get_component_function_dicts

get_component_function_dicts(
    graph: Graph,
) -> Tuple[Dict[str, List[Node]], Dict[str, List[Node]]]

Get two dictionaries from component node names to lists of transformation and goal function nodes.

Arguments graph: Instantiated ESL graph.

Returns Dictionaries from component node names to transformation and goal function nodes.

Source code in src/raesl/plot/utils.py
def get_component_function_dicts(
    graph: Graph,
) -> Tuple[Dict[str, List[Node]], Dict[str, List[Node]]]:
    """Get two dictionaries from component node names to lists of transformation and
    goal function nodes.

    Arguments
       graph: Instantiated ESL graph.

    Returns
       Dictionaries from component node names to transformation and goal function nodes.
    """
    trans = defaultdict(list)
    goals = defaultdict(list)
    for f in graph.get_nodes_by_kind(kind="function_spec"):
        if f.annotations.esl_info["sub_kind"] == "transformation":
            trans[f.annotations.esl_info["body"]["active"]].append(f)
        elif f.annotations.esl_info["sub_kind"] == "goal":
            goals[f.annotations.esl_info["body"]["active"]].append(f)

    return trans, goals

get_function_tree

get_function_tree(
    graph: Graph, node: Node, levels: int
) -> Tuple[Set[Node], Set[Edge]]

Walk down the traceability tree of a node.

Parameters:

Name Type Description Default
graph Graph

Instantiated ESL graph.

required
node Node

Node to start the walk from.

required
levels int

Number of levels to continue walking.

required

Returns:

Type Description
Set[Node]

Tuple of the set of all nodes and the set of all edges that describe the

Set[Edge]

traceability tree.

Source code in src/raesl/plot/utils.py
def get_function_tree(graph: Graph, node: Node, levels: int) -> Tuple[Set[Node], Set[Edge]]:
    """Walk down the traceability tree of a node.

    Arguments:
        graph: Instantiated ESL graph.
        node: Node to start the walk from.
        levels: Number of levels to continue walking.

    Returns:
        Tuple of the set of all nodes and the set of all edges that describe the
        traceability tree.

    """
    edges = set()
    functions = set()
    functions.add(node)

    if levels > 1:
        edges.update(
            set([e for e in graph.edges_from(node) if e.kind == "traceability_dependency"])
        )

        function_targets = set([e.target for e in edges])
        functions.update(function_targets)
        for f in function_targets:
            if f.annotations.esl_info["sub_kind"] == "goal":
                continue

            fs, es = get_function_tree(graph, f, levels - 1)

            edges.update(es)
            functions.update(fs)

    return functions, edges

get_neighbors

get_neighbors(
    graph: Graph,
    root: Node,
    node: Node,
    node_kinds: Set[str],
    degree: int = 1,
) -> Set[Node]

Get neighbors of a node within the graph.

Parameters:

Name Type Description Default
graph Graph

Instantiated ESL graph.

required
root Node

The root node for which the nodes must be sought up to the given degree.

required
node Node

The node for which the direct neighbors must be collected.

required
node_kinds Set[str]

The kinds of the neighbor nodes that must be collected.

required
degree int

The degree up to which neighbors must be collected (neighbors of neighbors). Defaults to 1.

1

Returns Set of neighbors.

Source code in src/raesl/plot/utils.py
def get_neighbors(
    graph: Graph, root: Node, node: Node, node_kinds: Set[str], degree: int = 1
) -> Set[Node]:
    """Get neighbors of a node within the graph.

    Arguments:
        graph: Instantiated ESL graph.
        root: The root node for which the nodes must be sought up to the given degree.
        node: The node for which the direct neighbors must be collected.
        node_kinds: The kinds of the neighbor nodes that must be collected.
        degree: The degree up to which neighbors must be collected (neighbors of
            neighbors). Defaults to 1.

    Returns
        Set of neighbors.
    """

    neighbors = set(
        [
            neighbor
            for neighbor in list(graph.targets_of(node)) + list(graph.sources_of(node))
            if (
                neighbor.kind in node_kinds
                and (
                    neighbor.depth == root.depth
                    or (neighbor.depth < root.depth and not neighbor.children)
                )
            )
        ]
    )

    if degree == 1:
        # Stop searching further.
        return neighbors

    for neighbor in neighbors:
        neighbors = neighbors.union(
            get_neighbors(graph, root, neighbor, node_kinds, degree=degree - 1)
        )

    return neighbors

get_paths

get_paths(
    source: Node,
    edges: Dict[Node, Dict[Node, Edge]],
    targets: Set[Node],
    visited: List[Node] = [],
) -> List[List[str]]

Collection all paths (list of node names) between the source node and the set of target nodes.

Parameters:

Name Type Description Default
source Node

The source node where all paths should start.

required
edges Dict[Node, Dict[Node, Edge]]

Dictionary of Node to Node to Edge. Contains the edges to be considered when searching for paths.

required
targets Set[Node]

Set of node where the paths should end.

required
visited List[Node]

List of nodes already visited. Required to prevent running in cycles.

[]

Returns:

Type Description
List[List[str]]

List of lists of node names.

Source code in src/raesl/plot/utils.py
def get_paths(
    source: Node,
    edges: Dict[Node, Dict[Node, Edge]],
    targets: Set[Node],
    visited: List[Node] = [],
) -> List[List[str]]:
    """Collection all paths (list of node names) between the source node and the set of
    target nodes.

    Arguments:
        source: The source node where all paths should start.
        edges: Dictionary of Node to Node to Edge. Contains the edges to be
            considered when searching for paths.
        targets: Set of node where the paths should end.
        visited: List of nodes already visited. Required to prevent running in cycles.

    Returns:
        List of lists of node names.
    """
    paths: List[List[str]] = []
    steps = edges.get(source)
    if not steps:
        return paths

    for target in steps:
        if target.name in visited:
            # node has already been visited. Loop entered.
            continue
        elif target in targets:
            # A target has been reached
            visited.append(target.name)
            paths.append(visited)
        else:
            new_path = deepcopy(visited)
            new_path.append(target.name)
            paths.extend(get_paths(target, edges, targets, visited=new_path))

    return paths

get_paths_between_all

get_paths_between_all(
    sources: List[Node],
    targets: List[Node],
    edges: List[Edge],
) -> List[List[str]]

Compute paths between nodes. Based on depth first search.

Parameters:

Name Type Description Default
sources List[Node]

List of starting nodes of paths.

required
targets List[Node]

Set of ending nodes of path.

required
edges List[Edge]

List of edges between nodes.

required

Yields:

Type Description
List[List[str]]

List of lists of node names.

Source code in src/raesl/plot/utils.py
def get_paths_between_all(
    sources: List[Node], targets: List[Node], edges: List[Edge]
) -> List[List[str]]:
    """Compute paths between nodes. Based on depth first search.

    Arguments:
         sources: List of starting nodes of paths.
         targets: Set of ending nodes of path.
         edges: List of edges between nodes.

    Yields:
        List of lists of node names.
    """
    paths = []

    ed: Dict[str, Dict[str, Edge]] = defaultdict(dict)
    for e in edges:
        ed[e.source][e.target] = e

    for source in sources:
        paths.extend(get_paths(source, ed, set(targets), visited=[source.name]))

    return paths

get_up_to_depth

get_up_to_depth(
    roots: List[Node], depth: int
) -> Generator[Node, None, None]

Get nodes up to a certain depth with bus nodes at the start of child lists.

Parameters:

Name Type Description Default
roots List[Node]

List of nodes to walk down from.

required
depth int

Depth up to which nodes must be returned.

required

Returns:

Type Description
None

Nodes up to the provided list.

Source code in src/raesl/plot/utils.py
def get_up_to_depth(roots: List[Node], depth: int) -> Generator[Node, None, None]:
    """Get nodes up to a certain depth with bus nodes at the start of child lists.

    Arguments:
        roots: List of nodes to walk down from.
        depth: Depth up to which nodes must be returned.

    Returns:
        Nodes up to the provided list.
    """
    for node in roots:
        if node.is_leaf or node.depth == depth:
            yield node
            continue

        children = sorted(node.children, key=lambda n: n.is_bus, reverse=True)

        if node.depth == depth - 1:
            yield from children
        else:
            yield from get_up_to_depth(children, depth)

has_mapping_dependency

has_mapping_dependency(
    graph: Graph, node: Node, nodes: List[Node]
) -> bool

Check if a node is mapped to any node in a list of nodes.

Parameters:

Name Type Description Default
graph Graph

Instantiated ESL graph.

required
node Node

The node for which the existence of mapping dependencies must be checked.

required
nodes List[Node]

List of nodes which are to be considered for checking the existence of a mapping dependency.

required

Returns:

Type Description
bool

Bool that indicates if a mapping dependency exists.

Source code in src/raesl/plot/utils.py
def has_mapping_dependency(graph: Graph, node: Node, nodes: List[Node]) -> bool:
    """Check if a node is mapped to any node in a list of nodes.

    Arguments:
        graph: Instantiated ESL graph.
        node: The node for which the existence of mapping dependencies must be checked.
        nodes: List of nodes which are to be considered for checking the existence
            of a mapping dependency.

    Returns:
        Bool that indicates if a mapping dependency exists.
    """
    return any(
        True for e in graph.edges_between_all(nodes, [node]) if e.kind == "mapping_dependency"
    ) or any(True for e in graph.edges_between_all([node], nodes) if e.kind == "mapping_dependency")

migrate_edges_between_variables

migrate_edges_between_variables(
    graph: Graph, lead_components: List[Node]
) -> List[Edge]

Migrate edges between variables up into the decomposition tree.

Parameters:

Name Type Description Default
graph Graph

Instantiated ESL graph.

required
lead_components List[Node]

The components that are displayed.

required

Returns:

Type Description
List[Edge]

List of edges to are created for the specific view.

Note

The ESL compiler only adds functional dependencies between variables based on the transformation specs of leaf components. As such, when one displays components at higher hierarchical levels gaps appear in the variable dependency structure. Hence, this method is used to fill those gaps by adding additional edges.

Source code in src/raesl/plot/utils.py
def migrate_edges_between_variables(graph: Graph, lead_components: List[Node]) -> List[Edge]:
    """Migrate edges between variables up into the decomposition tree.

    Arguments:
        graph: Instantiated ESL graph.
        lead_components: The components that are displayed.

    Returns:
        List of edges to are created for the specific view.

    Note:
        The ESL compiler only adds functional dependencies between variables based on
        the transformation specs of leaf components. As such, when one displays
        components at higher hierarchical levels gaps appear in the variable dependency
        structure. Hence, this method is used to fill those gaps by adding additional
        edges.
    """
    edges = []
    for c in lead_components:
        if not c.children:
            continue
        # Component is a collapsed component so get transformation specs and add
        # dependencies between in and output variables.
        tfs = [
            n
            for n in graph.targets_of(c)
            if n.kind == "function_spec" and n.annotations.esl_info["sub_kind"] == "transformation"
        ]
        for t in tfs:
            for vi in t.annotations.esl_info["body"]["input_variables"]:
                for vj in t.annotations.esl_info["body"]["output_variables"]:
                    edges.append(
                        Edge(
                            source=graph[vi],
                            target=graph[vj],
                            labels=[graph[vi].annotations.esl_info["type_ref"]],
                            kind="functional_dependency",
                            annotations=dict(esl_info=dict(reason=dict(transformations=[t.name]))),
                        )
                    )
    return edges

rebuild_hierarchical_structure

rebuild_hierarchical_structure(graph: Graph, sliced: Graph)

Rebuilding the hierarchical structure within the sliced graph.

Parameters:

Name Type Description Default
graph Graph

Instantiated ESL graph.

required
sliced Graph

Sliced data graph.

required
Note

Creates deepcopies of child nodes to preserve the original data structure.

Source code in src/raesl/plot/utils.py
def rebuild_hierarchical_structure(graph: Graph, sliced: Graph):
    """Rebuilding the hierarchical structure within the sliced graph.

    Arguments:
        graph: Instantiated ESL graph.
        sliced: Sliced data graph.

    Note:
        Creates deepcopies of child nodes to preserve the original data structure.
    """
    components = sliced.get_nodes_by_kind(kind="component")
    depth = max([graph[c.name].depth for c in components]) if components else 0
    parents = set(
        [
            graph[c.name].parent
            for c in components
            if graph[c.name].parent is not None and graph[c.name].parent.depth == depth - 1
        ]
    )

    while parents:
        add_level(graph, sliced, parents=parents)
        depth -= 1
        comps = sliced.get_nodes_by_kind(kind="component")
        parents = set(
            [
                graph[c.name].parent
                for c in comps
                if graph[c.name].parent is not None and graph[c.name].parent.depth == depth - 1
            ]
        )

select_components_for_function_path

select_components_for_function_path(
    graph: Graph,
    start_points: List[Tuple[Node, List[Node]]],
    end_points: List[Tuple[Node, List[Node]]],
    levels: int,
) -> List[Node]

Select components that belong to a certain level within the decomposition structure to draw a functional path.

Parameters:

Name Type Description Default
graph Graph

Instantiated ESL graph.

required
start_points List[Tuple[Node, List[Node]]]

List of tuples that contain the component node and list of function nodes that serve as a starting point of the function chains.

required
end_points List[Tuple[Node, List[Node]]]

List of Tuples that contain the component node and list of functions that nodes server.

required
levels int

Number of levels to decompose intermediate components into. This number is relative to the maximum of the depth of the start node and end node.

required
Note

Components that are a descendant of the start or end component node of the function path are excluded.

Source code in src/raesl/plot/utils.py
def select_components_for_function_path(
    graph: Graph,
    start_points: List[Tuple[Node, List[Node]]],
    end_points: List[Tuple[Node, List[Node]]],
    levels: int,
) -> List[Node]:
    """Select components that belong to a certain level within the decomposition
    structure to draw a functional path.

    Arguments:
        graph: Instantiated ESL graph.
        start_points: List of tuples that contain the component node and list of
            function nodes that serve as a starting point of the function chains.
        end_points: List of Tuples that contain the component node and list of functions
            that nodes server.
        levels: Number of levels to decompose intermediate components into.
            This number is relative to the maximum of the depth of the start
            node and end node.

    Note:
       Components that are a descendant of the start or end component node of the
       function path are excluded.
    """
    start_comps = [p[0] for p in start_points]
    end_comps = [p[0] for p in end_points]
    level = max([n.depth for n in start_comps + end_comps]) + levels - 1
    start_descendants = get_all_descendants(start_comps)
    end_descendants = get_all_descendants(end_comps)
    components = []

    for c in graph.get_nodes_by_kind(kind="component"):
        if c in start_descendants or c in end_descendants or c.depth > level:
            continue
        elif c.depth == level or (c.depth < level and not c.children):
            components.append(c)

    return components

view_funcs

GraphView view functions for ESL.

function_chain

function_chain(
    graph: Graph,
    start_points: List[Tuple[Node, List[Node]]],
    end_points: List[Tuple[Node, List[Node]]],
    levels: int = 1,
    style: Style = Style(),
) -> Tuple[List[Node], List[Edge]]

Filter nodes and edges for a function chain diagram.

Parameters:

Name Type Description Default
graph Graph

Instantiated ESL graph.

required
start_points List[Tuple[Node, List[Node]]]

List of start-points for the function chains.

required
end_points List[Tuple[Node, List[Node]]]

List of end-points for the function chains.

required
levels int

Number of levels to decompose intermediate components into (if present).

1
style Style

style options class.

Style()

Returns:

Type Description
Tuple[List[Node], List[Edge]]

List of selected nodes and list of selected edges.

Source code in src/raesl/plot/view_funcs.py
def function_chain(
    graph: Graph,
    start_points: List[Tuple[Node, List[Node]]],
    end_points: List[Tuple[Node, List[Node]]],
    levels: int = 1,
    style: Style = Style(),
) -> Tuple[List[Node], List[Edge]]:
    """Filter nodes and edges for a function chain diagram.

    Arguments:
        graph: Instantiated ESL graph.
        start_points: List of start-points for the function chains.
        end_points: List of end-points for the function chains.
        levels: Number of levels to decompose intermediate components into (if present).
        style: style options class.

    Returns:
        List of selected nodes and list of selected edges.
    """
    components = set(
        utils.select_components_for_function_path(
            graph, start_points=start_points, end_points=end_points, levels=levels
        )
    )

    _, edges = multi_domain(
        graph,
        node_kinds=["function_spec"],
        edge_kinds=["functional_dependency", "logical_dependency"],
        lead_components=list(components),
    )

    sfs = []
    for p in start_points:
        sfs.extend(p[1])
    efs = []
    for p in end_points:
        efs.extend(p[1])

    path_functions = set()
    path_components = set()
    migrated_goals = []

    for path in utils.get_paths_between_all(sfs, efs, edges=edges):
        for fname in path:
            f = graph[fname]
            path_functions.add(f)
            active = graph[f.annotations.esl_info["body"]["active"]]
            if active in components:
                path_components.add(active)
            else:
                des = components.intersection(set(active.descendants))
                migrated_goals.append(f)
                for d in des:
                    if graph.edge_dict.get(d.name):
                        if graph.edge_dict[d.name].get(f.name):
                            path_components.add(d)

    sliced = graph.get_graph_slice(nodes=path_components.union(path_functions))

    if style.diagram.show_hierarchy:
        utils.rebuild_hierarchical_structure(graph, sliced)

    return sliced.leafs, [
        e
        for e in sliced.edges
        if e.source.kind == "function_spec" and e.target.kind == "function_spec"
    ]

functional_context

functional_context(
    graph: Graph,
    root: Node,
    degree: int,
    style: Style = Style(),
) -> Tuple[List[Node], List[Edge]]

Filter nodes and edges for drawing a functional context diagram.

Parameters:

Name Type Description Default
graph Graph

Instantiated ESL graph.

required
root Node

The root node for which the functional context diagram must be drawn.

required
degree int

Degree up to which neighbors of neighbors must be sought.

required
style Style

RaESL style options.

Style()

Returns:

Type Description
Tuple[List[Node], List[Edge]]

List of selected nodes and list of selected edges.

Source code in src/raesl/plot/view_funcs.py
def functional_context(
    graph: Graph, root: Node, degree: int, style: Style = Style()
) -> Tuple[List[Node], List[Edge]]:
    """Filter nodes and edges for drawing a functional context diagram.

    Arguments:
        graph: Instantiated ESL graph.
        root: The root node for which the functional context diagram must be
            drawn.
        degree: Degree up to which neighbors of neighbors must be sought.
        style: RaESL style options.

    Returns:
        List of selected nodes and list of selected edges.
    """
    neighbors = utils.get_neighbors(graph, root, root, node_kinds={"component"}, degree=degree)

    components = []
    tree = set()
    tree.add(root.name)
    if style.diagram.show_root_children and root.children:
        components.extend(root.children)
        tree.update(set([c.name for c in root.children]))
    else:
        components.append(root)

    tree.update(set([n.name for n in neighbors]))
    if style.diagram.show_neighbor_children:
        for neighbor in neighbors:
            if neighbor.children:
                components.extend(neighbor.children)
                tree.update(set([c.name for c in neighbor.children]))
            else:
                components.append(neighbor)
    else:
        components.extend(neighbors)

    functions, edges = multi_domain(
        graph,
        node_kinds=["function_spec"],
        edge_kinds=["functional_dependency", "logical_dependency"],
        lead_components=components,
    )

    edges += _add_component_goal_edges(graph, sorted(tree), functions)

    return components + functions, edges

functional_dependency

functional_dependency(
    graph: Graph, root: Node, levels: int
) -> Tuple[List[Node], List[Node]]

Filter method for drawing a nested functional dependency structure.

Arguments graph: Instantiated ESL graph. root: Root node for which the dependency structure must be drawn. levels: Number of levels to include in the tree.

Returns:

Type Description
Tuple[List[Node], List[Node]]

List of nodes and edges to be displayed.

Note

This method assumes that the data source graph is generated from an ESL specification.

Source code in src/raesl/plot/view_funcs.py
def functional_dependency(graph: Graph, root: Node, levels: int) -> Tuple[List[Node], List[Node]]:
    """Filter method for drawing a nested functional dependency structure.

    Arguments
        graph: Instantiated ESL graph.
        root: Root node for which the dependency structure must be drawn.
        levels: Number of levels to include in the tree.

    Returns:
       List of nodes and edges to be displayed.

    Note:
       This method assumes that the data source graph is generated from an
       ESL specification.
    """

    components = list(utils.get_up_to_depth(roots=[root], depth=root.depth + levels))

    functions, edges = multi_domain(
        graph,
        node_kinds=["function_spec"],
        edge_kinds=["functional_dependency", "logical_dependency"],
        lead_components=components,
    )

    # Add dependencies between goals and components that cannot be traced down to
    # transformations.
    tree = sorted(
        set([a.name for c in components for a in c.ancestors]).union(
            set([c.name for c in components])
        )
    )
    edges += _add_component_goal_edges(graph, tree, functions)

    return components + functions, edges

hierarchy

hierarchy(
    graph: Graph, roots: List[Node], levels: int
) -> Tuple[List[Node], List[Edge]]

Filter nodes and create edges for drawing a hierarchical decomposition diagram.

Parameters:

Name Type Description Default
graph Graph

Source data graph.

required
roots List[Node]

Roots of the hierarchical diagram.

required
levels int

Number of levels to include in the diagram.

required

Returns:

Type Description
Tuple[List[Node], List[Edge]]

List of selected nodes and list of created edges.

Source code in src/raesl/plot/view_funcs.py
def hierarchy(graph: Graph, roots: List[Node], levels: int) -> Tuple[List[Node], List[Edge]]:
    """Filter nodes and create edges for drawing a hierarchical decomposition diagram.

    Arguments:
       graph: Source data graph.
       roots: Roots of the hierarchical diagram.
       levels: Number of levels to include in the diagram.

    Returns:
        List of selected nodes and list of created edges.
    """

    nodes, edges = [], []
    for r in roots:
        max_depth = r.depth + levels - 1
        nodes.append(r)
        crawled_nodes, crawled_edges = utils.crawl_descendants(r, max_depth)
        nodes.extend(crawled_nodes)
        edges.extend(crawled_edges)

    # Create nodes in which hierarchy information is stripped to prevent
    # nested plotting.
    stripped_nodes = []
    for node in nodes:
        copied_node = deepcopy(node)
        copied_node.parent = None
        copied_node.children = None
        stripped_nodes.append(copied_node)

    # Modify source and targets of edges to stripped nodes.
    node_dict = {n.name: n for n in stripped_nodes}
    for e in edges:
        e.source = node_dict[e.source.name]
        e.target = node_dict[e.target.name]

    return stripped_nodes, edges

multi_domain

multi_domain(
    graph: Graph,
    node_kinds: Optional[List[str]] = None,
    edge_kinds: Optional[List[str]] = None,
    edge_labels: Optional[List[str]] = None,
    edge_weights: Optional[List[str]] = None,
    lead_components: Optional[List[Node]] = None,
    depth: int = 2,
) -> Tuple[List[Node], List[Edge]]

Create multi-domain-matrix visualization based on an ESL derived graph.

Parameters:

Name Type Description Default
graph Graph

Input graph object derived from an ESL specification.

required
node_kinds Optional[List[str]]

The node kinds that are included in the plot. Defaults to all node_kinds present within the graph.

None
edge_kinds Optional[List[str]]

The edge kinds that are included in the plot. Defaults to all edge_kinds present within the graph.

None
edge_labels Optional[List[str]]

The edge labels that are shown in the plot. Defaults to all edge_labels present within the graph.

None
edge_weights Optional[List[str]]

The edge weight types that are shown in the plot. Defaults to a single random edge weight present within the graph.

None
lead_components Optional[List[Node]]

The lead components to be used to select other nodes.

None
depth int

Depth up to which lead components must be selected if no lead components are provided.

2

Returns:

Type Description
Tuple[List[Node], List[Edge]]

Node list and Edge list.

Source code in src/raesl/plot/view_funcs.py
def multi_domain(
    graph: Graph,
    node_kinds: Optional[List[str]] = None,
    edge_kinds: Optional[List[str]] = None,
    edge_labels: Optional[List[str]] = None,
    edge_weights: Optional[List[str]] = None,
    lead_components: Optional[List[Node]] = None,
    depth: int = 2,
) -> Tuple[List[Node], List[Edge]]:
    """Create multi-domain-matrix visualization based on an ESL derived graph.

    Arguments:
        graph: Input graph object derived from an ESL specification.
        node_kinds: The node kinds that are included in the plot. Defaults to all
            node_kinds present within the graph.
        edge_kinds: The edge kinds that are included in the plot. Defaults to all
            edge_kinds present within the graph.
        edge_labels: The edge labels that are shown in the plot. Defaults to all
            edge_labels present within the graph.
        edge_weights: The edge weight types that are shown in the plot. Defaults to a
            single random edge weight present within the graph.
        lead_components: The lead components to be used to select other nodes.
        depth: Depth up to which lead components must be selected if no lead components
            are provided.

    Returns:
        Node list and Edge list.
    """

    if lead_components:
        utils.check_tree_disjunction(nodes=lead_components)
    else:
        lead_components = list(
            utils.get_up_to_depth(
                roots=[r for r in graph.roots if r.kind == "component"], depth=depth
            )
        )

    node_kinds = graph.node_kinds if node_kinds is None else node_kinds
    edge_kinds = graph.edge_kinds if edge_kinds is None else edge_kinds
    edge_labels = graph.edge_labels if edge_labels is None else edge_labels
    edge_weights = graph.edge_weight_labels if edge_weights is None else edge_weights

    nodes = utils.filter_nodes(graph, lead_components=lead_components, node_kinds=node_kinds)

    edges = [
        e for e in graph.edges_between_all(sources=nodes, targets=nodes) if e.kind in edge_kinds
    ]

    # Ensure that mapping dependencies appear below the diagonal.
    n2n = {kind: idx for idx, kind in enumerate(node_kinds)}
    modified_edges = []
    added_edges = []
    for e in edges:
        if e.kind != "mapping_dependency":
            continue
        if n2n[e.source.kind] > n2n[e.target.kind]:
            modified_edges.append(e)
            modified_edge = deepcopy(e)
            modified_edge.source = graph[e.source.name]
            modified_edge.target = graph[e.target.name]
            added_edges.append(modified_edge)

    for e in modified_edges:
        edges.remove(e)

    edges += added_edges

    if "variable" in node_kinds and "functional_dependency" in edge_kinds:
        edges += utils.migrate_edges_between_variables(graph, lead_components=lead_components)

    return nodes, edges

traceability

traceability(
    graph: Graph,
    root: Node,
    levels: int,
    style: Style = Style(),
) -> Tuple[List[Node], List[Edge]]

Filter nodes and edges for drawing a traceability diagram.

Parameters:

Name Type Description Default
graph Graph

Instantiated ESL graph.

required
root Node

The root transformation specification.

required
levels int

Number of levels to include in the traceability diagram.

required
style Style

RaESL style options.

Style()

Returns:

Type Description
Tuple[List[Node], List[Edge]]

List of selected nodes and list of selected edges.

Source code in src/raesl/plot/view_funcs.py
def traceability(
    graph: Graph, root: Node, levels: int, style: Style = Style()
) -> Tuple[List[Node], List[Edge]]:
    """Filter nodes and edges for drawing a traceability diagram.

    Arguments:
        graph: Instantiated ESL graph.
        root: The root transformation specification.
        levels: Number of levels to include in the traceability diagram.
        style: RaESL style options.

    Returns:
        List of selected nodes and list of selected edges.
    """
    # Get functions in tree and traceability edges.
    g = deepcopy(graph)
    functions, edges = utils.get_function_tree(g, root, levels)

    components = [c for c in set([g[f.annotations.esl_info["body"]["active"]] for f in functions])]

    # Remove component hierarchy to make traceability hierarchy leading.
    for c in components:
        c.parent = None
        c.children = []

    if style.diagram.show_function_dependencies:
        edges.update(
            set(
                [
                    e
                    for e in g.edges_between_all(functions, functions)
                    if e.kind in ["functional_dependency", "logical_dependency"]
                ]
            )
        )

    return components + list(functions), list(edges)