I've a problem with my directed graph in d3js,in a few words:
if number of nodes < 24 the graph is rendered correctly
if number of nodes >= 24 everything was stacked on top left of svg but if I inspect my html code I see all nodes, links and labels...
this is my code:
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
var simulation = d3.forceSimulation()
.force("x", d3.forceX().strength(8).x( function(d){ return yScale(d.type) }))
.force("y", d3.forceY().strength(10).y(height/2))
.force("center", d3.forceCenter().x(width / 2).y(height / 2)) // Attraction to the center of the svg area
.force("charge", d3.forceManyBody().strength(1)) // Nodes are attracted one each other of value is > 0
.force("collide", d3.forceCollide().strength(0.9).radius(50).iterations(10)) // Force that avoids circle overlapping
.force("link", d3.forceLink().distance(function(d){return d.link_distance}).strength(1))
var yScale = d3.scalePoint()
.domain([1, 2, 3])
.range([150, width-150])
.padding(0.6)
.round(false);
d3.json("{{=URL('professionista', 'get_current_plan_graph', extension=False)}}", function(error, graph) {
if (error) throw error;
var links = graph.links;
function getNeighbors(node) {
return links.reduce(function(neighbors, link) {
if (link.target.id === node.id) {
neighbors.push(link.source.id)
} else if (link.source.id === node.id) {
neighbors.push(link.target.id)
}
return neighbors
}, [node.id])
}
function isNeighborLink(node, link) {
return link.target.id === node.id || link.source.id === node.id
}
function getNodeColor(node, neighbors) {
if (Array.isArray(neighbors) && neighbors.indexOf(node.id) > -1) {
if (node.type === 1)
return "#3C99FB"
else if (node.type === 2)
return "#9EC2E2"
else if (node.type === 3)
return "#577EA7"
else
return '#dee2e6'
}
return '#dee2e6'
}
function getLinkColor(node, link) {
return isNeighborLink(node, link) ? '#000444' : '#E5E5E5'
}
function getTextColor(node, neighbors) {
return Array.isArray(neighbors) && neighbors.indexOf(node.id) > -1 ? 'green' : 'black'
}
function selectNode(selectedNode) {
var neighbors = getNeighbors(selectedNode)
nodeElement.attr('fill', function(node) {
return getNodeColor(node, neighbors)
})
labelElement.attr('fill', function(node) {
return getTextColor(node, neighbors)
})
linkElement.attr('stroke', function(link) {
return getLinkColor(selectedNode, link)
})
}
var linkElement = svg.append("g")
.attr("class", "links")
.selectAll("line")
.data(graph.links)
.enter().append("line")
.attr("stroke-width", 1)
.attr("stroke", "rgba(50, 50, 50, 0.2)");
var nodeElement = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(graph.nodes)
.enter()
.append("circle")
.attr("r", 30)
.attr("fill", getNodeColor)
.on('click', selectNode)
var labelElement = svg.append("g")
.attr("class", "labels")
.selectAll("text")
.data(graph.nodes)
.enter().append("text")
.text(function(d) {
return d.codice;
})
simulation
.nodes(graph.nodes)
.on("tick", ticked);
simulation.force("link")
.links(graph.links);
function ticked() {
linkElement
.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; });
nodeElement
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
labelElement
.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; });
}
});
what's wrong with this code?
This happen also if I disable all links and labels, the nodes goes to the corner, please help me!!!
Related
I got several nodes and links in place. Unfortunately those are "floating" out of the canvas. I am using D3.V4.js and found several guides how to solve the problem with D3.v3.js. Unfortuantely those doesn´t seem to work. Ideally a hidden or transparent frame would be arranged around the canvas area. I am new building D3 graphs, so I couldn´t figure it out yet.
Maybe you guys could help me to adjust the correct line in my code.
Thanks
var svg = d3.select("svg"),
width = window.innerWidth,
height = +svg.attr("height");
var color = d3.scaleOrdinal(d3.schemeCategory20);
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) { return d.id; }).distance(100))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width / 2, height / 2))
.force("attraceForce",d3.forceManyBody().strength(-2));
var opacity = 0.25;
d3.json("datav2.json", function(error, graph) {
if (error) throw error;
var link = svg.append("g")
.attr("class", "links")
.selectAll("line")
.data(graph.links)
.enter().append("line")
.style("stroke-width", 3)
.style("stroke-linecap", "round")
.attr("linkGroup",function(d) {return d.linkGroup; })
.attr("stroke-width", function(d) { return d.value; })
;
var node = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(graph.nodes)
.enter().append("circle")
.attr("r", 15)
.attr("fill", "#ffffff")
.style("stroke-width", 2)
.style("stroke", function(d) { return color(d.group); })
.attr("nodeGroup",function(d) {return d.nodeGroup; })
.on("click", function(d) {
// This is to toggle visibility - need to do it on the nodes and links
d3.selectAll("line:not([linkGroup='"+d.nodeGroup+"'])")
.style("opacity", function() {
currentDisplay = d3.select(this).style("opacity");
currentDisplay = currentDisplay == "1" ? "0.1" : "1";
return currentDisplay;
});
d3.selectAll("circle:not([nodeGroup='"+d.nodeGroup+"'])")
.style("opacity",function() {
currentDisplay = d3.select(this).style("opacity");
currentDisplay = currentDisplay == "1" ? "0.1" : "1";
return currentDisplay;
});
d3.selectAll("text:not([nodeGroup='"+d.nodeGroup+"'])")
.style("opacity",function() {
currentDisplay = d3.select(this).style("opacity");
currentDisplay = currentDisplay == "1" ? "0.1" : "1";
return currentDisplay;
});
})
.on("mouseover", function(d) {
d3.select(this).style("cursor", "crosshair");
})
.on("mouseout", function(d) {
d3.select(this).style("cursor", "default");
})
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
// This is the label for each node
var text = svg.append("g").selectAll("text")
.data(graph.nodes)
.enter().append("text")
.attr("dy",-25)
.text(function(d) { return d.name;})
.attr("text-anchor", "middle")
.attr("nodeGroup",function(d) {return d.nodeGroup;} ) ;
node.append("title")
.text(function(d) { return d.name; });
simulation
.nodes(graph.nodes)
.on("tick", ticked);
simulation.force("link")
.links(graph.links);
function neighboring(a, b) {
return graph.links.some(function(d) {
return (d.source.id === a.source.id && d.target.id === b.target.id)
|| (d.source.id === b.source.id && d.target.id === a.target.id);
});
}
function ticked() {
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; })
//.attr("cx2", function(d) { return d.x = Math.max(d.width, Math.min(width - d.width, d.x)); })
//.attr("cy2", function(d) { return d.y = Math.max(d.height, Math.min(height - heightDelta - d.height, d.y)); });
text
.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; });
}
});
function dragstarted(d) {
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
function dragended(d) {
if (!d3.event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
Ok I found the issue and added a radius var with a size which fits to the nodes and modified the following line:
node
.attr("cx", function(d) { return d.x = Math.max(radius, Math.min(width - radius, d.x)); })
.attr("cy", function(d) { return d.y = Math.max(radius, Math.min(height - radius, d.y)); })
I'm creating a d3js graph where there are multiple nodes connected to multiple users. So far I've been able to set up the graph with a center node and then group them together. I want these groups to collapse when there's a click on the center node, somewhat similar to this example here.
My sample code for the same is here:
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) { return d.id; }).strength(0.9))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width / 2, height / 2))
d3.json('demo.json', function(error, graph) {
if (error) throw error;
drawGraph("Artifacts");
function drawGraph(selectedValue) {
// create groups, links and nodes
groups = svg.append('g').attr('class', 'groups');
link = svg.append('g')
.attr('class', 'links')
.selectAll('line')
.data(graph.links)
.enter().append('line')
.attr('stroke-width', function (d) {
return Math.sqrt(d.value);
});
node = svg.append('g')
.attr('class', 'nodes')
.selectAll('circle')
.data(graph.nodes)
.enter().append('circle')
.attr('r', function (d) {
if (d.type == "agent") {
return 10
}
return 5
})
.attr('fill', function (d) {
if (selectedValue === "Artifacts") {
if (d.type == "Process") {
return "white"
} else if (d.type == "user") {
return "blue"
} else if (d.type == "File") {
return "green"
} else if (d.type == "File") {
return "green"
} else if (d.type == "agent") {
return color(d.group);
}
} else {
console.log(d.threatscore);
if(d.threatscore>=0&&d.threatscore<3){
if(d.type=="agent"){
return color(d.group);
}else {
return "green"
}
}
else if(d.threatscore>=3&&d.threatscore<6){
return "yellow"
}
if(d.threatscore>=6&&d.threatscore<9){
return "red"
}
}
})
// .attr('fill', function(d) { return color(d.group); })
.call(d3.drag()
.on('start', dragstarted)
.on('drag', dragged)
.on('end', dragended));
var tip;
svg.on("click", function () {
if (tip) tip.remove();
});
node.on("click", function (d) {
d3.event.stopPropagation();
if (tip) tip.remove();
tip = svg.append("g")
.attr("transform", "translate(" + d.x + "," + d.y + ")");
var rect = tip.append("rect")
.style("fill", "white")
.style("stroke", "steelblue");
tip.append("text")
.text("Name: " + d.name)
.attr("dy", "1em")
.attr("x", 5);
tip.append("text")
.text("Type: " + d.type)
.attr("dy", "2em")
.attr("x", 5);
var con = graph.links
.filter(function (d1) {
return d1.source.id === d.id;
})
.map(function (d1) {
return d1.target.name;
})
tip.append("text")
.text("Connected to: " + con.join(","))
.attr("dy", "3em")
.attr("x", 5);
tip.append("text")
.text("Threat Score: " + d.threatscore)
.attr("dy", "4em")
.attr("x", 5);
tip.append("text")
.text("Labels: " + d.labels)
.attr("dy", "5em")
.attr("x", 5);
tip.append("text")
.text("Artifact ID: " + d.artifactid)
.attr("dy", "6em")
.attr("x", 5);
tip.append("text")
.html("More Information : <a href='dashboard#ajax/host_details.html?'" + d.artifactid + "'> " + d.artifactid + "</a>")
.attr("dy", "8em")
.attr("x", 5);
var bbox = tip.node().getBBox();
rect.attr("width", bbox.width + 5)
.attr("height", bbox.height + 5)
});
// count members of each group. Groups with less
// than 3 member will not be considered (creating
// a convex hull need 3 points at least)
groupIds = d3.set(graph.nodes.map(function (n) {
return +n.group;
}))
.values()
.map(function (groupId) {
return {
groupId: groupId,
count: graph.nodes.filter(function (n) {
return +n.group == groupId;
}).length
};
})
.filter(function (group) {
return group.count > 2;
})
.map(function (group) {
return group.groupId;
});
paths = groups.selectAll('.path_placeholder')
.data(groupIds, function (d) {
return +d;
})
.enter()
.append('g')
.attr('class', 'path_placeholder')
.append('path')
.attr('stroke', function (d) {
return color(d);
})
.attr('fill', function (d) {
return color(d);
})
.attr('opacity', 0);
paths
.transition()
.duration(2000)
.attr('opacity', 1);
// add interaction to the groups
groups.selectAll('.path_placeholder')
.call(d3.drag()
.on('start', group_dragstarted)
.on('drag', group_dragged)
.on('end', group_dragended)
);
node.append('title')
.text(function (d) {
return d.type + " - " + d.name;
});
simulation
.nodes(graph.nodes)
.on('tick', ticked)
.force('link')
// .force("link", d3.forceLink().distance(function(d) {return d.distance;}).strength(0.1))
.links(graph.links);
function ticked() {
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;
});
updateGroups();
}
}
});
// select nodes of the group, retrieve its positions
// and return the convex hull of the specified points
// (3 points as minimum, otherwise returns null)
var polygonGenerator = function(groupId) {
var node_coords = node
.filter(function(d) { return d.group == groupId; })
.data()
.map(function(d) { return [d.x, d.y]; });
console.log("Came here",node_coords)
return d3.polygonHull(node_coords);
};
function updateGroups() {
groupIds.forEach(function(groupId) {
var path = paths.filter(function(d) { return d == groupId;})
.attr('transform', 'scale(1) translate(0,0)')
.attr('d', function(d) {
polygon = polygonGenerator(d);
centroid = d3.polygonCentroid(polygon);
return valueline(
polygon.map(function(point) {
return [ point[0] - centroid[0], point[1] - centroid[1] ];
})
);
});
d3.select(path.node().parentNode).attr('transform', 'translate(' + centroid[0] + ',' + (centroid[1]) + ') scale(' + scaleFactor + ')');
});
}
// drag nodes
function dragstarted(d) {
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
function dragended(d) {
if (!d3.event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
// drag groups
function group_dragstarted(groupId) {
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
d3.select(this).select('path').style('stroke-width', 3);
}
function group_dragged(groupId) {
node
.filter(function(d) { return d.group == groupId; })
.each(function(d) {
d.x += d3.event.dx;
d.y += d3.event.dy;
})
}
function group_dragended(groupId) {
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
d3.select(this).select('path').style('stroke-width', 1);
}
I want these groups to collapse into one on clicking the center node and then expanding on doing the same later.
I have this data :
[{"node":"A","group":"1","type":"node"},
{"node":"B","group":"2","type":"node"},
{"node":"D","group":"1","type":"node"},
{"node":"C","group":"2","type":"node"},
{"type":"link","interest":"1","source":"A","target":"B"},
{"type":"link","interest":"2","source":"A","target":"C"},
{"type":"link","interest":"10","source":"B","target":"C"},
{"type":"link","interest":"3","source":"D","target":"B"}]
Here is my code:
var force = d3.layout.force()
.nodes(data)
.size([+width(), +height()])
.gravity(.08)
.charge(-10)
.on("tick", tick)
.on("end", function(){
chart.dispatchEndDrawing()
})
.start();
var g = selection
.attr("width", width)
.attr("height", height);
var link = g.selectAll("line")
.data(data.filter(function (d){ return d.type == "link"; }))
.enter().append("line")
.style("stroke-width", function(d) { return d.interest; })
.style("stroke", "grey")
.call(force.drag);
var node = g.selectAll("circle")
.data(data.filter(function (d){ return d.type == "node"; }))
.enter().append("circle")
.attr("r", 5)
.style("fill", "blue")
.call(force.drag);
function tick(e) {
node
.attr("cx", function(d) { return d.x = Math.max(radius(), Math.min(width() - radius(), d.x)); })
.attr("cy", function(d) { return d.y = Math.max(radius(), Math.min(height()-10 - radius(), d.y)); });
link
.attr("x1", function(d) { return d3.selectAll('circle').filter(function (k) { return d.source === k.node; }).attr('cx');})
.attr("y1", function(d) { return d3.selectAll('circle').filter(function (k) { return d.source === k.node; }).attr('cy'); })
.attr("x2", function(d) { return d3.selectAll('circle').filter(function (k) { return d.target === k.node; }).attr('cx'); })
.attr("y2", function(d) { return d3.selectAll('circle').filter(function (k) { return d.target === k.node; }).attr('cy'); });
chart.dispatchStartDrawing()
}
})
I want to display 4 circles (A, B, C, D) in separate groups:
Group 1 - (A,D) | Group 2 - (B,C).
The grouping should be automatic by using the attribute "group".
The simulation is done by creating a d3 force layout object
I'm trying to visualize a graph using D3.js via web sockets. I'm trying to display node label ( can be seen in the code below ), but it does not seem to appear at all. Please see the function start(). What is wrong here?
<script>
var width = 1900,
height = 1080;
var color = d3.scale.category10();
var nodes = [],
links = [];
var force = d3.layout.force()
.nodes(nodes)
.links(links)
.charge(-100)
.gravity(0.1)
.linkDistance(100)
.size([width, height])
.on("tick", tick);
var svg = d3.select("body").append("svg").attr("width",width).attr("height", height);
var node = svg.selectAll(".node"),
link = svg.selectAll(".link");
function tick() {
node.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
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; });
}
// Add and remove elements on the graph object
function addNode(id) {
nodes.push({"name":id, "id":id});
start();
}
function addEdge(edgeId,sourceId,targetId) {
var sourceNode = findNode(sourceId);
var targetNode = findNode(targetId);
if((sourceNode !== undefined) && (targetNode !== undefined)) {
links.push({"edgeId":edgeId, "source": sourceNode, "target": targetNode});
start();
}
}
function removeEdge(edgeId) {
for (var i = 0; i < links.length; i++) {
if (links[i].edgeId == edgeId) {
links.splice(i, 1);
break;
}
}
start();
}
var findNode = function (id) {
for (var i=0; i < nodes.length; i++) {
if (nodes[i].id === id)
return nodes[i]
};
}
function start() {
var drag = force.drag().origin(function(d) { return d; }).on("dragstart", dragstarted).on("drag", dragged).on("dragend", dragended);
link = link.data(force.links(), function(d) { return d.source.id + "-" + d.target.id; });
link.enter().insert("line", ".node").attr("class", "link");
link.exit().remove();
node = node.data(force.nodes(), function(d) { return d.id;});
node.enter().append("circle").attr("class", "node")
.attr("r", 5)
.style("fill", function(d) { return color(d.group); })
.call(drag);
node.append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.text(function(d) { return d.name });
node.exit().remove();
force.start();
}
function dragstarted(d) {
d3.event.sourceEvent.stopPropagation();
d3.select(this).classed("dragging", true);
}
function dragged(d) {
d3.select(this).attr("cx", d.x = d3.event.x).attr("cy", d.y = d3.event.y);
}
function dragended(d) {
d3.select(this).classed("dragging", false);
}
var socket = new WebSocket('ws://localhost:8887');
socket.onopen = function(){
console.log("Connection established, handle with function");
};
socket.onmessage = function(evt){
var obj = JSON.parse(evt.data);
if(obj.operation == "nodeAdded")
{
addNode( obj.nodeId );
}
if(obj.operation == "edgeAdded")
{
addEdge(obj.edgeId,obj.fromNodeId,obj.toNodeId);
}
if(obj.operation == "edgeRemoved")
{
removeEdge(obj.edgeId);
}
}
</script>
You will have to group the circles and corresponding labels for each node. Try this way.
node = node.data(force.nodes(), function(d) { return d.id;})
.enter().append("g")
.attr("class", "node")
.call(drag);
node.append("circle")
.attr("r", 5)
.style("fill", function(d) { return color(d.group); });
node.append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.text(function(d) { return d.name });
Also update the tick function as shown below.
function tick() {
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("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
}
By default text elements are of the white color. Add .style("fill", "black"), so you can see them.
node.append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.style("fill", "black")
.text(function(d) { return d.name });
Also your node is the <circle> element. You can't append <text> element to the <circle> element ( it's not a container ). Use a <g> element and append <circle> and <text> to it or append <text> element to the <svg> element.
I am making a timeline sort of project. As of right now, my code looks like the following:
var width = 960,
height = 500;
var color = d3.scale.category20();
var force = d3.layout.force()
.charge(-300)
.linkDistance(30)
.size([width, height]);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
d3.json("input.json", function(error, graph) {
force
.nodes(graph.nodes)
.links(graph.links)
.start();
var link = svg.selectAll(".link")
.data(graph.links)
.enter().append("line")
.attr("class", "link")
.style("stroke-width", function(d) { return Math.sqrt(d.value); })
.style("visibility", function(d) {
return d.value == 2 ? "hidden" : "visible";
});
var node = svg.selectAll(".node")
.data(graph.nodes)
.enter().append("circle")
.attr("class", "node")
.attr("r", 20)
.style("fill", function(d) { return color(d.group); })
.call(force.drag)
.style("visibility", function(d) {
return d.group == 1 ? "hidden" : "visible";
})
.on("mouseover", function(d) {
if(d.group == 2) {
node.filter(function(d) { return d.group == 1; }).style("visibility", "visible");
link.filter(function(d) { return d.value == 2; }).style("visibility", "visible");
texts.filter(function(d) { return d.value == 2; }).style("visibility", "visible");
}
}).on("mouseout", function(d) {
if(d.group == 2) {
node.filter(function(d) { return d.group == 1; }).style("visibility", "hidden");
link.filter(function(d) { return d.value == 2; }).style("visibility", "hidden");
}
});
var texts = svg.selectAll("text.label")
.data(graph.nodes)
.enter().append("text")
.attr("class", "label")
.attr("fill", "black")
.text(function(d) { return d.name; });
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; });
texts.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
});
});
As of right now, the text, or labelsconstantly show, even when the nodes and links don't. I'm wondering what I need to do? I don't know if I can assign a value or group to the labels