Related
I've been trying to build a multiline chart of three lines and display tooltip along with circles on data points when hovered over. However the circle is not following the line and at certain data points and it comes out of the line. Setting the element focus, for the particular line by adjusting transform, translate for the specific line solves the issue but then messes up the other lines. How to pass all the datasets instead of one dataset on focus element? I tried sorting all the datasets in one common dataset consisting of all the objects and pass it on transform of focus but that is also making the circle shift away from the lines at certain datapoints
drawAUCROCGraphExpanded(fprTpr_train, fprTpr_test, fprTpr_oot, fprTpr_default, trainroc, testroc, ootroc) {
var margin = { top: 20, right: 0, bottom: 20, left: 0 },
width = 560 - margin.left - margin.right,
height = 395 - margin.top - margin.bottom;
var x = d3.scaleLinear().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
// Draw the X-axis on the DOM
this.aucRocSvgExpanded.append('g')
.attr('transform', 'translate(0,' + height + ')')
.call(d3.axisBottom(x).ticks(5));
// Draw the Y-axis on the DOM
this.aucRocSvgExpanded.append('g')
.attr('transform', 'translate(0, 0)')
.call(d3.axisLeft(y).ticks(10));
var valueline = d3.line()
.curve(d3.curveMonotoneX)
.x(function (d: any) { return x(d.fpr); })
.y(function (d: any) { return y(d.tpr); });
var valueline2 = d3.line()
.curve(d3.curveMonotoneX)
.x(function (d: any) { return x(d.fpr); })
.y(function (d: any) { return y(d.tpr); });
var valueline3 = d3.line()
.curve(d3.curveMonotoneX)
.x(function (d: any) { return x(d.fpr); })
.y(function (d: any) { return y(d.tpr); });
var valueline4 = d3.line()
.curve(d3.curveMonotoneX)
.x(function (d: any) { return x(d.fpr); })
.y(function (d: any) { return y(d.tpr); });
x.domain([0, 1]);
y.domain([0, 1]);
this.aucRocSvgExpanded.append("path")
.attr("style", 'stroke: #c1c2c2; stroke-width: 2; fill: none; stroke-dasharray: 3, 4.5;')
.attr("d", valueline(fprTpr_default))
this.aucRocSvgExpanded.append("path")
.attr("style", 'stroke: #2E4A71; stroke-width: 2; fill: none;')
//.style('fill', d => fprTpr_flag == true ? '#e6e8ed;' : null )
.attr("d", valueline2(fprTpr_train))
.attr("class", "line")
.attr("width", width)
.attr("height", height)
if (fprTpr_test.length > 0) {
this.aucRocSvgExpanded.append("path")
.attr("style", 'stroke: #517BAD; stroke-width: 2; fill: none;')
//.style('fill', d => fprTpr_flag == true ? '#e6e8ed;' : null )
.attr("d", valueline3(fprTpr_test))
.attr("class", "line")
.attr("width", width)
.attr("height", height)
}
if (fprTpr_oot.length > 0) {
this.aucRocSvgExpanded.append("path")
.attr("style", 'stroke: #74A0D8; stroke-width: 2; fill: none;')
.attr("d", valueline4(fprTpr_oot))
.attr("class", "line")
.attr("width", width)
.attr("height", height)
}
var focus = this.aucRocSvgExpanded.append("g")
.attr("class", "focus")
.style("display", "none")
// append the x line
focus.append("line")
.attr("class", "x")
.style("stroke", "blue")
.style("stroke-dasharray", "3,3")
.style("opacity", 0.5)
.attr("y1", 0)
.attr("y2", height);
// append the circles at the intersection
focus.append("circle")
.attr("id", "train_circle")
.style("fill", "#2E4A71")
.style("stroke", "#2E4A71")
.attr("r", 4);
if (fprTpr_test.length > 0) {
focus.append("circle")
.attr("id", "test_circle")
.style("fill", "#517BAD")
.style("stroke", "#517BAD")
.attr("r", 4);
}
if (fprTpr_oot.length > 0) {
focus.append("circle")
.attr("id", "oot_circle")
.style("fill", "#74A0D8")
.style("stroke", "#74A0D8")
.attr("r", 4);
}
var bisectData = d3.bisector((d: any) => d.fpr).right,
sortData_train = fprTpr_train.sort(function (a, b) { return d3.ascending(a.fpr, b.fpr) }),
sortData_test = fprTpr_test.sort(function (a, b) { return d3.ascending(a.fpr, b.fpr) }),
sortData_oot = fprTpr_oot.sort(function (a, b) { return d3.ascending(a.fpr, b.fpr) })
const mouseover = () => {
focus.style("display", null);
this.roc_tooltipExpanded.style("display", "block")
}
const mouseout = () => {
focus.style("display", "none");
this.roc_tooltipExpanded.style("display", "none")
}
const mousemove = (event) => {
var x0 = x.invert(d3.pointer(event)[0]),
i = bisectData(sortData_train, x0, 1),
j = bisectData(sortData_test, x0, 1),
k = bisectData(sortData_oot, x0, 1),
a = sortData_train[i].fpr,
d = sortData_train[i].tpr
if (fprTpr_test.length > 0) {
var b = sortData_test[j].fpr,
e = sortData_test[j].tpr
}
if (fprTpr_oot.length > 0) {
var c = sortData_oot[k].fpr,
f = sortData_oot[k].tpr
}
if (fprTpr_oot.length > 0) {
focus.attr("transform", "translate(" + x(sortData_oot[k].fpr) + "," + 0 + ")");
} else if (fprTpr_test.length > 0) {
focus.attr("transform", "translate(" + x(sortData_test[j].fpr) + "," + 0 + ")"); //selecting one of this fixes issue for the particular line but messes up others
} else {
focus.attr("transform", "translate(" + x(sortData_train[i].fpr) + "," + 0 + ")");
}
var roc_label;
if (fprTpr_oot.length > 0 && fprTpr_test.length > 0) {
roc_label = `<div style="height: 20px; padding: 2px 4px; background-color: #E6E8ED;"">AUC:</div><div style="background-color: #fff; padding: 2px 4px"><div style = "color: #2E4A71;"><span>Train:</span> ${trainroc} - <b>${d}</b></div><div style="color: #517BAD;"><span style="padding: 0px 7px 0px 0px;">Test:</span> ${testroc} - <b>${e}</b></div><div style="color: #74A0D8;"><span style="padding: 0px 6px 0px 0px;">OOT:</span> ${ootroc} - <b>${f}</b></div></div>`;
} else if (fprTpr_test.length > 0 && fprTpr_oot.length <= 0) {
roc_label = `<div style="height: 20px; padding: 2px 4px; background-color: #E6E8ED;"">AUC:</div><div style="background-color: #fff; padding: 2px 4px"><div style = "color: #2E4A71;><span>Train:</span> ${trainroc} - <b>${d}</b></div><div style="color: #517BAD;><span style="padding: 0px 7px 0px 0px;">Test:</span> ${testroc} - <b>${e}</b></div></div>`;
} else if (fprTpr_test.length <= 0 && fprTpr_oot.length > 0) {
roc_label = `<div style="height: 20px; padding: 2px 4px; background-color: #E6E8ED;"">AUC:</div><div style="background-color: #fff; padding: 2px 4px"><div style = "color: #2E4A71;><span>Train:</span> ${trainroc} - <b>${d}</b></div><div style="color: #74A0D8;"><span style="padding: 0px 6px 0px 0px;">OOT:</span> ${ootroc} - <b>${f}</b></div></div>`;
} else {
roc_label = `<div style="height: 20px; padding: 2px 4px; background-color: #E6E8ED;"">AUC:</div><div style="background-color: #fff; padding: 2px 4px"><div style = "color: #2E4A71;><span>Train:</span> ${trainroc} - <b>${d}</b></div></div>`;
}
this.roc_tooltipExpanded
.html(roc_label)
// .style("left", (d3.pointer(event)[0]+40) + "px")
.style("right", (d3.pointer(event)[0]) + "px")
.style("top", (d3.pointer(event)[1]) + "px")
.style("pointer-events", "none")
.style("z-index", "1")
.style("display", "block")
focus.select("circle#train_circle").attr("transform", "translate(" + x(0) + "," + y(sortData_train[i].tpr) + ")")
if (fprTpr_test.length > 0) {
focus.select("circle#test_circle").attr("transform", "translate(" + x(0) + "," + y(sortData_test[j].tpr) + ")")
}
if (fprTpr_oot.length > 0) {
focus.select("circle#oot_circle").attr("transform", "translate(" + x(0) + "," + y(sortData_oot[k].tpr) + ")")
}
}
this.aucRocSvgExpanded.append("g")
.append("rect")
.attr("class", "rect")
.attr("width", width)
.attr("height", height)
.style("fill", "transparent")
.on("mouseover", mouseover)
.on("mouseout", mouseout)
.on("mousemove", mousemove);
this.aucRocSvgExpanded.append("g")
.append("text")
.attr('y', 390)
.attr('x', 280)
.style("font-size", "14px")
.style("font-weight", "600")
.style("text-anchor", "middle")
.text("FPR");
this.aucRocSvgExpanded.append("g")
.append("text")
.attr("transform", "rotate(-90)")
.attr('y', -35)
.attr('x', -180)
.style("font-size", "14px")
.style("font-weight", "600")
.style("text-anchor", "middle")
.text("TPR");
}
I am interested to display nodes on 3 different colors based on 3 groups defined in csv file (R=Red,G=green,B=blue etc).
However my code is not returning 3 different colors.
Index.html file
<!DOCTYPE html>
enter code here`<meta charset="utf-8">
enter code here`<script src="http://d3js.org/d3.v3.js"></script>
<style>
path.link {
fill: none;
stroke: #666;
stroke-width: 1.5px;
}
path.link.twofive {
opacity: 0.25;
}
path.link.fivezero {
opacity: 0.50;
}
path.link.sevenfive {
opacity: 0.75;
}
path.link.onezerozero {
opacity: 1.0;
}
circle {
fill: #ccc;
stroke: #fff;
stroke-width: 1.5px;
}
text {
fill: #000;
font: 10px sans-serif;
pointer-events: none;
}
</style>
<body>
<script>
// get the data
d3.csv("force1.csv", function(error, links) {
var nodes = {};
// Compute the distinct nodes from the links.
links.forEach(function(link) {
link.source = nodes[link.source] ||
(nodes[link.source] = {name: link.source});
link.target = nodes[link.target] ||
(nodes[link.target] = {name: link.target});
link.value = +link.value;
});
var width = 960,
height = 500,
//color = d3.scale.category20c();
color = d3.scale.ordinal()
.domain(["G", "R", "B"])
.range(["#009933" ,"#FF0000","#0000FD"]);
var force = d3.layout.force()
.nodes(d3.values(nodes))
.links(links)
.size([width, height])
.linkDistance(60)
.charge(-300)
.on("tick", tick)
.start();
// Set the range
var v = d3.scale.linear().range([0, 100]);
// Scale the range of the data
v.domain([0, d3.max(links, function(d) { return d.value; })]);
// asign a type per value to encode opacity
links.forEach(function(link) {
if (v(link.value) <= 25) {
link.type = "twofive";
} else if (v(link.value) <= 50 && v(link.value) > 25) {
link.type = "fivezero";
} else if (v(link.value) <= 75 && v(link.value) > 50) {
link.type = "sevenfive";
} else if (v(link.value) <= 100 && v(link.value) > 75) {
link.type = "onezerozero";
}
});
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
// build the arrow.
svg.append("svg:defs").selectAll("marker")
.data(["end"]) // Different link/path types can be defined here
.enter().append("svg:marker") // This section adds in the arrows
.attr("id", String)
.attr("viewBox", "0 -5 10 10")
.attr("refX", 15)
.attr("refY", -1.5)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5");
// add the links and the arrows
var path = svg.append("svg:g").selectAll("path")
.data(force.links())
.enter().append("svg:path")
.attr("class", function(d) { return "link " + d.type; })
.attr("marker-end", "url(#end)");
// define the nodes
var node = svg.selectAll(".node")
.data(force.nodes())
.enter().append("g")
.attr("class", "node")
//.style("fill", function(d) { return color(d.group); })
.on("click", click)
.on("dblclick", dblclick)
.call(force.drag);
// add the nodes
/*node.append("circle")
.attr("r", 5)
//.style("fill", function(d) { return color(d.name); });
.style("fill", function(d) {
//if (d.link.group="G")
//return color(d.green)
//else if(d.link.group="R")
return color (d.Red); });*/
//My amendment but style attr is not working
node.append("circle")
.attr("r", 5)
.style("fill", function(d) { return color(d.group); })
// add the text
node.append("text")
.attr("x", 12)
.attr("dy", ".35em")
.text(function(d) { return d.name; });
// add the curvy lines
function tick() {
path.attr("d", function(d) {
var dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y,
dr = Math.sqrt(dx * dx + dy * dy);
return "M" +
d.source.x + "," +
d.source.y + "A" +
dr + "," + dr + " 0 0,1 " +
d.target.x + "," +
d.target.y;
});
node
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")"; });
}
// action to take on mouse click
function click() {
d3.select(this).select("text").transition()
.duration(750)
.attr("x", 22)
.style("stroke", "lightsteelblue")
.style("stroke-width", ".5px")
.style("font", "20px sans-serif");
d3.select(this).select("circle").transition()
.duration(750)
.attr("r", 16);
}
// action to take on mouse double click
function dblclick() {
d3.select(this).select("circle").transition()
.duration(750)
.attr("r", 6);
d3.select(this).select("text").transition()
.duration(750)
.attr("x", 12)
.style("stroke", "none")
.style("fill", "black")
.style("stroke", "none")
.style("font", "10px sans-serif");
}
});
</script>
</body>
</html>
force1.csv file data is as under
source,target,value,group
A,B,1.2,G
C,D,1.3,G
B,D,0.2,G
E,A,0.5,G
A,F,0.6,R
G,H,0.5,B
J,A,0.7,R
Despite the fact that you have a group property in the original links array, you don't have a group property for the nodes.
Solution: create one.
links.forEach(function(link) {
link.source = nodes[link.source] ||
(nodes[link.source] = {
name: link.source,
group: link.group//create the group property here
});
link.target = nodes[link.target] ||
(nodes[link.target] = {
name: link.target
});
link.value = +link.value;
});
Here is your working code:
var links = d3.csv.parse(d3.select("#csv").text())
var nodes = {};
// Compute the distinct nodes from the links.
links.forEach(function(link) {
link.source = nodes[link.source] ||
(nodes[link.source] = {
name: link.source,
group: link.group
});
link.target = nodes[link.target] ||
(nodes[link.target] = {
name: link.target
});
link.value = +link.value;
});
var width = 400,
height = 300,
//color = d3.scale.category20c();
color = d3.scale.ordinal()
.domain(["G", "R", "B"])
.range(["#009933", "#FF0000", "#0000FD"]);
var force = d3.layout.force()
.nodes(d3.values(nodes))
.links(links)
.size([width, height])
.linkDistance(60)
.charge(-300)
.on("tick", tick)
.start();
// Set the range
var v = d3.scale.linear().range([0, 100]);
// Scale the range of the data
v.domain([0, d3.max(links, function(d) {
return d.value;
})]);
// asign a type per value to encode opacity
links.forEach(function(link) {
if (v(link.value) <= 25) {
link.type = "twofive";
} else if (v(link.value) <= 50 && v(link.value) > 25) {
link.type = "fivezero";
} else if (v(link.value) <= 75 && v(link.value) > 50) {
link.type = "sevenfive";
} else if (v(link.value) <= 100 && v(link.value) > 75) {
link.type = "onezerozero";
}
});
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
// build the arrow.
svg.append("svg:defs").selectAll("marker")
.data(["end"]) // Different link/path types can be defined here
.enter().append("svg:marker") // This section adds in the arrows
.attr("id", String)
.attr("viewBox", "0 -5 10 10")
.attr("refX", 15)
.attr("refY", -1.5)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5");
// add the links and the arrows
var path = svg.append("svg:g").selectAll("path")
.data(force.links())
.enter().append("svg:path")
.attr("class", function(d) {
return "link " + d.type;
})
.attr("marker-end", "url(#end)");
// define the nodes
var node = svg.selectAll(".node")
.data(force.nodes())
.enter().append("g")
.attr("class", "node")
//.style("fill", function(d) { return color(d.group); })
.on("click", click)
.on("dblclick", dblclick)
.call(force.drag);
// add the nodes
/*node.append("circle")
.attr("r", 5)
//.style("fill", function(d) { return color(d.name); });
.style("fill", function(d) {
//if (d.link.group="G")
//return color(d.green)
//else if(d.link.group="R")
return color (d.Red); });*/
//My amendment but style attr is not working
node.append("circle")
.attr("r", 5)
.style("fill", function(d) {
return color(d.group);
})
// add the text
node.append("text")
.attr("x", 12)
.attr("dy", ".35em")
.text(function(d) {
return d.name;
});
// add the curvy lines
function tick() {
path.attr("d", function(d) {
var dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y,
dr = Math.sqrt(dx * dx + dy * dy);
return "M" +
d.source.x + "," +
d.source.y + "A" +
dr + "," + dr + " 0 0,1 " +
d.target.x + "," +
d.target.y;
});
node
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
}
// action to take on mouse click
function click() {
d3.select(this).select("text").transition()
.duration(750)
.attr("x", 22)
.style("stroke", "lightsteelblue")
.style("stroke-width", ".5px")
.style("font", "20px sans-serif");
d3.select(this).select("circle").transition()
.duration(750)
.attr("r", 16);
}
// action to take on mouse double click
function dblclick() {
d3.select(this).select("circle").transition()
.duration(750)
.attr("r", 6);
d3.select(this).select("text").transition()
.duration(750)
.attr("x", 12)
.style("stroke", "none")
.style("fill", "black")
.style("stroke", "none")
.style("font", "10px sans-serif");
}
path.link {
fill: none;
stroke: #666;
stroke-width: 1.5px;
}
path.link.twofive {
opacity: 0.25;
}
path.link.fivezero {
opacity: 0.50;
}
path.link.sevenfive {
opacity: 0.75;
}
path.link.onezerozero {
opacity: 1.0;
}
circle {
fill: #ccc;
stroke: #fff;
stroke-width: 1.5px;
}
pre {
display: none;
}
text {
fill: #000;
font: 10px sans-serif;
pointer-events: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<pre id="csv">source,target,value,group
A,B,1.2,G
C,D,1.3,G
B,D,0.2,G
E,A,0.5,G
A,F,0.6,R
G,H,0.5,B
J,A,0.7,R</pre>
I have a problem with marks on my D3 map. When I set up zooming for each state the marks are not zooming within the map..
That's my first try for D3 map, so sorry if I messed up something in the code. If I did or missed something I'll really appreciate explanation.
If anyone can help with that I'll be really thankful.
var width = 960,
height = 500,
centered;
var projection = d3.geo.albersUsa()
.scale(1070)
.translate([width / 2, height / 2]);
var path = d3.geo.path()
.projection(projection);
var svg = d3.select("body")
.append("svg")
.attr("viewBox", "0 0 " + width + " " + height )
.attr("preserveAspectRatio", "xMinYMin");
svg.append("rect")
.attr("class", "background")
.attr("width", width)
.attr("height", height)
.on("click", clicked);
var g = svg.append("g");
var div = d3.select("body")
.append("div")
.attr("class", "tooltip")
.style("opacity", 0);
d3.json("https://bl.ocks.org/mbostock/raw/4090846/us.json", function(error, us) {
if (error) throw error;
g.append("g")
.attr("id", "states")
.selectAll("path")
.data(topojson.feature(us, us.objects.states).features)
.enter().append("path")
.attr("d", path)
.style("fill", "#26404b")
.on("click", clicked);
g.append("path")
.datum(topojson.mesh(us, us.objects.states, function(a, b) { return a !== b; }))
.attr("id", "state-borders")
.attr("d", path);
// CITIES
d3.json("https://gist.githubusercontent.com/Miserlou/c5cd8364bf9b2420bb29/raw/2bf258763cdddd704f8ffd3ea9a3e81d25e2c6f6/cities.json", function(data) {
svg.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("cx", function(d) {
return projection([d.longitude, d.latitude])[0];
})
.attr("cy", function(d) {
return projection([d.longitude, d.latitude])[1];
})
.attr("r", function(d) {
if(d.population >= 1 && d.population < 10000){
return 1;
}
else if(d.population >= 10000 && d.population < 100000){
return 2;
}
else if(d.population >= 100000 && d.population < 500000){
return 3;
}
else if(d.population >= 500000 && d.population < 1000000){
return 4;
}
else if(d.population >= 1000000 && d.population < 5000000){
return 5;
}
else if(d.population < 5000000){
return 10;
}
else {
return 0;
};
})
.style("fill", "rgba(26, 188, 156,0.8)")
// HOVER
.on("mouseover", function(d) {
d3.select(this)
.transition()
.duration(200)
.style('fill', 'rgba(26, 188, 156, 0.3)')
.style('stroke', '#1abc9c')
.style('stroke-width', 4);
div.transition()
.duration(200)
.style("opacity", .9);
div.html('<span id="place">City: ' + d.city + '</span><br><span id="people">Calls: ' + d.population + '</span>')
.style("left", (d3.event.pageX) + 35 + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout", function(d) {
d3.select(this)
.transition()
.duration(200)
.style('fill', 'rgba(26, 188, 156, 0.8)')
.style('stroke-width', 0);
div.transition()
.duration(200)
.style("opacity", 0);
});
});
});
function clicked(d) {
var x, y, k;
if (d && centered !== d) {
var centroid = path.centroid(d);
x = centroid[0];
y = centroid[1];
k = 4;
centered = d;
} else {
x = width / 2;
y = height / 2;
k = 1;
centered = null;
}
g.selectAll("path")
.classed("active", centered && function(d) { return d === centered; });
g.transition()
.duration(750)
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")scale(" + k + ")translate(" + -x + "," + -y + ")")
.style("stroke-width", 1.5 / k + "px");
}
.background {
fill: #435863;
pointer-events: all;
}
path:hover {
fill-opacity: .9;
}
#states .active {
fill-opacity: .9;
}
#state-borders {
fill: none;
stroke: rgba(22, 160, 133, .1);
stroke-width: 1px;
}
/* Style for Custom Tooltip */
div.tooltip {
position: absolute;
height: 28px;
padding: 5px 10px;
font: 12px sans-serif;
background: white;
border: 0px;
border-radius: 5px;
pointer-events: none;
}
<script src="http://d3js.org/topojson.v1.min.js"></script>
<script src="http://d3js.org/d3.v3.min.js"></script>
The problem is that you are appending your circles to the svg directly, but your US state are being appended to a g element. Your zoom transition is only applied to the g, meaning the circles will be unaffected:
g.transition()
.duration(750)
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")scale(" + k + ")translate(" + -x + "," + -y + ")")
.style("stroke-width", 1.5 / k + "px");
You should be able to correct this by simply appending your circles to the same g element itself, rather than the svg:
d3.json("cities.json", function(data) {
g.selectAll("circle")
.data(data)
.enter()
...
I'm trying to plot some directed links with a label in a force graph using D3 using this code:
// Per-type markers, as they don't inherit styles.
var svg = d3.select("body").select("svg");
svg.append("svg:defs").selectAll("marker")
.data(["end"])
.enter().append("svg:marker")
.attr("id", "arrow")
.attr("viewBox", "0 -5 10 10")
.attr("refX", 15)
.attr("refY", -0.8)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5");
// add the links and the arrows
var path = svg.append("svg:g").selectAll("path")
.data(force.links())
.enter().append("svg:path")
.attr("class", "link")
.attr("marker-mid", "url(#arrow)");
var marker = vis.selectAll("marker")
.data(force.links());
marker.append("text")
.attr("text-anchor", "middle")
.attr("font-family", "Arial, Helvetica, sans-serif")
.attr("fill", "Black")
.style("font", "normal 12px Arial")
.attr("transform", function(d) {
return "translate(" +
((d.source.y + d.target.y)/2) + "," +
((d.source.x + d.target.x)/2) + ")";
})
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text(function(d){return d.type;});
It looks like a LOT of code for me given that the task is reasonably simple. But that's the only way I've managed to do it and even then, the text is not showing.
I find interesting that if i change.text(function(d){return d.type;});
for: .text(function(d){console.log(d.type);});
The info is logged into the console.
I would like to know why my text is not being presented and if there's a simpler way to do what I'm trying to do.
Thanks.
Instead of doing .data(force.links()); twice, create a g element for each group and place the text and path in that:
var pg = svg.append("svg:g").selectAll("path")
.data(force.links())
.enter().append("g");
var path = pg.append("path")
.attr("class", "link")
.attr("marker-end", "url(#end)");
var text = pg.append("text")
.text("text")
.style("fill", "black");
Then in your tick function update the position of the text:
function tick() {
path.attr("d", function(d) {
var dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y,
dr = Math.sqrt(dx * dx + dy * dy);
return "M" +
d.source.x + "," +
d.source.y + "A" +
dr + "," + dr + " 0 0,1 " +
d.target.x + "," +
d.target.y;
});
text.attr("transform", function(d) {
return "translate(" + ((d.source.x + d.target.x)/2) + "," + ((d.source.y + d.target.y)/2) + ")"; });
node
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")"; });
}
Since you seem to be working off of this example, here's a modification of it:
<!DOCTYPE html>
<meta charset="utf-8">
<script src="http://d3js.org/d3.v3.js"></script>
<style>
path.link {
fill: none;
stroke: #666;
stroke-width: 1.5px;
}
circle {
fill: #ccc;
stroke: #fff;
stroke-width: 1.5px;
}
text {
fill: #000;
font: 10px sans-serif;
pointer-events: none;
}
</style>
<body>
<script>
// get the data
//d3.csv("force.csv", function(error, links) {
var links = [{"source":"Harry","target":"Sally","value":"1.2"},{"source":"Harry","target":"Mario","value":"1.3"},{"source":"Sarah","target":"Alice","value":"0.2"},{"source":"Eveie","target":"Alice","value":"0.5"},{"source":"Peter","target":"Alice","value":"1.6"},{"source":"Mario","target":"Alice","value":"0.4"},{"source":"James","target":"Alice","value":"0.6"},{"source":"Harry","target":"Carol","value":"0.7"},{"source":"Harry","target":"Nicky","value":"0.8"},{"source":"Bobby","target":"Frank","value":"0.8"},{"source":"Alice","target":"Mario","value":"0.7"},{"source":"Harry","target":"Lynne","value":"0.5"},{"source":"Sarah","target":"James","value":"1.9"},{"source":"Roger","target":"James","value":"1.1"},{"source":"Maddy","target":"James","value":"0.3"},{"source":"Sonny","target":"Roger","value":"0.5"},{"source":"James","target":"Roger","value":"1.5"},{"source":"Alice","target":"Peter","value":"1.1"},{"source":"Johan","target":"Peter","value":"1.6"},{"source":"Alice","target":"Eveie","value":"0.5"},{"source":"Harry","target":"Eveie","value":"0.1"},{"source":"Eveie","target":"Harry","value":"2.0"},{"source":"Henry","target":"Mikey","value":"0.4"},{"source":"Elric","target":"Mikey","value":"0.6"},{"source":"James","target":"Sarah","value":"1.5"},{"source":"Alice","target":"Sarah","value":"0.6"},{"source":"James","target":"Maddy","value":"0.5"},{"source":"Peter","target":"Johan","value":"0.7"}];
var nodes = {};
// Compute the distinct nodes from the links.
links.forEach(function(link) {
link.source = nodes[link.source] ||
(nodes[link.source] = {name: link.source});
link.target = nodes[link.target] ||
(nodes[link.target] = {name: link.target});
link.value = +link.value;
});
var width = 960,
height = 500;
var force = d3.layout.force()
.nodes(d3.values(nodes))
.links(links)
.size([width, height])
.linkDistance(60)
.charge(-300)
.on("tick", tick)
.start();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
// build the arrow.
svg.append("svg:defs").selectAll("marker")
.data(["end"]) // Different link/path types can be defined here
.enter().append("svg:marker") // This section adds in the arrows
.attr("id", String)
.attr("viewBox", "0 -5 10 10")
.attr("refX", 15)
.attr("refY", -1.5)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5");
// add the links and the arrows
var pg = svg.append("svg:g").selectAll("path")
.data(force.links())
.enter().append("g");
var path = pg.append("path")
.attr("class", "link")
.attr("marker-end", "url(#end)");
var text = pg.append("text")
.text("My Awesome Text!!")
.style("fill", "black");
// define the nodes
var node = svg.selectAll(".node")
.data(force.nodes())
.enter().append("g")
.attr("class", "node")
.call(force.drag);
// add the nodes
node.append("circle")
.attr("r", 5);
// add the text
node.append("text")
.attr("x", 12)
.attr("dy", ".35em")
.text(function(d) { return d.name; });
// add the curvy lines
function tick() {
path.attr("d", function(d) {
var dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y,
dr = Math.sqrt(dx * dx + dy * dy);
return "M" +
d.source.x + "," +
d.source.y + "A" +
dr + "," + dr + " 0 0,1 " +
d.target.x + "," +
d.target.y;
});
text.attr("transform", function(d) {
return "translate(" + ((d.source.x + d.target.x)/2) + "," + ((d.source.y + d.target.y)/2) + ")"; });
node
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")"; });
}
//});
</script>
</body>
</html>
I am trying to use the D3 javascript library to make a force-directed graph, and I want to offset text appended to my nodes. However, no attributes that I seem to try and change have an affect on this. Instead, my text gets overlayed on the node and it's very hard to read:
Here is how I am appending the text:
node.append("text")
.attr("dy", -3)
.attr("text-anchor", "right")
.style("color", "white", null)
.text(function(d) { return d.name; });
I have tried different values of dx and dy but nothing seems to offset the text. It always looks the same.
Edit: Full code (http://g.nychis.com/force-directed.zip and open force-edges-names.html)
<!DOCTYPE html>
<html>
<head>
<title>Force-Directed Layout</title>
<script type="text/javascript" src="d3.v2.js"></script>
<style type="text/css">
div.node {
border-radius: 12px;
width: 24px;
height: 24px;
margin: -6px 0 0 -6px;
position: absolute;
}
div.link {
position: absolute;
border-bottom: solid #999 1px;
height: 0;
-webkit-transform-origin: 0 0;
-moz-transform-origin: 0 0;
-ms-transform-origin: 0 0;
-o-transform-origin: 0 0;
transform-origin: 0 0;
}
</style>
</head>
<body bgcolor="#000000">
<FONT COLOR="#FFFFFF">Floor: 1</FONT>
<script type="text/javascript">
var width = 700,
height = 500,
radius = 6,
fill = d3.scale.category20();
var force = d3.layout.force()
.gravity(0)
.charge(-100)
.linkDistance(function(d) { return (d.value-d.sub)*8; })
.size([width, height]);
var vis = d3.select("body").append("div")
.style("width", width + "px")
.style("height", height + "px");
d3.json("home.json", function(json) {
var link = vis.selectAll("div.link")
.data(json.links)
.enter().append("div")
.attr("class", "link");
var node = vis.selectAll("div.node")
.data(json.nodes)
.enter().append("div")
.attr("class", "node")
.style("background", function(d) { return fill(d.group); })
.style("border-color", function(d) { return d3.rgb(fill(d.group)).darker(); })
.call(force.drag);
node.append("text")
.style("color", "white", null)
.text(function(d) { return d.name; });
force
.nodes(json.nodes)
.links(json.links)
.on("tick", tick)
.start();
function tick() {
node.style("left", function(d) { return (d.x = Math.max(radius, Math.min(width - radius, d.x))) + "px"; })
.style("top", function(d) { return (d.y = Math.max(radius, Math.min(height - radius, d.y))) + "px"; });
link.style("left", function(d) { return d.source.x + "px"; })
.style("top", function(d) { return d.source.y + "px"; })
.style("width", length)
.style("-webkit-transform", transform)
.style("-moz-transform", transform)
.style("-ms-transform", transform)
.style("-o-transform", transform)
.style("transform", transform);
}
function transform(d) {
return "rotate(" + Math.atan2(d.target.y - d.source.y, d.target.x - d.source.x) * 180 / Math.PI + "deg)";
}
function length(d) {
var dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y;
return Math.sqrt(dx * dx + dy * dy) + "px";
}
});
</script>
</body>
</html>
You can translate the text element:
node.append("div")
.style("transform", "translate(0px,-10px)")
...
.html(...);
The name of the "transform" property depends on the browser you're using, see here. You might want to set all of these.
node.append("text")
.attr({
"dy": -3,
"transform": "translate(" + 15 + "," + 5 + ")"
})
.style("color", "white", null)
.text(function(d) { return d.name; });