【问题标题】:Force layout drag behaviour with transition on zoom but no transition on drag强制布局拖动行为,缩放时有过渡,但拖动时没有过渡
【发布时间】:2015-07-17 06:42:45
【问题描述】:

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

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

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

所以问题是如何仅在拖动事件上禁用过渡/持续时间?这甚至有可能吗?

【问题讨论】:

  • 您可以通过在相同的名称空间中对同一对象启动另一个零长度转换来中断转换。在您的情况下:g.transition().duration(0)。如果你想更好地管理转换冲突,那么你应该使用像g.transition("gTransition").duration(450)这样的命名转换,在这种情况下你可以用g.transition("gTransition").duration(0)中断它
  • 听起来不错,但怎么可能呢?我不知道如何将比例与翻译分开。我需要两者分开。但我不认为这是可能的。您的解决方案听起来像是您没有将它们分开。是的,在那种情况下这很容易。还是我误会你了,我的朋友?如果不是,请告诉我在 transform 语句中要写什么。
  • 好吧,我以为你说的是​​拖动节点时节点标签上的过渡。我误解了吗?啊,你是在说 setzoom 吗?
  • 好的,推荐的处理方法是使用drag + zoom“使用stopPropagation 允许拖动行为优先于平移。”
  • 谢谢。但是您的示例没有任何持续时间值,对吗?是的,正如我所说,想要缩放的持续时间,但没有拖拽的持续时间。 =)

标签: javascript d3.js force-layout


【解决方案1】:

第一阶段

我认为这很接近... 它只需要验证它是否能很好地适应节点上的拖动行为。

策略

  • 使用d3.event.sourceEvent.type 来检查鼠标移动
  • 使用d3.transform 增加当前变换状态
  • 转场 translatescale 用于鼠标滚轮事件,而无转场用于鼠标按钮事件

工作示例

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>

第二阶段

加入 FDG

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

策略

  • 应用与第一阶段相同的原则,但对鼠标滚轮事件进行跨浏览器测试。
  • 向节点添加标准force.drag
  • 挂钩 force.drag 以添加自定义行为
  • 仅在 shift-drag (或 shift-dragend)上修复节点
  • 对于触摸设备,如果在拖动开始时触摸 > 1,也修复节点

最后两点可以让固定节点在需要时轻松释放。

force.drag 钩子

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

工作示例

//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;}
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;
    }
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.3.0/css/font-awesome.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="https://gitcdn.xyz/repo/cool-Blue/40e550b1507cca31b0bb/raw/b83ceb0f8b4a2b6256f079f5887fc5243baedd4f/elapsed%2520time%25201.0.js"></script>
<div id="panel">
  <div id="inputDiv">
    <input id="update" type="button" value="update">
  </div>
  <div id="wrapAlpha">alpha:
    <div id="alpha"></div>
  </div>
  <div id="fdg">
</div>
<div id="viz"></div>

【讨论】:

  • @kwoxer,好的,你能澄清一下你做了什么来尝试它吗?在发布它们之前,我运行these two files 作为您站点中同名文件的替代品,它们运行完美。那是你做的吗?它们是直接替代品...如果您是这样做的,那么您有什么办法可以提供指向修改后站点的链接以及所做的更改?
  • @kwoxer,我刚刚在您的网站上再次尝试了它们,就像现在一样,它们运行良好。我所做的更改是消除svg.remove(); 之类的内容。如果你做得对,d3 允许你驱动关于 dom 的所有内容只需更改数据,然后只需更新 viz(更新也是初始化)。这使得实现即时隐藏所有 - 全部显示变得更加容易,而无需重新加载。这也避免了显示全部的混乱行为。我还展示了如何挂钩默认强制拖动,并保持默认行为,但仍然有您的自定义鼠标事件。
  • @kwoxer 你是怎么去的?有没有运气让它工作?我使用 Fiddler 作为代理运行它以重定向到我的文件版本,这就是我测试它的方式。如果你愿意,我们可以在 GitHub 中讨论它......
  • @kwoxer,好的,我们聊聊here
  • @kwoxer,我还在聊天室回复了您的问题...如果您愿意,现在可以聊天。
猜你喜欢
  • 2014-07-12
  • 2023-01-30
  • 2015-05-27
  • 1970-01-01
  • 2015-06-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多