diff --git a/lib/forcegraph.js b/lib/forcegraph.js index 4b28245..2cc5d34 100644 --- a/lib/forcegraph.js +++ b/lib/forcegraph.js @@ -1,5 +1,5 @@ -define(['d3-selection', 'd3-force', 'd3-zoom', 'd3-drag', 'utils/math', 'forcegraph/draw'], - function (d3Selection, d3Force, d3Zoom, d3Drag, math, draw) { +define(['d3-selection', 'd3-force', 'd3-zoom', 'd3-drag', 'd3-timer', 'd3-ease', 'd3-interpolate', 'utils/math', 'forcegraph/draw'], + function (d3Selection, d3Force, d3Zoom, d3Drag, d3Timer, d3Ease, d3Interpolate, math, draw) { 'use strict'; return function (config, linkScale, sidebar, router) { @@ -14,10 +14,12 @@ define(['d3-selection', 'd3-force', 'd3-zoom', 'd3-drag', 'utils/math', 'forcegr var intNodes = []; var dictNodes = {}; var intLinks = []; + var movetoTimer; var NODE_RADIUS_DRAG = 10; var NODE_RADIUS_SELECT = 15; var LINK_RADIUS_SELECT = 12; + var ZOOM_ANIMATE_DURATION = 350; var ZOOM_MIN = 1 / 8; var ZOOM_MAX = 3; @@ -32,9 +34,43 @@ define(['d3-selection', 'd3-force', 'd3-zoom', 'd3-drag', 'utils/math', 'forcegr draw.setMaxArea(canvas.width, canvas.height); } - function moveTo(x, y) { - transform.x = (canvas.width + sidebar()) / 2 - x * transform.k; - transform.y = canvas.height / 2 - y * transform.k; + function transformPosition(p) { + transform.x = p.x; + transform.y = p.y; + transform.k = p.k; + } + + function moveTo(callback, forceMove) { + clearTimeout(movetoTimer); + if (!forceMove && force.alpha() > 0.3) { + movetoTimer = setTimeout(function timerOfMoveTo() { + moveTo(callback); + }, 300); + return; + } + var result = callback(); + var x = result[0]; + var y = result[1]; + var k = result[2]; + var end = { k: k }; + + end.x = (canvas.width + sidebar()) / 2 - x * k; + end.y = canvas.height / 2 - y * k; + + var start = { x: transform.x, y: transform.y, k: transform.k }; + + var interpolate = d3Interpolate.interpolateObject(start, end); + + var timer = d3Timer.timer(function (t) { + if (t >= ZOOM_ANIMATE_DURATION) { + timer.stop(); + return; + } + + var v = interpolate(d3Ease.easeQuadInOut(t / ZOOM_ANIMATE_DURATION)); + transformPosition(v); + window.requestAnimationFrame(redraw); + }); } function onClick() { @@ -111,7 +147,8 @@ define(['d3-selection', 'd3-force', 'd3-zoom', 'd3-drag', 'utils/math', 'forcegr .force('x', d3Force.forceX().strength(0.02)) .force('y', d3Force.forceY().strength(0.02)) .force('collide', d3Force.forceCollide()) - .on('tick', redraw); + .on('tick', redraw) + .alphaDecay(0.015); var drag = d3Drag.drag() .subject(function () { @@ -192,40 +229,40 @@ define(['d3-selection', 'd3-force', 'd3-zoom', 'd3-drag', 'utils/math', 'forcegr }; self.resetView = function resetView() { - draw.setHighlight(null); - transform.k = (ZOOM_MIN + 1) / 2; - moveTo(0, 0); - redraw(); + moveTo(function calcToReset() { + draw.setHighlight(null); + return [0, 0, (ZOOM_MIN + 1) / 2]; + }, true); }; self.gotoNode = function gotoNode(d) { - for (var i = 0; i < intNodes.length; i++) { - var n = intNodes[i]; - if (n.o.node.nodeinfo.node_id !== d.nodeinfo.node_id) { - continue; + moveTo(function calcToNode() { + for (var i = 0; i < intNodes.length; i++) { + var n = intNodes[i]; + if (n.o.node.nodeinfo.node_id !== d.nodeinfo.node_id) { + continue; + } + draw.setHighlight({ type: 'node', o: n.o.node }); + return [n.x, n.y, (ZOOM_MAX + 1) / 2]; } - draw.setHighlight({ type: 'node', o: n.o.node }); - transform.k = (ZOOM_MAX + 1) / 2; - moveTo(n.x, n.y); - break; - } - redraw(); + return [0, 0, (ZOOM_MIN + 1) / 2]; + }); }; self.gotoLink = function gotoLink(d) { - draw.setHighlight({ type: 'link', o: d }); - for (var i = 0; i < intLinks.length; i++) { - var l = intLinks[i]; - if (l.o !== d) { - continue; + moveTo(function calcToLink() { + draw.setHighlight({ type: 'link', o: d }); + for (var i = 0; i < intLinks.length; i++) { + var l = intLinks[i]; + if (l.o !== d) { + continue; + } + return [(l.source.x + l.target.x) / 2, (l.source.y + l.target.y) / 2, (ZOOM_MAX / 2) + ZOOM_MIN]; } - moveTo((l.source.x + l.target.x) / 2, (l.source.y + l.target.y) / 2); - break; - } - redraw(); + return [0, 0, (ZOOM_MIN + 1) / 2]; + }); }; - self.gotoLocation = function gotoLocation() { // ignore };