链接转换
Chaining transitions
我正在尝试在 D3
中链接一个转换,但我不太清楚如何让它正常工作。我已经通读了一些例子,我觉得我遗漏了一些关于 select 离子的东西(可能是因为我的 select 离子跨越了不同的层)。
您可以在下面看到一个示例,单击 'Objectives' 应该会为 "Service" 节点设置光脉冲动画。一旦脉冲到达,我希望服务节点通过过渡填充为橙色。目前我知道我的 selection 会填满两个圆圈 - 我会尽快解决这个问题。
然而,当脉冲到达时什么也没有发生:
var t0 = svg.transition();
var t1 = t0.selectAll(".pulse")
.duration(2000)
.ease("easeInOutSine")
.attr("cx", function(d) { return d.x2; })
.attr("cy", function(d) { return d.y2; });
t1.selectAll(".node")
.style("fill", "#F79646");
我似乎能够进行更改的唯一方法是将代码的最后一位更改为:
t0.selectAll(".node")
.style("fill", "#F79646");
然而,这会导致节点立即填充,而不是等待脉冲到达。感觉 selection 不是 "expanding" 到 select .node
实例,但我不太确定
var nodes = [
{ x: 105, y: 105, r: 55, color: "#3BAF4A", title: "Objectives" },
{ x: 305, y: 505, r: 35, color: "#F79646", title: "Service" }
];
var links = [
{ x1: 105, y1: 105, x2: 305, y2: 505 }
];
var svg = d3.select("svg");
var relationshipLayer =svg.append("g").attr("id", "relationships");
var nodeLayer = svg.append("g").attr("id", "nodes");
// Add the nodes
var nodeEnter = nodeLayer.selectAll("circle").data(nodes).enter();
var nodes = nodeEnter.append("g")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")";})
.on("click", function (d) {
d3.select(this)
.select("circle")
.transition()
.style("stroke", "#397F42")
.style("fill", "#3BAF4A");
pulse(d);
});
var circles = nodes.append("circle")
.attr("class", "node")
.attr("r", function (d) { return d.r; })
.style("fill", "#1C1C1C")
.style("stroke-width", "4px")
.style("stroke", function (d) { return d.color; });
var texts = nodes.append("text")
.text(function (d) { return d.title; })
.attr("dx", function(d) { return -d.r / 2; })
.style("fill", "white");
function pulse(d) {
function distanceFunction(x1, y1, x2, y2) {
var a = (x2 - x1) * (x2 - x1);
var b = (y2 - y1) * (y2 - y1);
return Math.sqrt(a + b);
};
var lineFunction = d3.svg.line()
.x(function (d) { return d.x; })
.y(function (d) { return d.y; })
.interpolate("linear");
var lines = relationshipLayer
.selectAll("line")
.data(links)
.enter()
.append("line")
.attr("x1", function(d) { return d.x1; })
.attr("y1", function(d) { return d.y1; })
.attr("x2", function(d) { return d.x2; })
.attr("y2", function(d) { return d.y2; })
.attr("stroke-dasharray", function(d) { return distanceFunction(d.x1, d.y1, d.x2, d.y2); })
.attr("stroke-dashoffset", function(d) { return distanceFunction(d.x1, d.y1, d.x2, d.y2); });
var pulse = relationshipLayer
.selectAll(".pulse")
.data(links)
.enter()
.append("circle")
.attr("class", "pulse")
.attr("cx", function(d) { return d.x1; })
.attr("cy", function(d) { return d.y1; })
.attr("r", 50);
lines.transition()
.duration(2000)
.ease("easeInOutSine")
.attr("stroke-dashoffset", 0);
var t0 = svg.transition();
var t1 = t0.selectAll(".pulse")
.duration(2000)
.ease("easeInOutSine")
.attr("cx", function(d) { return d.x2; })
.attr("cy", function(d) { return d.y2; });
t1.selectAll(".node")
.style("fill", "#F79646");
};
svg {
background: #222234;
width: 600px;
height: 600px;
font-size: 10px;
text-align: center;
font-family: 'Open Sans', Arial, sans-serif;
}
circle {
fill: url(#grad1);
}
line {
fill: none;
stroke: #fff;
stroke-width: 2px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<svg id="svg">
<defs>
<radialGradient id="grad1" cx="50%" cy="50%" r="50%" fx="50%" fy="50%">
<stop offset="5%" style="stop-color:rgb(255,255,255); stop-opacity:1" />
<stop offset="10%" style="stop-color:rgb(255,255,255); stop-opacity:0.8" />
<stop offset="20%" style="stop-color:rgb(255,255,255); stop-opacity:0.6" />
<stop offset="60%" style="stop-color:rgb(255,255,255);stop-opacity:0.0" />
</radialGradient>
</defs>
</svg>
您没有看到第二次转换的变化的原因是它没有应用到任何东西。您的第一个转换的 selection 包含具有 class pulse
的所有元素,然后您 selecting 具有 class [=13= 的元素] 来自这第一个selection的元素。没有同时具有两个 class 的元素,因此您的 selection 是空的,更改不会应用到任何元素。
一般来说,您不能按照当前在更改 select 离子时使用的方式链接转换。相反,使用转换的 .each()
事件处理程序,它允许您安装在转换完成时执行的处理函数。在您的情况下,它看起来像这样:
svg.selectAll(".pulse")
.transition()
.duration(2000)
.ease("easeInOutSine")
.attr("cx", function(d) { return d.x2; })
.attr("cy", function(d) { return d.y2; })
.each("end", function() {
svg.selectAll(".node")
.transition()
.duration(2000)
.style("fill", "#F79646");
});
这将 select 所有具有 class node
的元素,并通过过渡将它们的填充更改为橙色。
上面的代码有两个问题 -- 首先,正如您已经观察到的,它改变了 所有 节点的填充,而不仅仅是目标,其次, end
事件处理程序针对转换中的每个 每个 元素执行,而不仅仅是一次。对于您的特定示例,这不是问题,因为您只有一个 link 是动画的,但如果您有多个,则该功能(以及因此的过渡)将被执行不止一次。
这两个问题可以用相同的代码很容易地解决。这个想法是过滤 node
元素的 selection 以仅包括该行的目标。一种方法是将直线的目标坐标与 selection:
中元素的坐标进行比较
svg.selectAll(".pulse")
.transition()
.duration(2000)
.ease("easeInOutSine")
.attr("cx", function(d) { return d.x2; })
.attr("cy", function(d) { return d.y2; })
.each("end", function(d) {
svg.selectAll(".node")
.filter(function(e) {
return e.x == d.x2 && e.y == d.y2;
})
.transition()
.duration(2000)
.style("fill", "#F79646");
});
处理函数的参数d
是绑定到正在转换的元素的数据,其中包含目标坐标。在 filter()
行之后,selection 将只包含该行移动到的圆。只要目标不同,多行多次执行此代码是安全的。
完成演示 here。
我正在尝试在 D3
中链接一个转换,但我不太清楚如何让它正常工作。我已经通读了一些例子,我觉得我遗漏了一些关于 select 离子的东西(可能是因为我的 select 离子跨越了不同的层)。
您可以在下面看到一个示例,单击 'Objectives' 应该会为 "Service" 节点设置光脉冲动画。一旦脉冲到达,我希望服务节点通过过渡填充为橙色。目前我知道我的 selection 会填满两个圆圈 - 我会尽快解决这个问题。
然而,当脉冲到达时什么也没有发生:
var t0 = svg.transition();
var t1 = t0.selectAll(".pulse")
.duration(2000)
.ease("easeInOutSine")
.attr("cx", function(d) { return d.x2; })
.attr("cy", function(d) { return d.y2; });
t1.selectAll(".node")
.style("fill", "#F79646");
我似乎能够进行更改的唯一方法是将代码的最后一位更改为:
t0.selectAll(".node")
.style("fill", "#F79646");
然而,这会导致节点立即填充,而不是等待脉冲到达。感觉 selection 不是 "expanding" 到 select .node
实例,但我不太确定
var nodes = [
{ x: 105, y: 105, r: 55, color: "#3BAF4A", title: "Objectives" },
{ x: 305, y: 505, r: 35, color: "#F79646", title: "Service" }
];
var links = [
{ x1: 105, y1: 105, x2: 305, y2: 505 }
];
var svg = d3.select("svg");
var relationshipLayer =svg.append("g").attr("id", "relationships");
var nodeLayer = svg.append("g").attr("id", "nodes");
// Add the nodes
var nodeEnter = nodeLayer.selectAll("circle").data(nodes).enter();
var nodes = nodeEnter.append("g")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")";})
.on("click", function (d) {
d3.select(this)
.select("circle")
.transition()
.style("stroke", "#397F42")
.style("fill", "#3BAF4A");
pulse(d);
});
var circles = nodes.append("circle")
.attr("class", "node")
.attr("r", function (d) { return d.r; })
.style("fill", "#1C1C1C")
.style("stroke-width", "4px")
.style("stroke", function (d) { return d.color; });
var texts = nodes.append("text")
.text(function (d) { return d.title; })
.attr("dx", function(d) { return -d.r / 2; })
.style("fill", "white");
function pulse(d) {
function distanceFunction(x1, y1, x2, y2) {
var a = (x2 - x1) * (x2 - x1);
var b = (y2 - y1) * (y2 - y1);
return Math.sqrt(a + b);
};
var lineFunction = d3.svg.line()
.x(function (d) { return d.x; })
.y(function (d) { return d.y; })
.interpolate("linear");
var lines = relationshipLayer
.selectAll("line")
.data(links)
.enter()
.append("line")
.attr("x1", function(d) { return d.x1; })
.attr("y1", function(d) { return d.y1; })
.attr("x2", function(d) { return d.x2; })
.attr("y2", function(d) { return d.y2; })
.attr("stroke-dasharray", function(d) { return distanceFunction(d.x1, d.y1, d.x2, d.y2); })
.attr("stroke-dashoffset", function(d) { return distanceFunction(d.x1, d.y1, d.x2, d.y2); });
var pulse = relationshipLayer
.selectAll(".pulse")
.data(links)
.enter()
.append("circle")
.attr("class", "pulse")
.attr("cx", function(d) { return d.x1; })
.attr("cy", function(d) { return d.y1; })
.attr("r", 50);
lines.transition()
.duration(2000)
.ease("easeInOutSine")
.attr("stroke-dashoffset", 0);
var t0 = svg.transition();
var t1 = t0.selectAll(".pulse")
.duration(2000)
.ease("easeInOutSine")
.attr("cx", function(d) { return d.x2; })
.attr("cy", function(d) { return d.y2; });
t1.selectAll(".node")
.style("fill", "#F79646");
};
svg {
background: #222234;
width: 600px;
height: 600px;
font-size: 10px;
text-align: center;
font-family: 'Open Sans', Arial, sans-serif;
}
circle {
fill: url(#grad1);
}
line {
fill: none;
stroke: #fff;
stroke-width: 2px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<svg id="svg">
<defs>
<radialGradient id="grad1" cx="50%" cy="50%" r="50%" fx="50%" fy="50%">
<stop offset="5%" style="stop-color:rgb(255,255,255); stop-opacity:1" />
<stop offset="10%" style="stop-color:rgb(255,255,255); stop-opacity:0.8" />
<stop offset="20%" style="stop-color:rgb(255,255,255); stop-opacity:0.6" />
<stop offset="60%" style="stop-color:rgb(255,255,255);stop-opacity:0.0" />
</radialGradient>
</defs>
</svg>
您没有看到第二次转换的变化的原因是它没有应用到任何东西。您的第一个转换的 selection 包含具有 class pulse
的所有元素,然后您 selecting 具有 class [=13= 的元素] 来自这第一个selection的元素。没有同时具有两个 class 的元素,因此您的 selection 是空的,更改不会应用到任何元素。
一般来说,您不能按照当前在更改 select 离子时使用的方式链接转换。相反,使用转换的 .each()
事件处理程序,它允许您安装在转换完成时执行的处理函数。在您的情况下,它看起来像这样:
svg.selectAll(".pulse")
.transition()
.duration(2000)
.ease("easeInOutSine")
.attr("cx", function(d) { return d.x2; })
.attr("cy", function(d) { return d.y2; })
.each("end", function() {
svg.selectAll(".node")
.transition()
.duration(2000)
.style("fill", "#F79646");
});
这将 select 所有具有 class node
的元素,并通过过渡将它们的填充更改为橙色。
上面的代码有两个问题 -- 首先,正如您已经观察到的,它改变了 所有 节点的填充,而不仅仅是目标,其次, end
事件处理程序针对转换中的每个 每个 元素执行,而不仅仅是一次。对于您的特定示例,这不是问题,因为您只有一个 link 是动画的,但如果您有多个,则该功能(以及因此的过渡)将被执行不止一次。
这两个问题可以用相同的代码很容易地解决。这个想法是过滤 node
元素的 selection 以仅包括该行的目标。一种方法是将直线的目标坐标与 selection:
svg.selectAll(".pulse")
.transition()
.duration(2000)
.ease("easeInOutSine")
.attr("cx", function(d) { return d.x2; })
.attr("cy", function(d) { return d.y2; })
.each("end", function(d) {
svg.selectAll(".node")
.filter(function(e) {
return e.x == d.x2 && e.y == d.y2;
})
.transition()
.duration(2000)
.style("fill", "#F79646");
});
处理函数的参数d
是绑定到正在转换的元素的数据,其中包含目标坐标。在 filter()
行之后,selection 将只包含该行移动到的圆。只要目标不同,多行多次执行此代码是安全的。
完成演示 here。