Related
Below sample generate text block but current test is align left, how to align text by center?
demo()
function demo() {
var svg = d3.select('body').append('svg')
.attr('width','100')
.attr('height','100')
.attr('transform','translate(50,50)')
var text = ["12","123456789","1234"]
var fontsize = 40
var txt = svg.append('text')
.attr("text-anchor", 'start')
.attr("dominant-baseline","middle")//text-before-edge
.attr("font-size",fontsize)
.attr('alignment-baseline','middle')
var tspan = txt.selectAll('tspan')
.data(text)
.join('tspan')
.attr('class','tspan')
.attr("x", 0)
.attr('y',function(d,i) {
return i*fontsize
})
.text(d => d)
}
<script src="https://d3js.org/d3.v7.min.js"></script>
Update
After apply #herrstrietzel's answer, I create below demo to show the usecase to put text box at specific position, currently it always at the center of svg, I wish it deployed at specific location (x,y).
demo()
function demo() {
var svg = d3.select('body').append('svg')
.attr('width', 300)
.attr('height', 200)
.style('background','#ececec')
add_grid(svg)
var data = [
{
text:["B", "BB", "BBB",'BBBB'],
x:50,
y:50
},
{
text:["AAAAA", "AAA", "A"],
x:150,
y:100
}
]
var fontsize = 20
var txt = svg.selectAll('.box')
.data(data)
.join('text')
.attr("text-anchor", 'middle')
.attr("x", '50%')
.attr("y", '0%')
.attr("dominant-baseline", "central")
.attr("font-size", fontsize)
.style('border','1px solid red')
.attr('x',d => d.x)
.attr('y',d => d.y)
var tspan = txt.selectAll('tspan')
.data(d => d.text)
.join('tspan')
.attr('class', 'tspan')
.attr("x", '50%')
.attr('dy', function(d) {
return fontsize * 1.2
})
.text(d => d)
function add_grid(svg) {
var w = +svg.attr('width')
var step = 10
var mygrid = function(d) {
return `M 0,${d} l ${w},0 M ${d},0 l 0,${w}`
}
var grid = []
for(var i = 0; i < w; i+=step) {
grid.push(i);
}
svg.append('g')
.selectAll(null)
.data(grid).enter()
.append('path')
.attr('d',d => mygrid(d))
.attr('fill','none')
.attr('stroke','green')
.attr('stroke-width',.5)
}
}
<script src="https://d3js.org/d3.v7.min.js"></script>
Your text is aligned left since your text-anchor value is "start" instead of "middle".
Working example
demo()
function demo() {
var svg = d3.select('body').append('svg')
.attr('width', '100')
.attr('height', '100')
//.attr('viewBox','0 0 100 100')
var text = ["12", "123456789", "1234"]
var fontsize = 20
var txt = svg.append('text')
.attr("text-anchor", 'middle')
.attr("x", '50%')
.attr("y", '0%')
.attr("dominant-baseline", "central")
.attr("font-size", fontsize)
var tspan = txt.selectAll('tspan')
.data(text)
.join('tspan')
.attr('class', 'tspan')
.attr("x", '50%')
.attr('dy', function(d) {
return fontsize * 1.2
})
.text(d => d)
}
svg {
border: 1px solid red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.6.1/d3.min.js"></script>
dy can actually be better suited to mimic line breaks.
Worth mentioning:
there are no block-like elements in svg:
the parent svg won't automatically grow or shrink according to it's content/child node dimensions ( so you need to calculate an appropriate height and with depending on your text length)
<text> elements need a x value of "50%" or "viewBox width/2" to be centered
I strongly recommend to test the best <text> properties "statically" (e.g. in a codepen or a static html)
Second example:
You need to set the same x value for both <text> and <tspan> elements to mimic a center aligned text block.
Red circles illustrate the desired horizontal/vertical center points.
demo();
function demo() {
var svg = d3
.select("body")
.append("svg")
.attr("width", 300)
.attr("height", 200)
.style("background", "#ececec");
add_grid(svg);
var data = [
{
text: ["B", "BB", "BBB", "BBBB"],
x: 50,
y: 50
},
{
text: ["AAAAA", "AAA", "A"],
x: 150,
y: 100
}
];
var fontsize = 20;
var txt = svg
.selectAll(".box")
.data(data)
.join("text")
.attr("text-anchor", "middle")
.attr("dominant-baseline", "text-bottom")
.attr("font-size", fontsize)
.attr("x", (d) => d.x)
.attr("y", (d) =>{
let textL = d.text.length;
let yOffset = d.y - (fontsize * 1.2 * textL/2);
return (yOffset);
});
var tspan = txt
.selectAll("tspan")
.data((d) => d.text)
.join("tspan")
.attr("class", "tspan")
.attr("x", function (d) {
let currentX = d3.select(this.parentNode).attr("x");
return currentX;
})
.attr("dy", function (d) {
return fontsize * 1.2;
})
.text((d) => d);
function add_grid(svg) {
var w = +svg.attr("width");
var step = 10;
var mygrid = function (d) {
return `M 0,${d} l ${w},0 M ${d},0 l 0,${w}`;
};
var grid = [];
for (var i = 0; i < w; i += step) {
grid.push(i);
}
svg
.append("g")
.selectAll(null)
.data(grid)
.enter()
.append("path")
.attr("d", (d) => mygrid(d))
.attr("fill", "none")
.attr("stroke", "green")
.attr("stroke-width", 0.5);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.6.1/d3.min.js"></script>
<svg width="300" height="200" style="background: rgb(236, 236, 236);">
<g>
<path fill="none" stroke="green" stroke-width="0.5" d="M0 0l300 0m-300 0l0 300m0-290l300 0m-290-10l0 300m-10-280l300 0m-280-20l0 300m-20-270l300 0m-270-30l0 300m-30-260l300 0m-260-40l0 300m-40-250l300 0m-250-50l0 300m-50-240l300 0m-240-60l0 300m-60-230l300 0m-230-70l0 300m-70-220l300 0m-220-80l0 300m-80-210l300 0m-210-90l0 300m-90-200l300 0m-200-100l0 300m-100-190l300 0m-190-110l0 300m-110-180l300 0m-180-120l0 300m-120-170l300 0m-170-130l0 300m-130-160l300 0m-160-140l0 300m-140-150l300 0m-150-150l0 300m-150-140l300 0m-140-160l0 300m-160-130l300 0m-130-170l0 300m-170-120l300 0m-120-180l0 300m-180-110l300 0m-110-190l0 300m-190-100l300 0m-100-200l0 300m-200-90l300 0m-90-210l0 300m-210-80l300 0m-80-220l0 300m-220-70l300 0m-70-230l0 300m-230-60l300 0m-60-240l0 300m-240-50l300 0m-50-250l0 300m-250-40l300 0m-40-260l0 300m-260-30l300 0m-30-270l0 300m-270-20l300 0m-20-280l0 300m-280-10l300 0m-10-290l0 300" />
</g>
<circle cx="50" cy="50" r="2%" fill="red"/>
<text text-anchor="middle" dominant-baseline="text-bottom" font-size="20" x="50" y="2" style="border: 1px solid red;">
<tspan class="tspan" x="50" dy="24">B</tspan>
<tspan class="tspan" x="50" dy="24">BB</tspan>
<tspan class="tspan" x="50" dy="24">BBB</tspan>
<tspan class="tspan" x="50" dy="24">BBBB</tspan>
</text>
<circle cx="150" cy="100" r="2%" fill="red"/>
<text text-anchor="middle" dominant-baseline="text-bottom" font-size="20" x="150" y="64" style="border: 1px solid red;">
<tspan class="tspan" x="150" dy="24">AAAAA</tspan>
<tspan class="tspan" x="150" dy="24">AAA</tspan>
<tspan class="tspan" x="150" dy="24">A</tspan>
</text>
</svg>
I am new to d3js and I want to create the following SVG from a JSON file. I am using d3js version 5.16.
<svg width="1024" height="700">
<g id="gp_node1">
<rect x="0" y="50" width="1024" height="10" id="node1"></rect>
<g id="gm_node1">
<circle cy="200" r="10" id="node1"></circle>
<text x="0" y="10" id="textnode1">node1</text>
</g>
</g>
<g id="gp_node2">
<rect x="0" y="100" width="1024" height="10" id="node2"></rect>
<g id="gm_node2">
<circle cx="200" cy="200" r="10" id="subnode2"></circle>
<text x="0" y="10" id="textsubnode2">subnode2</text>
</g>
</g>
</svg>
It worked (except the attributes) but I do not think, that it is the best way to do it, especially the update function (had to comment the return). The code I ended up with is:
var json = {"mainnodes":[{"name":"node1","y":50,"subnodes":[{"name":"subnode1","x":100}]},{"name":"node2","y":100,"subnodes":[{"name":"subnode2","x":200}]}]};
var json2 = {"mainnodes":[{"name":"node1","y":50,"subnodes":[{"name":"subnode1","x":100}]},{"name":"node2","y":150,"subnodes":[{"name":"subnode2","x":200}]}]};
var data = json;
var structure;
var svgContainer = d3.select("body")
.append("svg")
.attr("width", 1024)
.attr("height", 700);
function update(){
structure = svgContainer.selectAll("g")
.data(data.mainnodes)
.join(
function(enter) {
return enter
.append("g")
.attr("id",function(d){return "gp_"+d.name;})
.append("rect")
.attr("x",0)
.attr("y",function(d){return d.y;})
.attr("width",1024)
.attr("height",10)
.attr("id",function(d){return d.name;})
.each(function(d) {
var g = d3.select(this.parentNode)
.append("g")
.attr("id",function(d){return "gm_"+d.name;})
g.data(d.subnodes)
.append("circle")
.attr("cx",function(d){return d.x;})
.attr("cy",200)
.attr("r",10)
.attr("id",function(d){return d.name;});
g.data(d.subnodes)
.append("text")
.attr("x",0)
.attr("y",10)
.attr("id",function(d){return "text"+d.name;})
.text(function(d){return d.name;});
})
},
function(update) {
var s = d3.select("g");
s.attr("id",function(d){return "gp_"+d.name;});
s.select("rect")
.attr("x",0)
.attr("y",function(d){return d.y;})
.attr("width",1024)
.attr("height",10)
.attr("id",function(d){return d.name;});
var g = s.select("g");
g.attr("id",function(d){return "gm_"+d.name;});
g.select("circle")
.attr("cx",function(d){return d.x;})
.attr("cy",200)
.attr("r",10)
.attr("id",function(d){return d.name;});
g.select("text")
.attr("x",0)
.attr("y",10)
.attr("id",function(d){return "text"+d.name;})
.text(function(d){return d.name;});
//return update;
},
function(exit) {
return exit;
}
)
}
d3.select('svg').on('click', function(){
data = json2;
update();
})
update();
In all examples I saw so far the enter function had only one element appended (so update and exit is always is referring to this one element only). But if I split the append functions up into separate joins, I am not able to get the SVG structure I need. I already tried a lot of variations. Maybe someone can show me how to do it correctly. Thanks!
This is how I would do it (if I am not allowed to change the data structure?). Does this help? If I can change the data structure, I can do it easier and cleaner. Since D3 is "Data Driven Documents", I normally start with that.
const json = {
"mainnodes":[
{"name":"node1","y":50,"subnodes":[{"name":"subnode1","x":100}]},
{"name":"node2","y":75,"subnodes":[{"name":"subnode2","x":25}]}
]
};
const json2 = {
"mainnodes":[
{"name":"node3","y":50,"subnodes":[{"name":"subnode1","x":100}]},
{"name":"node4","y":150,"subnodes":[{"name":"subnode2","x":200}]}
]
};
const data = [json, json2];
let currIndex = 0;
const svg = d3.select("body")
.append("svg")
.attr("width", 1024)
.attr("height", 700);
function update(data){
const parentGroups = svg.selectAll('.gp')
.data(data.mainnodes);
parentGroups.enter()
.append("g")
.attr('class', 'gp')
.merge(parentGroups)
.transition()
.duration(750)
.attr("id",function(d){return "gp_"+d.name;});
const gr = parentGroups.selectAll('.gr')
.data(d => [d]);
gr.enter()
.append("rect")
.attr('class', 'gr')
.merge(gr)
.transition()
.duration(750)
.attr("x",0)
.attr("y",function(d){return d.y;})
.attr("width",1024)
.attr("height",10)
.attr("id",function(d){return d.name;});
const subGroups = parentGroups.selectAll('.gm')
.data(d => [d]);
subGroups.enter()
.append('g')
.attr('class', 'gm')
.attr("id",function(d){return "gm_"+d.name;})
.merge(subGroups)
.transition()
.duration(750);
const c_shapes = subGroups.selectAll('.circles')
.data(d => d.subnodes);
c_shapes.enter()
.append('circle')
.attr('class', 'circles')
.attr("id",function(d){return d.name;})
.merge(c_shapes)
.transition()
.duration(750)
.attr("cx",function(d){return d.x;})
.attr("cy",200)
.attr("r",10);
const t_shapes = subGroups.selectAll('.texts')
.data(d => d.subnodes);
t_shapes.enter()
.append('text')
.attr('class', 'texts')
.attr("id",function(d){return "text"+d.name;})
.merge(t_shapes)
.transition()
.duration(750)
.attr("x",function(d){return d.x;})
.attr("y",230)
.text(function(d){return d.name;});
///*
parentGroups.exit().remove();
gr.exit().remove();
subGroups.exit().remove();
c_shapes.exit().remove();
t_shapes.exit().remove();
//*/
}
console.log('start');
//not ideal, I think there is a way to avoid it but it is not coming to mind...
update(data[currIndex]);
update(data[currIndex]);
update(data[currIndex]);
svg.on('click', function(){
currIndex = (currIndex + 1) % 2;
//console.log('click', 'currIndex: ' + currIndex);
update(data[currIndex]);
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<!-- desired output:
<svg width="1024" height="700">
<g id="gp_node1">
<rect x="0" y="50" width="1024" height="10" id="node1"></rect>
<g id="gm_node1">
<circle cy="200" r="10" id="node1"></circle>
<text x="0" y="10" id="textnode1">node1</text>
</g>
</g>
<g id="gp_node2">
<rect x="0" y="100" width="1024" height="10" id="node2"></rect>
<g id="gm_node2">
<circle cx="200" cy="200" r="10" id="subnode2"></circle>
<text x="0" y="10" id="textsubnode2">subnode2</text>
</g>
</g>
</svg>
-->
Output:
An alternative approach, changing the data structure and using d3-selection-multi:
const json = {
"mainnodes":[
{"shape":"g","name":"gp_node1",
"subnodes":[
{"shape":"rect","name":"rectnode1","width":1024,"height":10,"y":50,"x":0},
{"shape":"g","name":"gm_node1",
"subnodes":[
{"shape":"circle","name":"circlenode1","cy":150,"cx":200,"r":10},
{"shape":"text","name":"textnode1","text":"subnode1","y":170,"x":200},
]
}
]
},
{"shape":"g", "name":"gp_node2",
"subnodes":[
{"shape":"rect","name":"rectnode2","width":1024,"height":10,"y":75,"x":0},
{"shape":"g","name":"gm_node2",
"subnodes":[
{"shape":"circle","name":"circlenode2","cy":150,"cx":100,"r":10},
{"shape":"text","name":"textnode2","text":"subnode2","y":170,"x":100},
]
}
]
}
]
};
const json2 = {
"mainnodes":[
{"shape":"g","name":"gp_node3",
"subnodes":[
{"shape":"rect","name":"rectnode3","width":1024,"height":10,"y":50,"x":0},
{"shape":"g","name":"gm_node3",
"subnodes":[
{"shape":"circle","name":"circlenode3","cy":150,"cx":250},
{"shape":"text","name":"textnode3","text":"subnode3","y":170,"x":250},
]
}
]
},
{"shape":"g", "name":"gp_node4",
"subnodes":[
{"shape":"rect","name":"rectnode4","width":1024,"height":10,"y":100,"x":0},
{"shape":"g","name":"gm_node2",
"subnodes":[
{"shape":"circle","name":"circlenode2","cy":150,"cx":100},
{"shape":"text","name":"textnode2","text":"subnode4","y":170,"x":100},
]
}
]
}
]
};
const data = [json, json2];
let currIndex = 0;
const svg = d3.select("body")
.append("svg")
.attr("width", 1024)
.attr("height", 700);
function update(data){
const parentGroups = svg.selectAll('.gp')
.data(data.mainnodes);
parentGroups.enter()
.append("g")
.attr('class', 'gp')
.merge(parentGroups)
.transition()
.duration(750)
.attr("id",function(d){return d.name;});
const subGroups_d1 = parentGroups.selectAll('.gm')
.data(d => d.subnodes);
subGroups_d1.enter()
.append(d => document.createElementNS("http://www.w3.org/2000/svg", d.shape))
.attr('class', 'gm')
.attr("id",function(d){return d.name;})
.merge(subGroups_d1)
.transition()
.duration(750)
.attrs(d => {
const copy = {...d}
delete copy.shape;
delete copy.name;
return copy;
});
const subGroups_d2 = subGroups_d1.selectAll('.gn')
.data(d => d.subnodes || []);
subGroups_d2.enter()
.append(d => document.createElementNS("http://www.w3.org/2000/svg", d.shape))
.attr('class', 'gn')
.attr("id",function(d){return d.name;})
.merge(subGroups_d2)
.transition()
.duration(750)
.attrs(d => {
const copy = {...d}
delete copy.shape;
delete copy.name;
return copy;
})
.text(d => d.text || "");
///*
parentGroups.exit().remove();
subGroups_d1.exit().remove();
subGroups_d1.exit().remove();
//*/
}
console.log('start');
//not ideal, I think there is a way to avoid it but it is not coming to mind...
update(data[currIndex]);
update(data[currIndex]);
update(data[currIndex]);
svg.on('click', function(){
currIndex = (currIndex + 1) % 2;
//console.log('click', 'currIndex: ' + currIndex);
update(data[currIndex]);
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<script src="https://d3js.org/d3-selection-multi.v1.min.js"></script>
<!-- desired output:
<svg width="1024" height="700">
<g id="gp_node1">
<rect x="0" y="50" width="1024" height="10" id="node1"></rect>
<g id="gm_node1">
<circle cy="200" r="10" id="node1"></circle>
<text x="0" y="10" id="textnode1">node1</text>
</g>
</g>
<g id="gp_node2">
<rect x="0" y="100" width="1024" height="10" id="node2"></rect>
<g id="gm_node2">
<circle cx="200" cy="200" r="10" id="subnode2"></circle>
<text x="0" y="10" id="textsubnode2">subnode2</text>
</g>
</g>
</svg>
-->
I'm pretty new to D3.js, and I've been able to create a simple animation that suits my needs from the Health and Wealth of Nations example. I had some trouble with adding labels to SVG circles, and when I investigated the source I found that while a group is being appended for each object in my dataset, all of the circles are being added to the last group only.
JSON data:
{"numSegments":4,
"movement":[
{"Id":"1",
"xValues":[[0,350],[1,400],[2,350],[3,300],[4,350]],
"yValues":[[0,690],[1,640],[2,590],[3,640],[4,690]]},
{"Id":"2",
"xValues":[[0,150],[1,150],[2,150],[3,150], [4, 150]],
"yValues":[[0,690],[1,490],[2,690],[3,490],[4, 690]]},
{"Id":"3",
"xValues":[[0,550],[1,650],[2,550],[3,650],[4,550]],
"yValues":[[0,690],[1,590],[2,490],[3,390],[4,490]]},
{"Id":"4",
"xValues":[[0,0],[1,200],[2,0],[3,200],[4,0]],
"yValues":[[0,0],[1,200],[2,0],[3,200],[4,0]]}
]
}
Relevant JavaScript:
var chart = d3.select(".chart")
.attr("width", width)
.attr("height", height)
var g = chart.append("g")
.selectAll(".node")
.data(interpolateData(0));
var sections = g.enter().append("g")
.attr("class", "node");
console.log(sections.size()); // this returns "4" as expected
var dot = sections.append("circle")
.attr("class", "dot")
.attr("fill", function (d) { return colour(key(d)); })
.attr("r", "10")
.call(position)
.sort(order);
dot.append("title")
.text(function (d) { return key(d); });
var text = sections.append("text")
.text(function (d) { return key(d); })
.call(positionText)
.sort(order);
SVG output:
<svg class="chart" width="700" height="700">
<g>
<g class="node"></g>
<g class="node"></g>
<g class="node"></g>
<g class="node">
<circle class="dot" fill="#fff" r="10">
<title>1</title>
</circle>
<circle class="dot" fill="#fff" r="10">
<title>2</title>
</circle>
<circle class="dot" fill="#fff" r="10">
<title>3</title>
</circle>
<circle class="dot" fill="#fff" r="10">
<title>4</title>
</circle>
<text>1</text>
<text>2</text>
<text>3</text>
<text>4</text>
</g>
</g>
If the size of sections is 4 as the console reports, is it not an array of the four <g> elements that were just appended? And if so, why does sections.append() add four elements to the last group instead of adding one element to each of the four groups?
I have the following code to generate a simple graph in D3...
var width = 800, height = 800;
// force layout setup
var force = d3.layout.force()
.charge(-200).linkDistance(30).size([width, height]);
// setup svg div
var svg = d3.select("#graph").append("svg")
.attr("width", "100%").attr("height", "100%")
.attr("pointer-events", "all");
// load graph (nodes,links) json from /graph endpoint
d3.json("/graph", function(error, graph) {
if (error) return;
force.nodes(graph.nodes).links(graph.links).start();
// render relationships as lines
var link = svg.selectAll(".link")
.data(graph.links).enter()
.append("line")
.attr("class", "link")
.attr("stroke", "black");
// render nodes as circles, css-class from label
var node = svg.selectAll(".node")
.data(graph.nodes).enter()
.append("circle")
.attr("r", 10)
.call(force.drag);
// html title attribute for title node-attribute
node.append("title")
.text(function (d) { return d.title; })
.attr("font-family", "sans-serif")
.attr("font-size", "20px")
.attr("fill", "black");
// force feed algo ticks for coordinate computation
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; });
});
});
Everything looks great except I cannot see the title. If I look at the DOM I see the following....
<circle r="10" cx="384.5368115283669" cy="390.4516626058579"><title font-family="sans-serif" font-size="20px" fill="black">My NAme</title></circle>
However no matter what I try I cannot seem to see the title. What am I missing here?
This is nothing to blame on d3. As per the SVG 1.1 spec the title is a description string which will not be rendered when the graphic gets rendered. Most browsers, however, will display the title as a tooltip when the mouse pointer is over the element.
I have set up an updated snippet based on your code. Placing the mouse over the red circle will bring up the tooltip "My Name".
<svg width="200" height="200">
<circle r="10" cx="38" cy="39" fill="red">
<title>My Name</title>
</circle>
</svg>
To add text to your svg which will get rendered, use the <text> element instead.
<svg width="200" height="200">
<text x="50" y="100">My Text</text>
</svg>
I create a group with 9 elements (circles) such as:
// JS
var data=[ 1,2,3,4,5,6,7,8,9 ];
var svg = d3.select("body").append("svg");
var circles = svg.append("g").attr("id", "groupOfCircles")
.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("cx", function(d){ return d*20;})
.attr("cy", function(d){ return d*10;})
.attr("r" , function(d){ return d;})
.attr("fill","green");
//xml
<svg>
<g id="groupOfCircles">
<circle cx="20" cy="10" r="1" fill="green"></circle>
<circle cx="40" cy="20" r="2" fill="green"></circle>
<circle cx="60" cy="30" r="3" fill="green"></circle>
<circle cx="80" cy="40" r="4" fill="green"></circle>
<circle cx="100" cy="50" r="5" fill="green"></circle>
<circle cx="120" cy="60" r="6" fill="green"></circle>
<circle cx="140" cy="70" r="7" fill="green"></circle>
<circle cx="160" cy="80" r="8" fill="green"></circle>
<circle cx="180" cy="90" r="9" fill="green"></circle>
</g>
</svg>
But How to select the nth element (i.e : the 3rd circle) of the group groupOfCircles while not knowing the circles' id or attributes values ?
I will later on loop over all elements via a for loop, and color each for one second.
Note: I tried things such as :
circles[3].attr("fill","red") // not working
d3.select("#groupOfCircles:nth-child(3)").attr("fill","red") // not working
..
The selector needs to be circle:nth-child(3) -- the child means that the element is the nth child, not to select the nth child of the element (see here).
You could also use:
// JS
var data=[ 1,2,3,4,5,6,7,8,9 ];
var svg = d3.select("body").append("svg");
var circles = svg.append("g").attr("id", "groupOfCircles")
.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("cx", function(d){ return d*20;})
.attr("cy", function(d){ return d*10;})
.attr("r" , function(d){ return d;})
.attr("fill","green");
d3.select("circle:nth-child(3)").attr("fill","red"); // <== CSS selector (DOM)
d3.select(circles[0][4]).attr("fill","blue"); // <== D3 selector (node)
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
d3 v4 now supports using selection.nodes() to return an array of all elements of this selection. Then, we can change the attributes of nth element by d3.select(selection.nodes()[n]).attr(something)
// JS
var data=[ 1,2,3,4,5,6,7,8,9 ];
var svg = d3.select("body").append("svg");
var circles = svg.append("g").attr("id", "groupOfCircles")
.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("cx", function(d){ return d*20;})
.attr("cy", function(d){ return d*10;})
.attr("r" , function(d){ return d;})
.attr("fill","green");
circleElements = circles.nodes(); // <== Get all circle elements
d3.select(circleElements[6]).attr("fill","red");
<script src="https://d3js.org/d3.v4.min.js"></script>
You can also use your circles array to set the element's attribute:
d3.select(circles[3]).attr("fill","red");
// JS
var data=[ 1,2,3,4,5,6,7,8,9 ];
var svg = d3.select("body").append("svg");
var circles = svg.append("g").attr("id", "groupOfCircles")
.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("cx", function(d){ return d*20;})
.attr("cy", function(d){ return d*10;})
.attr("r" , function(d){ return d;})
.attr("fill","green");
var group = document.querySelector('#groupOfCircles');
var circleNodes = group.getElementsByTagName('circle');
d3.select(circleNodes[3]).attr("fill", "red");
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
If you want to do it in d3 logic, the anonymous function always has an index parameter aside the data:
my_selection.attr("fill",function(d,i) {return i%3==0?"red":"green";});
http://jsfiddle.net/risto/os5fj9m6/
Here is another option by using a function as the selector.
circles.select(function (d,i) { return (i==3) ? this : null;})
.attr("fill", "red");
If the selector is a function it gets the datum (d) and the iterator (i) as parameter. It then returns either the object (this) if selected or null if not selected.