// 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; }; }