Given a datum containing multiple data elements, such as an object or array, is it possible to set multiple attributes on a selection with a single value function?
E.g. something like:
var data = [{ 'x': 10, 'y': 20, 'r': 5 }];
d3.select('body').append('svg').selectAll('circle')
.data(data)
.enter().append('circle')
.attr('cx cy r', function (d) {
return [d.x, d.y, d.r];
});
instead of:
var data = [{ 'x': 10, 'y': 20, 'r': 5 }];
d3.select('body').append('svg').selectAll('circle')
.data(data)
.enter().append('circle')
.attr('cx', function (d) {
return d.x;
});
.attr('cy', function (d) {
return d.y;
});
.attr('r', function (d) {
return d.r;
});
UPDATE (July 8th 2016) This answer applies to d3 v3.x — NOT v4.x. For the latter version, see Tim Hayes's answer, also on this page. Or... just swap attr with attrs in my answer below, and don't forget to require/import/script-embed d3-selection-multi. And... don't miss the bit about using .each, which may be useful to you.
Yeah, it's possible by passing in a hash (like jQuery's css() method):
d3.select('body').append('svg').selectAll('circle')
.data(data)
.enter().append('circle')
.attr({
cx: function (d) { return d.x; },
cy: function (d) { return d.y; },
r: function (d) { return d.r; }
});
This works for style() as well.
If the reoccurring function (d) {} start to feel like too much, this is another approach:
d3.select('body').append('svg').selectAll('circle')
.data(data)
.enter().append('circle')
.each(function (d) {
d3.select(this).attr({
cx: d.x,
cy: d.y,
r: d.r
});
})
NOTE: this feature only exists in d3.js v2.10.0 or higher
This is an old post, but I found it while Googling around for an answer. The accepted answer no longer works in D3 v4.0.
Moving forward, you can do the same by using the attrs() method. But attrs() is only supported if you load the optional d3-selection-multi script.
So using the example above, it would look like this in D3 v4.0:
// load d3-selection-multi as separate script
<script src="https://d3js.org/d3-selection-multi.v0.4.min.js"></script>
d3.select('body').append('svg').selectAll('circle')
.data(data)
.enter().append('circle')
.attrs({
cx: function (d) { return d.x; },
cy: function (d) { return d.y; },
r: function (d) { return d.r; }
});
Related
How to apply force repulsion on map's labels so they find their right places automatically ?
Bostock' "Let's Make a Map"
Mike Bostock's Let's Make a Map (screenshot below). By default, labels are put at the point's coordinates and polygons/multipolygons's path.centroid(d) + a simple left or right align, so they frequently enter in conflict.
Handmade label placements
One improvement I met requires to add an human made IF fixes, and to add as many as needed, such :
.attr("dy", function(d){ if(d.properties.name==="Berlin") {return ".9em"} })
The whole become increasingly dirty as the number of labels to reajust increase :
//places's labels: point objects
svg.selectAll(".place-label")
.data(topojson.object(de, de.objects.places).geometries)
.enter().append("text")
.attr("class", "place-label")
.attr("transform", function(d) { return "translate(" + projection(d.coordinates) + ")"; })
.attr("dy", ".35em")
.text(function(d) { if (d.properties.name!=="Berlin"&&d.properties.name!=="Bremen"){return d.properties.name;} })
.attr("x", function(d) { return d.coordinates[0] > -1 ? 6 : -6; })
.style("text-anchor", function(d) { return d.coordinates[0] > -1 ? "start" : "end"; });
//districts's labels: polygons objects.
svg.selectAll(".subunit-label")
.data(topojson.object(de, de.objects.subunits).geometries)
.enter().append("text")
.attr("class", function(d) { return "subunit-label " + d.properties.name; })
.attr("transform", function(d) { return "translate(" + path.centroid(d) + ")"; })
.attr("dy", function(d){
//handmade IF
if( d.properties.name==="Sachsen"||d.properties.name==="Thüringen"|| d.properties.name==="Sachsen-Anhalt"||d.properties.name==="Rheinland-Pfalz")
{return ".9em"}
else if(d.properties.name==="Brandenburg"||d.properties.name==="Hamburg")
{return "1.5em"}
else if(d.properties.name==="Berlin"||d.properties.name==="Bremen")
{return "-1em"}else{return ".35em"}}
)
.text(function(d) { return d.properties.name; });
Need for better solution
That's just not manageable for larger maps and sets of labels. How to add force repulsions to these both classes: .place-label and .subunit-label?
This issue is quite a brain storming as I haven't deadline on this, but I'am quite curious about it. I was thinking about this question as a basic D3js implementation of Migurski/Dymo.py. Dymo.py's README.md documentation set a large set of objectives, from which to select the core needs and functions (20% of the work, 80% of the result).
Initial placement: Bostock give a good start with left/right positionning relative to the geopoint.
Inter-labels repulsion: different approach are possible, Lars & Navarrc proposed one each,
Labels annihilation: A label annihilation function when one label's overall repulsion is too intense, since squeezed between other labels, with the priority of annihilation being either random or based on a population data value, which we can get via NaturalEarth's .shp file.
[Luxury] Label-to-dots repulsion: with fixed dots and mobile labels. But this is rather a luxury.
I ignore if label repulsion will work across layers and classes of labels. But getting countries labels and cities labels not overlapping may be a luxury as well.
In my opinion, the force layout is unsuitable for the purpose of placing labels on a map. The reason is simple -- labels should be as close as possible to the places they label, but the force layout has nothing to enforce this. Indeed, as far as the simulation is concerned, there is no harm in mixing up labels, which is clearly not desirable for a map.
There could be something implemented on top of the force layout that has the places themselves as fixed nodes and attractive forces between the place and its label, while the forces between labels would be repulsive. This would likely require a modified force layout implementation (or several force layouts at the same time), so I'm not going to go down that route.
My solution relies simply on collision detection: for each pair of labels, check if they overlap. If this is the case, move them out of the way, where the direction and magnitude of the movement is derived from the overlap. This way, only labels that actually overlap are moved at all, and labels only move a little bit. This process is iterated until no movement occurs.
The code is somewhat convoluted because checking for overlap is quite messy. I won't post the entire code here, it can be found in this demo (note that I've made the labels much larger to exaggerate the effect). The key bits look like this:
function arrangeLabels() {
var move = 1;
while(move > 0) {
move = 0;
svg.selectAll(".place-label")
.each(function() {
var that = this,
a = this.getBoundingClientRect();
svg.selectAll(".place-label")
.each(function() {
if(this != that) {
var b = this.getBoundingClientRect();
if(overlap) {
// determine amount of movement, move labels
}
}
});
});
}
}
The whole thing is far from perfect -- note that some labels are quite far away from the place they label, but the method is universal and should at least avoid overlap of labels.
One option is to use the force layout with multiple foci. Each foci must be located in the feature's centroid, set up the label to be attracted only by the corresponding foci. This way, each label will tend to be near of the feature's centroid, but the repulsion with other labels may avoid the overlapping issue.
For comparison:
M. Bostock's "Lets Make a Map" tutorial (resulting map),
my gist for an Automatic Labels Placement version (resulting map) implementing the foci's strategy.
The relevant code:
// Place and label location
var foci = [],
labels = [];
// Store the projected coordinates of the places for the foci and the labels
places.features.forEach(function(d, i) {
var c = projection(d.geometry.coordinates);
foci.push({x: c[0], y: c[1]});
labels.push({x: c[0], y: c[1], label: d.properties.name})
});
// Create the force layout with a slightly weak charge
var force = d3.layout.force()
.nodes(labels)
.charge(-20)
.gravity(0)
.size([width, height]);
// Append the place labels, setting their initial positions to
// the feature's centroid
var placeLabels = svg.selectAll('.place-label')
.data(labels)
.enter()
.append('text')
.attr('class', 'place-label')
.attr('x', function(d) { return d.x; })
.attr('y', function(d) { return d.y; })
.attr('text-anchor', 'middle')
.text(function(d) { return d.label; });
force.on("tick", function(e) {
var k = .1 * e.alpha;
labels.forEach(function(o, j) {
// The change in the position is proportional to the distance
// between the label and the corresponding place (foci)
o.y += (foci[j].y - o.y) * k;
o.x += (foci[j].x - o.x) * k;
});
// Update the position of the text element
svg.selectAll("text.place-label")
.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; });
});
force.start();
While ShareMap-dymo.js may work, it does not appear to be very well documented. I have found a library that works for the more general case, is well documented and also uses simulated annealing: D3-Labeler
I've put together a usage sample with this jsfiddle.The D3-Labeler sample page uses 1,000 iterations. I have found this is rather unnecessary and that 50 iterations seems to work quite well - this is very fast even for a few hundred data points. I believe there is room for improvement both in the way this library integrates with D3 and in terms of efficiency, but I wouldn't have been able to get this far on my own. I'll update this thread should I find the time to submit a PR.
Here is the relevant code (see the D3-Labeler link for further documentation):
var label_array = [];
var anchor_array = [];
//Create circles
svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("id", function(d){
var text = getRandomStr();
var id = "point-" + text;
var point = { x: xScale(d[0]), y: yScale(d[1]) }
var onFocus = function(){
d3.select("#" + id)
.attr("stroke", "blue")
.attr("stroke-width", "2");
};
var onFocusLost = function(){
d3.select("#" + id)
.attr("stroke", "none")
.attr("stroke-width", "0");
};
label_array.push({x: point.x, y: point.y, name: text, width: 0.0, height: 0.0, onFocus: onFocus, onFocusLost: onFocusLost});
anchor_array.push({x: point.x, y: point.y, r: rScale(d[1])});
return id;
})
.attr("fill", "green")
.attr("cx", function(d) {
return xScale(d[0]);
})
.attr("cy", function(d) {
return yScale(d[1]);
})
.attr("r", function(d) {
return rScale(d[1]);
});
//Create labels
var labels = svg.selectAll("text")
.data(label_array)
.enter()
.append("text")
.attr("class", "label")
.text(function(d) {
return d.name;
})
.attr("x", function(d) {
return d.x;
})
.attr("y", function(d) {
return d.y;
})
.attr("font-family", "sans-serif")
.attr("font-size", "11px")
.attr("fill", "black")
.on("mouseover", function(d){
d3.select(this).attr("fill","blue");
d.onFocus();
})
.on("mouseout", function(d){
d3.select(this).attr("fill","black");
d.onFocusLost();
});
var links = svg.selectAll(".link")
.data(label_array)
.enter()
.append("line")
.attr("class", "link")
.attr("x1", function(d) { return (d.x); })
.attr("y1", function(d) { return (d.y); })
.attr("x2", function(d) { return (d.x); })
.attr("y2", function(d) { return (d.y); })
.attr("stroke-width", 0.6)
.attr("stroke", "gray");
var index = 0;
labels.each(function() {
label_array[index].width = this.getBBox().width;
label_array[index].height = this.getBBox().height;
index += 1;
});
d3.labeler()
.label(label_array)
.anchor(anchor_array)
.width(w)
.height(h)
.start(50);
labels
.transition()
.duration(800)
.attr("x", function(d) { return (d.x); })
.attr("y", function(d) { return (d.y); });
links
.transition()
.duration(800)
.attr("x2",function(d) { return (d.x); })
.attr("y2",function(d) { return (d.y); });
For a more in depth look at how D3-Labeler works, see "A D3 plug-in for automatic label placement using simulated
annealing"
Jeff Heaton's "Artificial Intelligence for Humans, Volume 1" also does an excellent job at explaining the simulated annealing process.
You might be interested in the d3fc-label-layout component (for D3v5) that is designed exactly for this purpose. The component provides a mechanism for arranging child components based on their rectangular bounding boxes. You can apply either a greedy or simulated annealing strategy in order to minimise overlaps.
Here's a code snippet which demonstrates how to apply this layout component to Mike Bostock's map example:
const labelPadding = 2;
// the component used to render each label
const textLabel = layoutTextLabel()
.padding(labelPadding)
.value(d => d.properties.name);
// a strategy that combines simulated annealing with removal
// of overlapping labels
const strategy = layoutRemoveOverlaps(layoutGreedy());
// create the layout that positions the labels
const labels = layoutLabel(strategy)
.size((d, i, g) => {
// measure the label and add the required padding
const textSize = g[i].getElementsByTagName('text')[0].getBBox();
return [textSize.width + labelPadding * 2, textSize.height + labelPadding * 2];
})
.position(d => projection(d.geometry.coordinates))
.component(textLabel);
// render!
svg.datum(places.features)
.call(labels);
And this is a small screenshot of the result:
You can see a complete example here:
http://bl.ocks.org/ColinEberhardt/389c76c6a544af9f0cab
Disclosure: As discussed in the comment below, I am a core contributor of this project, so clearly I am somewhat biased. Full credit to the other answers to this question which gave us inspiration!
For 2D case
here are some examples that do something very similar:
one http://bl.ocks.org/1691430
two http://bl.ocks.org/1377729
thanks Alexander Skaburskis who brought this up here
For 1D case
For those who search a solution to a similar problem in 1-D i can share my sandbox JSfiddle where i try to solve it. It's far from perfect but it kind of doing the thing.
Left: The sandbox model, Right: an example usage
Here is the code snippet which you can run by pressing the button in the end of the post, and also the code itself. When running, click on the field to position the fixed nodes.
var width = 700,
height = 500;
var mouse = [0,0];
var force = d3.layout.force()
.size([width*2, height])
.gravity(0.05)
.chargeDistance(30)
.friction(0.2)
.charge(function(d){return d.fixed?0:-1000})
.linkDistance(5)
.on("tick", tick);
var drag = force.drag()
.on("dragstart", dragstart);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.on("click", function(){
mouse = d3.mouse(d3.select(this).node()).map(function(d) {
return parseInt(d);
});
graph.links.forEach(function(d,i){
var rn = Math.random()*200 - 100;
d.source.fixed = true;
d.source.px = mouse[0];
d.source.py = mouse[1] + rn;
d.target.y = mouse[1] + rn;
})
force.resume();
d3.selectAll("circle").classed("fixed", function(d){ return d.fixed});
});
var link = svg.selectAll(".link"),
node = svg.selectAll(".node");
var graph = {
"nodes": [
{"x": 469, "y": 410},
{"x": 493, "y": 364},
{"x": 442, "y": 365},
{"x": 467, "y": 314},
{"x": 477, "y": 248},
{"x": 425, "y": 207},
{"x": 402, "y": 155},
{"x": 369, "y": 196},
{"x": 350, "y": 148},
{"x": 539, "y": 222},
{"x": 594, "y": 235},
{"x": 582, "y": 185}
],
"links": [
{"source": 0, "target": 1},
{"source": 2, "target": 3},
{"source": 4, "target": 5},
{"source": 6, "target": 7},
{"source": 8, "target": 9},
{"source": 10, "target": 11}
]
}
function tick() {
graph.nodes.forEach(function (d) {
if(d.fixed) return;
if(d.x<mouse[0]) d.x = mouse[0]
if(d.x>mouse[0]+50) d.x--
})
link.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
}
function dblclick(d) {
d3.select(this).classed("fixed", d.fixed = false);
}
function dragstart(d) {
d3.select(this).classed("fixed", d.fixed = true);
}
force
.nodes(graph.nodes)
.links(graph.links)
.start();
link = link.data(graph.links)
.enter().append("line")
.attr("class", "link");
node = node.data(graph.nodes)
.enter().append("circle")
.attr("class", "node")
.attr("r", 10)
.on("dblclick", dblclick)
.call(drag);
.link {
stroke: #ccc;
stroke-width: 1.5px;
}
.node {
cursor: move;
fill: #ccc;
stroke: #000;
stroke-width: 1.5px;
opacity: 0.5;
}
.node.fixed {
fill: #f00;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<body></body>
My code is designed to draw simple circles. The data (cx, cy, r and colors) are in a json file. So I would like to extract them to draw, but I get this error : TypeError: e is undefined. Where is my error(s) ? Only the svg element appears, but nothing about my circles. I tried to simplify the code, but it seems that my code doesn't read my json file.
Here's my json file :
[{
"jsonCircles": [
{"cx": 30, "cy": 30, "r": 20, "color" : "red"},
{"cx": 70, "cy": 70, "r": 20, "color" : "red"},
{"cx": 110, "cy": 100, "r": 20, "color" : "red"}
]
}]
And my code :
var svgContainer = d3.select("body").append("svg")
.attr("width", 200)
.attr("height", 200);
d3.json("circles.json", function(error, data) {
if (error) alert(error);
var myCircles = svgContainer.selectAll("circle")
.data(data.jsonCircles)
.enter()
.append("circle");
var circleAttributes = myCircles
.attr("cx", function (d) { return d.cx; })
.attr("cy", function (d) { return d.cy; })
.attr("r", function (d) { return d.r; })
.style("fill", function(d) { return d.color; });
});
Thanks in advance,
d3.json("circles.json", function(error, data) {
if (error) alert(error);
var myCircles = svgContainer.selectAll("circle")
.data(data.jsonData)//Error number 1
.enter()
.append("circle");
});//Error number 2
var circleAttributes = circles // Error number 3
.attr("cx", function (d) { return d.cx; })
.attr("cy", function (d) { return d.cy; })
.attr("r", function (d) { return d.r; })
.style("fill", function(d) { return d.color; });
Description For Error Number 1:
d3.json("circles.json". function(error, data){})
In the above line data object holds your file data like below format
data = [{
"jsonCircles": [
{"cx": 30, "cy": 30, "r": 20, "color" : "red"},
{"cx": 70, "y": 70, "r": 20, "color" : "red"},
{"cx": 110, "y": 100, "": 20, "color" : "red"}
]
}]
So in order to point to your circles you need to write it as
.data(data.jsonCircles)
Description For Error Number 2:
Don't close the function there it self, we need to close after all our work is done. Like creating circles, setting attributes, filling colors.
Description For Error Number 3:
Here you are using circles object, but it depends like have you declared it or not, and as per above code you need to use myCircles not circles,
And also keep this code inside of d3.json("circles.json", function(error, data){
Because we can set the attributes of circles after getting the data only.
Okay
Hope you got it, If not ask me for more.
var circleAttributes = circles
.attr("cx", **function (d) { return d.cx; }**)
.attr("cy", **function (d) { return d.cy; }**)
.attr("r", **function (d) { return d.r; }**)
.style("fill", **function(d) { return d.color; }**);
Above bolded functions(closures) will be invoked the number of JSON Objects/records/rows existed in the data.jsonCircles array. And we have written that d.cx, d.cy, d.r to set cx, cy, r attributes of a circle.We need to make sure that all our records/rows are having these attributes.
Okay
To make sure that all our records are having the specific attribute, we can write the code like this
function (d) { return d.cx; }
add a condition to check whether the attribute is there or not
function (d) {
if(d.cx){
return d.cx;
}else{
return 2;//Here we are returning static value
}
}
or we can also write like this
function(d, i){
if(d.cx){
return d.cx;
}else{
return i*2;//See below for description about i
}
}
Description for i
i is passed by d3, and i refer to the index of the record like for first record i will hold 0, for second record 1 and so on.
I think you are a beginner and you need to learn a lot more things.
I solved my problem. In fact the D3js library didn't load correctly from the url. I added locally the library and now it's working.
Moreover, the brackets around the json data are not necessary.
Thanks to all who helped me!
I am using a force directed graph to draw entity relationships for a company. What I would like to do is use my built in index value for the "index" of the node instead of the array index. How do I override the d3 index value that gets set? Fiddle - http://jsfiddle.net/thielcole/ed9noqw1/
var forceLinks = [];
var forceData = [];
d3.csv('AmazonExampleforiCharts.csv', function(d) {
return { // This is the index I would like to use
index: d.Node,
parent: d.ParentNode,
color: d.NodeColor,
level: d.Hierarchy_code,
business: d.Business_Name,
power: d.Decisionpower,
hover: d.HoverOverValue,
link: d.LinkVALUE
};
}, function(error, rows) {
forceData = rows;
$.each(forceData, function(i, d) {
// console.log(d.parent);
if (d.parent != "" && d.parent !== undefined) { // generating link information here, I have to subtract one initially to match the array index
forceLinks.push({source: parseInt(d.parent , 10) - 1 , target: parseInt(d.index , 10) - 1, value: parseInt(d.level , 10)});
}
console.log(d);
});
$.each(forceLinks, function(i, d) {
// console.log(d);
});
initiateChart();
});
function initiateChart() {
var height = 1000,
width = 1400;
var graphData = [];
var graphLinks = [];
graphData = forceData;
graphLinks = forceLinks;
var color = d3.scale.category20();
var svg = d3.select('.svg')
.attr('width', width)
.attr('height', height);
var force = d3.layout.force()
.charge(-120)
.linkDistance(30)
.size([width, height]);
// this is where it gets set
force.nodes(graphData)
.links(graphLinks)
.start();
var link = svg.selectAll('.link')
.data(graphLinks)
.enter().append('line')
.attr('class', 'link')
.style('stroke-width', function (d) {
return Math.sqrt(d.value);
});
var node = svg.selectAll('.node')
.data(graphData)
.enter().append('circle')
.attr('class', 'node')
.attr('r', 8)
.style('fill', function (d) {
return color(d.level);
})
.on('mouseover', function(d , i) {
d3.select(this).attr('r', 12);
var tooltip = d3.select('body')
.append('div')
.attr('class', 'tooltip')
.style('position','absolute')
.style('top', (d3.event.pageY - 10) + 'px')
.style('left' , (d3.event.pageX) + 'px' )
.style('z-index' , '10')
.text(d.hover);
console.log(d)
})
.on('click', function(d , i) {
showChildren(d);
})
.on('mouseout', function(d, i) {
d3.select(this).attr('r', 8);
d3.select('body').selectAll('.tooltip')
.remove();
})
.call(force.drag);
//Now we are giving the SVGs co-ordinates - the force layout is generating the co-ordinates which this code is using to update the attributes of the SVG elements
force.on("tick", function () {
link.attr("x1", function (d) {
return d.source.x;
})
.attr("y1", function (d) {
return d.source.y;
})
.attr("x2", function (d) {
return d.target.x;
})
.attr("y2", function (d) {
return d.target.y;
});
node.attr("cx", function (d) {
return d.x;
})
.attr("cy", function (d) {
return d.y;
})
});
}
var node = svg.selectAll('.node')
.data(graphData)
.enter().append('circle')
.attr('class', 'node')
.attr('r', 8)
.attr('id', function(d){return(d.index);})// use the data 'index' property as node 'id' attribute
.style('fill', function (d) {
return color(d.level);
})
.call(force.drag);
The 'index'-es will be stored in circles 'id' attributes as strings (not numbers):
You can add as much attributes as you want and recall them later. Actually, the id tag is not the best choice. The much better alternatives are: data-index, data-id, data-company etc.
PS:
The notation .attr('class', 'node') is not a mistake, but d3.js have special selection.classed(name[, value]) method for classes. I recommend you for attributes use native js arrays:
var styles = {
node: {stroke: "white", "stroke-width": 1.5, r: 8},
link: {stroke: "#999", "stroke-width": 0.6}
};
...
var node = svg.selectAll('.node')
...
.attr(styles.node)
...
By the way, in SVG fill is attribute.
DEMO: http://jsfiddle.net/3gw8vxa3/
I'm trying to create multiple lines on a line graph one at a time. I've created an object array of about 100 lines in the below format:
var allLines = [{type: "linear", values: [1000, 2000, 3000]}, {}, ... {}];
var line = d3.svg.line()
.defined(function (d) {
return d != null;
})
.x(function (d, i) {
return x(new Date(minYear + i, 1, 1));
})
.y(function (d) {
return y(d);
});
Now I want to draw each line, one at a time with a delay of about 250 milliseconds between each line. I've tried the below approach which I thought would work, but I must be missing something because it just waits 250ms and then draws all the lines.
svg.append('g')
.attr('class', 'lineGroup')
.selectAll('path')
.data(allLines)
.enter()
.append('path')
.attr('class', function (d) {
return d.type;
})
.style('visibility', 'hidden')
.attr('d', function (d) {
return line(d.values);
});
function foo(transition) {
transition
.style('visibility', 'visible');
}
d3.select('.lineGroup').selectAll('path').transition().delay(250).call(foo);
Your basic approach is right, you just need to adjust the delay dynamically such that the later lines are drawn later. At the moment, the delay applies to all lines. To make it dynamic, you can use something like
svg.append("g")
// etc
.transition()
.delay(function(d, i) { return i * 250; })
.style('visibility', 'visible');
You can also do everything in one call, no need for a separate one after creating the lines.
I am trying to understand the D3.js code for this example and am confused by this code:
var circle = interpolation.selectAll("circle")
.data(Object);
circle.enter().append("circle")
.attr("r", 4)
.attr("fill","yellow");
circle
.attr("cx", function y(d) { console.log(d.attr("class")); return d.x; })
.attr("cy", function(d) { return d.y; });
What does the second line of this code actually do? What data does it bind to?
The data bound in the element above that is given by the function getLevels(d, t), where d is a number of range 2 - 4 and t is a number derived from the current time.
This only ever returns an array of arrays. Because an array is already of type Object, Calling Object() on an Array returns the original array.. Therefore, from what I can see, the author is simply using Object as a kind of identity function, similar to:
var identity = function(d){
return d;
}
var circle = interpolation.selectAll("circle")
.data(identity);
circle.enter().append("circle")
.attr("r", 4)
.attr("fill","yellow");
circle
.attr("cx", function y(d) { console.log(d.attr("class")); return d.x; })
.attr("cy", function(d) { return d.y; });