Skip to main content

Generates multi-view diagrams from textual descriptions

Project description

Introduction

hiearch is a CLI utility that generates multiple diagrams (views) from a common textual description. hiearch supports node hierarchies that are automatically expanded, collapsed, or hidden, depending on configuration of a particular view. In this sense the utility is more similar to visualization tools like https://structurizr.com than to classic generators such as graphviz. Currently, hiearch uses graphviz to generate diagrams, but other backends may be added in the future.

The main purpose of hiearch is graphical representation of complex systems, but it is meant to be generic and may find other applications.

Why would anyone need another diagram generator when there are many other tools supporting UML, C4, etc? I believe that the most important aspects of the system are its decomposition into components and connections between them, hiearch provides just that, nothing more, so that you can focus on documenting your system rather than fitting it into a specific design framework.

Features

  • hiearch does not use a DSL, but rather parses a set of input yaml files in arbitrary order. The file contents get composed into a single description, which, in turn, gets decomposed into views.

  • hiearch also accepts input files in graphviz format with dot or gv extension. graphviz files are internally converted to hiearch representation. This way hiearch views can be applied to graphviz files generated by other tools.

  • Description files have flat structure without nesting or inclusion and contain lists of the following objects: nodes, edges, and views. Hierarchical relations between nodes are specified using node parameters.

  • Unlike graphviz, hiearch does not have a concept of subgraphs: each node may automatically become a subgraph depending on a view.

  • hiearch is also somewhat stricter than graphviz: for example, all nodes must be defined explicitly and cannot be deduced from edge definitions.

  • View is not the same thing as graphviz layer https://graphviz.org/docs/attrs/layer/: graphviz places all nodes on each layer and simply makes some of them invisible, which results in awkward spacing.

  • hiearch allows nodes to have multiple parent nodes, which is referenced here as ‘multiscoping’. The idea is, of course, to show parents in different views, for example, to outline system from logical or hardware point of view. However, it is possible to visualize all parents in the same diagram, which may be a bit kinky.

  • hiearch supports label templates, which facilitates automatic generation of URLs, tables, icon inclusions, etc.

  • There are a few predefined styles distributed with the utility, e.g., for generation of state machine graphs.

Installation

  • pipx install hiearch

Examples

Command line options

usage: hiearch [-h] [-o OUTPUT] [-f FORMAT] [-t TEMP_DIR] [-r RESOURCE_DIRS] <filename> [<filename> ...]

Generates diagrams

positional arguments:
  <filename>            Input files

optional arguments:
  -h, --help            show this help message and exit
  -o OUTPUT, --output OUTPUT
                        Output directory [./]
  -f FORMAT, --format FORMAT
                        Output format [SVG]
  -t TEMP_DIR, --temp-dir TEMP_DIR
                        Temporary files output directory (defaults to output directory)
  -r RESOURCE_DIRS, --resource-dirs RESOURCE_DIRS
                        Directories to search for graphical resources (can be specified multiple times)

Trivial

-----------------------------------------------------------
nodes:
    - id: ["Test 1", test1]  # [label, unique id]
edges:
    - link: [test1, test1]   # [from node id, to node id]
views:
    - id: view1              # unique id / output filename
      nodes: [test1]         # nodes to include
view1
view1

Node relations

-----------------------------------------------------------
nodes:
    - id: ["Test 1", test1]
      graphviz:  # set graphviz attributes directly
        fillcolor: grey
        style: filled
    - id: ["Test 2", test2]
      graphviz:  # set graphviz attributes directly
        fillcolor: aqua
        style: filled
    - id: ["Test 3", test3]
      scope: test1  # test3 is contained in test1
      style: test2  # test3 inherits all test2 attributes
edges:
    - link: [test3, test3]
views:
    - id: view1
      nodes: [test2, test3]
    - id: view2  # test1 is shown as subgraph
      nodes: [test1, test3]
    - id: view3
      nodes: [test1, test2]
view1
view1
view2
view2
view3
view3

Node selection using tags

-----------------------------------------------------------
nodes:
    - id: ["Test 1", test1]
      # tags: ["default"] if not specified
    - id: ["Test 2", test2]
      tags: ["test2_tag"]
edges:
    - link: [test1, test1]
    - link: [test2, test2]
views:
    - id: view1
      tags: ["test2_tag"]
    - id: view2
      tags: ["default"]
view1
view1
view2
view2
-----------------------------------------------------------
nodes:
    - id: ["Test 1", test1]
# if no views are specified explicitly, a default one is
# added with 'tags: ["default"]'
default
default

Style inheritance without tags

-----------------------------------------------------------
nodes:
    - id: ["Node A", node_a]
      tags: ["custom_tag"]
      graphviz:
        fillcolor: grey
        style: filled
    - id: ["Node B", node_b]
      style: node_a
    - id: ["Node C", node_c]
      # no need to explicitly override inherited tags,
      # which is useful for dealing with pure style nodes.
      style_notag: node_a
views:
    - id: view_default          # includes node C
    - id: view_custom           # includes nodes A and B
      tags: ["custom_tag"]
view_default
view_default
view_custom
view_custom

Neighbour node selection

There are several types on automated node selection:

  • explicit – only explicitly specified nodes are selected;
  • direct – explicitly specified and their direct connections;
  • parent – similar to direct, but instead of directly connected nodes their top-most parents (scopes) are selected;
  • recursive_in, recursive_out, recursive_all – recursively expand connections of explicitly selected nodes.
-----------------------------------------------------------
nodes:
    - id: ["Node A", node_a]
    - id: ["Node B", node_b]
    - id: ["Node C", node_c]
      scope: [node_b]
    - id: ["Node D", node_d]
edges:
    - link: [node_a, node_c]
    - link: [node_c, node_d]
views:
    - id: explicit
      nodes: [node_a]
      # only node_a
      #neighbours: explicit
    - id: direct
      nodes: [node_a]
      # node_a and node_c (directly connected)
      neighbours: direct
    - id: parent
      nodes: [node_a]
      # node_a and node_b (parent of connected node C)
      neighbours: parent
    - id: recursive_out
      nodes: [node_a]
      # node_a, node_c, node_d (all connected)
      # + node_b -- parent of c
      neighbours: recursive_out
    - id: recursive_in
      nodes: [node_d]
      # node_a, node_c, node_d (all connected)
      # + node_b -- parent of c
      neighbours: recursive_in
    - id: recursive_in_c
      nodes: [node_c]
      # node_a, node_c
      neighbours: recursive_in
explicit
explicit
direct
direct
parent
parent
recursive_in
recursive_in
recursive_out
recursive_out
recursive_out
recursive_in_c

View expansion

-----------------------------------------------------------
nodes:
    - id: ["Node A", node_a]
      tags: [group1]
    - id: ["Node B", node_b]
      tags: [group1]
    - id: ["Node C", node_c]
      scope: [node_b]
    - id: ["Node D", node_d]
    - id: ["Node E", node_e]
    - id: ["Node F", node_f]
    - id: ["Node H", node_h]
edges:
    - link: [node_a, node_c]
    - link: [node_c, node_d]
    - link: [node_d, node_e]
    - link: [node_e, node_f]
    - link: [node_f, node_h]
    - link: [node_a, node_b]
views:
    - id: tag_based_recursive_out
      tags: [group1]
      # create additional views based on the current one:
      #  - for each node selected in the current view
      #    add a view, where this node is present as well
      #    as its relations specified in expand array;
      #  - note that only nodes present in the original
      #    view are shown in new views;
      #  - original view is preserved;
      #  - expanded views inherit properties of the
      #    original.
      expand: [recursive_out]
tag_based_recursive_out
original
tag_based_recursive_out_node_a_recursive_out
node A expansion
tag_based_recursive_out_node_b_recursive_out
node B expansion

View styles

-----------------------------------------------------------
nodes:
    - id: ["Test 1", test1]
edges:
    - link: [test1, test1]
views:
    - id: style
      nodes: []  # explicitly empty view is not rendered
      # defaults, overriden by node/edge attributes
      graphviz:
          graph:
              style: filled
              bgcolor: coral
          node:
              fontsize: "24"
              fontname: times
          edge:
              dir: both
    - id: styled
      nodes: [test1]
      style: style  # inherit style from another view
    - id: plain
      nodes: [test1]
styled
styled
plain
plain

Edge labels

-----------------------------------------------------------
nodes:
    - id: ["Test 1", test1]
    - id: ["Test 2", test2]
edges:
    - link: [test1, test1]
      label: 'test1_edge'
    - link: [test2, test2]
      label: ['tail', 'middle', 'head']
views:
    - id: view1
      nodes: [test1]
    - id: view2
      nodes: [test2]
view1
view1
view2
view2

Edge styles

-----------------------------------------------------------
nodes:
    - id: ["Test 1", test1]
    - id: ["Test 2", test2]
    - id: ["Test 3", test3]
    # helper node to define "invisible" edges used purely
    # as style templates
    - id: ["StyleNode", stylenode]
      # invisible unless this tag is requested in a view
      # note that tags are inherited too
      tags: ["mystyle"]
edges:
    # "pure" style link
    - link: [stylenode, stylenode, stylelink]
      graphviz:
        color: red
    - link: [test1, test1]
      label: 'test1'
      style: stylelink
    # optional third link parameter introduces an explicit
    # id, which must be unique
    - link: [test2, test2, edge2]
      # style can be referenced by link attribute
      style: [test1, test1]
      graphviz:
        dir: both
    - link: [test3, test3]
      # style can also be an explicit id
      style: edge2
      graphviz:
        color: blue
views:
    - id: view1
      nodes: [test1]
    - id: view2
      nodes: [test2]
    - id: view3
      nodes: [test3]
view1
view1
view2
view2
view3
view3

Automatic color selection

The keyword auto can be used as a color value for any graphviz color attribute (e.g., color, fillcolor, fontcolor, edgecolor, linecolor). When specified, a deterministic random color is generated based on a hash of the node or edge attributes.

-----------------------------------------------------------
nodes:
    - id: [A, node1]
      graphviz:
        shape: rectangle
        style: filled
        fillcolor: auto
    - id: [B, node2]
      graphviz:
        shape: rectangle
        style: filled
        fillcolor: auto
    - id: [C, node3]
      graphviz:
        shape: ellipse
        style: filled
        fillcolor: auto
        color: auto
        fontcolor: auto
edges:
    - link: [node1, node2]
      graphviz:
        color: auto
        style: solid
    - link: [node2, node3]
      graphviz:
        color: auto
        style: solid
default
default

Formatted labels

nodes:
    - id: ["Test 1", test1]
      # https://www.svgrepo.com/svg/479843/duck-toy-illustration-3
      # https://www.svgrepo.com/svg/479405/casa-pictogram-5
      graphviz:
        node_label_format: '<<table><tr><td><img src="https://raw.githubusercontent.com/asherikov/hiearch/master/icon_{id}.svg"/></td><td>{label}</td></tr></table>>'
        scope_label_format: '<<table><tr><td><img src="https://raw.githubusercontent.com/asherikov/hiearch/master/icon_{id}.svg"/></td><td>Scope: {label}</td></tr></table>>'
    - id: ["Test 2", test2]
      scope: test1
    - id: ["Test 3", test3]
      tags: []
      substitutions:
        suffix: '!'
      graphviz:
        node_label_format: '<<table><tr><td><img src="https://raw.githubusercontent.com/asherikov/hiearch/master/icon_{style}.svg"/></td><td>{label}{suffix}</td></tr></table>>'
    - id: ["Test 4", test4]
      style: test3
views:
    - id: view1
      nodes: [test1]
    - id: view2
      nodes: [test1, test2]
    - id: view3
      nodes: [test4]

Note that SVG with other embedded SVG is not always rendered properly, and embedded pictures may get lost during conversion to other formats. The PNG files below were generated with rsvg-convert view1.svg --format=png --output=view1.png, exporting directly to PNG using graphviz won’t work. Also, the included images must be present in the output directory.

view1
view1
view2
view2
view3
view3

Multiscoping

-----------------------------------------------------------
nodes:
    # root nodes
    - id: ["Test 1", test1]
    - id: ["Test 2", test2]
    # child nodes
    - id: ["Test 3", test3]
      # a child of both root nodes: if both scopes are
      # present in a view they are automatically ranked
      # to form a hierarchy
      scope: [test1, test2]
    # Both root nodes also include non-shared nodes.
    # Since is not possible to visualize overlaping
    # subgraphs with graphviz, one of them is going to be
    # divided into two parts.
    - id: ["Test 4", test4]
      scope: test2
    - id: ["Test 5", test5]
      scope: [test1]
views:
    - id: default
      tags: ["default"]
    - id: autoranking
      nodes: [test1, test2, test3]
autoranking
autoranking
default
default

Predefined styles

  • All predefined styles are automatically added to input files on invocation of hiearch.
  • Generally it is necessary to override tags inherited from style nodes.

State machine

-----------------------------------------------------------
nodes:
    - id: ["", choice1]
      style_notag: hh_state_machine_choice
    - id: [state1, state1]
      style_notag: hh_state_machine_regular
    - id: [state2, state2]
      style_notag: hh_state_machine_regular
    - id: ["", fork1]
      style_notag: hh_state_machine_junction
    - id: ["", join2]
      style_notag: hh_state_machine_junction
    - id: ["", start]
      style_notag: hh_state_machine_initial
    - id: ["", end1]
      style_notag: hh_state_machine_final
    - id: ["", end2]
      style_notag: hh_state_machine_final
edges:
    - link: [start, choice1]
      label: "from start\nto choice"
    - link: [choice1, fork1]
      label: "from choice\nto fork"
    - link: [choice1, join2]
      label: "from choice\nto join"
    - link: [choice1, end2]
      label: "from choice\nto end"
    - link: [fork1, state1]
      label: "from fork\nto state"
    - link: [fork1, state2]
      label: "from fork\nto state"
    - link: [state2, join2]
      label: "from state\nto join"
    - link: [state1, end1]
      label: "from state\nto end"
    - link: [join2, end1]
      label: "from join\nto end"
views:
    # Example
    - id: state_machine_example
      style: hh_state_machine_view
      # override empty selection inherited from style view
      tags: ["default"]
state_machine_example
state machine example

Use Case

-----------------------------------------------------------
nodes:
    - id: ["User", user]
      style_notag: hh_use_case_actor
    - id: ["Admin", admin]
      style_notag: hh_use_case_actor
    - id: ["Login System", system_boundary]
      style_notag: hh_use_case_system_boundary
    - id: ["Login", login]
      style_notag: hh_use_case_case
      scope: system_boundary
    - id: ["Logout", logout]
      style_notag: hh_use_case_case
      scope: system_boundary
    - id: ["Reset Password", reset_password]
      style_notag: hh_use_case_case
      scope: system_boundary
    - id: ["Register", register]
      style_notag: hh_use_case_case
      scope: system_boundary
    - id: ["Validate Credentials", validate_credentials]
      style_notag: hh_use_case_case
      scope: system_boundary
    - id: ["2FA Authentication", two_factor_auth]
      style_notag: hh_use_case_case
      scope: system_boundary
edges:
    - link: [user, login]
      style: hh_use_case_association
    - link: [user, logout]
      style: hh_use_case_association
    - link: [user, reset_password]
      style: hh_use_case_association
    - link: [user, register]
      style: hh_use_case_association
    - link: [admin, reset_password]
      style: hh_use_case_association
    - link: [login, validate_credentials]
      style: hh_use_case_include
    - link: [two_factor_auth, login]
      style: hh_use_case_extend
views:
    - id: use_case_example
      style: hh_use_case_view
      # override empty selection inherited from style view
      tags: ["default"]
use_case_example
use case example

Python diagrams

The diagrams style provides access to icons from the https://github.com/mingrammer/diagrams package (http://diagrams.mingrammer.com), allowing you to create cloud system architecture diagrams. The diagrams package is not a hiearch dependency and must be installed separately to access the icon files. The icons are located in the package’s resources directory (typically ~/.local/lib/python3.X/site-packages/resources or similar), which can be passed to hiearch like this

hiearch -r /path/to/diagrams/resources input.yaml

Example

nodes:
    - id: ["Mobile User", mobile_user]
      style_notag: hh_diagrams_generic_device_mobile
    - id: ["Tablet User", tablet_user]
      style_notag: hh_diagrams_generic_device_tablet

    - id: ["CloudFront\nCDN", cloudfront]
      style_notag: hh_diagrams_aws_network_cloudfront
      scope: aws_frontend
    - id: ["Application\nLoad Balancer", elb]
      style_notag: hh_diagrams_aws_network_elb_application_load_balancer
      scope: aws_frontend

    - id: ["EC2 Instances", ec2]
      style_notag: hh_diagrams_aws_compute_ec2
      scope: aws_compute
    - id: ["Lambda\nFunctions", lambda_func]
      style_notag: hh_diagrams_aws_compute_lambda
      scope: aws_compute
    - id: ["Elastic\nContainer", ecs]
      style_notag: hh_diagrams_aws_compute_elastic_container_service
      scope: aws_compute

    - id: ["Prometheus", prometheus]
      style_notag: hh_diagrams_onprem_monitoring_prometheus
    - id: ["Grafana", grafana]
      style_notag: hh_diagrams_onprem_monitoring_grafana

    - id: ["AWS\nFrontend", aws_frontend]
      style_notag: hh_diagrams_scope
    - id: ["AWS\nCompute", aws_compute]
      style_notag: hh_diagrams_scope

edges:
    - link: [mobile_user, cloudfront]
      style: hh_diagrams_link
    - link: [tablet_user, cloudfront]
      style: hh_diagrams_link
    - link: [cloudfront, elb]
      style: hh_diagrams_link

    - link: [elb, ec2]
      style: hh_diagrams_link
    - link: [elb, lambda_func]
      style: hh_diagrams_link
    - link: [elb, ecs]
      style: hh_diagrams_link

    - link: [prometheus, ec2]
      style: hh_diagrams_link
    - link: [prometheus, lambda_func]
      style: hh_diagrams_link
    - link: [grafana, prometheus]
      style: hh_diagrams_link

views:
    - id: cloud_architecture
      style: hh_diagrams_view
      tags: ["default"]
cloud_architecture

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

hiearch-0.9.1.tar.gz (59.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

hiearch-0.9.1-py3-none-any.whl (70.5 kB view details)

Uploaded Python 3

File details

Details for the file hiearch-0.9.1.tar.gz.

File metadata

  • Download URL: hiearch-0.9.1.tar.gz
  • Upload date:
  • Size: 59.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.3

File hashes

Hashes for hiearch-0.9.1.tar.gz
Algorithm Hash digest
SHA256 f8dbcd51e5b023a185c0893062a2e2fa09d5af1540c8ab6e87aa1dc7dbd94bbe
MD5 03b97a22917ab03f583ce2ae30f47234
BLAKE2b-256 a26d1ad0c1f081e35e1b259b602ef71de0edc500b48a8cecbc1614ee2dfb4cb3

See more details on using hashes here.

File details

Details for the file hiearch-0.9.1-py3-none-any.whl.

File metadata

  • Download URL: hiearch-0.9.1-py3-none-any.whl
  • Upload date:
  • Size: 70.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.3

File hashes

Hashes for hiearch-0.9.1-py3-none-any.whl
Algorithm Hash digest
SHA256 369809e39918078e3c19912c961146270a2457cc67a695aaac724689998f34ca
MD5 fddf8b3c4eb13a87fb3199b4c01e4ccb
BLAKE2b-256 f204ec810761616298547b3d2a37b8fe2a932ca56a336179927428014590e0a5

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page