hiearch 0.7.0
Generates multi-view diagrams from textual descriptions
Requires Python: >=3.8

- [Introduction](#introduction)
- [Features](#features)
- [Installation](#installation)
- [Examples](#examples)
  - [Command line options](#command-line-options)
  - [Trivial](#trivial)
  - [Node relations](#node-relations)
  - [Node selection using tags](#node-selection-using-tags)
  - [Style inheritance without tags](#style-inheritance-without-tags)
  - [Neighbour node selection](#neighbour-node-selection)
  - [View expansion](#view-expansion)
  - [View styles](#view-styles)
  - [Edge labels](#edge-labels)
  - [Edge styles](#edge-styles)
  - [Formatted labels](#formatted-labels)
  - [Multiscoping](#multiscoping)
- [Predefined styles](#predefined-styles)
  - [State machine](#state-machine)
  - [Use Case](#use-case)

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] <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)

Trivial
-------

<table>
<tr>
<td>
<pre>
-----------------------------------------------------------
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
</pre>
</td>
<td align="center">
<img src="https://raw.githubusercontent.com/asherikov/hiearch/master/test/07_trivial/view1.svg" alt="view1" />
<br /> view1
</td>
</tr>
</table>

Node relations
--------------

<table>
<tr>
<td rowspan="3">
<pre>
-----------------------------------------------------------
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]
</pre>
</td>
<td align="center">
<img src="https://raw.githubusercontent.com/asherikov/hiearch/master/test/08_node_realations/view1.svg" alt="view1" />
<br /> view1
</td>
</tr>
<tr>
<td align="center">
<img src="https://raw.githubusercontent.com/asherikov/hiearch/master/test/08_node_realations/view2.svg" alt="view2" />
<br /> view2
</td>
</tr>
<tr>
<td align="center">
<img src="https://raw.githubusercontent.com/asherikov/hiearch/master/test/08_node_realations/view3.svg" alt="view3" />
<br /> view3
</td>
</tr>
</table>

Node selection using tags
-------------------------

<table>
<tr>
<td rowspan="2">
<pre>
-----------------------------------------------------------
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"]
</pre>
</td>
<td align="center">
<img src="https://raw.githubusercontent.com/asherikov/hiearch/master/test/09_tags/view1.svg" alt="view1" />
<br /> view1
</td>
</tr>
<tr>
<td align="center">
<img src="https://raw.githubusercontent.com/asherikov/hiearch/master/test/09_tags/view2.svg" alt="view2" />
<br /> view2
</td>
</tr>
</table>
<table>
<tr>
<td>
<pre>
-----------------------------------------------------------
nodes:
    - id: ["Test 1", test1]
# if no views are specified explicitly, a default one is
# added with 'tags: ["default"]'
</pre>
</td>
<td align="center">
<img src="https://raw.githubusercontent.com/asherikov/hiearch/master/test/10_minimal/default.svg" alt="default" />
<br /> default
</td>
</tr>
</table>

Style inheritance without tags
------------------------------

<table>
<tr>
<td rowspan="2">
<pre>
-----------------------------------------------------------
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"]
</pre>
</td>
<td align="center">
<img src="https://raw.githubusercontent.com/asherikov/hiearch/master/test/22_style_notag_tag_inheritance/view_default.svg" alt="view_default" />
<br /> view_default
</td>
</tr>
<tr>
<td align="center">
<img src="https://raw.githubusercontent.com/asherikov/hiearch/master/test/22_style_notag_tag_inheritance/view_custom.svg" alt="view_custom" />
<br /> view_custom
</td>
</tr>
</table>

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.

<table>
<tr>
<td rowspan="6">
<pre>
-----------------------------------------------------------
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
</pre>
</td>
<td align="center">
<img src="https://raw.githubusercontent.com/asherikov/hiearch/master/test/11_neighbors/explicit.svg" alt="explicit" />
<br /> explicit
</td>
</tr>
<tr>
<td align="center">
<img src="https://raw.githubusercontent.com/asherikov/hiearch/master/test/11_neighbors/direct.svg" alt="direct" />
<br /> direct
</td>
</tr>
<tr>
<td align="center">
<img src="https://raw.githubusercontent.com/asherikov/hiearch/master/test/11_neighbors/parent.svg" alt="parent" />
<br /> parent
</td>
</tr>
<tr>
<td align="center">
<img src="https://raw.githubusercontent.com/asherikov/hiearch/master/test/11_neighbors/recursive_in.svg" alt="recursive_in" />
<br /> recursive_in
</td>
</tr>
<tr>
<td align="center">
<img src="https://raw.githubusercontent.com/asherikov/hiearch/master/test/11_neighbors/recursive_out.svg" alt="recursive_out" />
<br /> recursive_out
</td>
</tr>
<tr>
<td align="center">
<img src="https://raw.githubusercontent.com/asherikov/hiearch/master/test/11_neighbors/recursive_in_c.svg" alt="recursive_out" />
<br /> recursive_in_c
</td>
</tr>
</table>

View expansion
--------------

<table>
<tr>
<td rowspan="3">
<pre>
-----------------------------------------------------------
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]
</pre>
</td>
<td align="center">
<img src="https://raw.githubusercontent.com/asherikov/hiearch/master/test/23_expand/tag_based_recursive_out.svg" alt="tag_based_recursive_out" />
<br /> original
</td>
</tr>
<tr>
<td align="center">
<img src="https://raw.githubusercontent.com/asherikov/hiearch/master/test/23_expand/tag_based_recursive_out_node_a_recursive_out.svg" alt="tag_based_recursive_out_node_a_recursive_out" />
<br /> node A expansion
</td>
</tr>
<tr>
<td align="center">
<img src="https://raw.githubusercontent.com/asherikov/hiearch/master/test/23_expand/tag_based_recursive_out_node_b_recursive_out.svg" alt="tag_based_recursive_out_node_b_recursive_out" />
<br /> node B expansion
</td>
</tr>
</table>


View styles
-----------

<table>
<tr>
<td rowspan="2">
<pre>
-----------------------------------------------------------
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]
</pre>
</td>
<td align="center">
<img src="https://raw.githubusercontent.com/asherikov/hiearch/master/test/12_view_style/styled.svg" alt="styled" />
<br /> styled
</td>
</tr>
<tr>
<td align="center">
<img src="https://raw.githubusercontent.com/asherikov/hiearch/master/test/12_view_style/plain.svg" alt="plain" />
<br /> plain
</td>
</tr>
</table>

Edge labels
-----------

<table>
<tr>
<td rowspan="2">
<pre>
-----------------------------------------------------------
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]
</pre>
</td>
<td align="center">
<img src="https://raw.githubusercontent.com/asherikov/hiearch/master/test/13_edge_labels/view1.svg" alt="view1" />
<br /> view1
</td>
</tr>
<tr>
<td align="center">
<img src="https://raw.githubusercontent.com/asherikov/hiearch/master/test/13_edge_labels/view2.svg" alt="view2" />
<br /> view2
</td>
</tr>
</table>

Edge styles
-----------

<table>
<tr>
<td rowspan="3">
<pre>
-----------------------------------------------------------
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]
</pre>
</td>
<td align="center">
<img src="https://raw.githubusercontent.com/asherikov/hiearch/master/test/14_edge_style/view1.svg" alt="view1" />
<br /> view1
</td>
</tr>
<tr>
<td align="center">
<img src="https://raw.githubusercontent.com/asherikov/hiearch/master/test/14_edge_style/view2.svg" alt="view2" />
<br /> view2
</td>
</tr>
<tr>
<td align="center">
<img src="https://raw.githubusercontent.com/asherikov/hiearch/master/test/14_edge_style/view3.svg" alt="view3" />
<br /> view3
</td>
</tr>
</table>

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.

<table>
<tr>
<td align="center">
<img src="https://raw.githubusercontent.com/asherikov/hiearch/master/test/15_formatted_labels/view1.png" alt="view1" />
<br /> view1
</td>
<td align="center">
<img src="https://raw.githubusercontent.com/asherikov/hiearch/master/test/15_formatted_labels/view2.png" alt="view2" />
<br /> view2
</td>
<td align="center">
<img src="https://raw.githubusercontent.com/asherikov/hiearch/master/test/15_formatted_labels/view3.png" alt="view3" />
<br /> view3
</td>
</tr>
</table>

Multiscoping
------------

<table>
<tr>
<td rowspan="2">
<pre>
-----------------------------------------------------------
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]
</pre>
</td>
<td align="center">
<img src="https://raw.githubusercontent.com/asherikov/hiearch/master/test/06_multiscope/autoranking.svg" alt="autoranking" />
<br /> autoranking
</td>
</tr>
<tr>
<td align="center">
<img src="https://raw.githubusercontent.com/asherikov/hiearch/master/test/06_multiscope/default.svg" alt="default" />
<br /> default
</td>
</tr>
</table>

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
-------------

<table>
<tr>
<td>
<pre>
-----------------------------------------------------------
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"]
</pre>
</td>
<td align="center">
<img src="https://raw.githubusercontent.com/asherikov/hiearch/master/test/16_state_machine/state_machine_example.svg" alt="state_machine_example" />
<br /> state machine example
</td>
</tr>
</table>

Use Case
--------

<table>
<tr>
<td>
<pre>
-----------------------------------------------------------
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"]
</pre>
</td>
<td align="center">
<img src="https://raw.githubusercontent.com/asherikov/hiearch/master/test/17_use_case/use_case_example.svg" alt="use_case_example" />
<br /> use case example
</td>
</tr>