В примере кода есть несколько проблем, которые не являются минимальными. Предоставление минимального воспроизводимого примера действительно поможет решить проблемы.
- использование 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>