有人知道一种方法可以阻止一个元素跳回到上次被拖动的位置吗?请参阅下面的演示。只需点击 Jump ,然后在grey area
内拖动。
我尝试通过将zoom translate
属性更新为新值来修复它,但在ondrag(d3源代码中为translateTo
)期间会覆盖它。
origin
中的d3.behavior.zoom
是否等效?
//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"
});
g.h = h;
g.w = 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(" + [d.x, d.y] + ")"
});
}
})(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 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 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>
&#13;
答案 0 :(得分:0)
我的基本错误是使用强力移动由缩放行为管理的元素,而不是使用缩放行为来“按照它的方式”。这可以使用zoom.event(selection)
或更具体的zoom.translate([x,y]).event(selection)
缩放管理对象公开了这个......
g.jumpTo = function(p, t){
(t ? g.transition().duration(t) : g)
.call(zoom.translate(p).event)
};
...提供手动或程序化缩放服务。
//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());
}
g.h = h;
g.w = 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(" + [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.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 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() {
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>