show me a directed graph layout with d3 in a self-ctoaned html exmaple
Here's a complete, self-contained HTML example that creates a directed graph using D3.js:
<!DOCTYPE html>
<html>
<head>
<title>D3 Directed Graph</title>
<script src="https://d3js.org/d3.v7.min.js"></script>
<style>
.links line {
stroke: #999;
stroke-opacity: 0.6;
stroke-width: 2;
marker-end: url(#arrowhead);
}
.nodes circle {
stroke: #fff;
stroke-width: 1.5px;
}
.node-label {
font-family: Arial;
font-size: 12px;
}
</style>
</head>
<body>
<svg width="600" height="400"></svg>
<script>
// Sample data
const data = {
nodes: [
{ id: 1, name: "Node 1" },
{ id: 2, name: "Node 2" },
{ id: 3, name: "Node 3" },
{ id: 4, name: "Node 4" },
{ id: 5, name: "Node 5" }
],
links: [
{ source: 1, target: 2 },
{ source: 2, target: 3 },
{ source: 3, target: 4 },
{ source: 4, target: 5 },
{ source: 1, target: 5 },
{ source: 2, target: 4 }
]
};
// Set up the SVG
const svg = d3.select("svg");
const width = +svg.attr("width");
const height = +svg.attr("height");
// Define arrow marker
svg.append("defs").append("marker")
.attr("id", "arrowhead")
.attr("viewBox", "0 -5 10 10")
.attr("refX", 15)
.attr("refY", 0)
.attr("markerWidth", 8)
.attr("markerHeight", 8)
.attr("orient", "auto")
.append("path")
.attr("d", "M0,-5L10,0L0,5")
.attr("fill", "#999");
// Create the force simulation
const simulation = d3.forceSimulation(data.nodes)
.force("link", d3.forceLink(data.links).id(d => d.id).distance(100))
.force("charge", d3.forceManyBody().strength(-200))
.force("center", d3.forceCenter(width / 2, height / 2));
// Create the links
const link = svg.append("g")
.attr("class", "links")
.selectAll("line")
.data(data.links)
.join("line");
// Create the nodes
const node = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(data.nodes)
.join("circle")
.attr("r", 10)
.attr("fill", d => d3.schemeCategory10[d.id % 10]);
// Add labels to nodes
const labels = svg.append("g")
.attr("class", "node-labels")
.selectAll("text")
.data(data.nodes)
.join("text")
.attr("class", "node-label")
.text(d => d.name)
.attr("dx", 12)
.attr("dy", 4);
// Add drag behavior
node.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
// Update positions on each tick
simulation.on("tick", () => {
link
.attr("x1", d => d.source.x)
.attr("y1", d => d.source.y)
.attr("x2", d => d.target.x)
.attr("y2", d => d.target.y);
node
.attr("cx", d => d.x)
.attr("cy", d => d.y);
labels
.attr("x", d => d.x)
.attr("y", d => d.y);
});
// Drag functions
function dragstarted(event, d) {
if (!event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(event, d) {
d.fx = event.x;
d.fy = event.y;
}
function dragended(event, d) {
if (!event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
</script>
</body>
</html>
This example creates a directed graph with the following features:
- Five nodes connected by directed edges
- Arrows showing the direction of relationships
- Draggable nodes
- Node labels
- Force-directed layout
- Colored nodes
- Smooth animations
The graph is interactive - you can:
- Drag nodes around to rearrange the layout
- The force simulation will automatically adjust other nodes and links
- Nodes will return to a force-balanced position when released
The graph uses:
- D3.js force simulation for layout
- SVG for rendering
- CSS for styling
- Arrow markers for directed edges
- Force-directed layout with link distance and charge forces
You can modify the data
object to add more nodes and links or adjust the visual properties by modifying the CSS and D3 attributes.
To use this example, just save it as an HTML file and open it in a web browser. Make sure you have an internet connection as it loads D3.js from a CDN.
Examine the structure of this file and then update the display to visualize how one scene links to another:
Title: UCSC Animal Crossing
Start: Baskin
Scenes:
Baskin:
ImageUrl: https://cdn.glitch.global/21220f1c-83fb-496f-8048-d11cdac5ba60/IMG_0608.jpeg?v=1736887071471
Caption: It's a nice day. Maybe I should leave Baskin for a change.
Choices:
- Text: Walk towards McHenry Library
Target: To_McHenry
- Text: Walk towards Cowell Bus Stop
Target: Cowell
- Text: Walk towards East Field
Target: Parking_Lot_Turkeys
To_McHenry:
ImageUrl: https://cdn.glitch.global/21220f1c-83fb-496f-8048-d11cdac5ba60/IMG_3724.jpeg?v=1736886595142
Caption: I love the smell of books. Lets keep walking!
Choices:
- Text: HUH? Whats that!?
Target: Squirrel
- Text: Too much nature... lets go back to Baskin
Target: Baskin
Squirrel:
ImageUrl: https://cdn.glitch.global/21220f1c-83fb-496f-8048-d11cdac5ba60/IMG_6398.jpeg?v=1736886665213
Caption: Hey I jus— SQUIRREL
Choices:
- Text: Okay that was exciting, oh wait look down!
Target: McHenry_Slug
- Text: I've touched all the grass I can handle. Take me back the dungeon!
Target: Baskin
McHenry_Slug:
ImageUrl: https://cdn.glitch.global/21220f1c-83fb-496f-8048-d11cdac5ba60/IMG_3729.jpeg?v=1736886571773
Caption: I dare you to lick it.
Choices:
- Text: I will lick...
Target: El_Hospital
- Text: No thank you<3
Target: McHenry
McHenry:
ImageUrl: https://cdn.glitch.global/21220f1c-83fb-496f-8048-d11cdac5ba60/IMG_2034.JPG?v=1736888396630
Caption: You have arrived at McHenry! That was a close call.
Choices: [] # McHenry ending
El_Hospital:
ImageUrl: https://cdn.glitch.global/21220f1c-83fb-496f-8048-d11cdac5ba60/photo-1648224394432-8830fec15349.jpg?v=1736888206317
Caption: My bad sorry.
Choices: [] # Hospital ending
Cowell:
ImageUrl: https://cdn.glitch.global/21220f1c-83fb-496f-8048-d11cdac5ba60/IMG_5625.jpeg?v=1736886699130
Caption: Looks like your bus isn't coming for a while...
Choices:
- Text: Who's crossing the street?
Target: Cowell_Turkeys
- Text: Walk towards East Field
Target: East_Field
Cowell_Turkeys:
ImageUrl: https://cdn.glitch.global/21220f1c-83fb-496f-8048-d11cdac5ba60/IMG_4840.jpeg?v=1736886678017
Caption: These new Loop buses look strange. I don't think they're stopping for me.
Choices:
- Text: Keep waiting for the bus, I have faith
Target: Cowell
- Text: I give up, I want to go back to Baskin
Target: Baskin
Parking_Lot_Turkeys:
ImageUrl: https://cdn.glitch.global/21220f1c-83fb-496f-8048-d11cdac5ba60/taturkeys.jpg?v=1736886773539
Caption: Weird-looking cars. They definitely don't understand right of way.
Choices:
- Text: Get away
Target: East_Field
East_Field:
ImageUrl: https://cdn.glitch.global/21220f1c-83fb-496f-8048-d11cdac5ba60/IMG_1463.jpg?v=1736888784697
Caption: It's such a beautiful day! For now...
Choices:
- Text: What's on the field?
Target: Deer
- Text: Keep walking along the path
Target: Path
Deer:
ImageUrl: https://cdn.glitch.global/21220f1c-83fb-496f-8048-d11cdac5ba60/IMG_0731.jpeg?v=1736886587410
Caption: You do belong here, little deer.
Choices:
- Text: Head back to the path
Target: East_Field
Path:
ImageUrl: https://cdn.glitch.global/21220f1c-83fb-496f-8048-d11cdac5ba60/IMG_9282.jpeg?v=1736886629411
Caption: I've got a long walk ahead of me. This 'touching grass' business is hard work.
Choices:
- Text: What's that on the pole?
Target: Hawk
- Text: Forge ahead
Target: Cow
Hawk:
ImageUrl: https://cdn.glitch.global/21220f1c-83fb-496f-8048-d11cdac5ba60/IMG_9280.jpeg?v=1736887026501
Caption: Hello there, hawk!
Choices:
- Text: I want to get a little closer...
Target: Hawk_Close
- Text: Let's retreat
Target: Path
Hawk_Close:
ImageUrl: https://cdn.glitch.global/21220f1c-83fb-496f-8048-d11cdac5ba60/IMG_6571.jpeg?v=1736886656612
Caption: Awesome... but maybe it doesn't want to be disturbed.
Choices:
- Text: Retreat
Target: Path
Cow:
ImageUrl: https://cdn.glitch.global/21220f1c-83fb-496f-8048-d11cdac5ba60/IMG_4760.jpeg?v=1736886595805
Caption: Whoa there! Who's this handsome devil?
Choices:
- Text: Pet the cow!
Target: Cow_Pet
- Text: Pet the cow!
Target: Cow_Pet
- Text: Pet the cow!
Target: Cow_Pet
Cow_Pet:
ImageUrl: https://cdn.glitch.global/21220f1c-83fb-496f-8048-d11cdac5ba60/IMG_4758.jpeg?v=1736886564882
Caption: You feel fulfilled.
Choices: [] # Cow ending
I'll help you create a directed graph visualization of this scene structure. Here's a modified version of the previous D3 graph that represents your game's scenes and connections:
<!DOCTYPE html>
<html>
<head>
<title>UCSC Animal Crossing Scene Flow</title>
<script src="https://d3js.org/d3.v7.min.js"></script>
<style>
.links line {
stroke: #999;
stroke-opacity: 0.6;
stroke-width: 1.5;
marker-end: url(#arrowhead);
}
.nodes circle {
stroke: #fff;
stroke-width: 1.5px;
}
.node-label {
font-family: Arial;
font-size: 10px;
pointer-events: none;
}
</style>
</head>
<body>
<svg width="1200" height="800"></svg>
<script>
// Convert your scene data into nodes and links
const scenes = {
nodes: [
{ id: "Baskin", group: 1 },
{ id: "To_McHenry", group: 1 },
{ id: "Squirrel", group: 1 },
{ id: "McHenry_Slug", group: 1 },
{ id: "McHenry", group: 2 }, // ending
{ id: "El_Hospital", group: 2 }, // ending
{ id: "Cowell", group: 1 },
{ id: "Cowell_Turkeys", group: 1 },
{ id: "Parking_Lot_Turkeys", group: 1 },
{ id: "East_Field", group: 1 },
{ id: "Deer", group: 1 },
{ id: "Path", group: 1 },
{ id: "Hawk", group: 1 },
{ id: "Hawk_Close", group: 1 },
{ id: "Cow", group: 1 },
{ id: "Cow_Pet", group: 2 } // ending
],
links: [
{ source: "Baskin", target: "To_McHenry" },
{ source: "Baskin", target: "Cowell" },
{ source: "Baskin", target: "Parking_Lot_Turkeys" },
{ source: "To_McHenry", target: "Squirrel" },
{ source: "To_McHenry", target: "Baskin" },
{ source: "Squirrel", target: "McHenry_Slug" },
{ source: "Squirrel", target: "Baskin" },
{ source: "McHenry_Slug", target: "El_Hospital" },
{ source: "McHenry_Slug", target: "McHenry" },
{ source: "Cowell", target: "Cowell_Turkeys" },
{ source: "Cowell", target: "East_Field" },
{ source: "Cowell_Turkeys", target: "Cowell" },
{ source: "Cowell_Turkeys", target: "Baskin" },
{ source: "Parking_Lot_Turkeys", target: "East_Field" },
{ source: "East_Field", target: "Deer" },
{ source: "East_Field", target: "Path" },
{ source: "Deer", target: "East_Field" },
{ source: "Path", target: "Hawk" },
{ source: "Path", target: "Cow" },
{ source: "Hawk", target: "Hawk_Close" },
{ source: "Hawk", target: "Path" },
{ source: "Hawk_Close", target: "Path" },
{ source: "Cow", target: "Cow_Pet" }
]
};
// Set up the SVG
const svg = d3.select("svg");
const width = +svg.attr("width");
const height = +svg.attr("height");
// Define arrow marker
svg.append("defs").append("marker")
.attr("id", "arrowhead")
.attr("viewBox", "0 -5 10 10")
.attr("refX", 20)
.attr("refY", 0)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("path")
.attr("d", "M0,-5L10,0L0,5")
.attr("fill", "#999");
// Create the force simulation
const simulation = d3.forceSimulation(scenes.nodes)
.force("link", d3.forceLink(scenes.links).id(d => d.id).distance(100))
.force("charge", d3.forceManyBody().strength(-300))
.force("center", d3.forceCenter(width / 2, height / 2))
.force("x", d3.forceX(width / 2).strength(0.1))
.force("y", d3.forceY(height / 2).strength(0.1));
// Create the links
const link = svg.append("g")
.attr("class", "links")
.selectAll("line")
.data(scenes.links)
.join("line");
// Create the nodes
const node = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(scenes.nodes)
.join("circle")
.attr("r", d => d.group === 2 ? 12 : 8)
.attr("fill", d => d.group === 2 ? "#ff7f0e" : "#1f77b4");
// Add labels to nodes
const labels = svg.append("g")
.attr("class", "node-labels")
.selectAll("text")
.data(scenes.nodes)
.join("text")
.attr("class", "node-label")
.text(d => d.id.replace(/_/g, " "))
.attr("dx", 14)
.attr("dy", 4);
// Add drag behavior
node.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
// Update positions on each tick
simulation.on("tick", () => {
link
.attr("x1", d => d.source.x)
.attr("y1", d => d.source.y)
.attr("x2", d => d.target.x)
.attr("y2", d => d.target.y);
node
.attr("cx", d => d.x)
.attr("cy", d => d.y);
labels
.attr("x", d => d.x)
.attr("y", d => d.y);
});
// Drag functions
function dragstarted(event, d) {
if (!event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(event, d) {
d.fx = event.x;
d.fy = event.y;
}
function dragended(event, d) {
if (!event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
</script>
</body>
</html>
This visualization:
- Shows all scenes as nodes (circles)
- Connects scenes with directed arrows based on the choices
- Makes ending scenes (McHenry, El_Hospital, and Cow_Pet) larger and colored differently
- Allows dragging nodes to rearrange the layout
- Shows scene names as labels
- Uses force-directed layout to automatically position nodes
- Has arrows showing the direction of scene transitions
The ending scenes are marked in orange and are slightly larger than the regular scenes. You can drag nodes around to better see the connections, and the force simulation will help arrange them in a readable way.
give me a variation that has a little textarea where i can paste in a fresh story in yaml syntax and your code visualizes without you needing to interpreted with your LLM mind or whatever
Here's a modified version that allows direct YAML input and visualization:
<!DOCTYPE html>
<html>
<head>
<title>Story Graph Visualizer</title>
<script src="https://d3js.org/d3.v7.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/js-yaml/4.1.0/js-yaml.min.js"></script>
<style>
.container {
display: flex;
height: 100vh;
}
.input-panel {
width: 300px;
padding: 10px;
background: #f0f0f0;
}
.graph-panel {
flex-grow: 1;
}
textarea {
width: 100%;
height: 300px;
}
.links line {
stroke: #999;
stroke-opacity: 0.6;
stroke-width: 1.5;
marker-end: url(#arrowhead);
}
.nodes circle {
stroke: #fff;
stroke-width: 1.5px;
}
.node-label {
font-family: Arial;
font-size: 10px;
pointer-events: none;
}
</style>
</head>
<body>
<div class="container">
<div class="input-panel">
<textarea id="yamlInput"></textarea>
<button onclick="updateGraph()">Update Graph</button>
</div>
<div class="graph-panel">
<svg width="900" height="800"></svg>
</div>
</div>
<script>
function parseStoryToGraph(story) {
const nodes = [];
const links = [];
// Add start scene
if (story.Start) {
nodes.push({ id: story.Start, group: 1 });
}
// Process all scenes
for (const [sceneId, scene] of Object.entries(story.Scenes)) {
// Add node if not already added
if (!nodes.find(n => n.id === sceneId)) {
// Check if it's an ending (no choices)
const isEnding = !scene.Choices || scene.Choices.length === 0;
nodes.push({ id: sceneId, group: isEnding ? 2 : 1 });
}
// Add links from choices
if (scene.Choices) {
scene.Choices.forEach(choice => {
links.push({
source: sceneId,
target: choice.Target
});
// Add target node if not already added
if (!nodes.find(n => n.id === choice.Target)) {
nodes.push({ id: choice.Target, group: 1 });
}
});
}
}
return { nodes, links };
}
function createGraph(graphData) {
// Clear existing SVG
d3.select("svg").selectAll("*").remove();
const svg = d3.select("svg");
const width = +svg.attr("width");
const height = +svg.attr("height");
// Define arrow marker
svg.append("defs").append("marker")
.attr("id", "arrowhead")
.attr("viewBox", "0 -5 10 10")
.attr("refX", 20)
.attr("refY", 0)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("path")
.attr("d", "M0,-5L10,0L0,5")
.attr("fill", "#999");
const simulation = d3.forceSimulation(graphData.nodes)
.force("link", d3.forceLink(graphData.links).id(d => d.id).distance(100))
.force("charge", d3.forceManyBody().strength(-300))
.force("center", d3.forceCenter(width / 2, height / 2))
.force("x", d3.forceX(width / 2).strength(0.1))
.force("y", d3.forceY(height / 2).strength(0.1));
const link = svg.append("g")
.attr("class", "links")
.selectAll("line")
.data(graphData.links)
.join("line");
const node = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(graphData.nodes)
.join("circle")
.attr("r", d => d.group === 2 ? 12 : 8)
.attr("fill", d => d.group === 2 ? "#ff7f0e" : "#1f77b4");
const labels = svg.append("g")
.attr("class", "node-labels")
.selectAll("text")
.data(graphData.nodes)
.join("text")
.attr("class", "node-label")
.text(d => d.id.replace(/_/g, " "))
.attr("dx", 14)
.attr("dy", 4);
node.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
simulation.on("tick", () => {
link
.attr("x1", d => d.source.x)
.attr("y1", d => d.source.y)
.attr("x2", d => d.target.x)
.attr("y2", d => d.target.y);
node
.attr("cx", d => d.x)
.attr("cy", d => d.y);
labels
.attr("x", d => d.x)
.attr("y", d => d.y);
});
function dragstarted(event, d) {
if (!event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(event, d) {
d.fx = event.x;
d.fy = event.y;
}
function dragended(event, d) {
if (!event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
}
function updateGraph() {
try {
const yamlText = document.getElementById("yamlInput").value;
const storyData = jsyaml.load(yamlText);
const graphData = parseStoryToGraph(storyData);
createGraph(graphData);
} catch (e) {
console.error("Error parsing YAML or creating graph:", e);
alert("Error parsing YAML. Check console for details.");
}
}
</script>
</body>
</html>
To use this:
- There's now a textarea on the left side where you can paste your YAML story structure
- Click the "Update Graph" button to visualize the story
- The graph will appear on the right side
- You can still drag nodes around to arrange them
- Ending scenes (those with no choices) are shown in orange and larger
The code will:
- Parse the YAML input using js-yaml library
- Convert the story structure into nodes and links
- Create a directed graph visualization
- Automatically detect ending scenes (scenes with no choices)
- Show all connections between scenes
Just paste your YAML story structure into the textarea and click Update Graph to visualize it. If there are any errors in the YAML syntax, you'll get an alert and can check the console for details.