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 = activityScore

Rendering 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 temporalPosition for x-axis placement.
  • For network graphs, use normalizedWeight and strengthScore for edge styling.
  • Consider using the warmth scale (#FBBF24 → #F97316 → #EA580C) for visual consistency with RADIANT branding.

Related Guides