Skip to content

graflo.plot.merge

Draw a merge preview: the declaration graph, with its conflicts marked.

The picture a :class:~graflo.architecture.evolution.preview.MergePreview is asking to be: each side's classes grouped together, the merged names between them, and every declaration as an edge. Classes are drawn as records — one field per attribute, each its own Graphviz port — so a property rename lands on the row it renames rather than somewhere on the box. That is the whole reason to draw this at all: class_A - attr_a - attr_b - class_B is a statement about attributes, and a diagram that only draws classes cannot show it.

Findings colour the border of what they name and are numbered into a legend, the number repeated on the class or row it concerns, so the picture says where as well as what. A refusal — the one merge actually raised — is red; everything the structural pass found on its own is amber; the declarations a completion suggests are dashed green, because a refusal that carries its own fix should look like one.

:func:build_preview_graph returns a plain :mod:networkx graph and needs no Graphviz, so what is drawn can be asserted directly; :func:plot_merge_preview is the half that writes a file.

Attributes

EDGE_STYLE = {'member': {'style': 'solid', 'color': '#2C3E50', 'penwidth': '1.4'}, 'map': {'style': 'dashed', 'color': '#1F5FBF'}, 'property_map': {'style': 'dashed', 'color': '#1F5FBF', 'penwidth': '0.7'}, 'property_equivalence': {'style': 'dotted', 'color': '#6B4C9A', 'penwidth': '0.9'}, 'suggested': {'style': 'dashed', 'color': '#1E8449', 'penwidth': '1.2'}, 'fusion': {'style': 'solid', 'color': '#A0A0A0', 'penwidth': '0.6'}, 'demotion': {'style': 'dashed', 'color': '#8E5A2B', 'penwidth': '0.9'}, 'property_rename': {'style': 'dashed', 'color': '#6B4C9A', 'penwidth': '0.7'}, 'derivation': {'style': 'solid', 'color': '#B9770E', 'penwidth': '1.1'}, 'local_key': {'style': 'solid', 'color': '#117A65', 'penwidth': '1.1'}} module-attribute

FONT_SIZE = 11 module-attribute

HEADER_COLOR = {'left': '#B7D1DF', 'right': '#BEDFC8', 'merged': '#FFE5B4', 'canonical': '#DDD0E5', 'ghost': '#E4E1D7', 'relation': '#FFFFFF', 'resource': '#ECEFF1'} module-attribute

OVERRIDDEN_STYLE = {'style': 'dotted', 'color': '#B0B0B0', 'fontcolor': '#909090'} module-attribute

SEVERITY_COLOR = {'refusal': '#C0392B', 'possible': '#E67E22', 'note': '#7F8C8D'} module-attribute

__all__ = ['EDGE_STYLE', 'HEADER_COLOR', 'OVERRIDDEN_STYLE', 'SEVERITY_COLOR', 'build_preview_graph', 'plot_merge_preview'] module-attribute

Classes

Functions:

build_preview_graph(preview, *, max_rows=12, legend=True, fusion=False)

The preview as a networkx graph, styled but not yet drawn.

Only classes, relations and merged names become nodes: attributes are rows on their owner, addressed by port. Every node carries the preview id it came from as preview_id, so a caller can map back.

Parameters:

Name Type Description Default
preview MergePreview

What :func:~graflo.architecture.evolution.preview.preview_merge returned.

required
max_rows int

Attribute rows to draw per class before summarising the rest.

12
legend bool

Whether to include the findings legend.

True
fusion bool

Whether to draw the edge from each attribute that keeps its name; off by default, since on a whole merge it ties every row.

False

Returns:

Type Description
MultiDiGraph

A graph whose node and edge attributes are Graphviz attributes.

Source code in graflo/plot/merge.py
def build_preview_graph(
    preview: MergePreview,
    *,
    max_rows: int = 12,
    legend: bool = True,
    fusion: bool = False,
) -> nx.MultiDiGraph:
    """The preview as a networkx graph, styled but not yet drawn.

    Only classes, relations and merged names become nodes: attributes are
    rows on their owner, addressed by port. Every node carries the preview id
    it came from as ``preview_id``, so a caller can map back.

    Args:
        preview: What :func:`~graflo.architecture.evolution.preview.preview_merge` returned.
        max_rows: Attribute rows to draw per class before summarising the rest.
        legend: Whether to include the findings legend.
        fusion: Whether to draw the edge from each attribute that keeps its
            name; off by default, since on a whole merge it ties every row.

    Returns:
        A graph whose node and edge attributes are Graphviz attributes.
    """
    graph = nx.MultiDiGraph()
    graph.graph["graph"] = {
        "rankdir": "LR",
        "splines": "spline",
        "fontname": "Helvetica",
        "labelloc": "t",
        "label": _title(preview),
    }
    graph.graph["node"] = {"fontname": "Helvetica", "fontsize": str(FONT_SIZE)}
    graph.graph["edge"] = {"fontname": "Helvetica", "fontsize": "9"}

    node_flags, edge_flags, badges = _flags(preview)
    edges = _drawn_edges(preview, fusion=fusion)
    nodes = {n.id: n for n in preview.nodes}
    taken = _class_nodes(
        graph,
        preview,
        node_flags=node_flags,
        badges=badges,
        max_rows=max_rows,
        keep=_endpoints(edges),
    )
    for edge in edges:
        ends = _edge_ends(nodes, taken, edge)
        if ends is not None:
            _attach(graph, *ends, _edge_attrs(edge, edge_flags.get(edge.id)))

    if legend:
        graph.add_node("legend", **_legend(preview), kind="legend", preview_id="legend")
    return graph

plot_merge_preview(preview, path, *, output_format=None, prog='dot', dpi=None, max_rows=12, legend=True)

Draw preview to path.

Parameters:

Name Type Description Default
preview MergePreview

What preview_merge returned.

required
path str | PathLike[str]

Where to write; the suffix picks the format (svg/pdf/png/dot).

required
output_format str | None

Override the format the suffix implies.

None
prog str

Graphviz layout program.

'dot'
dpi int | None

Raster resolution, for png.

None
max_rows int

Attribute rows per class before the rest are summarised.

12
legend bool

Whether to draw the findings legend.

True

Returns:

Type Description
Path

The path written.

Raises:

Type Description
RuntimeError

Rendering is unavailable or failed; the message says why.

ValueError

The format is not one this can write.

Source code in graflo/plot/merge.py
def plot_merge_preview(
    preview: MergePreview,
    path: str | os.PathLike[str],
    *,
    output_format: str | None = None,
    prog: str = "dot",
    dpi: int | None = None,
    max_rows: int = 12,
    legend: bool = True,
) -> Path:
    """Draw *preview* to *path*.

    Args:
        preview: What ``preview_merge`` returned.
        path: Where to write; the suffix picks the format (svg/pdf/png/dot).
        output_format: Override the format the suffix implies.
        prog: Graphviz layout program.
        dpi: Raster resolution, for ``png``.
        max_rows: Attribute rows per class before the rest are summarised.
        legend: Whether to draw the findings legend.

    Returns:
        The path written.

    Raises:
        RuntimeError: Rendering is unavailable or failed; the message says why.
        ValueError: The format is not one this can write.
    """
    graph = build_preview_graph(preview, max_rows=max_rows, legend=legend)
    dot = to_dot(graph)
    _side_groups(graph, dot)
    return draw(dot, path, output_format=output_format, prog=prog, dpi=dpi)