Skip to main content

This tool parses one or more Orgdown files (= syntax of Emacs Org-mode), extracts headings with their metadata and inter-heading links, classifies the link types by strength, and generates an interactive HTML visualization of the resulting network.

Project description

* orgheadingnetwork

This tool parses one or more [[https://gitlab.com/publicvoit/orgdown/][Orgdown]] files (= syntax of [[https://www.gnu.org/software/emacs/][Emacs]]
[[https://orgmode.org/][Org-mode]]), extracts headings with their metadata and inter-heading
links, classifies the link types by strength, and generates an
interactive HTML visualization of the resulting network.

You can also limit the visualization for a specific sub-hierarchy of
an Orgdown file.

Link types from weakest to strongest:

1. *Parent/Child* — implicit hierarchy from heading nesting
2. *Broken outgoing* — ID reference to a heading not found in the input files
3. *Uni-directional* — one heading links to another by ID
4. *Bi-directional* — two headings link to each other by ID

Only the strongest link type between any two headings is kept.

** Screenshots

Overview of the network visualization of my =notes.org= containing 14491 headings:

[[file:screenshots/2026-02-15T14.33.16 orgheadingnetwork of notes.org - Overview -- screenshots.png][file:screenshots/2026-02-15T14.33.16 orgheadingnetwork of notes.org - Overview -- screenshots preview.png]]

Selecting a node highlights its connections and shows details:

[[file:screenshots/2026-02-15T14.34.52 orgheadingnetwork of notes.org with Emacs node selected -- screenshots.png][file:screenshots/2026-02-15T14.34.52 orgheadingnetwork of notes.org with Emacs node selected -- screenshots preview.png]]

Using the link on the right hand side, you can navigate to linked headings and re-center the visualization there.

Searching for nodes shows a ranked dropdown list for quick navigation:

[[file:screenshots/2026-02-15T15.07.51 orgheadingnetwork with notes.org and search for Emacs -- screenshots.png][file:screenshots/2026-02-15T15.07.51 orgheadingnetwork with notes.org and search for Emacs -- screenshots preview.png]]

** Search

The search box in the top-left panel finds nodes by title, tags, ID,
or source file name. As you type, non-matching nodes are dimmed on the
canvas and a dropdown list shows up to 50 results ranked by relevance:

- Exact title match (highest)
- Title starts with the query
- Exact tag match
- Title contains the query
- Tag contains the query
- ID or file name contains the query (lowest)

Click any result to center the view on that node. You can also
navigate the list with *Arrow Up/Down*, press *Enter* to jump to the
highlighted entry, or *Escape* to clear the search.

** Recognized ID Sources

The tool extracts IDs from several places within each heading. The
following example demonstrates all of them:

#+begin_example
,* TODO Example heading with [[id:aaa-title-link][a link]] in the title
:PROPERTIES:
:ID: 1234-abcd-5678-efgh
:TRIGGER: ids("id:bbb-trigger-id1" "ccc-trigger-id2")
:BLOCKER: ids(ddd-blocker-id)
:END:

This body text contains a link [[id:eee-body-link][to another heading]]
and also a bare link id:fff-body-bare without description.

Even a plain id:ggg-plain-ref in running text is recognized, as well as
an ID hidden in a link description: [[https://example.com][see id:hhh-in-desc]].
#+end_example

IDs extracted from this heading:

| Source | Extracted ID | How |
|-----------------+-----------------------+------------------------------------------|
| Title | =aaa-title-link= | =[[id:...][desc]]= link in heading title |
| =:ID:= property | =1234-abcd-5678-efgh= | Becomes this heading's own ID |
| =:TRIGGER:= | =bbb-trigger-id1= | org-edna =ids("id:...")= syntax |
| =:TRIGGER:= | =ccc-trigger-id2= | org-edna =ids("...")= syntax (no prefix) |
| =:BLOCKER:= | =ddd-blocker-id= | org-edna =ids(...)= unquoted syntax |
| Body | =eee-body-link= | =[[id:...][desc]]= link in body |
| Body | =fff-body-bare= | =[[id:...]]= link without description |
| Body | =ggg-plain-ref= | Bare =id:...= reference in text |
| Body | =hhh-in-desc= | =id:...= inside a link description |

Headings without an =:ID:= property receive an auto-generated UUID
(prefixed =temp-=) so they can still participate in the hierarchy.

** Usage

Requires Python 3.13+ and [[https://docs.astral.sh/uv/][uv]].

#+begin_src bash
# Install dependencies
uv sync

# Basic: parse org files, produce org-network.html
uv run orgheadingnetwork.py file1.org file2.org

# Custom output path
uv run orgheadingnetwork.py -o my-network.html file.org

# Visualize only a sub-hierarchy (see below)
uv run orgheadingnetwork.py --subtree 2026-02-my-heading-id file.org

# Open result in default browser
uv run orgheadingnetwork.py --open file.org

# Dump the parsed data structure to stdout (for debugging)
uv run orgheadingnetwork.py --dump file.org

# Verbose logging
uv run orgheadingnetwork.py -v file.org

# Quiet (errors only)
uv run orgheadingnetwork.py -q file.org
#+end_src

The output HTML file is self-contained (loads D3.js from CDN) and can
be opened directly in a browser. It provides an interactive
force-directed graph with zoom, pan, search, link-type filtering, and
click-to-inspect.

Note: Broken outgoing links are hidden by default in the
visualization. Use the "Broken" checkbox in the link type filter to
show them.

You can use an Elisp function to invoke this tool as well. I wrote
=my-orgheadingnetwork-current-file()= which you can get from [[https://github.com/novoid/dot-emacs/blob/master/config.org][my Emacs
configuration]]. You'll need to adapt it to your paths and so forth.
With that function, it's really convenient to get a visualization - in
my case for the current Org-mode file.

There's also an Elisp function for visualizing the current sub-hierarchy:
=my-orgheadingnetwork-current-heading()=

** Subtree Mode

Use =--subtree ID= to visualize only the sub-hierarchy rooted at a
specific heading. This requires exactly one input file.

#+begin_src bash
uv run orgheadingnetwork.py --subtree 2026-02-my-heading-id file.org
#+end_src

The =ID= parameter must match the =:ID:= property value of a heading
in the file. An optional =id:= prefix is accepted and stripped, so
=--subtree foo-bar= and =--subtree id:foo-bar= are equivalent:

#+begin_example
,* My project heading
:PROPERTIES:
:ID: 2026-02-my-heading-id
:END:
#+end_example

The visualization will include:

- The heading matching the given ID (as the root of the sub-graph)
- All its descendants (children, grandchildren, etc.)

Links from subtree headings to headings outside the subtree (but still
in the same file) are treated as broken outgoing links. Uni-directional
incoming links from outside the subtree are ignored. Bi-directional
links where one end is inside the subtree are kept as broken outgoing
links from the subtree side.

The HTML title shows =Subtree "[heading title]" of "[filename]"= so
you can tell which mode produced the output.

Combining =--subtree= with multiple input files is not allowed and
produces an error.

** Author

Karl Voit, https://Karl-Voit.at/

** How to Thank Me

I'm glad you like my tools. If you want to support me:

- Send old-fashioned *postcard* per snailmail - I love personal feedback!
- see [[http://tinyurl.com/j6w8hyo][my address]]
- Send feature wishes or improvements as an issue on GitHub
- Create issues on GitHub for bugs
- Contribute merge requests for bug fixes
- Check out my other cool [[https://github.com/novoid][projects on GitHub]]

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

orgheadingnetwork-2026.3.1.1.tar.gz (17.0 kB view details)

Uploaded Source

Built Distribution

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

orgheadingnetwork-2026.3.1.1-py3-none-any.whl (17.1 kB view details)

Uploaded Python 3

File details

Details for the file orgheadingnetwork-2026.3.1.1.tar.gz.

File metadata

  • Download URL: orgheadingnetwork-2026.3.1.1.tar.gz
  • Upload date:
  • Size: 17.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.9 {"installer":{"name":"uv","version":"0.9.9"},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"13","id":"trixie","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for orgheadingnetwork-2026.3.1.1.tar.gz
Algorithm Hash digest
SHA256 a66248295ae7f8670e09892c1aedf6f3a49c72a08d32e629a0633ab74bd29bad
MD5 2b0e766f1d379d15fccb81c111291a2f
BLAKE2b-256 95bbe390ff997570b74d89b330d278129e708f7833d5872d33566295e57bac38

See more details on using hashes here.

File details

Details for the file orgheadingnetwork-2026.3.1.1-py3-none-any.whl.

File metadata

  • Download URL: orgheadingnetwork-2026.3.1.1-py3-none-any.whl
  • Upload date:
  • Size: 17.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.9 {"installer":{"name":"uv","version":"0.9.9"},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"13","id":"trixie","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for orgheadingnetwork-2026.3.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 27f8a1a5dfd270b5c29cb911fdc3ca9a05ecd929af26edd862edc78d4ad85fc5
MD5 c5214d75bd3b9505656fa6943b64e9e5
BLAKE2b-256 a527ba7b25fae68aa456c137d63a5b191be29d76ce010ab95da3acad25998c58

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