d3.js - force simulation graph not visiable - d3.js

I would like to draw force simulation but the nodes not show on page correctly!
demo()
function demo() {
var width = 600, height = 400;
var svg = d3.select('body').append('svg')
.attr('width',width)
.attr('height',height)
.style('border','1px solid red')
var g = svg.append('g')
var colorScale = ['orange', 'lightblue', '#B19CD9'];
var xCenter = [width/4, width*2/4, width*3/4];
var numNodes = 100;
var nodes1 = d3.range(numNodes).map(function(d, i) {
return {
radius: 15,// Math.random() * 25,
category: i % 3
}
});
var links = [
{source: 0, target: 1},
{source: 0, target: 2},
{source: 0, target: 3},
{source: 1, target: 6},
{source: 3, target: 4},
{source: 3, target: 7},
{source: 4, target: 5},
{source: 4, target: 7}
]
var radius = 8
var nodes = []
links.forEach(function(d) {
nodes[d.source] ||
(nodes[d.source] = {name:d.source})
nodes[d.target] ||
(nodes[d.target] = {name:d.target})
})
var simulation = d3.forceSimulation(nodes)
.force("collide", d3.forceCollide(radius))
.force("x", d3.forceX().x(function(d) {return width/2;}).strength(5))
.force("y", d3.forceY().y(function(d) {return d.dy;}).strength(.5))
var debug = true
if (debug) {
simulation.tick(10).stop();
ticked()
}else {
simulation.on('tick', ticked);
}
function ticked() {
d3.select('g').selectAll('.link')
.data(links)
.join('path')
.attr('class','link')
.attr('stroke','black')
.attr('d',function(d) {
var path = ['M',d.source.x,d.source.y,'L',
d.target.x,d.target.y]
return path.join(' ')
})
d3.select('g').selectAll('circle')
.data(nodes)
.join('circle')
.attr('r', function(d) {
return radius;
})
.style('fill', function(d) {
return colorScale[0];
})
.attr('cx', function(d) {
return d.x;
})
.attr('cy', function(d) {
return d.y;
});
}
}
<script src="https://unpkg.com/d3#7.0.4/dist/d3.min.js"></script>

Related

d3.js - Add background rectangle on force directed diagram groups

I would like to add a background rectangle to group 2, the idea is add a g element and append all group 2 nodes to the g element, then use g element bbox to draw a rectangle.
But I don't know how to move exist nodes to g element! (Maybe not possible?).
Example code as below:
var graph = {
nodes:[
{id: "A",name:'AAAA', group: 1},
{id: "B", name:'BBBB',group: 2},
{id: "C", name:'CCCC',group: 2},
{id: "D", name:'DDDD',group: 2},
{id: "E", name:'EEEE',group: 2},
{id: "F", name:'FFFF',group: 3},
{id: "G", name:'GGGG',group: 3},
{id: "H", name:'HHHH',group: 3},
{id: "I", name:'IIII',group: 3}
],
links:[
{source: "A", target: "B", value: 1},
{source: "A", target: "C", value: 1},
{source: "A", target: "D", value: 1},
{source: "A", target: "E", value: 1},
{source: "A", target: "F", value: 1},
{source: "A", target: "G", value: 1},
{source: "A", target: "H", value: 1},
{source: "A", target: "I", value: 1},
]
};
var width = 400
var height = 200
var svg = d3.select('body').append('svg')
.attr('width',width)
.attr('height',height)
.style('border','1px solid red')
var color = d3.scaleOrdinal(d3.schemeCategory10);
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) { return d.id; }).distance(100))
.force("charge", d3.forceManyBody())
.force("x", d3.forceX(function(d){
if(d.group === 2){
return width/3
} else if (d.group === 3){
return 2*width/3
} else {
return width/2
}
}))
.force("y", d3.forceY(height/2))
.force("center", d3.forceCenter(width / 2, height / 2));
var g = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(graph.nodes)
.enter()
var w = 80
var txts = g.append('text')
.attr('class','text')
.attr('text-anchor','middle')
.attr("dominant-baseline", "central")
.attr('fill','black')
.text(d => d.name)
.each((d,i,n) => {
var bbox = d3.select(n[i]).node().getBBox()
var margin = 4
bbox.x -= margin
bbox.y -= margin
bbox.width += 2*margin
bbox.height += 2*margin
if (bbox.width < w) {
bbox.width = w
}
d.bbox = bbox
})
var node = g
.insert('rect','text')
.attr('stroke','black')
.attr('width', d => d.bbox.width)
.attr('height',d => d.bbox.height)
.attr("fill", function(d) { return color(d.group); })
.attr('fill-opacity',0.3)
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
var link = svg.append("g")
.attr("class", "links")
.attr('stroke','black')
.selectAll("line")
.data(graph.links)
.enter().append("path")
.attr("stroke-width", function(d) { return Math.sqrt(d.value); });
simulation
.nodes(graph.nodes)
.on("tick", ticked);
simulation.force("link")
.links(graph.links);
function ticked() {
link
.attr("d", function(d) {
var ax = d.source.x
var ay = d.source.y
var bx = d.target.x
var by = d.target.y
if (bx < ax) {
ax -= w/2
bx += w/2
}else{
ax += w/2
bx -= w/2
}
var path = ['M',ax,ay,'L',bx,by]
return path.join(' ')
})
txts.attr('x',d => d.x)
.attr('y',d => d.y)
node
.attr("x", function(d) { return d.x - d.bbox.width/2; })
.attr("y", function(d) { return d.y - d.bbox.height/2; });
}
function dragstarted(event,d) {
if (!event.active) simulation.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) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.7.0/d3.min.js"></script>
The force simulation doesn't use the DOM for anything. It merely calculates where nodes should be, how you render them, if you render them, is up to you. So putting some nodes in a g but not others is not a problem. For example, we could add a g for group 2, run through all the nodes, detach them from the DOM if they are from group 2 and reappend them to the new g:
var parent = d3.select("g").append("g").lower();
node.each(function(d) {
if (d.group == 2) {
d3.select(this).remove();
parent.append((d)=>this);
}
})
Then all we need to do is create a background rectangle:
var background = d3.select("g")
.append("rect")
.lower() // so it is behind the nodes.
....
And update it on tick with a new bounding box of the g, as shown below.
var graph = {
nodes:[
{id: "A",name:'AAAA', group: 1},
{id: "B", name:'BBBB',group: 2},
{id: "C", name:'CCCC',group: 2},
{id: "D", name:'DDDD',group: 2},
{id: "E", name:'EEEE',group: 2},
{id: "F", name:'FFFF',group: 3},
{id: "G", name:'GGGG',group: 3},
{id: "H", name:'HHHH',group: 3},
{id: "I", name:'IIII',group: 3}
],
links:[
{source: "A", target: "B", value: 1},
{source: "A", target: "C", value: 1},
{source: "A", target: "D", value: 1},
{source: "A", target: "E", value: 1},
{source: "A", target: "F", value: 1},
{source: "A", target: "G", value: 1},
{source: "A", target: "H", value: 1},
{source: "A", target: "I", value: 1},
]
};
var width = 400
var height = 200
var svg = d3.select('body').append('svg')
.attr('width',width)
.attr('height',height)
.style('border','1px solid red')
var color = d3.scaleOrdinal(d3.schemeCategory10);
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) { return d.id; }).distance(100))
.force("charge", d3.forceManyBody())
.force("x", d3.forceX(function(d){
if(d.group === 2){
return width/3
} else if (d.group === 3){
return 2*width/3
} else {
return width/2
}
}))
.force("y", d3.forceY(height/2))
.force("center", d3.forceCenter(width / 2, height / 2));
var g = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(graph.nodes)
.enter()
var w = 80
var txts = g.append('text')
.attr('class','text')
.attr('text-anchor','middle')
.attr("dominant-baseline", "central")
.attr('fill','black')
.text(d => d.name)
.each((d,i,n) => {
var bbox = d3.select(n[i]).node().getBBox()
var margin = 4
bbox.x -= margin
bbox.y -= margin
bbox.width += 2*margin
bbox.height += 2*margin
if (bbox.width < w) {
bbox.width = w
}
d.bbox = bbox
})
var node = g
.insert('rect','text')
.attr('stroke','black')
.attr('width', d => d.bbox.width)
.attr('height',d => d.bbox.height)
.attr("fill", function(d) { return color(d.group); })
.attr('fill-opacity',0.3)
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
// Start Changes 1/2
var parent = d3.select("g").append("g").lower();
node.each(function(d) {
if (d.group == 2) {
d3.select(this).remove();
parent.append((d)=>this);
}
})
var background = d3.select("g")
.append("rect")
.lower()
.attr("ry", 5)
.attr("rx", 5)
.attr("fill","#ccc")
.attr("stroke","#999")
.attr("stroke-width", 1);
// End Changes 1/2
var link = svg.append("g")
.attr("class", "links")
.attr('stroke','black')
.selectAll("line")
.data(graph.links)
.enter().append("path")
.attr("stroke-width", function(d) { return Math.sqrt(d.value); });
simulation
.nodes(graph.nodes)
.on("tick", ticked);
simulation.force("link")
.links(graph.links);
function ticked() {
link
.attr("d", function(d) {
var ax = d.source.x
var ay = d.source.y
var bx = d.target.x
var by = d.target.y
if (bx < ax) {
ax -= w/2
bx += w/2
}else{
ax += w/2
bx -= w/2
}
var path = ['M',ax,ay,'L',bx,by]
return path.join(' ')
})
txts.attr('x',d => d.x)
.attr('y',d => d.y)
node
.attr("x", function(d) { return d.x - d.bbox.width/2; })
.attr("y", function(d) { return d.y - d.bbox.height/2; });
// Start changes 2/2
var box = parent.node().getBBox()
background.attr("width", box.width+10)
.attr("height",box.height+10)
.attr("x", box.x-5)
.attr("y", box.y-5);
//End Changes 2/2
}
function dragstarted(event,d) {
if (!event.active) simulation.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) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.7.0/d3.js"></script>
If you wanted more than one group, or had dynamic data, this approach isn't ideal - the join or the data structure would need to be modified a bit to make a more canonical approach work - I might revisit it later tonight with an alternative. As is, this solution is likely the least invasive with respect to your existing code.

d3.js - select force layout group not update the nodes

Below example should update the nodes after select the groups at the bottom. but the select works fine, the nodes on the svg not updated!
force_json()
function force_json(){
var svg = d3.select('body').append('svg')
.attr('width',200).attr('height',100)
.style('border','1px solid red'),
width = +svg.attr("width"),
height = +svg.attr("height");
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) { return d.id; }))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width / 2, height / 2));
var color = d3.scaleOrdinal(d3.schemeCategory10);
var jsonobj = {
"nodes": [
{"id": "Myriel", "group": 1},
{"id": "Napoleon", "group": 2},
{"id": "Mlle.Baptistine", "group": 3},
{"id": "Mme.Magloire", "group": 1},
],
"links": [
{"source": "Napoleon", "target": "Myriel", "value": 10},
{"source": "Mlle.Baptistine", "target": "Myriel", "value": 8},
{"source": "Mme.Magloire", "target": "Myriel", "value": 10},
]
}
// d3.json("miserables.json",function(error, graph) {
// if (error) throw error;
// });
process_data(jsonobj)
function process_data(graph) {
var currNodes = graph.nodes
var currLinks = graph.links
var nodesByGroup = d3.group(graph.nodes,d => d.group)
var catMenu = d3.select("body").append('div')
catMenu
.append("select")
.selectAll("option")
.data(nodesByGroup)
.enter()
.append("option")
.attr("value", function(d,i) {
return d[0];
})
.text(function(d,i){
return d[0];
})
var link = svg.append("g")
.attr("class", "links")
.selectAll("line")
.data(currLinks)
.enter().append("line")
.attr('stroke','#aaa')
var node = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(currNodes)
.enter().append("circle")
.attr("r", 5)
.attr('pointer-events','all')
.attr('stroke','none')
.attr('stroke-wdith',40)
.attr("fill", function(d) { return color(d.group);})
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
node.append("title")
.text(function(d) { return d.id; });
node.append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.text(function(d) {return d.id;})
simulation
.nodes(currNodes)
.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("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
}
catMenu.on('change', function(){
var selectedGroup = +d3.select(this)
.select("select")
.property("value");
currNodes = filterNodes(selectedGroup);
});
function filterNodes(group) {
var filteredNodes = nodesByGroup.get(group)
return filteredNodes;
}
}
function dragstarted(event,d) {
if (!event.active) simulation.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) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.7.0/d3.min.js"></script>
Re-render filtered nodes and links upon group selection:
force_json()
function force_json(){
var svg = d3.select('body').append('svg')
.attr('width',200).attr('height',200)
.style('border','1px solid red'),
width = +svg.attr("width"),
height = +svg.attr("height");
var color = d3.scaleOrdinal(d3.schemeCategory10);
var jsonobj = {
"nodes": [
{"id": "Myriel", "group": 1},
{"id": "Napoleon", "group": 2},
{"id": "Mlle.Baptistine", "group": 3},
{"id": "Mme.Magloire", "group": 1},
{"id": "A", "group": 2},
{"id": "B", "group": 3},
{"id": "C", "group": 3}
],
"links": [
{"source": "Napoleon", "target": "Myriel", "value": 10},
{"source": "Napoleon", "target": "A", "value": 10},
{"source": "Mlle.Baptistine", "target": "Myriel", "value": 8},
{"source": "Mlle.Baptistine", "target": "B", "value": 8},
{"source": "Mme.Magloire", "target": "Myriel", "value": 10},
{"source": "A", "target": "B", "value": 10},
{"source": "C", "target": "B", "value": 10},
]
}
process_data(jsonobj)
function process_data(graph) {
var nodesByGroup = d3.group(graph.nodes,d => d.group)
var catMenu = d3.select("body").append('div')
catMenu
.append("select")
.selectAll("option")
.data(nodesByGroup)
.enter()
.append("option")
.attr("value", function(d,i) {
return d[0];
})
.text(function(d,i){
return d[0];
})
catMenu.select('select').append('option').text('all').attr("selected", "selected");
const updateGraph = (nodes, links) => {
const simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) { return d.id; }))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width / 2, height / 2));
svg.selectAll('g').remove();
var link = svg.append("g")
.attr("class", "links")
.selectAll("line")
.data(links)
.enter().append("line")
.attr('stroke','#aaa')
var node = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(nodes)
.enter().append("circle")
.attr("r", 5)
.attr('pointer-events','all')
.attr('stroke','none')
.attr('stroke-wdith',40)
.attr("fill", function(d) { return color(d.group);})
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
node.append("title")
.text(d => d.id);
node.append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.text(function(d) {return d.id;})
simulation
.nodes(nodes)
.on("tick", ticked);
simulation.force("link")
.links(links);
function ticked() {
link
.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
}
function dragstarted(event,d) {
if (!event.active) simulation.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) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
}
updateGraph(graph.nodes, graph.links);
catMenu.on('change', function(){
var selectedGroup = d3.select(this)
.select("select")
.property("value");
let nodes, links;
if (selectedGroup === 'all') {
nodes = graph.nodes;
links = graph.links;
} else {
const group = parseInt(selectedGroup);
nodes = graph.nodes.filter(n => n.group === group);
console.log(graph.links)
links = graph.links.filter(link => {
const source = nodes.find(n => n.id === link.source.id);
const target = nodes.find(n => n.id === link.target.id);
return source && target;
})
}
updateGraph(nodes, links);
});
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.7.0/d3.min.js"></script>

How to change color of nodes in force directed graph

I am new at d3.js. I am working with force directed graph. I made 3d nodes of graph. All nodes have same color. I want to change color of nodes and want to apply random colors. Can anybody help me?
here is my code:
var w = 1000,
h = 800,
circleWidth = 5;
var palette = {
"lightgray": "#E5E8E8",
"gray": "#708284",
"mediumgray": "#536870",
"blue": "#3B757F"
}
var colors = d3.scale.category20();
var nodes = [
{ name: "My Skills"},
{ name: "HTML5", target: [0], value: 58 },
{ name: "CSS3", target: [0, 1], value: 65 },
{ name: ".NET", target: [0, 3], value: 48 },
{ name: "Java", target: [0,3,4], value: 40 },
{ name: "jQuery", target: [0, 1, 2], value: 52 },
{ name: "Photoshop", target: [0, 1, 2, 8], value: 37 },
{ name: "PHP", target: [0,1,2], value: 20 },
{ name: "Wordpress", target: [0,1,2,3,9], value: 67 },
{ name: "d3", target: [0,1,2,7,8], value: 25 },
{ name: "Angular", target: [0,1,2,7,8], value: 25 },
{ name: "Adobe CS", target: [0,1,2,12], value: 57 },
{ name: "mySql", target: [0,9,10], value: 20 },
];
var links = [];
for (var i = 0; i < nodes.length; i++){
if (nodes[i].target !== undefined) {
for ( var x = 0; x < nodes[i].target.length; x++ )
links.push({
source: nodes[i],
target: nodes[nodes[i].target[x]]
});
};
};
var myChart = d3.select('body')
.append("div")
.classed("svg-container", true)
.append('svg')
.attr("preserveAspectRatio", "xMinYMin meet")
.attr("viewBox", "0 0 1000 800")
.classed("svg-content-responsive", true)
var force = d3.layout.force()
.nodes(nodes)
.links([])
.gravity(0.1)
.charge(-1000)
.size([w,h]);
var link = myChart.selectAll('line')
.data(links).enter().append('line')
.attr('stroke', palette.lightgray)
.attr('strokewidth', '1');
var node = myChart.selectAll('circle')
.data(nodes).enter()
.append('g')
.call(force.drag);
var grads = myChart.append("defs").selectAll("radialGradient")
.data(nodes)
.enter()
.append("radialGradient")
.attr("gradientUnits", "objectBoundingBox")
.attr("cx","20%")
.attr("cy", "30%")
.attr("r", "100%")
.attr("id", function(d, i) { return "grad" + i; });
grads.append("stop")
.attr("offset", "0%")
.style("stop-color", "white");
grads.append("stop")
.attr("offset", "100%")
.style("stop-color", function(d) { return colors(d.cluster); });
node.append('circle')
.attr('cx', function(d){return d.x; })
.attr('cy', function(d){return d.y; })
.attr('fx', function(d){return d.x; })
.attr('fy', function(d){return d.y; })
.attr('r', function(d,i){
console.log(d.value);
if ( i > 0 ) {
return circleWidth + d.value;
} else {
return circleWidth + 35;
}
})
.attr('fill', function(d,i){
if ( i > 0 ) {
return "url(#grad" + i + ")";
} else {
return '#fff';
}
})
.attr('strokewidth', function(d,i){
if ( i > 0 ) {
return '0';
} else {
return '2';
}
})
.attr('stroke', function(d,i){
if ( i > 0 ) {
return '';
} else {
return 'black';
}
});
force.on('tick', function(e){
node.attr('transform', function(d, i){
return 'translate(' + d.x + ','+ d.y + ')'
})
link
.attr('x1', function(d){ return d.source.x; })
.attr('y1', function(d){ return d.source.y; })
.attr('x2', function(d){ return d.target.x; })
.attr('y2', function(d){ return d.target.y; })
});
node.append('text')
.text(function(d){ return d.name; })
.attr('font-family', 'Raleway', 'Helvetica Neue, Helvetica')
.attr('fill', function(d, i){
console.log(d.value);
if ( i > 0 && d.value < 10 ) {
return palette.mediumgray;
} else if ( i > 0 && d.value >10 ) {
return palette.lightgray;
} else {
return palette.blue;
}
})
.attr('text-anchor', function(d, i) {
return 'middle';
})
.attr('font-size', function(d, i){
if (i > 0) {
return '.8em';
} else {
return '.9em';
}
})
force.start();
You have to use node index instead of d.cluster in your code because there are no multiple clusters in your data array.
grads.append("stop")
.attr("offset", "100%")
.style("stop-color", function(d,i) {
return colors(i);
});
var w = 1000,
h = 800,
circleWidth = 5;
var palette = {
"lightgray": "#E5E8E8",
"gray": "#708284",
"mediumgray": "#536870",
"blue": "#3B757F"
}
var colors = d3.scale.category20();
var nodes = [{
name: "My Skills"
}, {
name: "HTML5",
target: [0],
value: 58
}, {
name: "CSS3",
target: [0, 1],
value: 65
}, {
name: ".NET",
target: [0, 3],
value: 48
}, {
name: "Java",
target: [0, 3, 4],
value: 40
}, {
name: "jQuery",
target: [0, 1, 2],
value: 52
}, {
name: "Photoshop",
target: [0, 1, 2, 8],
value: 37
}, {
name: "PHP",
target: [0, 1, 2],
value: 20
}, {
name: "Wordpress",
target: [0, 1, 2, 3, 9],
value: 67
}, {
name: "d3",
target: [0, 1, 2, 7, 8],
value: 25
}, {
name: "Angular",
target: [0, 1, 2, 7, 8],
value: 25
}, {
name: "Adobe CS",
target: [0, 1, 2, 12],
value: 57
}, {
name: "mySql",
target: [0, 9, 10],
value: 20
}, ];
var links = [];
for (var i = 0; i < nodes.length; i++) {
if (nodes[i].target !== undefined) {
for (var x = 0; x < nodes[i].target.length; x++)
links.push({
source: nodes[i],
target: nodes[nodes[i].target[x]]
});
};
};
var myChart = d3.select('body')
.append("div")
.classed("svg-container", true)
.append('svg')
.attr("preserveAspectRatio", "xMinYMin meet")
.attr("viewBox", "0 0 1000 800")
.classed("svg-content-responsive", true)
var force = d3.layout.force()
.nodes(nodes)
.links([])
.gravity(0.1)
.charge(-1000)
.size([w, h]);
var link = myChart.selectAll('line')
.data(links).enter().append('line')
.attr('stroke', palette.lightgray)
.attr('strokewidth', '1');
var node = myChart.selectAll('circle')
.data(nodes).enter()
.append('g')
.call(force.drag);
var grads = myChart.append("defs").selectAll("radialGradient")
.data(nodes)
.enter()
.append("radialGradient")
.attr("gradientUnits", "objectBoundingBox")
.attr("cx", "20%")
.attr("cy", "30%")
.attr("r", "100%")
.attr("id", function(d, i) {
return "grad" + i;
});
grads.append("stop")
.attr("offset", "0%")
.style("stop-color", "white");
grads.append("stop")
.attr("offset", "100%")
.style("stop-color", function(d, i) {
return colors(i);
});
node.append('circle')
.attr('cx', function(d) {
return d.x;
})
.attr('cy', function(d) {
return d.y;
})
.attr('fx', function(d) {
return d.x;
})
.attr('fy', function(d) {
return d.y;
})
.attr('r', function(d, i) {
if (i > 0) {
return circleWidth + d.value;
} else {
return circleWidth + 35;
}
})
.attr('fill', function(d, i) {
if (i > 0) {
return "url(#grad" + i + ")";
} else {
return '#fff';
}
})
.attr('strokewidth', function(d, i) {
if (i > 0) {
return '0';
} else {
return '2';
}
})
.attr('stroke', function(d, i) {
if (i > 0) {
return '';
} else {
return 'black';
}
});
force.on('tick', function(e) {
node.attr('transform', function(d, i) {
return 'translate(' + d.x + ',' + d.y + ')'
})
link
.attr('x1', function(d) {
return d.source.x;
})
.attr('y1', function(d) {
return d.source.y;
})
.attr('x2', function(d) {
return d.target.x;
})
.attr('y2', function(d) {
return d.target.y;
})
});
node.append('text')
.text(function(d) {
return d.name;
})
.attr('font-family', 'Raleway', 'Helvetica Neue, Helvetica')
.attr('fill', function(d, i) {
if (i > 0 && d.value < 10) {
return palette.mediumgray;
} else if (i > 0 && d.value > 10) {
return palette.lightgray;
} else {
return palette.blue;
}
})
.attr('text-anchor', function(d, i) {
return 'middle';
})
.attr('font-size', function(d, i) {
if (i > 0) {
return '.8em';
} else {
return '.9em';
}
})
force.start();
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
You can do like this:
define a color scale
var color = d3.scale.category10();
Then in the fill of the circle do:
.attr('fill', function(d,i){
return color(i);
})

Flip the x-axis of a D3 Stacked Bar Chart

Chapter 11 of "Interactive Data Visualization for the Web" shows how to create stacked bar charts with the D3.js library. The example produces an upside down chart with the bars attached to the top of the x-axis.
Flipping the chart and attaching them to the bottom is left as an exercise for the reader.
Given this starting point:
<script src="http://d3js.org/d3.v3.min.js"></script>
<script type="text/javascript">
var w = 500;
var h = 300;
var dataset = [[ { x: 0, y: 5 }, { x: 1, y: 4 }, { x: 2, y: 2 }, { x: 3, y: 7 }, { x: 4, y: 23 }],
[ { x: 0, y: 10 }, { x: 1, y: 12 }, { x: 2, y: 19 }, { x: 3, y: 23 }, { x: 4, y: 17 } ],
[ { x: 0, y: 22 }, { x: 1, y: 28 }, { x: 2, y: 32 }, { x: 3, y: 35 }, { x: 4, y: 43 } ]];
var stack = d3.layout.stack();
stack(dataset);
var xScale = d3.scale.ordinal()
.domain(d3.range(dataset[0].length)).rangeRoundBands([0, w], 0.05);
var yScale = d3.scale.linear()
.domain([0, d3.max(dataset, function(d) {
return d3.max(d, function(d) { return d.y0 + d.y; });
})])
.range([0, h]);
var colors = d3.scale.category10();
var svg = d3.select("body").append("svg").attr("width", w).attr("height", h);
var groups = svg.selectAll("g").data(dataset).enter().append("g")
.style("fill", function(d, i) { return colors(i); });
var rects = groups.selectAll("rect")
.data(function(d) { return d; })
.enter()
.append("rect")
.attr("x", function(d, i) { return xScale(i); })
.attr("y", function(d) { return yScale(d.y0); })
.attr("height", function(d) { return yScale(d.y); })
.attr("width", xScale.rangeBand());
</script>
What needs to be done to flip the chart?
The solution I came up with involves three changes:
Change the yScale .range from:
.range([0, h]);
to:
.range([h, 0]);
Change the rect "y" .attr from:
.attr("y", function(d) { return yScale(d.y0); })
to:
.attr("y", function(d) { return yScale(d.y0) + yScale(d.y) - h; })
Change the rect "height" .attr from:
.attr("height", function(d) { return yScale(d.y); })
to:
.attr("height", function(d) { return h - yScale(d.y); })
With those changes applied, the stacks attach to the bottom and still maintain their relative sizes.

Appending multiline text to nodes in directed graphs in d3js

I am trying to create multi line text as nodes of the directed graph as :
<rect height="27" width="56" rx="100" ry="100" style="fill: #ffffff;"></rect>
<text dx="6" dy="6">
<tspan x="0" dy="15">Donovan</tspan>
<tspan x="0" dy="15">3</tspan>
<tspan x="0" dy="15">what</tspan>
</text>
as seen in: http://jsfiddle.net/nikosdim/S4eaL/1/
I currently have this:
// setting up parameters to be use through rest of the code
var w = 2000;
var h = 800;
var r = 30;
var linkDistance = 100;
var boxHeight = 50;
var boxWidth = 50;
var colors = d3.scale.category10();
// This is what how we should be setting gravity, theta and charge ideally: http://stackoverflow.com/questions/9901565/charge-based-on-size-d3-force-layout
var charge = -5000;
var gravity = 0.3;
var theta = 0.01;
var dataset = {
nodes: [
{ "name": "Adam", "id": "0" },
{ "name": "Bob", "id": "1" },
{ "name": "Carrie", "id": "2" },
{ "name": "Donovan", "id": "3" },
{ "name": "Edward", "id": "4" },
{ "name": "Felicity", "id": "5" },
{ "name": "George", "id": "6" },
{ "name": "Hannah", "id": "7" },
{ "name": "Iris", "id": "8" },
{ "name": "Jerry", "id": "9" }
],
edges: [
{ "source": 0, "target": 4 },
{ "source": 1, "target": 5 },
{ "source": 2, "target": 5 },
{ "source": 2, "target": 5 },
{ "source": 5, "target": 8 },
{ "source": 5, "target": 9 },
{ "source": 6, "target": 7 },
{ "source": 7, "target": 8 },
{ "source": 8, "target": 9 }
]
};
var svg = d3.select("body").append("svg").attr({ "width": w, "height": h });
var force = d3.layout.force()
.nodes(dataset.nodes)
.links(dataset.edges)
.size([w, h])
.linkDistance([linkDistance])
.charge(charge)
.theta(theta)
.gravity(gravity);
var edges = svg.selectAll("line")
.data(dataset.edges)
.enter()
.append("line")
.attr("id", function (d, i) { return 'edge' + i; })
.attr('marker-end', 'url(#arrowhead)')
.style("stroke", "#ccc")
.style("pointer-events", "none");
var nodes = svg.selectAll("rect")
.data(dataset.nodes)
.enter()
.append("rect")
.attr({ "width": boxWidth })
.attr({ "height": boxHeight })
//.style("fill", function (d, i) { return colors(i); })
.style("fill", 'white')
.attr('stroke', 'black')
.call(force.drag);
var nodelabels = svg.selectAll(".nodelabel")
.data(dataset.nodes)
.enter()
.append("text")
.attr({
//"x": function (d) { return d.x; },
//"y": function (d) { return d.y; },
"class": "nodelabel",
"stroke": "black"
});
var edgepaths = svg.selectAll(".edgepath")
.data(dataset.edges)
.enter()
.append('path')
.attr({
//'d': function (d) { return 'M ' + d.source.x + ' ' + d.source.y + ' L ' + d.target.x + ' ' + d.target.y; },
'class': 'edgepath',
'fill-opacity': 0,
'stroke-opacity': 0,
'fill': 'blue',
'stroke': 'red',
'id': function (d, i) { return 'edgepath' + i; }
})
.style("pointer-events", "none");
var edgelabels = svg.selectAll(".edgelabel")
.data(dataset.edges)
.enter()
.append('text')
.style("pointer-events", "none")
.attr({
'class': 'edgelabel',
'id': function (d, i) { return 'edgelabel' + i; },
'dx': 80,
'dy': 0,
'font-size': 10,
'fill': '#aaa'
});
edgelabels.append('textPath')
.attr('xlink:href', function (d, i) { return '#edgepath' + i; })
.style("pointer-events", "none")
.text(function (d, i) { return 'label ' + i; });
svg.append('defs').append('marker')
.attr({
'id': 'arrowhead',
'viewBox': '-0 -5 10 10',
'refX': 25,
'refY': 0,
//'markerUnits':'strokeWidth',
'orient': 'auto',
'markerWidth': 10,
'markerHeight': 10,
'xoverflow': 'visible'
})
.append('svg:path')
.attr('d', 'M 0,-5 L 10 ,0 L 0,5')
.attr('fill', '#ccc')
.attr('stroke', '#ccc');
force.on("tick", tick).start();
function ConstrainX(point) {
return Math.max(r, Math.min(w - r, point));
}
function ConstrainY(point) {
return Math.max(r, Math.min(h - r, point));
}
function tick(e) {
// Push sources up and targets down to form a weak tree.
var k = 60 * e.alpha;
dataset.edges.forEach(function (d, i) {
d.source.y -= k;
d.target.y += k;
});
edges.attr({
"x1": function (d) { return ConstrainX(d.source.x); },
"y1": function (d) { return ConstrainY(d.source.y); },
"x2": function (d) { return ConstrainX(d.target.x); },
"y2": function (d) { return ConstrainY(d.target.y); }
});
nodes.attr({
"x": function (d) { return ConstrainX(d.x) - boxWidth / 2; },
"y": function (d) { return ConstrainY(d.y) - boxHeight / 2; }
});
// appending boxWidth/2 to make sure the labels are within the box
nodelabels.attr("x", function (d) { return ConstrainX(d.x) - boxWidth / 2; })
.attr("y", function (d) { return ConstrainY(d.y); });
edgepaths.attr('d', function (d) {
var path = 'M ' + ConstrainX(d.source.x) + ' ' + ConstrainY(d.source.y) + ' L ' + ConstrainX(d.target.x) + ' ' + ConstrainY(d.target.y);
//console.log(d)
return path;
});
edgelabels.attr('transform', function (d, i) {
if (d.target.x < d.source.x) {
bbox = this.getBBox();
rx = bbox.x + bbox.width / 2;
ry = bbox.y + bbox.height / 2;
return 'rotate(180 ' + rx + ' ' + ry + ')';
}
else {
return 'rotate(0)';
}
});
var insertLinebreaks = function (d) {
var el = d3.select(this);
var name = d.name;
var id = d.id;
el.text('');
//for (var i = 0; i < words.length; i++) {
var tspan = el.append('tspan').text(name);
tspan = el.append('tspan').text(id);
//if (i > 0)
tspan.attr('x', 0);
tspan.attr('dy', '15');
tspan = el.append('tspan').text('what');
tspan.attr('x', '0');
tspan.attr('dy', '15');
//}
};
nodelabels.each(insertLinebreaks); <== Insert new lines
}
But this is messing new lines in the nodes. Once I insert the new lines the text shows up left aligned at the start of the screen. This is not what I want. I wanted the text to be aligned in the node as shown in the first image.
This is the output using the above code:
http://jsfiddle.net/6afc2vp8/
// **********start configuration***************
var dataset = {
"Nodes": [
{
"ID": 0,
"Identity": "af12689c-de83-4a0d-a63d-1f548fd02e26",
"RoleInstance": "RoleInstance",
"LogonID": "LogonID1",
"WeightedScore": 120,
"AccountInstance": "AccountInstance1",
"MinTimestampPst": "2014-11-19T17:08:46.6797242-05:00"
},
{
"ID": 1,
"Identity": "a5bd36db-00e6-4492-92d7-49278f0046a7",
"RoleInstance": "RoleInstance2",
"LogonID": "LogonID2",
"WeightedScore": 100,
"AccountInstance": "AccountInstance2",
"MinTimestampPst": "2014-11-19T17:08:46.6797242-05:00"
},
{
"ID": 2,
"Identity": "a5bd36db-00e6-4492-92d7-49278f0046a7",
"RoleInstance": "RoleInstance2",
"LogonID": "LogonID2",
"WeightedScore": 100,
"AccountInstance": "AccountInstance2",
"MinTimestampPst": "2014-11-19T17:08:46.6797242-05:00"
},
{
"ID": 3,
"Identity": "a5bd36db-00e6-4492-92d7-49278f0046a7",
"RoleInstance": "RoleInstance2",
"LogonID": "LogonID2",
"WeightedScore": 100,
"AccountInstance": "AccountInstance2",
"MinTimestampPst": "2014-11-19T17:08:46.6797242-05:00"
}
],
"Edges": [
{
"source": 0,
"target": 1
}, {
"source": 3,
"target": 2
}
]
};
// select all the features from the node that you want in the output
var nodelabelslist = ["ID", "Identity", "RoleInstance", "LogonID", "WeightedScore", "AccountInstance", "MinTimestampPst"];
var nodelabelslistlength = nodelabelslist.length;
// this is the rectangle height and width
var boxHeight = nodelabelslistlength * 20;
var boxWidth = 400;
var nodelabelverticaloffset = 15;
var nodelabelhorizontaloffset = 5;
var labelStartPos = -(nodelabelslistlength/2 - 1);
// setting up d3js parameters to be use through rest of the code
var w = 2000;
var h = 800;
var r = 30;
var linkDistance = 200;
var colors = d3.scale.category10();
// This is what how we should be setting gravity, theta and charge ideally: http://stackoverflow.com/questions/9901565/charge-based-on-size-d3-force-layout
var charge = -6000;
var gravity = 0.10;
var theta = 0.01;
// **********end configuration***************
var jsonNodes = dataset.Nodes;
var jsonEdges = dataset.Edges;
var svg = d3.select("body").append("svg").attr({ "width": w, "height": h });
var force = d3.layout.force()
.nodes(jsonNodes)
.links(jsonEdges)
.size([w, h])
.linkDistance([linkDistance])
.charge(charge)
.theta(theta)
.gravity(gravity);
var edges = svg.selectAll("line")
.data(jsonEdges)
.enter()
.append("line")
.attr("id", function (d, i) { return 'edge' + i; })
.attr('marker-end', 'url(#arrowhead)')
.style("stroke", "#000")
.style("pointer-events", "none");
var nodes = svg.selectAll("rect")
.data(jsonNodes)
.enter()
.append("rect")
//.style("fill", function (d, i) { return colors(i); })
.style("fill", 'none')
.attr('stroke', 'black')
.call(force.drag);
var nodelabelsarr = [];
for (var index = 0; index < nodelabelslist.length; ++index) {
var nodelabels = svg.selectAll(".nodelabel" + index)
.data(jsonNodes)
.enter()
.append("text")
.attr({
"class": "nodelabel",
"stroke": "black"
})
.text(function (d) { return nodelabelslist[index] + ": " + d[nodelabelslist[index]]; });
nodelabelsarr[index] = nodelabels;
}
var edgepaths = svg.selectAll(".edgepath")
.data(jsonEdges)
.enter()
.append('path')
.attr({
'class': 'edgepath',
'fill-opacity': 0,
'stroke-opacity': 0,
'fill': 'blue',
'stroke': 'red',
'id': function (d, i) { return 'edgepath' + i; }
})
.style("pointer-events", "none");
var edgelabels = svg.selectAll(".edgelabel")
.data(jsonEdges)
.enter()
.append('text')
.style("pointer-events", "none")
.attr({
'class': 'edgelabel',
'id': function (d, i) { return 'edgelabel' + i; },
'dx': 80,
'dy': 0,
'font-size': 10,
'fill': '#000'
});
edgelabels.append('textPath')
.attr('xlink:href', function (d, i) { return '#edgepath' + i; })
.style("pointer-events", "none")
.text(function (d, i) { return 'label ' + i; });
svg.append('defs').append('marker')
.attr({
'id': 'arrowhead',
'viewBox': '-0 -5 10 10',
'refX': 25,
'refY': 0,
//'markerUnits':'strokeWidth',
'orient': 'auto',
'markerWidth': 10,
'markerHeight': 10,
'xoverflow': 'visible'
})
.append('svg:path')
.attr('d', 'M 0,-5 L 10 ,0 L 0,5')
.attr('fill', '#000')
.attr('stroke', '#000');
force.on("tick", tick).start();
function ConstrainX(point) {
return Math.max(r, Math.min(w - r, point));
}
function ConstrainY(point) {
return Math.max(r, Math.min(h - r, point));
}
function edgeArc(d) {
var path = 'M ' + ConstrainX(d.source.x) + ' ' + ConstrainY(d.source.y) + ' L ' + ConstrainX(d.target.x) + ' ' + ConstrainY(d.target.y);
//console.log(d)
return path;
}
function edgelabeltransform(d) {
if (d.target.x < d.source.x) {
var bbox = this.getBBox();
var rx = bbox.x + bbox.width / 2;
var ry = bbox.y + bbox.height / 2;
return 'rotate(180 ' + rx + ' ' + ry + ')';
}
else {
return 'rotate(0)';
}
}
function tick(e) {
// Push sources up and targets down to form a weak tree.
var k = 60 * e.alpha;
jsonEdges.forEach(function (d) {
d.source.y -= k;
d.target.y += k;
});
edges.attr({
"x1": function (d) { return ConstrainX(d.source.x); },
"y1": function (d) { return ConstrainY(d.source.y); },
"x2": function (d) { return ConstrainX(d.target.x); },
"y2": function (d) { return ConstrainY(d.target.y); }
});
nodes.attr({
"x": function (d) { return ConstrainX(d.x) - boxWidth / 2; },
"y": function (d) { return ConstrainY(d.y) - boxHeight / 2; }
})
.attr({ "width": boxWidth })
.attr({ "height": boxHeight });
// appending boxWidth/2 to make sure the labels are within the box
var startOffset = labelStartPos;
for (var index = 0; index < nodelabelsarr.length; index++) {
nodelabelsarr[index].attr("x", function (d) { return ConstrainX(d.x) - boxWidth / 2 + nodelabelhorizontaloffset; })
.attr("y", function (d) { return ConstrainY(d.y) + (nodelabelverticaloffset * startOffset); });
startOffset++;
}
edgepaths.attr('d', edgeArc);
edgelabels.attr('transform', edgelabeltransform);
}

Resources