D3
OG
Old school D3 from simpler times
All examples
By author
By category
About
Djokovalle
Full window
Github gist
TP nodes
Built with
blockbuilder.org
<!DOCTYPE html> <html> <head> <title>Force-Directed Layout with Convex Hull</title> <script src="https://d3js.org/d3.v4.min.js"></script> </head> <body> <style> line { fill: white; stroke: black; stroke-width: 1; } .background { fill: none; stroke: none; } .polygons { fill: none; stroke: #000; } </style> <script type="text/javascript"> // parameters var w = 960, h = 500, n = 100, nb_groups = 9, nb_simulation = 120, fill = d3.scaleOrdinal(d3.schemeCategory20), graph = {}; // we generate n random nodes in nb_groups graph.nodes = d3.range(n).map(function(d, i) { return {id: i, group: i % nb_groups, r: 10*Math.random()} }); // we don't generate links, yet graph.links = []; // main svg canvas var svg = d3.select("body").append("svg") .attr("width", w) .attr("height", h); // we separate ndoes using 3 arbitrary columns var cols = Math.ceil(Math.sqrt(nb_groups)); // horizontal scale for the columns var x = d3.scaleLinear() .domain([0, cols]) .range([w/4, 3*w/4]); var y = d3.scaleLinear() .domain([0, cols]) .range([w/4, 3*w/4]); // MAIN LOOP // sets positions for each node based on index // -we store the pixel values // -those are calculated only once graph.nodes.forEach(function(d, i) { var col = i % cols; d.x = x(col); d.y = w/2; }); // simulation of positions var simulation = d3.forceSimulation() .force("link", d3.forceLink().id(function(d) { return d.id; })) .force("charge", d3.forceManyBody()) .force("x", d3.forceX(function(d, i) { return d.x; })) .force("y", d3.forceY(function(d, i) { return d.y; })) .force("collide", d3.forceCollide(12).radius(function(d) { return d.r + 0.5; }).iterations(2)) .force("center", d3.forceCenter(w / 2, h / 2)) .stop(); // apply the simulation to nodes simulation .nodes(graph.nodes); // apply the simulation to links simulation.force("link") .links(graph.links); // we run the simulation nb_simulation times for (var i = 0; i < nb_simulation; ++i) simulation.tick(); // we create a Voronoi partition based on nodes positions var voronoi = d3.voronoi() .extent([[0, 0], [w, h]]) .x(function(d) { return d.x; }) .y(function(d) { return d.y; }) // we draw the voronoi partition using paths var polygon = svg.append("g") .attr("class", "polygons") .selectAll("path") .data(voronoi.polygons(graph.nodes)) .enter() .append("path") .attr("id", function(d) { return "_" + d.data.id; }) .style("fill", "white") .on("mouseenter", function(d) { // highlight nodes in the current partition d3.select(this).style("fill", "red").transition().style("fill", "white") d3.selectAll("circle#" + d3.select(this).attr("id")) .attr("r", 10) .transition() .attr("r", function(d) { return d.r; }) }) .on("mouseleave", function(d) { }) .call(redrawPolygon); // create nodes using the graph.nodes object var node = svg.selectAll("circle.node") .data(graph.nodes) .enter() .append("circle") .attr("class", "node") .attr("r", function(d) { return d.r; }) .attr("id", function(d) { return "_" + d.id; }) .style("fill", function(d, i) { return fill(i); }) .style("stroke", function(d, i) { return d3.rgb(fill(i)).darker(2); }) .style("stroke-width", 1.5); // draw the nodes node .attr("cx", function(d) { return d.x; }) .attr("cy", function(d) { return d.y; }); // create links using the graph.links object var link = svg.append("g") .attr("class", "links") .selectAll("line") .data(graph.links) .enter() .append("line") .attr("stroke-width", function(d) { return Math.sqrt(d.value); }); // draw links link .attr("x1", function(d) { return d.source.x; }) .attr("y1", function(d) { return d.source.y; }) .attr("x2", function(d) { return d.target.x; }) .attr("y2", function(d) { return d.target.y; }); // function to draw the vornoi partition function redrawPolygon(polygon) { polygon .attr("d", function(d) { return d ? "M" + d.join("L") + "Z" : null; }); } </script> </body> </html>
https://d3js.org/d3.v4.min.js