How to do animation of sequential data with d3.js? - javascript

I have a sequential data. Data is an array divided into 30 steps.
In the first step I draw initial data. Then I take data from second step, compare it with data from previous step. And make some animation/transition.
Now I want to repeat animation for the rest of 28 steps.
If I will use code like this
var old_data = start_data;
for (var i = 0, i < rest_data.length; i++){
var new_data = rest_data[i];
var diff_data = diff(old_data, new_data);
var t0 = d3.transition().duration(3000);
var selection = d3.selectAll('g').data(diff_data)
selection.transition(t0).attr('my', function(d,i) { ... });
old_data = new_data;
}
it will not work because transition() is async code.The loop run faster than complete the first animation. And at best I will have an animation between data[0] and data[29].
What is the best way to create an animation with sequential data like my?
Use d3.transition().on('end', function() {...}), where function() { ... } some function with closure where I will store old_data, rest_data and other variables?
I don't like this solution because I have many variables to calculate diff_data, and I have to keep them in the closure.

You are correct: the for loop will run to the end almost instantly, and it will simply call that bunch of transitions at the same time, which, of course,
will not work.
There are some different solutions here. Good news is that transition().on("end"... is just one of them, you don't need to use it if you don't want.
My favourite one is creating a function that you call repeatedly with setTimeout or, even better, with d3.timeout.
This is the logic of it:
var counter = 0;//this is, of course, a counter
function loop(){
//if there is a data element for the next counter index, call 'loop' again:
if(data[counter + 1]){ d3.timeout(loop, delay)}
//access the data using the counter here,
//and manipulate the selection
//increase the counter
counter++;
}
Have a look at this demo:
var data = [30, 400, 170, 280, 460, 40, 250];
var svg = d3.select("body")
.append("svg")
.attr("width", 500)
.attr("height", 100);
var circle = svg.append("circle")
.attr("cy", 50)
.attr("cx", 250)
.attr("r", 10)
.attr("fill", "tan")
.attr("stroke", "black");
var counter = 0;
loop();
function loop() {
if (data[counter + 1]) {
d3.timeout(loop, 1000)
}
circle.transition()
.duration(500)
.attr("cx", data[counter]);
counter++
}
<script src="https://d3js.org/d3.v4.min.js"></script>

Related

d3-attrTween with custom function.(What did I misunderstand about tween function?)

I have a question about attrTween (sometimes tween()).
I understood custom tween function as
after " attrTween('d' " argument,
I define the custom function.
So, I wrote the custom function as below.
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))
}
})
What I intended is
For All the circles I drew, makes a transition. The transition
is attrTween. The changes is based on data array tied into the
circles. Original data array is sdata and the cy value in the
sdata is sdata.vader. And the transition is heading toward
sdata1.and cy value for sdata1 is sdata1.vader.
To access all the cy value for every single circle, I used
d3.select(this).attr('cy')
However, no error message is shown but no animation was made either.
What did I misunderstand for the custom tween function?
Can anyone help me to fix this code?
Thank you inadvance.
Full code is in the following link.
https://codepen.io/jotnajoa/pen/WNQeEBE
There are multiple problems in the example code, which is not minimal. Providing a minimal, reproducible example would really help solve the problems.
usage of HTML Id to multiple elements.
In HTML, and id attribute must be unique. Here, ids are assigned to groups of circles. A class attribute should be used for this purpose, not an id.
.attr('id','circles1')
should be:
.attr('class','circles1')
Accordingly, the attrTween should lookup the circles with class circle1, rather than the unique circle with id #circle1
d3.selectAll('circle#circles1')
should be
d3.selectAll('.circles1')
Id (or class) assigned in the wrong place.
The circles1 class is assigned before the creation of the circle, hence the instructions applies to an empty selection. The class attribute should be set right after circles have been created.
.attr('id','circles1')
.enter()
.append('circle')
should be
.enter()
.append('circle')
.attr('class','circles1')
Wrong attribute tweened
The attribute to transition is the circle's cy attribute, not a path's d attribute. Hence
.attrTween('d',function(){
should be
.attrTween('cy',function(){
Wrong data interpolated
sdata.vader and sdata1.vader do not exist, sdata and sdata1 seem to be arrays of objects, which in turn do have a vader property.
You probably want d.vader, and the corresponding .vader in sdata1, which would be sdata1[i].vader, in case items are the same order in both arrays.
Interpolating original measures instead of coordinates.
cy is originally defined as:
height-yscale(d.vader)
In the interpolator function, the scale function should also be used.
The attrTween function calls becomes:
.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)}
})
Using attrTween where not needed.
Simply transitioning the circles with attr is sufficient for this use case, there is no need to define an interpolator.
d3 will move the position of circles from the original position to the destination, interpolating implicitly.
d3.selectAll('.circles1')
.transition()
.duration(2000)
.attr('cy',function(d, i){
return height-yscale(sdata1[i].vader)
})
I added a long duration for demo purpose, to make obvious that the circles move to the correct location. Once in their final position, they disappear, because they are under the pink circles.
P.S. Same set of corrections is applicable to circles2 set whenever relevant.
Demo of the solution in the snippet below, as codepen does not allow to save modifications without creating an account.
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>

Trying to get two circles to transition simultaneously

Trying to get two circles (one red, one blue) to move to center of screen from opposite directions. Can only get the second circle to do it - unsure as to why.
I tried everything from switching up the order of functions being called to switching variable names
var svg = d3.select("body")
.append("svg")
.attr("width", 600)
.attr("height", 200);
var circles = svg.append('circle')
.attr('cx',50).attr('cy',50).attr('r',10).style('fill','red');
var circlesTwo = svg.append('circle')
.attr('cx',50).attr('cy',50).attr('r',10).style('fill','blue');
animation();
animationTwo();
function animation() {
svg.transition()
.duration(750)
.tween("precision", function() {
var area = d3.interpolateRound(0, 300);
return function(t) {
minArea = area(t);
render();
};
})
}
function animationTwo() {
svg.transition()
.duration(750)
.tween("precision", function() {
var area = d3.interpolateRound(600, 300);
return function(t) {
minArea = area(t);
renderTwo();
};
})
}
function render() {
circles.attr('cx',minArea);
}
function renderTwo() {
circlesTwo.attr('cx',minArea);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
Expected results are two circles coming to their respective positions (from off screen originally).
Actual results are I am only getting my blue circle to work.
Applying the transitions to the SVG selection is not a very idiomatic D3: you should apply them to the elements that are moving (i.e., the circles). That, by the way, is the cause of the problem you're facing: one transition is cancelling the other.
This happens because since your transitions have no name, null is used (link):
selection.transition([name]) <>
Returns a new transition on the given selection with the specified name. If a name is not specified, null is used.
Then, because all of them have the same name (null), the last one cancels the first one:
The starting transition also cancels any pending transitions of the same name on the same element that were created before the starting transition.
Therefore, to apply multiple transitions to the same element, you have to name them:
function animation() {
svg.transition("foo")
//etc...
}
function animationTwo() {
svg.transition("bar")
//etc...
}
Here is your code with that change:
var svg = d3.select("body")
.append("svg")
.attr("width", 600)
.attr("height", 300);
var circles = svg.append('circle')
.attr('cx', 50).attr('cy', 50).attr('r', 10).style('fill', 'red');
var circlesTwo = svg.append('circle')
.attr('cx', 50).attr('cy', 50).attr('r', 10).style('fill', 'blue');
animation();
animationTwo();
function animation() {
svg.transition("foo")
.duration(750)
.tween("precision", function() {
var area = d3.interpolateRound(0, 300);
return function(t) {
minArea = area(t);
render();
};
})
}
function animationTwo() {
svg.transition("bar")
.duration(750)
.tween("precision", function() {
var area = d3.interpolateRound(600, 300);
return function(t) {
minArea = area(t);
renderTwo();
};
})
}
function render() {
circles.attr('cx', minArea);
}
function renderTwo() {
circlesTwo.attr('cx', minArea);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
Again, have in mind that I'm simply answering your question here: my advice is that you should refactor that transition code completely.

Styling in D3 with a loop?

I wonder if it possible to use styling actions on the same element many times? I got this Array called A which stores 3 colours and I want to go through a loop that fills my element link.
var A = ["blue", "red", "green"]
for ( var i = 0; i < A.length; i++){
link.style("stroke", function(d)
{
return A[i];
}
)
};
To style each link a different style based on the index of that element/datum in the selection. As with many things in d3, this does not require a loop. You can access the index of each datum with code like:
selection.attr("property",function(d,i) {
return array[i];
})
For sake of ease, I've used some circles and fill rather than links and stroke, but the method is identical. The snippet below will color links sequentially based on the colors array.
var svg = d3.select("body")
.append("svg")
.attr("width",500)
.attr("height",300);
var data = [[50,50],[100,50],[200,50],[300,100]];
var colors = ["steelblue","orange","pink","red"]
// create some circles:
var circles = svg.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("transform",function(d) { return "translate("+d+")"; })
.attr("r",10)
// to style sequentially, you can use the datum's index:
circles.attr("fill",function(d,i) {
return colors[i];
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

d3: Adding and removing force.nodes based on slider values

I had some difficulty a while back adding and removing nodes based on user input which was solved by updating the entire set of objects pushed into force.nodes() each time the user selected which they wanted to view from a list of checkboxes.
Updating from a slider however I think requires a more finesse touch - I don't want to update the entire set every time the slider is moved. I want to push and pop nodes in and out of force.nodes().
With my current code the nodes are coming out just fine - they're just not going back in. jsfiddle here - https://jsfiddle.net/hiwilson1/ancmtxux/3/
This is the part causing problems;
function brushed() {
var exists;
//var newd = new Date(2013, 05, 01)
data.forEach(function(d, i) {
//if data point in range (between extent 0 and 1)
if (d.date >= brush.extent()[0] && d.date <= brush.extent()[1]) {
exists = force.nodes().some(function(node, i) {
//check if data point already exists in force.nodes()
return (node.mIndex == d.mIndex)
})
console.log(exists)
if (!exists) {
force.nodes().push(d)
}
}
else {
force.nodes().splice(i, 1)
}
})
d3.select("#nodeCount").text(force.nodes().length)
}
For each data point, I'm checking whether or not the point lies between extent()[0] and [1]. If it does, then check force.nodes() to see if it's currently a member there. If it isn't, then push it into force.nodes().
If the data point doesn't lie between the extents then splice it from force.nodes(). This last bit works just fine.
UPDATE: Fixed. I actually also worked out how to filter links attached to nodes as well. jsfiddle here - https://jsfiddle.net/hiwilson1/7oumeat5/2/. Never try and do this with indexes/hard coded indexes, compare the nodes/links as objects.
Incidentally I'm seeing links over the top of nodes. If there's some way to fix this I'd be happy to hear it.
FURTHER UPDATE: To ensure links behind nodes use .insert("line", ":first-child") in place of .append("line")
There are a few problems with your current code. The fundamental problem is that you're giving data to force.nodes(), which means that the two data structures are actually the same. That is, when you're removing elements from force.nodes(), you're modifying the underlying data as well. Hence you can't add them back -- they're gone.
This is easily fixed by passing a copy of data to force.nodes():
var force = d3.layout.force()
.nodes(JSON.parse(JSON.stringify(data)))
Then you're removing the wrong nodes from force.nodes() -- the index you're using is for data, not force.nodes(). You can compute the index of the data element in force.nodes() and use it like this:
data.forEach(function(d, i) {
var idx = -1;
force.nodes().forEach(function(node, j) {
if(node.mIndex == d.mIndex) {
idx = j;
}
});
//if data point in range (between extent 0 and 1)
if (d.date >= brush.extent()[0] && d.date <= brush.extent()[1]) {
if (idx == -1) {
force.nodes().push(d)
}
}
else if(idx > -1) {
force.nodes().splice(idx, 1)
}
Finally, you need to call force.start() at the end of brushed for the changes to become visible after the layout has settled down.
Complete example here.
Building on the answer from the mighty #Lars Kotthoff, who fixed your technical problem, I will focus on the architecture. Here is an architecture that is simpler and more in keeping with d3 idiom.
The main principles are:
Use Array.prototype.filter() to manage the data
Use standard, data driven patterns.
Drive the visualisation by managing the data and then use the standard, general update pattern to drive the changes into the vis.
Seperate data events and animation events.
Respond to the data changes only when need... do it in the brushed event, not on every tick. The brushed event is a data event and the tick routine is an animation event. It's not optimal to be managing data on animation events on the off chance that it is required.
Make the entry and exit of the nodes a bit smoother.
The benefit of point 1 is that the filtered array is actually an array of reference to the original data elements, so when the extra state is added on the copied array, it is actually added on the original data array. Thus, the previous state is available when it is filtered back in, hence the smooth exit and entry behaviour. Meanwhile, no elements are deleted in the original data when the brush filters down: only the references to them are deleted in the cloned array. I have to admit I had not expected that but it is a nice discovery, even if by accident!
Of course this only works because the array elements are objects.
working example here...
var width = 700,
height = 600,
padding = 20;
var start = new Date(2013, 0, 1),
end = new Date(2013, 11, 31)
var data = []
for (i = 0; i < 100; i++) {
var point = {}
var year = 2013;
var month = Math.floor(Math.random() * 12)
var day = Math.floor(Math.random() * 28)
point.date = new Date(year, month, day)
point.mIndex = i
data.push(point)
}
var force = d3.layout.force()
.size([width - padding, height - 100])
.on("tick", tick)
.start()
var svg = d3.select("body").append("svg")
.attr({
"width": width,
"height": height
})
//build stuff
var x = d3.time.scale()
.domain([start, end])
.range([padding, width - 6 * padding])
.clamp(true)
var xAxis = d3.svg.axis()
.scale(x)
.tickSize(0)
.tickPadding(20)
//.tickFormat(d3.time.format("%x"))
var brush = d3.svg.brush()
.x(x)
.extent([start, end])
.on("brush", brushed1)
//append stuff
var slidercontainer = svg.append("g")
.attr("transform", "translate(100, 500)")
var axis = slidercontainer.append("g")
.call(xAxis)
var slider = slidercontainer.append("g")
.call(brush)
.classed("slider", true)
//manipulate stuff
d3.selectAll(".resize").append("circle")
.attr("cx", 0)
.attr("cy", 0)
.attr("r", 10)
.attr("fill", "Red")
.classed("handle", true)
d3.select(".domain")
.select(function () { return this.parentNode.appendChild(this.cloneNode(true)) })
.classed("halo", true)
function brushed1(e) {
var nodes = includedNodes(data, brush);
nodes.enter().append("circle")
.attr("r", 10)
.attr("fill", "red")
.call(force.drag)
.attr("class", "node")
.attr("cx", function (d) { return d.x })
.attr("cy", function (d) { return d.y })
nodes
.exit()
.remove()
force
.nodes(includedData(data, brush))
.start()
}
function includedData(data, brush) {
return data.filter(function (d, i, a) {
return d.date >= brush.extent()[0] && d.date <= brush.extent()[1]
})
}
function includedNodes(data, brush) {
return svg.selectAll(".node")
.data(includedData(data, brush), function (d, i) {
return d.mIndex
})
}
function tick() {
includedNodes(data, brush)
.attr("cx", function (d) { return d.x })
.attr("cy", function (d) { return d.y })
}
brushed1()
.domain {
fill: none;
stroke: #000;
stroke-opacity: .3;
stroke-width: 10px;
stroke-linecap: round;
}
.halo {
fill: none;
stroke: #ddd;
stroke-width: 8px;
stroke-linecap: round;
}
.tick {
font-size: 10px;
}
.selecting circle {
fill-opacity: .2;
}
.selecting circle.selected {
stroke: #f00;
}
.handle {
fill: #fff;
stroke: #000;
stroke-opacity: .5;
stroke-width: 1.25px;
cursor: crosshair;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<p id="nodeCount"></p>

Drawing path from 2 dataset

I have been trying to use loop in order to prepare dataset for visualization with d3 but I got some problem when getting data back from loop.
Let's say I have 2 data set,
setX = [1,2 ,3, 4, 5] and setY = [10, 20, 30, 40, 50]
and I create for loop to get array of coordinate x and y from 2 dataset.
var point = new Object();
var coordinate = new Array();
for(var i=0; i < setX.length;i++){
point.x = setX[i];
point.y = setY[i];
coordinate.push(point);
}
and pulling back data from array to draw with
var d3line = d3.svg.line()
.x(function(d){return d.x;})
.y(function(d){return d.y;})
.interpolate("linear");
But the value of x and y data of d3line(coordinate) are always 5 and 50.
Is there a correct way to fix this?
Here is example of code
<div id="path"></div>
<script>
var divElem = d3.select("#path");
canvas = divElem.append("svg:svg")
.attr("width", 200)
.attr("height", 200)
var setX = [1,2 ,3, 4, 5];
var setY = [10, 20, 30, 40, 50];
var point = new Object();
var coordinate = new Array();
for(var i=0; i < setX.length;i++){
point.x = setX[i];
point.y = setY[i];
coordinate.push(point);
}
var d3line = d3.svg.line()
.x(function(d){return d.x;})
.y(function(d){return d.y;})
.interpolate("linear");
canvas.append("svg:path")
.attr("d", d3line(coordinate))
.style("stroke-width", 2)
.style("stroke", "steelblue")
.style("fill", "none");
</script>
You also see my example of this code on http://jsfiddle.net/agadoo/JDtqf/
There are two problems with your code. First (and this causes the behaviour you see), you're overwriting the same object in the loop that creates the points. So you end up with exactly the same object several times in your array and each iteration of the loop changes it. The output you're producing inside the loop prints the correct values because you're printing before the next iteration overwrites the object. This is fixed easily by moving the declaration of point inside the loop.
The second problem isn't really a problem but more of a style issue. You can certainly use d3 in the way that you're using it by simply passing the data in where you need it. A better way is to use d3s selections however where you pass in the data and then tell it what to do with it.
I've updated your js fiddle here with those changes made.

Categories