How to collapse groups of circle in D3js? - javascript

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.

Related

stacked nodes in directed graph d3js

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!!!

D3 nodes floating out of the frame

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)); })

How to Update d3 force directed graph dynamically in Vue.js?

I have this VueJS code generating d3 force directed network node on mount using json call.
I'm trying to update the network graph with new nodes and links using the same json call and after calculating differences, I want it to add/remove nodes/links accordingly but I can't figure out how to do it.
I already tried to look for examples of dynamic updated of d3 force directed graphs. I actually did find some vue js + d3 examples but non with dynamic updates such as this one which shows how to change the data of the graph and causes the simulation to get restarted but this is not what I look to accomplish.
I've been trying for days to Adapt proper d3 js code from examples into vue js but with no success.
couldn't find any examples with Vue.js and d3 v5 (found some with v4).
new Vue({
el: "#app",
data: function() {
return {
graph: null,
simulation: null,
color: d3.scaleOrdinal(d3.schemeCategory10),
settings: {
strokeColor: "#29B5FF",
width: 100,
svgWidth: 960,
svgHeight: 600
},
radius: 50,
tooltip: d3.select("body").append("div").attr("class", "tooltip2").html(""),
};
},
mounted: function() {
var that = this;
var initData = '{"nodes":[{"id":1,"ip":"100.64.1.118","name":"PCRF","description":"Policy and Charging Rules Function","image":"https://i.imgur.com/mpAqPb4.png"}],"links":[]}';
var initBlob = new Blob([JSON.stringify(initData)], {
type: "application/json"
});
var initUrl = URL.createObjectURL(initBlob);
d3.json(initUrl, function(error, graph) {
console.log(graph);
if (error) throw error;
that.graph = graph;
that.simulation = d3.forceSimulation(that.graph.nodes)
.force("link", d3.forceLink(that.graph.links).distance(300).strength(1))
.force("charge", d3.forceManyBody().strength(-1000))
.force("center", d3.forceCenter(that.settings.svgWidth / 2, that.settings.svgHeight / 2))
.force("xAxis", d3.forceX(that.settings.svgWidth / 2).strength(0.4))
.force("yAxis", d3.forceY(that.settings.svgHeight / 2).strength(0.6))
.force("repelForce", d3.forceManyBody().strength(-5000).distanceMax(300).distanceMin(300));
});
d3.interval(function() {
var interData = '{"nodes":[{"id":1,"ip":"100.64.1.118","name":"PCRF","description":"Allot Policy and Charging Rules Function","image":"https://i.imgur.com/mpAqPb4.png"},{"id":2,"ip":"100.64.1.119","name":"Client","description":"Diameter Client","image":"https://i.imgur.com/NsUvLZb.png"}],"links":[{"id":1,"source":1,"target":2,"type":"OKAY"}]}';
var interBlob = new Blob([JSON.stringify(interData)], {
type: "application/json"
});
var interUrl = URL.createObjectURL(interBlob);
d3.json(interUrl, function(error, graph) {
console.log(graph);
that.updateGraph(graph);
});
}, 5000);
},
methods: {
fixna(x) {
if (isFinite(x)) return x;
return 0;
},
updateGraph(graph) {
var that = this;
that.graph = graph;
//TODO Nodes
var newLinks = graph.links;
newLinks.forEach(function(d) {
d3.select("#l" + d.id)
.attr("class", function(d2) {
if (d.type == 'OKAY') {
return "connected"
} else {
return "disconnected"
}
})
.on("mouseover", function(d2) {
if (d.type == 'OKAY') {
d3.select(this).attr("stroke-width", "3px");
} else {
d3.select(this).attr("stroke-width", "2px");
}
t_text = "<strong>" + d2.source.ip + " <-> " + d2.target.ip + "</strong>";
if (d.type == 'OKAY') {
t_text += '<br>Connection Status: <font color="green">' + d.type + '</font>';
} else {
t_text += '<br>Connection Status: <font color="red">' + d.type + '</font>';
}
that.tooltip.html(t_text)
return that.tooltip.style("visibility", "visible");
})
.on("mousemove", function() {
return that.tooltip.style("top", (event.pageY - 10) + "px").style("left", (event.pageX + 10) + "px");
})
.on("mouseout", function(d2) {
if (d.type == 'OKAY') {
d3.select(this).attr("stroke-width", "2px");
} else {
d3.select(this).attr("stroke-width", "1px");
}
return that.tooltip.style("visibility", "hidden");
});
});
}
},
computed: {
nodes: function() {
var that = this;
if (that.graph) {
var node = d3.select("svg").append("g").attr("class", "nodes").selectAll("g")
.data(that.graph.nodes).enter().append("g")
.call(d3.drag()
.on("start", function dragstarted(d) {
if (!d3.event.active) that.simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
})
.on("drag", function dragged(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
})
.on("end", function dragended(d) {
if (!d3.event.active) that.simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
})).attr("fill", "white");
var defs = node.append("defs");
defs.append('pattern')
.attr("id", function(d, i) {
return "my_image" + i
})
.attr("width", 1)
.attr("height", 1)
.append("svg:image")
.attr("xlink:href", function(d) {
return d.image
})
.attr("height", that.radius)
.attr("width", that.radius)
.attr("x", 0)
.attr("y", 0);
var circle = node.append("circle")
.attr("r", that.radius / 2)
.attr("fill", function(d, i) {
return "url(#my_image" + i + ")"
})
.attr("id", function(d) {
if (d.id == "p0") {
return "mainNode"
} else {
return d.id
}
})
.attr("stroke", "#494b4d")
.attr("stroke-width", "2px")
.on("mouseover", function(d) {
d3.select(this).attr("stroke-width", "3px");
//sets tooltip. t_text = content in html
t_text = "<strong>" + d.name + "</strong><br>IP Address: " + d.ip
that.tooltip.html(t_text)
return that.tooltip.style("visibility", "visible");
})
.on("mousemove", function() {
return that.tooltip.style("top", (event.pageY - 10) + "px").style("left", (event.pageX + 10) + "px");
})
.on("mouseout", function() {
d3.select(this).attr("stroke-width", "2px");
return that.tooltip.style("visibility", "hidden");
});
node.append("text")
.style("fill", "black")
.attr("dx", 0)
.attr("dy", that.radius - ((that.radius / 10) - 3) * 5)
.attr("text-anchor", "middle")
.text(function(d) {
return d.name + " - " + d.ip;
});
return node;
}
},
links: function() {
var that = this;
if (that.graph) {
return d3.select("svg").append("g")
.attr("class", "links")
.selectAll("line")
.data(that.graph.links)
.enter()
.append("line")
.attr("id", function(d) {
return "l" + d.id
})
.attr("class", function(d) {
if (d.type == 'OKAY') {
return "connected"
} else {
return "disconnected"
}
})
.on("mouseover", function(d) {
if (d.type == 'OKAY') {
d3.select(this).attr("stroke-width", "3px");
} else {
d3.select(this).attr("stroke-width", "2px");
}
t_text = "<strong>" + d.source.ip + " <-> " + d.target.ip + "</strong>";
if (d.type == 'OKAY') {
t_text += '<br>Connection Status: <font color="green">' + d.type + '</font>';
} else {
t_text += '<br>Connection Status: <font color="red">' + d.type + '</font>';
}
that.tooltip.html(t_text)
return that.tooltip.style("visibility", "visible");
})
.on("mousemove", function() {
return that.tooltip.style("top", (event.pageY - 10) + "px").style("left", (event.pageX + 10) + "px");
})
.on("mouseout", function(d) {
if (d.type == 'good') {
d3.select(this).attr("stroke-width", "2px");
} else {
d3.select(this).attr("stroke-width", "1px");
}
return that.tooltip.style("visibility", "hidden");
});
}
},
},
updated: function() {
var that = this;
that.simulation.nodes(that.graph.nodes).on('tick', function ticked() {
that.links
.attr("x1", function(d) {
return that.fixna(d.source.x);
})
.attr("y1", function(d) {
return that.fixna(d.source.y);
})
.attr("x2", function(d) {
return that.fixna(d.target.x);
})
.attr("y2", function(d) {
return that.fixna(d.target.y);
});
that.nodes
.attr("transform", function(d) {
return "translate(" + that.fixna(d.x) + "," + that.fixna(d.y) + ")";
});
});
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
I created this fiddle.
<div id="app">
<div class="svg-container" :style="{width: settings.width + '%'}">
<svg id="svg" pointer-events="all" viewBox="0 0 960 600" preserveAspectRatio="xMinYMin meet">
<g :id="links"></g>
<g :id="nodes"></g>
</svg>
</div>
</div>
it's working with d3.v4. I tried changing to v5 but it doesn't work (maybe can someone shed some light).
Would be happy if anyone can point me to the right direction or help me with the implementation.
I'm trying to add nodes and remove them exactly like in this demo here.
there you can see new nodes generated without the whole simulation restarting which makes it look very smooth.

JavaScript Code executing twice outside of functions

I am developing a site locally using D3 JS. When I do anything outside of any function it is happening twice, no matter where it is within the code. Example:
console.log("2x");
Console output is:
2x
2x
However if the code is inside any of the functions it only prints once. I noticed that next to the logs there is two different locations for their origin
Console output
2x site.js:3
2x site.js?v=twO2e-dF40DXz0Jm_X753ZBfaW8vwQs0ht7UrLyed5E:3
Inside a function the logs only originate from the longer string version. This affects any code outside a function, it seems to run twice...I've included the full code for reference if required.
I have found many similarly titled or tagged questions but all of them were due to the logging occurring in a loop or otherwise, I couldn't find any where it happened in the base code.
EDIT: My code does have two console.logs but that results in 4 prints in that case sorry for being unclear on that.
JavaScript
//Azibuda
console.log("2x");
//Get the SVG element
var svg = d3.select("svg");
var width = 960, height = 600;
var color = d3.scaleOrdinal(d3.schemeCategory20);
var link = svg.append("g").selectAll(".link");
var node = svg.append("g").selectAll(".node");
var label = svg.append("g").selectAll(".label");
//Begin the force simulation
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function (d) { return d.id; }).distance(50).strength(0.3))
.force("charge", d3.forceManyBody().strength(-15))
.force("center", d3.forceCenter(width / 2, height / 2));
//Highlight variables
var highlight_color = "blue";
var tHighlight = 0.05;
var config;
var linkedByIndex = {};
var linksAsString = {};
//Get the data
d3.json("/../../data.json", function (data) {
config = data;
if (!localStorage.graph)
{
localStorage.graph = JSON.stringify(data);
}
update();
});
function update() {
console.log(localStorage.graph);
//Create an array of source,target containing all links
config.links.forEach(function (d) {
linkedByIndex[d.source + "," + d.target] = true;
linkedByIndex[d.target + "," + d.source] = true;
//linksAsString[d.index] = d.source + "," + d.target;
});
var nodesAsString = {};
config.nodes.forEach(function (d) {
nodesAsString[d.index] = d.id + "," + d.radius;
});
//Draw links
link = link.data(config.links);
link.exit().remove();
link = link.enter().append("line")
.attr("class", "link")
.attr("stroke-width", 2)
.attr("stroke", "#888")
//.attr("opacity", function (d) { if (d.target.radius > 7) { return 1 }; return 0; })
.merge(link);
node = node.data(config.nodes);
node.exit().remove();
node = node.enter().append("circle")
.attr("class", "node")
.attr("r", function(d) { return d.radius; })
.attr("fill", function (d) { return color(d.id); })
.attr("stroke", "black")
// .attr("pointer-events", function (d) { if (d.radius <= 7) { return "none"; } return "visibleAll"; })
// .attr("opacity", function (d) { if (d.radius <= 7) { return 0; } return 1; })
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended))
.on("mouseover", mouseOver)
.on("mouseout", mouseOut)
.merge(node);
label = label.data(config.nodes);
label.exit().remove();
label = label.enter().append("text")
.attr("class", "label")
.attr("dx", function (d) { return d.radius * 1.25; })
.attr("dy", ".35em")
.attr("opacity", function (d) { if (d.radius <= 7) { return 0; } return 1; })
.attr("font-weight", "normal")
.style("font-size", 10)
.text(function (d) { return d.id; })
.merge(label);
//Add nodes to simulation
simulation
.nodes(config.nodes)
.on("tick", ticked);
//Add links to simulation
simulation.force("link")
.links(config.links);
simulation.alphaTarget(0.3).restart();
}
//Animating by ticks function
function ticked() {
node
.attr("cx", function (d) { return d.x = Math.max(d.radius, Math.min(width - d.radius, d.x)); })
.attr("cy", function (d) { return d.y = Math.max(d.radius, Math.min(height - d.radius, 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; });
label
.attr("x", function (d) { return d.x = Math.max(d.radius, Math.min(width - d.radius, d.x)); })
.attr("y", function (d) { return d.y = Math.max(d.radius, Math.min(height - d.radius, d.y)); });
}
//Using above array, check if two nodes are linked
function isConnected(node1, node2) {
return linkedByIndex[node1.id + "," + node2.id] || node1.index == node2.index;
}
//Highlight a node
function setHighlight(d) {
svg.style("cursor", "pointer");
//Set highlighted stroke around the current node, text and its links
node.style("stroke", function (tNode) {
return isConnected(d, tNode) ? highlight_color : "black";
});
label.style("font-weight", function (tNode) {
return isConnected(d, tNode) ? "bold" : "normal";
});
link.style("stroke", function (tNode) {
return tNode.source.index == d.index || tNode.target.index == d.index ? highlight_color : "#888";
});
}
//Drag/mousedown on a node
function dragstarted(d) {
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
//Dragging a node
function dragged(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
//Highlight/focus on held down node
setFocus(d);
setHighlight(d);
}
//End drag/mouseup on a node
function dragended(d) {
if (!d3.event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
//Mouse over on a node
function mouseOver(d) {
setFocus(d);
setHighlight(d);
}
//Mouse off of a node
function mouseOut(d) {
unFocus(d);
highlightOff();
}
//Turning off highlight
function highlightOff() {
svg.style("cursor", "default");
//Set node attributes back to normal
node.style("stroke", "black");
label.style("font-weight", "normal");
link.style("stroke", "#888");
}
//Focus on a node
function setFocus(d) {
//Set opacity of all non-connected nodes and their elements (text/links) to faded
node.style("opacity", function (tNode) {
return isConnected(d, tNode) ? 1 : tHighlight;
});
label.style("opacity", function (tNode) {
return isConnected(d, tNode) ? 1 : tHighlight;
});
link.style("opacity", function (tNode) {
return tNode.source.index == d.index || tNode.target.index == d.index ? 1 : tHighlight;
});
}
//Unfocus on a node (reset all to normal)
function unFocus(d) {
//node.style("opacity", function (d) { if (d.radius <= 7) { return 0; } return 1; });
//node.style("pointer-events", function (d) { if (d.radius <= 7) { return "none"; } return "visibleAll"; })
node.style("opacity", 1);
label.style("opacity", function (d) { if (d.radius <= 7) { return 0; } return 1; });
//link.style("opacity", function (d) { if (d.target.radius > 7) { return 1 }; return 0; });
link.style("opacity", 1);
}
function updateR()
{
console.log(config.nodes[2]);
config.nodes.splice(2, 1);
update();
}
var temp = JSON.parse(localStorage.graph);
//temp.nodes.push({"id":"Cheese", "radius":20});
//localStorage.graph = JSON.stringify(temp);
console.log("2x");
HTML
#{
ViewData["Title"] = "Home Page";
}
<link href="/css/geico-design-kit.css" rel="stylesheet">
<script src="~/js/geico-design-kit.bundle.js"></script>
<script src="~/js/d3.js"></script>
<script src="~/js/site.js"></script>
<script src="https://d3js.org/d3.v4.min.js"></script>
<style>
.link line {
stroke: #888;
}
text {
pointer-events: none;
font: 10px sans-serif;
}
</style>
<div class="col-md-12">
<div class="col-md-3">
<h2>Quick Links</h2>
<ul class="list list--unordered">
<li>Example Quick Links Here</li>
<li>Google</li>
<li>Google</li>
</ul>
</div>
</div>
<button onclick="updateR()" type="button" style="background-color:red">DO NOT PRESS!</button>
<svg id="container" width="960" height="600" style="border:1px solid black;"></svg>
<form id="nodes"></form>
If this ASP.NET view is using a layout view and the same file is referenced in that layout file, the rendered output would have multiple references.

Displaying Node label in a dynamic forcelayout graph in D3.js

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.

Categories