简体   繁体   English

转换目标后的d3缩放行为

[英]d3 zoom behaviour after target has been translated

Does anybody know of a way to stop an element from jumping back to where it was last dragged to if translated since? 有没有人知道一种方法来阻止元素跳回到上次拖动到的位置? See the demo below. 请参见下面的演示。 Just click on Jump and then drag inside the grey area . 只需单击“ 跳转” ,然后在grey area内拖动即可。

I tried to fix it by updating the zoom translate property to the new value, but it gets overridden during ondrag ( translateTo in the d3 source code). 我试图通过将zoom translate属性更新为新值来修复它,但是在ondrag(d3源代码中的translateTo过程中它被覆盖。

Is there the equivalent to origin in d3.behavior.zoom ? d3.behavior.zoom有与origin等效的d3.behavior.zoom吗?


Example

 //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(dataSets[(dataSet = !dataSet, +dataSet)]) } })()); d3.select("#jump").on("click", (function() { return function() { var t = d3.transform(fdg.attr("transform")).translate.map(function(d) { return d + 200 }) fdg.attr("transform", "translate(" + t + ")"); } })()); ////////////////////////////////////////////////////////////////////////////////////////// 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, zoomStart = function() { return this }, zoomed = function() { return this }, zoom = d3.behavior.zoom().scaleExtent([0.4, 4]) .on("zoom", function(d, i, j) { zoomed.call(this, d, i, j); }) .on("zoomstart", function(d, i, j) { zoomStart.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" }); gh = h; gw = w; g.onZoom = function(cb) { zoomed = cb; }; g.onZoomStart = function(cb) { zoomStart = cb; }; d3.rebind(g, zoom, "translate") 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(" + [dx, dy] + ")" }); } })(instID)) .start(); svg.onZoom(zoomed); svg.onZoomStart(zoomStart); 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 dr }) .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 zoomStart() { svg.translate(d3.transform(content.attr("transform")).translate) } 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; d3.rebind(fdg, content, "attr") }; 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; margin: 0 .25em 3px 0; } 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"> <div id="panel"> <div id="inputDiv"> <input id="update" type="button" value="update"> <input id="jump" type="button" value="jump"> </div> <div id="wrapAlpha">alpha: <div id="alpha"></div> </div> <div id="fdg"> </div> </div> <div id="viz"></div> <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script> <script src="https://gitcdn.xyz/repo/cool-Blue/40e550b1507cca31b0bb/raw/b83ceb0f8b4a2b6256f079f5887fc5243baedd4f/elapsed%2520time%25201.0.js"></script> 

Sorted! 排序!

My basic mistake was to use brute-force to move the element which is managed by the zoom behaviour, instead of using the zoom behaviour to do it "it's way". 我的基本错误是使用蛮力来移动由缩放行为管理的元素,而不是使用缩放行为来做到这一点。 And this can be done with zoom.event(selection) or more specificaly zoom.translate([x,y]).event(selection) 这可以通过zoom.event(selection)或更具体地说zoom.translate([x,y]).event(selection)


Working solution 工作方案

The zoom management object exposes this... 缩放管理对象公开了此...

g.jumpTo = function(p, t){
    (t ? g.transition().duration(t) : g)
        .call(zoom.translate(p).event)
};

... to provide a manual or programmatic zoom service. ...提供手动或程序化缩放服务。

 //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.data(dataSets[(dataSet = !dataSet, +dataSet)]) } })()); d3.select("#jump").on("click", function() { var jumpXY = 50; fdg.jumpTo(d3.transform(fdg.attr("transform")) .translate.map(function(d) { return d + jumpXY }), 1000); }); ////////////////////////////////////////////////////////////////////////////////////////// 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.data(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, zoomStart = function() { return this }, zoomed = function() { return this }, container, zoom = d3.behavior.zoom().scaleExtent([0.4, 4]) .on("zoom", function(d, i, j) { onZoom.call(this, d, i, j); zoomed.call(this, d, i, j); }) .on("zoomstart", function(d, i, j) { var t = d3.transform(d3.select(this).attr("transform")); //zoom.translate(t.translate).scale(t.scale[0]); // assumes x and y scale is same onZoomStart.call(this, d, i, j); zoomStart.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("g#zoom: transform = translate ( margin.left , margin.top ); .call(zoom)") .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("event capture surface: style='pointer-events: none'") .style("fill", "#5c5c5c") .attr({ "dy": "1em", "dx": ".2em" }); function onZoomStart() { // zoom translate and scale are initially [0,0] and 1 // this needs to be aligned with the container to stop // jump back to zero before first jump transition var t = d3.transform(container.attr("transform")); zoom.translate(t.translate); zoom.scale(t.scale[0]); } function onZoom() { var e = d3.event.sourceEvent, isWheel = e && ((e.type == "mousewheel") || (e.type == "wheel")), t = d3.transform(container.attr("transform")); t.translate = d3.event.translate; t.scale = [d3.event.scale, d3.event.scale]; return isWheel ? zoomWheel.call(this, t, container) : zoomInst.call(this, t, container) } function zoomInst(t, target) { target.attr("transform", t.toString()); } function zoomWheel(t, target) { target.transition().duration(450).attr("transform", t.toString()); } gh = h; gw = w; g.zoom = zoom; g.container = function(selection) { var d3_data, d3_datum; if (selection) { container = g.selectAll(selection); // temporarily subclass container d3_data = container.data; d3_datum = container.datum; // need a reference to the update selection // so force data methods back to here container.data = function() { delete container.data; // remove the sub-classing return container = d3_data.apply(container, arguments) } container.datum = function() { delete container.datum; // remove the sub-classing return container = d3_datum.apply(container, arguments) } } return container; } g.onZoom = function(cb) { zoomed = cb; }; g.onZoomStart = function(cb) { zoomStart = cb; }; g.jumpTo = function(p, t) { (t ? g.transition().duration(t) : g) .call(zoom.translate(p).event) }; d3.rebind(g, zoom, "translate"); d3.rebind(g, zoom, "scale"); 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(); }) fdg = {}; function data(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(" + [dx, dy] + ")" }); } })(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.container("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 dr }) .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() { force.alpha(0.01); } fdg.force = force; fdg.jumpTo = svg.jumpTo; // zoom context services // content is the target for zoom movements in zoomed d3.rebind(fdg, content, "attr"); // access the current transform state in zoom listener coordinates d3.rebind(fdg, svg.zoom, "translate") }; fdg.data = data; 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; margin: 0 .25em 3px 0; } 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"> <div id="panel"> <div id="inputDiv"> <input id="update" type="button" value="update"> <input id="jump" type="button" value="jump"> </div> <div id="wrapAlpha">alpha: <div id="alpha"></div> </div> <div id="fdg"> </div> </div> <div id="viz"></div> <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script> <script src="https://gitcdn.xyz/repo/cool-Blue/40e550b1507cca31b0bb/raw/b83ceb0f8b4a2b6256f079f5887fc5243baedd4f/elapsed%2520time%25201.0.js"></script> 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM