Force-directed graph · qwen3-235b-a22b
Using the pre-provided d3.min.js (global d3), render an interactive force-directed graph of about 40 nodes in several clusters with draggable nodes, hover highlighting of neighbors, and zoom/pan. Define the sample nodes and links inline.
transcript · 4 messages
Using the pre-provided d3.min.js (global d3), render an interactive force-directed graph of about 40 nodes in several clusters with draggable nodes, hover highlighting of neighbors, and zoom/pan. Define the sample nodes and links inline.
thinking
Okay, I need to create a single-page web app that uses d3.min.js to render a force-directed graph with about 40 nodes in clusters. The user wants draggable nodes, hover highlighting of neighbors, and zoom/pan functionality. All in one index.html file with inline CSS and JS, no external resources.
First, I'll start by setting up the basic HTML structure. The script tag for d3.min.js should be included since it's already in the current directory. Then, create a div for the SVG container. Wait, but D3 might append the SVG directly, so maybe just a body with a script.
For the CSS, I'll style the SVG to fill the window and maybe some basic styles for the nodes and links. Maybe circles for nodes, lines for links. Hover effects could be handled with CSS, but since we need to highlight neighbors on hover, which involves dynamic changes, probably need to handle that in JS.
Next, the JavaScript part. Using D3's force simulation. I need to define nodes and links. Let's create 40 nodes divided into clusters. Maybe 4 clusters of 10 nodes each. Each node has a group. Links can be within clusters and some between clusters.
Nodes can be generated with positions, but since it's force-directed, initial positions might be random. The links should have a source and target as indices.
Then, set up the simulation with d3.forceSimulation, adding forces like link, charge, center, and maybe collide for better spacing.
For the visualization, create SVG elements: lines for links and circles for nodes. Each node's color can be based on its group. Drag behavior using d3.drag and simulation.on("tick") to update positions.
Zoom and pan can be handled with d3.zoom(). Apply it to the SVG, and adjust the transform on the group containing the links and nodes.
Hover highlighting: when a node is hovered, find its connected links and nodes and change their opacity or color. Use D3's event listeners for mouseover and mouseout. For this, I'll need to select all links and nodes, then filter based on the connected ones.
Let me outline the steps:
1. HTML structure with script tag for d3.min.js.
2. Define the SVG and group element for zooming.
3. Generate the nodes and links data.
4. Create the force simulation with appropriate forces.
5. Draw the links and nodes, applying styles based on groups.
6. Add drag behavior to nodes.
7. Implement zoom/pan on the SVG.
8. Add hover effects to highlight neighbors.
Potential issues: Making sure that the zoom works with the group element. Also, handling the hover events correctly. For the hover, when a node is hovered, select all links and nodes that are connected to it and apply a style change.
Testing the code in mind: Let's think about the data structure. Each node has an id and a group. Links have source and target as indices. The simulation nodes are the data array.
For the zoom, the SVG should have a g element that contains all the visual elements, and zooming applies to that g. The drag behavior should affect the node positions, and the simulation should be restarted on each tick.
Wait, in D3 force, nodes are automatically positioned, but when dragging, you need to fix the node's position during drag. So the drag event functions should set the node's x and y to the event coordinates, and set fixed flags.
Also, the simulation needs to be linked to the DOM elements. The tick function updates the positions of the nodes and links.
Putting it all together, the code will have a lot of parts. Let me draft the code step by step.
First, the HTML:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Force Directed Graph</title>
<style>
/* CSS styles here */
</style>
</head>
<body>
<script src="d3.min.js"></script>
<script>
// JavaScript code here
</script>
</body>
</html>
In the style section, set the SVG to 100% height and width, maybe a background color. Nodes and links can have basic styles.
For the JS:
- Generate nodes and links. Let's create 4 clusters of 10 nodes each. Each cluster has a color.
nodes = Array.from({length: 40}, (_, i) => ({id: i, group: Math.floor(i / 10)}));
links = [];
// Add intra-cluster links
for (let i = 0; i < 40; i++) {
// Connect to a few nodes in the same cluster
const group = Math.floor(i / 10);
for (let j = 0; j < 3; j++) {
let target = Math.floor(Math.random() * 10) + group * 10;
if (target !== i) links.push({source: i, target: target});
}
}
// Add some inter-cluster links
for (let i = 0; i < 5; i++) {
let group1 = Math.floor(Math.random() * 4);
let group2 = (group1 + 1) % 4;
let source = Math.floor(Math.random() * 10) + group1 * 10;
let target = Math.floor(Math.random() * 10) + group2 * 10;
links.push({source: source, target: target});
}
Then create the simulation:
const simulation = d3.forceSimulation(nodes)
.force("link", d3.forceLink(links).id(d => d.id).distance(50))
.force("charge", d3.forceManyBody().strength(-100))
.force("center", d3.forceCenter(window.innerWidth / 2, window.innerHeight / 2))
.force("collide", d3.forceCollide(30));
Then create the SVG elements:
const svg = d3.select("body").append("svg")
.attr("width", "100%")
.attr("height", "100%");
const g = svg.append("g");
const link = g.append("g")
.selectAll("line")
.data(links)
.enter()
.append("line")
.attr("stroke", "#999")
.attr("stroke-opacity", 0.6);
const node = g.append("g")
.selectAll("circle")
.data(nodes)
.enter()
.append("circle")
.attr("r", 10)
.attr("fill", d => d3.schemeCategory10[d.group % 10])
.call(drag(simulation));
Then the drag behavior:
function drag(simulation) {
return d3.drag()
.on("start", function(event, d) {
if (!event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
})
.on("drag", function(event, d) {
d.fx = event.x;
d.fy = event.y;
})
.on("end", function(event, d) {
if (!event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
});
}
Then the zoom behavior:
svg.call(d3.zoom().on("zoom", function(event) {
g.attr("transform", event.transform);
}));
Then the tick event to update positions:
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);
});
For hover effects: when a node is hovered, find its connected links and nodes and highlight them.
node.on("mouseover", function(event, d) {
// Find all links connected to this node
const connectedLinks = links.filter(l => l.source === d || l.target === d);
// Highlight links
link.style("stroke", "#ff0000").style("stroke-opacity", 1);
// Highlight nodes connected
const connectedNodes = new Set();
connectedLinks.forEach(l => {
if (l.source === d) connectedNodes.add(l.target);
if (l.target === d) connectedNodes.add(l.source);
});
node.style("opacity", n => connectedNodes.has(n) ? 1 : 0.2);
// Also highlight the hovered node
d3.select(this).style("stroke", "#000").style("stroke-width", 2);
});
node.on("mouseout", function() {
link.style("stroke", "#999").style("stroke-opacity", 0.6);
node.style("opacity", 1);
d3.select(this).style("stroke", null).style("stroke-width", null);
});
Wait, but in the code, the links are stored in the data as objects with source and target as indices. However, when D3 binds the data, the source and target might be converted to node objects. Or maybe not. Need to check. In D3's forceLink, the source and target are expected to be references to the nodes. But when initializing links with indices, the id function is set to d => d.id. Wait, the forceLink is created with .id(d => d.id), which should map the source and target from the link's source and target values (which are indices here) to the node's id. Wait, maybe the nodes have an id field, which is their index. Because when creating the nodes, each has id: i. So the links have source and target as indices, which correspond to the node's id. Therefore, the forceLink should correctly assign the source and target nodes.
But in the code above, the links are generated with source and target as indices. Then, when D3 processes the links, the source and target should be the node objects. So when checking in the mouseover event, l.source and l.target are the node objects, so comparing to d (the current node) would work.
But in the code, when filtering connectedLinks, the check is l.source === d or l.target === d. That should work if the links' source and target are the actual node objects.
So that part should be okay.
Now, putting all this into the index.html file. The CSS can be added to style the SVG and nodes.
Potential issues: The zoom might not work if the SVG's viewport is not set correctly. Also, the initial positioning with the center force should center the graph.
Testing the code in the mind, I think this should work. Now, write all this into the index.html file with proper structure.
Wait, the user mentioned that the sample nodes and links should be defined inline. So the data generation code is part of the script.
Also, the CSS should be in the style tag. Maybe add some basic styles for the body, svg, etc.
Putting it all together in the write function.index.html (4599 bytes)Successfully wrote 4599 bytes to index.html
thinking
Okay, the user wanted a single-page web app with a force-directed graph using D3.js. They specified that everything should be in one index.html file, with inline CSS and JS, and no external resources. The d3.min.js is already present, so I need to include it via a script tag. First, I'll set up the basic HTML structure. The head section will have the title and a style block for CSS. The body will include the script tags for D3 and the main code. For the CSS, I'll make sure the SVG takes up the full window and set some basic styles for links and nodes. The body should have no margin or padding to avoid scrollbars. Next, the JavaScript part. I need to generate 40 nodes in clusters. Let's split them into 4 clusters of 10 each. Each node has an id and a group. Links are created within clusters and some between clusters to add connections. The D3 simulation setup includes forces like link, charge, center, and collide. The link force uses the provided links, and the id function ensures sources and targets are correctly mapped. Creating the SVG elements: lines for links and circles for nodes. The circles are colored by their group using D3's category10 scheme. Drag behavior is added so nodes can be moved around, which restarts the simulation when dragged. Zoom and pan functionality is handled with D3's zoom behavior. The SVG element listens for zoom events and applies the transform to the group containing the graph elements. For hover highlighting, when a node is hovered, I'll find all connected links and nodes. The links are highlighted in red, and connected nodes are made more opaque. On mouseout, everything resets. The tick function updates the positions of nodes and links on each simulation step. Also, ensuring that the zoom and drag interactions work smoothly together. I need to make sure all code is self-contained, no external dependencies, and that the file is written correctly. Testing the code structure to avoid syntax errors and ensure all parts are included.
The index.html file has been created successfully with a self-contained force-directed graph visualization using D3.js. It includes: 1. **Clustered Nodes**: 40 nodes divided into 4 color-coded clusters 2. **Interactive Features**: - Draggable nodes with physics simulation - Zoom/pan functionality - Neighbor highlighting on hover 3. **Visual Elements**: - Force-directed layout with collision detection - Intra-cluster and inter-cluster connections - Dynamic link/node highlighting To use: 1. Open index.html in a browser 2. Drag nodes to interact with the physics simulation 3. Hover over nodes to see connected neighbors highlighted 4. Use mouse wheel or pinch gestures to zoom The implementation uses only the provided d3.min.js library and requires no external resources. All styles and scripts are embedded in the single HTML file.
TAKE IT TO CODE
A single self-contained HTML file — copy it and open in any browser, or reuse the prompt with your own pi-agent run.
RUN
EVALUATION
Force-directed clustered graph with colored nodes, simulation settles, drag/zoom shift the view; keyboard has no effect as expected.
Vision grade · anthropic/claude-sonnet-5
MEASUREMENTS
Raw readings from the capture, not a verdict — the grade above is the only judgement. Percentages are how much of the screen changed; the measure under-reports sparse motion on a dark background.