---
title: "Graph: the Edges around a Block"
description: "The graph route that walks the Edges of the Block index in both directions, the walk's limits, the client call, and the @skaile/forge-editor-graph add-on that draws the answer with sigma in Vue and React."
canonical_url: "https://skaile-ai.github.io/forge-editor/guides/graph"
---
# Graph: the Edges around a Block

> The graph route that walks the Edges of the Block index in both directions, the walk's limits, the client call, and the @skaile/forge-editor-graph add-on that draws the answer with sigma in Vue and React.

The [Edges](https://skaile-ai.github.io/forge-editor/guides/edges) of the Block index say which Block points at which. The **graph** is their neighbourhood in one answer: the Blocks within a few hops of a start, and the Edges between them. The library answers it (`GET /api/editor/graph`); the add-on `@skaile/forge-editor-graph` draws it.

## The route

<table>
<thead>
  <tr>
    <th>
      
    </th>
    
    <th>
      
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      Route
    </td>
    
    <td>
      <code>
        GET /api/editor/graph?documentId=&blockId=&depth=
      </code>
      
       (<code>
        API.graph
      </code>
      
      , port <code>
        index
      </code>
      
      )
    </td>
  </tr>
  
  <tr>
    <td>
      Start
    </td>
    
    <td>
      <code>
        documentId
      </code>
      
       alone: every Block of the Document that has an Edge. With <code>
        blockId
      </code>
      
      : that one Block (kept even without an Edge).
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        depth
      </code>
    </td>
    
    <td>
      Hops from the start Blocks, <code>
        1
      </code>
      
       to <code>
        3
      </code>
      
      , default <code>
        2
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      Answer
    </td>
    
    <td>
      <code>
        GraphData
      </code>
      
      : <code>
        { nodes: { address, title, type }[], edges: BlockEdge[], truncated: boolean }
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      Client
    </td>
    
    <td>
      <code>
        EditorBackend.index.graph({ documentId, blockId?, depth?, signal? })
      </code>
      
      , optional: a backend without it leaves it out
    </td>
  </tr>
</tbody>
</table>

The walk follows Edges in **both** directions (what a Block points at and what points at it). It never follows `tag` Edges (a tag is no Block) or Edges to a Block that does not exist, and it never leaves the Documents the user may read: a Block in another Document is a node only when the user may read that Document. Every Edge in `edges` has both its Blocks in `nodes`.

The answer holds at most 500 Blocks. When the walk meets more, it keeps the first ones in the order it found them (start Blocks first) and sets `truncated`. Edges are answered once each, in the order they were met.

<table>
<thead>
  <tr>
    <th>
      Status
    </th>
    
    <th>
      When
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        400
      </code>
    </td>
    
    <td>
      no <code>
        documentId
      </code>
      
      , or a <code>
        depth
      </code>
      
       outside 1 to 3
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        401
      </code>
    </td>
    
    <td>
      no user
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        403
      </code>
    </td>
    
    <td>
      the user may not read the Document
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        404
      </code>
    </td>
    
    <td>
      <code>
        blockId
      </code>
      
       names no Block of the Document
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        501
      </code>
    </td>
    
    <td>
      the host has no <code>
        index
      </code>
      
       port
    </td>
  </tr>
</tbody>
</table>

The walk itself is `walkGraph(seeds, source, { depth, maxNodes?, keepIsolated? })` in `@skaile/forge-editor/core`, over a `GraphSource` (`edges(address, direction)` and `node(address)`). The route reads a `GraphSource` from the `index` port; the memory backend (`createMemoryBackend`) reads one from its projected rows (`graphSourceOfRows`), so a test or a demo draws the same graph.

## The panel

```ts
import { GraphPanel } from '@skaile/forge-editor-graph/vue' // or '@skaile/forge-editor-graph/react'
```

```vue
<GraphPanel :document-id="documentId" :block-id="blockId" :open="openBlock" />
```

<table>
<thead>
  <tr>
    <th>
      Prop
    </th>
    
    <th>
      
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        documentId
      </code>
    </td>
    
    <td>
      the Document the graph is about
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        blockId
      </code>
    </td>
    
    <td>
      optional: the graph around this Block; it is drawn larger and in the middle
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        open(address)
      </code>
    </td>
    
    <td>
      what a click on a node does; resolve <code>
        false
      </code>
      
       when it did not happen
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        depth
      </code>
    </td>
    
    <td>
      hops to start with (1 to 3, default 2); the panel has a select for it
    </td>
  </tr>
</tbody>
</table>

**Opening a Block** is the host's job, as for a search hit: in the open Document `editor.commands.revealBlock(blockId)`, else navigate to the Document with `?block=<id>`. The workspace host of this repository does both in `openHit`; the same function serves the graph (`{ documentId, blockId }` is what a `BlockSearchHit` also carries).

The panel draws:

<table>
<thead>
  <tr>
    <th>
      
    </th>
    
    <th>
      
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      Node
    </td>
    
    <td>
      one per Block, labelled with its title (else its type); colour by Block type, the focus Block in rose
    </td>
  </tr>
  
  <tr>
    <td>
      Edge colour
    </td>
    
    <td>
      the kind: <code>
        reference
      </code>
      
      , <code>
        derivation
      </code>
      
      , <code>
        embed
      </code>
      
      , and one colour per authored kind (<code>
        supports
      </code>
      
      , <code>
        contradicts
      </code>
      
      , ...), the same one every time
    </td>
  </tr>
  
  <tr>
    <td>
      Edge dashed
    </td>
    
    <td>
      the Edge was written by hand (<code>
        origin: 'meta'
      </code>
      
      , in the inspector's „Kanten“ section); content Edges are solid
    </td>
  </tr>
  
  <tr>
    <td>
      Arrow
    </td>
    
    <td>
      at the Block the Edge points to
    </td>
  </tr>
  
  <tr>
    <td>
      Legend
    </td>
    
    <td>
      the kinds present, as they are drawn
    </td>
  </tr>
  
  <tr>
    <td>
      List
    </td>
    
    <td>
      every node again as a button, for keyboards and screen readers (the canvas is WebGL and has only a label)
    </td>
  </tr>
  
  <tr>
    <td>
      Notice
    </td>
    
    <td>
      <code>
        truncated
      </code>
      
      : more than 500 Blocks, the rest left out
    </td>
  </tr>
</tbody>
</table>

The panel renders nothing when the backend has no `index.graph`.

## Framework-free parts

`@skaile/forge-editor-graph` (no sigma, no DOM):

<table>
<thead>
  <tr>
    <th>
      
    </th>
    
    <th>
      
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        buildGraph(data, { focus?, iterations? })
      </code>
    </td>
    
    <td>
      the graphology <code>
        MultiDirectedGraph
      </code>
      
      : node attributes <code>
        label x y size color documentId blockId blockType focus
      </code>
      
      , Edge attributes <code>
        color size dash gap kind origin
      </code>
      
      ; positions are deterministic (circle, then ForceAtlas2)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        edgeStyleOf({ kind, origin })
      </code>
    </td>
    
    <td>
      <code>
        { color, dashed }
      </code>
      
      : the style table of the panel
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        legendOf(data)
      </code>
    </td>
    
    <td>
      the legend entries
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        createGraphPanelModel({ load, open, depth? })
      </code>
    </td>
    
    <td>
      the model both components render: <code>
        state
      </code>
      
       (target, depth, status, last answer), <code>
        setTarget
      </code>
      
      , <code>
        setDepth
      </code>
      
      , <code>
        reload
      </code>
      
      , <code>
        open
      </code>
      
      , <code>
        dispose
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        graphLoaderOf(backend)
      </code>
    </td>
    
    <td>
      <code>
        load
      </code>
      
       for <code>
        createGraphPanelModel
      </code>
      
      , over <code>
        backend.index.graph
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        createGraphTranslator(locale, overrides?)
      </code>
    </td>
    
    <td>
      the add-on's own German and English strings
    </td>
  </tr>
</tbody>
</table>

sigma (WebGL) loads when a panel mounts, so importing the package costs nothing and server-side rendering never meets it. The dashed Edges use sigma's `layerDashed` with the Edge attributes `dash` and `gap`: an authored Edge has 8 px dashes and 5 px gaps, every other Edge one dash longer than the canvas and no gap.

## Wiring a host

The add-on needs no feature and no port of its own. The host only mounts the panel where it wants it (a sidebar tab, the Block's details) and passes `open`:

```vue
<script setup lang="ts">
import { GraphPanel } from '@skaile/forge-editor-graph/vue'
</script>

<template>
  <GraphPanel :document-id="documentId" :block-id="blockId" :open="openHit" />
</template>
```

The repository's document pages (the file-drawer layout, `app/layouts/default.vue`) show it behind `NUXT_PUBLIC_ADDONS_GRAPH=1`; the kitchen sink's standalone pages have no workspace index and show no graph.


## Sitemap

See the full [sitemap](https://skaile-ai.github.io/forge-editor/sitemap.md) for all pages.
