D3js forced directed graph animation and reload issue - d3.js

Below code generates a force directed graph but there are couple of problems.
Like how do I control the opening animation speed
How do I change the drag speed
And major problem every time I try to drag some element it reloads automatically.
I not sure what am I doing wrong.
var width = $(window).width(),
height = 700;
var force = d3.layout.force()
.size([width, height])
.on("tick", tick2);
var svg = d3.select("body .banner").append("svg")
.attr("width", width)
.attr("height", height);
//.on("click", explicitlyPosition);
var link = svg.selectAll(".link"),
node = svg.selectAll(".node");
function tick2() {
link
.attr("x1", function (d) {
return width * 0.5;
})
.attr("y1", function (d) {
return height * 0.5;
})
.attr("x2", function (d) {
return width * 0.5;
})
.attr("y2", function (d) {
return height * 0.5;
});
d3.selectAll("circle")
.attr("cx", function (d) {
return width * 0.5;
})
.attr("cy", function (d) {
return height * 0.5;
});
d3.selectAll("text")
.attr("x", function (d) {
return width * 0.5;
})
.attr("y", function (d) {
return height * 0.5;
});
tick();
}
function tick() {
link.transition()
.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;
});
d3.selectAll("circle").transition()
.attr("cx", function (d) {
return d.x;
})
.attr("cy", function (d) {
return d.y;
});
d3.selectAll("text").transition()
.attr("x", function (d) {
return d.x;
})
.attr("y", function (d) {
return d.y;
});
}
var graph = {
"nodes": [
{"name": "You", "val": "You", "x": width * 0.50, "y": height * 0.5, "fixed": false},
{"name": "SaaS", "val": 768, "x": width * 0.40, "y": height * 0.14, "fixed": true},
{"name": "Education", "val": 1021, "x": width * 0.65, "y": height * 0.10, "fixed": true},
{"name": "E-Commerce", "val": 1345, "x": width * 0.75, "y": height * 0.35, "fixed": true},
{"name": "Food Tech", "val": 512, "x": width * 0.70, "y": height * 0.72, "fixed": true},
{"name": "Healthcare", "val": 246, "x": width * 0.57, "y": height * 0.70, "fixed": true},
{"name": "Fashion Industry", "val": 657, "x": width * 0.30, "y": height * 0.80, "fixed": true},
{"name": "Hardware", "val": 145, "x": width * 0.30, "y": height * 0.65, "fixed": true},
{"name": "Fintech", "val": 1160, "x": width * 0.25, "y": height * 0.18, "fixed": true},
{"name": "Series A", "val": 392, "x": width * 0.85, "y": height * 0.13, "fixed": true},
{"name": "Series B", "val": 873, "x": width * 0.80, "y": height * 0.60, "fixed": true},
{"name": "2014", "val": 592, "x": width * 0.125, "y": height * 0.25, "fixed": true},
{"name": "2015", "val": 630, "x": width * 0.19, "y": height * 0.45, "fixed": true}
],
"links": [
{"source": 0, "target": 1},
{"source": 0, "target": 2},
{"source": 0, "target": 3},
{"source": 3, "target": 9},
{"source": 3, "target": 10},
{"source": 0, "target": 4},
{"source": 0, "target": 5},
{"source": 0, "target": 6},
{"source": 0, "target": 7},
{"source": 0, "target": 8},
{"source": 8, "target": 11},
{"source": 8, "target": 12}
]
};
link = link.data(graph.links)
.enter().append("line")
.attr("class", "link");
node = node.data(graph.nodes)
.enter().append("g")
.call(force.drag);
node.append("circle")
.attr("class", "node")
.attr("r", function (d) {
you_val = (d.val === "You") ? 1500 : d.val;
return ((you_val) / 30) < 15 ? 15 : ((you_val) / 30);
});
node.append("text")
.attr("x", 0)
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.attr("fill", "#9a9a9a")
.attr("font-size", "12px")
.attr("font-weight", "600")
.text(function (d) {
return d.val;
});
node.append("text")
.attr("x", 0)
.attr("dy", function (d) {
you_val = (d.val === "You") ? 1500 : d.val;
var rad = ((you_val) / 30) < 15 ? 15 : ((you_val) / 30);
return (rad + 15) + "px";
})
.attr("text-anchor", "middle")
.attr("fill", "#9a9a9a")
.attr("font-size", "12px")
.text(function (d) {
return d.name;
});
force
.nodes(graph.nodes)
.links(graph.links)
.start();

I don't understand why you have two tick functions.
How do I change the drag speed
And major problem every time I try to drag some element it reloads automatically.
Just have a single tick function like this:
function tick2() {
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;
});
d3.selectAll("circle")
.attr("cx", function (d) {
return d.x;
})
.attr("cy", function (d) {
return d.y;
});
d3.selectAll("text")
.attr("x", function (d) {
return d.x;
})
.attr("y", function (d) {
return d.y;
});
}
In your case you have two tick functions, both have a very different logic.
3) Like how do I control the opening animation speed
You have specified the node as fixed with x and y
Like: {"name": "You", "val": "You", "x": width * 0.50, "y": height * 0.5, "fixed": true}
In this case the force layout does not calculate the x and y since you have said its a fixed node which means it cannot be moved via force layout.
In case you want the layout to have animation on load to calculate its own place read this awesome tutorial
working code here

Related

d3js force directed graph arrows not showing up

I have a force directed graph using version 5 of d3.js and would like to include arrow heads for each link. I've included the code below and posted a jsfiddle. I'm seeking guidance on why the arrow heads (id=end-arrow) are not showing up in the graph. I'm referencing the end-arrow as an attribute in the declaration of link: .attr("marker-end","url(#end-arrow)"), and I don't know how to troubleshoot this.
HTML:
<svg id="viz"></svg>
Javascript with d3.js version 5:
// based on https://bl.ocks.org/mapio/53fed7d84cd1812d6a6639ed7aa83868
var width = 600;
var height = 400;
var border = 1;
var bordercolor="black";
var color = d3.scaleOrdinal(d3.schemeCategory10); // coloring of nodes
var graph = {
"nodes": [
{"id": "4718871", "group": 2, "img": "https://derivationmap.net/static/multiplybothsidesby.png", "width": 298, "height": 30, "linear index": 2},
{"id": "2131616531", "group": 0, "img": "https://derivationmap.net/static/2131616531.png", "width": 103, "height": 30, "linear index": 0},
{"id": "9565166889", "group": 0, "img": "https://derivationmap.net/static/9565166889.png", "width": 24, "height": 23, "linear index": 0},
{"id": "9040079362", "group": 0, "img": "https://derivationmap.net/static/9040079362.png", "width": 18, "height": 30, "linear index": 0},
{"id": "9278347", "group": 1, "img": "https://derivationmap.net/static/declareinitialexpr.png", "width": 270, "height": 30, "linear index": 1},
{"id": "6286448", "group": 4, "img": "https://derivationmap.net/static/declarefinalexpr.png", "width": 255, "height": 30, "linear index": 4},
{"id": "2113211456", "group": 0, "img": "https://derivationmap.net/static/2113211456.png", "width": 121, "height": 34, "linear index": 0},
{"id": "2169431", "group": 3, "img": "https://derivationmap.net/static/dividebothsidesby.png", "width": 260, "height": 30, "linear index": 3},
{"id": "3131111133", "group": 0, "img": "https://derivationmap.net/static/3131111133.png", "width": 121, "height": 34, "linear index": 0}
],
"links": [
{"source": "2169431", "target": "2113211456", "value": 1},
{"source": "2113211456", "target": "6286448", "value": 1},
{"source": "9278347", "target": "3131111133", "value": 1},
{"source": "4718871", "target": "2131616531", "value": 1},
{"source": "9040079362", "target": "4718871", "value": 1},
{"source": "2131616531", "target": "2169431", "value": 1},
{"source": "3131111133", "target": "4718871", "value": 1},
{"source": "9565166889", "target": "2169431", "value": 1}
]
};
var label = {
"nodes": [],
"links": []
};
graph.nodes.forEach(function(d, i) {
label.nodes.push({node: d});
label.nodes.push({node: d});
label.links.push({
source: i * 2,
target: i * 2 + 1
});
});
var labelLayout = d3.forceSimulation(label.nodes)
.force("charge", d3.forceManyBody().strength(-50))
.force("link", d3.forceLink(label.links).distance(0).strength(2));
var graphLayout = d3.forceSimulation(graph.nodes)
.force("charge", d3.forceManyBody().strength(-3000))
.force("center", d3.forceCenter(width / 2, height / 2))
.force("x", d3.forceX(width / 2).strength(1))
.force("y", d3.forceY(height / 2).strength(1))
.force("link", d3.forceLink(graph.links).id(function(d) {return d.id; }).distance(50).strength(1))
.on("tick", ticked);
var adjlist = [];
graph.links.forEach(function(d) {
adjlist[d.source.index + "-" + d.target.index] = true;
adjlist[d.target.index + "-" + d.source.index] = true;
});
function neigh(a, b) {
return a == b || adjlist[a + "-" + b];
}
var svg = d3.select("#viz").attr("width", width).attr("height", height);
// define arrow markers for graph links
svg.append("svg:defs").append("svg:marker")
.attr("id", "end-arrow")
.attr("viewBox", "0 -5 10 10")
.attr("refX", 6)
.attr("markerWidth", 3)
.attr("markerHeight", 3)
.attr("orient", "auto")
.append("svg:line")
.attr("d", "M0,-5L10,0L0,5")
.attr("fill", "black");
// http://bl.ocks.org/AndrewStaroscik/5222370
var borderPath = svg.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("height", height)
.attr("width", width)
.style("stroke", bordercolor)
.style("fill", "none")
.style("stroke-width", border);
var container = svg.append("g");
svg.call(
d3.zoom()
.scaleExtent([.1, 4])
.on("zoom", function() { container.attr("transform", d3.event.transform); })
);
var link = container.append("g").attr("class", "links")
.selectAll("line")
.data(graph.links)
.enter()
.append("line")
.attr("stroke", "#aaa")
.attr("stroke-width", "1px")
.attr("marker-end","url(#end-arrow)");
var node = container.append("g").attr("class", "nodes")
.selectAll("g")
.data(graph.nodes)
.enter()
.append("circle")
.attr("r", 5)
.attr("fill", function(d) { return color(d.group); })
node.on("mouseover", focus).on("mouseout", unfocus);
node.call(
d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended)
);
var labelNode = container.append("g").attr("class", "labelNodes")
.selectAll("text")
.data(label.nodes)
.enter()
.append("image")
// alternative option, unverified: https://stackoverflow.com/questions/39908583/d3-js-labeling-nodes-with-image-in-force-layout
// I have no idea why the i%2 is needed; without it I get two images per node
// switching between i%2==1 and i%2==0 produces different image locations (?)
.attr("xlink:href", function(d, i) { return i % 2 == 1 ? "" : d.node.img; } )
.attr("x", 0)
.attr("y", 0)
// the following alter the image size
.attr("width", function(d, i) { return d.node.width/2; })
.attr("height", function(d, i) { return d.node.height/2; })
// .append("text")
// .text(function(d, i) { return i % 2 == 0 ? "" : d.node.id; })
// .style("fill", "#555")
// .style("font-family", "Arial")
// .style("font-size", 12)
.style("pointer-events", "none"); // to prevent mouseover/drag capture
node.on("mouseover", focus).on("mouseout", unfocus);
function ticked() {
node.call(updateNode);
link.call(updateLink);
labelLayout.alphaTarget(0.3).restart();
labelNode.each(function(d, i) {
if(i % 2 == 0) {
d.x = d.node.x;
d.y = d.node.y;
} else {
var b = this.getBBox();
var diffX = d.x - d.node.x;
var diffY = d.y - d.node.y;
var dist = Math.sqrt(diffX * diffX + diffY * diffY);
var shiftX = b.width * (diffX - dist) / (dist * 2);
shiftX = Math.max(-b.width, Math.min(0, shiftX));
var shiftY = 16;
this.setAttribute("transform", "translate(" + shiftX + "," + shiftY + ")");
}
});
labelNode.call(updateNode);
}
function fixna(x) {
if (isFinite(x)) return x;
return 0;
}
function focus(d) {
var index = d3.select(d3.event.target).datum().index;
node.style("opacity", function(o) {
return neigh(index, o.index) ? 1 : 0.1;
});
labelNode.attr("display", function(o) {
return neigh(index, o.node.index) ? "block": "none";
});
link.style("opacity", function(o) {
return o.source.index == index || o.target.index == index ? 1 : 0.1;
});
}
function unfocus() {
labelNode.attr("display", "block");
node.style("opacity", 1);
link.style("opacity", 1);
}
function updateLink(link) {
link.attr("x1", function(d) { return fixna(d.source.x); })
.attr("y1", function(d) { return fixna(d.source.y); })
.attr("x2", function(d) { return fixna(d.target.x); })
.attr("y2", function(d) { return fixna(d.target.y); });
}
function updateNode(node) {
node.attr("transform", function(d) {
return "translate(" + fixna(d.x) + "," + fixna(d.y) + ")";
});
}
function dragstarted(d) {
d3.event.sourceEvent.stopPropagation();
if (!d3.event.active) graphLayout.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) graphLayout.alphaTarget(0);
d.fx = null;
d.fy = null;
}
Based on feedback in the d3js Slack channel, there were two issues:
In the definition of the arrow, needed .append("svg:path")
With that fixed, the arrows were too small and were hidden behind the node circles. By making the arrows larger, they were visible.
I've updated http://bl.ocks.org/bhpayne/0a8ef2ae6d79aa185dcf2c3a385daf25 and the revised code is below:
HTML
<svg id="viz"></svg>
Javascript + d3js
// based on https://bl.ocks.org/mapio/53fed7d84cd1812d6a6639ed7aa83868
var width = 600;
var height = 400;
var border = 3;
var bordercolor = "black";
var color = d3.scaleOrdinal(d3.schemeCategory10); // coloring of nodes
var graph = {
"nodes": [{
"id": "4718871",
"group": 2,
"img": "https://derivationmap.net/static/multiplybothsidesby.png",
"width": 298,
"height": 30,
"linear index": 2
},
{
"id": "2131616531",
"group": 0,
"img": "https://derivationmap.net/static/2131616531.png",
"width": 103,
"height": 30,
"linear index": 0
},
{
"id": "9565166889",
"group": 0,
"img": "https://derivationmap.net/static/9565166889.png",
"width": 24,
"height": 23,
"linear index": 0
},
{
"id": "9040079362",
"group": 0,
"img": "https://derivationmap.net/static/9040079362.png",
"width": 18,
"height": 30,
"linear index": 0
},
{
"id": "9278347",
"group": 1,
"img": "https://derivationmap.net/static/declareinitialexpr.png",
"width": 270,
"height": 30,
"linear index": 1
},
{
"id": "6286448",
"group": 4,
"img": "https://derivationmap.net/static/declarefinalexpr.png",
"width": 255,
"height": 30,
"linear index": 4
},
{
"id": "2113211456",
"group": 0,
"img": "https://derivationmap.net/static/2113211456.png",
"width": 121,
"height": 34,
"linear index": 0
},
{
"id": "2169431",
"group": 3,
"img": "https://derivationmap.net/static/dividebothsidesby.png",
"width": 260,
"height": 30,
"linear index": 3
},
{
"id": "3131111133",
"group": 0,
"img": "https://derivationmap.net/static/3131111133.png",
"width": 121,
"height": 34,
"linear index": 0
}
],
"links": [{
"source": "2169431",
"target": "2113211456",
"value": 1
},
{
"source": "2113211456",
"target": "6286448",
"value": 1
},
{
"source": "9278347",
"target": "3131111133",
"value": 1
},
{
"source": "4718871",
"target": "2131616531",
"value": 1
},
{
"source": "9040079362",
"target": "4718871",
"value": 1
},
{
"source": "2131616531",
"target": "2169431",
"value": 1
},
{
"source": "3131111133",
"target": "4718871",
"value": 1
},
{
"source": "9565166889",
"target": "2169431",
"value": 1
}
]
};
var label = {
"nodes": [],
"links": []
};
graph.nodes.forEach(function(d, i) {
label.nodes.push({
node: d
});
label.nodes.push({
node: d
});
label.links.push({
source: i * 2,
target: i * 2 + 1
});
});
var labelLayout = d3.forceSimulation(label.nodes)
.force("charge", d3.forceManyBody().strength(-50))
.force("link", d3.forceLink(label.links).distance(0).strength(2));
var graphLayout = d3.forceSimulation(graph.nodes)
.force("charge", d3.forceManyBody().strength(-3000))
.force("center", d3.forceCenter(width / 2, height / 2))
.force("x", d3.forceX(width / 2).strength(1))
.force("y", d3.forceY(height / 2).strength(1))
.force("link", d3.forceLink(graph.links).id(function(d) {
return d.id;
}).distance(50).strength(1))
.on("tick", ticked);
var adjlist = [];
graph.links.forEach(function(d) {
adjlist[d.source.index + "-" + d.target.index] = true;
adjlist[d.target.index + "-" + d.source.index] = true;
});
function neigh(a, b) {
return a == b || adjlist[a + "-" + b];
}
var svg = d3.select("#viz").attr("width", width).attr("height", height);
// define arrow markers for graph links
svg.append("svg:defs").append("svg:marker")
.attr("id", "end-arrow")
.attr("viewBox", "0 -5 10 10")
.attr("refX", 10)
.attr("markerWidth", 20)
.attr("markerHeight", 20)
.attr("orient", "auto")
.append("svg:path")
.attr("d", "M0,-5L20,0L0,5")
.attr("fill", "#000");
// http://bl.ocks.org/AndrewStaroscik/5222370
var borderPath = svg.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("height", height)
.attr("width", width)
.style("stroke", bordercolor)
.style("fill", "none")
.style("stroke-width", border);
var container = svg.append("g");
svg.call(
d3.zoom()
.scaleExtent([.1, 4])
.on("zoom", function() {
container.attr("transform", d3.event.transform);
})
);
var link = container.append("g").attr("class", "links")
.selectAll("line")
.data(graph.links)
.enter()
.append("line")
.attr("stroke", "#aaa")
.attr("marker-end", "url(#end-arrow)")
.attr("stroke-width", "1px");
var node = container.append("g").attr("class", "nodes")
.selectAll("g")
.data(graph.nodes)
.enter()
.append("circle")
.attr("r", 10)
.attr("fill", function(d) {
return color(d.group);
})
node.on("mouseover", focus).on("mouseout", unfocus);
node.call(
d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended)
);
var labelNode = container.append("g").attr("class", "labelNodes")
.selectAll("text")
.data(label.nodes)
.enter()
.append("image")
// alternative option, unverified: https://stackoverflow.com/questions/39908583/d3-js-labeling-nodes-with-image-in-force-layout
// I have no idea why the i%2 is needed; without it I get two images per node
// switching between i%2==1 and i%2==0 produces different image locations (?)
.attr("xlink:href", function(d, i) {
return i % 2 == 1 ? "" : d.node.img;
})
.attr("x", 0)
.attr("y", 0)
// the following alter the image size
.attr("width", function(d, i) {
return d.node.width / 2;
})
.attr("height", function(d, i) {
return d.node.height / 2;
})
// .append("text")
// .text(function(d, i) { return i % 2 == 0 ? "" : d.node.id; })
// .style("fill", "#555")
// .style("font-family", "Arial")
// .style("font-size", 12)
.style("pointer-events", "none"); // to prevent mouseover/drag capture
node.on("mouseover", focus).on("mouseout", unfocus);
function ticked() {
node.call(updateNode);
link.call(updateLink);
labelLayout.alphaTarget(0.3).restart();
labelNode.each(function(d, i) {
if (i % 2 == 0) {
d.x = d.node.x;
d.y = d.node.y;
} else {
var b = this.getBBox();
var diffX = d.x - d.node.x;
var diffY = d.y - d.node.y;
var dist = Math.sqrt(diffX * diffX + diffY * diffY);
var shiftX = b.width * (diffX - dist) / (dist * 2);
shiftX = Math.max(-b.width, Math.min(0, shiftX));
var shiftY = 16;
this.setAttribute("transform", "translate(" + shiftX + "," + shiftY + ")");
}
});
labelNode.call(updateNode);
}
function fixna(x) {
if (isFinite(x)) return x;
return 0;
}
function focus(d) {
var index = d3.select(d3.event.target).datum().index;
node.style("opacity", function(o) {
return neigh(index, o.index) ? 1 : 0.1;
});
labelNode.attr("display", function(o) {
return neigh(index, o.node.index) ? "block" : "none";
});
link.style("opacity", function(o) {
return o.source.index == index || o.target.index == index ? 1 : 0.1;
});
}
function unfocus() {
labelNode.attr("display", "block");
node.style("opacity", 1);
link.style("opacity", 1);
}
function updateLink(link) {
link.attr("x1", function(d) {
return fixna(d.source.x);
})
.attr("y1", function(d) {
return fixna(d.source.y);
})
.attr("x2", function(d) {
return fixna(d.target.x);
})
.attr("y2", function(d) {
return fixna(d.target.y);
});
}
function updateNode(node) {
node.attr("transform", function(d) {
return "translate(" + fixna(d.x) + "," + fixna(d.y) + ")";
});
}
function dragstarted(d) {
d3.event.sourceEvent.stopPropagation();
if (!d3.event.active) graphLayout.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) graphLayout.alphaTarget(0);
d.fx = null;
d.fy = null;
}

Inserting nodes locations for network graph D3

I am new to D3 and I am trying to solve a homework.
I am trying plot a network graph by drawing the nodes from this dataset(sample).
{ "nodes": [
{
"id": "site09",
"x": 317.5,
"y": 282.5
},
{
"id": "site01",
"x": 112,
"y": 47
},
{
"id": "site03",
"x": 69.5,
"y": 287
},
{
"id": "site04",
"x": 424.5,
"y": 99.5
} ]
"links": [
{"node01": "site05", "node02": "site08", "amount": 10},
{"node01": "site05", "node02": "site02", "amount": 120},
{"node01": "site05", "node02": "site03", "amount": 50},
I want to plot the location of the circles using x and y from the dataset.
I tried the following but the circles/nodes are not showing.
//importing the json data
d3.json("project.json", function(data) {
console.log(data);
svgCanvas.selectAll("circle")
.data(data).enter() // create place hodlers if the data are new .append("circle") // create one circle for each
.append("svg:circle")
.attr("cx", function(data){
return data.x;})
.attr("cy", function(data){
return data.y;})
.attr("r", function(thisElement, index){
// use the value from data to create the radius
return thisElement["amount"]})
vis.selectAll("circle.nodes")
.data(nodes)
.enter()
.append("svg:circle")
.attr("class", "nodes")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("r", "10px")
.attr("fill", "black")
})
}
Thank you,

d3 connect visibility attribute to slider position

hey guys i am currently trying to change the visibility of 4 lines depending on whether the slider position is at the same "year-tick" as i have in my data.
for example: i would want line 3 which has the value "1994" stored to only be visible as long as the slider is on position "1994"
i havent workder with sliders yet, which is why i am having this much trouble getting this to work.
here is a fiddle of my code: https://fiddle.jshell.net/42jdw2Lt/3/
<!DOCTYPE html>
<meta charset="utf-8">
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.2.2/d3.min.js">
</script>
<script src="http://d3js.org/topojson.v1.min.js"></script>
<script src="https://d3js.org/d3-selection-multi.v1.min.js"></script>
<script>
var width = 1500;
var height = 1500;
var margin = {top: 50, left: 50, right: 50, bottom:50},
height = 650-margin.top-margin.bottom,
width = 1200-margin.left-margin.right;
var svg = d3.select("body").append("svg")
.attr("width", width+margin.left+margin.right)
.attr("height", height+margin.top+margin.bottom)
.append("g")
.attr("transform", "translate("+margin.left+","+margin.top+")")
var strwi = d3.scaleLinear()
.domain([100, 400])
.range([7,35])
var group = svg.append("g")
var series = [
[{"x": 360, "y": 250, "num": 100}, {"x": 520, "y": 400, "num": 100}, {"x":
630, "y": 300, "num": 100, "year": 1991}],
[{"x": 71, "y": 45, "num": 200}, {"x": 32, "y": 39, "num": 200}, {"x": 43,
"y": 70, "num": 200, "year": 1992}],
[{"x": 100, "y": 300, "num": 300}, {"x": 200, "y": 200, "num": 300}, {"x":
300, "y": 200, "num": 300, "year": 1994}],
[{"x": 101, "y": 202, "num": 400}, {"x": 102, "y": 204, "num": 400}, {"x":
103, "y": 215, "num": 400, "year": 1995}]
];
var line = d3.line()
.curve(d3.curveBasis)
.x(function(d) { return d.x; })
.y(function(d) { return d.y; });
group.selectAll(".line")
.data(series)
.enter().append("path")
.attr("class", "line")
// .attr("visibility", "hidden")
.attr("stroke-width", function(d) {return strwi(d); })
.attr("stroke", "black")
.attr("fill", "none")
.attr("d", line);
var data = [1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999,
2000, 2001, 2002]
var x = d3.scaleLinear()
.domain(d3.extent(data))
.range([0, width])
.clamp(true);
var slider = svg.append("g")
.attr("class", "slider")
.attr("transform", "translate(" + 0 + "," + 300 + ")");
slider.append("line")
.attr("class", "track")
.attr("x1", x.range()[0])
.attr("x2", x.range()[1])
.attr("stroke", "black")
.attr("stroke-width", "4")
.select(function() { return
this.parentNode.appendChild(this.cloneNode(true)); })
.attr("class", "track-inset")
.select(function() { return
this.parentNode.appendChild(this.cloneNode(true)); })
.attr("class", "track-overlay")
.call(d3.drag()
.on("start.interrupt", function() { slider.interrupt(); })
.on("start drag", function() { hue(x.invert(d3.event.x));
}));
slider.insert("g", ".track-overlay")
.attr("class", "ticks")
.selectAll("ticks")
.data(x.ticks(data.length))
.enter().append("text")
.attr("x", x)
.attr("text-anchor", "middle")
.attr("transform", "translate(0," + 30 + ")")
.text(function(d) { return d; })
.exit()
.data(x.ticks(data.length * 2))
.enter().append("circle")
.attr("cx", x)
.attr("r", 3)
.attr("fill", "#c1c7cd");
slider.insert("g", ".track-overlay")
.attr("class", "ticks--cirlces")
.selectAll("ticks--ticks");
var handle = slider.insert("circle", ".track-overlay")
.attr("class", "handle")
.attr("r", 9);
slider.transition() // Gratuitous intro!
.duration(750)
.tween("hue", function() {
var i = d3.interpolate(0, 70);
return function(t) { hue(i(t)); };
});
function hue(h) {
handle.attr("cx", x(h));
d3.select(".text")
.text( (Math.round(h*2)/2).toFixed(1) );
}
</script>
I added the prop year to all your elements in the data selecting all the line paths in a variable called paths so that I can change the opacity when you move your slider like this
var paths = group.selectAll(".line")
.data(series)
.enter().append("path")
.attr("class", "line")
// .attr("visibility", "hidden")
.attr("stroke-width", function(d) {
return strwi(d);
})
.attr("stroke", "black")
.attr("fill", "none")
.attr("d", line);
and then in your hue function, I added this code to change the opacity based on your slider
paths.attr('opacity', function(d) {
if (((Math.round(h * 2) / 2).toFixed(1)) > d[2]['year']) {
return 0;
} else {
return 1;
}
})
Here's a fiddle for you:
var width = 1500;
var height = 1500;
var margin = {
top: 50,
left: 50,
right: 50,
bottom: 50
},
height = 650 - margin.top - margin.bottom,
width = 1200 - margin.left - margin.right;
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
var strwi = d3.scaleLinear()
.domain([100, 400])
.range([7, 35])
var group = svg.append("g")
var series = [
[{
"x": 360,
"y": 250,
"num": 100,
"startYear": 1991,
"endYear": 1995
}, {
"x": 520,
"y": 400,
"num": 100,
"startYear": 1991,
"endYear": 1995
}, {
"x": 630,
"y": 300,
"num": 100,
"startYear": 1991,
"endYear": 1995
}],
[{
"x": 71,
"y": 45,
"num": 200,
"startYear": 1992,
"endYear": 1993
}, {
"x": 32,
"y": 39,
"num": 200,
"startYear": 1992,
"endYear": 1993
}, {
"x": 43,
"y": 70,
"num": 200,
"startYear": 1992,
"endYear": 1993
}],
[{
"x": 100,
"y": 300,
"num": 300,
"startYear": 1994,
"endYear": 1996
}, {
"x": 200,
"y": 200,
"num": 300,
"startYear": 1994,
"endYear": 1996
}, {
"x": 300,
"y": 200,
"num": 300,
"startYear": 1994,
"endYear": 1996
}],
[{
"x": 101,
"y": 202,
"num": 400,
"startYear": 1995,
"endYear": 1997
}, {
"x": 102,
"y": 204,
"num": 400,
"startYear": 1995,
"endYear": 1997
}, {
"x": 103,
"y": 215,
"num": 400,
"startYear": 1995,
"endYear": 1997
}]
];
var line = d3.line()
.curve(d3.curveBasis)
.x(function(d) {
return d.x;
})
.y(function(d) {
return d.y;
});
var paths = group.selectAll(".line")
.data(series)
.enter().append("path")
.attr("class", "line")
.attr('v1',function(d){
return d[2]['startYear'];
})
.attr('v2',function(d) {
return d[2]['endYear'];
})
.attr('opacity',0)
//.attr("visibility", "hidden")
.attr("stroke-width", function(d) {
return strwi(d);
})
.attr("stroke", "black")
.attr("fill", "none")
.attr("d", line);
var data = [1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002]
var x = d3.scaleLinear()
.domain(d3.extent(data))
.range([0, width])
.clamp(true);
var slider = svg.append("g")
.attr("class", "slider")
.attr("transform", "translate(" + 0 + "," + 300 + ")");
slider.insert("g", ".track-overlay")
.attr("class", "ticks")
.selectAll("ticks")
.data(x.ticks(data.length))
.enter().append("text")
.attr("x", x)
.attr("text-anchor", "middle")
.attr("transform", "translate(0," + 30 + ")")
.text(function(d) {
return d;
})
.exit()
.data(x.ticks(data.length * 2))
.enter().append("circle")
.attr("cx", x)
.attr("r", 3)
.attr("fill", "#c1c7cd");
slider.append("line")
.attr("class", "track")
.attr("x1", x.range()[0])
.attr("x2", x.range()[1])
.attr("stroke", "black")
.attr("stroke-width", "4")
.select(function() {
return this.parentNode.appendChild(this.cloneNode(true));
})
.attr("class", "track-inset")
.select(function() {
return this.parentNode.appendChild(this.cloneNode(true));
})
.attr("class", "track-overlay")
.call(d3.drag()
.on("start.interrupt", function() {
slider.interrupt();
})
.on("start drag", function() {
//console.log(d3.event.x)
hue(x.invert(d3.event.x));
}));
slider.insert("g", ".track-overlay")
.attr("class", "ticks--cirlces")
.selectAll("ticks--ticks");
var handle = slider.insert("circle", ".track-overlay")
.attr("class", "handle")
.attr("r", 9);
slider.transition() // Gratuitous intro!
.duration(750)
.tween("hue", function() {
var i = d3.interpolate(0, 70);
return function(t) {
//nsole.log(t)
hue(i(t));
};
});
function hue(h) {
handle.attr("cx", x(h));
d3.select(".text")
.text((Math.round(h * 2) / 2).toFixed(1));
paths
.transition().duration(300)
.attr('opacity', function(d) {
if (((Math.round(h * 2) / 2).toFixed(1)) >= d[2]['startYear'] && ((Math.round(h * 2) / 2).toFixed(1)) <= d[2]['endYear']) {
return 1;
} else {
return 0;
}
})
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.2.2/d3.min.js"></script>
<script src="https://d3js.org/topojson.v1.min.js"></script>
<script src="https://d3js.org/d3-selection-multi.v1.min.js"></script>
Let me know if this is what you wanted or there's something else.

How to create d3.js Collapsible force layout with non tree data?

I have a d3 force directed layout with data in a similar structure below. Is it possible to apply collapsible force layout such as http://bl.ocks.org/mbostock/1062288 to it? I want a node to be collapsed /expanded on click.
{
"nodes": [
{"x": 469, "y": 410},
{"x": 493, "y": 364},
{"x": 442, "y": 365},
{"x": 467, "y": 314},
],
"links": [
{"source": 0, "target": 1},
{"source": 1, "target": 2},
{"source": 2, "target": 0},
{"source": 1, "target": 3},
{"source": 3, "target": 2},
]
}
If I understand correctly, perhaps this is what you are looking for. I edited the demo you linked to. Now when a source node is collapsed, we iterate over all edges and look for other nodes it has edges to.
for each target node that the source node has an edge to, we increment it's collapsing count. If a node has a collapsing count of greater than zero, it is not displayed.
When we uncollapse a node, we do the same thing, except we decrement from the collapsing count.
We need this collapsing count, since, as we are not in a tree, nodes can have more than one node which should cause them to collapse.
I made this work for directed graphs, though I'm not sure that's what you wanted.
Let me know what you think!
The json I used:
{
"nodes": [
{"x": 469, "y": 410},
{"x": 493, "y": 364},
{"x": 442, "y": 365},
{"x": 467, "y": 314}
],
"links": [
{"source": 0, "target": 1},
{"source": 1, "target": 2},
{"source": 2, "target": 0},
{"source": 1, "target": 3},
{"source": 3, "target": 2}
]
}
Modified tutorial code:
<!DOCTYPE html>
<meta charset="utf-8">
<title>Force-Directed Graph</title>
<style>
.node {
cursor: pointer;
stroke: #3182bd;
stroke-width: 1.5px;
}
.link {
fill: none;
stroke: #9ecae1;
stroke-width: 1.5px;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var width = 960,
height = 500,
root;
var force = d3.layout.force()
.size([width, height])
.on("tick", tick);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
//Added markers to indicate that this is a directed graph
svg.append("defs").selectAll("marker")
.data(["arrow"])
.enter().append("marker")
.attr("id", function(d) { return d; })
.attr("viewBox", "0 -5 10 10")
.attr("refX", 15)
.attr("refY", -1.5)
.attr("markerWidth", 4)
.attr("markerHeight", 4)
.attr("orient", "auto")
.append("path")
.attr("d", "M0,-5L10,0L0,5");
var link = svg.selectAll(".link"),
node = svg.selectAll(".node");
d3.json("graph.json", function(json) {
root = json;
//Give nodes ids and initialize variables
for(var i=0; i<root.nodes.length; i++) {
var node = root.nodes[i];
node.id = i;
node.collapsing = 0;
node.collapsed = false;
}
//Give links ids and initialize variables
for(var i=0; i<root.links.length; i++) {
var link = root.links[i];
link.source = root.nodes[link.source];
link.target = root.nodes[link.target];
link.id = i;
}
update();
});
function update() {
//Keep only the visible nodes
var nodes = root.nodes.filter(function(d) {
return d.collapsing == 0;
});
var links = root.links;
//Keep only the visible links
links = root.links.filter(function(d) {
return d.source.collapsing == 0 && d.target.collapsing == 0;
});
force
.nodes(nodes)
.links(links)
.start();
// Update the links…
link = link.data(links, function(d) { return d.id; });
// Exit any old links.
link.exit().remove();
// Enter any new links.
link.enter().insert("line", ".node")
.attr("class", "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; })
.attr("marker-end", "url(#arrow)");
// Update the nodes…
node = node.data(nodes, function(d){ return d.id; }).style("fill", color);
// Exit any old nodes.
node.exit().remove();
// Enter any new nodes.
node.enter().append("circle")
.attr("class", "node")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("r", function(d) { return Math.sqrt(d.size) / 10 || 4.5; })
.style("fill", color)
.on("click", click)
.call(force.drag);
}
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("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
}
// Color leaf nodes orange, and packages white or blue.
function color(d) {
return d.collapsed ? "#3182bd" : d.children ? "#c6dbef" : "#fd8d3c";
}
// Toggle children on click.
function click(d) {
if (!d3.event.defaultPrevented) {
//check if link is from this node, and if so, collapse
root.links.forEach(function(l) {
if(l.source.id == d.id) {
if(d.collapsed){
l.target.collapsing--;
} else {
l.target.collapsing++;
}
}
});
d.collapsed = !d.collapsed;
}
update();
}
</script>
Try this:
var width = 960,height = 500;
var force = d3.layout.force().size([width, height]).charge(-400)
.linkDistance(40)
.on("tick", tick);
var drag = force.drag().on("dragstart", dragstart);
var svg = d3.select("body").append("svg").attr("width", width)
.attr("height", height);
var link = svg.selectAll(".link"),
node = svg.selectAll(".node");
d3.json("graph.json", function(error, graph) {
force.nodes(graph.nodes).links(graph.links)
.start();
link = link.data(graph.links).enter().append("line")
.attr("class", "link");
node = node.data(graph.nodes)
.enter().append("circle")
.attr("class", "node")
.attr("r", 12)
.call(drag);
});
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("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
}
function dragstart(d) {
d3.select(this).classed("fixed", d.fixed = true);
}
You should use json file like this:
graph.json
{
"nodes": [
{"x": 469, "y": 410},
{"x": 493, "y": 364},
{"x": 442, "y": 365},
{"x": 467, "y": 314},
],
"links": [
{"source": 0, "target": 1},
{"source": 1, "target": 2},
{"source": 2, "target": 0},
{"source": 1, "target": 3},
{"source": 3, "target": 2},
]
}

Binding "mouseover" events to points on a line in d3.js

I would like to get the coordinates of a point on a line by clicking on the line using the following code:
var lineData = [ { "x": 1, "y": 5}, { "x": 20, "y": 20},
{ "x": 40, "y": 10}, { "x": 60, "y": 40},
{ "x": 80, "y": 5}, { "x": 100, "y": 60}];
var lineFunction = d3.svg.line()
.x(function(d) { return d.x; })
.y(function(d) { return d.y; })
.interpolate("linear");
var svgContainer = d3.select("body").append("svg")
.attr("width", 200)
.attr("height", 200);
var lineGraph = svgContainer.append("path")
.data([lineData]).attr("d", lineFunction)
//.attr("d", lineFunction(lineData))
.attr("stroke", "blue")
.attr("stroke-width", 2)
.attr("fill", "none")
.on('mousedown', function(d) {
console.log({"x":d.x, "y":d.y})
});
(I updated the code to address the comments, but I still get "Object {x: undefined, y: undefined}")
I keep getting an "undefined" when clicking on the line. Am I missing a step?
You can get the coordinates of an event using d3.event:
.on("mousedown", function() {
console.log({"x": d3.event.x, "y": d3.event.y});
});
use mouse event
.on('mousedown', function(d) {
var m = d3.mouse(this);
console.log("x:"+m[0]+" y:"+m[1]);
});
in your function m[0] and m[1] gives you X and Y.

Resources