This code works perfectly with a JSON file where the source and index are in the form of indices. However, when I switch to a format with the source and target as strings, it throws up TypeError: e[u.source.index] is undefined. How do I overcome this?
<!DOCTYPE html>
<meta charset="utf-8">
<body>
<script src="https://d3js.org/d3.v3.min.js"></script>
<script>
var width = 960,
height = 500,
active = d3.select(null);
var zoom = d3.behavior.zoom()
.scaleExtent([1, 8])
.on("zoom", zoomed);
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)
// .on("click", reset);
var g = svg.append("g");
var link = g.selectAll(".link"),
node = g.selectAll(".node");
svg
.call(zoom) // delete this line to disable free zooming
.call(zoom.event);
d3.json("data/miserables.json", function(error, graph) {
if (error) throw error;
force
.nodes(graph.nodes)
.links(graph.links)
.start();
link = link.data(graph.links)
.enter().append("line")
.attr("class", "links")
.style("stroke", "#999");
node = node.data(graph.nodes)
.enter().append("circle")
.attr("class", "node")
.attr("r", 12)
.on("click", clicked)
//.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 clicked(d){
if (active.node() === this) return reset();
active.classed("active", false);
active = d3.select(this).classed("active", true);
var bbox = active.node().getBBox(),
bounds = [[bbox.x, bbox.y],[bbox.x + bbox.width, bbox.y + bbox.height]];
var dx = bounds[1][0] - bounds[0][0],
dy = bounds[1][1] - bounds[0][1],
x = (bounds[0][0] + bounds[1][0]) / 2,
y = (bounds[0][1] + bounds[1][1]) / 2,
scale = Math.max(1, Math.min(8, 0.9 / Math.max(dx / width, dy / height))),
translate = [width / 2 - scale * x, height / 2 - scale * y];
svg.transition()
.duration(750)
.call(zoom.translate(translate).scale(scale).event);
}
function reset() {
active.classed("active", false);
active = d3.select(null);
svg.transition()
.duration(750)
.call(zoom.translate([0, 0]).scale(1).event);
}
function dragstart(d) {
d3.select(this).classed("fixed", d.fixed = true);
}
function zoomed() {
console.log(d3.event)
g.style("stroke-width", 1.5 / d3.event.scale + "px");
g.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}
</script>
You're using D3 v3.x. While linking by name is an easy task in D3 v4.x, it seems that it's not possible in D3 v3.x. See this issue in D3 v3.x, and this explanation in the API:
Note: the values of the source and target attributes may be initially specified as indexes into the nodes array; these will be replaced by references after the call to start.
Thus, the snippet below won't work (the code is not mine, I just found it online and changed the links array from indices to names):
var nodes = [{
name: "node1"
}, {
name: "node2"
}, {
name: "node3"
}, {
name: "node4"
}, {
name: "node5"
}, {
name: "node6"
}, {
name: "node7"
}];
var edges = [{
source: "node1",
target: "node3"
}, {
source: "node1",
target: "node2"
}, {
source: "node1",
target: "node4"
}, {
source: "node2",
target: "node3"
}, {
source: "node2",
target: "node5"
}, {
source: "node2",
target: "node6"
}, {
source: "node3",
target: "node"
}];
var width = 400;
var height = 400;
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
var force = d3.layout.force()
.nodes(nodes)
.links(edges)
.size([width, height])
.linkDistance(150)
.charge(-400);
force.start();
var svg_edges = svg.selectAll("line")
.data(edges)
.enter()
.append("line")
.style("stroke", "#ccc")
.style("stroke-width", 1);
var color = d3.scale.category20();
var svg_nodes = svg.selectAll("circle")
.data(nodes)
.enter()
.append("circle")
.attr("r", 20)
.style("fill", function(d, i) {
return color(i);
})
.call(force.drag);
var svg_texts = svg.selectAll("text")
.data(nodes)
.enter()
.append("text")
.style("fill", "black")
.attr("dx", 20)
.attr("dy", 8)
.text(function(d) {
return d.name;
});
force.on("tick", function() {
svg_edges.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;
});
svg_nodes.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
});
svg_texts.attr("x", function(d) {
return d.x;
})
.attr("y", function(d) {
return d.y;
});
});
<script src="https://d3js.org/d3.v4.min.js"></script>
If you click "run snippet", you'll only see an error:
Uncaught TypeError: Cannot read property 'force' of undefined
Solution: Keep your links array with indices. However, if you already have/receive an array with names, you can change it to indices:
var nodeByName = d3.map(nodes, function(d) {
return d.name;
});
edges.forEach(function(d) {
d.source = nodeByName.get(d.source);
d.target = nodeByName.get(d.target);
});
Here is the same code of the first snippet with the above-mentioned changes. Now it works:
var nodes = [{
name: "node1"
}, {
name: "node2"
}, {
name: "node3"
}, {
name: "node4"
}, {
name: "node5"
}, {
name: "node6"
}, {
name: "node7"
}];
var edges = [{
source: "node1",
target: "node3"
}, {
source: "node1",
target: "node2"
}, {
source: "node1",
target: "node4"
}, {
source: "node2",
target: "node3"
}, {
source: "node2",
target: "node5"
}, {
source: "node2",
target: "node6"
}, {
source: "node2",
target: "node7"
}];
var nodeByName = d3.map(nodes, function(d) {
return d.name;
});
edges.forEach(function(d) {
d.source = nodeByName.get(d.source);
d.target = nodeByName.get(d.target);
});
var width = 400;
var height = 400;
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
var force = d3.layout.force()
.nodes(nodes)
.links(edges)
.size([width, height])
.linkDistance(150)
.charge(-400);
force.start();
var svg_edges = svg.selectAll("line")
.data(edges)
.enter()
.append("line")
.style("stroke", "#ccc")
.style("stroke-width", 1);
var color = d3.scale.category20();
var svg_nodes = svg.selectAll("circle")
.data(nodes)
.enter()
.append("circle")
.attr("r", 20)
.style("fill", function(d, i) {
return color(i);
})
.call(force.drag);
var svg_texts = svg.selectAll("text")
.data(nodes)
.enter()
.append("text")
.style("fill", "black")
.attr("dx", 20)
.attr("dy", 8)
.text(function(d) {
return d.name;
});
force.on("tick", function() {
svg_edges.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;
});
svg_nodes.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
});
svg_texts.attr("x", function(d) {
return d.x;
})
.attr("y", function(d) {
return d.y;
});
});
<script src="https://d3js.org/d3.v3.min.js"></script>
Related
I'm new to d3 and want to connect nodes using elbows. Searching online I found one solution similar to what is required Similar solution however this solution does not work for d3 v4+.
Additionally, I have found a viable approach from d3 named d3.line().curve(d3.curveStepAfter) (I'm not sure if this is the correct use) an example can be seen here. However I can not find a way to implement this for my current set-up which uses x1, y1, and x2, y2.
Data
var data = {
"nodes": [
{
"name": "Node 1",
fx: 50,
fy: 50
},
{
"name": "Node 2",
fx: 50,
fy: 100
},
{
"name": "Node 3",
fx: 200,
fy: 50
},
{
"name": "Node 4",
fx: 350,
fy: 50
},
{
"name": "Node 5",
fx: 200,
fy: 150
}].map(function(d, i) { return (d.fixed = true, d) }),
"links": [
{
"source": 0,
"target": 2
},
{
"source": 1,
"target": 2
},
{
"source": 2,
"target": 3
},
{
"source": 2,
"target": 4
}]
}
Code
var width = 560
var height = 500;
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
var force = self.force = d3.forceSimulation(data.nodes)
.force("link", d3.forceLink(data.links))
.force("collide", d3.forceCollide())
.force("center", d3.forceCenter(width / 2, height / 2))
.on("tick", ticked);
var link = svg.selectAll("line.link")
.data(data.links)
.enter()
.append("line")
.style("stroke", "black")
.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; })
var node = svg.selectAll("g.node")
.data(data.nodes)
.enter()
.append("g")
.attr("class", "node")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
d3.selectAll(".node")
.append("circle")
.style("fill", "red")
.attr("r", 15);
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("transform", function (d) {
return "translate(" + d.x + "," + d.y + ")";
});
};
function dragstarted(event) {
if (!event.active) force.alphaTarget(0.3).restart();
event.subject.fx = event.subject.x;
event.subject.fy = event.subject.y;
}
function dragged(event) {
event.subject.fx = Math.ceil(event.x/5)*5;
event.subject.fy = Math.ceil(event.y/5)*5;
}
function dragended(event) {
if (!event.active) force.alphaTarget(0);
}
The code renders the nodes and connects them with straight lines, the goal is to add the curveStepAfter to create an elbow join as it looks neater for the type of diagram I require.
Any help is appreciated.
Here's a complete example.
<html>
<head>
<script src="https://d3js.org/d3.v7.min.js"></script>
</head>
<body>
<script>
const data = {
nodes: [
{ name: "Node 1", x: 50, y: 50 },
{ name: "Node 2", x: 50, y: 100 },
{ name: "Node 3", x: 200, y: 50 },
{ name: "Node 4", x: 350, y: 50 },
{ name: "Node 5", x: 200, y: 150 },
],
links: [
{ source: 0, target: 2 },
{ source: 1, target: 2 },
{ source: 2, target: 3 },
{ source: 2, target: 4 },
],
};
const segments = data.links.map(({ source, target }) => [
data.nodes[source],
data.nodes[target],
]);
const width = 560
const height = 500;
const svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
const line = d3.line()
.x(d => d.x)
.y(d => d.y)
.curve(d3.curveStep);
const link = svg.selectAll("path.link")
.data(segments)
.join("path")
.attr("d", line)
.attr("stroke", "black")
.attr("fill", "none")
const node = svg.selectAll("g.node")
.data(data.nodes)
.join("g")
.attr("class", "node")
.attr("transform", d => `translate(${d.x},${d.y})`)
.call(d3.drag()
.on("drag", dragged));
node.append("circle")
.style("fill", "red")
.attr("r", 15);
function dragged(event, d) {
d.x = Math.ceil(event.x / 5) * 5;
d.y = Math.ceil(event.y / 5) * 5;
link.attr("d", line);
d3.select(this)
.attr("transform", `translate(${d.x},${d.y})`)
}
</script>
</body>
</html>
The main idea is that
const segments = data.links.map(({ source, target }) => [
data.nodes[source],
data.nodes[target],
]);
is an array of line segments in the network. We can pass each segment to a d3.line() to create a <path> for the segment. I've also removed the dependency on d3-force, which isn't needed.
I am developing a directed graph with nodes in static position using d3.js library.
Currently I am having 3 nodes, and I want to connect the two computers with the printer. The edges are set to different positions as seen in this image:
How can i make the lines connect the nodes based on the nodes position?
Here is the code I have so far.
var width = 640,
height = 400;
var nodes = [
{ x: 184.53020651496104, y: 0, id: 0, url:
"http://icons.iconarchive.com/icons/tpdkdesign.net/refresh-
cl/32/Hardware-My-Computer-3-icon.png"},
{ x: 100, y: 150, id: 1, url:
"http://icons.iconarchive.com/icons/tpdkdesign.net/refresh-
cl/32/Hardware-My-Computer-3-icon.png" },
{ x: width/3, y: height/2, id: 2, url:
"http://icons.iconarchive.com/icons/tpdkdesign.net/refresh-
cl/32/Hardware-Printer-Blue-icon.png" },
];
var links = [
{ source: 1, target: 2 },
{ source: 0, target: 2 }
];
var graph = d3.select('#graph');
var svg = graph.append('svg')
.attr('width', width)
.attr('height', height);
var force = d3.layout.force()
.size([width, height])
.nodes(nodes)
.links(links);
force.linkDistance(width/2);
var link = svg.selectAll('.link')
.data(links)
.enter().append('line')
.attr('class', 'link');
var node = svg.selectAll('.node')
.data(nodes)
.enter().append("image")
.attr("xlink:href", d=> d.url)
.attr("x", d=> d.x)
.attr("y", d=> d.y)
.attr("width", 30)
.attr("height", 30)
.attr('class', 'node');
force.on('end', function() {
node.attr('r', width/25)
.attr('cx', function(d) { return d.x; })
.attr('cy', function(d) { return d.y; });
link.attr('x1', function(d) { return d.source.x; })
.attr('y1', function(d) { return d.source.y; })
.attr('x2', function(d) { return d.target.x; })
.attr('y2', function(d) { return d.target.y; });
});
force.start();
Thank you in advance.
I need to plot bubble chart, where each bubble is a donut chart like in below image in d3 version 3. I am able to achieve something, but don't understand how to distribute the circles horizontally, as my widget will be rectangular.
Also, how to make the donut bubble like in the image below. Any help would be appreciated. Thanks.
Code:
let colorCircles = {
'a': '#59bcf9',
'b': '#faabab',
'd': '#ffde85'
};
let tooltip = d3.select("body")
.append("div")
.attr("class", "tooltip-inner")
.style("position", "absolute")
.style("min-width", "12rem")
.style("visibility", "hidden")
.style("color", "#627386")
.style("padding", "15px")
.style("stroke", '#b8bfca')
.style("fill", "none")
.style("stroke-width", 1)
.style("background-color", "#fff")
.style("border-radius", "6px")
.style("text-align", "center")
.text("");
let bubble = d3.layout.pack()
.sort(null)
.size([width, diameter])
.padding(15)
.value(function(d) {
return d[columnForRadius];
});
let svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", diameter)
.attr("class", "bubble");
let nodes = bubble.nodes({
children: dataset
}).filter(function(d) {
return !d.children;
});
let circles = svg.selectAll("circle")
.data(nodes)
.enter()
.append("circle")
.attr("r", function(d) {
return d.r;
})
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y - 20;
})
.style("fill", function(d) {
return colorCircles[d[columnForColors]]
})
.on("mouseover", function(d) {
tooltip.style("visibility", "visible");
tooltip.html('<p>' + d[columnForColors] + ": " + d[columnForText] + "</p><div class='font-bold displayInlineBlock'> $" + d[columnForRadius] + '</div>');
})
.on("mousemove", function() {
return tooltip.style("top", (d3.event.offsetY - 10) + "px").style("left", (d3.event.offsetX + 10) + "px");
})
// .on("mouseout", function() {
// return tooltip.style("visibility", "hidden");
// })
.attr("class", "node");
circles.transition()
.duration(1000)
.attr("r", function(d) {
return d.r;
})
.each('end', function() {
display_text();
});
function display_text() {
let text = svg
.selectAll(".text")
.data(nodes, function(d) {
return d[columnForText];
});
text.enter().append("text")
.attr("class", "graphText")
.attr("x", function(d) {
return d.x;
})
.attr("y", function(d) {
return d.y - 20;
})
.attr("dy", ".2em")
.attr("fill", "white")
.attr("font-size", function(d) {
return d.r / 5;
})
.attr("text-anchor", "middle")
.text(function(d) {
console.log(d)
return d[columnForText].substring(0, d.r / 3);
});
text.enter().append("text")
.attr("class", "graphText")
.attr("x", function(d) {
return d.x;
})
.attr("y", function(d) {
return d.y - 20;
})
.attr("dy", "1.3em")
.style("text-anchor", "middle")
.text(function(d) {
return '$' + d[columnForRadius];
})
.attr("font-size", function(d) {
return d.r / 5;
})
.attr("fill", "white");
}
function hide_text() {
let text = svg.selectAll(".text").remove();
}
d3.select(self.frameElement)
.style("height", diameter + "px");
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<script type="text/javascript">
var dataset = [
{ "Name": "Olives", "Count": 4319, "Category": "d" },
{ "Name": "Tea", "Count": 4159, "Category": "d" },
{ "Name": "Boiled Potatoes", "Count": 2074, "Category": "a" },
{ "Name": "Milk", "Count": 1894, "Category": "a" },
{ "Name": "Chicken Salad", "Count": 1809, "Category": "a" },
{ "Name": "Lettuce Salad", "Count": 1566, "Category": "a" },
{ "Name": "Lobster Salad", "Count": 1511, "Category": "a" },
{ "Name": "Chocolate", "Count": 1489, "Category": "b" }
];
var width = 300, diameter = 300;
var columnForText = 'Name',
columnForColors = 'Category',
columnForRadius = "Count";
</script>
Here's my fiddle: http://jsfiddle.net/71s86zL7/
I created a compound bubble pie chart and specified the inner radius in the pie chart.
var arc = d3.svg.arc()
.innerRadius(radius)
.outerRadius(radius);
.attr("d", function(d) {
arc.innerRadius(d.r+5);
arc.outerRadius(d.r);
return arc(d);
})
please let me know if there's any alternative solution to this problem.
I have a sorta hacky solution for this. What I did was:
to use the d3.layout.pie to get the startAngles and endAngles for arcs and create the arcs on top of the circles.
Give the circles a stroke line creating an effect of a donut chart.
And then I just had to adjust the startAngles and the endAngles so that all the arcs start from the same position.
Here's the fiddle:
let colorCircles = {
'a': '#59bcf9',
'b': '#faabab',
'd': '#ffde85'
};
let tooltip = d3.select("body")
.append("div")
.attr("class", "tooltip-inner")
.style("position", "absolute")
.style("min-width", "12rem")
.style("visibility", "hidden")
.style("color", "#627386")
.style("padding", "15px")
.style("stroke", '#b8bfca')
.style("fill", "none")
.style("stroke-width", 1)
.style("background-color", "#fff")
.style("border-radius", "6px")
.style("text-align", "center")
.text("");
let bubble = d3.layout.pack()
.sort(null)
.size([width, diameter])
.padding(15)
.value(function(d) {
return d[columnForRadius];
});
var pie = d3.layout.pie()
.sort(null)
.value(function(d) {
return d.Count;
});
var arc = d3.svg.arc()
let svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", diameter)
.attr("class", "bubble");
let nodes = bubble.nodes({
children: dataset
}).filter(function(d) {
return !d.children;
});
let g = svg.append('g')
let circles = g.selectAll("circle")
.data(nodes)
.enter()
.append("circle")
.attr("r", function(d) {
return d.r;
})
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y - 20;
})
.style("fill", function(d) {
return colorCircles[d[columnForColors]]
})
.attr("class", "node")
.on("mouseover", function(d) {
tooltip.style("visibility", "visible");
tooltip.html('<p>' + d[columnForColors] + ": " + d[columnForText] + "</p><div class='font-bold displayInlineBlock'> $" + d[columnForRadius] + '</div>');
})
.on("mousemove", function() {
return tooltip.style("top", (d3.event.offsetY - 10) + "px").style("left", (d3.event.offsetX + 10) + "px");
})
.on("mouseout", function() {
return tooltip.style("visibility", "hidden");
});
arcs = g.selectAll(".arc")
.data(pie(dataset))
.enter().append("g")
.attr("class", "arc");
arcs.append("path")
.attr('transform', function(d) {
return 'translate(' + d['data']['x'] + ',' + (d['data']['y'] - 20) + ')';
})
.attr("d", function(d) {
return arc({
startAngle: 0,
endAngle: d.startAngle - d.endAngle,
innerRadius: d['data']['r'] - 2,
outerRadius: d['data']['r'] + 2,
})
}).on("mouseover", function(d) {
tooltip.style("visibility", "visible");
tooltip.html('<p>' + d['data'][columnForColors] + ": " + d['data'][columnForText] + "</p><div class='font-bold displayInlineBlock'> $" + d['data'][columnForRadius] + '</div>');
})
.on("mousemove", function() {
return tooltip.style("top", (d3.event.offsetY - 10) + "px").style("left", (d3.event.offsetX + 10) + "px");
})
.on("mouseout", function() {
return tooltip.style("visibility", "hidden");
});
circles.transition()
.duration(1000)
.attr("r", function(d) {
return d.r;
})
.each('end', function() {
display_text();
});
function display_text() {
let text = svg
.selectAll(".text")
.data(nodes, function(d) {
return d[columnForText];
});
text.enter().append("text")
.attr("class", "graphText")
.attr("x", function(d) {
return d.x;
})
.attr("y", function(d) {
return d.y - 20;
})
.attr("dy", ".2em")
.attr("fill", "white")
.attr("font-size", function(d) {
return d.r / 3;
})
.attr("text-anchor", "middle")
.text(function(d) {
return d[columnForText].substring(0, d.r / 3);
});
text.enter().append("text")
.attr("class", "graphText")
.attr("x", function(d) {
return d.x;
})
.attr("y", function(d) {
return d.y - 20;
})
.attr("dy", "1.3em")
.style("text-anchor", "middle")
.text(function(d) {
return '$' + d[columnForRadius];
})
.attr("font-size", function(d) {
return d.r / 5;
})
.attr("fill", "white");
}
function hide_text() {
let text = svg.selectAll(".text").remove();
}
d3.select(self.frameElement)
.style("height", diameter + "px");
path {
fill: orange;
stroke-width: 1px;
stroke: crimson;
}
path:hover {
fill: yellow;
}
circle {
fill: white;
stroke: slategray;
stroke-width: 4px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.3.13/d3.min.js"></script>
<script type="text/javascript">
var dataset = [{
"Name": "Olives",
"Count": 4319,
"Category": "d"
},
{
"Name": "Tea",
"Count": 4159,
"Category": "d"
},
{
"Name": "Boiled Potatoes",
"Count": 2074,
"Category": "a"
},
{
"Name": "Milk",
"Count": 1894,
"Category": "a"
},
{
"Name": "Chicken Salad",
"Count": 1809,
"Category": "a"
},
{
"Name": "Lettuce Salad",
"Count": 1566,
"Category": "a"
},
{
"Name": "Lobster Salad",
"Count": 1511,
"Category": "a"
},
{
"Name": "Chocolate",
"Count": 1489,
"Category": "b"
}
];
var width = 300,
diameter = 300;
var columnForText = 'Name',
columnForColors = 'Category',
columnForRadius = "Count";
</script>
I have been trying to add text and arrow to a d3js force layout diagram but was not being able to get the desired results.
My script looks like the following:
var width = 600;
height = 400;
var color = d3.scale.category20();
var force = d3.layout.force()
.charge(-120)
.linkDistance(100)
.size([width, height]);
var svg = d3.select("#t").append("svg")
.attr("width", width)
.attr("height", height);
d3.json("topology.json", function(error, graph) {
if (error) throw error;
force
.nodes(graph.nodes)
.links(graph.links)
.start();
var link = svg.selectAll(".link")
.data(graph.links)
.enter().append("line")
.attr("class", "link")
.style("stroke-width", function(d) { return 2*(d.value); });//Math.sqrt
var node = svg.selectAll(".node")
.data(graph.nodes)
.enter().append("circle")
.attr("class", "node")
.attr("r", 10)
.style("fill", function(d) { return color(d.group); })
.call(force.drag);
node.append("title")
.text(function(d) { return d.name; });
force.on("tick", function() {
link.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
});
});
I tried to add the following snippet to add text
node.append("text")
.attr("x", 12)
.attr("dy", ".35em")
.text(function(d) { return d.name; });
The sample json data is
{
"nodes": [{
"name": "0",
"group": 2
}, {
"name": "1",
"group": 2
}, {
"name": "2",
"group": 2
}, {
"name": "3",
"group": 1
}],
"links": [{
"source": 0,
"target": 1,
"value": 1
}, {
"source": 1,
"target": 3,
"value": 2
}, {
"source": 2,
"target": 3,
"value": 1
}]
}
Any help with adding text to the circles and arrows would be great. Sorry if I sounded a noob.
Thanks :) :)
Mobashyr,I have used the below sample for fulfill my requirement :
// Restart the force layout.
var force.nodes(nodes)
.links(links)
.charge(-1000)
.linkDistance(120)
.alpha(-15)
.start();
var link = vis.selectAll(".link")
.data(links);
link.enter().insert("svg:line", ".node")
.attr("class", "link")
.style("stroke", "#ccc")
.attr("cursor", "pointer")
.style("stroke-width", "0")
.style("stroke-width", function(d) { return d.weight;})
.on("mouseover", function() { d3.select(this).style("stroke", "#555555").attr("stroke-opacity", "1.0").attr("stroke-width","10");})
.on("mouseout", function() { d3.select(this).style("stroke", "#ccc").attr("stroke-opacity", "1.0").attr("stroke-width","4") });
link.exit().remove();
var node = vis.selectAll("g.node")
.data(nodes)
var groups = node.enter().append("g")
.attr("class", "node")
.attr("id", function (d) {
return d.entityType;
})
.on('click', click)
groups.append("circle")
.attr("cursor", "pointer")
.style("fill", function(d) { return color(d.entityType); })
.style("fill", "#fff")
.style("stroke-width", "0")
.style("stroke", "#ddd")
.attr("r", 20);
groups.append("text")
.attr("dy", 18)
.style("font-size", "2.5px")
.style("text-anchor", "middle")
.style('fill','#000')
.attr("refX", 15)
.attr("refY", -1.5)
.text(function (d) {
return d.text;
});
node.exit().remove();
force.on("tick", function () {
link.attr("x1", function (d) {
return d.source.x;
})
.attr("y1", function (d) {
return d.source.y;
})
.attr("x2", function (d) {
return d.target.x;
})
.attr("y2", function (d) {
return d.target.y;
});
node.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
So I have been doing a lot of experimenting with D3 but have never attempted to turn it into an AngularJS directive, and I am not even sure of the proper way to change it over. I have an example that I believe I turned into an Angular D3 chart however I believe there are more efficient ways in loading the data , maybe in a $scope.data... Please use my jsfiddle to reference or correct.
https://jsfiddle.net/bcnmLrns/1/
var app = angular.module("chartApp", []); {
var data = {
"nodes": [{
"name": "hblodget",
"group": 1,
"size": 1,
"image": null
}, {
"name": "DowntownDonna69",
"group": 1,
"size": 20,
"image": "http://pbs.twimg.com/profile_images/636139174672732160/L5cd008s_normal.jpg"
}, {
"name": "PupsherLive",
"group": 1,
"size": 19,
"image": "http://pbs.twimg.com/profile_images/378800000210840839/93a8ba3852a8e20364957eb8b907b6b3_normal.jpeg"
}],
"links": [{
"source": 1,
"target": 0,
"value": 1
}, {
"source": 2,
"target": 0,
"value": 1
}]
};
var width = 960,
height = 500;
var color = d3.scale.category20();
var force = d3.layout.force()
.charge(-120)
.linkDistance(30)
.size([width, height]);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
d3.json(data, function(error, graph) {
if (error) throw error;
force
.nodes(graph.nodes)
.links(graph.links)
.start();
var link = svg.selectAll(".link")
.data(graph.links)
.enter().append("line")
.attr("class", "link")
.style("stroke-width", function(d) {
return Math.sqrt(d.value);
});
var node = svg.selectAll(".node")
.data(graph.nodes)
.enter().append("circle")
.attr("class", "node")
.attr("r", 5)
.style("fill", function(d) {
return color(d.group);
})
.call(force.drag);
node.append("title")
.text(function(d) {
return d.name;
});
force.on("tick", function() {
link.attr("x1", function(d) {
return d.source.x;
})
.attr("y1", function(d) {
return d.source.y;
})
.attr("x2", function(d) {
return d.target.x;
})
.attr("y2", function(d) {
return d.target.y;
});
node.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
});
});
});
}
You can use other directives and services people have made to make D3 easier to use in Angular (see comments on question), but I still like the easy setup of modularizing graphs and such inside a simple directive:
myApp.directive('graph', function() {
var graphLink = function(scope, element) {
var width = 960,
height = 500;
var color = d3.scale.category20();
var force = d3.layout.force()
.charge(-120)
.linkDistance(30)
.size([width, height]);
var svg = d3.select(element[0]).append("svg") // attach d3 to directive element
.attr("width", width)
.attr("height", height);
d3.json(data, function(error, graph) {
if (error) throw error;
force
.nodes(graph.nodes)
.links(graph.links)
.start();
var link = svg.selectAll(".link") // there might be a more angular way to do this...
.data(graph.links)
.enter().append("line")
.attr("class", "link")
.style("stroke-width", function(d) {
return Math.sqrt(d.value);
});
var node = svg.selectAll(".node")
.data(graph.nodes)
.enter().append("circle")
.attr("class", "node")
.attr("r", 5)
.style("fill", function(d) {
return color(d.group);
})
.call(force.drag);
node.append("title")
.text(function(d) {
return d.name;
});
force.on("tick", function() {
link.attr("x1", function(d) {
return d.source.x;
})
.attr("y1", function(d) {
return d.source.y;
})
.attr("x2", function(d) {
return d.target.x;
})
.attr("y2", function(d) {
return d.target.y;
});
node.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
});
});
};
return {
link: graphLink, // pass in your link function here
scope: {
data: '=' // pass in your data as an attribute
// this makes this reusable, and you can redraw if the data changes
}
};
});