How the Interactive D3.js Visualization with Community Toggles Functions in code-review-graph

The interactive D3.js visualization in code-review-graph renders the knowledge graph as a self-contained HTML page with a "Communities" toggle that colors nodes by their detected community, displays a filterable legend, and allows users to hide or show individual communities.

The code-review_graph.visualization module generates this interactive experience using D3.js v7 with zero external dependencies beyond the vendored library. The community toggle system combines data-driven aggregation, dynamic color scaling, and DOM manipulation to help developers explore large code review graphs through the lens of detected communities.

Data Preparation and Community Aggregation

Before rendering, generate_html() calls export_graph_data() to obtain the full node and edge list. When the user selects auto mode and the graph exceeds the full-render budget, _resolve_auto_mode() automatically switches to community aggregation mode.

In this mode, _aggregate_community() (lines 37-45 of visualization.py) compresses the graph by creating super-nodes — one node per community with size proportional to member count:

def _aggregate_community(data: dict) -> dict:
    ...
    # each community becomes a single node sized by member count

    super_nodes.append({
        "qualified_name": f"__community__{cid}",
        "name": info.get("name", f"Community {cid}"),
        "kind": "Community",
        ...
    })
    ...

This aggregation preserves a community_details map for drill-down rendering, allowing the visualization to scale to thousands of nodes while maintaining community structure.

HTML Template Structure

The module embeds two template strings directly in visualization.py:

  • _HTML_TEMPLATE — full-graph layout with individual nodes
  • _AGGREGATED_HTML_TEMPLATE — used when data are aggregated (community or file mode)

Both templates include the Communities toggle button at line 815:

<button id="btn-community" title="Toggle community coloring"
        aria-label="Toggle community coloring" aria-pressed="false">
    Communities
</button>

The __D3_SCRIPTS__ placeholder is replaced with the vendored D3 library during HTML generation.

D3 Initialization and Color Scaling

When the generated page loads, the embedded JavaScript:

  1. Parses the injected __GRAPH_DATA__ JSON into graphData
  2. Maps nodes and edges to internal nodes and edges arrays
  3. Initializes communityColorScale = d3.scaleOrdinal(d3.schemeTableau10) at line 71

This Tableau 10 color scale provides visually distinct, accessible colors for up to 10 communities, with automatic recycling for larger community counts.

The Community Toggle Mechanism

The core interaction lives in the event handler at lines 667-676:

document.getElementById("btn-community").addEventListener("click", function() {
  communityColoringOn = !communityColoringOn;
  this.classList.toggle("active");
  this.setAttribute("aria-pressed", communityColoringOn);
  // recolour nodes and glow rings
  nodeGroup.selectAll("g.node-g").select(".node-shape")
    .transition().duration(300)
    .attr("fill", d => nodeColor(d));
  nodeGroup.selectAll("g.node-g").select(".glow-ring")
    .transition().duration(300)
    .attr("stroke", d => nodeColor(d));
  // show/hide the community legend
  var cl = document.getElementById("community-legend");
  if (communityColoringOn) cl.classList.add("visible");
  else cl.classList.remove("visible");
});

Both the node shape and its glow ring transition simultaneously over 300ms, creating a cohesive visual effect. The aria-pressed attribute ensures screen readers announce the toggle state correctly.

The nodeColor(d) function at lines 124-127 determines the actual color:

function nodeColor(d) {
  if (communityColoringOn && d.community_id != null) {
    return communityColorScale(d.community_id);
  }
  return kindColorScale(d.kind || "Unknown");
}

When community coloring is active, nodes use their community_id; otherwise they fall back to colors based on node kind (file, function, class, etc.).

Community Legend and Filtering

The buildCommunityLegend() function (lines 82-98) generates a dynamic list of checkboxes — one per detected community — using the same communityColorScale for consistent coloring:

function buildCommunityLegend() {
  var legend = document.getElementById("community-legend");
  var communities = [...new Set(nodes.map(n => n.community_id).filter(id => id != null))];
  communities.sort((a, b) => a - b);
  
  communities.forEach(cid => {
    var item = document.createElement("div");
    item.className = "legend-item";
    var checkbox = document.createElement("input");
    checkbox.type = "checkbox";
    checkbox.checked = true;
    checkbox.dataset.cid = cid;
    checkbox.addEventListener("change", applyCommunityFilter);
    ...
  });
}

Each checkbox triggers applyCommunityFilter() (lines 109-141), which maintains a hiddenCommunities Set and updates node visibility:

function applyCommunityFilter() {
  if (!communityColoringOn || !hiddenCommunities.size) { ... }
  nodeGroup.selectAll("g.node-g")
    .attr("display", d => {
      if (d._hidden) return "none";
      var cid = d.community_id ?? nodeToCommunity.get(d.qualified_name);
      return cid != null && hiddenCommunities.has(cid) ? "none" : null;
    });
  ...
}

This hides both the node group and its label when a community is unchecked, supporting focused exploration of specific subgraphs.

Complete Interaction Flow

  1. Generation — generate_html() writes the HTML, embeds JSON data, and copies d3.v7.min.js via _write_d3_asset()
  2. Initial render — Nodes appear in default kind-colors; Communities button is inactive
  3. Toggle activation — Clicking "Communities" flips communityColoringOn, transitions colors, and reveals the filterable legend
  4. Community filtering — Check/uncheck legend items to update hiddenCommunities and hide/show corresponding nodes
  5. Persistent interactions — Hover tooltips, detail panels, and flow highlighting work identically in both color modes

Code Examples

Generating an HTML file with automatic community aggregation

from code_review_graph.visualization import generate_html
from code_review_graph.graph import GraphStore

store = GraphStore('/path/to/db')
output = generate_html(store, 'graph.html', mode='auto')
print(f'HTML written to {output}')  # switches to community mode if graph is large

Programmatically controlling the community toggle

// Activate community coloring
document.getElementById('btn-community').click();

// Hide community 3 programmatically
const checkbox = document.querySelector('input[data-cid="3"]');
checkbox.checked = false;
checkbox.dispatchEvent(new Event('change'));

Selecting community-colored nodes with D3

// Highlight all nodes in community 2
d3.selectAll('g.node-g')
  .filter(d => d.community_id === 2)
  .style('stroke-width', '3px');

Key Source Files

File Purpose
code_review_graph/visualization.py HTML generation, community aggregation, D3 template embedding, and UI logic
code_review_graph/assets/d3.v7.min.js Vendored D3 v7 library for standalone rendering
code_review_graph/graph.py Core graph store supplying nodes, edges, and community IDs
code_review_graph/communities.py Community detection and metadata for coloring/legend

Summary

  • Self-contained output — The visualization module produces a single HTML file with embedded data and vendored D3, requiring no server or build step
  • Automatic aggregation — Large graphs trigger community super-node creation via _aggregate_community() to maintain interactivity
  • Dual coloring modes — Toggle between kind-based (file/function/class) and community-based coloring with smooth 300ms transitions
  • Interactive filtering — Checkboxes in the community legend add/remove communities from view via hiddenCommunities and applyCommunityFilter()
  • Accessible design — Proper aria-pressed states and keyboard-navigable controls support inclusive usage

Frequently Asked Questions

How does the community toggle handle large graphs with thousands of nodes?

When mode='auto' is selected and the node count exceeds the render budget, _resolve_auto_mode() automatically switches to community aggregation. The _aggregate_community() function at lines 37-45 compresses each community into a single super-node sized by member count, reducing thousands of nodes to tens of communities while preserving cross-community connection patterns.

Can I customize the community colors used in the visualization?

The current implementation uses d3.scaleOrdinal(d3.schemeTableau10) hardcoded at line 71. To customize colors, you would modify the visualization.py template or override the communityColorScale definition in the generated HTML. The Tableau 10 scheme provides good perceptual differentiation for up to 10 communities before cycling.

What happens to node interactions when community coloring is active?

All interactions remain fully functional. Hover tooltips, click-to-focus, detail panel updates, and flow highlighting work identically in both modes. The nodeColor() function simply changes its return value based on communityColoringOn, while event handlers and force simulation continue operating on the underlying data structures unchanged.

How can I programmatically filter to show only specific communities?

After the page loads, access the legend checkboxes via their data-cid attributes. Unchecking a checkbox triggers the same applyCommunityFilter() function used by user interactions: document.querySelector('input[data-cid="5"]').checked = false followed by a change event dispatch will hide community 5. To show only specific communities, first uncheck all via document.querySelectorAll('#community-legend input').forEach(cb => { cb.checked = false; cb.dispatchEvent(new Event('change')); }), then check your target communities.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →