我正在尝试找出沿着路径插入圆圈的最佳方式,就像迈克博斯托克在这个例子中所做的那样:http://bl.ocks.org/mbostock/1705868。但是,我不想像他那样设置一个过渡值,而是希望能够为每个点对点插值设置一个唯一的持续时间;例如,在x毫秒内将圆从节点[0]转换到节点[1],在y毫秒内从节点[1]转换到节点[2]等等。有没有办法在不将路径分割成a一堆较小的独立路径并连续沿着它们过渡?限制因素似乎是path.getTotalLength() - 有没有办法获得路径子集的长度?
transition();
function transition() {
circle.transition()
.duration(10000)
.attrTween("transform", translateAlong(path.node()))
.each("end", transition);
}
// Returns an attrTween for translating along the specified path element.
function translateAlong(path) {
var l = path.getTotalLength();
return function(d, i, a) {
return function(t) {
var p = path.getPointAtLength(t * l);
return "translate(" + p.x + "," + p.y + ")";
};
};
}
答案 0 :(得分:2)
事实上有一种方式,但它太丑陋了(因为它需要初始暴力计算),解决方案涉及以下内容:
首先,您需要一个具有节点之间转换时间的数组,在我的示例中为times
,例如,第一个元素3000
对应于从{{1}获取的以ms为单位的时间} [480,200]
[580,400]
数组中的数字列表,例如假设到达第一个点的线性时间是50ms,我们当前的时间是times
,我们必须将这个值在[0ms,50ms]之间映射到范围内的某个位置[0ms,3000ms],由公式t < 50ms
3000 * (t ms - 0ms) / (50ms - 0ms)
var points = [
[480, 200],
[580, 400],
[680, 100],
[780, 300],
[180, 300],
[280, 100],
[380, 400]
];
var times = [3000, 100, 5000, 100, 3000, 100, 1000]
var totalTime = times.reduce(function (a, b) {return a + b}, 0)
var svg = d3.select("body").append("svg")
.attr("width", 960)
.attr("height", 500);
var path = svg.append("path")
.data([points])
.attr("d", d3.svg.line()
.tension(0) // Catmull–Rom
.interpolate("cardinal-closed"));
svg.selectAll(".point")
.data(points)
.enter().append("circle")
.attr("r", 4)
.attr("transform", function(d) { return "translate(" + d + ")"; });
var circle = svg.append("circle")
.attr("r", 13)
.attr("transform", "translate(" + points[0] + ")");
function transition() {
circle.transition()
.duration(totalTime)
.ease('linear')
.attrTween("transform", translateAlong(path.node()))
.each("end", transition);
}
// initial computation, linear time needed to reach a point
var timeToReachPoint = []
var pathLength = path.node().getTotalLength();
var pointIndex = 0
for (var t = 0; pointIndex < points.length && t <= 1; t += 0.0001) {
var data = points[pointIndex]
var point = path.node().getPointAtLength(t * pathLength)
// if the distance to the point[i] is approximately less than 1 unit
// make `t` the linear time needed to get to that point
if (Math.sqrt(Math.pow(data[0] - point.x, 2) + Math.pow(data[1] - point.y, 2)) < 1) {
timeToReachPoint.push(t);
pointIndex += 1
}
}
timeToReachPoint.push(1)
function translateAlong(path) {
return function(d, i, a) {
return function(t) {
// TODO: optimize
var timeElapsed = t * totalTime
var acc = 0
for (var it = 0; acc + times[it] < timeElapsed; it += 1) {
acc += times[it]
}
var previousTime = timeToReachPoint[it]
var diffWithNext = timeToReachPoint[it + 1] - timeToReachPoint[it]
// range mapping
var placeInDiff = diffWithNext * ((timeElapsed - acc) / times[it])
var p = path.getPointAtLength((previousTime + placeInDiff) * pathLength)
return "translate(" + p.x + "," + p.y + ")"
}
}
}
transition();
path {
fill: none;
stroke: #000;
stroke-width: 3px;
}
circle {
fill: steelblue;
stroke: #fff;
stroke-width: 3px;
}