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.
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 am wondering if there is a way to get svg text of an element to align to the right. It is currently working, positioned left and wonder if I am missing something simple.
svg.append("text")
.attr("class", "role-title")
.text(function (d) {return d.role;})
.call(wrapText, 190)
.attr('transform', function (d) {
if (d.place === 1) {
return 'translate(75,-40)'; ... // parameter for right align
<text class="role-title" transform="translate(-240, 50)">
<tspan x="0" dy="0em">Computer and Information</tspan>
<tspan x="0" dx="0" dy="1.1em">Research Scientist</tspan>
</text>
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>