From d2fe76f736ddfee4173282ccbaf4dca89d919e26 Mon Sep 17 00:00:00 2001 From: Nils Schneider Date: Tue, 31 Mar 2015 21:18:06 +0200 Subject: [PATCH] pan/zoom in forcelayout --- lib/forcegraph.js | 92 ++++++++++++++++++++++++++++++++++--------- scss/_forcegraph.scss | 5 +++ scss/main.scss | 5 ++- 3 files changed, 83 insertions(+), 19 deletions(-) diff --git a/lib/forcegraph.js b/lib/forcegraph.js index 681ecec..50a3b22 100644 --- a/lib/forcegraph.js +++ b/lib/forcegraph.js @@ -1,13 +1,9 @@ -// TODO -// - window size -// - avoid sidebar -// - pan to node -// - pan and zoom to link define(["d3"], function (d3) { return function (linkScale, sidebar, router) { var self = this var vis, link, node, label var nodesDict, linksDict + var zoomBehavior var force function nodeName(d) { @@ -34,9 +30,51 @@ define(["d3"], function (d3) { } function panzoom() { - vis.attr("transform", - "translate(" + d3.event.translate + ") " - + "scale(" + d3.event.scale + ")") + var translate = zoomBehavior.translate() + var scale = zoomBehavior.scale() + vis.attr("transform", "translate(" + translate + ") " + + "scale(" + scale + ")") + } + + function panzoomTo(a, b) { + var sidebarWidth = sidebar.getWidth() + var size = force.size() + + var targetWidth = Math.max(1, b[0] - a[0]) + var targetHeight = Math.max(1, b[1] - a[1]) + + var scaleX = size[0] / targetWidth + var scaleY = size[1] / targetHeight + var scaleMax = zoomBehavior.scaleExtent()[1] + var scale = 0.5 * Math.min(scaleMax, Math.min(scaleX, scaleY)) + + var centroid = [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2] + var x = -centroid[0] * scale + size[0] / 2 + var y = -centroid[1] * scale + size[1] / 2 + var translate = [x + sidebarWidth, y] + + zoomBehavior.scale(scale) + zoomBehavior.translate(translate) + + vis.transition().duration(500) + .attr("transform", "translate(" + translate + ") " + + "scale(" + scale + ")") + } + + function resize(initial) { + var sidebarWidth = sidebar.getWidth() + var width = el.offsetWidth - sidebarWidth + var height = el.offsetHeight + + if (initial === true) { + var translate = zoomBehavior.translate() + translate[0] += sidebarWidth + zoomBehavior.translate(translate) + } + + force.size([width, height]) + force.start() + panzoom() } function tickEvent() { @@ -59,9 +97,13 @@ define(["d3"], function (d3) { el.classList.add("graph") self.div = el + zoomBehavior = d3.behavior.zoom() + .scaleExtent([1/3, 3]) + .on("zoom", panzoom) + vis = d3.select(el).append("svg") .attr("pointer-events", "all") - .call(d3.behavior.zoom().on("zoom", panzoom)) + .call(zoomBehavior) .append("g") vis.append("g").attr("class", "links") @@ -69,13 +111,9 @@ define(["d3"], function (d3) { vis.append("g").attr("class", "labels").attr("pointer-events", "none") force = d3.layout.force() - .size([500, 500]) - .charge(-100) + .charge(-70) .gravity(0.05) - .friction(0.73) - .theta(0.8) .linkDistance(70) - .linkStrength(0.2) .on("tick", tickEvent) var draggableNode = d3.behavior.drag() @@ -83,6 +121,8 @@ define(["d3"], function (d3) { .on("drag", dragmove) .on("dragend", dragend) + window.addEventListener("resize", resize) + self.setData = function (data) { var links = data.graph.links.filter( function (d) { return !d.vpn @@ -111,7 +151,7 @@ define(["d3"], function (d3) { link.each( function (d) { if (d.source.node && d.target.node) - linksDict[linkId(d)] = this + linksDict[linkId(d)] = d }) var nodes = data.graph.nodes @@ -144,7 +184,7 @@ define(["d3"], function (d3) { node.each( function (d) { if (d.node) - nodesDict[d.node.nodeinfo.node_id] = this + nodesDict[d.node.nodeinfo.node_id] = d }) label = vis.select("g.labels") @@ -181,10 +221,13 @@ define(["d3"], function (d3) { node.selectAll("title").text(nodeName) + force.nodes(nodes) .links(links) - .alpha(0.1) - .start() + + resize(true) + + force.start() } self.resetView = function () { @@ -197,6 +240,11 @@ define(["d3"], function (d3) { node.classed("highlight", function (e) { return e.node === d && d !== undefined }) + + var n = nodesDict[d.nodeinfo.node_id] + + if (n) + panzoomTo([n.x, n.y], [n.x, n.y]) } self.gotoLink = function (d) { @@ -204,6 +252,14 @@ define(["d3"], function (d3) { link.classed("highlight", function (e) { return e === d && d !== undefined }) + + var l = linksDict[linkId(d)] + + if (l) { + var x = d3.extent([l.source, l.target], function (d) { return d.x }) + var y = d3.extent([l.source, l.target], function (d) { return d.y }) + panzoomTo([x[0], y[0]], [x[1], y[1]]) + } } return self diff --git a/scss/_forcegraph.scss b/scss/_forcegraph.scss index 29e7678..7d186fd 100644 --- a/scss/_forcegraph.scss +++ b/scss/_forcegraph.scss @@ -55,3 +55,8 @@ } } +@media screen and (max-width: $minscreenwidth) { + .graph { + height: 60vh; + } +} diff --git a/scss/main.scss b/scss/main.scss index 178edc8..16f59f6 100644 --- a/scss/main.scss +++ b/scss/main.scss @@ -1,5 +1,8 @@ @import '_leaflet'; @import '_leaflet.label'; + +$minscreenwidth: 60em; + @import '_forcegraph'; .stroke-first { @@ -284,7 +287,7 @@ button.close:after { } } -@media screen and (max-width: 60em) { +@media screen and (max-width: $minscreenwidth) { .sidebar .container { overflow: visible; height: auto;