d3-attrTween с пользовательской функцией. (Что я неправильно понял в функции анимации движения?) - PullRequest
0 голосов
/ 08 апреля 2020

У меня есть вопрос об 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))
            }
          })

То, что я хотел, это

Для всех нарисованных мною кругов
выполняет переход.
transition is attrTween.
Изменения основаны на массиве данных, связанном с кругами.
Исходный массив данных - sdata, а значение cy в sdata - sdata.vader.
И переход идет в направлении sdata1. и значение cy для sdata1 равно sdata1.vader.

Чтобы получить доступ ко всем значениям cy для каждого круга,
я использовал d3.select (this) .attr ('cy' )

Однако сообщение об ошибке не отображается, но анимация также не создавалась.

Что я неправильно понял для пользовательской функции анимации движения? Может кто-нибудь помочь мне исправить это? код?

Спасибо за предварительную оценку.

Полный код приведен ниже ссылка.

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

1 Ответ

2 голосов
/ 08 апреля 2020

В примере кода есть несколько проблем, которые не являются минимальными. Предоставление минимального воспроизводимого примера действительно поможет решить проблемы.

  1. использование HTML Id для нескольких элементов.

В * 1100 Атрибуты * и id должны быть уникальными. Здесь идентификаторы присваиваются группам кругов. Для этой цели следует использовать атрибут class, а не id.

.attr('id','circles1')

должен быть:

.attr('class','circles1')

Соответственно, attrTween должен искать круги с класс circle1, а не уникальный круг с идентификатором #circle1

d3.selectAll('circle#circles1')

должен быть

d3.selectAll('.circles1')
Идентификатор (или класс) назначен в неправильном месте.

Класс circles1 назначается до создания круга, поэтому инструкции применяются к пустому выбору. Атрибут класса должен быть установлен сразу после создания кругов.

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

должно быть

.enter()
  .append('circle')
  .attr('class','circles1')
Неправильный атрибут tweened

Атрибутом перехода является атрибут cy круга, а не атрибут d пути. Следовательно

.attrTween('d',function(){

должно быть

.attrTween('cy',function(){
Неправильные данные интерполированы

sdata.vader и sdata1.vader не существуют, sdata и sdata1 кажутся массивами объектов, которые, в свою очередь, имеют свойство 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)
  })

Я добавил большую длительность для демонстрационной цели, чтобы было очевидно, что круги перемещаются в правильное местоположение. Оказавшись в своем окончательном положении, они исчезают, потому что находятся под розовыми кружками.

PS Такой же набор исправлений применим к 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>
...