I was working on one of my topic modeling project where I try to use D3.js to visualize the result. And sadly this is my first experience with D3.js
I tried to link the main topics to their subtopics, so I followed the tutorial about how to use "force simulation" in D3. And now the nodes looks very good, however, the link never shows up.
Compare to the tutorial, the only difference is in this project I have to fix x-axis since all topic was binding to a time slot.
Also, after calling "function restart()", my text in the small bubble disappeared.
Please give me some advice on this.
data= [
{id: "Topic1", date: "2017-08-21", name: "Topic1", count: .4, subtopics: ["sub1", "sub2", "sub3", "sub5"]},
{id: "Topic2", date: "2017-08-23", name: "Topic2", count: 1, subtopics: ["sub3", "sub6", "sub7", "sub8"]},
{id: "Topic3", date: "2017-08-25", name: "Topic3",count: 2, subtopics: ["sub7", "sub9"]},
{id: "Topic4", date: "2017-08-27", name: "Topic4", count: 2, subtopics: ["sub8"]},
{id: "sub1",date:"2017-08-21", name:"sub1", count: .1, subtopics: []},
{id: "sub2",date:"2017-08-22", name:"sub2", count: .2, subtopics: []},
{id: "sub3",date:"2017-08-22", name:"sub3", count: .2, subtopics: []},
{id: "sub4",date:"2017-08-28", name:"sub4", count: .1, subtopics: []},
{id: "sub5",date:"2017-08-20", name:"sub5", count: .2, subtopics: []},
{id: "sub6",date:"2017-08-23", name:"sub6", count: .1, subtopics: []},
{id: "sub7",date:"2017-08-24", name:"sub7", count: .3, subtopics: []},
{id: "sub8",date:"2017-08-24", name:"sub8", count: .1, subtopics: []},
{id: "sub9",date:"2017-08-25", name:"sub9", count: .1, subtopics: []},
{id: "sub10",date:"2017-08-27", name:"sub10", count: .1, subtopics: []},
{id: "sub11",date:"2017-08-29", name:"sub11", count: .1, subtopics: []},
{id: "sub12",date:"2017-08-30", name:"sub12", count: .4, subtopics: []},
]
var vis_node = [];
var vis_link = [];
var r = d3.scaleSqrt()
.domain([0, d3.max(data, function (d) {
return d.count;
})])
.range([0, 65]);
var margin = {top: 50, right: 20, bottom: 100, left: 50},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
//map elements to time
var parseTime = d3.timeParse("%Y-%m-%d");
data.forEach(function(d) {
d.date = parseTime(d.date);
d.close = +d.close;
});
//separete big topic and subtopics and setup the visualization data
var parents_node = data.filter(function(d){return d.subtopics.length != 0;});
var child_node = data.filter(function(d){return d.subtopics.length == 0;});
vis_node = data;
//link all the big topic with common subtopic
for(i = 0;i<data.length;i++){
var tmps = data[i].subtopics
for(j = 0; j < tmps.length; j++){
for(k = i+1; k < data.length;k++){
if(data[k].subtopics.includes(tmps[j])){
vis_link.push({source: data[i], target: data[k]})
continue;
}
}
}
}
//link all the big topic with its subtopic
for(i = 0;i<parents_node.length;i++){
for(j = 0;j<child_node.length;j++){
if(parents_node[i].subtopics.includes(child_node[j].name)){
vis_link.push({source: parents_node[i], target: child_node[j]});
}
}
}
//setup a force field for the d3
var simulation = d3.forceSimulation()
.force("charge", d3.forceManyBody().strength(-700).distanceMin(100).distanceMax(280))
.force("link", d3.forceLink().id(function(d) { return d.index }))
.force("center", d3.forceCenter(width / 2, height / 2))
.force("y", d3.forceY(0.0001))
.force("x", d3.forceX(0.0001))
var svg = d3.select('body').append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom),
g = svg.append('g')
.attr('transform','translate(' + margin.left + ',' + margin.top + ')');
var formatNumber = d3.format('');
var x = d3.scaleTime()
.range([0, width]);
x.domain(d3.extent(data, function(d) { return d.date; }));
//begin to render the circle
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x)
.tickFormat(d3.timeFormat("%Y-%m-%d")))
var node = g.selectAll('.node')
.data(vis_node)
.enter().append('g')
.attr("class", "node");
node.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
node.append('circle')
.attr('r', function(d) {return Math.max(16, r(d.count)); })
.style("stroke", function(d){
if(d.subtopics.length == 0){
return "blue"
}
else{
return "pink"
}
})
.style("fill", "transparent");
node.append('text')
.text(function(d){return d.name})
.attr("text-anchor", "middle")
.style('fill', function(d){
if(d.subtopics.length == 0){
return "darkblue"
}
else{
return "darkred"
}
})
.style('font-size','20px')
.attr("pointer-events", "none");
node.on("click", function(d){
var subs = d.subtopics
child_node.forEach(function(d){
if(subs.includes(d.name)){
if(vis_node.includes(d)){
var index = vis_node.indexOf(d)
vis_node.splice(index, 1)
}
else{
vis_node.push(d);
}
}
})
restart();
});
//begin to render a link
var link = g.selectAll('.link')
.data(vis_link)
.enter().append('g')
.attr('class','link')
link.append('line')
.attr("stroke","black")
//when click on the big topic call restart function to redraw everything
function restart(){
vis_node.forEach(function(d){console.log(d.name)})
node = node.data(vis_node)
node.exit().remove()
node = node.enter().append('circle')
.attr('r', function(d) {return Math.max(16, r(d.count)); })
.style("stroke", function(d){
if(d.subtopics.length == 0){
return "blue"
}
else{
return "pink"
}
})
.style("fill", "transparent")
.merge(node);
node.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended))
node.append('text')
.text(function(d){return d.name})
.attr("text-anchor", "middle")
.style('fill', function(d){
if(d.subtopics.length == 0){
return "darkblue"
}
else{
return "darkred"
}
})
.style('font-size','20px')
.attr("pointer-events", "none");
simulation.nodes(vis_node);
simulation.alphaTarget(0.3).restart();
}
//I think something wrong here
var ticked = function() {
node.attr("transform", function (d) {
return "translate(" + x(d.date) + "," + d.y + ")";
})
link.attr("x1", function (d) {return x(d.source.date); })
.attr("y1", function (d) {return d.source.y;})
.attr("x2", function (d) {return x(d.target.date)})
.attr("y2", function (d) {return d.target.y;});
}
//add link and node to the force field
simulation.force("link").links(vis_link);
simulation.nodes(vis_node);
simulation.on("tick", ticked);
//drag related functions
function dragstarted(d) {
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(d) {
d.fy = d3.event.y;
}
function dragended(d) {
if (!d3.event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
<!DOCTYPE html>
<meta charset="utf-8">
<head>
<title>Topic Explorer</title>
<base href="/">
<meta http-equiv="content-type" content="text/html;charset=utf-8"/>
<link rel="stylesheet" href="./styles/simple-style.css">
</head>
<body style="margin:10px 0">
</div>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="./scripts/test.js"></script>
</body>
</html>
Since your link selection is a selection of groups...
var link = g.selectAll('.link')
.data(vis_link)
.enter().append('g')
.attr('class', 'link');
... you're applying the x1, x2, y1 and y2 attributes to the groups, not to the lines.
An easy solution is just naming another selection, just for the lines:
var line = link.append('line')
.attr("stroke", "black");
Here is your modified code:
data = [{
id: "Topic1",
date: "2017-08-21",
name: "Topic1",
count: .4,
subtopics: ["sub1", "sub2", "sub3", "sub5"]
},
{
id: "Topic2",
date: "2017-08-23",
name: "Topic2",
count: 1,
subtopics: ["sub3", "sub6", "sub7", "sub8"]
},
{
id: "Topic3",
date: "2017-08-25",
name: "Topic3",
count: 2,
subtopics: ["sub7", "sub9"]
},
{
id: "Topic4",
date: "2017-08-27",
name: "Topic4",
count: 2,
subtopics: ["sub8"]
},
{
id: "sub1",
date: "2017-08-21",
name: "sub1",
count: .1,
subtopics: []
},
{
id: "sub2",
date: "2017-08-22",
name: "sub2",
count: .2,
subtopics: []
},
{
id: "sub3",
date: "2017-08-22",
name: "sub3",
count: .2,
subtopics: []
},
{
id: "sub4",
date: "2017-08-28",
name: "sub4",
count: .1,
subtopics: []
},
{
id: "sub5",
date: "2017-08-20",
name: "sub5",
count: .2,
subtopics: []
},
{
id: "sub6",
date: "2017-08-23",
name: "sub6",
count: .1,
subtopics: []
},
{
id: "sub7",
date: "2017-08-24",
name: "sub7",
count: .3,
subtopics: []
},
{
id: "sub8",
date: "2017-08-24",
name: "sub8",
count: .1,
subtopics: []
},
{
id: "sub9",
date: "2017-08-25",
name: "sub9",
count: .1,
subtopics: []
},
{
id: "sub10",
date: "2017-08-27",
name: "sub10",
count: .1,
subtopics: []
},
{
id: "sub11",
date: "2017-08-29",
name: "sub11",
count: .1,
subtopics: []
},
{
id: "sub12",
date: "2017-08-30",
name: "sub12",
count: .4,
subtopics: []
},
]
var vis_node = [];
var vis_link = [];
var r = d3.scaleSqrt()
.domain([0, d3.max(data, function(d) {
return d.count;
})])
.range([0, 65]);
var margin = {
top: 50,
right: 20,
bottom: 100,
left: 50
},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
//map elements to time
var parseTime = d3.timeParse("%Y-%m-%d");
data.forEach(function(d) {
d.date = parseTime(d.date);
d.close = +d.close;
});
//separete big topic and subtopics and setup the visualization data
var parents_node = data.filter(function(d) {
return d.subtopics.length != 0;
});
var child_node = data.filter(function(d) {
return d.subtopics.length == 0;
});
vis_node = data;
//link all the big topic with common subtopic
for (i = 0; i < data.length; i++) {
var tmps = data[i].subtopics
for (j = 0; j < tmps.length; j++) {
for (k = i + 1; k < data.length; k++) {
if (data[k].subtopics.includes(tmps[j])) {
vis_link.push({
source: data[i],
target: data[k]
})
continue;
}
}
}
}
//link all the big topic with its subtopic
for (i = 0; i < parents_node.length; i++) {
for (j = 0; j < child_node.length; j++) {
if (parents_node[i].subtopics.includes(child_node[j].name)) {
vis_link.push({
source: parents_node[i],
target: child_node[j]
});
}
}
}
//setup a force field for the d3
var simulation = d3.forceSimulation()
.force("charge", d3.forceManyBody().strength(-700).distanceMin(100).distanceMax(280))
.force("link", d3.forceLink().id(function(d) {
return d.index
}))
.force("center", d3.forceCenter(width / 2, height / 2))
.force("y", d3.forceY(0.0001))
.force("x", d3.forceX(0.0001))
var svg = d3.select('body').append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom),
g = svg.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
var formatNumber = d3.format('');
var x = d3.scaleTime()
.range([0, width]);
x.domain(d3.extent(data, function(d) {
return d.date;
}));
//begin to render the circle
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x)
.tickFormat(d3.timeFormat("%Y-%m-%d")))
var node = g.selectAll('.node')
.data(vis_node)
.enter().append('g')
.attr("class", "node");
node.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
node.append('circle')
.attr('r', function(d) {
return Math.max(16, r(d.count));
})
.style("stroke", function(d) {
if (d.subtopics.length == 0) {
return "blue"
} else {
return "pink"
}
})
.style("fill", "transparent");
node.append('text')
.text(function(d) {
return d.name
})
.attr("text-anchor", "middle")
.style('fill', function(d) {
if (d.subtopics.length == 0) {
return "darkblue"
} else {
return "darkred"
}
})
.style('font-size', '20px')
.attr("pointer-events", "none");
node.on("click", function(d) {
var subs = d.subtopics
child_node.forEach(function(d) {
if (subs.includes(d.name)) {
if (vis_node.includes(d)) {
var index = vis_node.indexOf(d)
vis_node.splice(index, 1)
} else {
vis_node.push(d);
}
}
})
restart();
});
//begin to render a link
var link = g.selectAll('.link')
.data(vis_link)
.enter().append('g')
.attr('class', 'link')
var line = link.append('line')
.attr("stroke", "black")
//when click on the big topic call restart function to redraw everything
function restart() {
vis_node.forEach(function(d) {
console.log(d.name)
})
node = node.data(vis_node)
node.exit().remove()
node = node.enter().append('circle')
.attr('r', function(d) {
return Math.max(16, r(d.count));
})
.style("stroke", function(d) {
if (d.subtopics.length == 0) {
return "blue"
} else {
return "pink"
}
})
.style("fill", "transparent")
.merge(node);
node.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended))
node.append('text')
.text(function(d) {
return d.name
})
.attr("text-anchor", "middle")
.style('fill', function(d) {
if (d.subtopics.length == 0) {
return "darkblue"
} else {
return "darkred"
}
})
.style('font-size', '20px')
.attr("pointer-events", "none");
simulation.nodes(vis_node);
simulation.alphaTarget(0.3).restart();
}
//I think something wrong here
var ticked = function() {
node.attr("transform", function(d) {
return "translate(" + x(d.date) + "," + d.y + ")";
})
line.attr("x1", function(d) {
return x(d.source.date);
})
.attr("y1", function(d) {
return d.source.y;
})
.attr("x2", function(d) {
return x(d.target.date)
})
.attr("y2", function(d) {
return d.target.y;
});
}
//add link and node to the force field
simulation.force("link").links(vis_link);
simulation.nodes(vis_node);
simulation.on("tick", ticked);
//drag related functions
function dragstarted(d) {
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(d) {
d.fy = d3.event.y;
}
function dragended(d) {
if (!d3.event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
<!DOCTYPE html>
<meta charset="utf-8">
<head>
<title>Topic Explorer</title>
<base href="/">
<meta http-equiv="content-type" content="text/html;charset=utf-8" />
<link rel="stylesheet" href="./styles/simple-style.css">
</head>
<body style="margin:10px 0">
</div>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="./scripts/test.js"></script>
</body>
</html>
Related
I'm creating horizontal tree from nested array. After tree is created, the links from one node to another are regularly being updated, but the removal functionality is not working.
import { Component, OnInit } from '#angular/core';
import * as d3 from 'd3';
#Component({
selector: 'app-rtm-graph',
templateUrl: './rtm-graph.component.html',
styleUrls: ['./rtm-graph.component.css']
})
export class RtmGraphComponent implements OnInit {
private rtmData: {id:number, name: string, children: any[] } =
{ id: 0, name: 'RTM', children: [
{ id: 0, name: 'RTM', children: [
{ id: 0, name: 'RTM', children: []},
{ id: 0, name: 'RTM', children: []},
{ id: 0, name: 'RTM', children: []},
]},
{ id: 0, name: 'RTM', children: []},
] };
private svg: any;
private margin = 50;
private width = 750 - (this.margin * 2);
private height = 750 - (this.margin * 2);
private root: any;
private treemap = d3.tree().size([this.height, this.width])
private duration = 1124;
private i = 0;
constructor(public _rs:RootService) { }
ngOnInit(): void {
this.createSvg();
this.createTree()
}
private callSvg() {
return d3.select("figure#rtm-graph");
}
private createTree(): void {
this.root = d3.hierarchy(this.rtmData, (d) => {
return d.children;
})
this.root.x0 = this.height / 2;
this.root.y0 = 0;
this.update(this.root);
}
private createSvg(): void {
this.svg = this.callSvg()
.append("svg")
.attr("width", this.width + (this.margin * 2))
.attr("height", this.height + (this.margin * 2))
.append("g")
.attr("transform", `translate(${this.margin},${this.margin})`);
}
private update(source): void {
let treedata = this.treemap(this.root);
let nodes = treedata.descendants();
nodes.forEach(d => {
d.y = d.depth * this.width / 5;
});
let node = this.svg.selectAll("g.node").data(nodes, (d) => d.id || (d.id = ++this.i));
// links
let links = treedata.descendants().slice(1);
let link = this.svg.selectAll('path.link').data(links, (d) => {
return d.id;
})
let linkEnter = link
.enter()
.insert('path', 'g')
.attr('class', 'links')
.attr('d', (d) => {
let o = { x: source.x0, y: source.y0 + 40 }
return this.diagonal(o, o)
})
let linkUpdate = linkEnter.merge(link);
linkUpdate
.transition()
.duration(this.duration)
.attr("d", (d) => {
return this.diagonal(d, d.parent);
});
link
.exit()
.transition()
.attr('d', (d: any) => {
console.log("Inside link exit")
let o = { x: source.x0, y: source.y0 }
return this.diagonal(o, o);
})
.remove();
let nodeEnter = node
.enter()
.append("g")
.attr("class", "node")
.attr("transform", d => {
return `translate(${source.y0 + 20},${source.x0})`
})
.on("click", this.clicked.bind(this))
nodeEnter.append('circle')
.attr('class', 'node')
.attr('r', 0)
.style('fill', d => {
return d._children ? "red" : "white";
})
let nodeUpdate = nodeEnter.merge(node);
nodeUpdate.transition()
.duration(this.duration)
.attr("transform", d => `translate(${d.y + 20},${d.x})`)
.attr("opacity", 1)
nodeUpdate.select("circle.node")
.attr('r', 10)
.style("fill", d => d._children ? "red" : "black")
.attr("cursor", "pointer");
nodeUpdate.append('rect')
.attr('x', 0)
.attr('y', -20)
.attr('rx', 5)
.attr('ry', 5)
.attr('width', 80)
.attr('height', 40)
.attr('fill', 'grey')
.exit();
nodeUpdate.append('text')
.attr('x', 0)
.attr('y', 0)
.attr('dx', 10)
.text(d => {
console.log(d.data.name)
return d.data.name;
});
let nodeExit = node.exit()
.transition()
.duration(this.duration)
.attr("transform", function () { return `translate(${source.y + 20},${source.x})` })
.attr("opacity", 0.5)
.remove();
// collapsing of the nodes
nodes.forEach(d => {
d.x0 = d.x;
d.y0 = d.y;
})
}
diagonal(s, d) {
let path = `M ${s.y} ${s.x}
C ${(s.y + d.y) / 2} ${s.x}
${(s.y + d.y) / 2} ${d.x}
${d.y} ${d.x}`;
return path;
}
clicked(event, d) {
console.log('ddddd:::',d);
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
this.update(d);
}
}
If I add console.log("Inside link exit") to the code, then no log is written to the console. But if I run same code in vanilla JavaScript, then I get the expected output from this console.log.
For the first time, the graph is generated correctly.
but after collapsing nodes, the output is as follows:
How to remove this residual link?
Context : I have this Django server that manages devices, i want to show a communication graph between these devices, i've decide to use D3 force graph for this purpose, the Django server will send a json through Redis with a websocket, i want the client to read the json and print the graph.
So far i've been able to print static graph, but i can't manage to update it live.
Usefull link :
Core code from This example.
Tried to follow This, but i don't think it's the right direction.
Goal : Update a Force graph in real time using websocket.
My JS code :
var graph = {
"nodes": [
{"id": "Agent_1", "group": 1},
{"id": "Agent_2", "group": 2},
{"id": "Agent_3", "group": 1},
{"id": "Agent_4", "group": 3}
],
"links": []
};
const comSocket = new WebSocket(
'ws://'
+ window.location.host
+ '/ws/com/'
);
comSocket.onmessage = function (e) {
graph = JSON.parse(e.data).message;
console.log(graph);
simulation.nodes(graph.nodes);
simulation.force("link").links(graph.links);
simulation.alpha(1).restart();
};
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
var color = d3.scaleOrdinal(d3.schemeCategory20);
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) { return d.id; }))
.force("charge", d3.forceManyBody().strength(-2500))
.force("center", d3.forceCenter(width / 2, height / 2));
var link = svg.append("g").attr("class", "links").selectAll("line")
.data(graph.links).enter().append("line")
.attr("stroke-width", function(d) { return Math.sqrt(d.value); });
var node = svg.append("g")
.attr("class", "nodes")
.selectAll("g")
.data(graph.nodes)
.enter().append("g")
var circles = node.append("circle")
.attr("r", 20)
.attr("fill", function(d) { return color(d.group); });
var lables = node.append("text").text(function(d) {return d.id;}).attr('x', 16).attr('y', 13);
node.append("title").text(function(d) { return d.id; });
simulation.nodes(graph.nodes).on("tick", ticked);
simulation.force("link").links(graph.links);
function ticked() {
link
.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
})
};
Using the above code it produce this error :
Uncaught TypeError: Cannot read properties of undefined (reading 'length')
at the line :
simulation.nodes(graph.nodes);
in onmessage()
The data value is a json with the same structure as var graph (line 1). So i don't know why it can initialize the graph correctly but canno't refresh with the same value.. :
{'nodes': [{'id': 'Agent_0', 'group': 1}, {'id': 'Agent_1', 'group': 2}, {'id': 'Agent_2', 'group': 1}, {'id': 'Agent_3', 'group': 3}], 'links': [{'source': 'Agent_0', 'target': 'Agent_2', 'value': 1}, {'source': 'Agent_0', 'target': 'Agent_1', 'value': 3}, {'source': 'Agent_0', 'target': 'Agent_3', 'value': 5}, {'source': 'Agent_1', 'target': 'Agent_3', 'value': 3}, {'source': 'Agent_2', 'target': 'Agent_3', 'value': 5}, {'source': 'Agent_1', 'target': 'Agent_2', 'value': 5}]}
It was a server side issue, wrong type was sent.
In the end i've also update the code to latest version (only color needed to be updated). Here's the final working version :
var graph = {
"nodes": [
{"id": "Agent_0", "group": 1},
{"id": "Agent_1", "group": 2},
{"id": "Agent_2", "group": 1},
{"id": "Agent_3", "group": 3}
],
"links": []
};
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
var color = d3.schemeCategory10;
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) { return d.id; }))
.force("charge", d3.forceManyBody().strength(-2500))
.force("center", d3.forceCenter(width / 2, height / 2));
var link = svg.append("g").attr("class", "links").selectAll("line").data(graph.links).enter().append("line")
.attr("stroke-width", function(d) { return Math.sqrt(d.value); });
var node = svg.append("g").attr("class", "nodes").selectAll("g").data(graph.nodes).enter().append("g")
var circles = node.append("circle").attr("r", 20).attr("fill", function(d) { return color[d.group]; });
var lables = node.append("text").text(function(d) {return d.id;}).attr('x', 16).attr('y', 13);
node.append("title").text(function(d) { return d.id; });
simulation.nodes(graph.nodes).on("tick", ticked);
simulation.force("link").links(graph.links);
function ticked() {
link
.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
})
};
const comSocket = new WebSocket(
'ws://'
+ window.location.host
+ '/ws/com/'
);
comSocket.onmessage = function (e) {
graph = JSON.parse(e.data).message;
console.log(graph);
console.log(typeof graph);
svg.selectAll("*").remove();
link = svg.append("g").attr("class", "links").selectAll("line").data(graph.links).enter().append("line")
.attr("stroke-width", function(d) { return Math.sqrt(d.value); });
node = svg.append("g").attr("class", "nodes").selectAll("g").data(graph.nodes).enter().append("g")
circles = node.append("circle").attr("r", 20).attr("fill", function(d) { return color[d.group]; });
lables = node.append("text").text(function(d) {return d.id;}).attr('x', 16).attr('y', 13);
node.append("title").text(function(d) { return d.id; });
simulation.nodes(graph.nodes).on("tick", ticked);
simulation.force("link").links(graph.links);
simulation.alpha(1).restart();
};
I want to visualize the "children" insight each node. I guess the D3v6 .join() function can be nested. Unfortunately I can´t find any example. The snippet below contains an outerGraph with 3 nodes and children as attribute. So far those children aren´t used yet.
The innerGraph instead visualize the small nodes which will be obsolete as soon as the children approach is working. Another Idea would be to work with those two graphs and create a gravity / cluster, which will be the parent.
Goal: Either utilize the children attribute or combine both graphs with the help of an cluster /gravity or even nested join(). I am appreciating any hint / tip. The visuals result should be:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>D3v6 nested nodes</title>
<script src="https://d3js.org/d3.v6.min.js"></script>
</head>
<style>
body {
background-color: whitesmoke;
}
</style>
<body>
<script>
var svg = d3.select("body").append("svg")
.attr("width", window.innerWidth)
.attr("height", window.innerHeight);
var width = window.innerWidth
var height = window.innerHeight
var outerLinkContainer = svg.append("g").attr("class", "outerLinkContainer")
var outerNodeContainer = svg.append("g").attr("class", "outerNodeContainer")
var innerLinkContainer = svg.append("g").attr("class", "innerLinkContainer")
var innerNodeContainer = svg.append("g").attr("class", "innerNodeContainer")
//###############################################
//############# outer force Layouts #############
//###############################################
var outerGraph = {
"nodes": [
{
"id": "A",
"children": [
{
"id": "A1"
},
{
"id": "A2"
}
]
},
{
"id": "B",
"children": [
{
"id": "B1"
},
{
"id": "B2"
}
]
},
{
"id": "C",
"children": [
{
"id": "C1"
},
{
"id": "C2"
}
]
}
],
"links": [
{
"source": "A",
"target": "B"
},
{
"source": "B",
"target": "C"
},
{
"source": "C",
"target": "A"
},
]
}
var outerLayout = d3.forceSimulation()
.force("center", d3.forceCenter(width / 2, height / 2))
.force("charge", d3.forceManyBody().strength(-500))
.force("link", d3.forceLink().id(function (d) {
return d.id;
}).distance(200))
var outerLinks = outerLinkContainer.selectAll(".link")
.data(outerGraph.links)
.join("line")
.attr("class", "link")
.style("stroke", "black")
.style("opacity", 0.2)
var outerNodes = outerNodeContainer.selectAll("g.outer")
.data(outerGraph.nodes, function (d) { return d.id; })
.join("circle")
.attr("class", "outer")
.style("fill", "pink")
.style("stroke", "blue")
.attr("r", 40)
.call(d3.drag()
.on("start", dragStarted)
.on("drag", dragged)
.on("end", dragEnded)
)
outerLayout
.nodes(outerGraph.nodes)
.on("tick", ticked)
outerLayout
.force("link")
.links(outerGraph.links)
//###############################################
//############## inner force Layout #############
//###############################################
var innerGraph = {
"nodes": [
{ "id": "A1" },
{ "id": "A2" }
],
"links": [
{
"source": "A1",
"target": "A2"
}
]
}
var innerlayout = d3.forceSimulation()
.force("center", d3.forceCenter(width / 2, height / 2))
.force("charge", d3.forceManyBody().strength(-500))
.force("link", d3.forceLink().id(function (d) {
return d.id;
}).distance(200))
var innerLinks = innerLinkContainer.selectAll(".link")
.data(innerGraph.links)
.join("line")
.attr("class", "link")
.style("stroke", "black")
var innerNodes = innerNodeContainer.selectAll("g.inner")
.data(innerGraph.nodes, function (d) { return d.id; })
.join("circle")
.style("fill", "orange")
.style("stroke", "blue")
.attr("r", 6)
.attr("class", "inner")
.attr("id", function (d) { return d.id; })
.call(d3.drag()
.on("start", dragStarted)
.on("drag", dragged)
.on("end", dragEnded)
)
innerlayout
.nodes(innerGraph.nodes)
.on("tick", ticked)
innerlayout
.force("link")
.links(innerGraph.links)
//###############################################
//################## functons ###################
//###############################################
function ticked() {
outerLinks
.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;
});
innerLinks
.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;
});
outerNodes.attr("transform", function (d) {
return "translate(" + d.x + "," + d.y + ")";
});
innerNodes.attr("transform", function (d) {
return "translate(" + (d.x) + "," + (d.y) + ")";
});
}
function dragStarted(event, d) {
if (!event.active)
outerLayout.alphaTarget(0.3).restart();
innerlayout.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(event, d) {
d.fx = event.x;
d.fy = event.y;
}
function dragEnded(event, d) {
if (!event.active)
outerLayout.alphaTarget(0)
innerlayout.alphaTarget(0)
d.fx = undefined;
d.fy = undefined;
}
</script>
</body>
</html>
I will update the post as soon as I found a solution.
Here's a slightly hack way to do it - I am a bit disappointed in the outcome because if you play with the outerNodes then the links between innerNodes cross over in an unattractive way.
The changes I made in your code:
update innerGraph so nodes have a parent property (plus add the links required to match your screenshot in the question)
add an additional class on outerNodes so that each outer node can be identified e.g. .outer_A, .outer_B etc
add an additional class on innerNodes so that each inner node can be identified e.g. .child_A1, .child_A2 etc
in ticked - for innerNodes return a point for the inner node so that it is sitting inside centre of it's parent at roughly 20px from the centre on the vector between the original force simulation selected point and the parent's centre.
in ticked - for innerLinks, force the source and target coordinates to update per the previous step
Those last two points are per here and here.
So it works - but only just. Vertical scrolling in the stack snippet seems to upset it a bit but it's maybe better if you try it out on your own dev environment. I still think you could you look at other tools - maybe this one from cytoscape.js and also the webcola example I mentioned in the comments?
var svg = d3.select("body").append("svg")
.attr("width", window.innerWidth)
.attr("height", window.innerHeight);
var width = window.innerWidth
var height = window.innerHeight
var outerLinkContainer = svg.append("g").attr("class", "outerLinkContainer")
var outerNodeContainer = svg.append("g").attr("class", "outerNodeContainer")
var innerLinkContainer = svg.append("g").attr("class", "innerLinkContainer")
var innerNodeContainer = svg.append("g").attr("class", "innerNodeContainer")
//###############################################
//############# outer force Layouts #############
//###############################################
var outerGraph = {
"nodes": [
{
"id": "A",
"children": [
{
"id": "A1"
},
{
"id": "A2"
}
]
},
{
"id": "B",
"children": [
{
"id": "B1"
},
{
"id": "B2"
}
]
},
{
"id": "C",
"children": [
{
"id": "C1"
},
{
"id": "C2"
}
]
}
],
"links": [
{
"source": "A",
"target": "B"
},
{
"source": "B",
"target": "C"
},
{
"source": "C",
"target": "A"
},
]
}
var outerLayout = d3.forceSimulation()
.force("center", d3.forceCenter(width / 2, height / 2))
.force("charge", d3.forceManyBody().strength(-500))
.force("link", d3.forceLink().id(function (d) {
return d.id;
}).distance(200))
var outerLinks = outerLinkContainer.selectAll(".link")
.data(outerGraph.links)
.join("line")
.attr("class", "link")
.style("stroke", "black")
.style("opacity", 0.2)
var outerNodes = outerNodeContainer.selectAll("g.outer")
.data(outerGraph.nodes, function (d) { return d.id; })
.join("circle")
.attr("class", d => `outer outer_${d.id}`)
.style("fill", "pink")
.style("stroke", "blue")
.attr("r", 40)
.call(d3.drag()
.on("start", dragStarted)
.on("drag", dragged)
.on("end", dragEnded)
)
outerLayout
.nodes(outerGraph.nodes)
.on("tick", ticked)
outerLayout
.force("link")
.links(outerGraph.links)
//###############################################
//############## inner force Layout #############
//###############################################
var innerGraph = {
"nodes": [
{ "id": "A1", "parent": "A" },
{ "id": "A2", "parent": "A" },
{ "id": "B1", "parent": "B" },
{ "id": "B2", "parent": "B" },
{ "id": "C1", "parent": "C" },
{ "id": "C2", "parent": "C" }
],
"links": [
{
"source": "A1",
"target": "A2"
},
{
"source": "A2",
"target": "B2"
},
{
"source": "A1",
"target": "C2"
},
{
"source": "B1",
"target": "B2"
},
{
"source": "B1",
"target": "C1"
},
{
"source": "C2",
"target": "C1"
}
]
}
var innerlayout = d3.forceSimulation()
.force("center", d3.forceCenter(width / 2, height / 2))
.force("charge", d3.forceManyBody().strength(-500))
.force("link", d3.forceLink().id(function (d) {
return d.id;
}).distance(200))
var innerLinks = innerLinkContainer.selectAll(".link")
.data(innerGraph.links)
.join("line")
.attr("class", "link linkChild")
.style("stroke", "black")
var innerNodes = innerNodeContainer.selectAll("g.inner")
.data(innerGraph.nodes, function (d) { return d.id; })
.join("circle")
.style("fill", "orange")
.style("stroke", "blue")
.attr("r", 6)
.attr("class", d => `inner child_${d.id}`)
.attr("id", function (d) { return d.id; })
.call(d3.drag()
.on("start", dragStarted)
.on("drag", dragged)
.on("end", dragEnded)
)
innerlayout
.nodes(innerGraph.nodes)
.on("tick", ticked)
innerlayout
.force("link")
.links(innerGraph.links)
//###############################################
//################## functons ###################
//###############################################
function ticked() {
outerLinks
.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;
});
outerNodes.attr("transform", function (d) {
return "translate(" + d.x + "," + d.y + ")";
});
innerNodes.attr("transform", function (d) {
var parent = d3.select(`.outer_${d.parent}`);
var pr = parent.node().getBoundingClientRect();
var prx = pr.left + (pr.width / 2);
var pry = pr.top + (pr.height / 2);
var distance = Math.sqrt( ((d.x - prx) ** 2) + ((d.y - pry) ** 2 ));
var ratio = 20 / distance;
var childX = ((1 - ratio) * prx) + (ratio * d.x);
var childY = ((1 - ratio) * pry) + (ratio * d.y);
return "translate(" + (childX) + "," + (childY) + ")";
});
innerLinks.attr("x1", d => {
var m1 = d3.select(`.child_${d.source.id}`).node().transform.baseVal[0].matrix;
return m1.e;
}).attr("y1", d => {
var m1 = d3.select(`.child_${d.source.id}`).node().transform.baseVal[0].matrix;
return m1.f;
}).attr("x2", d => {
var m2 = d3.select(`.child_${d.target.id}`).node().transform.baseVal[0].matrix;
return m2.e;
}).attr("y2", d => {
var m2 = d3.select(`.child_${d.target.id}`).node().transform.baseVal[0].matrix;
return m2.f;
});
}
function dragStarted(event, d) {
if (!event.active)
outerLayout.alphaTarget(0.3).restart();
innerlayout.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(event, d) {
d.fx = event.x;
d.fy = event.y;
}
function dragEnded(event, d) {
if (!event.active)
outerLayout.alphaTarget(0)
innerlayout.alphaTarget(0)
d.fx = undefined;
d.fy = undefined;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.5.0/d3.min.js"></script>
I am trying to create a simple tree map with D3.js but want to include the d3 code from an external file. I have 2 files, treemap.js and treemap.html. However, the treemap is not displaying and there is no errors popping up in the console. Am I referencing the d3 file correctly? Thank you.
This is the code inside the treemap.js
var tree = {
name: "tree",
children: [
{ name: "Word-wrapping comes for free in HTML", size: 16000 },
{ name: "animate makes things fun", size: 8000 },
{ name: "data data everywhere...", size: 5220 },
{ name: "display something beautiful", size: 3623 },
{ name: "flex your muscles", size: 984 },
{ name: "physics is religion", size: 6410 },
{ name: "query and you get the answer", size: 2124 }
]
};
var width = innerWidth-40,
height = innerHeight-40,
color = d3.scale.category20c(),
div = d3.select("#treemap").append("div")
.style("position", "relative");
var treemap = d3.layout.treemap()
.size([width, height])
.sticky(true)
.value(function(d) { return d.size; });
var node = div.datum(tree).selectAll(".node")
.data(treemap.nodes)
.enter().append("div")
.attr("class", "node")
.call(position)
.style("background-color", function(d) {
return d.name == 'tree' ? '#fff' : color(d.name); })
.append('div')
.style("font-size", function(d) {
// compute font size based on sqrt(area)
return Math.max(20, 0.18*Math.sqrt(d.area))+'px'; })
.text(function(d) { return d.children ? null : d.name; });
function position() {
this.style("left", function(d) { return d.x + "px"; })
.style("top", function(d) { return d.y + "px"; })
.style("width", function(d) { return Math.max(0, d.dx - 1) + "px"; })
.style("height", function(d) { return Math.max(0, d.dy - 1) + "px"; });
}
And this is the code inside the body tag inside the treemap.html
<body>
<div id="treemap"></div>
<script src="d3.v3.js"></script>
<script src="treemap.js"></script>
</body>
I am having an extremely difficult time with loading this custom JSON object into my Force Directed Graph. I currently have one node being plotted on the canvas but nothing else seems to be showing up, yet I see the JSON is coming through the variable. I know there are some mistake with the getElementById and other conventions but I am not concerned about that. I am more concerned with figuring out why my JSON object is not being loaded into D3. I believe the problem is in:
root = JSON.parse(jsonObject);
console.log("root"+root);
root.fixed = true;
root.x = w / 2;
root.y = h / 2 - 80;
update();
Here is the JSON Object:
{"nodes":[{"name":"Enrique_Acevedo","group":1,"size":1,"image":null},{"name":"DanaSenna","group":1,"size":3,"image":"http://pbs.twimg.com/profile_images/523240959111208960/f7yo6MeN_normal.jpeg"},{"name":"samspe3ks","group":1,"size":1,"image":"http://pbs.twimg.com/profile_images/639272140353568769/aMk9kLfV_normal.jpg"},{"name":"NRGMdaie","group":1,"size":1,"image":"http://pbs.twimg.com/profile_images/602232150822236160/QuZ9o-LY_normal.jpg"},{"name":"aPulaCVABBB","group":1,"size":5,"image":"http://pbs.twimg.com/profile_images/612764147353128961/SjqBEzvS_normal.jpg"},{"name":"amanda_paola","group":1,"size":1,"image":"http://pbs.twimg.com/profile_images/625547329463033856/fO_L38_I_normal.jpg"},{"name":"memoluna","group":1,"size":9,"image":"http://pbs.twimg.com/profile_images/603850856358744065/P1Y001yF_normal.jpg"},{"name":"chiquisholla","group":1,"size":20,"image":"http://pbs.twimg.com/profile_images/568655048419209216/_1nkyI3J_normal.jpeg"},{"name":"OrangeSky31","group":1,"size":4,"image":"http://pbs.twimg.com/profile_images/565820749345067009/WF1MuChB_normal.jpeg"},{"name":"megustanadar","group":1,"size":1,"image":"http://pbs.twimg.com/profile_images/604971301506281472/m9VNqFPA_normal.jpg"}],"links":[{"source":1,"target":0,"value":1},{"source":2,"target":0,"value":1},{"source":3,"target":0,"value":1},{"source":4,"target":0,"value":1},{"source":5,"target":0,"value":1},{"source":6,"target":0,"value":1},{"source":7,"target":0,"value":1},{"source":8,"target":0,"value":1},{"source":9,"target":0,"value":1}]}
And here is the original JS File:
function start(){
var w = 1200,
h = 600,
radius = 10,
node,
link,
root;
var count = 0;
var force = d3.layout.force()
.on("tick", tick)
.charge(function(d) { return -500; })
.linkDistance(function(d) { return d.target._children ? 100 : 50; })
.size([w, h - 160]);
var svg = d3.select("body").append("svg")
.attr("width", w)
.attr("height", h);
root = JSON.parse(jsonObject);
console.log("root"+root);
root.fixed = true;
root.x = w / 2;
root.y = h / 2 - 80;
update();
console.log("JsonObject2"+jsonObject)
function update() {
var nodes = flatten(root),
links = d3.layout.tree().links(nodes);
// Restart the force layout.
force
.nodes(nodes)
.links(links)
.start();
// Update the links…
link = svg.selectAll(".link")
.data(links);
// Enter any new links.
link.enter().insert("svg: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; });
// Exit any old links.
link.exit().remove();
// Update the nodes…
node = svg.selectAll("circle.node")
.data(nodes, function(d) {
return d.name;
})
.style("fill", color);
node.transition()
.attr("r", radius);
// Enter any new nodes.
node.enter().append("svg:circle")
.attr("xlink:href", function(d) { return d.image;})
.attr("class", "node")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("r", radius)
.style("fill", color)
.on("click", click)
.call(force.drag);
node.append("title")
.text(function(d) { return d.name; });
// Exit any old nodes.
node.exit().remove();
title = svg.selectAll("text.title")
.data(nodes);
// Enter any new titles.
title.enter()
.append("text")
.attr("class", "title");
//.text(function(d) { return d.name; });
// Exit any old titles.
title.exit().remove();
}
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; });
title.attr("transform", function(d){ return "translate("+d.x+","+d.y+")"; });
}
function checkTitle() {
}
// Color leaf nodes orange, and packages white or blue.
function color(d) {
if(d._children){
return "#95a5a6";
}else{
switch(d.group) {
case 'r': //adverb
return "#e74c3c";
break;
case 'n': //noun
return "#3498db";
break;
case 'v': //verb
return "#2ecc71";
break;
case 's': //adjective
return "#e78229";
break;
default:
return "#9b59b6";
}
}
}
// Toggle children on click.
function click(d) {
document.getElementById("image").src = d.image;
document.getElementById("username").innerHTML = "Username:"+d.name;
document.getElementById("id").innerHTML = "ID:" + d.id;
document.getElementById("friends").innerHTML = d.friend;
document.getElementById("nodeTitle").innerHTML = "";
//document.getElementById("id").innerHTML = "Friend Count:" + d.name;
//if (d._children)
//grabImage();
//document.getElementById("image").innerHTML = (d.image);
/*if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update();*/
}
function mouseover() {
d3.select(this).select("circle").transition()
.duration(750)
.attr("r", 16);
}
function mouseout() {
d3.select(this).select("circle").transition()
.duration(750)
.attr("r", 8);
}
// Returns a list of all nodes under the root.
function flatten(root) {
var nodes = [], i = 0;
function recurse(node) {
if (node.children) node.size = node.children.reduce(function(p, v) { return p + recurse(v); }, 0);
if (!node.id) node.id = ++i;
nodes.push(node);
return node.size;
}
root.size = recurse(root);
return nodes;
}};
do{
var intervalID = window.setTimeout(start, 1000)
}
while(jsonObject!=""){
}
Your input data is wrong. nodes must be an array; you are passing it an object. You don't really need the flatten function here, your data is already flat. Also, you shouldn't need to call d3.layout.tree().links as your link data is also already formatted correctly:
function update() {
var nodes = root.nodes,
links = root.links;
Here's your code working:
<!DOCTYPE html>
<html>
<head>
<script data-require="d3#3.5.3" data-semver="3.5.3" src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.3/d3.js"></script>
<style>
.node {
cursor: pointer;
stroke: #3182bd;
stroke-width: 1.5px;
}
.link {
fill: none;
stroke: #9ecae1;
stroke-width: 1.5px;
}
</style>
</head>
<body>
<script>
var jsonObject = {
"nodes": [{
"name": "Enrique_Acevedo",
"group": 1,
"size": 1,
"image": null
}, {
"name": "DanaSenna",
"group": 1,
"size": 3,
"image": "http://pbs.twimg.com/profile_images/523240959111208960/f7yo6MeN_normal.jpeg"
}, {
"name": "samspe3ks",
"group": 1,
"size": 1,
"image": "http://pbs.twimg.com/profile_images/639272140353568769/aMk9kLfV_normal.jpg"
}, {
"name": "NRGMdaie",
"group": 1,
"size": 1,
"image": "http://pbs.twimg.com/profile_images/602232150822236160/QuZ9o-LY_normal.jpg"
}, {
"name": "aPulaCVABBB",
"group": 1,
"size": 5,
"image": "http://pbs.twimg.com/profile_images/612764147353128961/SjqBEzvS_normal.jpg"
}, {
"name": "amanda_paola",
"group": 1,
"size": 1,
"image": "http://pbs.twimg.com/profile_images/625547329463033856/fO_L38_I_normal.jpg"
}, {
"name": "memoluna",
"group": 1,
"size": 9,
"image": "http://pbs.twimg.com/profile_images/603850856358744065/P1Y001yF_normal.jpg"
}, {
"name": "chiquisholla",
"group": 1,
"size": 20,
"image": "http://pbs.twimg.com/profile_images/568655048419209216/_1nkyI3J_normal.jpeg"
}, {
"name": "OrangeSky31",
"group": 1,
"size": 4,
"image": "http://pbs.twimg.com/profile_images/565820749345067009/WF1MuChB_normal.jpeg"
}, {
"name": "megustanadar",
"group": 1,
"size": 1,
"image": "http://pbs.twimg.com/profile_images/604971301506281472/m9VNqFPA_normal.jpg"
}],
"links": [{
"source": 1,
"target": 0,
"value": 1
}, {
"source": 2,
"target": 0,
"value": 1
}, {
"source": 3,
"target": 0,
"value": 1
}, {
"source": 4,
"target": 0,
"value": 1
}, {
"source": 5,
"target": 0,
"value": 1
}, {
"source": 6,
"target": 0,
"value": 1
}, {
"source": 7,
"target": 0,
"value": 1
}, {
"source": 8,
"target": 0,
"value": 1
}, {
"source": 9,
"target": 0,
"value": 1
}]
};
start();
function start() {
var w = 1200,
h = 600,
radius = 10,
node,
link,
root;
var count = 0;
var force = d3.layout.force()
.on("tick", tick)
.charge(function(d) {
return -500;
})
.linkDistance(function(d) {
return d.target._children ? 100 : 50;
})
.size([w, h - 160]);
var svg = d3.select("body").append("svg")
.attr("width", w)
.attr("height", h);
root = jsonObject;
console.log("root" + root);
root.fixed = true;
root.x = w / 2;
root.y = h / 2 - 80;
update();
console.log("JsonObject2" + jsonObject)
function update() {
var nodes = root.nodes,
links = root.links;
// Restart the force layout.
force
.nodes(nodes)
.links(links)
.start();
// Update the links…
link = svg.selectAll(".link")
.data(links);
// Enter any new links.
link.enter().insert("svg: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;
});
// Exit any old links.
link.exit().remove();
// Update the nodes…
node = svg.selectAll("circle.node")
.data(nodes, function(d) {
return d.name;
})
.style("fill", color);
node.transition()
.attr("r", radius);
// Enter any new nodes.
node.enter().append("svg:circle")
.attr("xlink:href", function(d) {
return d.image;
})
.attr("class", "node")
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
})
.attr("r", radius)
.style("fill", color)
.on("click", click)
.call(force.drag);
node.append("title")
.text(function(d) {
return d.name;
});
// Exit any old nodes.
node.exit().remove();
title = svg.selectAll("text.title")
.data(nodes);
// Enter any new titles.
title.enter()
.append("text")
.attr("class", "title");
//.text(function(d) { return d.name; });
// Exit any old titles.
title.exit().remove();
}
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;
});
title.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
}
function checkTitle() {}
// Color leaf nodes orange, and packages white or blue.
function color(d) {
if (d._children) {
return "#95a5a6";
} else {
switch (d.group) {
case 'r': //adverb
return "#e74c3c";
break;
case 'n': //noun
return "#3498db";
break;
case 'v': //verb
return "#2ecc71";
break;
case 's': //adjective
return "#e78229";
break;
default:
return "#9b59b6";
}
}
}
// Toggle children on click.
function click(d) {
document.getElementById("image").src = d.image;
document.getElementById("username").innerHTML = "Username:" + d.name;
document.getElementById("id").innerHTML = "ID:" + d.id;
document.getElementById("friends").innerHTML = d.friend;
document.getElementById("nodeTitle").innerHTML = "";
//document.getElementById("id").innerHTML = "Friend Count:" + d.name;
//if (d._children)
//grabImage();
//document.getElementById("image").innerHTML = (d.image);
/*if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update();*/
}
function mouseover() {
d3.select(this).select("circle").transition()
.duration(750)
.attr("r", 16);
}
function mouseout() {
d3.select(this).select("circle").transition()
.duration(750)
.attr("r", 8);
}
// Returns a list of all nodes under the root.
function flatten(root) {
var nodes = [],
i = 0;
function recurse(node) {
if (node.children) node.size = node.children.reduce(function(p, v) {
return p + recurse(v);
}, 0);
if (!node.id) node.id = ++i;
nodes.push(node);
return node.size;
}
root.size = recurse(root);
return nodes;
}
}
</script>
</body>
</html>