I'm trying to create a force-directed graph in d3.js with arrows pointing to non-uniform sized nodes. I came across this example here but for the life of me I can't adapt my code to add the arrows in. I'm not sure if I need to be adding something extra to the tick function, or if I'm referencing something incorrectly. Can anyone help me out?
Codepen - https://codepen.io/quirkules/pen/dqXRwj
var links_data = [{"source":"ABS","target":"ABS","count":8},{"source":"ABS","target":"ATS","count":1},{"source":"ABS","target":"CR","count":8},{"source":"ABS","target":"ENV","count":1},{"source":"ABS","target":"INT","count":16},{"source":"ABS","target":"ITS","count":9},{"source":"ABS","target":"PDG","count":1},{"source":"ABS","target":"PER","count":4},{"source":"ABS","target":"PRAC","count":3},{"source":"AC","target":"AC","count":1},{"source":"AC","target":"INT","count":9},{"source":"AC","target":"ITS","count":1},{"source":"ACDC","target":"ACDC","count":1},{"source":"ACDC","target":"CR","count":2},{"source":"ACDC","target":"ITS","count":13},{"source":"ACDC","target":"PER","count":4},{"source":"APL","target":"APL","count":8},{"source":"APL","target":"CR","count":3},{"source":"APL","target":"ENV","count":1},{"source":"APL","target":"INT","count":1},{"source":"APL","target":"ITS","count":29},{"source":"APL","target":"LA","count":1},{"source":"APL","target":"PEG","count":1},{"source":"APL","target":"PER","count":3},{"source":"AST","target":"AST","count":17},{"source":"AST","target":"COP","count":1},{"source":"AST","target":"DBT","count":2},{"source":"AST","target":"DEVOPS","count":1},{"source":"AST","target":"IGN","count":1},{"source":"AST","target":"INT","count":2},{"source":"AST","target":"ITS","count":32},{"source":"AST","target":"PDG","count":2},{"source":"AST","target":"PER","count":8},{"source":"ATS","target":"ABS","count":1},{"source":"ATS","target":"ATS","count":21},{"source":"ATS","target":"DBT","count":1},{"source":"ATS","target":"INT","count":3},{"source":"ATS","target":"PDG","count":1},{"source":"ATS","target":"PEG","count":1},{"source":"CAR","target":"APL","count":1},{"source":"CAR","target":"CAR","count":9},{"source":"CAR","target":"COP","count":1},{"source":"CAR","target":"INT","count":9},{"source":"CAR","target":"ITS","count":8},{"source":"IGN","target":"CR","count":4},{"source":"IGN","target":"IGN","count":13},{"source":"IGN","target":"INT","count":5},{"source":"IGN","target":"ITS","count":13},{"source":"IGN","target":"PER","count":4},{"source":"IGN","target":"PRAC","count":1},{"source":"LA","target":"AC","count":1},{"source":"LA","target":"INT","count":1},{"source":"LA","target":"ITS","count":37},{"source":"LA","target":"LA","count":18},{"source":"LA","target":"PER","count":2},{"source":"LOT","target":"LOT","count":18},{"source":"PDG","target":"ABS","count":1},{"source":"PDG","target":"AST","count":4},{"source":"PDG","target":"ATS","count":1},{"source":"PDG","target":"CAR","count":1},{"source":"PDG","target":"CR","count":8},{"source":"PDG","target":"ICS","count":1},{"source":"PDG","target":"IGN","count":3},{"source":"PDG","target":"INT","count":18},{"source":"PDG","target":"ITS","count":6},{"source":"PDG","target":"NRB","count":4},{"source":"PDG","target":"ONT","count":1},{"source":"PDG","target":"PDG","count":24},{"source":"PDG","target":"PER","count":1},{"source":"PEG","target":"CAR","count":1},{"source":"PEG","target":"ENV","count":1},{"source":"PEG","target":"INFRA","count":1},{"source":"PEG","target":"ITS","count":22},{"source":"PEG","target":"LA","count":1},{"source":"PEG","target":"PEG","count":51},{"source":"PEG","target":"PER","count":6},{"source":"RPT","target":"ABS","count":1},{"source":"RPT","target":"APL","count":1},{"source":"RPT","target":"IGN","count":1},{"source":"RPT","target":"INT","count":9},{"source":"RPT","target":"ITS","count":2},{"source":"RPT","target":"RPT","count":11},{"source":"RPT","target":"RTR","count":1},{"source":"RWWA","target":"INT","count":1},{"source":"RWWA","target":"ITS","count":1},{"source":"RWWA","target":"PER","count":1},{"source":"RWWA","target":"RWWA","count":1},{"source":"SCOR","target":"SCOR","count":5},{"source":"SPK","target":"INT","count":4},{"source":"SPK","target":"ITS","count":4},{"source":"SPK","target":"SPK","count":21},{"source":"TS","target":"CS","count":1},{"source":"TS","target":"TS","count":10}];
var nodes_data = [{"name":"ABS","total":11},{"name":"ATS","total":23},{"name":"CR","total":25},{"name":"ENV","total":3},{"name":"INT","total":78},{"name":"ITS","total":177},{"name":"PDG","total":28},{"name":"PER","total":33},{"name":"PRAC","total":4},{"name":"AC","total":2},{"name":"ACDC","total":1},{"name":"APL","total":10},{"name":"LA","total":20},{"name":"PEG","total":53},{"name":"AST","total":21},{"name":"COP","total":2},{"name":"DBT","total":3},{"name":"DEVOPS","total":1},{"name":"IGN","total":18},{"name":"CAR","total":11},{"name":"LOT","total":18},{"name":"ICS","total":1},{"name":"NRB","total":4},{"name":"ONT","total":1},{"name":"INFRA","total":1},{"name":"RPT","total":11},{"name":"RTR","total":1},{"name":"RWWA","total":1},{"name":"SCOR","total":5},{"name":"SPK","total":21},{"name":"CS","total":1},{"name":"TS","total":10}];
//create node size scale
var nodeSizeScale = d3.scaleLinear()
.domain(d3.extent(nodes_data, d => d.total))
.range([30, 70]);
//create node size scale
var linkSizeScale = d3.scaleLinear()
.domain(d3.extent(links_data, d => d.count))
.range([5, 30]);
//create node size scale
var linkColourScale = d3.scaleLinear()
.domain(d3.extent(links_data, d => d.count))
.range(['blue', 'red']);
//document.getElementsByTagName('body')[0].innerHTML = '<div>' + JSON.stringify(nodes_data) + '</div>';
//create somewhere to put the force directed graph
var height = 650,
width = 950;
var svg = d3.select("body").append("svg")
.attr('width',width)
.attr('height',height);
var radius = 15;
//set up the simulation and add forces
var simulation = d3.forceSimulation()
.nodes(nodes_data);
var link_force = d3.forceLink(links_data)
.id(function(d) { return d.name; })
;
var charge_force = d3.forceManyBody()
.strength(-1000);
var center_force = d3.forceCenter(width / 2, height / 2);
simulation
.force("charge_force", charge_force)
.force("center_force", center_force)
.force("link",link_force)
;
//add tick instructions:
simulation.on("tick", tickActions );
// THIS CODE SECTION ISN'T RENDERING
// Per-type markers, as they don't inherit styles.
svg.append("defs").selectAll("marker")
.data(["dominating"])
.enter().append("marker")
.attr("id", function (d) {
return d;
})
.attr("viewBox", "0 -5 10 10")
.attr("refX", 15)
.attr("refY", -1.5)
.attr("markerWidth", 12)
.attr("markerHeight", 12)
.attr("orient", "auto")
.append("path")
.attr("d", "M0,-5L10,0L0,5");
//add encompassing group for the zoom
var g = svg.append("g")
.attr("class", "everything");
// add the curved links to our graphic
var link = g.selectAll(".link")
.data(links_data)
.enter()
.append("path")
.attr("class", "link")
.style('stroke', d => {return linkColourScale(d.count);})
.attr('stroke-opacity', 0.5)
.attr('stroke-width', d => {return linkSizeScale(d.count);});
//draw circles for the nodes
var node = g.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(nodes_data)
.enter()
.append("circle")
.attr("r", d => {return nodeSizeScale(d.total);})
.attr("fill", "#333")
.on("mouseover", mouseOver(.1))
.on("mouseout", mouseOut);
//add text labels
var text = g.append("g")
.attr("class", "labels")
.selectAll("text")
.data(nodes_data)
.enter().append("text")
.style("text-anchor","middle")
.style("font-weight", "bold")
.style("pointer-events", "none")
.attr("dy", ".35em")
.text(function(d) { return d.name });
//add drag capabilities
var drag_handler = d3.drag()
.on("start", drag_start)
.on("drag", drag_drag)
.on("end", drag_end);
drag_handler(node);
//add zoom capabilities
var zoom_handler = d3.zoom()
.on("zoom", zoom_actions);
zoom_handler(svg);
/** Functions **/
//Drag functions
//d is the node
function drag_start(d) {
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
//make sure you can't drag the circle outside the box
function drag_drag(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
function drag_end(d) {
if (!d3.event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
//Zoom functions
function zoom_actions(){
g.attr("transform", d3.event.transform)
}
function tickActions() {
//update circle positions each tick of the simulation
node
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
//update link positions
link.attr("d", positionLink);
text.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; });
}
// links are drawn as curved paths between nodes,
// through the intermediate nodes
function positionLink(d) {
var offset = 30;
var midpoint_x = (d.source.x + d.target.x) / 2;
var midpoint_y = (d.source.y + d.target.y) / 2;
var dx = (d.target.x - d.source.x);
var dy = (d.target.y - d.source.y);
var normalise = Math.sqrt((dx * dx) + (dy * dy));
var offSetX = midpoint_x + offset * (dy / normalise);
var offSetY = midpoint_y - offset * (dx / normalise);
return "M" + d.source.x + "," + d.source.y +
"S" + offSetX + "," + offSetY +
" " + d.target.x + "," + d.target.y;
}
// build a dictionary of nodes that are linked
var linkedByIndex = {};
links_data.forEach(function(d) {
linkedByIndex[d.source.index + "," + d.target.index] = 1;
});
// check the dictionary to see if nodes are linked
function isConnected(a, b) {
return linkedByIndex[a.index + "," + b.index] || linkedByIndex[b.index + "," + a.index] || a.index == b.index;
}
// fade nodes on hover
function mouseOver(opacity) {
return function(d) {
// check all other nodes to see if they're connected
// to this one. if so, keep the opacity at 1, otherwise
// fade
node.style("stroke-opacity", function(o) {
thisOpacity = isConnected(d, o) ? 1 : opacity;
return thisOpacity;
});
node.style("fill-opacity", function(o) {
thisOpacity = isConnected(d, o) ? 1 : opacity;
return thisOpacity;
});
text.style("fill-opacity", function(o) {
thisOpacity = isConnected(d, o) ? 1 : opacity;
return thisOpacity;
});
// also style link accordingly
link.style("stroke-opacity", function(o) {
return o.source === d || o.target === d ? 1 : opacity;
});
link.style("stroke", function(o) {
return o.source === d || o.target === d ? linkColourScale(o.count) : "#333";
});
};
}
function mouseOut() {
node.style("stroke-opacity", 1);
node.style("fill-opacity", 1);
text.style("fill-opacity", 1);
link.style("stroke-opacity", 0.5);
link.style("stroke", d => {return linkColourScale(d.count);});
}
I have applied the same code changes in the example you referred, to your code and it seems to work fine.
The only extra update required was, since you had nodes connecting to itself, I had to apply marker-end property and path length updates only to the filtered set of links.
var links_data = [{"source":"ABS","target":"ABS","count":8},{"source":"ABS","target":"ATS","count":1},{"source":"ABS","target":"CR","count":8},{"source":"ABS","target":"ENV","count":1},{"source":"ABS","target":"INT","count":16},{"source":"ABS","target":"ITS","count":9},{"source":"ABS","target":"PDG","count":1},{"source":"ABS","target":"PER","count":4},{"source":"ABS","target":"PRAC","count":3},{"source":"AC","target":"AC","count":1},{"source":"AC","target":"INT","count":9},{"source":"AC","target":"ITS","count":1},{"source":"ACDC","target":"ACDC","count":1},{"source":"ACDC","target":"CR","count":2},{"source":"ACDC","target":"ITS","count":13},{"source":"ACDC","target":"PER","count":4},{"source":"APL","target":"APL","count":8},{"source":"APL","target":"CR","count":3},{"source":"APL","target":"ENV","count":1},{"source":"APL","target":"INT","count":1},{"source":"APL","target":"ITS","count":29},{"source":"APL","target":"LA","count":1},{"source":"APL","target":"PEG","count":1},{"source":"APL","target":"PER","count":3},{"source":"AST","target":"AST","count":17},{"source":"AST","target":"COP","count":1},{"source":"AST","target":"DBT","count":2},{"source":"AST","target":"DEVOPS","count":1},{"source":"AST","target":"IGN","count":1},{"source":"AST","target":"INT","count":2},{"source":"AST","target":"ITS","count":32},{"source":"AST","target":"PDG","count":2},{"source":"AST","target":"PER","count":8},{"source":"ATS","target":"ABS","count":1},{"source":"ATS","target":"ATS","count":21},{"source":"ATS","target":"DBT","count":1},{"source":"ATS","target":"INT","count":3},{"source":"ATS","target":"PDG","count":1},{"source":"ATS","target":"PEG","count":1},{"source":"CAR","target":"APL","count":1},{"source":"CAR","target":"CAR","count":9},{"source":"CAR","target":"COP","count":1},{"source":"CAR","target":"INT","count":9},{"source":"CAR","target":"ITS","count":8},{"source":"IGN","target":"CR","count":4},{"source":"IGN","target":"IGN","count":13},{"source":"IGN","target":"INT","count":5},{"source":"IGN","target":"ITS","count":13},{"source":"IGN","target":"PER","count":4},{"source":"IGN","target":"PRAC","count":1},{"source":"LA","target":"AC","count":1},{"source":"LA","target":"INT","count":1},{"source":"LA","target":"ITS","count":37},{"source":"LA","target":"LA","count":18},{"source":"LA","target":"PER","count":2},{"source":"LOT","target":"LOT","count":18},{"source":"PDG","target":"ABS","count":1},{"source":"PDG","target":"AST","count":4},{"source":"PDG","target":"ATS","count":1},{"source":"PDG","target":"CAR","count":1},{"source":"PDG","target":"CR","count":8},{"source":"PDG","target":"ICS","count":1},{"source":"PDG","target":"IGN","count":3},{"source":"PDG","target":"INT","count":18},{"source":"PDG","target":"ITS","count":6},{"source":"PDG","target":"NRB","count":4},{"source":"PDG","target":"ONT","count":1},{"source":"PDG","target":"PDG","count":24},{"source":"PDG","target":"PER","count":1},{"source":"PEG","target":"CAR","count":1},{"source":"PEG","target":"ENV","count":1},{"source":"PEG","target":"INFRA","count":1},{"source":"PEG","target":"ITS","count":22},{"source":"PEG","target":"LA","count":1},{"source":"PEG","target":"PEG","count":51},{"source":"PEG","target":"PER","count":6},{"source":"RPT","target":"ABS","count":1},{"source":"RPT","target":"APL","count":1},{"source":"RPT","target":"IGN","count":1},{"source":"RPT","target":"INT","count":9},{"source":"RPT","target":"ITS","count":2},{"source":"RPT","target":"RPT","count":11},{"source":"RPT","target":"RTR","count":1},{"source":"RWWA","target":"INT","count":1},{"source":"RWWA","target":"ITS","count":1},{"source":"RWWA","target":"PER","count":1},{"source":"RWWA","target":"RWWA","count":1},{"source":"SCOR","target":"SCOR","count":5},{"source":"SPK","target":"INT","count":4},{"source":"SPK","target":"ITS","count":4},{"source":"SPK","target":"SPK","count":21},{"source":"TS","target":"CS","count":1},{"source":"TS","target":"TS","count":10}];
var nodes_data = [{"name":"ABS","total":11},{"name":"ATS","total":23},{"name":"CR","total":25},{"name":"ENV","total":3},{"name":"INT","total":78},{"name":"ITS","total":177},{"name":"PDG","total":28},{"name":"PER","total":33},{"name":"PRAC","total":4},{"name":"AC","total":2},{"name":"ACDC","total":1},{"name":"APL","total":10},{"name":"LA","total":20},{"name":"PEG","total":53},{"name":"AST","total":21},{"name":"COP","total":2},{"name":"DBT","total":3},{"name":"DEVOPS","total":1},{"name":"IGN","total":18},{"name":"CAR","total":11},{"name":"LOT","total":18},{"name":"ICS","total":1},{"name":"NRB","total":4},{"name":"ONT","total":1},{"name":"INFRA","total":1},{"name":"RPT","total":11},{"name":"RTR","total":1},{"name":"RWWA","total":1},{"name":"SCOR","total":5},{"name":"SPK","total":21},{"name":"CS","total":1},{"name":"TS","total":10}];
//create node size scale
var nodeSizeScale = d3.scaleLinear()
.domain(d3.extent(nodes_data, d => d.total))
.range([30, 70]);
//create node size scale
var linkSizeScale = d3.scaleLinear()
.domain(d3.extent(links_data, d => d.count))
.range([5, 30]);
//create node size scale
var linkColourScale = d3.scaleLinear()
.domain(d3.extent(links_data, d => d.count))
.range(['blue', 'red']);
//document.getElementsByTagName('body')[0].innerHTML = '<div>' + JSON.stringify(nodes_data) + '</div>';
//create somewhere to put the force directed graph
var height = 650,
width = 950;
var svg = d3.select("body").append("svg")
.attr('width',width)
.attr('height',height);
var radius = 15;
//set up the simulation and add forces
var simulation = d3.forceSimulation()
.nodes(nodes_data);
var link_force = d3.forceLink(links_data)
.id(function(d) { return d.name; })
;
var charge_force = d3.forceManyBody()
.strength(-1000);
var center_force = d3.forceCenter(width / 2, height / 2);
simulation
.force("charge_force", charge_force)
.force("center_force", center_force)
.force("link",link_force)
;
//add tick instructions:
simulation.on("tick", tickActions );
// THIS CODE SECTION ISN'T RENDERING
// Per-type markers, as they don't inherit styles.
svg.append("defs").selectAll("marker")
.data(["dominating"])
.enter().append("marker")
.attr('markerUnits', 'userSpaceOnUse')
.attr("id", function (d) {
return d;
})
.attr("viewBox", "0 -5 10 10")
.attr("refX", 0)
.attr("refY", 0)
.attr("markerWidth", 12)
.attr("markerHeight", 12)
.attr("orient", "auto-start-reverse")
.append("path")
.attr("d", "M0,-5L10,0L0,5")
.attr("fill", "red");
//add encompassing group for the zoom
var g = svg.append("g")
.attr("class", "everything");
// add the curved links to our graphic
var link = g.selectAll(".link")
.data(links_data)
.enter()
.append("path")
.attr("class", "link")
.style('stroke', d => {return linkColourScale(d.count);})
.attr('stroke-opacity', 0.5)
.attr('stroke-width', d => {return linkSizeScale(d.count);})
.attr("marker-end", function(d) {
if(JSON.stringify(d.target) !== JSON.stringify(d.source))
return "url(#dominating)";
});
//draw circles for the nodes
var node = g.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(nodes_data)
.enter()
.append("circle")
.attr("r", d => {return nodeSizeScale(d.total);})
.attr("fill", "#333")
.on("mouseover", mouseOver(.1))
.on("mouseout", mouseOut);
//add text labels
var text = g.append("g")
.attr("class", "labels")
.selectAll("text")
.data(nodes_data)
.enter().append("text")
.style("text-anchor","middle")
.style("font-weight", "bold")
.style("pointer-events", "none")
.attr("dy", ".35em")
.text(function(d) { return d.name });
//add drag capabilities
var drag_handler = d3.drag()
.on("start", drag_start)
.on("drag", drag_drag)
.on("end", drag_end);
drag_handler(node);
//add zoom capabilities
var zoom_handler = d3.zoom()
.on("zoom", zoom_actions);
zoom_handler(svg);
/** Functions **/
//Drag functions
//d is the node
function drag_start(d) {
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
//make sure you can't drag the circle outside the box
function drag_drag(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
function drag_end(d) {
if (!d3.event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
//Zoom functions
function zoom_actions(){
g.attr("transform", d3.event.transform)
}
function tickActions() {
//update circle positions each tick of the simulation
node
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
//update link positions
link.attr("d", positionLink1);
link.filter(function(d){ return JSON.stringify(d.target) !== JSON.stringify(d.source); })
.attr("d",positionLink2);
text.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; });
}
function positionLink1(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;
}
// recalculate and back off the distance
function positionLink2(d) {
// length of current path
var pl = this.getTotalLength(),
// radius of circle plus marker head
r = nodeSizeScale(d.target.total)+ 12, //12 is the "size" of the marker Math.sqrt(12**2 + 12 **2)
// position close to where path intercepts circle
m = this.getPointAtLength(pl - r);
var dx = m.x - d.source.x,
dy = m.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 " + m.x + "," + m.y;
}
// build a dictionary of nodes that are linked
var linkedByIndex = {};
links_data.forEach(function(d) {
linkedByIndex[d.source.index + "," + d.target.index] = 1;
});
// check the dictionary to see if nodes are linked
function isConnected(a, b) {
return linkedByIndex[a.index + "," + b.index] || linkedByIndex[b.index + "," + a.index] || a.index == b.index;
}
// fade nodes on hover
function mouseOver(opacity) {
return function(d) {
// check all other nodes to see if they're connected
// to this one. if so, keep the opacity at 1, otherwise
// fade
node.style("stroke-opacity", function(o) {
thisOpacity = isConnected(d, o) ? 1 : opacity;
return thisOpacity;
});
node.style("fill-opacity", function(o) {
thisOpacity = isConnected(d, o) ? 1 : opacity;
return thisOpacity;
});
text.style("fill-opacity", function(o) {
thisOpacity = isConnected(d, o) ? 1 : opacity;
return thisOpacity;
});
// also style link accordingly
link.style("stroke-opacity", function(o) {
return o.source === d || o.target === d ? 1 : opacity;
});
link.style("stroke", function(o) {
return o.source === d || o.target === d ? linkColourScale(o.count) : "#333";
});
};
}
function mouseOut() {
node.style("stroke-opacity", 1);
node.style("fill-opacity", 1);
text.style("fill-opacity", 1);
link.style("stroke-opacity", 0.5);
link.style("stroke", d => {return linkColourScale(d.count);});
}
body {
width:99%;
height:100%;
background: #111111;
}
.svg {
width:1000;
height:1000;
}
.link {
fill: none;
}
.labels {
font-family: Arial;
fill: white;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
i have the following simple graph to visualize:
sample graph
First, only 'n1' is shown connecting to its neighbor 'n2'
Second, after 'n2' is clicked, the network expands by adding two edges 'n2'->'n3' and 'n2'->'n4'
Third, after 'n3' is clicked, the network expands by adding two edges 'n3'->'n1' and 'n3'->'n5'.
To do so, i thought i could create an array of nodes that are visible. By clicking a node, this node name will be added into the array. And then in the tick() function the visible nodes in that array will be used to set up the network.
But when I do so with the following code:
// return currently visible nodes as the source nodes
// e.g. called by
// var visible_seeds = seeding(nodes);
//
function seeding(nodes){
var visible = [];
nodes.forEach(function(n){
if(n.name=='n1')
visible.push(n);
});
return visible;
}
The above function is then called as following:
var visible_seeds = seeding(nodes);
var force = d3.layout.force()
.nodes(d3.values(visible_seeds))
.links(links)
.size([width, height])
.linkDistance(60)
.charge(-300)
.on("tick", tick)
.start();
It gives the error
index.html TypeError:'undefined' is not a function (evaluating 'nodes.forEach(function(n){...')
What is the correct way to achieve my goal? Thank you out there!!!
p.s. I am absolutely new to D3 and javascript.
The data read in by D3 are from a csv file:
source,target,value
n1,n2,1.0
n2,n3,1.0
n2,n4,1.0
n3,n1,1.0
n3,n5,1.0
My code is based on the example at:
http://bl.ocks.org/d3noob/5155181
Added - source code - the issue happens in the function seeding()
<!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;
}
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("force.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, vis:0});
link.target = nodes[link.target] || (nodes[link.target] = {name: link.target, vis:0});
link.value = +link.value;
});
//nodes["n1"] = {name:"n1", vis:1};
var vis = ['n1'];
var width = 960,
height = 500;
function seeding(){
var visible = [];
nodes.forEach(function(n){
if(n.name=='n1')
visible.push(n);
});
return visible;
}
var visible_seeds = seeding();
var force = d3.layout.force()
.nodes(d3.values(visible_seeds))
//.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")
.on("click", click)
.on("dblclick", dblclick)
.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;
});
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("fill", "steelblue")
.style("stroke", "lightsteelblue")
.style("stroke-width", ".5px")
.style("font", "20px sans-serif");
d3.select(this).select("circle").transition()
.duration(750)
.attr("r", 16)
.style("fill", "lightsteelblue");
}
// action to take on mouse double click
function dblclick() {
d3.select(this).select("circle").transition()
.duration(750)
.attr("r", 6)
.style("fill", "#ccc");
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>
First, the source of your error is because nodes is a JavaScript object (not an array), it has no forEach method.
Second, I'm not sure where you are going with your approach. You start the force layout with one node, then what? On click you append the other nodes? How you going to handle the links?
Intead, I'd suggest just toggling the visibility of the nodes and links on click. Here's a quick implementation:
<!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;
}
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("force.csv", function(error, links) {
var links = [{"source":"n1","target":"n2","value":"1.0"},{"source":"n2","target":"n3","value":"1.0"},{"source":"n2","target":"n4","value":"1.0"},{"source":"n3","target":"n1","value":"1.0"},{"source":"n3","target":"n5","value":"1.0 "}];
var nodes = {};
// Compute the distinct nodes from the links.
links.forEach(function(link) {
link.source = nodes[link.source] || (nodes[link.source] = {name: link.source, vis:0});
link.target = nodes[link.target] || (nodes[link.target] = {name: link.target, vis:0});
link.value = +link.value;
});
//nodes["n1"] = {name:"n1", vis:1};
var vis = ['n1'];
var width = 300,
height = 300;
for (key in nodes){
var node = nodes[key];
node.visible = (node.name === 'n1');
}
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)")
.style("opacity", function(d){
return d.target.visible && d.source.visible ? 1 : 0;
})
// define the nodes
var node = svg.selectAll(".node")
.data(force.nodes())
.enter().append("g")
.attr("class", "node")
.style("opacity", function(d){
return d.visible ? 1 : 0;
})
.on("click", click)
.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;
});
node
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")"; });
}
// action to take on mouse click
function click(d0) {
links.forEach(function(d1){
console.log(d0, d1.source)
if (d1.source === d0) {
d1.target.visible = !d1.target.visible;
}
});
node.style("opacity", function(d){
return d.visible ? 1 : 0;
});
path.style("opacity", function(d){
return d.target.visible && d.source.visible ? 1 : 0;
});
}
//});
</script>
</body>
</html>
I'm trying to use this example http://bl.ocks.org/eesur/be2abfb3155a38be4de4 in my own project. I'm trying to generalize the code block to use. I changed the code a little but now it's not generating similar graph in example. You can see my attempt in jsfiddle https://jsfiddle.net/wykbbvzf/1/
var SuperHeroes = function(selector, w, h) {
this.w = w;
this.h = h;
d3.select(selector).selectAll("svg").remove();
this.svg = d3.select(selector).append("svg:svg")
.attr('width', w)
.attr('height', h);
this.svg.append("svg:rect")
.style("stroke", "#999")
.style("fill", "#fff")
.attr('width', w)
.attr('height', h);
this.force = d3.layout.force()
.charge(function(d) { return d._children ? -d.size / 100 : -40; })
.linkDistance(function(d) { return d.target._children ? 80 : 25; })
.size([h, w]);
};
// some colour variables
var tcBlack = "#130C0E";
// rest of vars
var maxNodeSize = 50,
x_browser = 20,
y_browser = 25;
/*
d3.json("marvel.json", function(json) {
// Build the path
var defs = this.svg.insert("svg:defs")
.data(["end"]);
defs.enter().append("svg:path")
.attr("d", "M0,-5L10,0L0,5");
this.update();
});
*/
/**
*
*/
SuperHeroes.prototype.update = function(json) {
this.root = json;
this.root.fixed = true;
this.root.x = w / 2;
this.root.y = h / 4;
var nodes = this.flatten(this.root),
links = d3.layout.tree().links(nodes);
// Restart the force layout.
this.force.nodes(nodes)
.links(links)
.gravity(0.05)
.charge(-1500)
.linkDistance(100)
.friction(0.5)
.linkStrength(function(l, i) {return 1; })
.size([w, h])
.on("tick", tick)
.start();
var path = this.svg.selectAll("path.link")
.data(links, function(d) { return d.target.id; });
path.enter().insert("svg:path")
.attr("class", "link")
// .attr("marker-end", "url(#end)")
.style("stroke", "#eee");
// Exit any old paths.
path.exit().remove();
// Update the nodes…
var node = this.svg.selectAll("g.node")
.data(nodes, function(d) { return d.id; });
// Enter any new nodes.
var nodeEnter = node.enter().append("svg:g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
.on("click", this.click)
.call(this.force.drag);
// Append a circle
nodeEnter.append("svg:circle")
.attr("r", function(d) { return Math.sqrt(d.size) / 10 || 4.5; })
.style("fill", "#eee");
// Append images
var images = nodeEnter.append("svg:image")
.attr("xlink:href", function(d) { return d.img;})
.attr("x", function(d) { return -25;})
.attr("y", function(d) { return -25;})
.attr("height", 50)
.attr("width", 50);
// make the image grow a little on mouse over and add the text details on click
var setEvents = images
// Append hero text
.on( 'click', function (d) {
d3.select("h1").html(d.hero);
d3.select("h2").html(d.name);
d3.select("h3").html ("Take me to " + "<a href='" + d.link + "' >" + d.hero + " web page ⇢"+ "</a>" );
})
.on( 'mouseenter', function() {
// select element in current context
d3.select( this )
.transition()
.attr("x", function(d) { return -60;})
.attr("y", function(d) { return -60;})
.attr("height", 100)
.attr("width", 100);
})
// set back
.on( 'mouseleave', function() {
d3.select( this )
.transition()
.attr("x", function(d) { return -25;})
.attr("y", function(d) { return -25;})
.attr("height", 50)
.attr("width", 50);
});
// Append hero name on roll over next to the node as well
nodeEnter.append("text")
.attr("class", "nodetext")
.attr("x", x_browser)
.attr("y", y_browser +15)
.attr("fill", tcBlack)
.text(function(d) { return d.hero; });
// Exit any old nodes.
node.exit().remove();
// Re-select for update.
path = this.svg.selectAll("path.link");
node = this.svg.selectAll("g.node");
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", this.nodeTransform);
}
}
/**
* Gives the coordinates of the border for keeping the nodes inside a frame
* http://bl.ocks.org/mbostock/1129492
*/
SuperHeroes.prototype.nodeTransform = function(d) {
d.x = Math.max(maxNodeSize, Math.min(w - (d.imgwidth/2 || 16), d.x));
d.y = Math.max(maxNodeSize, Math.min(h - (d.imgheight/2 || 16), d.y));
return "translate(" + d.x + "," + d.y + ")";
}
/**
* Toggle children on click.
*/
SuperHeroes.prototype.click = function(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
this.update();
}
/**
* Returns a list of all nodes under the root.
*/
SuperHeroes.prototype.flatten = function(root) {
var nodes = [];
var i = 0;
function recurse(node) {
if (node.children)
node.children.forEach(recurse);
if (!node.id)
node.id = ++i;
nodes.push(node);
}
recurse(root);
return nodes;
}
SuperHeroes.prototype.cleanup = function() {
this.update([]);
this.force.stop();
};
var currentSuperHereos;
var createSuperHeroes = function(json) {
// remove previous flower to save memory
if (currentSuperHereos) currentSuperHereos.cleanup();
// adapt layout size to the total number of elements
var total = 5;
w = parseInt(Math.sqrt(total) * 30, 10);
h = parseInt(Math.sqrt(total) * 30, 10);
if (h < 300) h = 300;
if (w < 300) w = 300;
// create a new SuperHeroes
currentSuperHereos = new SuperHeroes("#visualization", w, h).update(json);
var defs = this.svg.insert("svg:defs")
.data(["end"]);
defs.enter().append("svg:path")
.attr("d", "M0,-5L10,0L0,5");
};
createSuperHeroes(JSON.parse('{"name":"MAlkara","img":"https://upload.wikimedia.org/wikipedia/commons/6/60/Google-Fit-Icon.png","children":[{"hero":"Kesan","name":"Keşan","img":"https://upload.wikimedia.org/wikipedia/commons/6/60/Google-Fit-Icon.png","additionalProperties":{}}],"additionalProperties":{}}'));
Do you have any idea about my mistakes?
You will just need to update node transform attribute inside tick function. this.nodeTransform is not seems to be defined in your code. So the tick function should be as shown below.
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 + ")"; });
}
EDIT: Adding link labels
path.enter().insert("svg:path")
.attr("class", "link")
.style("stroke", "#eee")
.attr("id",function(d,i){ return "linkId_"+i; });
path.enter().append("g").attr("class", "linklabelholder")
.append("text")
.attr("class", "linklabel")
.style("font-size", "13px")
.attr("text-anchor", "middle")
.style("fill","#000")
.append("textPath")
.style('text-anchor', 'middle')
.attr('startOffset', '50%')
.attr("xlink:href",function(d,i) { return "#linkId_" + i;})
.text(function(d) {
return "my text"; //Can be dynamic via d object
});
var SuperHeroes = function(selector, w, h) {
this.w = w;
this.h = h;
d3.select(selector).selectAll("svg").remove();
this.svg = d3.select(selector).append("svg:svg")
.attr('width', w)
.attr('height', h);
var defs = this.svg.insert("svg:defs")
.data(["end"]);
defs.enter().append("svg:path")
.attr("d", "M0,-5L10,0L0,5");
this.svg.append("svg:rect")
.style("stroke", "#999")
.style("fill", "#fff")
.attr('width', w)
.attr('height', h);
this.force = d3.layout.force()
.charge(function(d) {
return d._children ? -d.size / 100 : -40;
})
.linkDistance(function(d) {
return d.target._children ? 80 : 25;
})
.size([h, w]);
};
// some colour variables
var tcBlack = "#130C0E";
// rest of vars
var maxNodeSize = 50,
x_browser = 20,
y_browser = 25;
/*
d3.json("marvel.json", function(json) {
// Build the path
var defs = this.svg.insert("svg:defs")
.data(["end"]);
defs.enter().append("svg:path")
.attr("d", "M0,-5L10,0L0,5");
this.update();
});
*/
/**
*
*/
SuperHeroes.prototype.update = function(json) {
this.root = json;
this.root.fixed = true;
this.root.x = w / 2;
this.root.y = h / 4;
var nodes = this.flatten(this.root),
links = d3.layout.tree().links(nodes);
// Restart the force layout.
this.force.nodes(nodes)
.links(links)
.gravity(0.05)
.charge(-1500)
.linkDistance(100)
.friction(0.5)
.linkStrength(function(l, i) {
return 1;
})
.size([w, h])
.on("tick", tick)
.start();
var path = this.svg.selectAll("path.link")
.data(links, function(d) {
return d.target.id;
});
path.enter().insert("svg:path")
.attr("class", "link")
.style("stroke", "#eee")
.attr("id",function(d,i){ return "linkId_"+i; });
path.enter().append("g").attr("class", "linklabelholder")
.append("text")
.attr("class", "linklabel")
.style("font-size", "13px")
.attr("text-anchor", "middle")
.style("fill","#000")
.append("textPath")
.style('text-anchor', 'middle')
.attr('startOffset', '50%')
.attr("xlink:href",function(d,i) { return "#linkId_" + i;})
.text(function(d) {
return "my text"; //Can be dynamic via d object
});
// Exit any old paths.
path.exit().remove();
// Update the nodes…
var node = this.svg.selectAll("g.node")
.data(nodes, function(d) {
return d.id;
});
// Enter any new nodes.
var nodeEnter = node.enter().append("svg:g")
.attr("class", "node")
.on("click", this.click)
.call(this.force.drag);
// Append a circle
nodeEnter.append("svg:circle")
.attr("r", function(d) {
return Math.sqrt(d.size) / 10 || 4.5;
})
.style("fill", "#eee");
// Append images
var images = nodeEnter.append("svg:image")
.attr("xlink:href", function(d) {
return d.img;
})
.attr("x", function(d) {
return -25;
})
.attr("y", function(d) {
return -25;
})
.attr("height", 50)
.attr("width", 50);
// make the image grow a little on mouse over and add the text details on click
var setEvents = images
// Append hero text
.on('click', function(d) {
d3.select("h1").html(d.hero);
d3.select("h2").html(d.name);
d3.select("h3").html("Take me to " + "<a href='" + d.link + "' >" + d.hero + " web page ⇢" + "</a>");
})
.on('mouseenter', function() {
// select element in current context
d3.select(this)
.transition()
.attr("x", function(d) {
return -60;
})
.attr("y", function(d) {
return -60;
})
.attr("height", 100)
.attr("width", 100);
})
// set back
.on('mouseleave', function() {
d3.select(this)
.transition()
.attr("x", function(d) {
return -25;
})
.attr("y", function(d) {
return -25;
})
.attr("height", 50)
.attr("width", 50);
});
// Append hero name on roll over next to the node as well
nodeEnter.append("text")
.attr("class", "nodetext")
.attr("x", x_browser)
.attr("y", y_browser + 15)
.attr("fill", tcBlack)
.text(function(d) {
return d.hero;
});
// Exit any old nodes.
node.exit().remove();
// Re-select for update.
path = this.svg.selectAll("path.link");
node = this.svg.selectAll("g.node");
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 + ")";
});
}
}
/**
* Gives the coordinates of the border for keeping the nodes inside a frame
* http://bl.ocks.org/mbostock/1129492
*/
SuperHeroes.prototype.nodeTransform = function(d) {
d.x = Math.max(maxNodeSize, Math.min(w - (d.imgwidth / 2 || 16), d.x));
d.y = Math.max(maxNodeSize, Math.min(h - (d.imgheight / 2 || 16), d.y));
return "translate(" + d.x + "," + d.y + ")";
}
/**
* Toggle children on click.
*/
SuperHeroes.prototype.click = function(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
}
/**
* Returns a list of all nodes under the root.
*/
SuperHeroes.prototype.flatten = function(root) {
var nodes = [];
var i = 0;
function recurse(node) {
if (node.children)
node.children.forEach(recurse);
if (!node.id)
node.id = ++i;
nodes.push(node);
}
recurse(root);
return nodes;
}
SuperHeroes.prototype.cleanup = function() {
this.update([]);
this.force.stop();
};
var currentSuperHereos;
var createSuperHeroes = function(json) {
// remove previous flower to save memory
if (currentSuperHereos) currentSuperHereos.cleanup();
// adapt layout size to the total number of elements
var total = 5;
w = parseInt(Math.sqrt(total) * 30, 10);
h = parseInt(Math.sqrt(total) * 30, 10);
if (h < 300) h = 300;
if (w < 300) w = 300;
// create a new SuperHeroes
currentSuperHereos = new SuperHeroes("#visualization", w, h).update(json);
};
createSuperHeroes(JSON.parse('{"name":"MAlkara","img":"https://upload.wikimedia.org/wikipedia/commons/6/60/Google-Fit-Icon.png","children":[{"hero":"Kesan","name":"Keşan","img":"https://upload.wikimedia.org/wikipedia/commons/6/60/Google-Fit-Icon.png","additionalProperties":{}}],"additionalProperties":{}}'));
body {
font-family: "Source Code Pro", Consolas, monaco, monospace;
line-height: 160%;
font-size: 16px;
margin: 0;
}
path.link {
fill: none;
stroke-width: 2px;
}
.node:not(:hover) .nodetext {
display: none;
}
h1 {
font-size: 36px;
margin: 10px 0;
text-transform: uppercase;
font-weight: normal;
}
h2,
h3 {
font-size: 18px;
margin: 5px 0;
font-weight: normal;
}
header {
padding: 20px;
position: absolute;
top: 0;
left: 0;
}
a:link {
color: #EE3124;
text-decoration: none;
}
a:visited {
color: #EE3124;
}
a:hover {
color: #A4CD39;
text-decoration: underline;
}
a:active {
color: #EE3124;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="visualization"></div>