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.
Related
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 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.
so Im really new to d3 and Im just getting started with data visualizations. So im trying to use mike bostok treemap to visualize with my data. I have some understanding of parent nodes and children nodes but im at a lost when it comes to understanding or writing a code related to nodes.
bostoks JSON data has multiple levels or depths while mine has one root node and an array of objects all at the same depth.
JSON file
Javascript code
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
var fader = function(color) { return d3.interpolateRgb(color, "#fff")(0.2); },
color = d3.scaleOrdinal(d3.schemeCategory20.map(fader)),
format = d3.format(",d");
var treemap = d3.treemap()
.tile(d3.treemapResquarify)
.size([width, height])
.round(true)
.paddingInner(1);
d3.json("data.json", function(data) {
var root = d3.hierarchy(data)
.eachBefore(function(d) {
d.data.id = (d.parent ? d.parent.data.id + "." : "") + d.data.name; })
.sum(sumBySize)
.sort(function(a, b) {
return b.height - a.height || b.value - a.value; });
treemap(root);
console.log(d.data.id);
var cell = svg.selectAll("g")
.data(root.leaves())
.enter().append("g")
.attr("transform", function(d) { return "translate(" + d.x0 + "," + d.y0 + ")"; });
cell.append("rect")
.attr("id", function(d) { return d.data.id; })
.attr("width", function(d) { return d.x1 - d.x0; })
.attr("height", function(d) { return d.y1 - d.y0; })
//.attr("fill", function(d) { return color(d.parent.data.id); });
cell.append("clipPath")
.attr("id", function(d) { return "clip-" + d.data.id; })
.append("use")
.attr("xlink:href", function(d) { return "#" + d.data.id; });
cell.append("text")
.attr("clip-path", function(d) { return "url(#clip-" + d.data.id + ")"; })
.selectAll("tspan")
.data(function(d) { return d.data.name.split(/(?=[A-Z][^A-Z])/g); })
.enter().append("tspan")
.attr("x", 4)
.attr("y", function(d, i) { return 13 + i * 10; })
.text(function(d) { return d; });
cell.append("title")
.text(function(d) { return d.data.id + "\n" + format(d.value); });
d3.selectAll("input")
.data([sumBySize, sumByCount], function(d) { return d ? d.name : this.value; })
.on("change", changed);
var timeout = d3.timeout(function() {
d3.select("input[value=\"sumByCount\"]")
.property("checked", true)
.dispatch("change");
}, 2000);
function changed(sum) {
timeout.stop();
treemap(root.sum(sum));
cell.transition()
.duration(750)
.attr("transform", function(d) { return "translate(" + d.x0 + "," + d.y0 + ")"; })
.select("rect")
.attr("width", function(d) { return d.x1 - d.x0; })
.attr("height", function(d) { return d.y1 - d.y0; });
}
});
function sumByCount(d) {
return d.children ? 0 : 1;
}
function sumBySize(d) {
return d.size;
}
could someone help me with this . I think it has to do with the hierarchy.
I think I might need to change something here
d3.json("data.json", function(data) {
var root = d3.hierarchy(data)
.eachBefore(function(d) {
d.data.id = (d.parent ? d.parent.data.id + "." : "") + d.data.name; })
I appreciate any help
I'm trying to add Nodes and Links between them on a Button click without loading any .Json file.
Here is what i've done: Create Nodes/Links on document ready
$(document).ready(function(){
var w = $("#graph").innerWidth();
var h = $("#graph").innerHeight();
var default_node_color = "#ccc";
var default_link_color = "red";
var nominal_base_node_size = 8;
var nominal_text_size = 10;
var max_text_size = 24;
var nominal_stroke = 1.5;
var highlight_stroke_width = 4;
var max_stroke = 4.5;
var min_zoom = 0.1;
var max_zoom = 7;
var svg = d3.select("#graph").append("svg");
var zoom = d3.behavior.zoom().scaleExtent([min_zoom,max_zoom])
var g = svg.append("g");
svg.style("cursor","move");
var dnodes = [];
var dlinks = [];
function findNode(id) {
for (var i in dnodes) {
if (dnodes[i]["id"] === id) return dnodes[i];
};
};
function addNode(id,name) {
var newNode = findNode(id);
if(newNode == undefined)
{
dnodes.push({"id":id,"name":name});
//update(dnodes,dlinks);
}
};
function addLink(sourceId, targetId) {
var sourceNode = findNode(sourceId);
var targetNode = findNode(targetId);
if((sourceNode !== undefined) && (targetNode !== undefined)) {
dlinks.push({"source": sourceNode, "target": targetNode});
//update(dnodes,dlinks);
}
};
addNode("1","2XYZ");
addNode("3","3XYZ");
addNode("4","4XYZ");
addNode("5","5XYZ");
addLink("1","2");
addLink("1","3");
addLink("1","4");
addLink("1","5");
var force = d3.layout.force()
.linkDistance(160)
.charge(-300)
.friction(0.5)
.size([w,h]);
var nodes = force.nodes(dnodes);
var links = force.links(dlinks);
force.start();
//function update(dnodes, dlinks)
//{
function isConnected(sourceNodeid, destNodeid)
{
for(var i in dlinks)
{
if((dlinks[i].source.id == sourceNodeid && dlinks[i].target.id==destNodeid)||((dlinks[i].source.id == destNodeid && dlinks[i].target.id==sourceNodeid)))
{
return true;
}
}
if(sourceNodeid == destNodeid)
{
return true;
}
return false;
};
function dragstart(d, i) {
force.stop() // stops the force auto positioning before you start dragging
}
function dragmove(d, i) {
d.px += d3.event.dx;
d.py += d3.event.dy;
d.x += d3.event.dx;
d.y += d3.event.dy;
tick();
}
function dragend(d, i) {
d.fixed = true;
tick();
}
var node_drag = d3.behavior.drag()
.on("dragstart", dragstart)
.on("drag", dragmove)
.on("dragend", dragend);
var link = g.selectAll(".link")
.data(dlinks)
.enter().append("line")
.attr("class", "link")
.style("stroke-width",nominal_stroke)
.style("stroke", default_link_color)
var node = g.selectAll(".node")
.data(dnodes)
.enter().append("g")
.attr("class", "node")
.call(node_drag);
var circle = node.append("rect")
.attr("x", "-15px")
.attr("y", "-15px")
.attr("rx", "4")
.attr("ry", "4")
.attr("width", "30px")
.attr("height", "30px")
.attr("id", function (d) {return d.id;})
.attr("fill", "#336699");
var text = g.selectAll(".text")
.data(dnodes)
.enter().append("text")
.attr("dy", ".35em")
.attr("y","22")
.style("font-size", nominal_text_size + "px")
text.text(function(d) { return d.name; })
.style("text-anchor", "middle");
zoom.on("zoom", function() {
var stroke = nominal_stroke;
if (nominal_stroke*zoom.scale()>max_stroke) stroke = max_stroke/zoom.scale();
link.style("stroke-width",stroke);
circle.style("stroke-width",stroke);
var text_size = nominal_text_size;
if (nominal_text_size*zoom.scale()>max_text_size) text_size = max_text_size/zoom.scale();
text.style("font-size",text_size + "px");
g.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
});
svg.call(zoom);
resize();
force.on("tick", tick);
function tick()
{
node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
text.attr("transform", function(d) { return "translate(" + d.x + "," + 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; });
//node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
node.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
}
function resize() {
var width = $("#graph").innerWidth();
var height = $("#graph").innerHeight();
svg.attr("width", width).attr("height", height);
force.size([force.size()[0]+(width-w)/zoom.scale(),force.size()[1]+(height-h)/zoom.scale()]).resume();
w = width;
h = height;
}
//}
});
text {
font-family: sans-serif;
pointer-events: none;
}
html,body { width:100%; height:100%; margin:none; padding:none; }
#graph { width:100%;height:100%; margin:auto; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.3.10/d3.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<body>
<div id="graph"></div>
</body>
and Here is What i'm trying to do: By Update function
$(document).ready(function(){
var w = $("#graph").innerWidth();
var h = $("#graph").innerHeight();
var default_node_color = "#ccc";
var default_link_color = "red";
var nominal_base_node_size = 8;
var nominal_text_size = 10;
var max_text_size = 24;
var nominal_stroke = 1.5;
var highlight_stroke_width = 4;
var max_stroke = 4.5;
var min_zoom = 0.1;
var max_zoom = 7;
var svg = d3.select("#graph").append("svg");
var zoom = d3.behavior.zoom().scaleExtent([min_zoom,max_zoom])
var g = svg.append("g");
svg.style("cursor","move");
var dnodes = [];
var dlinks = [];
function findNode(id) {
for (var i in dnodes) {
if (dnodes[i]["id"] === id) return dnodes[i];
}
}
function addNode(id,name) {
var newNode = findNode(id);
if(newNode == undefined)
{
dnodes.push({"id":id,"name":name});
update(dnodes,dlinks);
}
}
function addLink(sourceId, targetId) {
var sourceNode = findNode(sourceId);
var targetNode = findNode(targetId);
if((sourceNode !== undefined) && (targetNode !== undefined)) {
dlinks.push({"source": sourceNode, "target": targetNode});
update(dnodes,dlinks);
}
}
$("#btnadd").click(function(){
addNode("1","2XYZ");
addNode("3","3XYZ");
addNode("4","4XYZ");
addNode("5","5XYZ");
addLink("1","2");
addLink("1","3");
addLink("1","4");
addLink("1","5");
});
var force = d3.layout.force()
.linkDistance(160)
.charge(-300)
.friction(0.5)
.size([w,h]);
var nodes = force.nodes(dnodes);
var links = force.links(dlinks);
force.start();
function update(dnodes, dlinks)
{
function isConnected(sourceNodeid, destNodeid)
{
for(var i in dlinks)
{
if((dlinks[i].source.id == sourceNodeid && dlinks[i].target.id==destNodeid)||((dlinks[i].source.id == destNodeid && dlinks[i].target.id==sourceNodeid)))
{
return true;
}
}
if(sourceNodeid == destNodeid)
{
return true;
}
return false;
}
function dragstart(d, i) {
force.stop();
}
function dragmove(d, i) {
d.px += d3.event.dx;
d.py += d3.event.dy;
d.x += d3.event.dx;
d.y += d3.event.dy;
tick();
}
function dragend(d, i) {
d.fixed = true;
tick();
}
var node_drag = d3.behavior.drag()
.on("dragstart", dragstart)
.on("drag", dragmove)
.on("dragend", dragend);
var link = g.selectAll(".link")
.data(dlinks)
.enter().append("line")
.attr("class", "link")
.style("stroke-width",nominal_stroke)
.style("stroke", default_link_color);
var node = g.selectAll(".node")
.data(dnodes)
.enter().append("g")
.attr("class", "node")
.call(node_drag);
var circle = node.append("rect")
.attr("x", "-15px")
.attr("y", "-15px")
.attr("rx", "4")
.attr("ry", "4")
.attr("width", "30px")
.attr("height", "30px")
.attr("id", function (d) {return d.id;})
.attr("fill", "#336699");
var text = g.selectAll(".text")
.data(dnodes)
.enter().append("text")
.attr("dy", ".35em")
.attr("y","22")
.style("font-size", nominal_text_size + "px")
text.text(function(d) { return d.name; })
.style("text-anchor", "middle");
zoom.on("zoom", function() {
var stroke = nominal_stroke;
if (nominal_stroke*zoom.scale()>max_stroke) stroke = max_stroke/zoom.scale();
link.style("stroke-width",stroke);
circle.style("stroke-width",stroke);
var text_size = nominal_text_size;
if (nominal_text_size*zoom.scale()>max_text_size) text_size = max_text_size/zoom.scale();
text.style("font-size",text_size + "px");
g.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
});
svg.call(zoom);
resize();
force.on("tick", tick);
function tick()
{
node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
text.attr("transform", function(d) { return "translate(" + d.x + "," + 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; });
//node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
node.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
}
function resize() {
var width = $("#graph").innerWidth();
var height = $("#graph").innerHeight();
svg.attr("width", width).attr("height", height);
force.size([force.size()[0]+(width-w)/zoom.scale(),force.size()[1]+(height-h)/zoom.scale()]).resume();
w = width;
h = height;
}
}
});
text {
font-family: sans-serif;
pointer-events: none;
}
html,body { width:100%; height:100%; margin:none; padding:none; }
#graph { width:100%;height:100%; margin:auto; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.3.10/d3.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<body>
<button id="btnadd">Add</button>
<div id="graph"></div>
</body>
1st link code is working fine , 2nd link code is not working properly.
in 2nd link code i'm updating graph by update() function to add nodes on a button click.
addNode() addLink() i want to call on a button click. something like:
$("#btnadd").click(function(){
addNode("1","2XYZ");
addNode("3","3XYZ");
addLink("1","2");
});
I'm not getting where i'm doing wrong.
Thanks!
You are calling the update after each node insertion.
if(newNode == undefined)
{
dnodes.push({"id":id,"name":name});
update(dnodes,dlinks);//don't do this
}
Instead make all your nodes and links in the button click like this(commented below).
$("#btnadd").click(function() {
addNode("1", "2XYZ");
addNode("3", "3XYZ");
addNode("4", "4XYZ");
addNode("5", "5XYZ");
addLink("1", "2");
addLink("1", "3");
addLink("1", "4");
addLink("1", "5");
update(dnodes, dlinks);//call your update function
force.start();//you forgot to start the force layout.
});
working code here
I'm very new to D3, and I don't understand all the logic of it.
I try to use examples, but it seems to be so many way to do things that it confuses me...
Anyway, I try to make this example dynamic.
Here is the code I have right now :
<!DOCTYPE html>
<meta charset="utf-8">
<style>
text {
font: 12px sans-serif;
text-anchor: middle;
}
.titre {
font-size: 18px;
}
.node--hover circle {
stroke: #000;
stroke-width: 1.2px;
}
#csv {
float: left;
}
</style>
<form>
<textarea name="csv" id="csv" cols="50" rows="30">id,taille,titre
D3,
D3.one,2000
D3.two,2000</textarea>
</form>
<svg width="400" height="400"><g transform="translate(1,1)"></g></svg>
<script src="https://code.jquery.com/jquery-3.1.1.slim.min.js"></script>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
var format = d3.format(",d");
var color = d3.scaleSequential(d3.interpolateMagma)
.domain([-4, 4]);
var stratify = d3.stratify()
.parentId(function(d) { return d.id.substring(0, d.id.lastIndexOf(".")); });
var pack = d3.pack()
.size([width - 2, height - 2])
.padding(3);
var csv = $("#csv").val();
data = d3.csvParse(csv);
var root = stratify(data)
.sum(function(d) { return d.taille; })
.sort(function(a, b) { return b.taille - a.taille; });
pack(root);
var node = svg.select("g")
.selectAll("g")
.data(root.descendants())
.enter().append("g")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
.attr("class", function(d) { return "node" + (!d.children ? " node--leaf" : d.depth ? "" : " node--root"); })
.each(function(d) { d.node = this; });
node.append("circle")
.attr("id", function(d) { return "node-" + d.id; })
.attr("r", function(d) { return d.r; })
.style("fill", function(d) {
return color(d.depth);
});
var leaf = node.filter(function(d) { return !d.children; });
leaf.append("clipPath")
.attr("id", function(d) { return "clip-" + d.id; })
.append("use")
.attr("xlink:href", function(d) { return "#node-" + d.id + ""; });
leaf.append("text")
.attr("clip-path", function(d) { return "url(#clip-" + d.id + ")"; }).attr("class", function(d) { return d.data.titre=="1" ? "titre" : ""})
.selectAll("tspan")
.data(function(d) { return d.id.substring(d.id.lastIndexOf(".") + 1).split(/(?=[A-Z][^A-Z])/g); })
.enter().append("tspan")
.attr("x", 0)
.attr("y", function(d, i, nodes) { return 13 + (i - nodes.length / 2 - 0.5) * 20; })
.text(function(d) { return d; });
$(function() {
$("#csv").blur(function()
{
update();
});
});
function update()
{
var csv = $("#csv").val();
data = d3.csvParse(csv);
var root = stratify(data)
.sum(function(d) { return d.taille; })
.sort(function(a, b) { return b.taille - a.taille; });
pack(root);
//console.log(root.descendants());
node = node.data(root.descendants(), function(d) {return d});
console.log(node);
//node.exit().remove();
node.enter().append("g")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
.attr("class", function(d) { return "node" + (!d.children ? " node--leaf" : d.depth ? "" : " node--root"); })
.each(function(d) { d.node = this; });
node.append("circle")
.attr("id", function(d) { return "node-" + d.id; })
.attr("r", function(d) { return d.r; })
.style("fill", function(d) {
return color(d.depth);
});
var leaf = node.filter(function(d) { return !d.children; });
leaf.append("clipPath")
.attr("id", function(d) { return "clip-" + d.id; })
.append("use")
.attr("xlink:href", function(d) { return "#node-" + d.id + ""; });
leaf.append("text")
.attr("clip-path", function(d) { return "url(#clip-" + d.id + ")"; }).attr("class", function(d) { return d.data.titre=="1" ? "titre" : ""})
.selectAll("tspan")
.data(function(d) { return d.id.substring(d.id.lastIndexOf(".") + 1).split(/(?=[A-Z][^A-Z])/g); })
.enter().append("tspan")
.attr("x", 0)
.attr("y", function(d, i, nodes) { return 13 + (i - nodes.length / 2 - 0.5) * 20; })
.text(function(d) { return d; });
}
</script>
You can test it on bl.ocks :
https://bl.ocks.org/matthieubrunet/79ef9968e2eaaba7f0718a373d240025
The update is supposed to happen on blur.
I think my problem is around the enter function (in the update function), which returns all elements instead of only new ones.
As ou can see, I commented out the exit, because it removes all the childs circles.
Thanks a lot
One of the most confusing things with d3 is how to handle the enter, update and exit pattern. I won't provide you a tutorial on it as there are a number of great resources already - out there. But I've taken the time to re-factor your update function to handle the situations properly:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
text {
font: 12px sans-serif;
text-anchor: middle;
}
.titre {
font-size: 18px;
}
.node--hover circle {
stroke: #000;
stroke-width: 1.2px;
}
#csv {
float: left;
}
</style>
<form>
<textarea name="csv" id="csv" cols="50" rows="30">id,taille,titre
D3,
D3.one,2000
D3.two,2000</textarea>
</form>
<svg width="400" height="400">
<g transform="translate(1,1)"></g>
</svg>
<script src="https://code.jquery.com/jquery-3.1.1.slim.min.js"></script>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
var format = d3.format(",d");
var color = d3.scaleSequential(d3.interpolateMagma)
.domain([-4, 4]);
var stratify = d3.stratify()
.parentId(function(d) {
return d.id.substring(0, d.id.lastIndexOf("."));
});
var pack = d3.pack()
.size([width - 2, height - 2])
.padding(3);
update();
$(function() {
$("#csv").blur(function() {
update();
});
});
function update() {
var csv = $("#csv").val();
data = d3.csvParse(csv);
var root = stratify(data)
.sum(function(d) {
return d.taille;
})
.sort(function(a, b) {
return b.taille - a.taille;
});
pack(root);
// data-binding
var node = svg.selectAll(".node").data(root.descendants(), function(d){
return d;
});
// exiting nodes
node.exit().remove();
// entering nodes
var nodeEnter = node.enter().append("g")
.attr("class", function(d) {
return "node" + (!d.children ? " node--leaf" : d.depth ? "" : " node--root");
});
// add circles
nodeEnter.append("circle")
.attr("id", function(d) {
return "node-" + d.id;
});
// update + enter
node = nodeEnter.merge(node);
// position everyone
node.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
// update circles
node.select("circle")
.attr("r", function(d) {
return d.r;
})
.style("fill", function(d) {
return color(d.depth);
});
// handle enter of leafs
var leafEnter = nodeEnter.filter(function(d) {
return !d.children;
});
leafEnter.append("clipPath")
.attr("id", function(d) {
return "clip-" + d.id;
})
.append("use")
.attr("xlink:href", function(d) {
return "#node-" + d.id + "";
});
leafEnter.append("text")
.attr("clip-path", function(d) {
return "url(#clip-" + d.id + ")";
}).attr("class", function(d) {
return d.data.titre == "1" ? "titre" : "";
});
node.select("text")
.selectAll("tspan")
.data(function(d) {
return d.id.substring(d.id.lastIndexOf(".") + 1).split(/(?=[A-Z][^A-Z])/g);
})
.enter().append("tspan")
.attr("x", 0)
.attr("y", function(d, i, nodes) {
return 13 + (i - nodes.length / 2 - 0.5) * 20;
})
.text(function(d) {
return d;
});
}
</script>