Where ideas percolate and thoughts brew

The Friendship Transaction

About This Sketch

A network visualization exploring the uncomfortable reality that all friendships involve value exchange. Nodes represent people, connections show relationships, and colors indicate balance—bright for healthy reciprocity, muted for imbalances. Arrows point toward whoever's receiving more. Watch as relationships drift and occasionally rebalance, making visible the usually invisible accounting we all do in social bonds.

Algorithm

This sketch visualizes friendship as a network of value exchanges between people. Each node represents a person, and the connections between them represent friendships with bidirectional value flows. The sketch tracks how much value each person gives and receives in each relationship, calculating a "balance" for each connection. Connections are color-coded based on balance: bright color for balanced trades (both parties getting roughly equal value), and muted colors for imbalanced trades (one party giving more than receiving). Arrows on the connections point toward whoever is receiving more value, making the asymmetry visible. The nodes drift gently to create organic movement, and the relationships occasionally rebalance over time to simulate how friendships evolve. This visualization makes explicit the usually invisible accounting we all do in relationships—showing that healthy friendships (balanced connections) can coexist with various forms of imbalanced exchanges.

Pseudocode

SETUP:
  Create 8 friend nodes positioned in a loose circle
  For each node, create 2-3 random connections to other nodes
  Assign random "give" and "receive" values to each connection

EACH FRAME:
  Clear background
  For each node:
    Apply gentle random drift
    Calculate balance for each connection (received - given)
    Draw connections with color based on balance:
      - Balanced (|balance| < 0.3): accent1 (healthy)
      - Receiving more (balance > 0): accent3 (taking)
      - Giving more (balance < 0): accent2 (giving)
    Draw arrow pointing toward whoever receives more
    Draw the node itself

  Every 200 frames:
    Randomly adjust one connection's give/receive values
    (Simulates relationship rebalancing over time)

Source Code

let sketch = function(p) {
    let nodes = [];
    let numNodes = 8;

    class FriendNode {
        constructor(x, y, id) {
            this.x = x;
            this.y = y;
            this.id = id;
            this.vx = 0;
            this.vy = 0;
            this.connections = [];
            this.valueGiven = {};
            this.valueReceived = {};
            this.size = 12;
        }

        addConnection(otherNode, giveAmount, receiveAmount) {
            this.connections.push({
                node: otherNode,
                give: giveAmount,
                receive: receiveAmount
            });
            this.valueGiven[otherNode.id] = giveAmount;
            this.valueReceived[otherNode.id] = receiveAmount;
        }

        getBalance(otherNode) {
            let given = this.valueGiven[otherNode.id] || 0;
            let received = this.valueReceived[otherNode.id] || 0;
            return received - given; // Positive means receiving more
        }

        update() {
            // Gentle drift
            this.vx += p.random(-0.1, 0.1);
            this.vy += p.random(-0.1, 0.1);

            // Damping
            this.vx *= 0.95;
            this.vy *= 0.95;

            this.x += this.vx;
            this.y += this.vy;

            // Constrain to canvas
            this.x = p.constrain(this.x, 40, 360);
            this.y = p.constrain(this.y, 40, 260);
        }

        display(colors) {
            // Draw connections first (so nodes appear on top)
            for (let conn of this.connections) {
                let balance = this.getBalance(conn.node);
                let strokeColor;
                let strokeAlpha;

                if (p.abs(balance) < 0.3) {
                    // Balanced trade - healthy green-ish (accent1)
                    strokeColor = colors.accent1;
                    strokeAlpha = 180;
                } else if (balance > 0) {
                    // Receiving more - yellowish (accent3)
                    strokeColor = colors.accent3;
                    strokeAlpha = 120;
                } else {
                    // Giving more - reddish (accent2)
                    strokeColor = colors.accent2;
                    strokeAlpha = 120;
                }

                p.stroke(...strokeColor, strokeAlpha);
                p.strokeWeight(p.map(p.abs(balance), 0, 2, 1, 3));

                // Draw arrow to show direction of flow
                let midX = (this.x + conn.node.x) / 2;
                let midY = (this.y + conn.node.y) / 2;

                p.line(this.x, this.y, conn.node.x, conn.node.y);

                // Draw small arrow indicating flow direction
                let angle = p.atan2(conn.node.y - this.y, conn.node.x - this.x);
                let arrowSize = 6;
                if (balance !== 0) {
                    // Arrow points toward whoever is receiving more
                    if (balance > 0) {
                        // This node receiving more, arrow points to it
                        angle += p.PI;
                    }
                    p.push();
                    p.translate(midX, midY);
                    p.rotate(angle);
                    p.fill(...strokeColor, strokeAlpha);
                    p.noStroke();
                    p.triangle(-arrowSize, -arrowSize/2, -arrowSize, arrowSize/2, 0, 0);
                    p.pop();
                }
            }

            // Draw node
            p.fill(...colors.accent1);
            p.noStroke();
            p.circle(this.x, this.y, this.size);

            // Inner circle
            p.fill(...colors.accent2);
            p.circle(this.x, this.y, this.size * 0.5);
        }
    }

    p.setup = function() {
        p.createCanvas(400, 300);

        // Create nodes in a loose circle
        for (let i = 0; i < numNodes; i++) {
            let angle = (i / numNodes) * p.TWO_PI;
            let radius = 80;
            let x = 200 + p.cos(angle) * radius + p.random(-20, 20);
            let y = 150 + p.sin(angle) * radius + p.random(-20, 20);
            nodes.push(new FriendNode(x, y, i));
        }

        // Create connections with varying balance
        for (let i = 0; i < nodes.length; i++) {
            // Each node connects to 2-3 others
            let numConnections = p.floor(p.random(2, 4));
            for (let j = 0; j < numConnections; j++) {
                let otherIndex = p.floor(p.random(nodes.length));
                if (otherIndex !== i) {
                    let giveAmount = p.random(0.5, 2);
                    let receiveAmount = p.random(0.5, 2);
                    nodes[i].addConnection(nodes[otherIndex], giveAmount, receiveAmount);
                }
            }
        }
    };

    p.draw = function() {
        const colors = getThemeColors();
        p.background(...colors.bg);

        // Update and display all nodes
        for (let node of nodes) {
            node.update();
            node.display(colors);
        }

        // Draw title
        p.fill(...colors.accent3);
        p.noStroke();
        p.textAlign(p.CENTER);
        p.textSize(11);
        p.text('The Friendship Network: Value Flows', 200, 20);

        // Draw legend
        p.textAlign(p.LEFT);
        p.textSize(8);

        p.stroke(...colors.accent1, 180);
        p.strokeWeight(2);
        p.line(15, 40, 35, 40);
        p.noStroke();
        p.fill(...colors.accent3);
        p.text('Balanced', 40, 42);

        p.stroke(...colors.accent3, 120);
        p.strokeWeight(2);
        p.line(15, 52, 35, 52);
        p.noStroke();
        p.text('Receiving more', 40, 54);

        p.stroke(...colors.accent2, 120);
        p.strokeWeight(2);
        p.line(15, 64, 35, 64);
        p.noStroke();
        p.text('Giving more', 40, 66);

        // Occasional rebalancing animation
        if (p.frameCount % 200 === 0) {
            // Randomly adjust some connections
            let node = p.random(nodes);
            if (node.connections.length > 0) {
                let conn = p.random(node.connections);
                conn.give += p.random(-0.2, 0.2);
                conn.receive += p.random(-0.2, 0.2);
            }
        }
    };
};