Sigma graph library#
Data Schema / Graph Data Model#
Node Types#
The graph consists of four primary node types:
-
Users - User DIDs (Decentralized Identifiers)
- Source:
users.id - Display:
users.handleor DID
- Source:
-
URLs - Web resources saved as URL cards
- Source:
cards.urlwherecards.type = 'URL' - Metadata:
cards.urlType,cards.contentData
- Source:
-
Collections - Curated groups of cards
- Source:
collections.id - Display:
collections.name - Metadata:
collections.description,collections.accessType
- Source:
-
Note Cards - User annotations and notes
- Source:
cards.idwherecards.type = 'NOTE' - Metadata:
cards.contentData
- Source:
Edge Types#
The graph relationships are derived from the following sources:
1. Follow Relationships#
User → User (social follows)
SELECT followerId, targetId
FROM follows
WHERE targetType = 'user'
User → Collection (collection follows)
SELECT followerId, targetId
FROM follows
WHERE targetType = 'collection'
2. Authorship (User → URL)#
Inferred from URL card creation:
SELECT authorId as userId, url
FROM cards
WHERE type = 'URL' AND url IS NOT NULL
3. Note Connections (Note → URL)#
Inferred from note cards referencing parent URL cards:
SELECT n.id as noteCardId, p.url
FROM cards n
JOIN cards p ON n.parentCardId = p.id
WHERE n.type = 'NOTE' AND p.type = 'URL'
4. Collection Memberships (Collection ↔ URL)#
Inferred from collection-card relationships:
SELECT cc.collectionId, c.url
FROM collection_cards cc
JOIN cards c ON cc.cardId = c.id
WHERE c.type = 'URL' AND c.url IS NOT NULL
Alternative (include metadata):
SELECT
cc.collectionId,
c.url,
cc.addedBy,
cc.addedAt
FROM collection_cards cc
JOIN cards c ON cc.cardId = c.id
WHERE c.url IS NOT NULL
Additional Relationship Data#
The connections table provides explicit curator-defined relationships between nodes:
SELECT
curatorId,
sourceType, -- 'URL' or 'CARD'
sourceValue, -- URL string or Card UUID
targetType, -- 'URL' or 'CARD'
targetValue, -- URL string or Card UUID
connectionType, -- SUPPORTS, OPPOSES, etc.
note
FROM connections
These can be used to create typed/weighted edges with semantic meaning (e.g., "supports", "opposes").
Sigma.js + Next.js Setup#
1. Install#
npm install sigma graphology @react-sigma/core @react-sigma/layout-forceatlas2
2. Dynamic Import Component#
Create components/Graph.tsx (no 'use client' needed—dynamic import handles it):
'use client';
import { SigmaContainer, useSigma } from '@react-sigma/core';
import { useEffect, useState } from 'react';
import Graph from 'graphology';
export default function GraphView() {
const [graph, setGraph] = useState<Graph | null>(null);
useEffect(() => {
const g = new Graph();
g.addNode('a', { x: 0, y: 0, size: 10, label: 'Node A' });
g.addNode('b', { x: 1, y: 1, size: 15, label: 'Node B' });
g.addEdge('a', 'b');
setGraph(g);
}, []);
if (!graph) return null;
return (
<SigmaContainer style={{ height: '600px', background: '#1e1e1e' }} graph={graph}>
<GraphEvents />
</SigmaContainer>
);
}
3. Events & Camera Controls#
function GraphEvents() {
const sigma = useSigma();
const zoomToNode = (nodeId: string) => {
const node = sigma.getGraph().getNodeAttributes(nodeId);
sigma.getCamera().animate(
{ x: node.x, y: node.y, ratio: 0.3 },
{ duration: 800 }
);
};
return (
<Controls zoomToNode={zoomToNode} />
);
}
4. Page Integration#
import dynamic from 'next/dynamic';
const Graph = dynamic(() => import('@/components/Graph'), { ssr: false });
export default function Page() {
return <Graph />;
}
5. Force Layout#
Add physics with useLayoutForceAtlas2() hook from @react-sigma/layout-forceatlas2 1:
import { useLayoutForceAtlas2 } from '@react-sigma/layout-forceatlas2';
function GraphWithPhysics({ graph }: { graph: Graph }) {
const { start, stop } = useLayoutForceAtlas2({
settings: { gravity: 0.5 },
});
useEffect(() => {
start();
return () => stop();
}, []);
return null;
}
Key notes: Always use ssr: false 2. Sigma uses WebGL 3—canvas rendering, not DOM elements.