我使用最新的强制布局d3js来创建交互式图形,如下所示:
要求是:
有人请帮助我1& 2。
此问题的背景是this相关问题。
谢谢。
答案 0 :(得分:2)
这个答案的背景是我对相关问题的回答here。
这个问题是关于为什么节点在发布后跳回来的主要原因是,force.drag
行为期间前一节点位置(d.px,d.py)和当前位置( dx,dy)实际上是逆转的。因此,当释放阻力时,初始速度因此反转,导致跳回行为
这实际上是由于在拖动事件上更新先前位置的拖动行为以及将先前值复制到每个位置计算的当前值的内部force.tick
方法。 (顺便提一下,我确信这是有充分理由的,我怀疑它与this有关...)
为了实现惯性拖动,需要更正此速度反转,因此在dragend
之后,当前和之前的点需要立即反转。
这是一个良好的开端,但还存在其他一些问题:
mouseover
上)在dragend
上重新建立,这往往会重新捕获节点并破坏惯性效应。第一个意味着如果在释放阻力和校正速度之间发生嘀嗒声,即紧接在dragend
之后,那么速度将为零并且节点将停止死亡。这种情况经常发生,令人讨厌。一种解决方案是保留d3.event.dx
和d3.event.dy
的记录,并使用这些记录在dragend
上修改(d.px,d.py)。这也避免了因前一点和当前点的反转而引起的问题。
可以通过延迟粘性节点行为恢复直到mouseout
之后来解决剩下的第二个问题。如果鼠标在 mouseout
之后立即重新进入节点,建议mouseout
之后的小延迟。
实现上述两个修正的基本策略是将前一个力布局中的拖动事件和后一个中的力布局中的鼠标事件挂钩。出于防御原因,各种钩子的标准回调存储在节点的datum
对象上,并在取消挂钩时从那里检索。
摩擦参数在代码中设置为1,这意味着它们无限期地保持其速度,看到稳定的惯性效果将其设置为0.9 ......我像弹跳球一样开玩笑。
$(function() {
var width = 1200,
height = 800;
var circles = [{
x: width / 2 + 100,
y: height / 2,
radius: 100
}, {
x: width / 2 - 100,
y: height / 2,
radius: 100
}, ],
nodeFill = "#006E3C";
var force = d3.layout.force()
.gravity(0)
.charge(-100)
.friction(1)
.size([width, height])
.nodes(circles)
.linkDistance(250)
.linkStrength(1)
.on("tick", tick)
.start();
SliderControl("#frictionSlider", "friction", force.friction, [0, 1], ",.3f");
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height)
.style("background-color", "white");
var nodes = svg.selectAll(".node");
nodes = nodes.data(circles);
nodes.exit().remove();
var enterNode = nodes.enter().append("g")
.attr("class", "node")
.call(force.drag);
console.log(enterNode);
//Add circle to group
enterNode.append("circle")
.attr("r", function(d) {
return d.radius;
})
.style("fill", "#006E3C")
.style("opacity", 0.6);
;
(function(d3, force) {
//Drag behaviour///////////////////////////////////////////////////////////////////
// hook drag behavior on force
//VELOCITY
// maintain velocity state in case a force tick occurs emidiately before dragend
// the tick wipes out the previous position
var dragVelocity = (function() {
var dx, dy;
function f(d) {
if (d3.event) {
dx = d3.event.dx;
dy = d3.event.dy;
}
return {
dx: dx,
dy: dy
}
};
f.correct = function(d) {
//tick occured and set px/y to x/y, re-establish velocity state
d.px = d.x - dx;
d.py = d.y - dy;
}
f.reset = function() {
dx = dy = 0
}
return f;
})()
//DRAGSTART HOOK
var stdDragStart = force.drag().on("dragstart.force");
force.drag().on("dragstart.force", myDragStart);
function myDragStart(d) {
var that = this,
node = d3.select(this);
nonStickyMouse();
dragVelocity.reset();
stdDragStart.call(this, d)
function nonStickyMouse() {
if (!d.___hooked) {
//node is not hooked
//hook mouseover/////////////////////////
//remove sticky node on mouseover behavior and save listeners
d.___mouseover_force = node.on("mouseover.force");
node.on("mouseover.force", null);
d.___mouseout_force = node.on("mouseout.force");
d.___hooked = true;
//standard mouseout will clear d.fixed
d.___mouseout_force.call(that, d);
}
//dissable mouseout/////////////////////////
node.on("mouseout.force", null);
}
}
//DRAG HOOK
var stdDrag = force.drag().on("drag.force");
force.drag().on("drag.force", myDrag);
function myDrag(d) {
var v, p;
//maintain back-up velocity state
v = dragVelocity();
p = {
x: d3.event.x,
y: d3.event.y
};
stdDrag.call(this, d)
}
//DRAGEND HOOK
var stdDragEnd = force.drag().on("dragend.force");
force.drag().on("dragend.force", myDragEnd);
function myDragEnd(d) {
var that = this,
node = d3.select(this);
//correct the final velocity vector at drag end
dragVelocity.correct(d)
//hook mouseout/////////////////////////
//re-establish standard behavior on mouseout
node.on("mouseout.force", function mouseout(d) {
myForceMouseOut.call(this, d)
});
stdDragEnd.call(that, d);
function myForceMouseOut(d) {
var timerID = window.setTimeout((function() {
var that = this,
node = d3.select(this);
return function unhookMouseover() {
//if (node.on("mouseover.force") != d.___mouseout_force) {
if (node.datum().___hooked) {
//un-hook mouseover and mouseout////////////
node.on("mouseout.force", d.___mouseout_force);
node.on("mouseover.force", d.___mouseover_force);
node.datum().___hooked = false;
}
}
}).call(this), 500);
return timerID;
}
}
})(d3, force);
function tick(e) {
//contain the nodes...
nodes.attr("transform", function(d) {
var r = 100;
if (d.x - r <= 0 && d.px > d.x) d.px -= (d.px - d.x) * 2;
if (d.x + r >= width && d.px < d.x) d.px += (d.x - d.px) * 2;
if (d.y - r <= 0 && d.py > d.y) d.py -= (d.py - d.y) * 2;
if (d.y + r >= height && d.py < d.y) d.py += (d.y - d.py) * 2;
return "translate(" + d.x + "," + d.y + ")";
});
//indicate status by color
nodes.selectAll("circle")
.style("fill", function(d, i) {
return ((d.___hooked && !d.fixed) ? "red" : nodeFill)
})
force.start();
}
function SliderControl(selector, title, value, domain, format) {
var accessor = d3.functor(value),
rangeMax = 1000,
_scale = d3.scale.linear().domain(domain).range([0, rangeMax]),
_$outputDiv = $("<div />", {
class: "slider-value"
}),
_update = function(value) {
_$outputDiv.css("left", 'calc( ' + (_$slider.position().left + _$slider.outerWidth()) + 'px + 1em )')
_$outputDiv.text(d3.format(format)(value));
$(".input").width(_$outputDiv.position().left + _$outputDiv.outerWidth() - _innerLeft)
},
_$slider = $(selector).slider({
value: _scale(accessor()),
max: rangeMax,
slide: function(e, ui) {
_update(_scale.invert(ui.value));
accessor(_scale.invert(ui.value)).start();
}
}),
_$wrapper = _$slider.wrap("<div class='input'></div>")
.before($("<div />").text(title + ":"))
.after(_$outputDiv).parent(),
_innerLeft = _$wrapper.children().first().position().left;
_update(_scale.invert($(selector).slider("value")))
};
});
body {
/*font-family: 'Open Sans', sans-serif;*/
font-family: 'Roboto', sans-serif;
}
svg {
outline: 1px solid black;
background-color: rgba(255, 127, 80, 0.6);
}
div {
display: inline-block;
}
#method,
#clear {
margin-left: 20px;
background-color: rgba(255, 127, 80, 0.6);
border: none;
}
#clear {
float: right;
}
#inputs {
font-size: 16px;
display: block;
width: 900px;
}
.input {
display: inline-block;
background-color: rgba(255, 127, 80, 0.37);
outline: 1px solid black;
position: relative;
margin: 10px 10px 0 0;
padding: 3px 10px;
}
.input div {
width: 60px;
}
.method {
display: block;
}
.ui-slider,
span.ui-slider-handle.ui-state-default {
width: 3px;
background: black;
border-radius: 0;
}
span.ui-slider-handle.ui-state-default {
top: calc(50% - 1em / 2);
height: 1em;
margin: 0;
border: none;
}
div.ui-slider-horizontal {
width: 200px;
margin: auto 10px auto 10px;
/*position: absolute;*/
/*bottom: 0.1em;*/
position: absolute;
bottom: calc(50% - 2.5px);
/*vertical-align: middle;*/
height: 5px;
border: none;
}
.slider-value {
position: absolute;
text-align: right;
}
input,
select,
button {
font-family: inherit;
font-size: inherit;
}
<link href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/themes/smoothness/jquery-ui.css" rel="stylesheet" />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/jquery-ui.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="inputs">
<div id="frictionSlider"></div>
</div>