Metadata-Version: 2.4
Name: orgheadingnetwork
Version: 2026.3.1.1
Summary: 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.
Author-email: Karl Voit <tools@Karl-Voit.at>
License-Expression: GPL-3.0-or-later
Project-URL: Homepage, https://codeberg.org/publicvoit/orgheadingnetwork
Project-URL: Download, https://codeberg.org/publicvoit/orgheadingnetwork/archive/main.zip
Keywords: orgmode,visualization,zettelkasten,PIM
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Console
Classifier: Intended Audience :: End Users/Desktop
Classifier: Operating System :: OS Independent
Requires-Python: >=3.13
Description-Content-Type: text/plain
License-File: LICENSE.txt
Requires-Dist: dataclasses>=0.8
Requires-Dist: typing>=3.10.0.0
Dynamic: license-file

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

