Skip to main content

Grot

Grot is a noun and means arrowhead in polish language.

Makes graphviz usage simpler

Much less headache. Gets you faster into the point.

Hello Grot

To generate a graph you need to import Grot class, create its instance and define nodes and edges. While g.edge() call, you can pass unlimited number of nodes or plain strings (creates implicit node).

If you don't connect given node (as unconnected) it's going to float somewhere around.

def example_01():
    import os
    from grot import Grot

    this_dir_path = os.path.dirname(__file__)  # if run in console - remove 'directory' parameter below
    out_dir_path = os.path.join(this_dir_path, "out")

    g = Grot(name="example_01", format="png", directory=out_dir_path, graph_attrs={"rankdir": "LR"})

    one = g.node("It is\neaiser")
    two = g.node("graphs", color="#8a9bac")
    ignored = g.node("Node floats when\nunconnected", color="#da3080")

    g.edge(one, "to define", two)
    g.render()

Source of this example is in examples/example_01.py.

It generates raw dot-syntax text file in: examples/out/example_01.gv. And the final graph file is in: examples/out/example_01.gv.png:

Rendered example image to be shown in gitlab)

Branches

One call of g.edge(node1, node2, node3, ...) creates single chain of arrows. To make a branch, you need to call g.edge once again.

def example_02a():
    from grot import Grot

    this_dir_path = os.path.dirname(__file__)  # if run in console - remove 'directory' parameter below
    out_dir_path = os.path.join(this_dir_path, "out")
    g = Grot(name="example_02a", format="png", directory=out_dir_path, graph_attrs={"rankdir": "LR"})

    one = g.node("One")
    two = g.node("Two")
    three = g.node("Three")
    four = g.node("Four")
    five = g.node("Five")
    six = g.node("Six")
    seven = g.node("Seven")

    g.edge(one, two, three, four, "A")
    g.edge(two, five, six, "B")
    g.edge(two, seven, "C")

    g.render()

Source of this example is in examples/example_02.py.

It generates raw dot-syntax text file in: examples/out/example_02a.gv. And the final graph file is in: examples/out/example_02a.gv.png:

Rendered example image to be shown in gitlab)

Branches - syntax variant

You don't have to assign nodes to variables. However it's a good practice to do so. You can define nodes while g.edge(...) call. Here node two is assigned to a local variable, because we refer it several times.

def example_02b():
    from grot import Grot

    this_dir_path = os.path.dirname(__file__)  # if run in console - remove 'directory' parameter below
    out_dir_path = os.path.join(this_dir_path, "out")

    g = Grot(name="example_02b", format="png", directory=out_dir_path, graph_attrs={"rankdir": "LR"})

    two = g.node("Two")
    g.edge(g.node("One"), two, g.node("Three"), g.node("Four"), "A")
    g.edge(two, g.node("Five"), g.node("Six"), "B")
    g.edge(two, g.node("Seven"), "C")

    g.render()

Source of this example is in examples/example_02.py.

It generates raw dot-syntax text file in: examples/out/example_02b.gv. And the final graph file is in: examples/out/example_02b.gv.png:

Rendered example image to be shown in gitlab)

Branches

One call of g.edge(node1, node2, node3, ...) creates single chain of arrows. To make a branch, you need to call g.edge once again.

def example_tree():
    import os
    from typing import List, Optional

    from airium import Airium
    from grot import Grot, NodeVisit, Node

    this_dir_path = os.path.dirname(__file__)  # if run in console - remove 'directory' parameter below
    out_dir_path = os.path.join(this_dir_path, "out")

    font_face = "Monospace"  # "Courier New", "Helvetica"
    g = Grot(
        name="example_tree",
        format="png",
        directory=out_dir_path,
        graph_attrs={
            "fontname": font_face,
            "layout": "neato",
            "overlap": "scalexy",
            "mode": "ipsep",
            "sep": "+5",
        },
        node_attrs={
            "fontname": font_face,
            "penwidth": "1.1",
            "shape": "box",
            "width": "0",
            "height": "0",
            "margin": "0",
        },
        edge_attrs={
            "fontname": font_face,
            "penwidth": "0.80",
        },
    )

    def html_table_label(
        own_name: str, own_type: str, own_path: str, children: list[NodeVisit], fg_color: str, bg_color=str
    ) -> str:
        a = Airium(base_indent="")

        with a.table(border="0", bgcolor=bg_color):
            shaded_color = "#757575"
            with a.tr():
                with a.td(align="left").font(color=fg_color, **{"point-size": "29"}):
                    a(f" <b><u>{own_name}</u></b>")
                with a.td(align="right").font(color=fg_color):
                    types_ = " | ".join(sorted(set(c.type for c in children)))
                    types_ = f" [{types_[30:]}...]" if len(types_) > 36 else f" [{types_}]"
                    a(f"{own_type}{types_}")
            with a.tr():
                with a.td(colspan=2, align="center").font(color=shaded_color):
                    a(f"<i>{own_path}</i>")

            for visit in children:
                type_annotation = f'<font color="{shaded_color}"><b>{visit.type}</b> '
                sub_types = " | ".join(sorted(NodeVisit.children_types(visit.value)))
                if len(sub_types) > 36:
                    type_annotation += f"[{sub_types[:30]}...]"
                elif sub_types:
                    type_annotation += f"[{sub_types}]"
                type_annotation += "</font>"
                right = repr(visit.value)
                if len(right) < 55 and "<" not in right:
                    left = f"{visit.key}: {type_annotation}"
                    right = f" = {right}"
                else:
                    left = f"{visit.key}:"
                    right = type_annotation

                with a.tr():
                    a.td(align="left").font(_t=left, color=fg_color)
                    a.td(align="right").font(_t=right, color=fg_color)

            if not children:
                a.tr().td(colspan=2).font(_t="No children", color=fg_color)
        return str(a)

    def draw_node(node_object, path=("root",)) -> Optional[Node]:
        children: List[NodeVisit] = []
        for visit in NodeVisit.iter_tree_nodes(node_object):
            visit: NodeVisit
            visit.graph_node_id = draw_node(visit.value, (*path, visit.key))
            children.append(visit)

        if not children:
            return  # draw only containers not leafs
        fg_col = "#111111" if len(path) < 2 else "#112233" if len(path) % 2 else "#334433"
        bg_col = "#eeeeee" if len(path) < 2 else "#ddeedd" if len(path) % 2 else "#eee6dd"
        table_body: str = html_table_label(
            own_name=path[-1],
            own_type=type(node_object).__name__,
            own_path=NodeVisit.join_path_str(*path),
            children=children,
            fg_color=fg_col,
            bg_color=bg_col,
        )

        own_node: Node = g.html_node(table_body)

        for visit in children:
            if visit.graph_node_id:
                g.edge(own_node, visit.graph_node_id)

        return own_node

    my_nested_structure = {
        "web-app": {
            "servlet": [
                {
                    "servlet-name": "cofaxCDS",
                    "servlet-class": "org.cofax.cds.CDSServlet",
                    "init-param": {
                        "configGlossary:installationAt": "Philadelphia, PA",
                        "configGlossary:poweredBy": "Cofax",
                        "dataStoreLogFile": "/usr/local/tomcat/logs/datastore.log",
                        "dataStoreConnUsageLimit": 100,
                        "dataStoreLogLevel": "debug",
                        "maxUrlLength": 500,
                    },
                },
                {
                    "servlet-name": "cofaxEmail",
                    "servlet-class": "org.cofax.cds.EmailServlet",
                    "init-param": {"mailHost": "mail1", "mailHostOverride": "mail2"},
                },
                {"servlet-name": "cofaxAdmin", "servlet-class": "org.cofax.cds.AdminServlet"},
                {"servlet-name": "fileServlet", "servlet-class": "org.cofax.cds.FileServlet"},
                {
                    "servlet-name": "cofaxTools",
                    "servlet-class": "org.cofax.cms.CofaxToolsServlet",
                    "init-param": {
                        "templatePath": "toolstemplates/",
                        "log": 1,
                        "logLocation": "/usr/local/tomcat/logs/CofaxTools.log",
                        "lookInContext": 1,
                        "adminGroupID": 4,
                        "betaServer": True,
                    },
                },
            ],
            "servlet-mapping": {
                "cofaxCDS": "/",
                "cofaxEmail": "/cofaxutil/aemail/*",
                "cofaxAdmin": "/admin/*",
                "fileServlet": "/static/*",
                "cofaxTools": "/tools/*",
            },
            "taglib": {"taglib-uri": "cofax.tld", "taglib-location": "/WEB-INF/tlds/cofax.tld"},
        }
    }
    draw_node(my_nested_structure)

    g.render()

Source of this example is in examples/example_tree.py.

It generates raw dot-syntax text file in: examples/out/example_tree.gv. And the final graph file is in: examples/out/example_tree.gv.png:

Rendered example image to be shown in gitlab)

Metadata

Release files for grot 0.2.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for grot 0.2.1
File Size Uploaded
grot-0.2.1.tar.gz 19.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for grot 0.2.1
File Interpreter ABI Platform
grot-0.2.1-py3-none-any.whl Python 3 none any Details

Total release size: 28.9 kB

Release files / grot-0.2.1.tar.gz

Download URL grot-0.2.1.tar.gz
Size 19.5 kB
Tags Source
SHA-256 checksum
How to use checksums
6288943645947b430658883ab753e315b5b4f02052a57ba613927620b11fb557
BLAKE2b-256 checksum
How to use checksums
d000af8841fc36f65d73c776576cc157fc16128c90d83df1ce66304c144c81f8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/4.0.1 CPython/3.10.7

Release files / grot-0.2.1-py3-none-any.whl

Download URL grot-0.2.1-py3-none-any.whl
Size 9.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
d54aa1ec0706b9eac23ded038a741474942c876915c62aeb5a21db263924cd18
BLAKE2b-256 checksum
How to use checksums
57c5d9fef3170dc6c0de5c9ff8d5c6bc0a3de58b034b85b85026ed7a1c8fbec9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/4.0.1 CPython/3.10.7

Release history Release notifications | RSS feed

This release

0.2.1 This release

2 release files

0.2.0

1 release file

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

0.0.1

1 release file

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page