| title | Codebase Visualization |
|---|---|
| sidebarTitle | Visualization |
| icon | share-nodes |
| iconType | solid |
Codegen provides the ability to create interactive graph visualizations via the codebase.visualize(...) method.
These visualizations have a number of applications, including:
- Understanding codebase structure
- Monitoring critical code paths
- Analyzing dependencies
- Understanding inheritance hierarchies
- Exploring symbol relationships
- Visualizing code impact and blast radius
This guide provides a basic overview of graph creation and customization. Like the one below which displays the call_graph for the modal/client.py module.
<iframe width="100%" height="600px" scrolling="no" src={`https://codegen.sh/embedded/graph?id=299beefe-0207-43b6-bff3-6ca9036f62eb&zoom=0.5`} className="rounded-xl " style={{ backgroundColor: "#15141b", }} ></iframe> Codegen visualizations are powered by [NetworkX](https://networkx.org/) and rendered using [d3](https://d3js.org/what-is-d3).The Codebase.visualize method operates on a NetworkX DiGraph.
import networkx as nx
# Basic visualization
G = nx.grid_2d_graph(5, 5)
# Or start with an empty graph
# G = nx.DiGraph()
codebase.visualize(G)It is up to the developer to add nodes and edges to the graph.
When adding nodes to your graph, you can either add the symbol directly or just its name:
import networkx as nx
G = nx.DiGraph()
function = codebase.get_function("my_function")
# Add the function object directly - enables source code preview
graph.add_node(function) # Will show function's source code on click
# Add just the name - no extra features
graph.add_node(function.name) # Will only show the nameCodegen's visualization system has been enhanced with several new features to provide more detailed insights into your codebase:
The visualization UI now includes a selection row that shows corresponding methods when selecting Symbols, Files, Functions, or Classes. This allows you to:
- See all methods associated with a selected class
- View related symbols when selecting a function
- Explore file dependencies when selecting a file
- Navigate between related elements with a single click
The enhanced visualization system now provides more detailed representations of relationships between code elements:
- Color-coded relationships based on type
- Directional indicators showing dependency flow
- Weighted edges representing relationship strength
- Grouped related nodes for better organization
You can now filter visualizations based on various criteria:
# Filter call graph by module
filtered_graph = nx.DiGraph()
for node, data in call_graph.nodes(data=True):
if isinstance(node, Function) and "api" in node.filepath:
filtered_graph.add_node(node, **data)
# Filter edges based on relationship type
for u, v, data in call_graph.edges(data=True):
if u in filtered_graph.nodes and v in filtered_graph.nodes:
if data.get("type") == "direct_call":
filtered_graph.add_edge(u, v, **data)
codebase.visualize(filtered_graph)Visualize how functions call each other and trace execution paths:
def create_call_graph(entry_point: Function):
graph = nx.DiGraph()
def add_calls(func):
for call in func.call_sites:
called_func = call.resolved_symbol
if called_func:
# Add function objects for rich previews
graph.add_node(func)
graph.add_node(called_func)
graph.add_edge(func, called_func)
add_calls(called_func)
add_calls(entry_point)
return graph
# Visualize API endpoint call graph
endpoint = codebase.get_function("handle_request")
call_graph = create_call_graph(endpoint)
codebase.visualize(call_graph, root=endpoint)Visualize the hierarchy of React components:
def create_component_tree(root_component: Class):
graph = nx.DiGraph()
def add_children(component):
for usage in component.usages:
if isinstance(usage.parent, Class) and "Component" in usage.parent.bases:
graph.add_edge(component.name, usage.parent.name)
add_children(usage.parent)
add_children(root_component)
return graph
# Visualize component hierarchy
app = codebase.get_class("App")
component_tree = create_component_tree(app)
codebase.visualize(component_tree, root=app)Visualize class inheritance relationships:
import networkx as nx
G = nx.DiGraph()
base = codebase.get_class("BaseModel")
def add_subclasses(cls):
for subclass in cls.subclasses:
G.add_edge(cls, subclass)
add_subclasses(subclass)
add_subclasses(base)
codebase.visualize(G, root=base)Visualize dependencies between modules:
def create_module_graph(start_file: File):
G = nx.DiGraph()
def add_imports(file):
for imp in file.imports:
if imp.resolved_symbol and imp.resolved_symbol.file:
graph.add_edge(file, imp.resolved_symbol.file)
add_imports(imp.resolved_symbol.file)
add_imports(start_file)
return graph
# Visualize module dependencies
main = codebase.get_file("main.py")
module_graph = create_module_graph(main)
codebase.visualize(module_graph, root=main)Visualize function groupings by modularity:
def create_modularity_graph(functions: list[Function]):
graph = nx.Graph()
# Group functions by shared dependencies
for func in functions:
for dep in func.dependencies:
if isinstance(dep, Function):
weight = len(set(func.dependencies) & set(dep.dependencies))
if weight > 0:
graph.add_edge(func.name, dep.name, weight=weight)
return graph
# Visualize function modularity
funcs = codebase.functions
modularity_graph = create_modularity_graph(funcs)
codebase.visualize(modularity_graph)Visualize the impact of changes to a specific function or symbol:
def create_blast_radius_visualization(symbol: PySymbol, depth: int = 0, max_depth: int = 5):
"""Create visualization of symbol usage relationships"""
graph = nx.DiGraph()
visited = set()
def add_usages(sym, current_depth=0):
if current_depth >= max_depth or sym in visited:
return
visited.add(sym)
for usage in sym.usages:
usage_symbol = usage.usage_symbol
# Add node and edge to graph
graph.add_node(usage_symbol)
graph.add_edge(sym, usage_symbol)
# Recursively process usage symbol
add_usages(usage_symbol, current_depth + 1)
# Add root node
graph.add_node(symbol)
# Build the visualization
add_usages(symbol)
return graph
# Get target function to analyze
target_func = codebase.get_function('process_data')
# Create and visualize the blast radius
blast_graph = create_blast_radius_visualization(target_func)
codebase.visualize(blast_graph, root=target_func)You can customize your visualizations using NetworkX's attributes while still preserving the smart node features:
def create_custom_graph(codebase):
graph = nx.DiGraph()
# Add nodes with custom attributes while preserving source preview
for func in codebase.functions:
graph.add_node(func,
color='red' if func.is_public else 'blue',
shape='box' if func.is_async else 'oval'
)
# Add edges between actual function objects
for func in codebase.functions:
for call in func.call_sites:
if call.resolved_symbol:
graph.add_edge(func, call.resolved_symbol,
style='dashed' if call.is_conditional else 'solid',
weight=call.count
)
return graphThe enhanced visualization system supports additional customization options:
# Group nodes into clusters
for node in codebase.functions:
if "controller" in node.name.lower():
graph.add_node(node, cluster="controllers", color="#ff7e5f")
elif "service" in node.name.lower():
graph.add_node(node, cluster="services", color="#feb47b")
elif "model" in node.name.lower():
graph.add_node(node, cluster="models", color="#7ee8fa")
# Add custom tooltips
graph.add_node(function,
tooltip=f"Function: {function.name}\nLines: {function.line_count}\nComplexity: {function.complexity}")
# Add custom edge labels
graph.add_edge(source_func, target_func,
label=f"Calls {call_count} times",
weight=call_count)The enhanced visualization system includes several interactive features:
- Zoom and Pan: Navigate large graphs with zoom and pan controls
- Node Selection: Click on nodes to see detailed information
- Expand/Collapse: Expand or collapse node groups to manage complexity
- Search: Search for specific nodes within the visualization
- Filtering: Filter the visualization based on node or edge attributes
- Export: Export the visualization as an image or JSON data
-
Use Symbol Objects for Rich Features
# Better: Add symbol objects for rich previews # This will include source code previews, syntax highlighting, type information, etc. for func in api_funcs: graph.add_node(func) # Basic: Just names, no extra features for func in api_funcs: graph.add_node(func.name)
-
Focus on Relevant Subgraphs
# Better: Visualize specific subsystem api_funcs = [f for f in codebase.functions if "api" in f.filepath] api_graph = create_call_graph(api_funcs) codebase.visualize(api_graph) # Avoid: Visualizing entire codebase full_graph = create_call_graph(codebase.functions) # Too complex
-
Use Meaningful Layouts
# Group related nodes together graph.add_node(controller_class, cluster="api") graph.add_node(service_class, cluster="db")
-
Add Visual Hints
# Color code by type while preserving rich previews for node in codebase.functions: if "Controller" in node.name: graph.add_node(node, color="red") elif "Service" in node.name: graph.add_node(node, color="blue")
-
Use Filtering for Large Graphs
# Filter by module or package filtered_nodes = [n for n in graph.nodes() if "core" in getattr(n, "filepath", "")] subgraph = graph.subgraph(filtered_nodes) codebase.visualize(subgraph)
-
Combine Visualization Types
# Combine call graph with dependency information for u, v in call_graph.edges(): if v in u.dependencies: combined_graph.add_edge(u, v, relationship="calls_and_depends")
def visualize(self, G: Graph | go.Figure, root: Editable | str | int | None = None) -> None:
"""Visualizes a NetworkX graph or Plotly figure.
Creates a visualization of the provided graph using GraphViz. This is useful for visualizing
dependency graphs, call graphs, directory structures, or other graph-based representations
of code relationships.
Args:
G (Graph | go.Figure): A NetworkX graph or Plotly figure to visualize
root (Editable | str | int | None): The root node to visualize around. When specified,
the visualization will be centered on this node. Defaults to None.
Returns:
None
"""The VizNode class defines the attributes that can be used to customize node appearance:
@dataclass(frozen=True)
class VizNode:
name: str | None = None
text: str | None = None
code: str | None = None
color: str | None = None
shape: str | None = None
start_point: tuple | None = None
emoji: str | None = None
end_point: tuple | None = None
file_path: str | None = None
symbol_name: str | None = None- Large graphs may become difficult to read
- Complex relationships might need multiple views
- Some graph layouts may take time to compute
- Preview features only work when adding symbol objects directly
- Very large codebases may require filtering to maintain performance