【问题标题】:a compact way to code chained transitions in D3一种在 D3 中编码链式转换的紧凑方法
【发布时间】:2019-02-26 18:39:49
【问题描述】:

我必须应用两个长序列的链式转换,它们的区别主要在于某些元素的转换顺序,我正在寻找一种紧凑的编码方式。 假设(仅作为玩具示例)我有两个圆圈,我必须将以下颜色应用于第一个:

orange -> purple -> blue -> yellow;

以及第二个颜色如下:

blue -> yellow -> orange -> purple。

我已经尝试使用下面的代码(小提琴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); 

【问题讨论】:

  • 我的预期问题比上述问题更广泛。由于我的描述很差,所以被误解了。我写了一个新问题:stackoverflow.com/questions/52463487/…
  • 很公平。不过,为了结束这一点,您应该考虑接受其中一个答案,因为它们是 this 问题的可行解决方案。

标签: javascript d3.js transition


【解决方案1】:

有多种方法可以实现这一目标。如另一个答案中所述,为此使用函数将使您的代码保持紧凑。就我个人而言,我倾向于使用转换的结束事件从转换函数中触发下一个转换。

这类函数的一般形式如下:

function transition() {
   // ....  optional logic here.
   d3.select(this) // do transition:
     .transition()
     .attr("fill", ... )
     .on("end", transition); // and repeat.
}

可以用selection.each(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 的属性,并在为每个过渡设置填充时增加该属性(再次假设我们有一个颜色数组,并且每个圆圈从其上的不同点开始):

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>

您可能会看到我的 sn-ps 都没有使用子选择器:这可以通过自定义属性、基准属性或更简单地在转换本身中使用 .attr("fill",function(d,i){ 来区分奇数和偶数元素来避免。不过,我对这些选择器没有意见,它只是需要进行额外的选择。

【讨论】:

    【解决方案2】:

    如果你创建一个函数来应用转换,你可以压缩你的代码:

    /**
     * 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>

    【讨论】:

    • 简洁的解决方案 - 我喜欢使用颜色对象进行选择并设置过渡的想法。
    猜你喜欢
    • 2020-11-22
    • 2016-09-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-26
    • 1970-01-01
    • 2016-06-29
    • 2020-05-03
    相关资源
    最近更新 更多