我有一个关于attrTween的问题(有时是tween())。

我将自定义补间功能理解为

在“ attrTween('d'”参数之后,
我定义了自定义函数。
因此,我编写了如下的自定义函数。

        d3.selectAll('circle#circles1')
          .transition()
          .attrTween('d',function(){

            let interpolator=d3.interpolateArray(sdata.vader,sdata1.vader);
            return function(t){
                return d3.select(this).attr('cy',interpolator(t))
            }
          })


我的意图是


  对于我绘制的所有圈子,进行过渡。过渡
  是attrTween。所做的更改基于绑定到
  界。原始数据数组是sdata,而cy值在
  sdata是sdata.vader。过渡正在走向
  sdata1.sdata1的cy值为sdata1.vader。
  
  为了访问每个圆的所有cy值,我使用了
  d3.select(this).attr('cy')


但是,没有显示错误消息,但也没有制作动画。

我对自定义补间功能有什么误解?
谁能帮我修复此代码?

先感谢您。

完整的代码在下面的链接中。

https://codepen.io/jotnajoa/pen/WNQeEBE

最佳答案

示例代码中存在多个问题,这不是最小的问题。提供minimal, reproducible example确实有助于解决问题。


对多个元素使用HTML Id。


在HTML中,id属性必须唯一。在此,将ID分配给圈子组。为此,应使用class属性,而不是id

.attr('id','circles1')


应该:

.attr('class','circles1')


因此,attrTween应该查找类为circle1的圆圈,而不是ID为#circle1的唯一圆圈。

d3.selectAll('circle#circles1')


应该

d3.selectAll('.circles1')



ID(或类)分配在错误的位置。


circles1类是在创建圆之前分配的,因此该说明适用于空选择。创建圆圈之后,应立即设置class属性。

.attr('id','circles1')
.enter()
  .append('circle')


应该

.enter()
  .append('circle')
  .attr('class','circles1')



补间错误的属性


要过渡的属性是圆的cy属性,而不是路径的d属性。因此

.attrTween('d',function(){


应该

.attrTween('cy',function(){



插值错误的数据


sdata.vadersdata1.vader不存在,sdatasdata1似乎是对象数组,而对象确实具有vader属性。

如果项目在两个数组中的顺序相同,则可能需要d.vader.vader中的相应sdata1(可能为sdata1[i].vader)。


内插原始量度而不是坐标。


cy最初定义为:

height-yscale(d.vader)


在插值器功能中,也应使用比例功能。

attrTween函数调用变为:

  .attrTween('cy',function(d, i){
    //console.log( i, height-yscale(d.vader), height-yscale(sdata1[i].vader))
    let interpolator=d3.interpolateArray(height-yscale(d.vader), height-yscale(sdata1[i].vader));

    return function(t) { return interpolator(t)}
  })



在不需要的地方使用attrTween


对于此用例,只需简单地用attr转换圆就足够了,无需定义插值器。

d3会将圆的位置从原始位置移动到目的地,并隐式插值。

d3.selectAll('.circles1')
  .transition()
  .duration(2000)
      .attr('cy',function(d, i){
      return height-yscale(sdata1[i].vader)
  })


为了演示的目的,我添加了很长的时间,以使圆圈明显移动到正确的位置。一旦处于最终位置,它们便消失了,因为它们位于粉红色圆圈的下方。

附言只要相关,同一组更正适用于circles2组。

下面的代码段中的解决方案演示,因为Codepen不允许在不创建帐户的情况下保存修改。



var svg;
var xscale;
var yscale;
var sdata;
var xAxis;
var yAxis;
var width=1500;
var height=500;
var margin=50;
var duration =250;
var vader ='vader'
var textblob='textblob'
var delay =5000;
var tbtrue=false;
var areas
var circles1,circles2;
var sdata1,sdata2

d3.csv('https://raw.githubusercontent.com/jotnajoa/Javascript/master/tweetdata.csv').then(function(data){
    svg=d3.select('body').append('svg').attr('width',width).attr('height',height)

    var parser = d3.timeParse("%m/%d/%y")

    // data를 처리했고, date parser 하는 법 다시한번 명심하자.
    sdata = data;
    sdata.forEach(function(d){
        d.vader = +d.vader;
        d.textblob= + d.textblob;
        d.date=parser(d.date)

    })




    // scale을 정해야 함. 나중에 brushable한 범위로 고쳐야함. nice()안하면 정렬도안되고, 첫번째 엔트리 미싱이고
    // 난리도 아님.

    xscale=d3.scaleTime()
    .domain(d3.extent(sdata, function(d) {return d.date }))
    .range([0,width*9/10])
    .nice()



    yscale =d3.scaleLinear()
    .domain(d3.extent([-1,1]))
    .range([height*4/5,height*1/5])
    .nice()

    //yaxis는 필요 없을 것 같은데.

    //캔버스에 축을 그려야 함 단, translate해서 중간에 걸치게 해야함.

    svg.append('g').attr('class','xaxis')
       .call(d3.axisBottom(xscale))
       .attr('transform','translate('+margin+','+height*1/2+')')


    //sdata plotting

    var circles = svg.append('g').attr('class','circles')
    var area = svg.append('g').attr('class','pathline')

    firststage();
    //generator로 데이터를 하나씩 떨어뜨리도록 한다.
    function firststage(){
    function* vaderdropping(data){
        for( let i=0;i<data.length;i++){
            if( i%50==0) yield svg.node();

            let cx = margin+xscale(data[i].date)
            let cy = height-yscale(data[i].vader)

        circles.append('circle')
               .attr('cx',cx)
               .attr('cy',0)
               .transition()
               .duration(duration)
               .ease(d3.easeBounce)
               .attr('cy',cy)
               .attr('r',3)
               .style('fill','rgba(230, 99, 99, 0.528)')
        }
        yield svg.node()

    }
    //generator 돌리는 부분

    let vadergen = vaderdropping(sdata);
    let result = vadergen.next()
    let interval = setInterval(function(){
        if(!result.done) {
          vadergen.next();
        }
        else {
         clearInterval(interval)

        }
     }, 100);
    setTimeout(secondstage,5000)
}


     function secondstage(){
     function* textblobdropping(data){
        for( let i=0;i<data.length;i++){
            if( i%50==0) yield svg.node();

            let cx = margin+xscale(data[i].date)
            let cy = height-yscale(data[i].textblob)

        circles.append('circle')
               .attr('cx',cx)
               .attr('cy',0)
               .transition()
               .duration(duration)
               .ease(d3.easeBounce)
               .attr('cy',cy)
               .attr('r',3)
               .style('fill','rgba(112, 99, 230, 0.528)')
        }
        yield svg.node()

    }
    //generator 돌리는 부분

    let textblobgen = textblobdropping(sdata);
    let tresult = textblobgen.next()
    let tinterval = setInterval(function(){
        if(!tresult.done) {
          textblobgen.next();
        }
        else {
         clearInterval(tinterval)

        }
     }, 100);
     setTimeout(thirdstage,2500)
    }

    function thirdstage(){

        //진동을 만들기 위해서,
        //베이다와 텍스트 블랍 값을 플립한거다 (제발 워크 아웃하길...)
        //그 다음 트윈으로 sdata 와 sdata1을 왔다갔다 하게하면 되지않을까?
             sdata1 = sdata.map(function(x){
                var y={};
                y['date']=x.date;
                y['vader']=x.textblob;
                y['textblob']=x.vader;
                return y});
             sdata2 = sdata.map(function(x){

                var y={};
                    y['date']=x.date;
                    y['vader']=0;
                    y['textblob']=0;
                    return y});

            d3.selectAll('circle').transition()
            .duration(3500)
            .style('fill','rgba(1, 1, 1, 0.228)')

            //areas는 일종의 함수다, 에리아에다가 데이터를 먹이면,
            //에리아를 그리는 역할을 하는것임.

            areas = d3.area()
            .x(function(d){return margin+xscale(d.date)})
            .y0(function(d){return height-yscale(d.vader)})
            .y1(function(d){return height-yscale(d.textblob)})
            .curve(d3.curveCardinal)

            //이렇게 하지말고, sdata2도 만들었으니까 2->1->0 반복하는
            // 무한반복 on('end','repeat') loop를 만들어보자.

            var uarea=area.append('path')
            setTimeout(repeat,500)

            function repeat(){
            uarea
            .style('fill','rgba(112, 99, 230, 0.4)')
            .attr('d', areas(sdata))
            .transition()
            .duration(500)
            .attrTween('d',function(){
                var interpolator=d3.interpolateArray(sdata,sdata1);
                return function(t){
                    return areas(interpolator(t))
                }
            })
            .transition()
            .duration(500)
            .attrTween('d',function(){
                var interpolator=d3.interpolateArray(sdata1,sdata2);
                return function(t){
                    return areas(interpolator(t))
                }
            })
            .transition()
            .duration(500)
            .attrTween('d',function(){
                var interpolator=d3.interpolateArray(sdata2,sdata);
                return function(t){
                    return areas(interpolator(t))
                }
            })
            .on('end',repeat)
            }
            setTimeout(fourthstage,500)
        }

        function fourthstage(){
            // console.log(d3.selectAll('circle#circles1').node())

            circles1=svg.append('g').selectAll('circle').data(sdata)
                                   .enter().append('circle').attr('class','circles1')
                                   .attr('cx',function(d){return margin+xscale(d.date)})
                                   .attr('cy',function(d){return height-yscale(d.vader)})
                                   .style('fill','green')
                                   .attr('r',3)

            circles2=svg.append('g').selectAll('circle').data(sdata)
            .enter().append('circle').attr('class','circles2')
            .attr('cx',function(d){return margin+xscale(d.date)})
            .attr('cy',function(d){return height-yscale(d.textblob)})
            .style('fill','pink')
            .attr('r',3)

          d3.selectAll('.circles1')
            .transition()
            .duration(5000)
            .attr('cy',function(d, i){
            return height-yscale(sdata1[i].vader)
          })

            //   d3.selectAll('circle#circles2')
            //   .transition()
            //   .attr('cy',function(d){return 0})

            //tween 팩토리를 정의해야한다.
            //주의사항, 리턴을 갖는 함수여야한다는 것.

            //왜 꼭 return function(){}을 해야하나?
            /*
            function movey(d2){
                let y1 = this.attr('cy')
                let y2 = d2.vader
                let interpolate=d3.interpolate(y1,y2);
                interpolate;
            } 하면 안되나??
            */



        }

})

<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

10-06 12:02