Last active
September 29, 2016 18:52
-
-
Save trebor/c5ef29301512f8325fa058d9092742eb to your computer and use it in GitHub Desktop.
Nautiloid
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> | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
<title>Nautiloid</title> | |
<link type="text/css" rel="stylesheet" href="nautiloid.css"/> | |
<script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js" ></script> | |
<script src="//d3js.org/d3.v3.min.js"></script> | |
<script src="./nautiloid.js"></script> | |
</head> | |
<body> | |
<div id="chart"></div> | |
</body> | |
</html> |
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
html { | |
background: black; | |
} | |
* { | |
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; | |
margin: auto; | |
padding: auto; | |
position: relative; | |
width: 100%; | |
height: 100%; | |
} | |
#chart { | |
} | |
svg { | |
-webkit-filter: drop-shadow(0 0 10px #ff0000); | |
} | |
.body { | |
fill: #888; | |
opacity: 0.25; | |
} | |
.node { | |
opacity: 0.25; | |
} | |
.node text{ | |
fill: blue; | |
} | |
path.link { | |
fill: #848; | |
stroke: none; | |
opacity: 0.5; | |
} | |
.tentical { | |
fill: #FCF; | |
} |
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
var margin = {top: 10, right: 10, bottom: 10, left: 10}; | |
var padding = {top: 0, right: 0, bottom: 0, left: 0}; | |
var outerWidth; | |
var outerHeight; | |
var innerWidth; | |
var innerHeight; | |
var width; | |
var height; | |
var svg; | |
var force; | |
var nextName = 0; | |
var depth = 5; | |
var armLengthScale = d3.scale.linear().range([3, 3]); | |
var degreeScale = d3.scale.linear().range([1, 3]); | |
var radiusScale = d3.scale.pow().exponent(.8).range([25, 0]); | |
var distanceScale = d3.scale.pow().exponent(1).range([20, 10]); | |
var linkStrengthScale = d3.scale.linear().range([10, 10]); | |
var timeFactorScale = d3.scale.linear().range([1/300, 1/5000]); | |
var waggleFactorScale = d3.scale.linear().range([.005, .007]); | |
var chargeScale = d3.scale.linear().range([-1, -1]); | |
var root; | |
var nodes = []; | |
var links = []; | |
//var joints = []; | |
var mouse; | |
var currentSegmentId = 0; | |
function getNextSegmentId() { | |
return currentSegmentId++; | |
} | |
$(document).ready(function() { | |
console.log("ready!"); | |
init(); | |
}); | |
function init() { | |
// size stuff | |
outerWidth = $("#chart").width(); | |
outerHeight = $("#chart").height(); | |
innerWidth = outerWidth - margin.left - margin.right; | |
innerHeight = outerHeight - margin.top - margin.bottom; | |
width = innerWidth - padding.left - padding.right; | |
height = innerHeight - padding.top - padding.bottom; | |
// init svg | |
svg = d3.select("#chart") | |
.append("svg") | |
.attr("width", outerWidth) | |
.attr("height", outerHeight) | |
.on("mousemove", function() {mouse = d3.event}) | |
.append("g") | |
.attr("transform", "translate(" + margin.left + "," + margin.top + ")"); | |
// create fdl | |
force = d3.layout.force() | |
.gravity(0.0) | |
.friction(.9) | |
.distance(function(link) {return distanceScale(link.target.depth)}) | |
.charge(function(node) {return chargeScale(node.depth)}) | |
.linkStrength(function(link) {return linkStrengthScale(link.source.depth)}) | |
.size([width, height]); | |
// create root ndoe | |
root = { | |
name: "root", | |
x: width / 2, | |
y: height / 2, | |
depth: 0, | |
fixed: true, | |
children: [], | |
}; | |
// construct tree | |
var tree = generateTree( | |
root, | |
depth, | |
function() {return degreeScale(Math.random())}, | |
function() {return armLengthScale(Math.random())}, | |
nodes, | |
links); | |
// joints = links.filter(function(link) {return true; link.joint;}); | |
// joints.sort(function(j1, j2) {return j1.source.depth - j2.source.depth}); | |
// joints.forEach(function(joint) { | |
// joint.timeFactor = timeFactorScale(Math.random()); | |
// joint.waggleFactor = waggleFactorScale(Math.random()); | |
// }); | |
// console.log(d3.extent(joints, function(j) {return j.timeFactor;}).map(function(d) {return Math.round(d * 100000)})); | |
nodes.push(root); | |
// configure scales | |
distanceScale.domain(d3.extent(nodes, function(d) {return d.depth})); | |
radiusScale.domain(d3.extent(nodes, function(d) {return d.depth})); | |
linkStrengthScale.domain(d3.extent(nodes, function(d) {return d.depth})); | |
chargeScale.domain(d3.extent(nodes, function(d) {return d.depth})); | |
// precompute node and link properties | |
nodes.forEach(function(node) { | |
node.radius = radiusScale(node.depth); | |
}); | |
links.forEach(function(link) { | |
link.distance = link.source.radius + link.target.radius * 2; | |
}) | |
update(); | |
} | |
function generateTree(root, maxDepth, degreeFunc, lengthFunc, nodes, links, depth) { | |
depth = depth || 0; | |
nodes = nodes || []; | |
links = links || []; | |
var degree = degreeFunc(); | |
var length = lengthFunc(); | |
var segmentId = getNextSegmentId(); | |
// for each degree | |
for (var d = 0; d < degree; ++d) { | |
// the source node of each link | |
var source = root; | |
// for length | |
for (var l = 0; l < length; ++l) { | |
var effectiveDepth = depth * length + l + 1; | |
var node = { | |
name: String.fromCharCode(65 + nextName++), | |
segmentId: segmentId, | |
depth: effectiveDepth, | |
x: width / 2, | |
y: height / 2, | |
group: 1, | |
children: [], | |
}; | |
nodes.push(node); | |
links.push({ | |
joint: source == root, | |
source: source, | |
target: node, | |
value: 1, | |
}); | |
source.children.push(node); | |
source = node; | |
} | |
if (depth < maxDepth - 1) | |
generateTree(source, maxDepth, degreeFunc, lengthFunc, nodes, links, depth + 1); | |
} | |
return {nodes: nodes, links: links}; | |
} | |
function treePoints(root, points) { | |
points = points || []; | |
var d90 = Math.PI / 2; | |
root.children.forEach(function(child) { | |
var angle = child.angle; | |
points.push(polarPoint(root, root.radius, angle + d90)); | |
points.push(polarPoint(child, child.radius, angle + d90)); | |
treePoints(child, points); | |
points.push(polarPoint(child, child.radius, angle - d90)); | |
points.push(polarPoint(root, root.radius, angle - d90)); | |
}); | |
return points; | |
} | |
function sortChildren(root) { | |
root.children.forEach(function(child) { | |
child.angle = polarAngle(root, child); | |
sortChildren(child); | |
}); | |
root.children.sort(function(c1, c2) { | |
return c2.angle - c1.angle; | |
}); | |
} | |
function bodyPath() { | |
sortChildren(root); | |
var tp1 = treePoints(root); | |
var tp2 = []; | |
for (var i = 1; i < tp1.length; i += 2) { | |
var i1 = i; | |
var i2 = (i + 1) % tp1.length; | |
// console.log(i1, i2, tp1.length); | |
tp2.push(avergePoints(tp1[i1], tp1[i2])); | |
} | |
// debugger; | |
return populatePoloy(tp1); | |
} | |
function linkPath(link) { | |
var s = link.source; | |
var t = link.target; | |
var angle = polarAngle(s, t); | |
var d90 = Math.PI / 2; | |
var points = [ | |
polarPoint(s, s.radius, angle + d90), | |
polarPoint(s, s.radius, angle - d90), | |
polarPoint(t, t.radius, angle - d90), | |
polarPoint(t, t.radius, angle + d90), | |
]; | |
return populatePoloy(points); | |
} | |
var colors = d3.scale.category20(); | |
function update() { | |
force | |
.nodes(nodes) | |
.links(links) | |
.start(); | |
// var tentical = svg | |
// .append("circle") | |
// .attr("class", "tentical") | |
// .attr("r", 50); | |
// var link = svg.selectAll(".link") | |
// .data(links) | |
// .enter() | |
// .append("path") | |
// // .style("stroke-width", function(d) {return d.target.radius * 2}) | |
// .attr("linecap", "round") | |
// .attr("class", "link"); | |
var body = svg | |
.append("path") | |
.attr("class", "body"); | |
var node = svg.selectAll(".node") | |
.data(nodes) | |
.enter().append("g") | |
.attr("class", "node") | |
.call(force.drag); | |
node.append("circle") | |
.attr("cx", 0) | |
.attr("cy", 0) | |
.style("fill", function(d) {return colors(d.segmentId)}) | |
.attr("r", function(d) {return d.radius}); | |
// node.append("text") | |
// .attr("dx", 12) | |
// .attr("dy", ".35em") | |
// .text(function(d) { return d.name }); | |
var lastTime = new Date().getTime(); | |
force.on("tick", function() { | |
if (mouse) { | |
var bias = 0.99; | |
root.px = root.x * bias + mouse.x * (1 - bias); | |
root.py = root.y * bias + mouse.y * (1 - bias); | |
} | |
var now = new Date().getTime(); | |
dTime = now - lastTime; | |
lastTime = now; | |
// joints.forEach(function(joint) { | |
// var s = joint.source; | |
// var t = joint.target; | |
// var theta = polarAngle(s, t); | |
// var radius = distance(s, t); | |
// var dTheta = Math.sin(now * joint.timeFactor) * joint.waggleFactor; | |
// // console.log(Math.round(dTheta * 10000)); | |
// theta += dTheta; | |
// polarPoint(s, radius, theta, t); | |
// }); | |
//link.attr("d", linkPath); | |
body.attr("d", bodyPath); | |
// 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; }); | |
// var nodesX = 0; | |
// var nodesY = 0; | |
// nodes.forEach(function(node) { | |
// nodesX += node.x; | |
// nodesY += node.y; | |
// }); | |
// nodesX /= nodes.length; | |
// nodesY /= nodes.length; | |
// tentical | |
// .attr("cx", nodesX) | |
// .attr("cy", nodesY); | |
node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; }); | |
force.resume(); | |
}); | |
} | |
function polarAngle(p1, p2) { | |
var dx = p2.x - p1.x; | |
var dy = p2.y - p1.y; | |
return Math.atan2(dy, dx); | |
} | |
function polarPoint(p1, radius, theta, p2) { | |
p2 = p2 || {}; | |
p2.x = p1.x + radius * Math.cos(theta); | |
p2.y = p1.y + radius * Math.sin(theta); | |
return p2; | |
} | |
function distance(p1, p2) { | |
var dx = p2.x - p1.x; | |
var dy = p2.y - p1.y; | |
return Math.sqrt(dx * dx + dy * dy); | |
} | |
function populatePath(path, points) { | |
for(index in points) { | |
path = path | |
.replace("X" + index, points[index].x) | |
.replace("Y" + index, points[index].y); | |
}; | |
return path; | |
} | |
function populatePoloy(points) { | |
var path = ""; | |
points.forEach(function(point, i) { | |
path += (i == 0 ? "M" : "L") + point.x + " " + point.y + " "; | |
}) | |
path += "z"; | |
return path; | |
} | |
function avergePoints(p1, p2) { | |
return { | |
x: (p1.x + p2.x) / 2, | |
y: (p1.y + p2.y) / 2, | |
}; | |
} | |
function Segment(length, parent) { | |
var segmentId = getNextSegmentId(); | |
var depth = parent ? parent.getDepth() + 1 : 0; | |
var children = []; | |
var nodes = []; | |
var links = []; | |
function init() { | |
// the source node of each link | |
var source = parent ? parent.getJoint() : undefined; | |
// for length | |
for (var l = 0; l < length; ++l) { | |
var effectiveDepth = depth * length + l + 1; | |
var node = { | |
name: String.fromCharCode(65 + nextName++), | |
segmentId: segmentId, | |
depth: effectiveDepth, | |
x: width / 2, | |
y: height / 2, | |
group: 1, | |
children: [], | |
}; | |
nodes.push(node); | |
links.push({ | |
joint: source == root, | |
source: source, | |
target: node, | |
value: 1, | |
}); | |
source.children.push(node); | |
source = node; | |
} | |
if (depth < maxDepth - 1) | |
generateTree(source, maxDepth, degreeFunc, lengthFunc, nodes, links, depth + 1); | |
} | |
function getDepth() { | |
return depth; | |
} | |
function getPath() { | |
} | |
function getNodes() { | |
return nodes; | |
} | |
function getLinks() { | |
return links; | |
} | |
function getJoint() { | |
return nodes[length - 1]; | |
} | |
return { | |
getPath: getPath, | |
getNodes: getNodes, | |
getLinks: getLinks, | |
getJoint: getJoint, | |
getDepth: getDepth, | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment