How Ghidra's Graph Framework Works for Visualization: JUNG-Based MVC Architecture Explained

Ghidra's graph framework employs a model-view-controller architecture built on the JUNG library to render interactive visual graphs with Swing-based vertices, supporting zooming, panning, and satellite views through three distinct coordinate spaces.

The National Security Agency's Ghidra reverse engineering platform renders complex program structures through a flexible graph framework built atop the Java Universal Network/Graph (JUNG) library. This architecture separates model, layout, and view concerns to deliver responsive, interactive visualizations capable of handling thousands of vertices while supporting custom vertex components and edge articulations.

Core MVC Architecture

Ghidra's graph framework implements a strict model-view-controller pattern that abstracts JUNG's low-level rendering capabilities while exposing Swing-centric vertex models. The core pieces are located in Ghidra/Framework/Graph/src/main/java/ghidra/graph/ and its subpackages.

The Model Layer (VisualGraph)

The Model layer centers on VisualGraph<V,E> defined in Ghidra/Framework/Graph/src/main/java/ghidra/graph/VisualGraph.java. This directed graph implementation maintains vertex locations, focus states, and selection sets while notifying registered listeners of changes through methods like vertexLocationChanged(). Concrete implementations such as FunctionGraph supply the underlying JUNG Graph<V,E> and associated layout algorithms.

Layout Abstraction (VisualGraphLayout)

The Layout layer uses VisualGraphLayout<V,E> found in Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/layout/VisualGraphLayout.java. This extension of JUNG's Layout interface provides vertex positioning, reports articulated edge support via usesEdgeArticulations(), and optionally supplies custom edge renderers through getEdgeRenderer(). The framework supports force-directed, hierarchical, or radial layouts through this abstraction.

View and Container Components

The View layer consists of GraphViewer<V,E> in Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/GraphViewer.java, which extends JUNG's VisualizationViewer. It installs custom renderers (VisualEdgeRenderer, VisualVertexRenderer) and pick support (VisualGraphShapePickSupport) that resolves clicks using actual vertex shapes rather than bounding boxes.

The Container layer is handled by GraphComponent<V,E,G> in Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/GraphComponent.java. This Swing container constructs the primary GraphViewer, manages the satellite view, wires mouse plugins, and handles layout changes. When disposed, GraphComponent.dispose() clears cached layouts and removes listeners to prevent memory leaks.

Coordinate Space Transformations

Accurate mouse interaction requires translating coordinates between three distinct spaces, managed by GraphViewerUtils in Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/GraphViewerUtils.java.

  • Layout Space: Raw Point2D values produced by the layout algorithm.
  • Graph Space: Layout points transformed by the current pan and zoom operations via JUNG's MultiLayerTransformer LAYOUT layer.
  • View Space: Final Java 2D screen coordinates after view-layer scaling.

The static helper translatePointFromViewSpaceToGraphSpace() converts screen coordinates back to graph coordinates for vertex picking, while translatePointFromLayoutSpaceToViewSpace() handles the inverse operation for tool-tip positioning.

Interaction and Event Handling

Mouse Plugins and Selection

GraphViewer delegates input processing to VisualGraphPluggableGraphMouse, which contains specialized plugins:

  • VisualGraphHoverMousePlugin: Handles hover highlighting and cursor changes.
  • VertexClickMousePlugin: Processes double-click events on vertices.

Selection state is maintained through GPickedState. When a vertex is picked, VertexPickingListener implementations update the model via graph.setVertexFocused() or graph.setSelectedVertices(). Zoom operations can be configured as mouse-relative (default) or view-centered through GraphViewer.useMouseRelativeZoom().

Path Highlighting

The framework includes a VisualGraphPathHighlighter accessible through GraphViewer.setVertexHoverPathHighlightMode(). This component calculates and renders the shortest path from a focused vertex to all reachable vertices, enabling visual tracing of data flow or call chains.

Satellite View Synchronization

GraphComponent automatically creates a satellite view using SatelliteGraphViewer located in Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/satellite/SatelliteGraphViewer.java. This miniature overview displays the entire graph at reduced scale and may employ caching for large graphs.

Synchronization is handled by VisualGraphViewUpdater in Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/VisualGraphViewUpdater.java, which receives the satellite instance via viewUpdater.setSatelliteViewer(). The updater manages animated panning, zoom-to-fit operations, vertex "twinkle" effects, and ensures the satellite rectangle accurately reflects the primary view's viewport. Users can dock, undock, or pop out the satellite into independent windows.

Practical Implementation Example

The following self-contained example demonstrates creating a simple directed graph visualization using Ghidra's framework. This mirrors the implementation pattern used by Ghidra's Function Graph plugin.

import ghidra.graph.*;
import ghidra.graph.viewer.*;
import ghidra.graph.viewer.layout.*;
import ghidra.graph.viewer.options.*;
import edu.uci.ics.jung.graph.DirectedSparseGraph;
import edu.uci.ics.jung.algorithms.layout.FRLayout;
import java.awt.*;
import javax.swing.*;

public class SimpleGraphDemo {

    /** Simple vertex that implements VisualVertex */
    static class SimpleVertex implements VisualVertex {
        private final String label;
        private Point location = new Point(0, 0);
        private boolean focused, selected;

        SimpleVertex(String s) { this.label = s; }
        @Override public JComponent getComponent() { return new JLabel(label); }
        @Override public void setLocation(Point p) { this.location = p; }
        @Override public Point getLocation() { return location; }
        @Override public void setFocused(boolean f) { this.focused = f; }
        @Override public boolean isFocused() { return focused; }
        @Override public void setSelected(boolean s) { this.selected = s; }
        @Override public boolean isSelected() { return selected; }
        // Boiler‑plate for VisualVertex …
        @Override public double getEmphasis() { return 0; }
        @Override public void setEmphasis(double e) {}
        @Override public Component getComponent() { return null; }
    }

    /** Simple edge – no extra data needed */
    static class SimpleEdge implements VisualEdge<SimpleVertex> {
        private final SimpleVertex start, end;
        private boolean selected, hovered;
        SimpleEdge(SimpleVertex s, SimpleVertex e) { start=s; end=e; }
        @Override public SimpleVertex getStart() { return start; }
        @Override public SimpleVertex getEnd()   { return end; }
        @Override public void setSelected(boolean b) { selected=b; }
        @Override public boolean isSelected() { return selected; }
        @Override public void setHovered(boolean b) { hovered=b; }
        @Override public boolean isHovered() { return hovered; }
        // Boiler‑plate …
        @Override public double getEmphasis() { return 0; }
        @Override public void setEmphasis(double e) {}
    }

    /** Concrete VisualGraph implementation */
    static class SimpleVisualGraph
            implements VisualGraph<SimpleVertex, SimpleEdge> {

        private final DirectedSparseGraph<SimpleVertex, SimpleEdge> g =
                new DirectedSparseGraph<>();

        private final VisualGraphLayout<SimpleVertex, SimpleEdge> layout;

        SimpleVisualGraph() {
            // Populate graph
            SimpleVertex a = new SimpleVertex("A");
            SimpleVertex b = new SimpleVertex("B");
            SimpleVertex c = new SimpleVertex("C");
            g.addVertex(a); g.addVertex(b); g.addVertex(c);
            g.addEdge(new SimpleEdge(a,b));
            g.addEdge(new SimpleEdge(b,c));
            g.addEdge(new SimpleEdge(c,a));

            // Use a force‑directed layout wrapped in a default implementation
            FRLayout<SimpleVertex, SimpleEdge> fr = new FRLayout<>(g);
            fr.setSize(new Dimension(800,600));
            layout = new DefaultVisualGraphLayout<>(g, fr); // see Ghidra source for wrapper
        }

        @Override public void vertexLocationChanged(SimpleVertex v, Point p,
                ChangeType ct) { v.setLocation(p); }
        @Override public SimpleVertex getFocusedVertex() { return null; }
        @Override public void setVertexFocused(SimpleVertex v, boolean b) {}
        @Override public void clearSelectedVertices() {}
        @Override public void setSelectedVertices(Set<SimpleVertex> s) {}
        @Override public Set<SimpleVertex> getSelectedVertices() { return Set.of(); }
        @Override public void addGraphChangeListener(VisualGraphChangeListener<SimpleVertex,SimpleEdge> l){}
        @Override public void removeGraphChangeListener(VisualGraphChangeListener<SimpleVertex,SimpleEdge> l){}
        @Override public VisualGraphLayout<SimpleVertex,SimpleEdge> getLayout() { return layout; }
        @Override public VisualGraph<SimpleVertex,SimpleEdge> copy() { return this; }
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            SimpleVisualGraph graph = new SimpleVisualGraph();

            // GraphComponent is the high‑level container
            GraphComponent<SimpleVertex, SimpleEdge, SimpleVisualGraph> comp =
                    new GraphComponent<>(graph);

            // Optional: tweak colours
            VisualGraphOptions opts = new VisualGraphOptions();
            opts.setBackgroundColor(new GColor("color.bg.visualgraph"));
            comp.setGraphOptions(opts);

            JFrame f = new JFrame("Ghidra Graph Demo");
            f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            f.add(comp.getComponent(), BorderLayout.CENTER);
            f.setSize(900,700);
            f.setLocationRelativeTo(null);
            f.setVisible(true);
        });
    }
}

Key implementation steps:

  1. Implement VisualVertex and VisualEdge: SimpleVertex provides a Swing JComponent via getComponent() and tracks location, selection, and focus state. SimpleEdge connects vertices and maintains hover/selection flags.
  2. Create the VisualGraph: SimpleVisualGraph builds a JUNG DirectedSparseGraph, populates it with vertices and edges, and wraps a force-directed FRLayout in a DefaultVisualGraphLayout.
  3. Instantiate GraphComponent: The GraphComponent constructor creates the primary viewer, satellite view, and mouse plugins automatically.
  4. Configure Options: VisualGraphOptions (defined in Ghidra/Framework/Graph/src/main/java/ghidra/service/graph/GraphDisplayOptions.java) controls background colors, zoom behavior, and path highlighting modes.

Summary

  • Ghidra's graph framework extends the JUNG library with a model-view-controller architecture that separates graph data from rendering and interaction logic.
  • Three coordinate spaces (Layout, Graph, and View) enable precise mouse interaction and vertex positioning, translated via GraphViewerUtils.
  • VisualGraph and VisualGraphLayout interfaces allow developers to plug custom data structures into the visualization pipeline while reusing Ghidra's layout algorithms.
  • GraphComponent provides a complete Swing container with satellite view, mouse plugins, and lifecycle management via dispose().
  • The framework supports articulated edges, custom vertex components (any Swing widget), and interactive path highlighting through VisualGraphPathHighlighter.

Frequently Asked Questions

What underlying library powers Ghidra's graph visualization framework?

The framework is built atop the Java Universal Network/Graph (JUNG) library, which provides the fundamental graph data structures and layout algorithms. Ghidra wraps JUNG with classes like GraphViewer (extending VisualizationViewer) and VisualGraphLayout to add Swing component support, coordinate transformations, and Ghidra-specific interaction models.

How does the framework handle coordinate translation for mouse interactions?

The system maintains three coordinate spaces: Layout Space (raw algorithm output), Graph Space (post-pan/zoom transformation), and View Space (screen pixels). The GraphViewerUtils class provides static methods like translatePointFromViewSpaceToGraphSpace() to convert screen coordinates back to graph coordinates for vertex picking, ensuring accurate selection even when zoomed or panned.

What is the satellite view and how does it stay synchronized with the main graph?

The satellite view is a miniature SatelliteGraphViewer instance created by GraphComponent that displays the entire graph at reduced scale. The VisualGraphViewUpdater maintains synchronization by updating the satellite's viewport rectangle whenever the primary view pans or zooms. It also manages animated transitions like "twinkle" effects and zoom-to-fit operations across both views.

How can developers customize vertex appearance and behavior in Ghidra graphs?

Developers implement the VisualVertex interface (located in Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/VisualVertex.java) to return custom Swing components via getComponent(). This allows any Java Swing widget—from simple labels to complex code listings—to serve as a graph vertex. Additionally, implementing VisualGraphLayout enables custom edge shapes through getEdgeShapeTransformer() and articulated edge support via usesEdgeArticulations().

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 →