inspired by
building on
having found useful
- [Kent C. Dodds' How to Contribute to an Open Source Project on GitHub] (https://github.com/eggheadio-github/stack-overflow-copy-paste)
$ npm install
$ npm start
$ npm run build
MIT
inspired by
building on
having found useful
$ npm install
$ npm start
$ npm run build
MIT
if (typeof require === "function") { | |
var d3 = require('d3.v4.js') | |
} | |
(function (global, factory) { | |
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : | |
typeof define === 'function' && define.amd ? define(['exports'], factory) : | |
(factory((global.d3c = global.d3c || {}))); | |
}(this, function (exports) { 'use strict'; | |
/* ------------- */ | |
/* quad */ | |
/* ------------- */ | |
function quad (form = {type: 'rect', x0: 0, y0: 0, x1: 100, y1: 100}) { | |
if (form.type !== 'rect') { return } | |
let x0 = form.x0 | |
let y0 = form.y0 | |
let x1 = form.x1 | |
let y1 = form.y1 | |
let extent = [ | |
[x1 - 1 , y0 - 1], | |
[x1 + 1 , y1 + 1] ] | |
let bestcandie = function () {} | |
let candidates = 10 | |
var quad = d3.quadtree() // quad | |
.extent(extent) | |
.x(function(d) {return d[0]}) | |
.y(function(d) {return d[1]}) | |
quad.diagonal = function(d, p) { // error: d is undefined | |
var v = p || 1 | |
var s = d.source | |
var t = d.target | |
var rd = 1 + d3.randomNormal(0, v)() // v | |
var r = "M" + s.x + "," + s.y | |
+ "C" + (s.x + rd * ((t.x - s.x))) + "," + s.y | |
+ " " + (s.x + rd * ((t.x - s.x))) + "," + t.y | |
+ " " + t.x + "," + t.y; | |
return r | |
} | |
quad.findmany = function(x, y, r=Infinity, thesemany = 1) { | |
var ret = [] | |
let quadCopy = quad.copy() | |
let limit = Math.min(thesemany, quadCopy.data().length) | |
for (let i = 0; i < limit; i++) { | |
let p = quadCopy.find(x, y, r) | |
quadCopy.remove(p) | |
ret.push(p) | |
} | |
return ret | |
} | |
quad.candysearch = function(candidates, r=Infinity) { | |
let fp = [x9 + Math.random() * (x1 - x0), Math.random() * (y1 - y0), 0] // first point | |
quad.add(fp) | |
// let bestcandy = candysearch({ ctr: [0, 0], rds: [width, height - 110], }) | |
let bestCandidate, bestDistance = 0 // ?? | |
let x, y = 0; // x,y is bestCandidate | |
let z2 = 0; // z2 is bestDistance | |
for (let i = 0; i < candidates; ++i) { | |
let c = [x0 + Math.random() * (x1 - x0), y0 + Math.random() * (y1 - y0)] // c random candidate i | |
let p = quad.find(c[0], c[1], 1000) // p point closest to candidate | |
let dx = p ? c[0] - p[0] : r // x delta if exist tbc | |
let dy = p ? c[1] - p[1] : r // y delta if exist | |
let d2 = dx * dx + dy * dy // distance from candidate to closest | |
if (d2 > z2) x = c[0], y = c[1], z2 = d2 // further is better | |
} | |
quad.add(p = [x, y]) // add selected point | |
return p // return selected point | |
} | |
quad.bestcandie = function(_) { | |
return arguments.length ? (bestcandie = typeof _ === "function" ? _ : constant(!!_), quad) : bestcandie; | |
} | |
quad.candidates = function (_) { // kandidates | |
return (arguments.length) ? (candidates = _ ,quad) : candidates | |
} | |
quad.rds = function (_) { // radius | |
return (arguments.length) ? (rds = _ ,quad) : rds | |
} | |
return quad | |
} | |
/* ------------- */ | |
/* layer */ | |
/* ------------- */ | |
function layer(payload) { | |
if (payload == null ) { | |
d3.selectAll("svg").data([]).exit().remove() | |
var svgLayer = d3.select('body').append("svg") | |
.attr("class", "svg") | |
.attr("id", "svg") | |
.attr("width", function() {return (typeof width !== 'undefined') ? width : 600}) | |
.attr("height", function() {return (typeof height !== 'undefined') ? height : 400}) | |
.style("border", "1px solid lightgray") | |
return svgLayer | |
} else { | |
var dfn = function(d, i) { return (d.id !== undefined) ? d.id : i } | |
var parent = payload.parent || 'svg' | |
var cls = payload.cls || item | |
var item = payload.item | |
var subclass = payload.subclass || item | |
var itemParts = item.split('.') | |
if (itemParts.length > 1) { | |
item = itemParts[0] | |
subclass = itemParts[1] | |
} | |
var idfn = payload.idfn || dfn | |
var data = payload.data || [] | |
var layerMark = d3.select(parent).selectAll('.' + cls).data([cls]) | |
var layerEnter = layerMark.enter().append("g").attr("class", cls) | |
var elemsUpdate = d3.select("." + cls).selectAll(item + '.' + subclass).data(data, idfn) | |
var elemsEnter = elemsUpdate.enter().append(item).attr("class", subclass) | |
var elemsMerge = elemsEnter.merge(elemsUpdate) | |
var elemsExit = elemsUpdate.exit() //.remove() | |
return {e: elemsEnter , u: elemsUpdate, x: elemsExit, m: elemsMerge} | |
} | |
} | |
function itemsControls(payload) { | |
var jsonCircles = [ | |
{ "x": 30, "y": 30, "rad": 20, "c" : "Orange" }, | |
{ "x": 70, "y": 70, "rad": 20, "c" : "Peru"}, | |
{ "x": 110, "y": 100, "rad": 20, "c" : "Sienna"}] | |
var items = { | |
circles: d3c.layer({cls: 'circles', item:'circle', data: jsonCircles}), | |
texts: d3c.layer({cls: 'texts', item:'text', data: jsonCircles}) | |
} | |
items.circles.e | |
.attr("cx", function (d) { return d.x }) | |
.attr("cy", function (d) { return d.y }) | |
.attr("r", function (d) { return d.rad }) | |
.style("fill", function(d) { return d.c }) | |
.attr("transform", function(d, i) { | |
// d.ddx = (d.ddx || 0) + i * 50 | |
// d.ddy = (d.ddy || 0) + i * 50 | |
return "translate(" + i * 50 + "," + i * 50 + ")" | |
}) | |
items.texts.e | |
.attr("x", function (d) { return d.x }) | |
.attr("y", function (d) { return d.y }) | |
.text(function(d, i) { return 'circle ' + i }) | |
.attr("transform", function(d, i) { | |
// d.ddx = (d.ddx || 0) + i * 50 | |
// d.ddy = (d.ddy || 0) + i * 50 | |
return "translate(" + i * 50 + "," + i * 50 + ")" | |
}) | |
return items | |
} | |
function circles(payload) { | |
var jsonCircles = [ | |
{ "x": 30, "y": 30, "rad": 20, "c" : "green" }, | |
{ "x": 70, "y": 70, "rad": 20, "c" : "purple"}, | |
{ "x": 110, "y": 100, "rad": 20, "c" : "red"}] | |
var circles = d3c.layer({cls: 'circles', item:'circle', data: jsonCircles}).e | |
.attr("cx", function (d) { return d.x }) | |
.attr("cy", function (d) { return d.y }) | |
.attr("r", function (d) { return d.rad }) | |
.style("fill", function(d) { return d.c }) | |
.attr("transform", function(d, i) { | |
d.ddx = (d.ddx || 0) + i * 50 | |
d.ddy = (d.ddy || 0) + i * 50 | |
return "translate(" + i * 50 + "," + i * 50 + ")" | |
}) | |
var texts = d3c.layer({cls: 'texts', item:'text', data: jsonCircles}).e | |
.attr("x", function (d) { return d.x }) | |
.attr("y", function (d) { return d.y }) | |
.text(function(d) { return 'hello' }) | |
.attr("transform", function(d, i) { | |
d.ddx = (d.ddx || 0) + i * 50 | |
d.ddy = (d.ddy || 0) + i * 50 | |
return "translate(" + i * 50 + "," + i * 50 + ")" | |
}) | |
} | |
function nodes(payload) { | |
var jsonCircles = [ | |
{ "x": 30, "y": 30, "rad": 20, "c" : "green" }, | |
{ "x": 70, "y": 70, "rad": 20, "c" : "purple"}, | |
{ "x": 110, "y": 100, "rad": 20, "c" : "red"}] | |
var nodes = d3c.layer({cls: 'nodes', code: 'node', item:'g', data: jsonCircles}).e | |
nodes | |
.append("circle") | |
.attr("class", "circleNode") | |
.attr("cx", function (d) { return d.x }) | |
.attr("cy", function (d) { return d.y }) | |
.attr("r", function (d) { return d.rad }) | |
.style("fill", function(d) { return d.c }) | |
.attr("transform", function(d, i) { | |
// d.ddx = (d.ddx || 0) + i * 50 | |
// d.ddy = (d.ddy || 0) + i * 50 | |
return "translate(" + i * 50 + "," + i * 50 + ")" | |
}) | |
nodes | |
.append("text") | |
.attr("class", "textNode") | |
.attr("x", function (d) { return d.x }) | |
.attr("y", function (d) { return d.y }) | |
.text(function(d) { return 'hello' }) | |
.attr("transform", function(d, i) { | |
d.ddx = (d.ddx || 0) + i * 50 | |
d.ddy = (d.ddy || 0) + i * 50 | |
return "translate(" + i * 50 + "," + i * 50 + ")" | |
}) | |
} | |
/* ================================= */ | |
/* time control */ | |
/* ================================= */ | |
function time(payload) { | |
var currentListeners = [] | |
var nextListeners = currentListeners | |
var period = payload.period || 400 // time period in milliseconds | |
var fps = payload.fps || 60 // frames per second | |
// ------------------------- ensureCanMutateNextListeners | |
function ensureCanMutateNextListeners() { | |
if (nextListeners === currentListeners) { | |
nextListeners = currentListeners.slice() | |
} | |
} | |
// ------------------------- timer | |
function timer() {} | |
// ------------------------- start | |
timer.start = function start() { | |
var started = false | |
var listeners = currentListeners = nextListeners | |
for (var i = 0; i < listeners.length; i++) { | |
// listeners[i]() | |
} | |
return timer | |
} | |
// ------------------------- subscribe | |
timer.subscribe = function subscribe (listener) { | |
if (typeof listener !== 'function') { | |
throw new Error('Expected listener to be a function.') | |
} | |
var isSubscribed = true | |
ensureCanMutateNextListeners() | |
nextListeners.push(listener) | |
d3.timer( | |
listener | |
, period) | |
return function unsubscribe() { | |
if (!isSubscribed) { | |
return | |
} | |
isSubscribed = false | |
ensureCanMutateNextListeners() | |
var index = nextListeners.indexOf(listener) | |
nextListeners.splice(index, 1) | |
} | |
} | |
return timer | |
} | |
/* ================================= */ | |
/* movControls */ | |
/* ================================= */ | |
function mov (selection) { | |
console.log(selection) | |
function dragsubject() { | |
return this | |
} | |
function dragstarted(d) { // --------------- dragstarted | |
d3.select(this).raise().classed("active", true) | |
d.x0 = d3.event.x // set x position at begining of drag | |
d.y0 = d3.event.y // set x position at begining of drag | |
} | |
function dragged(d) { // --------------- dragged | |
d.dx = d3.event.x - d.x0 // set delta x of current drag | |
d.dy = d3.event.y - d.y0 // set delta y of current drag | |
var ddx = (d.ddx || 0) + (d.dx || 0 ) // set delta x of current drag | |
var ddy = (d.ddy || 0) + (d.dy || 0 ) // set delta y of current drag | |
d3.select(this) | |
.attr("transform", "translate(" + ddx + "," + ddy + ")") | |
} | |
function dragended(d) { // --------------- dragended | |
d.ddx = (d.ddx || 0) + d.dx // set accumulated x drag displacment | |
d.ddy = (d.ddy || 0) + d.dy // set accumulated y drag displacment | |
d.dx = 0 // reset last x drag displacment | |
d.dy = 0 // reset last y drag displacment | |
} | |
var drag = d3.drag() | |
drag | |
.on("start", dragstarted) | |
.on("drag", dragged) | |
.on("end", dragended) | |
selection.call(drag) | |
return drag | |
} | |
/* ================================= */ | |
/* drag Control */ | |
/* ================================= */ | |
function drag (selection) { | |
function dragsubject() { | |
return this | |
} | |
function dragstarted(d) { | |
d3.select(this).raise().classed("active", true) | |
} | |
function dragged(d) { | |
d3.select(this).attr("x", d.x = d3.event.x).attr("y", d.y = d3.event.y); | |
} | |
function dragended(d) { | |
} | |
var drag = d3.drag() | |
drag | |
.on("start", dragstarted) | |
.on("drag", dragged) | |
.on("end", dragended) | |
selection.call(drag) | |
return drag | |
} | |
/* ================================= */ | |
/* pos Control */ | |
/* ================================= */ | |
function pos (selection) { // selection | |
function prevent(e) { | |
} | |
function subject() { | |
return this | |
} | |
function started(d) { | |
} | |
function moved(d) { | |
function createPostipElem() { | |
var padLayer = d3.select('body').selectAll('g.refs').data(['refs']).enter().insert("g", "svg").attr("class", "refs") | |
var postipElem = d3.select("g.refs").selectAll("div.postip").data(['divMousePos']).enter().append("div").attr("class", "postip").call(drawPostipElem) | |
} | |
function drawPostipElem (postip) { | |
postip | |
.attr("viewBox", "0 0 10 10") | |
.style("top", "-5px") | |
.style("position", "absolute") | |
.style("padding", "10px") | |
.style("background", "rgba(255, 255, 255, .90)") | |
.style("border", "1px solid lightgray") | |
.style("pointer-events", "none") | |
.style("z-index", "100") | |
.style('border', '1px solid orange') | |
.style('color', 'grey') | |
.classed('postip-hidden', true) | |
.style("opacity", 0) | |
} | |
function textPadFn (a) { | |
var s = String("_______" + Math.floor(a.ox) + " : " + Math.floor(a.oy) + "_______") | |
return s | |
} | |
// https://github.com/1wheel/swoopy-drag/blob/master/lib/d3-jetpack.js | |
function displayTextPad(a) { | |
d3.select('.postip') | |
.classed('postip-hidden', false) | |
.style('opacity', 1) | |
.html('') | |
.selectAll('div') | |
.data([textPadFn]).enter() | |
.append('div') | |
.html(function(textPadFn) { | |
return (textPadFn(a)) | |
}) | |
} | |
function moveTextPad(node) { | |
var postip = d3.select('div.postip') | |
if (!postip.size()) return | |
var e = d3.event, | |
x = e.clientX, | |
y = e.clientY, | |
doctop = (window.scrollY)? window.scrollY : (document.documentElement && document.documentElement.scrollTop)? document.documentElement.scrollTop : document.body.scrollTop, | |
n = postip.node(), | |
nBB = n.getBoundingClientRect() | |
postip.style('top', (y+doctop-nBB.height-18)+"px"); | |
postip.style('left', Math.min(Math.max(0, (x-nBB.width/2)), window.innerWidth - nBB.width)+"px"); | |
prevent(e) | |
} | |
var datum = d, // d datum | |
node = this, // elem | |
parent = node.parentNode, | |
origin = d3.mouse(parent), | |
ox = origin[0], | |
oy = origin[1] | |
var action = {ox: ox, oy: oy} | |
createPostipElem() | |
displayTextPad(action) | |
moveTextPad(node) | |
} | |
function ended(d) { | |
var node = d3.select(this) // selection | |
var datum = node.datum() // datum | |
d3.select('div.postip') | |
.classed('postip-hidden', true) | |
.style('opacity', 0) | |
d3.selectAll('.postipped') | |
.classed('postipped', false) | |
} | |
function d3Control(selection) { | |
selection.on("mouseenter.pos", started) | |
selection.on("mousemove.pos", moved) | |
selection.on("mouseout.pos", ended) | |
} | |
selection.call(d3Control) | |
return d3Control | |
} | |
/* ================================= */ | |
/* tip Control */ | |
/* ================================= */ | |
function tip (selection) { | |
function prevent(e) { | |
if(e.stopPropagation) e.stopPropagation(); | |
if(e.preventDefault) e.preventDefault(); | |
return false; | |
} | |
function createTooltip() { | |
var tooltipLayer = d3.select('body').selectAll('g.refs').data(['refs']).enter().insert("g", "svg").attr("class", "refs") | |
var toolTipElem = d3.select("g.refs").selectAll("div.tooltip").data(['divTooltip']).enter().append("div").attr("class", "tooltip").call(drawTooltipElem) | |
} | |
function drawTooltipElem (tooltip) { | |
tooltip | |
.attr("viewBox", "0 0 10 10") | |
.style("top", "-5px") | |
.style("position", "absolute") | |
.style("padding", "10px") | |
.style("background", "rgba(255, 255, 255, .90)") | |
.style("border", "1px solid lightgray") | |
.style("pointer-events", "none") | |
.style("z-index", "100") | |
.style('border', '1px solid red') | |
.style('color', 'grey') | |
.classed('tooltip-hidden', true) | |
.style("opacity", 0) | |
} | |
// ------------------------- diaplay action.datum | |
// https://github.com/1wheel/swoopy-drag/blob/master/lib/d3-jetpack.js | |
function displayTooltip(node) { | |
var d = node.datum() | |
if (d.hasOwnProperty('tip')) { // true | |
// console.log('_______________ has tip') | |
var fieldFns = d3.keys(d) // get text keys in datum | |
.filter(function(str){ | |
var r = (str === 'tip') | |
return r | |
}) | |
.map(function(str){ | |
return function (d) { return str + ': ' + '<b>' + wordwrap(d[str].toString()) + '</b>' }}) | |
} else { | |
var fieldFns = d3.keys(d) // get text keys in datum | |
.filter(function(str){ | |
var r = (typeof d[str] != 'object') && (d[str] != 'array') | |
return r | |
}) | |
.map(function(str){ | |
return function (d) { return str + ': ' + '<b>' + textwrap(wordwrap(d[str].toString())) + '</b>' }}) | |
} | |
var tooltip = d3.select('.tooltip') | |
.classed('tooltip-hidden', false) | |
.style('opacity', 1) | |
.html('') | |
.selectAll('div') | |
.data(fieldFns).enter() | |
.append('div') | |
.html(function(fieldFns) { | |
var r = fieldFns(d) | |
return r | |
}) | |
} | |
// ------------------------- moveMoveTooltip | |
function moveMoveTooltip() { | |
var tooltip = d3.select('.tooltip') | |
if (!tooltip.size()) return | |
var e = d3.event, | |
x = e.clientX, | |
y = e.clientY, | |
doctop = (window.scrollY)? window.scrollY : (document.documentElement && document.documentElement.scrollTop)? document.documentElement.scrollTop : document.body.scrollTop, | |
n = tooltip.node(), | |
nBB = n.getBoundingClientRect() | |
tooltip.style('top', (y+doctop-nBB.height-18)+"px"); | |
tooltip.style('left', Math.min(Math.max(0, (x-nBB.width/2)), window.innerWidth - nBB.width)+"px"); | |
prevent(e) | |
} | |
function wordwrap (line, maxCharactersPerLine) { | |
var w = line.split(' '), | |
lines = [], | |
words = [], | |
maxChars = maxCharactersPerLine || 40, | |
l = 0; | |
w.forEach(function(d) { | |
if (l+d.length > maxChars) { | |
lines.push(words.join(' ')); | |
words.length = 0; | |
l = 0; | |
} | |
l += d.length; | |
words.push(d); | |
}); | |
if (words.length) { | |
lines.push(words.join(' ')); | |
} | |
return lines.join('<br/>') | |
} | |
function textwrap (line, maxCharactersPerLine) { | |
var c = line.split(''), | |
lines = [], | |
chars = [], | |
maxChars = maxCharactersPerLine || 40, | |
l = 0; | |
c.forEach(function(d) { | |
if (l+d.length > maxChars) { | |
lines.push(chars.join('')); | |
chars.length = 0; | |
l = 0; | |
} | |
l += d.length; | |
chars.push(d); | |
}); | |
if (chars.length) { | |
lines.push(chars.join(' ')); | |
} | |
return lines.join('<br/>') | |
} | |
function started(d) { | |
// if datum().tiped !== false | |
var node = d3.select(this) // selection | |
var datum = node.datum() // datum | |
createTooltip(node) | |
displayTooltip(node) | |
moveMoveTooltip(node) | |
} | |
function moved(d) { | |
var node = d3.select(this) // selection | |
var datum = node.datum() // datum | |
createTooltip(node) | |
displayTooltip(node) | |
moveMoveTooltip(node) | |
} | |
function ended(d) { | |
var node = d3.select(this) // set selection | |
var datum = node.datum() // set datum | |
d3.select('.tooltip') // select tooltip ref | |
.classed('tooltip-hidden', true) | |
.style('opacity', 0) | |
d3.selectAll('.tooltipped') | |
.classed('tooltipped', false) | |
} | |
function d3Control(selection) { | |
selection.on("mouseenter.tip", started) | |
selection.on("mousemove.tip", moved) | |
selection.on("mouseout.tip", ended) | |
} | |
selection.call(d3Control) | |
return d3Control | |
} | |
exports.layer = layer | |
exports.circles = circles | |
exports.nodes = nodes | |
exports.quad = quad | |
exports.time = time | |
exports.drag = drag | |
exports.pos = pos | |
exports.mov = mov | |
exports.tip = tip | |
})); |
<!DOCTYPE html> | |
<meta charset="utf-8"> | |
<head> | |
<script src="https://d3js.org/d3.v4.min.js"></script> | |
<script src="d3c.js"></script> | |
</head> | |
<body> | |
<script src="index.js"></script> | |
</body> | |
</html> |
/* ---------------------- */ | |
/* ref */ | |
/* ---------------------- */ | |
// https://twitter.com/monfera/status/761355001474187268 | |
/* ---------------------- */ | |
/* config */ | |
/* ---------------------- */ | |
var state = {} // state | |
state.epsilon = 1e-6 | |
state.tau = 2 * Math.PI | |
state.pi = Math.PI | |
state.distance2 = (s, t) => (t.x - s.x) * (t.x - s.x) + (t.y - s.y) * (t.y - s.y) | |
state.distance = (s, t) => Math.sqrt(state.distance2(s, t)) | |
state.quad = d3c.quad() // quadtree for multifind | |
.x(function(d) {return d.x}) | |
.y(function(d) {return d.y}) | |
state.diabonalv = d => state.quad.diagonal(d, v = 1.1) // cords vibration | |
state.debug = false | |
state.started = true | |
state.mousedown = function() { | |
if (state.debug && state.started) {simulation.stop(), state.started = false} | |
else {simulation.restart(), state.started = true} | |
} | |
state.linkId = (s, t) => s.idx + ':' + t.idx | |
state.width = 600 | |
state.height = 400 | |
state.imagePath = "./space.jpg" | |
state.raptorCount = 10 / 1 | |
state.raptorLineWidth = 1 | |
state.raptorFill = 'yellow' // "red" | |
state.raptorArmsStroke = "orange" | |
state.raptorArmsFactor = 4 / 1 // Infinity | |
state.raptorRadius = 3 // * 10 | |
state.raptorSearchRange = Infinity | |
state.raptorAttractionFactor = 1.1 // plays with arms factors against impact factors | |
state.raptorAbsorbfn = function(node) { return node.radius * state.raptorAttractionFactor} // collision effect | |
state.raptorRadiusPostAbsorb = (s, t) => Math.sqrt((s.radius * s.radius) + (t.radius * t.radius)) | |
state.raptorColorPostAbsorb = function(s, t) { | |
let scal = d3.scaleLinear() | |
.domain([state.preyCount, 0 ]) | |
.range([state.raptorFill, state.preyFill]) | |
return scal(state.preyLiving(state.nodes).length) | |
} | |
state.collideRadius = function(node) { return node.radius * state.prayImpactFactor} | |
state.preyCount = 3000 // / 30 // Infinity | |
state.preyRadius = 0.5 // * 10 | |
state.preyFill = 'grey' // 'blue' | |
state.preyStroke = 'blue' | |
state.preyStrokeWidth = 0 | |
state.preyPostFill = 'red' | |
state.preyPostStroke = 'yellow' | |
state.preyPostStrokeWidth = 0.2 | |
state.prayImpactFactor = state.raptorAttractionFactor * 2 || 1 // collision effect behavior factor | |
state.traceLineWidth = state.raptorRadius | |
state.traceStrokeStyleLight = "rgba(128, 0, 255, 0.02)" | |
state.traceStroke = 'yellow' // "red" | |
state.traceOpacity = 0.1 | |
state.traceFill = 'transparent' | |
state.traceLength = 5000 | |
state.tracePath = d => "M" + d.join("L") | |
/* ---------------------- */ | |
/* data */ | |
/* ---------------------- */ | |
state.raptorItems = d3.range(state.raptorCount).map(function(d, i) { | |
return { | |
radius: state.raptorRadius, | |
x: Math.random() * state.width, | |
y: Math.random() * state.height, | |
vx: 0.1, | |
vy: 0.5, | |
links: [], // ref to prey | |
type: 'raptor', | |
color: state.raptorFill, | |
idx: i | |
}}) | |
state.traceItems = d3.range(state.raptorCount).map(function(d, i) { | |
var p0 = [state.raptorItems[i].x, state.raptorItems[i].y] | |
return [p0] | |
}) | |
state.preyItems = d3.range(state.preyCount).map(function(d, i) { | |
var randomAngle = Math.random() * 2 * Math.PI | |
return { | |
radius: state.preyRadius, | |
x: Math.random() * state.width, | |
y: Math.random() * state.height, | |
vx: 5 * Math.cos(randomAngle), // browninan movement | |
vy: 5 * Math.sin(randomAngle), // as per Robert Monfera | |
saugs: [], // ref to raptors | |
type: 'prey', | |
color: state.preyFill, | |
idx: i | |
} | |
}) | |
state.preyLiving = d => d.filter(d => d.type === 'prey') // prey | |
.filter(d => d.radius > 1e-6) | |
state.links = state.raptorItems.reduce(function(p, c, i, a) { | |
return p.concat(c.links) | |
}, []) | |
state.nodes = [...state.raptorItems, ...state.preyItems] | |
/* ---------------------- */ | |
/* svg layer */ | |
/* ---------------------- */ | |
var svg = d3c.layer() | |
.on("mousedown", state.mousedown) | |
/* ---------------------- */ | |
/* image layer */ | |
/* ---------------------- */ | |
function showimage() { | |
var imgLayer = d3c.layer({cls: 'background', item: 'image.image', data: ['image']}).e | |
.attr("xlink:href", state.imagePath) | |
.attr("x", "0") | |
.attr("y", "0") | |
.attr("width", "600") | |
.attr("height", "400") | |
} | |
if (state.debug !== true) showimage() | |
/* ---------------------- */ | |
/* forces */ | |
/* ---------------------- */ | |
// container | |
var container = function(alpha, nodes) { | |
var node | |
var i | |
for (i = 0; i < nodes.length; i++) { | |
node = nodes[i] | |
if(Math.abs(node.x) < 0 + node.radius) { | |
node.vx *= -1 | |
} | |
if(Math.abs(node.x) > state.width - node.radius) { | |
node.vx *= -1 | |
} | |
if(Math.abs(node.y) < 0 + node.radius) { | |
node.vy *= -1 | |
} | |
if(Math.abs(node.y) > state.height - node.radius) { | |
node.vy *= -1 | |
} | |
} | |
} | |
/* ---------------------- */ | |
/* simulation */ | |
/* ---------------------- */ | |
var simulation = d3.forceSimulation(state.nodes) | |
.alphaDecay(0) | |
.velocityDecay(5e-4) | |
.force("collide", d3.forceCollide().radius(state.collideRadius).strength(2).iterations(1)) | |
.force("container", d => container(d, state.nodes)) | |
.on("tick", ticked) | |
/* ---------------------- */ | |
/* tick */ | |
/* ---------------------- */ | |
function ticked() { | |
if (state.debug === true) console.log('preys: ', state.preyLiving(state.nodes).length) | |
simulation | |
.nodes(state.nodes) | |
let quad = d3c.quad() | |
.x(function(d) {return d.x}) | |
.y(function(d) {return d.y}) | |
.addAll(state.preyLiving(state.nodes)) | |
for (let i = 0; i < state.raptorItems.length; i++) { | |
let s = state.raptorItems[i] | |
let ts = quad.findmany(s.x, s.y, state.raptorSearchRange, state.raptorArmsFactor) | |
let ls = [] | |
let hs = [] | |
for (let i = 0; i < ts.length; i++) { | |
let t = ts[i] | |
let ln = {source: s, target: t, idx: state.linkId(s, t)} | |
let hk = {source: t, target: s, idx: state.linkId(s, t)} | |
ls.push(ln) // raptor | |
hs.push(hk) // prey | |
let dst = state.distance(s, t) | |
let range = state.raptorAbsorbfn(s) | |
if (dst < range) { // absorbed | |
s.radius = state.raptorRadiusPostAbsorb(s, t) | |
s.color = state.raptorColorPostAbsorb(s, t) | |
t.radius = state.epsilon | |
t.isvoid = true | |
} | |
t.saugs = hs | |
} | |
s.links = ls | |
} | |
state.links = state.raptorItems.reduce(function(p, c, i, a) { | |
return p.concat(c.links) | |
}, []) | |
render(state) | |
} | |
/* ---------------------- */ | |
/* render */ | |
/* ---------------------- */ | |
function render(s) { | |
let state = Object.assign(s) | |
// links | |
var linksIds = (d, i) => state.linkId(d.source, d.target) | |
var linksLayer = d3c.layer({cls: 'links', item: 'path.linkPath', data: state.links, idfn: linksIds}) | |
linksLayer.m | |
.attr("d", state.diabonalv) | |
.style("fill", "transparent") | |
.style("stroke", state.raptorArmsStroke) | |
.style("stroke-width", "1px") | |
linksLayer.x | |
.remove() | |
// traces | |
var tracesLayer = d3c.layer({cls: 'traces', item: 'path.trace', data: state.traceItems}) | |
tracesLayer.m | |
.attr("d", function(d, i) { | |
d.push([state.raptorItems[i].x, state.raptorItems[i].y]) // add present | |
d.splice(0, d.length - state.traceLength) // remove past | |
var r = d ? state.tracePath(d) : null | |
return r | |
}) | |
.style("fill", function(d, i) { return state.traceFill }) | |
.style("stroke", function(d) { return state.traceStroke }) | |
.style("stroke-width", function(d) { return state.traceLineWidth }) | |
.style("opacity", state.traceOpacity) | |
// prey | |
var preysLayer = d3c.layer({cls: 'preys', item: 'circle.prey', data: state.preyItems}) | |
preysLayer.u | |
.style("fill", function(d, i) { return (d.isvoid === true) ? state.preyPostFill : state.preyFill }) | |
.style("stroke", function(d) { return (d.isvoid === true) ? state.preyPostStroke : state.preyFill }) | |
.style("stroke-width", function(d) { return (d.isvoid === true) ? state.preyPostStrokeWidth : state.preyStrokeWidth }) | |
.style("opacity", function(d, i) { return (d.isvoid === true) ? 0 : 1 }) | |
.attr("cx", function(d) { return d.x }) | |
.attr("cy", function(d) { return d.y }) | |
.attr("r", function(d) { return (d.isvoid === true) ? 10 : d.radius }) | |
preysLayer.e | |
.attr("cx", function(d) { return d.x }) | |
.attr("cy", function(d) { return d.y }) | |
.attr("r", function(d) {return d.radius }) | |
.style("fill", function(d, i) { return state.preyFill }) | |
.style("stroke", function(d) { return state.preyFill }) | |
.style("stroke-width", function(d) { return 0.2 }) | |
preysLayer.x | |
.attr("cx", function(d) { return d.x }) | |
.attr("cy", function(d) { return d.y }) | |
.style("fill", function(d, i) { return 'transparent' }) | |
.style("stroke", function(d) { return "red" }) | |
// raptors | |
var raptorsLayer = d3c.layer({cls: 'particules', item: 'circle', data: state.raptorItems}) | |
raptorsLayer.m | |
.attr("cx", function(d) { return d.x }) | |
.attr("cy", function(d) { return d.y }) | |
.attr("r", function(d) { return d.radius }) | |
.style("stroke-width", function(d) { return state.raptorLineWidth }) | |
.style("fill", function(d) { | |
return d.color }) | |
if (state.debug) d3c.drag(raptorsLayer.m) | |
} |