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>
Related
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.
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 have done basic things in D3 so I don't have deep knowledge about it.
I have created the zoomable circle packing chart with bar chart.
Here is the code:
var xr, yr, xaxis, yaxis, bar, bg;
var svg = d3.select("svg"),
margin = 20,
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom;
diameter = +svg.attr("width"),
g = svg.append("g").attr("transform", "translate(" + diameter / 2 + "," + diameter / 2 + ")");
var color = d3.scaleLinear()
.domain([-1, 5])
.range(["hsl(152,80%,80%)", "hsl(228,30%,40%)"])
.interpolate(d3.interpolateHcl);
var pack = d3.pack()
.size([diameter - margin, diameter - margin])
.padding(2);
d3.json("occupation.json", function(error, root) {
if (error) throw error;
root = d3.hierarchy(root)
.sum(function(d) {
return d.size;
})
.sort(function(a, b) {
return b.value - a.value;
});
var focus = root,
nodes = pack(root).descendants(),
view;
var circle = g.selectAll("circle")
.data(nodes.filter(function(d) {
return d.height > 0
}))
.enter().append("circle")
.attr("class", function(d) {
return d.parent ? d.children ? "node" : "node node--leaf" : "node node--root";
})
.style("fill", function(d) {
return d.children ? color(d.depth) : null;
})
.on("click", function(d) {
if (focus !== d) {
if (d.children) {
zoom(d), d3.event.stopPropagation();
} else {
var nextMonthVal = prompt("Please enter target value you want to set");
if (nextMonthVal) {
alert("You have set the target of Rs. " + nextMonthVal + " for next Month");
}
}
}
});
var leaf = g.selectAll(".bars")
.data(nodes.filter(function(d) {
return d.height == 1
}))
.enter()
.append("g")
.attr("x", 0)
.attr("y", 0)
.attr("height", function(d) {
return d.x + d.r
})
.attr("width", function(d) {
return d.y + d.r
})
.attr("class", "bars")
.each(function(d) {
drawBarData(this, this.__data__, d);
})
var text = g.selectAll(".label")
.data(nodes.filter(function(d) {
return d.height > 0
}))
.enter().append("text")
.attr("class", "label")
.style("fill-opacity", function(d) {
return d.parent === root ? 1 : 0;
})
.style("display", function(d) {
return d.parent === root ? "inline" : "none";
})
.text(function(d) {
return d.data.name + " " + d.data.size + " Rs.";
});
var node = g.selectAll("circle,.bars,.label");
svg
.style("background", color(-1))
.on("click", function() {
zoom(root);
});
zoomTo([root.x, root.y, root.r * 2 + margin]);
function zoom(d) {
var focus0 = focus;
focus = d;
var transition = d3.transition()
.duration(d3.event.altKey ? 7500 : 750)
.tween("zoom", function(d) {
var i = d3.interpolateZoom(view, [focus.x, focus.y, focus.r * 2 + margin]);
return function(t) {
zoomTo(i(t), focus);
};
});
transition.selectAll(".label")
.filter(function(d) {
return d.parent === focus || this.style.display === "inline";
})
.style("fill-opacity", function(d) {
return d.parent === focus ? 1 : 0;
})
.on("start", function(d) {
if (d.parent === focus) this.style.display = "inline";
})
.on("end", function(d) {
if (d.parent !== focus) this.style.display = "none";
});
}
function zoomTo(v, focus) {
var k = diameter / v[2];
view = v;
node.attr("transform", function(d) {
return "translate(" + (d.x - v[0]) * k + "," + (d.y - v[1]) * k + ")";
});
circle.attr("r", function(d) {
return d.r * k;
});
if (focus && focus.height == 1) {
var data2 = focus.children.map(function(d) {
return d.data
})
var data1 = []
rectwidth = focus.r,
rectheight = focus.r;
barsize = data2.length;
maxDataPoint = d3.max(data2, function(d) {
return d.size
});
var linearScale = d3.scaleLinear()
.domain([0, maxDataPoint])
.range([0, rectheight]);
for (var i = 0; i < data2.length; i++) {
data1.push({
name: data2[i].name,
size: linearScale(data2[i].size)
})
}
bg.attr("transform", function(d) {
console.log(d);
return "translate(" + (d.x - v[0]) * k + "," + (d.y - v[1]) * k + ")";
})
bar.attr("x", function(d, i) {
console.log("D ::: >", d);
return i * (rectwidth / data1.length);
})
.attr("y", function(d) {
return rectheight - d.size;
})
.attr("width", focus.r / data1.length - 2)
.attr("height", function(d) {
return d.size;
});
}
}
function drawBarData(ele, data, d) {
if (!data && !data.parent)
return;
var data2 = data.children.map(function(e) {
return e.data
})
var data1 = []
rectwidth = d.r,
rectheight = d.r;
barsize = data2.length;
maxDataPoint = d3.max(data2, function(d) {
return d.size
});
var linearScale = d3.scaleLinear()
.domain([0, maxDataPoint])
.range([0, rectheight]);
for (var i = 0; i < data2.length; i++) {
data1.push({
name: data2[i].name,
size: linearScale(data2[i].size)
})
}
bg = d3.select(ele).attr("transform", "translate(" + 0 + "," + 0 + ")").append("g")
.attr("class", "chart-wrapper")
.attr("transform", function(d) {
console.log('BG ::: >>>', d);
return "translate(" + -d.r / 2 + "," + -d.r / 2 + ")";
});
bar = bg.selectAll(".bar")
.data(data1)
.enter()
.append('rect')
.attr("class", "bar")
.attr("x", function(d, i) {
return i * (rectwidth / data1.length);
})
.attr("y", function(d) {
return rectheight - d.size;
})
.attr("width", d.r / data1.length - 2)
.attr("height", function(d) {
return d.size;
});
}
});
body {
padding: 50px;
font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
}
a {
color: #00B7FF;
}
.container {
width: 400px;
}
.node {
cursor: pointer;
}
.node:hover {
border: #000;
border-width: 1.5px;
}
.node--leaf {
color: white;
}
.label {
font: 11px "Helvetica Neue", Helvetica, Arial, sans-serif;
text-align: center;
text-shadow: 0 1px 0 #fff, 1px 0 0 #fff, -1px 0 0 #fff, 0 -1px 0 #fff;
}
.label,
.node--root {
pointer-events: none;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="960" height="960"></svg>
(not enough room for json - refer to plunkr)
I've placed the bar chart as per my understanding. But I also want barchart to be zoomed on the focus of each node. I am not getting any idea how to do it.
Please help me if anyone having idea or solution.
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
Im unable to remove old links in my force-directed visualization with d3js, when I modify the layout. That means the old link will not be deleted and instead stay in the tag. I did follow this example and did the following:
link = link.data(links, function(d) {
return d.source.id + '-' + d.target.id;
});
link.enter().append("g").attr("class", "link");
var linkline = link.append("line").attr("class", "linkline");
var linktext = link.append("text").attr("class", "linktext")
.text(function(d) {
return d[setup.label_edge];
});
link.exit().remove();
The rest is pretty much the same as in the example. However, I cant get rid of the old links, exit().remove() won't work. They will stay on the visualization.
This is what I get when I execute then code snippet from gilsha. There are still multiple entries for linkline and linktext in the <g class="link">-tag. They are not visible and appear in the upper left corner, but I still need to get rid of them. Because when I drag the graph this becomes a problem.
When writing the link.append in a new variable, it does not duplicate the lines and labels anymore, but when calling update the lines stay in the previous position and don't move anymore.
This is the tick. Do I need to link linkGroup somehow?
force.on("tick", function(e){
linkline
.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;
});
linktext
.attr("x", function(d) {
return ((d.source.x + d.target.x)/2);
})
.attr("y", function(d) {
return ((d.source.y + d.target.y)/2);
});
node.attr("transform", function(d) {
if(setup.source == 0) {
if(d.id==0){
damper = 0.1;
d.x = d.x + ($('svg').attr('width')/2 - d.x) * (damper + 0.02) * e.alpha;
d.y = d.y + ($('svg').attr('height')/2 - d.y) * (damper + 0.02) * e.alpha;
}
var x = Math.max(radius, Math.min($('svg').attr('width') - radius, d.x));
var y = Math.max(radius, Math.min($('svg').attr('height') - radius, d.y));
return "translate(" + x + "," + y + ")";
} else {
if(d.id==0){
damper = 0.1;
d.x = d.x + ($('svg').attr('width')/2 - d.x) * (damper + 0.02) * e.alpha;
d.y = d.y + ($('svg').attr('height')/2 - d.y) * (damper + 0.02) * e.alpha;
}
var x = Math.max(radius, Math.min($('svg').attr('width') - imagesize2(d.metadata[setup.external[1]])[0], d.x));
var y = Math.max(radius, Math.min($('svg').attr('height') - imagesize2(d.metadata[setup.external[1]])[1], d.y));
return "translate(" + x + "," + y + ")";
}
});
There is no error in the part of code you added in the question. The problem will be due to some other part of the code. Verify the dataset is as expected before and after update.
EDIT: To resolve the duplication of links and texts, append links and link labels to linkGroup rather than link. Since you are using group elements for links, dynamic update of links may cause layering issues of links(Links may lay over nodes). You can resolve that issue by using insert function as shown in code and snippet.
link = link.data(links, function(d) {
return d.source.id + '-' + d.target.id;
});
/* var linkGroup = link.enter().insert("g",".node").attr("class", "link");//Use insert to resolve dom element layering issue. */
var linkGroup = link.enter().append("g").attr("class", "link");
var linkline = linkGroup.append("line")
.attr("class", "linkline");
var linktext = linkGroup.append("text")
.attr("class", "linktext")
.text(function(d, i) {
return i;
});
link.exit().remove();
var width = 960,
height = 500;
var color = d3.scale.category10();
var nodes = [],
links = [];
var force = d3.layout.force()
.nodes(nodes)
.links(links)
.charge(-400)
.linkDistance(120)
.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");
// 1. Add three nodes and three links.
setTimeout(function() {
var a = {
id: "a"
},
b = {
id: "b"
},
c = {
id: "c"
};
nodes.push(a, b, c);
links.push({
source: a,
target: b
}, {
source: a,
target: c
}, {
source: b,
target: c
});
start();
}, 0);
// 2. Remove node B and associated links.
setTimeout(function() {
nodes.splice(1, 1); // remove b
links.shift(); // remove a-b
links.pop(); // remove b-c
start();
}, 3000);
// Add node B back.
setTimeout(function() {
var a = nodes[0],
b = {
id: "b"
},
c = nodes[1];
nodes.push(b);
links.push({
source: a,
target: b
}, {
source: b,
target: c
});
start();
}, 6000);
function start() {
link = link.data(links, function(d) {
return d.source.id + '-' + d.target.id;
});
var linkGroup = link.enter().insert("g",".node").attr("class", "link"); //Use insert to resolve dom element layering issue.
//var linkGroup = link.enter().append("g").attr("class", "link");
var linkline = linkGroup.append("line")
.attr("class", "linkline");
var linktext = linkGroup.append("text")
.attr("class", "linktext")
.text(function(d, i) {
return i;
});
link.exit().remove();
node = node.data(force.nodes(), function(d) {
return d.id;
});
node.enter().append("circle").attr("class", function(d) {
return "node " + d.id;
}).attr("r", 8);
node.exit().remove();
force.start();
}
function tick() {
node.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
})
link.selectAll("line").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;
});
}
.link {
stroke: #000;
stroke-width: 1.5px;
}
.node {
fill: #000;
stroke: #fff;
stroke-width: 1.5px;
}
.node.a {
fill: #1f77b4;
}
.node.b {
fill: #ff7f0e;
}
.node.c {
fill: #2ca02c;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>