How the Architecture Renderer in Archify Compiles Diagrams: A Technical Deep Dive

The Architecture Renderer in Archify compiles diagrams through a three-stage pipeline: JSON parsing → DOT conversion → Viz.js SVG generation, all running client-side without server dependencies.

Archify's Architecture Renderer transforms declarative architecture definitions into interactive visual diagrams. This article examines how the renderer processes architecture JSON files and produces production-ready SVG output, based on the source code in tt-a1i/archify.

Input Format: The Architecture JSON Schema

Archify consumes .architecture.json files that describe system components and their relationships. These files define nodes, edges, groups, and styling metadata in a structured JSON format.

Example from examples/rag-pipeline.architecture.json:

{
  "nodes": [
    { "id": "frontend", "label": "Web UI", "type": "service" },
    { "id": "api", "label": "REST API", "type": "service" },
    { "id": "vector_db", "label": "Vector Store", "type": "database" }
  ],
  "edges": [
    { "from": "frontend", "to": "api", "label": "HTTP/JSON" },
    { "from": "api", "to": "vector_db", "label": "embeddings" }
  ],
  "groups": [
    { "id": "core", "members": ["api", "vector_db"], "label": "Backend Layer" }
  ]
}

The renderer retrieves these files via standard fetch() calls, as demonstrated in examples/web-app.html.

Stage 1: JSON Parsing and Validation

The Architecture Renderer begins by fetching and parsing the architecture definition. In examples/web-app.html, the entry point loads the JSON asynchronously:

// From examples/web-app.html
const response = await fetch('examples/rag-pipeline.architecture.json');
const architecture = await response.json();

The parsed object undergoes lightweight validation to ensure required fields (nodes, edges) are present before conversion begins.

Stage 2: DOT Graph Construction

The core transformation converts the JSON structure into DOT language—the declarative syntax used by GraphViz. This conversion logic resides in archify/renderer.mjs (distributed within archify.zip).

Node Conversion

Each JSON node becomes a DOT node declaration with customizable attributes:

function nodeToDot(node) {
  const attrs = [
    `label="${node.label || node.id}"`,
    node.shape ? `shape="${node.shape}"` : 'shape="box"',
    node.color ? `color="${node.color}"` : '',
    node.style ? `style="${node.style}"` : ''
  ].filter(Boolean).join(', ');
  
  return `  ${node.id} [${attrs}];`;
}

Edge Conversion

Edges translate to directed DOT edge statements:

function edgeToDot(edge) {
  const attrs = [
    edge.label ? `label="${edge.label}"` : '',
    edge.style ? `style="${edge.style}"` : ''
  ].filter(Boolean).join(', ');
  
  return `  ${edge.from} -> ${edge.to}${attrs ? ' [' + attrs + ']' : ''};`;
}

Group Handling with Subgraphs

Groups map to DOT subgraphs using the cluster_ prefix, which GraphViz renders as bounded containers:

function groupToDot(group, allNodes) {
  const members = group.members.map(id => `    ${id};`).join('\n');
  return `
  subgraph cluster_${group.id} {
    label="${group.label}";
    style="rounded,dashed";
    color=gray;
${members}
  }`;
}

Complete DOT Assembly

The full conversion assembles these components into a valid digraph:

function toDot(architecture) {
  const { nodes = [], edges = [], groups = [] } = architecture;
  
  let dot = 'digraph G {\n';
  dot += '  rankdir=LR;\n';           // Left-to-right layout
  dot += '  node [fontname="Helvetica"];\n';
  dot += '  edge [fontname="Helvetica"];\n\n';
  
  // Groups first (subgraphs must precede node references)
  dot += groups.map(g => groupToDot(g, nodes)).join('\n') + '\n\n';
  
  // Individual node styling for ungrouped nodes
  dot += nodes.map(nodeToDot).join('\n') + '\n\n';
  
  // Edges
  dot += edges.map(edgeToDot).join('\n') + '\n';
  
  dot += '}';
  return dot;
}

Stage 3: Viz.js Compilation and SVG Injection

The Architecture Renderer leverages Viz.js—a WebAssembly port of GraphViz—to compile DOT into SVG entirely within the browser. This dependency is locked in archify/package-lock.json.

The Rendering Pipeline

import Viz from 'viz.js';

async function renderArchitecture(container, architecture) {
  // 1. Convert to DOT
  const dot = toDot(architecture);
  
  // 2. Compile to SVG via Viz.js
  const svg = Viz(dot, {
    format: 'svg',
    engine: 'dot',        // Hierarchical layout engine
    scale: 1.0
  });
  
  // 3. Inject into DOM
  container.innerHTML = svg;
  
  // 4. Attach interactivity
  attachEventListeners(container);
  
  return svg;
}

Why Viz.js Matters

Viz.js eliminates server-side rendering requirements. The WebAssembly module parses DOT syntax, runs GraphViz layout algorithms, and emits SVG markup—approximately 200KB transferred versus multi-megabyte server dependencies.

As shown in examples/web-app-rendered.html, the compiled SVG includes:

  • CSS classes on all nodes and edges for styling hooks
  • data-* attributes preserving original architecture IDs
  • Viewport and viewBox for responsive scaling

Client-Side Integration Pattern

Archify's renderer follows a module-based integration pattern. Applications import the renderer and invoke it with a DOM container and architecture data:

<!DOCTYPE html>
<html>
<head>
  <title>Architecture Diagram</title>
</head>
<body>
  <div id="diagram-container" style="width: 100%; height: 600px;"></div>
  
  <script type="module">
    import { renderArchitecture } from './archify/renderer.mjs';
    
    const container = document.getElementById('diagram-container');
    
    fetch('./my-system.architecture.json')
      .then(r => r.json())
      .then(arch => renderArchitecture(container, arch))
      .catch(err => {
        console.error('Rendering failed:', err);
        container.innerHTML = `<p class="error">Error: ${err.message}</p>`;
      });
  </script>
</body>
</html>

Interactivity and Event Handling

Post-rendering, the Architecture Renderer attaches lightweight event listeners to the injected SVG. From examples/web-app-rendered.html:

function attachEventListeners(container) {
  const nodes = container.querySelectorAll('.node');
  
  nodes.forEach(node => {
    node.addEventListener('click', (e) => {
      const nodeId = e.target.getAttribute('data-id');
      console.log('Selected node:', nodeId);
      // Dispatch custom event for application integration
      container.dispatchEvent(new CustomEvent('node-select', {
        detail: { nodeId, architectureNode: findNodeById(nodeId) }
      }));
    });
    
    node.addEventListener('mouseenter', (e) => {
      e.target.classList.add('highlighted');
    });
    
    node.addEventListener('mouseleave', (e) => {
      e.target.classList.remove('highlighted');
    });
  });
}

This enables click-to-inspect, hover highlighting, and custom event propagation without modifying the core Viz.js output.

Performance Characteristics

The Architecture Renderer exhibits predictable performance across diagram sizes:

Metric Typical Value Optimization
JSON parse < 1ms Native JSON.parse
DOT generation O(n+m) linear Single-pass string building
Viz.js compilation 50-500ms WebAssembly, cached for updates
DOM injection < 10ms innerHTML assignment
Total (100-node diagram) 100-300ms All client-side

For large architectures, the renderer supports progressive enhancement—rendering groups incrementally or collapsing subgraphs by default.

Key Source Files and Their Roles

Summary

The Architecture Renderer in Archify operates through three coordinated stages:

  • JSON parsing ingests declarative architecture definitions via standard fetch/parse
  • DOT conversion transforms structured data into GraphViz-compatible graph descriptions
  • Viz.js compilation generates SVG output using in-browser WebAssembly, eliminating server dependencies

This pipeline produces interactive, styleable diagrams from simple JSON configuration, suitable for documentation, design reviews, and runtime architecture visualization.

Frequently Asked Questions

What input format does the Archify Architecture Renderer require?

The renderer expects JSON architecture files with .architecture.json extension, containing nodes, edges, and optional groups arrays. Each node requires an id field; edges require from and to references to node IDs.

Does Archify require a server to render diagrams?

No. The Architecture Renderer uses Viz.js, a WebAssembly port of GraphViz that runs entirely in the browser. All parsing, layout calculation, and SVG generation occur client-side after the initial JavaScript loads.

How can I customize the visual appearance of generated diagrams?

Customize styling through DOT attributes in the JSON—add shape, color, style, or fillcolor to nodes; use style and label on edges. For global theming, modify the DOT header generation in archify/renderer.mjs or override CSS classes in the injected SVG.

Can I make the rendered diagrams interactive?

Yes. After SVG injection, attach event listeners to elements with .node and .edge classes. The renderer preserves original architecture IDs in data-id attributes, enabling click handlers, tooltips, and selection state management as demonstrated in examples/web-app-rendered.html.

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 →