Skip to content

Latest commit

 

History

History
453 lines (339 loc) · 13.1 KB

File metadata and controls

453 lines (339 loc) · 13.1 KB
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).

Basic Usage

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.

Adding Nodes and Edges

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 name
Adding symbols to the graph directly (as opposed to adding by name) enables automatic type information, code preview on hover, and more.

Enhanced Visualization Features

Codegen's visualization system has been enhanced with several new features to provide more detailed insights into your codebase:

Interactive Selection Row

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

Improved Relationship Visualization

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

Advanced Filtering Options

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)

Common Visualization Types

Call Graphs

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)
Learn more about [traversing the call graph here](/building-with-codegen/traversing-the-call-graph).

React Component Trees

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)

Inheritance Graphs

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)

Module Dependencies

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)

Function Modularity

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)

Blast Radius Visualization

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)

Customizing Visualizations

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 graph

Advanced Customization Options

The 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)

Interactive Features

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

Best Practices

  1. 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)
  2. 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
  3. Use Meaningful Layouts

    # Group related nodes together
    graph.add_node(controller_class, cluster="api")
    graph.add_node(service_class, cluster="db")
  4. 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")
  5. 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)
  6. 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")

Visualization API Reference

Codebase.visualize

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
    """

VizNode

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

Limitations

  • 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