Select Git revision
example-path-sl2.js
floopscr.js 7.79 KiB
// view force layout,
/* --------------------------- ---------------------------- */
/* ---------------------- FORCE LAYOUT ----------------------- */
/* --------------------------- ---------------------------- */
// ok, my thoughts on this
/*
when you're up with big programs, spend a day / a handful, just making the UI sing
- https://bl.ocks.org/mbostock/3750558
at the moment this is kind of 'fine'
- starting condition is mostly random (and elsewhere) - maybe some graph analysis
- for who-is-generally-downstream-of-whomst
- this is nice code golf for boring times when you have lots of graphs
- still not looking at links for layout force: do that first
- want to connect this notion with the 'design patterns' ides ...
- (links) find (comm/*) connected, arrange in a stack
- (view) finds (link) connected, also stackup ...
- the links / split through views -> this is actually a lot of the work,
- and it's not unimportant
*/
let blocks = new Array()
let flsimrun = false
let flsim = {}
let flnodes = []
let finAlpha = 0.1
let sizemultiple = 0.5
// happens when items added, deleted (changing topology)
let updateForceLoop = () => {
// init and/or update
if (!flsimrun && blocks.length > 3) {
// Case for starting sim
msgbox.write('starting force sim')
flsimrun = true
// start with two nodes
let positions = this.getAllHunkPositions()
let sizes = this.getAllHunkSizes()
for (let i in positions) {
let nd = {
index: i,
x: positions[i].x,
y: positions[i].y,
vx: 0,
vy: 0,
r: sizes[i].width * sizemultiple
}
flnodes.push(nd)
}
flsim = d3.forceSimulation(flnodes)
.force('charge', d3.forceManyBody().strength(250))
.force('center', d3.forceCenter(600, 600))
.force('collide', d3.forceCollide((node, i, nodes) => {
return node.r
}))
.alphaMin(finAlpha)
.on('tick', flTick)
.on('end', flEnd)
} else if (blocks.length <= 3) {
// donot
} else {
// case for adding / rming from sim
msgbox.write('UPD8 Force Sim')
let positions = this.getAllHunkPositions()
let sizes = this.getAllHunkSizes()
if (positions.length > flnodes.length) {
let last = positions.length - 1
//console.log('to add new node like', positions[last])
let nd = {
index: last,
x: positions[last].x,
y: positions[last].y,
vx: 0,
vy: 0,
r: sizes[last].width * sizemultiple
}
flnodes.push(nd)
// console.log('SIM adds now this', newNode.x, newNode.y)
} else {
//msgbox.write("SIM DELETE CASE NOT WRITTEN")
}
flsim.nodes(flnodes)
flsim.alpha(1)
.alphaMin(finAlpha)
.restart()
}
}
// happens when things perterbed in existing state (i.e. drags)
let kickForceLoop = () => {
// hmm... but fix the one you're dragging, say?
flsim.alpha(1).restart()
}
let flTick = () => {
// called on sim update
let blks = $(this.plane).children('.block').not('#NROL39_0').not('#TLView')
if (blks.length !== flnodes.length) {
console.log('FLOOP NODES MISMATCH', blks.length, flnodes.length)
} else {
for (let i = 0; i < blks.length; i++) {
blks[i].style.left = flnodes[i].x + 'px'
blks[i].style.top = flnodes[i].y + 'px'
}
}
this.drawLinks()
if ($(msgbox.zeCheckbox).prop('checked')) {
this.zoomExtents()
}
}
let flEnd = () => {
console.log('FIN DU SIM')
}
this.zoomExtents = () => {
// to zoom-extends
let psns = this.getAllHunkPositions()
let sizes = this.getAllHunkSizes()
// bless up, these are all in 0,0 relative space
let minxy = {
x: 0,
y: 0
}
let maxxy = {
x: 500,
y: 500
}
let maxx, minx, maxy, miny
for (let ind in psns) {
maxx = psns[ind].x + sizes[ind].width
minx = psns[ind].x
maxy = psns[ind].y + sizes[ind].height
miny = psns[ind].y
// max cases
if (maxx > maxxy.x) {
maxxy.x = maxx
}
if (maxy > maxxy.y) {
maxxy.y = maxy
}
// min cases
if (minx < minxy.x) {
minxy.x = minx
}
if (miny < minxy.y) {
minxy.y = miny
}
}
// margin
let margin = 100
minxy.x -= margin
minxy.y -= margin
maxxy.x += margin
maxxy.y += margin
// ok, compare bounding box to current frustrum ?
let ct = dt.readTransform(this.plane)
let wd = this.dom.clientWidth
let ht = this.dom.clientHeight
// to find scale, do
let pfsx = (wd) / (maxxy.x - minxy.x)
let pfsy = (ht) / (maxxy.y - minxy.y)
let pfs = Math.min(pfsx, pfsy)
// write em
ct.s = pfs
ct.x = -minxy.x * pfs
ct.y = -minxy.y * pfs
dt.writeTransform(this.plane, ct)
dt.writeBackgroundTransform(this.dom, ct)
}
this.getAllHunkPositions = () => {
// returns positions as numbers,
let nds = $(this.plane).children('.block')
let positions = new Array()
for (let nd of nds) {
if ($(nd).attr('id') === "NROL39_0" || $(nd).attr('id') === "TLView") {
//console.log('skip')
} else {
let pos = dt.readXY(nd)
pos.id = nd.id
positions.push(pos)
}
}
return positions
// should do transform here ?
}
this.getAllHunkSizes = () => {
let nds = $(this.plane).children('.block')
let sizes = new Array()
for (let nd of nds) {
if ($(nd).attr('id') === "NROL39_0" || $(nd).attr('id') === "TLView") {
//console.log('skip')
} else {
let sz = dt.readSize(nd)
sz.id = nd.id
sizes.push(sz)
}
}
return sizes
}
// here is where you rm'd drawing & moving
// from http://bl.ocks.org/natebates/273b99ddf86e2e2e58ff
var width = 960,
height = 500;
var nodes = d3.range(100).map(function(d, i) {
return {
width: ~~(Math.random() * 40 + 15),
height: ~~(Math.random() * 40 + 15),
};
}),
root = nodes[0],
color = d3.scale.category10();
var svg = d3.select('body').append('svg')
.attr('width', width)
.attr('height', height);
svg.selectAll('.rect')
.data(nodes.slice(1))
.enter().append('rect')
.attr('width', function(d) {
return d.width;
})
.attr('height', function(d) {
return d.height;
})
.style('fill', function(d, i) {
return color(i % 3);
})
.attr('transform', function(d) {
return 'translate(' + (-d.width / 2) + ',' + (-d.height / 2) + ')';
});
svg.on('mousemove', function() {
var p1 = d3.mouse(this);
root.px = p1[0];
root.py = p1[1];
force.resume();
});
// mouse node, position off screen initially
root.x = 2000;
root.y = 2000;
root.width = 0;
root.height = 0;
root.fixed = true;
var force = d3.layout.force()
.gravity(0.05)
.charge(function(d, i) {
return i ? -30 : -2000;
})
.nodes(nodes)
.size([width, height]);
force.on('tick', function(e) {
var q = d3.geom.quadtree(nodes),
i = 0,
n = nodes.length;
while (++i < n) {
q.visit(collide(nodes[i]));
}
svg.selectAll('rect')
.attr('x', function(d) {
return d.x;
})
.attr('y', function(d) {
return d.y;
});
});
force.start();
function collide(node) {
return function(quad, x1, y1, x2, y2) {
var updated = false;
if (quad.point && (quad.point !== node)) {
var x = node.x - quad.point.x,
y = node.y - quad.point.y,
xSpacing = (quad.point.width + node.width) / 2,
ySpacing = (quad.point.height + node.height) / 2,
absX = Math.abs(x),
absY = Math.abs(y),
l,
lx,
ly;
if (absX < xSpacing && absY < ySpacing) {
l = Math.sqrt(x * x + y * y);
lx = (absX - xSpacing) / l;
ly = (absY - ySpacing) / l;
// the one that's barely within the bounds probably triggered the collision
if (Math.abs(lx) > Math.abs(ly)) {
lx = 0;
} else {
ly = 0;
}
node.x -= x *= lx;
node.y -= y *= ly;
quad.point.x += x;
quad.point.y += y;
updated = true;
}
}
return updated;
};
}