This skill generates an interactive infographic visualization using p5.js that read vis-network compatible JSON data...
Generate interactive educational infographics using p5.js that visualize relationships between concepts through nodes and edges. Infographics are data-driven visualizations that read from JSON files in vis-network format, allowing users to hover over elements to see a short definition in a tooltip and detailed information with links below the drawing area.
Infographics transform complex concept relationships into visual, explorable diagrams. Unlike MicroSims which focus on simulation and interaction controls, infographics emphasize information display and exploration through hover interactions. The control region is dedicated exclusively to displaying detailed information about the currently hovered item.
Use this skill when:
data.json fileGather the following information:
Create a JSON file following the vis-network format with metadata, groups, nodes, and edges:
{
"metadata": {
"title": "Topic Overview",
"description": "An interactive infographic showing relationships between concepts",
"author": "Your Name",
"created": "2025-01-09"
},
"groups": {
"foundation": {
"color": "#FF6B6B",
"borderColor": "#C92A2A",
"shape": "ellipse"
},
"intermediate": {
"color": "#4ECDC4",
"borderColor": "#0B7285",
"shape": "box"
}
},
"nodes": [
{
"id": 1,
"label": "Concept Name",
"group": "foundation",
"x": 100,
"y": 100,
"shortDescription": "Brief one-sentence description for tooltip",
"fullDescription": "Detailed paragraph about this concept. Can include <a href='url'>links</a> and formatting."
}
],
"edges": [
{
"from": 1,
"to": 2,
"label": "depends on",
"color": "#999999",
"width": 2
}
]
}
Create the following folder structure in /docs/sims/$INFOGRAPHIC_NAME/:
/docs/sims/$INFOGRAPHIC_NAME/
āāā index.md # Documentation page with iframe
āāā main.html # HTML file with p5.js CDN
āāā $INFOGRAPHIC_NAME.js # p5.js JavaScript code
āāā data.json # Node and edge data
Every infographic must have two regions:
// Canvas dimensions - REQUIRED structure
let canvasWidth = 800; // Initial width (responsive)
let drawHeight = 600; // Drawing area height
let controlHeight = 120; // Detail display area height
let canvasHeight = drawHeight + controlHeight;
let margin = 20; // Margin for visual elements
let defaultTextSize = 16; // Base text size
// Data variables
let infographicData;
let nodes = [];
let edges = [];
let groups = {};
let hoveredNode = null;
let tooltipData = null;
function preload() {
infographicData = loadJSON('data.json');
}
function setup() {
updateCanvasSize();
const canvas = createCanvas(canvasWidth, canvasHeight);
canvas.parent(document.querySelector('main'));
// Parse data
parseData();
describe('Interactive infographic showing concept relationships', LABEL);
}
function draw() {
updateCanvasSize();
// Drawing area background
fill('aliceblue');
rect(0, 0, width, drawHeight);
// Detail display area background
fill('white');
rect(0, drawHeight, width, controlHeight);
// Draw edges first (behind nodes)
drawEdges();
// Draw nodes
drawNodes();
// Draw tooltip if hovering over a node
drawTooltip();
// Draw detail panel for hovered node
drawDetailPanel();
}
function parseData() {
// Parse groups
if (infographicData.groups) {
groups = infographicData.groups;
}
// Parse nodes
nodes = infographicData.nodes.map(n => ({
id: n.id,
label: n.label || '',
x: n.x || 0,
y: n.y || 0,
group: n.group || 'default',
shortDescription: n.shortDescription || '',
fullDescription: n.fullDescription || '',
shape: n.shape || (groups[n.group]?.shape || 'ellipse'),
color: n.color || (groups[n.group]?.color || '#97C2FC'),
borderColor: n.borderColor || (groups[n.group]?.borderColor || '#2B7CE9'),
borderWidth: n.borderWidth || 2,
size: n.size || 40,
icon: n.icon || null
}));
// Parse edges
edges = infographicData.edges.map(e => ({
from: e.from,
to: e.to,
label: e.label || '',
color: e.color || '#848484',
width: e.width || 1,
dashes: e.dashes || false,
smooth: e.smooth || { type: 'continuous', roundness: 0.5 }
}));
}
function drawNodes() {
hoveredNode = null;
for (let node of nodes) {
// Check if mouse is hovering over this node
let d = dist(mouseX, mouseY, node.x, node.y);
let isHovered = d < node.size;
if (isHovered && mouseY < drawHeight) {
hoveredNode = node;
}
// Draw node shape
push();
stroke(node.borderColor);
strokeWeight(isHovered ? node.borderWidth + 1 : node.borderWidth);
fill(node.color);
if (node.shape === 'ellipse' || node.shape === 'circle') {
ellipse(node.x, node.y, node.size * 2);
} else if (node.shape === 'box' || node.shape === 'square') {
rectMode(CENTER);
rect(node.x, node.y, node.size * 1.8, node.size * 1.8);
} else if (node.shape === 'diamond') {
drawDiamond(node.x, node.y, node.size);
}
// Draw icon if available
if (node.icon) {
// Icon drawing code here
}
pop();
// Draw label
fill(0);
noStroke();
textAlign(CENTER, CENTER);
textSize(14);
text(node.label, node.x, node.y + node.size + 15);
}
}
function drawDiamond(x, y, size) {
beginShape();
vertex(x, y - size); // top
vertex(x + size, y); // right
vertex(x, y + size); // bottom
vertex(x - size, y); // left
endShape(CLOSE);
}
function drawEdges() {
for (let edge of edges) {
let fromNode = nodes.find(n => n.id === edge.from);
let toNode = nodes.find(n => n.id === edge.to);
if (!fromNode || !toNode) continue;
push();
stroke(edge.color);
strokeWeight(edge.width);
if (edge.dashes) {
drawingContext.setLineDash([5, 5]);
}
// Draw curved or straight line based on smooth parameter
if (edge.smooth && edge.smooth.type === 'curvedCW') {
// Draw curved line clockwise
noFill();
let controlX = (fromNode.x + toNode.x) / 2 + 50;
let controlY = (fromNode.y + toNode.y) / 2;
bezier(fromNode.x, fromNode.y, controlX, controlY,
controlX, controlY, toNode.x, toNode.y);
} else {
// Draw straight line
line(fromNode.x, fromNode.y, toNode.x, toNode.y);
}
drawingContext.setLineDash([]);
pop();
// Draw edge label if exists
if (edge.label) {
let midX = (fromNode.x + toNode.x) / 2;
let midY = (fromNode.y + toNode.y) / 2;
fill(100);
noStroke();
textAlign(CENTER, CENTER);
textSize(12);
text(edge.label, midX, midY);
}
}
}
Tooltips must always remain visible within the drawing area, even when hovering near edges:
function drawTooltip() {
if (!hoveredNode || mouseY >= drawHeight) return;
let tooltipText = hoveredNode.shortDescription;
if (!tooltipText) return;
// Measure tooltip dimensions
textSize(14);
let tooltipWidth = textWidth(tooltipText) + 20;
let tooltipHeight = 30;
let padding = 10;
// Position tooltip near mouse, but keep within drawing bounds
let tooltipX = mouseX + 15;
let tooltipY = mouseY - 20;
// Adjust if tooltip would go off right edge
if (tooltipX + tooltipWidth > width - padding) {
tooltipX = mouseX - tooltipWidth - 15;
}
// Adjust if tooltip would go off left edge
if (tooltipX < padding) {
tooltipX = padding;
}
// Adjust if tooltip would go off top edge
if (tooltipY < padding) {
tooltipY = mouseY + 20;
}
// Adjust if tooltip would go off bottom of drawing area
if (tooltipY + tooltipHeight > drawHeight - padding) {
tooltipY = drawHeight - tooltipHeight - padding;
}
// Draw tooltip background
push();
fill(255, 255, 220);
stroke(150);
strokeWeight(1);
rect(tooltipX, tooltipY, tooltipWidth, tooltipHeight, 5);
// Draw tooltip text
fill(0);
noStroke();
textAlign(LEFT, CENTER);
textSize(14);
text(tooltipText, tooltipX + 10, tooltipY + tooltipHeight / 2);
pop();
}
The control region exclusively displays the full description of the hovered item:
function drawDetailPanel() {
push();
fill(0);
noStroke();
textAlign(LEFT, TOP);
textSize(defaultTextSize);
let panelX = margin;
let panelY = drawHeight + 10;
let panelWidth = width - 2 * margin;
if (hoveredNode) {
// Display node label as header
textSize(18);
textStyle(BOLD);
text(hoveredNode.label, panelX, panelY);
// Display full description
textSize(defaultTextSize);
textStyle(NORMAL);
// Note: p5.js doesn't render HTML, so strip tags for display
let displayText = hoveredNode.fullDescription.replace(/<[^>]*>/g, '');
// Wrap text to fit panel width
let words = displayText.split(' ');
let line = '';
let y = panelY + 25;
for (let word of words) {
let testLine = line + word + ' ';
if (textWidth(testLine) > panelWidth && line.length > 0) {
text(line, panelX, y);
line = word + ' ';
y += 20;
} else {
line = testLine;
}
}
text(line, panelX, y);
} else {
// Default message when nothing is hovered
textStyle(ITALIC);
fill(100);
text('Hover over a concept to see details...', panelX, panelY);
}
pop();
}
function windowResized() {
updateCanvasSize();
resizeCanvas(canvasWidth, canvasHeight);
}
function updateCanvasSize() {
const container = document.querySelector('main');
if (container) {
canvasWidth = container.offsetWidth;
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Infographic Title</title>
<script src="https://cdn.jsdelivr.net/npm/p5@1.11.10/lib/p5.js"></script>
<style>
body {
margin: 0px;
padding: 0px;
font-family: Arial, Helvetica, sans-serif;
}
</style>
<script src="infographic-name.js"></script>
</head>
<body>
<main></main>
<br/>
<a href=".">Back to Documentation</a>
</body>
</html>
---
title: Infographic Title
description: Brief description of the infographic content
---
# Infographic Title
<iframe src="main.html" height="722px" scrolling="no"></iframe>
[View Fullscreen](./main.html){ .md-button .md-button--primary }
## Description
[Description of what this infographic visualizes]
## How to Use
1. **Hover** over any concept to see a brief description
2. **Read** the detailed information in the panel below the diagram
3. **Explore** the relationships shown by the connecting lines
## Legend
[Explanation of colors, shapes, and edge types]
Each node in the nodes array supports these properties:
Each edge in the edges array supports these properties:
Groups define default styling for node categories:
Every infographic must meet these criteria:
After generating the infographic files:
/docs/sims/$INFOGRAPHIC_NAME/mkdocs.yml navigation to include the new infographicmkdocs servemkdocs gh-deployShow relationships between educational concepts with hierarchical or network structures.
Display historical events or process steps as connected nodes.
Visualize components and their relationships in complex systems.
Create explorable networks of related topics or ideas.
This skill uses vis-network compatible data formats. For detailed parameter documentation, see references/vis-network-parameters.md.
Template files are available in assets/ for quick start:
assets/template-main.htmlassets/template-infographic.jsassets/template-data.json