如何在D3中获得完整的顺时针旋转
how to obtain a complete clockwise rotation in D3
我想让一个正方形在旋转一半时暂停后,顺时针旋转一整圈。
下面的代码让它顺时针旋转一半,逆时针旋转另一半,这与我的预期相反。
var svg = d3.select('svg');
var s = svg.append("rect")
.attr("width", 50)
.attr("height", 50)
.attr("x", -25)
.attr("y", -25)
.attr("fill", "red")
.attr("transform", "translate(100,100)");
s
.transition()
.duration(1000)
.attr("transform", "translate(100,100) rotate(180)")
.transition()
.delay(1000)
.duration(1000)
.attr("transform", "translate(100,100) rotate(360)");
* {
margin: 0;
padding: 0;
border: 0;
}
body {
background: #ffd;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg></svg>
我可以破解这样的代码,将后半部分旋转分成两个四分之一顺时针旋转,但我想知道是否有更优雅的解决方案。
如果我使用 D3v3 它不会旋转到 180 度,如果我切换到 D3v4 它会旋转到 180 度。
您可以内插到 359.99。它不遵循文档中的字符串插值器,因为您还在转换中得到 scale()
。
translate(100, 100) rotate(359.989990234375) scale(0.999,0.999)
如果您编写自己的插值器,则不会发生这种情况。
var svg = d3.select('svg');
var s=svg.append("rect")
.attr("width",50)
.attr("height",50)
.attr("x",-25)
.attr("y",-25)
.attr("fill","red")
.attr("transform","translate(100,100) rotate(0)");
s
.transition()
.duration(3000)
.ease(d3.easeLinear)
.attr("transform","translate(100,100) rotate(180)")
.transition()
.delay(2000)
.duration(3000)
.ease(d3.easeLinear)
.attrTween("transform", () => d3.interpolate("translate(100,100) rotate(180)", "translate(100,100) rotate(360)") );
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg></svg>
这里的罪魁祸首是 D3 本身,而不是任何 SVG 规范。
问题是你的转换使用了d3.interpolateTransform
,我们可以看到here:
var fullname = namespace(name), i = fullname === "transform" ? interpolateTransform : interpolate;
这是v4的源码,不是v3的,但是原理是一样的,具体的v3代码可以看到:
var interpolate = nameNS == "transform" ? d3_interpolateTransform : d3_interpolate, name = d3.ns.qualify(nameNS);
然后,如果我们查看 interpolateTransform
的源代码(同样是 v4,但 v3 几乎相同),我们会看到它使用了一个名为 parseSvg
的函数来计算新变换的矩阵:
function parseSvg(value) {
if (value == null) return identity;
if (!svgNode) svgNode = document.createElementNS("http://www.w3.org/2000/svg", "g");
svgNode.setAttribute("transform", value);
if (!(value = svgNode.transform.baseVal.consolidate())) return identity;
value = value.matrix;
return decompose(value.a, value.b, value.c, value.d, value.e, value.f);
}
当您将 rotate(360)
传递给该函数时,该函数正在生成 0
作为矩阵中的最终值(实际值为 -2.4492935982947064e-16
,实际上为零)。
解决方案
这里有几种可能的解决方案,最简单的一种是使用 interpolateString
而不是 interpolateTransform
。
此外,由于您的代码使用 D3 v3,您可以利用 d3.transform()
,它已在 v4/v5:
中删除
d3.interpolateString(d3.transform(d3.select(this).attr("transform")), "translate(100,100) rotate(360)")
这是您的代码,其中包含该更改:
var svg = d3.select('svg');
var s = svg.append("rect")
.attr("width", 50)
.attr("height", 50)
.attr("x", -25)
.attr("y", -25)
.attr("fill", "red")
.attr("transform", "translate(100,100)");
s.transition()
.duration(1000)
.attr("transform", "translate(100,100) rotate(180)")
.transition()
.delay(1000)
.duration(1000)
.attrTween("transform", function() {
return d3.interpolateString(d3.transform(d3.select(this).attr("transform")), "translate(100,100) rotate(360)")
});
<svg></svg>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
我想让一个正方形在旋转一半时暂停后,顺时针旋转一整圈。 下面的代码让它顺时针旋转一半,逆时针旋转另一半,这与我的预期相反。
var svg = d3.select('svg');
var s = svg.append("rect")
.attr("width", 50)
.attr("height", 50)
.attr("x", -25)
.attr("y", -25)
.attr("fill", "red")
.attr("transform", "translate(100,100)");
s
.transition()
.duration(1000)
.attr("transform", "translate(100,100) rotate(180)")
.transition()
.delay(1000)
.duration(1000)
.attr("transform", "translate(100,100) rotate(360)");
* {
margin: 0;
padding: 0;
border: 0;
}
body {
background: #ffd;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg></svg>
我可以破解这样的代码,将后半部分旋转分成两个四分之一顺时针旋转,但我想知道是否有更优雅的解决方案。
如果我使用 D3v3 它不会旋转到 180 度,如果我切换到 D3v4 它会旋转到 180 度。
您可以内插到 359.99。它不遵循文档中的字符串插值器,因为您还在转换中得到 scale()
。
translate(100, 100) rotate(359.989990234375) scale(0.999,0.999)
如果您编写自己的插值器,则不会发生这种情况。
var svg = d3.select('svg');
var s=svg.append("rect")
.attr("width",50)
.attr("height",50)
.attr("x",-25)
.attr("y",-25)
.attr("fill","red")
.attr("transform","translate(100,100) rotate(0)");
s
.transition()
.duration(3000)
.ease(d3.easeLinear)
.attr("transform","translate(100,100) rotate(180)")
.transition()
.delay(2000)
.duration(3000)
.ease(d3.easeLinear)
.attrTween("transform", () => d3.interpolate("translate(100,100) rotate(180)", "translate(100,100) rotate(360)") );
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg></svg>
这里的罪魁祸首是 D3 本身,而不是任何 SVG 规范。
问题是你的转换使用了d3.interpolateTransform
,我们可以看到here:
var fullname = namespace(name), i = fullname === "transform" ? interpolateTransform : interpolate;
这是v4的源码,不是v3的,但是原理是一样的,具体的v3代码可以看到:
var interpolate = nameNS == "transform" ? d3_interpolateTransform : d3_interpolate, name = d3.ns.qualify(nameNS);
然后,如果我们查看 interpolateTransform
的源代码(同样是 v4,但 v3 几乎相同),我们会看到它使用了一个名为 parseSvg
的函数来计算新变换的矩阵:
function parseSvg(value) {
if (value == null) return identity;
if (!svgNode) svgNode = document.createElementNS("http://www.w3.org/2000/svg", "g");
svgNode.setAttribute("transform", value);
if (!(value = svgNode.transform.baseVal.consolidate())) return identity;
value = value.matrix;
return decompose(value.a, value.b, value.c, value.d, value.e, value.f);
}
当您将 rotate(360)
传递给该函数时,该函数正在生成 0
作为矩阵中的最终值(实际值为 -2.4492935982947064e-16
,实际上为零)。
解决方案
这里有几种可能的解决方案,最简单的一种是使用 interpolateString
而不是 interpolateTransform
。
此外,由于您的代码使用 D3 v3,您可以利用 d3.transform()
,它已在 v4/v5:
d3.interpolateString(d3.transform(d3.select(this).attr("transform")), "translate(100,100) rotate(360)")
这是您的代码,其中包含该更改:
var svg = d3.select('svg');
var s = svg.append("rect")
.attr("width", 50)
.attr("height", 50)
.attr("x", -25)
.attr("y", -25)
.attr("fill", "red")
.attr("transform", "translate(100,100)");
s.transition()
.duration(1000)
.attr("transform", "translate(100,100) rotate(180)")
.transition()
.delay(1000)
.duration(1000)
.attrTween("transform", function() {
return d3.interpolateString(d3.transform(d3.select(this).attr("transform")), "translate(100,100) rotate(360)")
});
<svg></svg>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>