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:
- Parses the injected
__GRAPH_DATA__JSON intographData - Maps nodes and edges to internal
nodesandedgesarrays - 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
- Generation —
generate_html()writes the HTML, embeds JSON data, and copiesd3.v7.min.jsvia_write_d3_asset() - Initial render — Nodes appear in default kind-colors; Communities button is inactive
- Toggle activation — Clicking "Communities" flips
communityColoringOn, transitions colors, and reveals the filterable legend - Community filtering — Check/uncheck legend items to update
hiddenCommunitiesand hide/show corresponding nodes - 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
hiddenCommunitiesandapplyCommunityFilter() - Accessible design — Proper
aria-pressedstates 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →