Skip to content

graphviz

Functionality to display the tracer as a visual graph.

It first convert it to a graph, using visual_graph.py, and then renders that with graphviz.

add_legend(dot, options)

Add a legend with nodes and edge styles.

Creates one node for each node style and one edge for each edge style.

It was difficult to get it to appear in a way that didn't disrupt the main graph. It was easiest to get it to be a vertically aligned column of nodes, on the left of the graph. To do this, I link all the nodes with invisible edges so they stay vertically aligned.

https://stackoverflow.com/a/52300532/907060.

Source code in lineapy/visualizer/graphviz.py
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
def add_legend(dot: graphviz.Digraph, options: VisualGraphOptions):
    """
    Add a legend with nodes and edge styles.

    Creates one node for each node style and one edge for each edge style.

    It was difficult to get it to appear in a way that didn't disrupt the  main
    graph. It was easiest to get it to be a vertically aligned column of nodes,
    on the left of the graph. To do this, I link all the nodes with invisible
    edges so they stay vertically aligned.

    https://stackoverflow.com/a/52300532/907060.
    """
    with dot.subgraph(name="cluster_0") as c:
        c.attr(color=CLUSTER_EDGE_COLOR)
        c.attr(label="Legend")

        ##
        # Add nodes to legend
        ##

        # Save the previous ID so we can add an invisible edge.
        prev_id: Optional[str] = None
        for node_type, label in NODE_LABELS.items():
            id_ = f"legend_node_{label}"

            extra_labels: ExtraLabels = []
            # If this isn't in the first node, add an invisible edge from
            # the previous node to it.
            if prev_id:
                c.edge(prev_id, id_, style="invis")
            # If this is the first node, add sample extra labels
            else:
                for v, extra_label_label in extra_label_labels(
                    options
                ).items():
                    extra_labels.append(ExtraLabel(extra_label_label, v))
            render_node(
                c,
                VisualNode(id_, node_type, label, extra_labels),
                fontname=SERIF_FONT,
            )
            prev_id = id_

        ##
        # Add edges to legend
        ##
        edges_for_legend = edge_labels(options)
        if edges_for_legend:
            # Keep adding invisible edges, so that all of the nodes are aligned vertically
            id_ = "legend_edge"
            c.node(id_, "", shape="box", style="invis")
            c.edge(prev_id, id_, style="invis")
            prev_id = id_
            for edge_type, label in edges_for_legend.items():
                id_ = f"legend_edge_{label}"
                # Add invisible nodes, so the edges have something to point to.
                c.node(id_, "", shape="box", style="invis")
                c.edge(
                    prev_id,
                    id_,
                    label=label,
                    **edge_type_to_kwargs(edge_type, highlighted=True),
                )
                prev_id = id_
    return id_

edge_labels(options)

Labels for the edge types to use in the legend,

Source code in lineapy/visualizer/graphviz.py
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
def edge_labels(
    options: VisualGraphOptions,
) -> Dict[VisualEdgeType, str]:
    """
    Labels for the edge types to use in the legend,
    """
    l: Dict[VisualEdgeType, str] = {
        VisualEdgeType.SOURCE_CODE: "Source Code",
        VisualEdgeType.MUTATE_CALL: "Mutate Call",
        VisualEdgeType.IMPLICIT_DEPENDENCY: "Implicit Dependency",
        VisualEdgeType.CONTROL_DEPENDENCY: "Control Flow",
    }
    if options.show_implied_mutations:
        l[VisualEdgeType.LATEST_MUTATE_SOURCE] = "Implied Mutate"
    if options.show_views:
        l[VisualEdgeType.VIEW] = "View"
    return l

extra_label_labels(options)

Labels for the extra label, to use in the legend,

Source code in lineapy/visualizer/graphviz.py
165
166
167
168
169
170
171
172
173
174
175
176
def extra_label_labels(
    options: VisualGraphOptions,
) -> Dict[ExtraLabelType, str]:
    """
    Labels for the extra label, to use in the legend,
    """
    l: Dict[ExtraLabelType, str] = {}
    if options.show_artifacts:
        l[ExtraLabelType.ARTIFACT] = "Artifact Name"
    if options.show_variables:
        l[ExtraLabelType.VARIABLE] = "Variable Name"
    return l

extra_labels_to_html(extra_labels, highlighted)

Convert extra labels into an HTML table, where each label is a row. A single node could have multiple variables and artifacts pointing at it. So we make a table with a row for each artifact. Why a table? Well we are putting it in the xlabel and a table was an easy way to have multiple rows with different colors.

Source code in lineapy/visualizer/graphviz.py
237
238
239
240
241
242
243
244
245
246
247
248
def extra_labels_to_html(extra_labels: ExtraLabels, highlighted: bool) -> str:
    """
    Convert extra labels into an HTML table, where each label is a row.
    A single node could have multiple variables and artifacts pointing at it.
    So we make a table with a row for each artifact. Why a table? Well we are putting
    it in the xlabel and a table was an easy way to have multiple rows with different colors.
    """
    rows = [
        f'<TR ><TD BGCOLOR="{get_color(el.type, highlighted)}"><FONT POINT-SIZE="10">{el.label}</FONT></TD></TR>'
        for el in extra_labels
    ]
    return f'<<TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0">{"".join(rows)}</TABLE>>'

get_color(tp, highlighted)

Get the color for a type. Note that graphviz colorscheme indexing is 1 based

Source code in lineapy/visualizer/graphviz.py
198
199
200
201
202
203
def get_color(tp: ColorableType, highlighted: bool) -> str:
    """
    Get the color for a type. Note that graphviz colorscheme indexing
    is 1 based
    """
    return color(COLORS[tp], highlighted)

Was this helpful?

Help us improve docs with your feedback!