强制布局拖动行为,在缩放时转换但在拖动时没有转换

时间:2015-07-17 06:42:46

标签: javascript d3.js force-layout

我认为问题很简单。我有这个网站可以让你理解我的意思: http://arda-maps.org/familytree/ 因此,如果您在屏幕上添加一些人,您就可以拖动缩放视图。

缩放完全没问题。缩放的持续时间很长。但我不喜欢拖动的持续时间,并希望在那里禁用它。基本上这是代码:

g.transition().duration(450).attr("transform", "translate(" + zoombuttonTranslate + ")scale(" + zoombuttonScale + ")");

所以问题是如何在拖动事件中禁用转换/持续时间?这有可能吗?

1 个答案:

答案 0 :(得分:2)

第一阶段

我认为这很接近...... 只需要验证它对节点上的拖动行为会很好。

Stategy

  • 使用d3.event.sourceEvent.type检查mousemove
  • 使用d3.transform
  • 扩充当前转换状态
  • 过渡翻译缩放鼠标滚轮事件,没有鼠标按钮事件转换

工作示例



var width = 600, height = 200-16,
    margin = {top: 25, right: 5, bottom: 5, left: 5},
    w = width - margin.left - margin.right,
    h = height - margin.top - margin.bottom,

    zoom = d3.behavior.zoom().scaleExtent([0.4, 4])
      .on("zoom", zoomed),
    svg = d3.select("#viz").attr({width: width, height: height})
      .append("g")
      .attr("transform", "translate(" + margin.left + "," + margin.top + ")")
      .call(zoom),
    transText = svg.append("text")
      .text("transform = translate ( margin.left , margin.top )")
      .style("fill", "#5c5c5c")
      .attr("dy", "-.35em")
    surface = svg.append("rect")
      .attr({width: w, height: h})
      .style({"pointer-events": "all", fill: "#ccc", "stroke-width": 3, "stroke": "#fff"}),
    surfaceText = svg.append("text")
      .text("pointer-events: all")
      .style("fill", "#5c5c5c")
      .attr({"dy": "1em", "dx": ".2em"})
    content = svg.append("g").attr("id", "content")
      .attr("transform", "translate(0,0)"),
    contentText = content.append("text")
    .text("transform = translate ( d3.event.translate ) scale ( d3.event.scale )")
    .style("fill", "#5c5c5c")
    .attr({"dy": 50, "dx": 20})
    content.selectAll("rect")
      .data([[20,60],[140,60]])
      .enter().append("rect")
      .attr({height: 50, width: 50})
      .style({"stroke-width": 3, "stroke": "#ccc"})
      .each(function(d){
        d3.select(this).attr({x: d[0], y: d[1]});
      });

  function zoomStart(){

  }
  function zoomed(){
    return d3.event.sourceEvent.buttons ? zoomDrag.call(this) : zoomScale.call(this)
  }
  function zoomDrag(){
  var t = d3.transform(content.attr("transform"));
    t.translate = d3.event.translate;
    content.attr("transform", t.toString());
  }
  function zoomScale(){
    var t = d3.transform(content.attr("transform"));
    t.translate = d3.event.translate; t.scale = d3.event.scale;
    content.transition().duration(450).attr("transform", t.toString());
  }

svg {
      outline: 1px solid #282f51;
      pointer-events: all;
    }
    g {
      outline: 1px solid red;
      shape-rednering: "geometricPrecision";
    }

<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<svg id="viz"></svg>
&#13;
&#13;
&#13;

第二阶段

合并FDG

由于FDG必须位于画布容器内,因此必须停止节点级事件传播到画布。这是在OP代码中通过使用自定义拖动行为,在 dragstart 上停止传播并添加一些force.drag行为(加上设置d.fixed = true {{1 force.drag ). This is great if you don't mind losing some of the force.drag`行为。

策略

  • 应用与第一阶段相同的原则,但对鼠标滚轮事件进行跨浏览器测试。
  • 将标准features like sticking nodes on mouseover. This is nice for capturing small, energetic nodes though. So, in order to get the best of both worlds, you can hook the添加到节点
  • 挂钩force.drag以添加自定义行为
  • 仅修复 shift -drag 上的节点(或 shift -dragend)
  • 用于触摸设备,如果触摸也可以修复节点&gt; 1在dragstart

最后两点允许在需要时轻松释放固定节点。

force.drag hook

force.drag

工作示例

&#13;
&#13;
        //hook force.drag behaviour
        var stdDragStart = force.drag().on("dragstart.force");
        force.drag()
            .on("dragstart", function(d){
                //prevent dragging on the nodes from dragging the canvas
                d3.event.sourceEvent.stopPropagation();
                stdDragStart.call(this, d);
            });
&#13;
//debug panel/////////////////////////////////////////////////////////////////////////////
var alpha = d3.select("#alpha").text("waiting..."),
		cog = d3.select("#wrapAlpha").insert("i", "#fdg").classed("fa fa-cog fa-spin", true).datum({instID: null}),
		fdgInst = d3.select("#fdg");
elapsedTime = ElapsedTime("#panel", {margin: 0, padding: 0})
	.message(function (id) {
		return 'fps : ' + d3.format(" >8.3f")(1/this.aveLap())
	});
elapsedTime.consoleOn = true;

alpha.log = function(e, instID) {
	elapsedTime.mark().timestamp();
	alpha.text(d3.format(" >8.4f")(e.alpha));
	fdgInst.text("fdg instance: " + instID);
};

d3.select("#update").on("click", (function() {
	var dataSet = false;
	return function() {
		//fdg.force.stop();
		fdg(dataSets[(dataSet = !dataSet, +dataSet)])
	}
})());
//////////////////////////////////////////////////////////////////////////////////////////
var dataSets = [{
				"nodes"    : [
					{"name": "node1", "r": 10},
					{"name": "node2", "r": 10},
					{"name": "node3", "r": 30},
					{"name": "node4", "r": 15}
				],
				"edges": [
					{"source": 2, "target": 0},
					{"source": 2, "target": 1},
					{"source": 2, "target": 3}
				]
			},
			{
				"nodes":[
					{"name": "node1", "r": 20},
					{"name": "node2", "r": 10},
					{"name": "node3", "r": 30},
					{"name": "node4", "r": 15},
					{"name": "node5", "r": 10},
					{"name": "node6", "r": 10}
				],
				"edges":[
					{"source": 2, "target": 0},
					{"source": 2, "target": 1},
					{"source": 2, "target": 3},
					{"source": 2, "target": 4},
					{"source": 2, "target": 5}
				]
			}
		],
		svg = SVG({width: 600, height: 200-34, margin: {top: 25, right: 5, bottom: 5, left: 5}}, "#viz"),
		fdg = FDG(svg, alpha.log);

fdg(dataSets[0]);

function SVG (size, selector){
	//delivers an svg background with zoom/drag context in the selector element
	//if height or width is NaN, assume it is a valid length but ignore margin
	var margin = size.margin || {top: 0, right: 0, bottom: 0, left: 0},
			unitW = isNaN(size.width), unitH = isNaN(size.height),
			w = unitW ? size.width : size.width - margin.left - margin.right,
			h = unitH ? size.height : size.height - margin.top - margin.bottom,
			zoomed = function(){return this},

			zoom = d3.behavior.zoom().scaleExtent([0.4, 4])
				.on("zoom", function(d, i, j){
					zoomed.call(this, d, i, j);
				}),

			svg = d3.select(selector).selectAll("svg").data([["transform root"]]);
			svg.enter().append("svg");
			svg.attr({width: size.width, height: size.height});

	var g = svg.selectAll("#zoom").data(id),
			gEnter = g.enter().append("g")
				.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
				.call(zoom)
				.attr({class: "outline", id: "zoom"}),
			zoomText = gEnter.append("text")
				.text("transform = translate ( margin.left , margin.top )")
				.style("fill", "#5c5c5c")
				.attr("dy", "-.35em"),
			surface = gEnter.append("rect")
				.attr({width: w, height: h})
				.style({"pointer-events": "all", fill: "#ccc", "stroke-width": 3, "stroke": "#fff"}),
			surfaceText = gEnter.append("text")
				.text("pointer-events: none")
				.style("fill", "#5c5c5c")
				.attr({"dy": "1em", "dx": ".2em"});

	g.h = h;
	g.w = w;
	g.onZoom = function(cb){zoomed = cb;};

	return g;
}
function FDG (svg, tickLog) {
	var instID = Date.now();
	force = d3.layout.force()
		.size([svg.w, svg.h])
		.charge(-1000)
		.linkDistance(50)
		.on("end", function(){
			// manage dead instances of force
			// only stop if this instance is the current owner
			if(cog.datum().instID != instID) return true;
			cog.classed("fa-spin", false);
			elapsedTime.stop();
		})
		.on("start", function(){
			// mark as active and brand the insID to establish ownership
			cog.classed("fa-spin", true).datum().instID = instID;
			elapsedTime.start();
		});

	function fdg(data) {
				force
					.nodes(data.nodes)
					.links(data.edges)
					.on("tick", (function(instID) {
						return function(e) {
							if(tickLog) tickLog.call(this, e, instID);
							lines.attr("x1", function(d) {
								return d.source.x;
							}).attr("y1", function(d) {
								return d.source.y;
							}).attr("x2", function(d) {
								return d.target.x;
							}).attr("y2", function(d) {
								return d.target.y;
							});
							node.attr("transform", function(d) {
								return "translate(" + [d.x, d.y] + ")"
							});
						}
					})(instID))
					.start();

		svg.onZoom(zoomed);

		hookDrag(force.drag(), "dragstart.force", function(d) {
			// prevent dragging on the nodes from dragging the canvas
			var e = d3.event.sourceEvent;
			e.stopPropagation();
			d.fixed = e.shiftKey || e.touches && (e.touches.length > 1);
		});
		hookDrag(force.drag(), "dragend.force", function(d) {
			// prevent dragging on the nodes from dragging the canvas
			var e = d3.event.sourceEvent;
			d.fixed = e.shiftKey || d.fixed;
		});

		var content = svg.selectAll("g#fdg").data([data]);
		content.enter().append("g").attr({"id": "fdg", class: "outline"});

		var contentText = content.selectAll(".contentText")
			.data(["transform = translate ( d3.event.translate ) scale ( d3.event.scale )"])
			.enter().append("text").classed("contentText", true)
			.text(id)
			.style("fill", "#5c5c5c")
			.attr({"dy": 20, "dx": 20});

		var lines = content.selectAll(".links")
					.data(linksData),
				linesEnter = lines.enter()
					.insert("line", d3.select("#nodes") ? "#nodes" : null)
					.attr("class", "links")
					.attr({stroke: "steelblue", "stroke-width": 3});
		var nodes = content.selectAll("#nodes")
					.data(nodesData),
				nodesEnter = nodes.enter().append("g")
					.attr("id", "nodes"),
				node = nodes.selectAll(".node")
					.data(id),
				newNode = node.enter().append("g")
					.attr("class", "node")
					.call(force.drag),
				circles = newNode.append("circle")
					.attr({class: "content"})
					.attr("r", function(d) {return d.r})
					.style({"fill": "red", opacity: 0.8});

		lines.exit().remove();
		node.exit().remove();

		function nodesData(d) {
			return [d.nodes];
		}

		function linksData(d) {
			return d.edges;
		}

		function hookDrag(target, event, hook) {
			//hook force.drag behaviour
			var stdDragStart = target.on(event);
			target.on(event, function(d) {
				hook.call(this, d);
				stdDragStart.call(this, d);
			});
		}

		function zoomed(){
			var e = d3.event.sourceEvent,
					isWheel = e && ((e.type == "mousewheel") || (e.type == "wheel"));
			force.alpha(0.01);
			return isWheel ? zoomWheel.call(this) : zoomInst.call(this)
		}
		function zoomInst(){
			var t = d3.transform(content.attr("transform"));
			t.translate = d3.event.translate; t.scale = d3.event.scale;
			content.attr("transform", t.toString());
		}
		function zoomWheel(){
			var t = d3.transform(content.attr("transform"));
			t.translate = d3.event.translate; t.scale = d3.event.scale;
			content.transition().duration(450).attr("transform", t.toString());
		}

		fdg.force = force;

	};
	return fdg

}
function id(d){return d;}
&#13;
svg {
      outline: 1px solid #282f51;
      pointer-events: all;
      overflow: visible;
    }

    g.outline {
      outline: 1px solid red;
    }

    #panel div {
      display: inline-block;
      margin: 0 .25em 3px 0; 
      
    }
    #panel div div {
      white-space: pre;
    }
    div#inputDiv {
      white-space: normal;
      display: inline-block;
    }

    .node {
      cursor: default;
    }

    text {
      font-size: 8px;
    }
&#13;
&#13;
&#13;