Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
Changelog
=========

0.9.0
-----

- Add support for integrating external Sphinx projects with :confval:`visualized_projects`

0.8.2
-----

Expand Down
18 changes: 18 additions & 0 deletions docs/source/advanced-configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,24 @@ With auto-clustering enabled:
- Root-level pages (like ``index.html``) remain unclustered
- You can combine auto-clustering with manual clusters - manual patterns take precedence

.. _external_projects:

Integrating External Projects
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Integrate documentation from other Sphinx projects that also use the **sphinx-visualized** extension:

.. code-block:: python

intersphinx_mapping = {
"sphinx": ("https://www.sphinx-doc.org/en/master/", None),
"python": ("https://docs.python.org/3/", None),
}

visualized_projects = ['sphinx']

.. note:: Each project name must match an entry in your :confval:`intersphinx_mapping`.

GraphSON Export
^^^^^^^^^^^^^^^

Expand Down
5 changes: 4 additions & 1 deletion docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,12 @@
pygments_style = "sphinx"

intersphinx_mapping = {
"sphinx": ("https://www.sphinx-doc.org/en/master/", None),
# "sphinx": ("https://www.sphinx-doc.org/en/master/", None),
"sphinx": ("../../../sphinx/build/sphinx/html", None),
}

visualized_projects = ['sphinx']


# -- Options for HTML output -------------------------------------------------

Expand Down
9 changes: 9 additions & 0 deletions docs/source/configuration-values.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,12 @@ For advanced usage, see :doc:`advanced-configuration`.
See :ref:`auto_clustering` for more details.

.. versionadded:: 0.6.0

.. confval:: visualized_projects

- **Type**: list of strings
- **Default**: ``[]`` (empty list)
- **Description**: List of external Sphinx projects to integrate into the link graph visualization.
See :ref:`external_projects` for detailed usage examples.

.. versionadded:: 0.9.0
3 changes: 2 additions & 1 deletion docs/source/getting-started.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ Directly install via pip by using:

pip install sphinx-visualized

Add ``sphinx_visualized`` to the `extensions <https://www.sphinx-doc.org/en/master/usage/configuration.html#confval-extensions>`_ array in your Sphinx **conf.py**.
Add ``sphinx_visualized`` to the :confval:`extensions <sphinx:extensions>` array in your Sphinx **conf.py**.

For example:

.. code-block:: python
Expand Down
68 changes: 65 additions & 3 deletions sphinx_visualized/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,19 @@
from docutils import nodes as docutils_nodes
from multiprocessing import Manager, Queue
from fnmatch import fnmatch
from sphinx.util import logging

__version__ = "0.8.2"
from .external import fetch_external_project_data, merge_external_project_data

__version__ = "0.9.0"

logger = logging.getLogger(__name__)


def setup(app):
app.add_config_value("visualized_clusters", [], "html")
app.add_config_value("visualized_auto_cluster", False, "html")
app.add_config_value("visualized_projects", [], "html")
app.connect("builder-inited", create_objects)
app.connect("doctree-resolved", get_links)
app.connect("doctree-resolved", track_includes)
Expand Down Expand Up @@ -412,6 +418,8 @@ def create_graphson(nodes, links, page_list, clusters_config):
vertex_label = "intersphinx"
elif node.get("is_external"):
vertex_label = "external"
elif node.get("is_external_project"):
vertex_label = "external_project"
else:
vertex_label = "page"

Expand All @@ -436,6 +444,15 @@ def create_graphson(nodes, links, page_list, clusters_config):
if node.get("is_intersphinx"):
vertex["properties"]["is_intersphinx"] = True

# Mark external project nodes
if node.get("is_external_project"):
vertex["properties"]["is_external_project"] = True
vertex["properties"]["external_project_name"] = node.get("external_project_name")

# Mark whether this external node connects to home project
if node.get("has_home_connection"):
vertex["properties"]["has_home_connection"] = True

vertices.append(vertex)

# Create edges (links)
Expand All @@ -457,21 +474,52 @@ def create_graphson(nodes, links, page_list, clusters_config):

# Collect all unique cluster names from nodes
cluster_names = set()
external_project_clusters = {} # Track external project clusters and their connection status

for node in nodes:
if node.get("cluster") is not None:
cluster_names.add(node["cluster"])

# Track external project clusters
if node.get("is_external_project"):
cluster = node["cluster"]
if cluster not in external_project_clusters:
external_project_clusters[cluster] = {
"has_any_home_connection": False,
"project_name": node.get("external_project_name")
}

# Update if any node in this cluster connects to home
if node.get("has_home_connection"):
external_project_clusters[cluster]["has_any_home_connection"] = True

# Build complete cluster list: manual configs + auto-generated clusters
all_clusters = list(clusters_config) if clusters_config else []
manual_cluster_names = {c.get("name") for c in clusters_config} if clusters_config else set()

# Add auto-generated clusters that aren't already in manual config
for cluster_name in cluster_names:
if cluster_name not in manual_cluster_names:
all_clusters.append({
cluster_config = {
"name": cluster_name,
"patterns": [] # Auto-generated clusters don't have patterns
})
}

# For external project clusters, set default visibility based on home connections
if cluster_name in external_project_clusters:
cluster_info = external_project_clusters[cluster_name]
cluster_config["is_external_project"] = True
cluster_config["external_project_name"] = cluster_info["project_name"]

# For external projects, only show nodes with home connections by default
# Unlinked nodes can be revealed with a checkbox
cluster_config["show_only_connected_by_default"] = True

# Default to completely hidden if no connections to home project at all
if not cluster_info["has_any_home_connection"]:
cluster_config["default_hidden"] = True

all_clusters.append(cluster_config)

# Include cluster configuration metadata
graphson = {
Expand Down Expand Up @@ -638,6 +686,20 @@ def create_json(app, exception):
"types": link_types, # New field: all link types for this edge
})

# Fetch and merge external project data if configured
# This must happen BEFORE writing nodes.js and links.js
visualized_projects = getattr(app.config, 'visualized_projects', [])
if visualized_projects:
logger.info(f"Fetching data for {len(visualized_projects)} external project(s): {', '.join(visualized_projects)}")

for project_name in visualized_projects:
external_data = fetch_external_project_data(app, project_name)
if external_data:
logger.info(f"Successfully fetched data for '{project_name}', merging into graph...")
nodes, links, _ = merge_external_project_data(app, nodes, links, external_data, page_list)
else:
logger.info(f"Skipping '{project_name}' - data could not be fetched")

filename = Path(app.outdir) / "_static" / "sphinx-visualized" / "js" / "links.js"
with open(filename, "w") as json_file:
json_file.write(f'var links_data = {json.dumps(links, indent=4)};')
Expand Down
Loading