Skip to content

Instantly share code, notes, and snippets.

@sifbuilder
Last active August 28, 2016 21:58
Show Gist options
  • Save sifbuilder/8be1e3ab5589602e4540d4e849271fe2 to your computer and use it in GitHub Desktop.
Save sifbuilder/8be1e3ab5589602e4540d4e849271fe2 to your computer and use it in GitHub Desktop.
#d3js d3raptor: space trees and collision forces
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)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment