在 D3 中编码链式转换的一种紧凑方式
a compact way to code chained transitions in D3
我必须应用两个长序列的链式转换,它们的不同之处主要在于某些元素的转换顺序,我正在寻找一种紧凑的编码方式。
假设(仅作为玩具示例)我有两个圆圈,我必须将以下颜色应用于第一个圆圈:
orange
-> purple
-> blue
-> yellow
;
和以下颜色到第二个:
blue
-> yellow
-> orange
-> purple
。
我试过下面的代码 (fiddle here),但它不起作用。哪种方法最紧凑?
var svg = d3.select('svg');
var dataSet = [20, 20];
var group=svg.append("g");
var circles = group.selectAll('circle')
.data(dataSet)
.enter()
.append('circle')
.attr("r",function(d){ return d })
.attr("cx",function(d, i){ return i * 100 + 50 })
.attr("cy",50)
.attr("fill",'black');
var t1 = d3
.transition()
.duration(1000)
.attr("fill","orange")
.transition()
.duration(1000)
.attr("fill","purple");
var t2 = d3
.transition()
.duration(1000)
.attr("fill","blue")
.transition()
.duration(1000)
.attr("fill","yellow");
group.select(":nth-child(1)")
.transition(t1).transition(t2);
group.select(":nth-child(2)")
.transition(t2).transition(t1);
如果您创建一个函数来应用转换,您可以将代码压缩得相当大:
/**
* Apply a transition with the appropriate delay to a selection
*
* @param sel: a d3 selection
* @param fill: the fill colour
* @param position: the position of the colour in the set of transitions
*/
function tr(sel, fill, position) {
sel.transition()
.duration(1000)
.delay(1000 * position)
.ease(d3.easeLinear)
.attr("fill", fill);
}
// example of use:
tr(group.select(":nth-child(1)"), 'blue', 0)
tr(group.select(":nth-child(2)"), 'red', 2)
进行中:
function tr(sel, fill, pos) {
sel.transition()
.duration(1000)
.delay(1000 * pos)
.ease(d3.easeLinear)
.attr("fill", fill);
}
var svg = d3.select('svg');
var dataSet = [20, 20];
var group = svg.append("g");
var circles = group.selectAll('circle')
.data(dataSet)
.enter()
.append('circle')
.attr("r",function(d){ return d })
.attr("cx",function(d, i){ return i * 100 + 50 })
.attr("cy",50)
.attr("fill",'black');
var colors = {
1: [ 'orange', 'purple', 'blue', 'yellow' ],
2: [ 'deepskyblue', 'deeppink', 'goldenrod', 'magenta']
};
Object.keys(colors).forEach(function(ix){
var el = group.select(":nth-child(" + ix + ")");
colors[ix].forEach( function(c,i) { tr(el, c, i); });
})
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg></svg>
有多种方法可以实现这一点。如另一个答案中所述,为此使用函数将使您的代码紧凑。我个人倾向于使用转换的结束事件从转换函数中触发下一个转换。
这种函数的一般形式如下:
function transition() {
// .... optional logic here.
d3.select(this) // do transition:
.transition()
.attr("fill", ... )
.on("end", transition); // and repeat.
}
可以用selection.each(transition)
调用
管理循环中当前 color/transition 的一种方法是使用自定义属性。下面我用.attr("i")
来记录:
var data = [
["orange","purple","blue","yellow"],
["blue","yellow","orange","purple"]
];
var svg = d3.select("svg");
var circles = svg.selectAll()
.data(data)
.enter()
.append("circle")
.attr("r", 20)
.attr("cx", function(d,i) { return i * 50 + 50; })
.attr("cy", 50)
.attr("fill", function(d) { return d[0]; })
.attr("i",0)
.each(transition);
// cycle endlessly:
function transition() {
var selection = d3.select(this);
// keep track of current value:
var i = selection.attr("i")
selection
.attr("i",i = ++i%4)
.transition()
.duration(1000)
.attr("fill", function(d) { return d[i] })
.on("end", transition);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
<svg width="500" height="300"></svg>
如果您的转换只是顺序(起点)不同,您可以稍微修改一下上面的方法:
var data = [50,75,100,125,150,175,200,225];
var colors = ["orange","purple","blue","yellow"];
var svg = d3.select("svg");
var circles = svg.selectAll()
.data(data)
.enter()
.append("circle")
.attr("r", 20)
.attr("cx", function(d) { return d; })
.attr("cy", 50)
.attr("fill", function(d,i) { return colors[i%4]; }) // set start fill
.attr("i", function(d,i) { return i%4; }) // record start position.
.each(transition);
function transition() {
var selection = d3.select(this);
var i = selection.attr("i");
i = ++i%colors.length;
selection.transition()
.duration(1000)
.attr("i",i)
.attr("fill", colors[i])
.on("end", transition);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
<svg width="500" height="300"></svg>
第一个我使用颜色集作为基准,第二个我只是使用位置访问带有 i
属性的颜色
上述方法使用自定义属性,但我们也可以使用数据。这让我们在某些方面更加熟悉 D3。
例如,我们可以有一个名为 color 的 属性 并在每次过渡时设置填充(再次假设我们有一个 colors 数组并且每个圆圈从其不同的点开始):
function transition() {
d3.select(this).transition()
.duration(1000)
.attr("fill", function(d) { return colors[++d.color%colors.length]; })
.on("end", transition);
}
var data = d3.range(8).map(function(d) { return {x: d*25+50}; })
var colors = ["orange","purple","blue","yellow"];
var svg = d3.select("svg");
var circles = svg.selectAll()
.data(data)
.enter()
.append("circle")
.attr("r", 20)
.attr("cx", function(d) { return d.x; })
.attr("cy", 50)
.attr("fill", function(d,i) { return colors[i%4]; }) // set start fill
.each(function(d,i) { d.color = i%4; }) // record start position.
.each(transition);
function transition() {
d3.select(this).transition()
.duration(1000)
.attr("fill", function(d) { return colors[++d.color%colors.length]; })
.on("end", transition);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
<svg width="500" height="300"></svg>
如果您希望转换重复 x 次,甚至以相同的颜色结束,我们也可以通过向基准添加新的 属性 来跟踪已完成的循环来相当轻松地做到这一点:
var data = d3.range(8).map(function(d) { return {x: d*25+50}; })
var colors = ["orange","purple","blue","yellow"];
var svg = d3.select("svg");
var circles = svg.selectAll()
.data(data)
.enter()
.append("circle")
.attr("r", 20)
.attr("cx", function(d) { return d.x; })
.attr("cy", 50)
.attr("fill", function(d,i) { return colors[i%4]; }) // set start fill
.each(function(d,i) { d.color = d.start = i%4; }) // record start position.
.each(transition);
var n = 8; // n cycles
function transition() {
d3.select(this).transition()
.duration(1000)
.attr("fill", function(d) { return colors[++d.color%colors.length]; })
.on("end", function(d) { if(d.color - d.start < n) transition.apply(this); else return null; });
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
<svg width="500" height="300"></svg>
有一些更懒惰的方法可以破坏性地修改数据(或其中的一部分),例如在这个只能 运行 一次的转换循环中使用 shift:
var data = [
["orange","purple","blue","yellow"],
["blue","yellow","orange","purple"]
];
var svg = d3.select("svg");
var circles = svg.selectAll()
.data(data)
.enter()
.append("circle")
.attr("r", 20)
.attr("cx", function(d,i) { return i * 50 + 50; })
.attr("cy", 50)
.attr("fill", function(d) { return d.shift(); })
.each(transition);
function transition() {
var selection = d3.select(this);
if(selection.datum().length) {
selection.transition()
.duration(1000)
.attr("fill", function(d) { return d.shift() })
.on("end", transition);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
<svg width="500" height="300"></svg>
您可能会看到 none 我的代码片段使用子选择器:这可以通过自定义属性、基准属性或更简单地在转换本身中使用 .attr("fill",function(d,i){
来区分奇数和甚至元素。不过,我并不反对这些选择器,只是需要进行额外的选择。
我必须应用两个长序列的链式转换,它们的不同之处主要在于某些元素的转换顺序,我正在寻找一种紧凑的编码方式。 假设(仅作为玩具示例)我有两个圆圈,我必须将以下颜色应用于第一个圆圈:
orange
-> purple
-> blue
-> yellow
;
和以下颜色到第二个:
blue
-> yellow
-> orange
-> purple
。
我试过下面的代码 (fiddle here),但它不起作用。哪种方法最紧凑?
var svg = d3.select('svg');
var dataSet = [20, 20];
var group=svg.append("g");
var circles = group.selectAll('circle')
.data(dataSet)
.enter()
.append('circle')
.attr("r",function(d){ return d })
.attr("cx",function(d, i){ return i * 100 + 50 })
.attr("cy",50)
.attr("fill",'black');
var t1 = d3
.transition()
.duration(1000)
.attr("fill","orange")
.transition()
.duration(1000)
.attr("fill","purple");
var t2 = d3
.transition()
.duration(1000)
.attr("fill","blue")
.transition()
.duration(1000)
.attr("fill","yellow");
group.select(":nth-child(1)")
.transition(t1).transition(t2);
group.select(":nth-child(2)")
.transition(t2).transition(t1);
如果您创建一个函数来应用转换,您可以将代码压缩得相当大:
/**
* Apply a transition with the appropriate delay to a selection
*
* @param sel: a d3 selection
* @param fill: the fill colour
* @param position: the position of the colour in the set of transitions
*/
function tr(sel, fill, position) {
sel.transition()
.duration(1000)
.delay(1000 * position)
.ease(d3.easeLinear)
.attr("fill", fill);
}
// example of use:
tr(group.select(":nth-child(1)"), 'blue', 0)
tr(group.select(":nth-child(2)"), 'red', 2)
进行中:
function tr(sel, fill, pos) {
sel.transition()
.duration(1000)
.delay(1000 * pos)
.ease(d3.easeLinear)
.attr("fill", fill);
}
var svg = d3.select('svg');
var dataSet = [20, 20];
var group = svg.append("g");
var circles = group.selectAll('circle')
.data(dataSet)
.enter()
.append('circle')
.attr("r",function(d){ return d })
.attr("cx",function(d, i){ return i * 100 + 50 })
.attr("cy",50)
.attr("fill",'black');
var colors = {
1: [ 'orange', 'purple', 'blue', 'yellow' ],
2: [ 'deepskyblue', 'deeppink', 'goldenrod', 'magenta']
};
Object.keys(colors).forEach(function(ix){
var el = group.select(":nth-child(" + ix + ")");
colors[ix].forEach( function(c,i) { tr(el, c, i); });
})
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg></svg>
有多种方法可以实现这一点。如另一个答案中所述,为此使用函数将使您的代码紧凑。我个人倾向于使用转换的结束事件从转换函数中触发下一个转换。
这种函数的一般形式如下:
function transition() {
// .... optional logic here.
d3.select(this) // do transition:
.transition()
.attr("fill", ... )
.on("end", transition); // and repeat.
}
可以用selection.each(transition)
管理循环中当前 color/transition 的一种方法是使用自定义属性。下面我用.attr("i")
来记录:
var data = [
["orange","purple","blue","yellow"],
["blue","yellow","orange","purple"]
];
var svg = d3.select("svg");
var circles = svg.selectAll()
.data(data)
.enter()
.append("circle")
.attr("r", 20)
.attr("cx", function(d,i) { return i * 50 + 50; })
.attr("cy", 50)
.attr("fill", function(d) { return d[0]; })
.attr("i",0)
.each(transition);
// cycle endlessly:
function transition() {
var selection = d3.select(this);
// keep track of current value:
var i = selection.attr("i")
selection
.attr("i",i = ++i%4)
.transition()
.duration(1000)
.attr("fill", function(d) { return d[i] })
.on("end", transition);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
<svg width="500" height="300"></svg>
如果您的转换只是顺序(起点)不同,您可以稍微修改一下上面的方法:
var data = [50,75,100,125,150,175,200,225];
var colors = ["orange","purple","blue","yellow"];
var svg = d3.select("svg");
var circles = svg.selectAll()
.data(data)
.enter()
.append("circle")
.attr("r", 20)
.attr("cx", function(d) { return d; })
.attr("cy", 50)
.attr("fill", function(d,i) { return colors[i%4]; }) // set start fill
.attr("i", function(d,i) { return i%4; }) // record start position.
.each(transition);
function transition() {
var selection = d3.select(this);
var i = selection.attr("i");
i = ++i%colors.length;
selection.transition()
.duration(1000)
.attr("i",i)
.attr("fill", colors[i])
.on("end", transition);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
<svg width="500" height="300"></svg>
第一个我使用颜色集作为基准,第二个我只是使用位置访问带有 i
属性的颜色
上述方法使用自定义属性,但我们也可以使用数据。这让我们在某些方面更加熟悉 D3。
例如,我们可以有一个名为 color 的 属性 并在每次过渡时设置填充(再次假设我们有一个 colors 数组并且每个圆圈从其不同的点开始):
function transition() {
d3.select(this).transition()
.duration(1000)
.attr("fill", function(d) { return colors[++d.color%colors.length]; })
.on("end", transition);
}
var data = d3.range(8).map(function(d) { return {x: d*25+50}; })
var colors = ["orange","purple","blue","yellow"];
var svg = d3.select("svg");
var circles = svg.selectAll()
.data(data)
.enter()
.append("circle")
.attr("r", 20)
.attr("cx", function(d) { return d.x; })
.attr("cy", 50)
.attr("fill", function(d,i) { return colors[i%4]; }) // set start fill
.each(function(d,i) { d.color = i%4; }) // record start position.
.each(transition);
function transition() {
d3.select(this).transition()
.duration(1000)
.attr("fill", function(d) { return colors[++d.color%colors.length]; })
.on("end", transition);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
<svg width="500" height="300"></svg>
如果您希望转换重复 x 次,甚至以相同的颜色结束,我们也可以通过向基准添加新的 属性 来跟踪已完成的循环来相当轻松地做到这一点:
var data = d3.range(8).map(function(d) { return {x: d*25+50}; })
var colors = ["orange","purple","blue","yellow"];
var svg = d3.select("svg");
var circles = svg.selectAll()
.data(data)
.enter()
.append("circle")
.attr("r", 20)
.attr("cx", function(d) { return d.x; })
.attr("cy", 50)
.attr("fill", function(d,i) { return colors[i%4]; }) // set start fill
.each(function(d,i) { d.color = d.start = i%4; }) // record start position.
.each(transition);
var n = 8; // n cycles
function transition() {
d3.select(this).transition()
.duration(1000)
.attr("fill", function(d) { return colors[++d.color%colors.length]; })
.on("end", function(d) { if(d.color - d.start < n) transition.apply(this); else return null; });
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
<svg width="500" height="300"></svg>
有一些更懒惰的方法可以破坏性地修改数据(或其中的一部分),例如在这个只能 运行 一次的转换循环中使用 shift:
var data = [
["orange","purple","blue","yellow"],
["blue","yellow","orange","purple"]
];
var svg = d3.select("svg");
var circles = svg.selectAll()
.data(data)
.enter()
.append("circle")
.attr("r", 20)
.attr("cx", function(d,i) { return i * 50 + 50; })
.attr("cy", 50)
.attr("fill", function(d) { return d.shift(); })
.each(transition);
function transition() {
var selection = d3.select(this);
if(selection.datum().length) {
selection.transition()
.duration(1000)
.attr("fill", function(d) { return d.shift() })
.on("end", transition);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
<svg width="500" height="300"></svg>
您可能会看到 none 我的代码片段使用子选择器:这可以通过自定义属性、基准属性或更简单地在转换本身中使用 .attr("fill",function(d,i){
来区分奇数和甚至元素。不过,我并不反对这些选择器,只是需要进行额外的选择。