Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Can you nest and reference other diagrams? #436

Open
jacorbello opened this issue Jan 11, 2021 · 7 comments
Open

Can you nest and reference other diagrams? #436

jacorbello opened this issue Jan 11, 2021 · 7 comments

Comments

@jacorbello
Copy link

jacorbello commented Jan 11, 2021

I'd like to create separate diagrams for various applications/departments/etc. Is there a way to create say 3 diagrams:

main.py
- diagrams
  - application1.py
  - appliaction2.py
  - application3.py

and reference them all in a "master" diagram?

with Diagram(path='./diagrams/application1.py', label='Application 1')
with Diagram(path='./diagrams/application2.py', label='Application 2')
with Diagram(path='./diagrams/application3.py', label='Application 3')

This is in an effort to reference application dependencies between applications without having to update the same architecture in multiple diagrams.

@clayms
Copy link

clayms commented Jan 12, 2021

The closest thing I can think of would be something similar to using the no-op flag in neato (-n CLI flag).

See "-n[num]" in https://graphviz.gitlab.io/doc/info/command.html
This Stack Overflow answer also describes how.
Also see the Neato Layout Manual.

This library uses the Digraph class from the Graphviz library.

from graphviz import Digraph

self.dot = Digraph(self.name, filename=self.filename)

Refering to the Graphviz library Digraph is a "Directed graph source code in the DOT language."
https://graphviz.readthedocs.io/en/stable/api.html?highlight=Digraph#digraph
https://graphviz.readthedocs.io/en/stable/_modules/graphviz/dot.html#Digraph
Or: https://github.com/xflr6/graphviz/blob/master/graphviz/dot.py#L296toL297

The filename parameter of Digraph :

filename – Filename for saving the source (defaults to name + '.gv').

This library generates a graphviz (.gv) file, then renders the output image, and finally removes the graphviz file when exiting the Diagram context.

# Remove the graphviz file leaving only the image.
os.remove(self.filename)

I tried commenting out the line that removes the graphviz file in hopes that I could somehow use it with the no-op flag, but the graphviz file did not remain.

@clayms
Copy link

clayms commented Mar 16, 2021

@jacorbello Pretty sure the answer is "yes."
Have a look at issue #487 (comment).

That example is furthered below by adding separate external graphs contained in their own Clusters

1. create and save separate graphviz dot files

from diagrams import Diagram, Cluster

from diagrams.k8s.clusterconfig import HPA
from diagrams.k8s.compute import Deployment, Pod, ReplicaSet
from diagrams.k8s.network import Ingress, Service


with Diagram("Exposed Pod with 3 Replicas", show=False) as diag_1:
    net = Ingress("domain.com") >> Service("svc")
    net >> [Pod("pod1"),
            Pod("pod2"),
            Pod("pod3")] << ReplicaSet("rs") << Deployment("dp") << HPA("hpa")


diag_1.dot.save("diag_1.gv")
from diagrams.aws.compute import EC2
from diagrams.aws.database import RDS
from diagrams.aws.network import ELB

with Diagram("Grouped Workers", show=False, direction="TB") as diag_2:
    ELB("lb") >> [EC2("worker1"),
                  EC2("worker2"),
                  EC2("worker3"),
                  EC2("worker4"),
                  EC2("worker5")] >> RDS("events")

diag_2.dot.save("diag_2.gv")

2. load dot files and convert to Digraph

from graphviz import Digraph, Source

diag_import_body1 = Source.from_file(filename="diag_1.gv", format="png").source.split('\n')[1:-2]
diag_import_body2 = Source.from_file(filename="diag_2.gv", format="png").source.split('\n')[1:-2]


diag_import1 = Digraph(body=diag_import_body1)
diag_import2 = Digraph(body=diag_import_body2)

3. Create final Diagram with each Digraph in a separate cluster

with Diagram("", show=False, ) as out_diag:
    with Cluster("1", ) as c1:
        c1.subgraph(diag_import1)
    with Cluster("2", graph_attr={"direction":"TB"}) as c2:
        c2.subgraph(diag_import2)

out_diag

image

@jobinjosem1
Copy link

@clayms Do you know if it is possible to connect the two clusters in the final output? Something in the above example like
c1 - Edge(color="red") - c2

@clayms
Copy link

clayms commented Dec 9, 2022

@jobinjosem1 I think you would have to add some blank and zero size nodes to the final clusters. Then do something like what is shown here: #17 (comment)

@ristillu
Copy link

I'm new to Diagrams. It looks awesome. I am one of the many people who it seems could benefit from a hierarchical breakdown of architecture components into separate files. Does anyone know if it's possible to take something like the solution in here and specifically the part here:

with Diagram("", show=False, ) as out_diag:
    with Cluster("1", ) as c1:
        c1.subgraph(diag_import1)
    with Cluster("2", graph_attr={"direction":"TB"}) as c2:
        c2.subgraph(diag_import2)

out_diag

but instead modifying it a bit like:

with Diagram("", show=False, ) as out_diag:
    with Cluster("1", ) as c1:
        c1.subgraph(diag_import1)
    with Cluster("2", graph_attr={"direction":"TB"}) as c2:
        c2.subgraph(diag_import2)

    SomeNodeType("foo") >> c1 >> c2 >> SomeNodeType("bar")

That is, I would like to build up a diagram with the parent diagram having relationships between the subgraphs that I'm loading in as per @clayms' excellent example. And ideally without delving into the graphviz data structures and iterating through their internals looking for things created by other files that I don't want to open.

The motivating use case is one or multiple people working on a large service oriented architecture where a parent document ties everyone's individual pieces of the overall architecture into a cohesive diagram at the top level.

@zingagent
Copy link

For arrows to clusters see #17

Otherwise, I approach the compartmentalisation of the diagrams differently - via python functions.

Here are 3 files to show how I handle it.

First - two independent diagrams

aws.py


from diagrams import Diagram
from diagrams.aws.compute import EC2
from diagrams.aws.database import RDS
from diagrams.aws.network import ELB

def aws_diagram():
    lb = ELB("lb")
    workers = [EC2("worker1"),
               EC2("worker2"),
               EC2("worker3"),
               EC2("worker4"),
               EC2("worker5")]
    events = RDS("events")

    lb >> workers >> events

    return lb, workers, events

def main():
    with Diagram("Grouped Workers", direction="TB", show=True) as aws_diag:
        aws_diagram()

if __name__ == "__main__":
    main()

k8s.py

from diagrams import Diagram
from diagrams.k8s.clusterconfig import HPA
from diagrams.k8s.compute import Deployment, Pod, ReplicaSet
from diagrams.k8s.network import Ingress, Service

def k8s_diagram():
    net = Ingress("domain.com") >> Service("svc")
    pods = [Pod("pod1"),
            Pod("pod2"),
            Pod("pod3")]
    rs = ReplicaSet("rs")
    dp = Deployment("dp")
    hpa = HPA("hpa")
    net >> pods << rs << dp << hpa

    return net, pods, rs, dp, hpa

def main():
    with Diagram("Exposed Pod with 3 Replicas", show=True) as k8s_diag:
        k8s_diagram()

if __name__ == "__main__":
    main()

Then tie everything together

combo.py

from diagrams import Diagram, Cluster
from diagrams.onprem.client import User, Users

from aws import aws_diagram
from k8s import k8s_diagram

with Diagram("Combo Diagram") as combo_diag:

    with Cluster("1", ) as c1:
        lb, workers, events = aws_diagram()

    with Cluster("2", ) as c2:
        net, pods, rs, dp, hpa = k8s_diagram()

    foo = User("foo")
    bar = Users("bar")

    foo >> lb >> bar
    foo >> net >> bar

So here are three diagrams from these files

exposed_pod_with_3_replicas
grouped_workers
combo_diagram

I also make use of parameters to the functions. So I might pass in a thing to be linked to the component in the diagram. One overall diagram might pass in something high level, so the diagram overall can focus on what is in the called function sub-diagram within a high level overview around it, or another diagram might pass in some object more specific, where the sub-diagram needs to be seen connecting to the details of the wider diagram.

Hope this make sense.

@ristillu
Copy link

Amazing, thanks @zingagent

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants