forked from mbostock's block: Multi-Foci Force Layout
forked from walkerjeffd's block: Multi-Foci Force Layout Along Path
| license: gpl-3.0 |
forked from mbostock's block: Multi-Foci Force Layout
forked from walkerjeffd's block: Multi-Foci Force Layout Along Path
| <!DOCTYPE html> | |
| <meta charset="utf-8"> | |
| <style> | |
| .node { | |
| stroke-width: 1.5px; | |
| } | |
| </style> | |
| <body> | |
| <button id="btnNext">Next</button> | |
| <script src="//d3js.org/d3.v3.min.js"></script> | |
| <script> | |
| var width = 960, | |
| height = 500; | |
| var timestep = 0, | |
| maxTimestep = 3; | |
| var fill = d3.scale.category10(); | |
| var allData = [ | |
| {id: 1, family: 0, foci: [0, 0, 1, 2]}, | |
| {id: 2, family: 0, foci: [0, 0, 2, 0]}, | |
| {id: 3, family: 2, foci: [0, 0, 1, 2]}, | |
| {id: 4, family: 1, foci: [0, 1, 2, 1]}, | |
| {id: 5, family: 1, foci: [0, 1, 1, 1]}, | |
| {id: 6, family: 2, foci: [0, 1, 0, 2]}, | |
| {id: 7, family: 0, foci: [0, 2, 0, 1]}, | |
| {id: 8, family: 1, foci: [0, 2, 1, 1]} | |
| ] | |
| var nodes = [], | |
| foci = [{x: 100, y: 300}, {x: 450, y: 300}, {x: 800, y: 300}]; | |
| var pathData = d3.range(100, 800, 1) | |
| .map(function (x) { | |
| return { | |
| x: x, | |
| y: 300 + yFunction(x) | |
| }; | |
| }); | |
| function yFunction(x) { | |
| return 100*Math.sin(2*Math.PI*(x - 100)/700); | |
| } | |
| nodes = allData.map(function (d) { | |
| return { | |
| index: d.id, | |
| foci: d.foci, | |
| family: d.family, | |
| x: foci[d.foci[timestep]].x, | |
| y: foci[d.foci[timestep]].y | |
| } | |
| }); | |
| var svg = d3.select("body").append("svg") | |
| .attr("width", width) | |
| .attr("height", height); | |
| var line = d3.svg.line() | |
| .x(function (d) { return d.x; }) | |
| .y(function (d) { return d.y; }); | |
| var paths = svg.selectAll("path") | |
| // .data([foci]); | |
| .data([pathData]); | |
| paths.enter() | |
| .append('path') | |
| .style('fill', 'none') | |
| .style('stroke', 'black') | |
| .attr('d', line); | |
| var force = d3.layout.force() | |
| .nodes(nodes) | |
| .links([]) | |
| .gravity(0) | |
| .size([width, height]) | |
| .on("tick", tick); | |
| var node = svg.selectAll("circle") | |
| .data(nodes); | |
| node.enter().append("circle") | |
| .attr("class", "node") | |
| .attr("cx", function(d) { return d.x; }) | |
| .attr("cy", function(d) { return d.y; }) | |
| .attr("r", 8) | |
| .style("fill", function(d) { return fill(d.family); }) | |
| .style("stroke", function(d) { return d3.rgb(fill(d.family)).darker(2); }); | |
| force.start(); | |
| function tick(e) { | |
| var k = .1 * e.alpha; | |
| // Push nodes toward their designated focus. | |
| nodes.forEach(function(o, i) { | |
| o.y += (foci[o.foci[timestep]].y - o.y) * k; | |
| o.x += (foci[o.foci[timestep]].x - o.x) * k; | |
| }); | |
| node | |
| .attr("cx", function(d) { return d.x; }) | |
| .attr("cy", function(d) { | |
| return d.y + 100*Math.sin(2*Math.PI*(d.x - 100)/700); | |
| }); | |
| } | |
| d3.select('#btnNext') | |
| .on('click', function(e) { | |
| timestep = timestep === maxTimestep ? 0 : timestep + 1; | |
| force.start(); | |
| }); | |
| </script> |