The Problem with Empathy
About This Sketch
A generative visualization contrasting empathy-driven and compassion-driven resource allocation. Small agents move between targets representing people or causes in need. Bright circles are emotionally salient (compelling stories, photogenic victims). Faded circles have low emotional appeal but high actual need. Watch how empathy mode clusters resources around visible targets while ignoring greater needs, versus compassion mode which allocates based on actual impact.
Algorithm
This sketch visualizes the difference between empathy-driven and compassion-driven resource allocation.
Small moving dots represent helpers/agents who can provide assistance. Larger circles represent people or causes in need. The bright, visible circles represent emotionally salient targets (photogenic victims, compelling stories). The faded circles represent targets with low emotional appeal but high actual need.
The visualization alternates between two modes every 5 seconds:
**Empathy Mode**: Agents are attracted only to emotionally salient targets (bright circles), regardless of actual need. Watch how resources cluster around the visible, compelling cases while high-need but non-emotional targets (faded circles) get ignored.
**Compassion Mode**: Agents distribute themselves based on actual need, prioritizing the most needy targets first regardless of emotional salience. Resources are allocated more fairly and effectively.
The fill inside each target circle grows as it receives help, allowing you to see the dramatic difference in outcomes between empathy-driven and compassion-driven allocation. This accompanies the blog post "The Problem with Empathy" which argues that empathy, while valuable for connection, is a flawed tool for moral decision-making and resource allocation.
Pseudocode
SETUP:
Create 15 helper agents at random positions
Create needy targets with varying combinations of:
- Emotional salience (high = visible/bright, low = faded)
- Actual need (represented by circle size)
DRAW (every frame):
Get current theme colors for rendering
IF time to switch modes (every 300 frames):
Toggle all agents between empathy/compassion mode
FOR each target:
Draw circle sized by actual need
Use bright color if emotionally salient
Use faded color if not emotionally salient
Show progress fill based on help received
FOR each agent:
IF in empathy mode:
Find closest emotionally salient target
Move toward it
ELSE (compassion mode):
Find target with highest unmet need
Move toward it
Apply velocity with damping
Check if close enough to help any target
If yes, increment that target's help counter
Draw agent (colored by mode)
Display current mode indicator
Display legend explaining circle meanings
Source Code
let sketch = function(p) {
let agents = [];
let needyTargets = [];
class Agent {
constructor() {
this.x = p.random(p.width);
this.y = p.random(p.height);
this.vx = 0;
this.vy = 0;
this.empathyMode = true;
}
update() {
const colors = getThemeColors();
if (this.empathyMode) {
// Empathy mode: attracted to emotionally salient target
let closest = null;
let closestDist = Infinity;
for (let target of needyTargets) {
if (target.emotional) {
let d = p.dist(this.x, this.y, target.x, target.y);
if (d < closestDist) {
closestDist = d;
closest = target;
}
}
}
if (closest) {
let angle = p.atan2(closest.y - this.y, closest.x - this.x);
this.vx += p.cos(angle) * 0.15;
this.vy += p.sin(angle) * 0.15;
}
} else {
// Compassion mode: distributes evenly to all targets
let mostNeedy = null;
let highestNeed = -1;
for (let target of needyTargets) {
if (target.actualNeed > highestNeed && target.helped < 1) {
highestNeed = target.actualNeed;
mostNeedy = target;
}
}
if (mostNeedy) {
let angle = p.atan2(mostNeedy.y - this.y, mostNeedy.x - this.x);
this.vx += p.cos(angle) * 0.15;
this.vy += p.sin(angle) * 0.15;
}
}
// Damping
this.vx *= 0.95;
this.vy *= 0.95;
this.x += this.vx;
this.y += this.vy;
// Bounds
this.x = p.constrain(this.x, 5, p.width - 5);
this.y = p.constrain(this.y, 5, p.height - 5);
// Check if helping any target
for (let target of needyTargets) {
let d = p.dist(this.x, this.y, target.x, target.y);
if (d < 15) {
target.helped += 0.02;
}
}
}
draw() {
const colors = getThemeColors();
if (this.empathyMode) {
p.fill(...colors.accent2, 200);
} else {
p.fill(...colors.accent1, 200);
}
p.noStroke();
p.circle(this.x, this.y, 8);
}
}
class NeedyTarget {
constructor(emotional, actualNeed) {
this.x = p.random(50, p.width - 50);
this.y = p.random(50, p.height - 50);
this.emotional = emotional; // How emotionally salient
this.actualNeed = actualNeed; // How much they actually need help
this.helped = 0;
}
draw() {
const colors = getThemeColors();
// Size based on actual need
let size = 15 + this.actualNeed * 15;
// Color based on how much help received vs need
let helpRatio = this.helped / this.actualNeed;
if (this.emotional) {
// Emotional targets: bright and visible
p.fill(...colors.accent2, 150);
p.stroke(...colors.accent2);
} else {
// Non-emotional targets: faded and less visible
p.fill(...colors.accent3, 100);
p.stroke(...colors.accent3, 150);
}
p.strokeWeight(2);
p.circle(this.x, this.y, size);
// Show help progress
if (this.helped > 0) {
p.fill(...colors.accent1, 150);
p.noStroke();
let progressSize = size * p.min(helpRatio, 1);
p.circle(this.x, this.y, progressSize);
}
}
}
p.setup = function() {
p.createCanvas(400, 300);
p.colorMode(p.RGB);
// Create agents (helpers)
for (let i = 0; i < 15; i++) {
agents.push(new Agent());
}
// Create needy targets
// Emotional but low need
needyTargets.push(new NeedyTarget(true, 0.3));
needyTargets.push(new NeedyTarget(true, 0.4));
// Non-emotional but high need
needyTargets.push(new NeedyTarget(false, 0.9));
needyTargets.push(new NeedyTarget(false, 0.8));
needyTargets.push(new NeedyTarget(false, 0.7));
// Mixed
needyTargets.push(new NeedyTarget(false, 0.5));
};
p.draw = function() {
const colors = getThemeColors();
p.background(...colors.bg);
// Switch modes periodically
if (p.frameCount % 300 === 0) {
for (let agent of agents) {
agent.empathyMode = !agent.empathyMode;
}
}
// Draw targets
for (let target of needyTargets) {
target.draw();
}
// Update and draw agents
for (let agent of agents) {
agent.update();
agent.draw();
}
// Draw mode indicator
p.fill(...colors.accent3);
p.noStroke();
p.textAlign(p.LEFT, p.TOP);
p.textSize(11);
let modeText = agents[0].empathyMode ? "Empathy Mode" : "Compassion Mode";
p.text(modeText, 10, 10);
// Legend
p.textSize(9);
p.text("Bright circles: Emotionally salient", 10, 270);
p.text("Faded circles: Low emotional appeal, high need", 10, 283);
};
};