Visual Stories
Building Influence Visualizations
Transform on-chain relationship data into visual stories. The protocol generates visualization-ready structures with pre-computed visual parameters.
What Are Influence Visualizations?
Influence visualizations are visual stories generated entirely from on-chain relationship data. They're not dashboards—they're provable artifacts that show how influence flows through creator-participant relationships.
"These aren't dashboards. They're visual stories generated entirely from on-chain relationship data."
The VisualizationAssembler contract transforms raw attestation data into visualization-ready structures with pre-computed visual parameters, making it easy to render graphs, timelines, and network visualizations.
Visualization Types
1. Participant Graph
Your personal journey with creators - a graph showing all your attestations as nodes, connected by shared creator tokens.
- Nodes represent individual attestations
- Edges connect attestations with shared creator tokens
- Visual parameters: size (token amount), position (temporal), weight (relative importance)
2. Temporal Journey
Chronological narrative view - your influence journey over time, showing when you connected with different creators.
- Nodes sorted chronologically
- Focused on temporal narrative
- Perfect for timeline visualizations
3. Creator Topology
Creator-centric view showing influence patterns - who engaged, when, and how deeply.
- Shows unique participants
- Total attestations and token volume
- Activity score (0-100) for engagement level
4. Creator Network
Network-scale map showing how influence flows between creators and holders.
- Edges connect creators with shared participants
- Network strength score (0-100)
- Shows deep engagers (participants with multiple attestations to both creators)
Visual Parameters
The VisualizationAssembler pre-computes visual parameters normalized to 0-100 scales for consistent rendering:
normalizedSize (0-100)Node size based on token amount. Larger purchases = larger nodes.
temporalPosition (0-100)Position on timeline. Maps attestation timestamp to spatial coordinates.
relativeWeight (0-100)Relative importance compared to average. 50 = average, 100 = 2x average.
normalizedWeight (0-100)Edge strength based on shared token holdings.
strengthScore (0-100)Edge strength based on context overlap ratio.
Building Participant Graphs
Get a complete visualization graph for a participant:
import { createRelationalClient } from '@influence-protocol/sdk';
import { JsonRpcProvider } from 'ethers';
const provider = new JsonRpcProvider('https://mainnet.base.org');
// For Base Mainnet, pass custom contract addresses
const client = createRelationalClient(provider, {
visualizationAssembler: '0x...', // Base Mainnet address
relationalResolver: '0xdaabbfc98a09f542f5f3f13694284ca96dd32934',
illuminationAnalyzer: '0x4b2f2a03ed6744df981198fc3c231953179db699',
});
// Get participant graph
const graph = await client.getParticipantGraph('0x...');
// Graph structure:
// {
// nodes: [
// {
// attestationUid: '0x...',
// creatorToken: '0x...',
// attestationTimestamp: 1234567890,
// tokenAmount: 1000000000000000000n,
// normalizedSize: 75, // 0-100
// temporalPosition: 45, // 0-100
// relativeWeight: 80, // 0-100
// // ... other fields
// },
// // ... more nodes
// ],
// edges: [
// {
// fromAttestationUid: '0x...',
// toAttestationUid: '0x...',
// sharedCreatorToken: '0x...',
// normalizedWeight: 60, // 0-100
// strengthScore: 75, // 0-100
// },
// // ... more edges
// ],
// metadata: {
// participant: '0x...',
// totalAttestations: 25,
// uniqueCreators: 8,
// firstAttestationTime: 1234567890,
// lastAttestationTime: 1234567899,
// totalTokens: 50000000000000000000n,
// maxTokenAmount: 10000000000000000000n,
// avgTokenAmount: 2000000000000000000n,
// }
// }Building Temporal Journeys
Get a chronological view of a participant's influence journey:
// Get temporal journey (chronological nodes)
const journey = await client.getParticipantJourney('0x...');
// Returns: VisualNode[] sorted chronologically
// Each node has temporalPosition (0-100) for timeline rendering
// Example rendering:
journey.forEach((node, index) => {
const x = (node.temporalPosition / 100) * canvasWidth;
const y = index * nodeSpacing;
const size = (node.normalizedSize / 100) * maxNodeSize;
// Render node at (x, y) with size
});Building Creator Topology
Get creator-centric visualization data:
// Get creator topology
const topology = await client.getCreatorTopology('0xCreatorToken...');
// Returns:
// {
// node: {
// creatorToken: '0x...',
// totalAttestations: 150,
// uniqueParticipants: 45,
// totalTokenVolume: 1000000000000000000000n,
// firstAttestation: 1234567890,
// lastAttestation: 1234567999,
// activityScore: 85, // 0-100 engagement level
// },
// participants: [
// '0xParticipant1...',
// '0xParticipant2...',
// // ... all unique participants
// ]
// }Building Creator Networks
Build network graphs showing connections between multiple creators:
// Build creator network
const network = await client.getCreatorNetwork([
'0xCreator1...',
'0xCreator2...',
'0xCreator3...',
]);
// Returns: CreatorEdge[]
// [
// {
// creatorA: '0xCreator1...',
// creatorB: '0xCreator2...',
// sharedParticipants: 12,
// deepEngagers: 5, // Participants with multiple attestations to both
// networkStrength: 65, // 0-100
// },
// // ... more edges
// ]
// Render as network graph:
// - Nodes = creators
// - Edges = shared participants
// - Edge thickness = networkStrength
// - Node size = activityScoreRendering Visualizations
The protocol provides visualization-ready data structures. Here's how to render them:
SVG Rendering
function renderParticipantGraph(graph) {
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.setAttribute('width', '800');
svg.setAttribute('height', '600');
// Render edges first (so nodes appear on top)
graph.edges.forEach(edge => {
const fromNode = graph.nodes.find(n => n.attestationUid === edge.fromAttestationUid);
const toNode = graph.nodes.find(n => n.attestationUid === edge.toAttestationUid);
const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
line.setAttribute('x1', fromNode.temporalPosition * 8);
line.setAttribute('y1', fromNode.normalizedSize * 6);
line.setAttribute('x2', toNode.temporalPosition * 8);
line.setAttribute('y2', toNode.normalizedSize * 6);
line.setAttribute('stroke-width', edge.normalizedWeight / 10);
line.setAttribute('stroke', '#F97316');
line.setAttribute('opacity', edge.strengthScore / 100);
svg.appendChild(line);
});
// Render nodes
graph.nodes.forEach(node => {
const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
circle.setAttribute('cx', node.temporalPosition * 8);
circle.setAttribute('cy', node.normalizedSize * 6);
circle.setAttribute('r', node.normalizedSize / 2);
circle.setAttribute('fill', '#F97316');
circle.setAttribute('opacity', node.relativeWeight / 100);
svg.appendChild(circle);
});
return svg;
}Canvas Rendering
function renderOnCanvas(canvas, graph) {
const ctx = canvas.getContext('2d');
const width = canvas.width;
const height = canvas.height;
// Clear canvas
ctx.clearRect(0, 0, width, height);
// Render edges
graph.edges.forEach(edge => {
const fromNode = graph.nodes.find(n => n.attestationUid === edge.fromAttestationUid);
const toNode = graph.nodes.find(n => n.attestationUid === edge.toAttestationUid);
ctx.beginPath();
ctx.moveTo(
(fromNode.temporalPosition / 100) * width,
(fromNode.normalizedSize / 100) * height
);
ctx.lineTo(
(toNode.temporalPosition / 100) * width,
(toNode.normalizedSize / 100) * height
);
ctx.strokeStyle = `rgba(249, 115, 22, ${edge.strengthScore / 100})`;
ctx.lineWidth = edge.normalizedWeight / 10;
ctx.stroke();
});
// Render nodes
graph.nodes.forEach(node => {
const x = (node.temporalPosition / 100) * width;
const y = (node.normalizedSize / 100) * height;
const radius = (node.normalizedSize / 100) * 20;
ctx.beginPath();
ctx.arc(x, y, radius, 0, 2 * Math.PI);
ctx.fillStyle = `rgba(249, 115, 22, ${node.relativeWeight / 100})`;
ctx.fill();
});
}Using Visualization Libraries
You can use popular graph visualization libraries:
- D3.js: Full control, powerful but complex
- vis.js: Network graphs with physics simulation
- Cytoscape.js: Graph theory and layout algorithms
- React Flow: React-based node graphs
// Example with vis.js
import { Network } from 'vis-network';
const nodes = graph.nodes.map(node => ({
id: node.attestationUid,
label: `Creator: ${node.creatorToken.slice(0, 6)}...`,
size: node.normalizedSize,
x: node.temporalPosition * 8,
y: node.normalizedSize * 6,
}));
const edges = graph.edges.map(edge => ({
from: edge.fromAttestationUid,
to: edge.toAttestationUid,
width: edge.normalizedWeight / 10,
color: {
opacity: edge.strengthScore / 100,
},
}));
const data = { nodes, edges };
const network = new Network(container, data, options);Visualization Standards
Normalization
All metrics are normalized to 0-100 scales for consistent rendering across different data ranges.
Temporal Mapping
Timestamps are mapped to spatial coordinates (0-100) based on the time span of attestations.
Visual Weight
Token amounts are mapped to visual weight/size. Larger purchases create more prominent nodes.
Edge Strength
Context overlap is mapped to edge strength. Shared creator tokens create visual connections.
Limits & Constraints
- MAX_GRAPH_NODES: 120 nodes per graph (DoS protection)
- MAX_GRAPH_EDGES: 500 edges maximum (gas limit protection)
- For larger datasets, use time windows or pagination
- All visual parameters are computed on-chain for provability
Agent Notes
- Visual parameters are pre-computed and normalized, making rendering straightforward.
- All values are 0-100 scale, so you can directly map to CSS, SVG, or canvas dimensions.
- Graphs are returned as plain objects—no BigNumber wrappers—so serialization is simple.
- For time-based visualizations, use
temporalPositionfor x-axis placement. - For network graphs, use
normalizedWeightandstrengthScorefor edge styling. - Consider using the warmth scale (#FBBF24 → #F97316 → #EA580C) for visual consistency with RADIANT branding.