Data Visualisation
Load JSON and CSV data, build bar charts, scatter plots, and network graphs, and animate data transitions.
Loading data
p5.js provides loadJSON() and loadTable() for the most common data formats. Always load in preload():
let data;
function preload() {
data = loadJSON('data.json');
// or:
data = loadTable('data.csv', 'csv', 'header');
}
JSON structure
{
"cities": [
{ "name": "London", "population": 9648110, "lat": 51.5, "lon": -0.12 },
{ "name": "Paris", "population": 2161000, "lat": 48.86, "lon": 2.35 },
{ "name": "Berlin", "population": 3664088, "lat": 52.52, "lon": 13.40 }
]
}
function draw() {
let cities = data.cities;
for (let city of cities) {
print(city.name, city.population);
}
}
CSV with loadTable()
// data.csv:
// name,value,category
// Alpha,42,A
// Beta,17,B
let table;
function preload() {
table = loadTable('data.csv', 'csv', 'header');
}
function draw() {
for (let row of table.rows) {
let name = row.getString('name');
let value = row.getNum('value');
print(name, value);
}
}
Bar chart
let data;
let animProgress = 0;
function preload() {
data = loadJSON('monthly.json');
}
function setup() {
createCanvas(700, 450);
}
function draw() {
background(20);
animProgress = min(animProgress + 0.03, 1);
let values = data.months;
let maxVal = max(values.map(d => d.value));
let barW = (width - 100) / values.length;
let chartH = height - 80;
let eased = easeOutCubic(animProgress);
for (let i = 0; i < values.length; i++) {
let d = values[i];
let barH = map(d.value, 0, maxVal, 0, chartH) * eased;
let x = 50 + i * barW;
let y = height - 40 - barH;
// Bar
colorMode(HSB, 360, 100, 100);
fill(map(i, 0, values.length, 200, 280), 70, 90);
noStroke();
rect(x + 2, y, barW - 4, barH, 3, 3, 0, 0);
// Label
colorMode(RGB);
fill(200);
textSize(11);
textAlign(CENTER);
text(d.label, x + barW / 2, height - 22);
// Value
if (animProgress > 0.9) {
fill(255);
textSize(10);
text(d.value, x + barW / 2, y - 4);
}
}
}
function easeOutCubic(t) {
return 1 - pow(1 - t, 3);
}
Scatter plot
function drawScatterPlot(records, xKey, yKey) {
let xs = records.map(r => r[xKey]);
let ys = records.map(r => r[yKey]);
let pad = 60;
let xMin = min(xs), xMax = max(xs);
let yMin = min(ys), yMax = max(ys);
// Axes
stroke(80);
strokeWeight(1);
line(pad, pad, pad, height - pad); // y axis
line(pad, height - pad, width - pad, height - pad); // x axis
// Points
noStroke();
for (let r of records) {
let x = map(r[xKey], xMin, xMax, pad, width - pad);
let y = map(r[yKey], yMin, yMax, height - pad, pad);
let hovering = dist(mouseX, mouseY, x, y) < 8;
fill(hovering ? color(255, 200, 50) : color(100, 180, 255, 180));
circle(x, y, hovering ? 14 : 8);
if (hovering) {
fill(255);
textSize(11);
textAlign(LEFT);
text(`${r.name}\n${xKey}: ${r[xKey]}\n${yKey}: ${r[yKey]}`, x + 10, y - 10);
}
}
}
Animated transitions
When data updates, lerp between old and new values:
let displayed = []; // current display values (lerped)
let target = []; // target values from data
function updateData(newValues) {
target = newValues;
if (displayed.length === 0) displayed = [...newValues];
}
function draw() {
// Lerp displayed values toward target
for (let i = 0; i < target.length; i++) {
displayed[i] = lerp(displayed[i] || 0, target[i], 0.08);
}
// Draw using displayed values
}
Network graph (force-directed)
let nodes = [], edges = [];
function initGraph(graphData) {
nodes = graphData.nodes.map(n => ({
...n,
x: random(100, width - 100),
y: random(100, height - 100),
vx: 0, vy: 0
}));
edges = graphData.edges;
}
function layoutStep() {
// Repulsion between all node pairs
for (let i = 0; i < nodes.length; i++) {
for (let j = i + 1; j < nodes.length; j++) {
let dx = nodes[j].x - nodes[i].x;
let dy = nodes[j].y - nodes[i].y;
let dist = Math.sqrt(dx * dx + dy * dy) || 1;
let f = 3000 / (dist * dist);
nodes[i].vx -= dx / dist * f;
nodes[i].vy -= dy / dist * f;
nodes[j].vx += dx / dist * f;
nodes[j].vy += dy / dist * f;
}
}
// Spring attraction along edges
for (let e of edges) {
let a = nodes[e.source], b = nodes[e.target];
let dx = b.x - a.x, dy = b.y - a.y;
let d = Math.sqrt(dx * dx + dy * dy) || 1;
let f = (d - 100) * 0.01;
a.vx += dx / d * f; a.vy += dy / d * f;
b.vx -= dx / d * f; b.vy -= dy / d * f;
}
// Apply velocity with damping
for (let n of nodes) {
n.x = constrain(n.x + n.vx, 20, width - 20);
n.y = constrain(n.y + n.vy, 20, height - 20);
n.vx *= 0.85;
n.vy *= 0.85;
}
}
Key takeaways
loadJSON()andloadTable()must be called inpreload()- Map data values to visual properties with
map(value, dataMin, dataMax, visualMin, visualMax) - Animated entrances (lerp toward target) make visualisations feel polished
- Hover detection (
dist()from mouse) adds interactivity with minimal code - Force-directed layouts for graphs: repel all nodes, attract connected pairs
- Compute
min()andmax()of your data before drawing to set the scale correctly