How do to use a force directed layout to see how a bunch of items can be combied.
Last active
October 25, 2016 19:49
-
-
Save trebor/79eaae89b930f73ddccf0fc274b44a2b to your computer and use it in GitHub Desktop.
Shaker
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| license: gpl-3.0 | |
| height: 600 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <meta charset="utf-8"> | |
| <style> | |
| .links line { | |
| stroke-linecap: round; | |
| } | |
| .nodes circle { | |
| stroke: #fff; | |
| stroke-width: 1.5px; | |
| } | |
| </style> | |
| <svg width="960" height="600"></svg> | |
| <script src="https://d3js.org/d3.v4.min.js"></script> | |
| <script> | |
| const COUNT = 20; | |
| const KULL_FACTOR = 0.83; | |
| const MAX_EDGE = 2; | |
| const MIN_WEIGHT = -2; | |
| const MAX_WEIGHT = 2; | |
| const WEIGHT_DOMAIN = [MIN_WEIGHT, MAX_WEIGHT]; | |
| var svg = d3.select("svg"), | |
| width = +svg.attr("width"), | |
| height = +svg.attr("height"); | |
| var defs = svg.append("defs"); | |
| var marker = defs.append("marker"); | |
| var markerAttr = { | |
| "id":"arrow", | |
| "viewBox":"0 -5 10 10", | |
| "refX":15, | |
| "refY":0, | |
| "markerWidth":2, | |
| "markerHeight":2, | |
| "orient":"auto" | |
| }; | |
| Object.keys(markerAttr).forEach(attr => | |
| marker.attr(attr, markerAttr[attr])); | |
| marker | |
| .append("path") | |
| .attr("d", "M0,-5L10,0L0,5") | |
| .attr("class","arrowHead"); | |
| var weightGen = d3.randomNormal(0, 1); | |
| var linkColor = d3.scaleOrdinal() | |
| .domain([-2, -1, 0, 1, 2]) | |
| .range(['red', 'red', 'black', 'green', 'green']); | |
| var linkOpacity = d3.scaleOrdinal() | |
| .domain([-2, -1, 0, 1, 2]) | |
| .range([0.6, 0.2, 0.1, 0.2, 0.6]); | |
| var linkWidth = d3.scaleLinear() | |
| .domain(WEIGHT_DOMAIN) | |
| .range([5, 20]); | |
| var linkDistance = d3.scalePow() | |
| .exponent(0.8) | |
| .domain(WEIGHT_DOMAIN) | |
| .range([200, 0]); | |
| var linkStrength = d3.scalePow() | |
| .exponent(1) | |
| .domain(WEIGHT_DOMAIN) | |
| .range([0.1, 1]); | |
| var linkLogic = d3.forceLink() | |
| .id(function(d) { return d.id; }) | |
| .distance(link => linkDistance(link.weight)) | |
| .strength(link => linkStrength(link.weight)); | |
| var chargeLogic = d3.forceManyBody().strength((a, b) => { | |
| return -100; | |
| }); | |
| var simulation = d3.forceSimulation() | |
| .force("link", linkLogic) | |
| .force("charge", chargeLogic) | |
| .force("center", d3.forceCenter(width / 2, height / 2)); | |
| function randomWeight() { | |
| return d3.max([MIN_WEIGHT, d3.min([MAX_WEIGHT, ~~weightGen()])]); | |
| } | |
| function main() { | |
| var nodes = d3.range(0, COUNT).map(function(d){ | |
| return {id: "l"+d, r:~~d3.randomUniform(8, 28)()} | |
| }); | |
| var links = nodes.reduce((l1, node1) => { | |
| return d3.shuffle(nodes | |
| .filter(node2 => node1 != node2) | |
| .map(node2 => ({id2: node2.id, weight: randomWeight()})) | |
| .filter(({id2, weight}) => weight != 0)) | |
| .filter(({id2, weight}, i) => i < MAX_EDGE) | |
| .reduce((l2, {id2, weight}) => { | |
| l2.push({ | |
| weight: weight, | |
| source: node1.id, | |
| target: id2, | |
| }); | |
| return l2; | |
| }, l1); | |
| }, []); | |
| drawChart({nodes, links}); | |
| } | |
| function drawChart(graph) { | |
| //console.log(d3.extent(graph.links, d => d.weight)); | |
| var link = svg.append("g") | |
| .attr("class", "links") | |
| .selectAll("line") | |
| .data(graph.links) | |
| .enter() | |
| .append("line") | |
| // .attr("marker-end", "url(#arrow)") | |
| .style('stroke-opacity', d => linkOpacity(d.weight)) | |
| .style('stroke', d => linkColor(d.weight)) | |
| .attr("stroke-width", d => linkWidth(d.weight)); | |
| var node = svg.append("g") | |
| .attr("class", "nodes") | |
| .selectAll("circle") | |
| .data(graph.nodes) | |
| .enter().append("circle") | |
| .attr("r", 10) | |
| .attr("fill", '#888') | |
| .call(d3.drag() | |
| .on("start", dragstarted) | |
| .on("drag", dragged) | |
| .on("end", dragended)); | |
| node.append("title") | |
| .text(function(d) { return d.id; }); | |
| simulation | |
| .nodes(graph.nodes) | |
| .on("tick", ticked); | |
| simulation | |
| .force("link") | |
| .links(graph.links); | |
| function ticked() { | |
| 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; }); | |
| node | |
| .attr("cx", function(d) { return d.x; }) | |
| .attr("cy", function(d) { return d.y; }); | |
| } | |
| } | |
| function dragstarted(d) { | |
| if (!d3.event.active) simulation.alphaTarget(0.3).restart(); | |
| d.fx = d.x; | |
| d.fy = d.y; | |
| } | |
| function dragged(d) { | |
| d.fx = d3.event.x; | |
| d.fy = d3.event.y; | |
| } | |
| function dragended(d) { | |
| if (!d3.event.active) simulation.alphaTarget(0); | |
| d.fx = null; | |
| d.fy = null; | |
| } | |
| main(); | |
| </script> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| �PNG | |