In 2018 I built a network model to figure out where to invest my time in the Iowa technology community. The model grew to around 10,000 elements – companies, organizations, people, cities, connectivity. I used eigenvector centrality to surface nodes I wasn't connected to that had high connectivity elsewhere. It influenced real decisions: I joined the TAI board, found communities I didn't know existed, and mapped density differences between Des Moines and coastal ecosystems.
That model stuck with me. Over the years at Brale, force-directed relationship graphs keep coming up – mapping issuers to assets to rails, connecting program operators to the blockchains they run on. Every time, someone reinvents the visual language. What does a color mean? What does a thick line represent? What shape distinguishes a company from a person from an event? These decisions get made fresh on every project. The next person who opens the graph has to reverse-engineer all of it before they can read the content.
Last year, CSF (Commons Stablecoin Format) standardized how we draw funds flows. Paste the spec into an LLM, ask for a stablecoin funds flow, and it follows the format every time. I wanted the same thing for relationship graphs. So I wrote Graphing Standard Format – GSF – a small grammar that defines how type drives shape, how weight drives size, and how density levels control what gets disclosed.
This is my network, rendered in GSF:
Circles are people. Rounded rectangles are organizations. Triangles are events – distinct from the companies that produce them. Diamonds are places. Hexagons are concepts. Pills are systems. Click any node to see its profile card. Toggle to Medium to see color. You didn't have to decide any of that. GSF did.
Here is a video demonstrating usage of GSF for the first time. It did a pretty good job!
GSF and CSF
| CSF | GSF | |
|---|---|---|
| Output | Mermaid sequence diagrams | D3 relationship graphs |
| Answers | How value steps from A to B, in order | What exists and how it is connected |
| Nature | Temporal, step-ordered | Structural, non-temporal |
| Levels | Light / Medium / Heavy | Light / Medium / Heavy |
Both implement the same value-layer primitives: ValueType, TransferType, Exchange. The one loss going from CSF to GSF is sequence order, since graphs are non-temporal.
The standard
GSF distills the rules for the visualization – not the visualization itself. When it's used, it means you define what to show and the format handles how to show it. The baseline stays consistent across projects.
Three things:
- GSF is the ruleset. Never visualized.
- A dataset is a
{view, nodes, links}instance that conforms to GSF. This is what gets rendered. - A renderer takes the rules plus a dataset and draws the picture.
Four primitives. Everything a renderer needs:
- Relationship – a link between exactly two entities. Links create the graph.
- Type – the qualitative classifier. Drives shape and color.
- Variables – the detail bag. Holds everything type does not.
- Weight – the quantitative magnitude. Drives size and width.
The encoding rule: value vs values. The one quantitative value (a number, carried in weight) drives size and width. The qualitative values (category, status, label) drive shape, color, grouping, and what shows on hover.
One design choice worth calling out: relationship types are open strings. There is no closed list. A dataset defines whatever types it needs – my graph above uses founder, cofounder, spinout, and operates, none of which are in the spec. It rendered fine. GSF fixes the encoding (how types become shapes and colors), not the vocabulary (what types you're allowed to use).
Density
Same dial as CSF. How much detail and color a renderer reveals:
- Light – black and white. Shape encodes type, line style encodes relationship class (solid = value, dashed = data, dotted = derived), thickness encodes magnitude, arrowhead encodes direction. No color. Print-ready.
- Medium – adds color as an overlay, a legend, and variables on hover.
- Heavy – the full record. All variables, transfer endpoints, expansion affordances.
One rule: Light must read in B&W. Color is never the only signal.
The format
A link needs source, destination, and type. Everything else is optional:
{
"format": "gsf",
"version": "0.5.2",
"renderer": "d3-force",
"view": { "level": "light", "hops": 1, "focus": ["Person A"] },
"links": [
{ "source": "Person A", "destination": "Person B", "type": "message" }
]
}
For value-layer graphs, endpoint objects carry value_type and transfer_type on each side. When they differ, it signals an exchange – and via names the exchanger:
{
"format": "gsf",
"version": "0.5.2",
"renderer": "d3-force",
"view": { "level": "heavy", "hops": 1, "focus": ["Brale"] },
"nodes": [
{ "id": "Sender", "type": "org" },
{ "id": "Brale", "type": "org", "variables": { "sub_type": "exchange" } },
{ "id": "Recipient Wallet", "type": "system", "variables": { "sub_type": "wallet" } }
],
"links": [
{
"source": { "id": "Sender", "value_type": "USD", "transfer_type": "wire" },
"destination": { "id": "Recipient Wallet", "value_type": "USDC", "transfer_type": "solana" },
"type": "transfers_via",
"via": "Brale",
"weight": 1000,
"variables": { "exchange": "USD -> USDC", "rate": 1.0 }
}
]
}
Using it
Paste gsf-0.5.2.json into an LLM and ask it to convert source material into a GSF dataset. It follows the format every time – same as CSF. The llm_instructions block in the spec is designed to be machine-consumed directly. Check the GSF repository for the latest version.
The standard is open-source: github.com/benmilne-com/standards/gsf. Fork it, use it, contribute.
Structure is free.