D3, JSON, graph, force layout, data update, not complete redraw, drag - javascript

First sorry for my question, because I think this is because I don't understand very well D3 library, especially the working of Selections.
Here's what I want to do:
Showing a graph that links publications with people who tweet about the publications.
Update the graph when the data change (in this case when use clicks the paragraph above that says "click to update").
Here I don't want to do complete redraw of the graph; I want the existing nodes and links stay where they are, and new nodes and links fly in to the scene. That's why I keep the instance of force layout outside of the drawGraph function (as global variable).
Task #1 done without a problem.
The problem is with task #2; I could make the new node enter the scene..., but for some reason I cannot drag the existing nodes. I can only drag the new nodes (Eduardo).
I have debugged it in Chrome, and saw this "var a" has 9 elements (after clicking). So, I suppose the function "force.layout" is supposed to be invoked by D3 for all those 9 elements. So, if I undestand correctly, I should've been able to drag all the 9 circles.
But that's not the case, so something is wrong with my code. Can anybody please point out where I got it wrong?
Here is the code. And after that comes the JSON (two separate jsons, news.json and news3.json).
Additional question:
I don't quite understand why this block (the key function) was executed 17 times (8 + 9) on the second invocation of drawGraph function (when the json is updated to news3.json)? My expectation is 9 times.
var lineSelections = svg.selectAll('line')
.data(dataset.edges, function(d){
console.log(d);
return d.source.index + '.' + d.target.index;
});
Thanks in advance!,
Raka
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>D3: Force layout</title>
<script type="text/javascript" src="../d3/d3.v3.js"></script>
<style type="text/css">
#tooltip {
position: absolute;
width: 200px;
height: auto;
padding: 10px;
background-color: white;
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
border-radius: 10px;
-webkit-box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
-moz-box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
pointer-events: none;
}
#tooltip.hidden {
display: none;
}
#tooltip p {
margin: 0;
font-family: sans-serif;
font-size: 16px;
line-height: 20px;
}
</style>
</head>
<body>
<div id="tooltip" class="hidden">
<p><span id="type"></span></p>
<p><span id="name"></span></p>
</div>
<p id="refresh">Click on this text to update the chart with new data values (once).</p>
<script type="text/javascript">
//Width and height
var w = 500;
var h = 300;
//http://stackoverflow.com/questions/16455194/how-to-store-a-json-object-loaded-from-a-file
var force = d3.layout.force()
.size([w, h])
.linkDistance([20])
.charge([-50]);
var svg = d3.select('body')
.append('svg')
.attr('width', w)
.attr('height', h);
drawGraph = function(dataset) {
force
.nodes(dataset.nodes)
.links(dataset.edges)
.start();
var lineSelections = svg.selectAll('line')
.data(dataset.edges, function(d){
console.log(d);
return d.source.index + '.' + d.target.index;
});
//Create edges as lines
var edges = lineSelections
.enter()
.append('line')
.style('stroke', function(d) {
if (d.target.type === 'publication') {
return 'red';
} else {
return 'blue';
}
})
.style('stroke-width', function(d) {
return d.weight;
});
var groups = svg.selectAll('g')
.data(dataset.nodes, function(d) {
console.log(d.name);
return d.name;
})
.enter()
.append('g');
var nodes = groups
.append('circle')
.attr('r', function(d) {
if (d.type === 'publication') {
var radius = d.weight / 6;
if (radius < 5) {
radius = 5;
}
return radius;
} else {
return d.weight * 3;
}
})
.style('fill', function(d, i) {
if (d.type === 'publication') {
return 'black';
} else {
return 'green';
}
});
var a = svg.selectAll('g circle');
a.call(force.drag);
//Create labels
var text = groups
.append('text')
.text(function(d) {
if (d.type === 'publication') {
return d.name;
} else {
return '';
}
})
.attr('x', function(d, i) {
d.x;
})
.attr('y', function(d) {
d.x;
})
.attr('font-family', 'sans-serif')
.attr('font-size', '24px')
.attr('fill', 'orange');
groups
.on("mouseover", function(d) {
//Get this bar's x/y values, then augment for the tooltip
//var hmm = d3.select(this).select('circle').attr('cx');
var xPosition = d3.select(this).select('circle').attr('cx');
var yPosition = d3.select(this).select('circle').attr('cy');
//Update the tooltip position and value
d3.select("#tooltip")
.style("left", xPosition + "px")
.style("top", yPosition + "px")
.select("#type")
.text(d.type);
d3.select("#tooltip")
.style("left", xPosition + "px")
.style("top", yPosition + "px")
.select("#name")
.text(d.name);
//Show the tooltip
//d3.select("#tooltip").classed("hidden", false);
})
.on("mouseout", function() {
//Hide the tooltip
d3.select("#tooltip").classed("hidden", true);
})
//Every time the simulation "ticks", this will be called
force.on('tick', function() {
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; });
nodes.attr('cx', function(d) { return d.x; })
.attr('cy', function(d) { return d.y; });
//Update all labels
svg.selectAll('text')
.data(dataset.nodes)
.attr('x', function(d, i) {
return d.x;
})
.attr('y', function(d) {
return d.y;
});
});
}
d3.json('news.json', function(dataset) {
drawGraph(dataset);
});
d3.select("p")
.on("click", function() {
});
d3.select("#refresh")
.on("click", function() {
d3.json('news3.json', function(dataset) {
console.log(dataset);
drawGraph(dataset);
});
});
</script>
</body>
</html>
news.json
{
"nodes": [
{ "name": "El Universal", "type": "publication", "weight": 10 },
{ "name": "Milenio", "type": "publication", "weight": 4},
{ "name": "Proceso", "type": "publication", "weight": 4},
{ "name": "Paco", "type": "person", "weight": 12},
{ "name": "Juan", "type": "person", "weight": 5},
{ "name": "Alberto", "type": "person", "weight": 5 },
{ "name": "Xochitl", "type": "person", "weight": 3 },
{ "name": "Reforma", "type": "publication", "weight": 2}
],
"edges": [
{ "source": 3, "target": 0, "weight": 9},
{ "source": 3, "target": 1, "weight": 3},
{ "source": 4, "target": 2, "weight": 4},
{ "source": 4, "target": 0, "weight": 1},
{ "source": 5, "target": 3, "weight": 5},
{ "source": 6, "target": 1, "weight": 1},
{ "source": 6, "target": 7, "weight": 2},
{ "source": 6, "target": 1, "weight": 1},
{ "source": 3, "target": 5, "weight": 4},
{ "source": 4, "target": 5, "weight": 1}
]
}
news3.json
{
"nodes": [
{ "name": "El Universal", "type": "publication", "weight": 10 },
{ "name": "Milenio", "type": "publication", "weight": 4},
{ "name": "Proceso", "type": "publication", "weight": 4},
{ "name": "Paco", "type": "person", "weight": 12},
{ "name": "Juan", "type": "person", "weight": 5},
{ "name": "Alberto", "type": "person", "weight": 5 },
{ "name": "Xochitl", "type": "person", "weight": 3 },
{ "name": "Reforma", "type": "publication", "weight": 2},
{ "name": "Eduardo", "type": "person", "weight": 2}
],
"edges": [
{ "source": 3, "target": 0, "weight": 9},
{ "source": 3, "target": 1, "weight": 3},
{ "source": 4, "target": 2, "weight": 4},
{ "source": 4, "target": 0, "weight": 1},
{ "source": 5, "target": 3, "weight": 5},
{ "source": 6, "target": 1, "weight": 1},
{ "source": 6, "target": 7, "weight": 2},
{ "source": 6, "target": 1, "weight": 1},
{ "source": 3, "target": 5, "weight": 4},
{ "source": 4, "target": 5, "weight": 1},
{ "source": 8, "target": 7, "weight": 2}
]
}

I got it working, after I modified the function assigned to the tick event on force.on('tick', ...);. Small modif: instead of using groupSelection.selectAll('circle') and groupSelection.selectAll('text'), now I use groupSelection.select('circle') and groupSelection.select('text').
You can see a working demo here.
Here is the code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>D3: Force layout</title>
<script type="text/javascript" src="../d3/d3.v3.js"></script>
<style type="text/css">
#tooltip {
position: absolute;
width: 200px;
height: auto;
padding: 10px;
background-color: white;
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
border-radius: 10px;
-webkit-box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
-moz-box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
pointer-events: none;
}
#tooltip.hidden {
display: none;
}
#tooltip p {
margin: 0;
font-family: sans-serif;
font-size: 16px;
line-height: 20px;
}
</style>
</head>
<body>
<div id="tooltip" class="hidden">
<p><span id="type"></span></p>
<p><span id="name"></span></p>
</div>
<p id="refresh">Click on this text to update the chart with new data values (once).</p>
<script type="text/javascript">
//Width and height
var w = 300;
var h = 200;
//http://stackoverflow.com/questions/16455194/how-to-store-a-json-object-loaded-from-a-file
var force = d3.layout.force()
.size([w, h])
.linkDistance([20])
.charge([-50]);
var svg = d3.select('body')
.append('svg')
.attr('width', w)
.attr('height', h);
drawGraph = function(dataset) {
force.nodes(dataset.nodes);
force.links(dataset.edges);
force.start();
var lineSelections = svg.selectAll('line')
.data(dataset.edges, function(d){
return '.'.concat(d.source.name, '.', d.target.name);
});
lineSelections
.enter()
.append('line')
.style('stroke', function(d) {
if (d.target.type === 'publication') {
return 'red';
} else {
return 'blue';
}
})
.style('stroke-width', function(d) {
return d.weight;
});
var groupSelection = svg.selectAll('g')
.data(dataset.nodes, function(d) {
return d.name;
})
.call(force.drag);
var groups = groupSelection
.enter()
.append('g')
.call(force.drag);
groups
.append('circle')
.attr('r', function(d) {
if (d.type === 'publication') {
var radius = d.weight / 6;
if (radius < 5) {
radius = 5;
}
return radius;
} else {
return d.weight * 3;
}
})
.style('fill', function(d, i) {
if (d.type === 'publication') {
return 'black';
} else {
return 'green';
}
});
//Create labels
groups
.append('text')
.text(function(d) {
if (d.type === 'publication') {
return d.name;
} else {
return d.name;
}
})
.attr('x', function(d, i) {
d.x;
})
.attr('y', function(d) {
d.x;
})
.attr('font-family', 'sans-serif')
.attr('font-size', '14px')
.attr('fill', 'orange');
//Every time the simulation "ticks", this will be called
force.on('tick', function() {
lineSelections
.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;
});
groupSelection
.select('circle')
.attr('cx', function(d) {
return d.x;
})
.attr('cy', function(d) {
return d.y;
});
//Update all labels
groupSelection
.select('text')
.attr('x', function(d, i) {
return d.x;
})
.attr('y', function(d) {
return d.y;
});
});
}
d3.json('news.json', function(dataset) {
drawGraph(dataset);
});
d3.select("p")
.on("click", function() {
});
d3.select("#refresh")
.on("click", function() {
console.log('==================');
d3.json('news3.json', function(dataset) {
drawGraph(dataset);
});
});
</script>
</body>
</html>

Related

Why the maximum variable take the d3.max() value but the minimum one d3.min() does not work properly?

My task is to organize a timeline visualization.
I took the example from - http://bl.ocks.org/bunkat/2338034.
Making necessary modifications I notice that timeBegin variable is equal to "0" and if I try to modify it by introducing:
d3.min(timeSegments, function (d) {
return d.segment_start;
})
The graph starts to enlarge horizontally. Only when the variable timeBegin is 0 the graph is displaying in a correct manner. Where is my wrong minding in D3?
I found another example - https://codepen.io/manglass/pen/MvLBRz, but this cases are slightly complicated for me.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>RePLICA</title>
<style type="text/css">
.chart-table {
shape-rendering: crispEdges;
}
.graph-square text {
font: 10px sans-serif;
}
div.tooltip-donut {
position: absolute;
text-align: center;
padding: .5rem;
background: #FFFFFF;
color: #313639;
border: 1px solid #313639;
border-radius: 8px;
pointer-events: none;
font-size: 1.3rem;
}
.brush .extent {
stroke: gray;
fill: dodgerblue;
fill-opacity: .365;
}
</style>
</head>
<body>
<script src="https://d3js.org/d3.v4.min.js" charset="utf-8"></script>
<script type="text/javascript">
//data
var lanes = ["Chinese", "Japanese", "Korean", "Moldova"],
laneLength = lanes.length,
timeSegments = [{
"lane": 0,
"id": "Qin",
"segment_start": 100,
"segment_end": 210,
"flag": false
},
{
"lane": 0,
"id": "Jin",
"segment_start": 210,
"segment_end": 420,
"flag": true
},
{
"lane": 0,
"id": "Sui",
"segment_start": 420,
"segment_end": 615,
"flag": false
},
{
"lane": 1,
"id": "Yamato",
"segment_start": 300,
"segment_end": 530,
"flag": false
},
{
"lane": 1,
"id": "Asuka",
"segment_start": 530,
"segment_end": 700,
"flag": true
},
{
"lane": 1,
"id": "Nara",
"segment_start": 710,
"segment_end": 800,
"flag": false
},
{
"lane": 1,
"id": "Heian",
"segment_start": 800,
"segment_end": 1180,
"flag": true
},
{
"lane": 2,
"id": "Three Kingdoms",
"segment_start": 100,
"segment_end": 670,
"flag": false
},
{
"lane": 2,
"id": "North and South States",
"segment_start": 670,
"segment_end": 900,
"flag": true
},
{
"lane": 3,
"id": "Chisinau",
"segment_start": 250,
"segment_end": 600,
"flag": false
},
{
"lane": 3,
"id": "Balti",
"segment_start": 600,
"segment_end": 900,
"flag": true
},
{
"lane": 3,
"id": "Ungheni",
"segment_start": 920,
"segment_end": 1380,
"flag": false
}
],
timeBegin = 0, // !!! d3.min(timeSegments, function (d) { return d.segment_start; }) ---- Does not work !!!
timeEnd = d3.max(timeSegments, function(d) {
return d.segment_end;
});
var widthTotal = 1300,
heightTotal = 500,
margin = {
top: 10,
right: 15,
bottom: 0,
left: 100
},
widthSVG = widthTotal - margin.right - margin.left,
heightSVG = heightTotal - margin.top - margin.bottom,
graphHeight = laneLength * 10 + heightTotal / 3; // - 3 just a coonstant
// scales
var scaleX = d3.scaleLinear()
.domain([timeBegin, timeEnd])
.range([0, widthSVG]);
var scaleY = d3.scaleLinear()
.domain([0, laneLength])
.range([0, graphHeight]);
var colorScale = d3.scaleOrdinal(d3.schemeCategory10);
var chart = d3.select("body")
.append("svg")
.attr("width", widthSVG + margin.right + margin.left)
.attr("height", heightSVG + margin.top + margin.bottom)
.attr("class", "chart-table");
var graph = chart.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.attr("width", widthSVG)
.attr("height", graphHeight)
.attr("class", "graph-square");
// Draw the axis
chart.append("g")
.attr("transform", "translate(" + margin.left + ", " + (graphHeight + 20) + ")") // This controls the vertical position of the Axis
.call(d3.axisBottom(scaleX));
// Delimitation lines
graph.append("g").selectAll(".laneLines")
.data(timeSegments)
.enter().append("line")
.attr("x1", 0)
.attr("y1", function(d) {
return scaleY(d.lane);
})
.attr("x2", widthSVG)
.attr("y2", function(d) {
return scaleY(d.lane);
})
.attr("stroke", "lightgray");
// Lanes Names display
graph.append("g").selectAll(".laneText")
.data(lanes)
.enter().append("text")
.text(function(d) {
return d;
})
.attr("x", -margin.right)
.attr("y", function(d, i) {
return scaleY(i + .5);
})
.attr("dy", ".5ex")
.attr("text-anchor", "end")
.attr("class", "laneText");
// Add DIV for "hover_info"
var div = d3.select("body").append("div")
.attr("class", "tooltip-donut")
.style("opacity", 0);
// Graph item rects
graph.append("g").selectAll(".graphItem")
.data(timeSegments)
.enter().append("rect")
.attr("x", function(d) {
return scaleX(d.segment_start);
})
.attr("y", function(d) {
let shiftVertical = 9;
if (d.flag) {
shiftVertical = 0
};
return scaleY(d.lane + .5) - shiftVertical;
})
.attr("width", function(d) {
return scaleX(d.segment_end - d.segment_start);
})
.attr("height", 10)
.style("fill", function(d) {
return colorScale(d.lane);
})
// Hover effect
.on('mouseover', function(d, i) {
d3.select(this).transition()
.duration('50')
.attr('opacity', '.5');
div.transition()
.duration(50)
.style("opacity", 1);
let hover_info = ("id:" + d.id + "<br/>" + "start:" + d.segment_start + "<br/>" + "end:" + d.segment_end).toString();
//Makes the new div appear on hover:
div.html(hover_info)
.style("left", (d3.event.pageX + 10) + "px")
.style("top", (d3.event.pageY - 15) + "px");
})
.on('mouseout', function(d, i) {
d3.select(this).transition()
.duration('50')
.attr('opacity', '1')
//Makes the new div disappear:
div.transition()
.duration('50')
.style("opacity", 0);
});
</script>
</body>
</html>
The d3.min function you have does work, that's not the problem. The problem is the math you're using to calculate the width of the rectangles:
.attr("width", function(d) {
return scaleX(d.segment_end - d.segment_start);
})
As you can see, that only works if the scale starts at 0. For a more dynamic scale, like the one you want, the width should be:
.attr("width", function(d) {
return scaleX(d.segment_end - d.segment_start + scaleX.domain()[0]);
})
That is, you add the first value of the domain to the number you pass to the scale.
Here is your code with that change only:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>RePLICA</title>
<style type="text/css">
.chart-table {
shape-rendering: crispEdges;
}
.graph-square text {
font: 10px sans-serif;
}
div.tooltip-donut {
position: absolute;
text-align: center;
padding: .5rem;
background: #FFFFFF;
color: #313639;
border: 1px solid #313639;
border-radius: 8px;
pointer-events: none;
font-size: 1.3rem;
}
.brush .extent {
stroke: gray;
fill: dodgerblue;
fill-opacity: .365;
}
</style>
</head>
<body>
<script src="https://d3js.org/d3.v4.min.js" charset="utf-8"></script>
<script type="text/javascript">
//data
var lanes = ["Chinese", "Japanese", "Korean", "Moldova"],
laneLength = lanes.length,
timeSegments = [{
"lane": 0,
"id": "Qin",
"segment_start": 100,
"segment_end": 210,
"flag": false
},
{
"lane": 0,
"id": "Jin",
"segment_start": 210,
"segment_end": 420,
"flag": true
},
{
"lane": 0,
"id": "Sui",
"segment_start": 420,
"segment_end": 615,
"flag": false
},
{
"lane": 1,
"id": "Yamato",
"segment_start": 300,
"segment_end": 530,
"flag": false
},
{
"lane": 1,
"id": "Asuka",
"segment_start": 530,
"segment_end": 700,
"flag": true
},
{
"lane": 1,
"id": "Nara",
"segment_start": 710,
"segment_end": 800,
"flag": false
},
{
"lane": 1,
"id": "Heian",
"segment_start": 800,
"segment_end": 1180,
"flag": true
},
{
"lane": 2,
"id": "Three Kingdoms",
"segment_start": 100,
"segment_end": 670,
"flag": false
},
{
"lane": 2,
"id": "North and South States",
"segment_start": 670,
"segment_end": 900,
"flag": true
},
{
"lane": 3,
"id": "Chisinau",
"segment_start": 250,
"segment_end": 600,
"flag": false
},
{
"lane": 3,
"id": "Balti",
"segment_start": 600,
"segment_end": 900,
"flag": true
},
{
"lane": 3,
"id": "Ungheni",
"segment_start": 920,
"segment_end": 1380,
"flag": false
}
],
timeBegin = d3.min(timeSegments, function(d) {
return d.segment_start;
}),
timeEnd = d3.max(timeSegments, function(d) {
return d.segment_end;
});
var widthTotal = 1300,
heightTotal = 500,
margin = {
top: 10,
right: 15,
bottom: 0,
left: 100
},
widthSVG = widthTotal - margin.right - margin.left,
heightSVG = heightTotal - margin.top - margin.bottom,
graphHeight = laneLength * 10 + heightTotal / 3; // - 3 just a coonstant
// scales
var scaleX = d3.scaleLinear()
.domain([timeBegin, timeEnd])
.range([0, widthSVG]);
var scaleY = d3.scaleLinear()
.domain([0, laneLength])
.range([0, graphHeight]);
var colorScale = d3.scaleOrdinal(d3.schemeCategory10);
var chart = d3.select("body")
.append("svg")
.attr("width", widthSVG + margin.right + margin.left)
.attr("height", heightSVG + margin.top + margin.bottom)
.attr("class", "chart-table");
var graph = chart.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.attr("width", widthSVG)
.attr("height", graphHeight)
.attr("class", "graph-square");
// Draw the axis
chart.append("g")
.attr("transform", "translate(" + margin.left + ", " + (graphHeight + 20) + ")") // This controls the vertical position of the Axis
.call(d3.axisBottom(scaleX));
// Delimitation lines
graph.append("g").selectAll(".laneLines")
.data(timeSegments)
.enter().append("line")
.attr("x1", 0)
.attr("y1", function(d) {
return scaleY(d.lane);
})
.attr("x2", widthSVG)
.attr("y2", function(d) {
return scaleY(d.lane);
})
.attr("stroke", "lightgray");
// Lanes Names display
graph.append("g").selectAll(".laneText")
.data(lanes)
.enter().append("text")
.text(function(d) {
return d;
})
.attr("x", -margin.right)
.attr("y", function(d, i) {
return scaleY(i + .5);
})
.attr("dy", ".5ex")
.attr("text-anchor", "end")
.attr("class", "laneText");
// Add DIV for "hover_info"
var div = d3.select("body").append("div")
.attr("class", "tooltip-donut")
.style("opacity", 0);
// Graph item rects
graph.append("g").selectAll(".graphItem")
.data(timeSegments)
.enter().append("rect")
.attr("x", function(d) {
return scaleX(d.segment_start);
})
.attr("y", function(d) {
let shiftVertical = 9;
if (d.flag) {
shiftVertical = 0
};
return scaleY(d.lane + .5) - shiftVertical;
})
.attr("width", function(d) {
return scaleX(d.segment_end - d.segment_start + scaleX.domain()[0]);
})
.attr("height", 10)
.style("fill", function(d) {
return colorScale(d.lane);
})
// Hover effect
.on('mouseover', function(d, i) {
d3.select(this).transition()
.duration('50')
.attr('opacity', '.5');
div.transition()
.duration(50)
.style("opacity", 1);
let hover_info = ("id:" + d.id + "<br/>" + "start:" + d.segment_start + "<br/>" + "end:" + d.segment_end).toString();
//Makes the new div appear on hover:
div.html(hover_info)
.style("left", (d3.event.pageX + 10) + "px")
.style("top", (d3.event.pageY - 15) + "px");
})
.on('mouseout', function(d, i) {
d3.select(this).transition()
.duration('50')
.attr('opacity', '1')
//Makes the new div disappear:
div.transition()
.duration('50')
.style("opacity", 0);
});
</script>
</body>
</html>

D3 v3 - String values as x-axis domain for stacked chart not working

I have been struggling to get this chart work. I tried so many methods. It didn't show any error but doesn't draw the chart properly.
I want to use strings as x axis domain. I had even tried to set domain for x-scale. Still no use. The code which I have tried so far:
$(".wrapper").delay(600).fadeIn(500);
var barDataset = [{
"name": "aA",
"date": 1,
"successful": 1,
"unsuccessful": 2
},
{
"name": "bB",
"date": 2,
"successful": 41,
"unsuccessful": 8
},
{
"name": "cC",
"date": 3,
"successful": 44,
"unsuccessful": 4
},
{
"name": "dD",
"date": 4,
"successful": 2,
"unsuccessful": 5
},
{
"name": "eE",
"date": 5,
"successful": 21,
"unsuccessful": 1
},
{
"name": "fF",
"date": 6,
"successful": 14,
"unsuccessful": 6
},
{
"name": "gG",
"date": 7,
"successful": 42,
"unsuccessful": 1
},
{
"name": "hH",
"date": 8,
"successful": 10,
"unsuccessful": 1
},
{
"name": "iI",
"date": 9,
"successful": 24,
"unsuccessful": 10
},
{
"name": "jJ",
"date": 10,
"successful": 23,
"unsuccessful": 6
},
{
"name": "kK",
"date": 11,
"successful": 21,
"unsuccessful": 15
},
{
"name": "lL",
"date": 12,
"successful": 28,
"unsuccessful": 15
}
]
function drawBarGraph(data) {
var status = ["successful", "unsuccessful"];
var colors = [
["Successful", "#50E3C2"],
["Unsuccessful", "#EF5C6E"]
];
var margin = {
top: 30,
right: 30,
bottom: 40,
left: 60
},
width = 860 - margin.left - margin.right,
height = 290 - margin.top - margin.bottom;
var z = d3.scale.ordinal()
.range(["#50E3C2", "#EF5C6E"]);
/* var n = 12;
var x = d3.scale.linear()
.domain([1, n - 1])
.rangeRound([0, width], .1);*/
var x = d3.scale.ordinal()
.domain(data.map(function(d) {
return d['name'];
}))
.rangeRoundBands([0, width], .5);
var y = d3.scale.linear()
.rangeRound([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
/*.tickFormat(d3.format("d"))
.ticks(30)*/
;
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(5)
.tickFormat(d3.format("d"));
var svg = d3.select("#chart-bar")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var layers = d3.layout.stack()
(status.map(function(c) {
return data.map(function(d) {
return {
x: d.date,
y: d[c]
};
});
}));
y.domain([
0, d3.max(layers[layers.length - 1], function(d) {
return d.y0 + d.y;
})
]);
// gridlines in y axis function
function make_y_gridlines() {
return d3.svg.axis().scale(y)
.orient("left").ticks(5);
}
// add the Y gridlines
svg.append("g")
.attr("class", "gridline")
.call(make_y_gridlines()
.tickSize(-width)
.tickFormat("")
);
svg.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(6," + height + ")")
.call(xAxis)
.append("text")
.attr("transform", "translate(364,0)")
.attr("y", "3em")
.style("text-anchor", "middle")
.text("Days");
svg.append("g")
.attr("class", "axis axis--y")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("x", "-5em")
.attr("y", "-2.5em")
.style("text-anchor", "end")
.text("Number of calls sent");
function type(d) {
// d.date = parseDate(d.date);
d.date;
status.forEach(function(c) {
d[c] = +d[c];
});
return d;
}
var tooltip = d3.select("#chart-bar").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
var layer = svg.selectAll(".layer")
.data(layers)
.enter().append("g")
.attr("class", "layer")
.style("fill", function(d, i) {
return z(i);
});
layer.selectAll("rect")
.data(function(d) {
return d;
})
.enter().append("rect")
.on("mouseover", function(d) {
tooltip.transition()
.duration(200)
.style("opacity", 1);
tooltip.html("<span>" + d.y + " calls" + "</span>")
.style("left", (d3.event.pageX - 25) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout", function(d) {
tooltip.transition()
.duration(500)
.style("opacity", 0);
})
.attr("x", function(d) {
return x(d.x);
})
.attr("y", function(d) {
return height;
})
.attr("width", 12)
.attr("height", 0)
.transition().duration(1500)
.attr("y", function(d) {
return y(d.y + d.y0);
})
.attr("height", function(d) {
return y(d.y0) - y(d.y + d.y0);
});
}
drawBarGraph(barDataset);
$('.count').each(function() {
$(this).prop('Counter', 0).animate({
Counter: $(this).text()
}, {
duration: 1500,
easing: 'swing',
step: function(now) {
$(this).text(Math.ceil(now));
}
});
});
#import url("https://fonts.googleapis.com/css?family=Overpass");
* {
box-sizing: border-box;
font: normal 14px/1.5 "Overpass", sans-serif;
}
body {
background: #76daff;
}
.chart-wrapper {
background: #333B66;
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
clear: both;
padding: 20px 0 10px;
}
text {
font-size: 12px;
fill: #A7B2EB;
}
.axis path,
.axis line,
.gridline line {
fill: none;
stroke: #fff;
opacity: 0.1;
shape-rendering: crispEdges;
}
.line {
stroke: #17EAD9;
stroke-width: 3px;
fill: none;
}
path.domain {
fill: none;
opacity: 0.1;
}
div.tooltip {
color: #4a4a4a;
position: absolute;
text-align: center;
padding: 3px 6px;
font-size: 12px;
background: #fff;
border-radius: 4px;
pointer-events: none;
}
.tick text {
font-size: 10px;
}
.vbroadcast-legend {
float: right;
margin-right: 40px;
margin-top: 16px;
}
.vbroadcast-legend li {
color: #fff;
font-size: 13px;
float: left;
list-style: none;
margin-left: 20px;
padding-left: 18px;
position: relative;
}
.vbroadcast-legend li:before {
content: "";
height: 12px;
left: 0;
position: absolute;
top: 3px;
width: 12px;
}
.vbroadcast-legend li.successful:before {
background: #50e3c2;
}
.vbroadcast-legend li.unsuccessful:before {
background: #ef5c6e;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="chart-bar" class="chart-wrapper"></div>
Kindly help me in fixing it. Thanks in advance.
Thanks for getting back with the data. It's a minor fix in your code.
You're using name for drawing the X axis but it's not being mapped while being assigned to the layers array.
Relevant changes:
Changed d.date to d.name
return data.map(function(d) {
return {
x: d.name,
y: d[c]
};
});
Assigning x to the rects using this d.x and axes rangeBand to align it with the ticks.
.attr("x", function(d) {
return x(d.x) + x.rangeBand()/2;
})
Snippet:
$(".wrapper").delay(600).fadeIn(500);
var barDataset = [{
"name": "aA",
"date": 1,
"successful": 1,
"unsuccessful": 2
},
{
"name": "bB",
"date": 2,
"successful": 41,
"unsuccessful": 8
},
{
"name": "cC",
"date": 3,
"successful": 44,
"unsuccessful": 4
},
{
"name": "dD",
"date": 4,
"successful": 2,
"unsuccessful": 5
},
{
"name": "eE",
"date": 5,
"successful": 21,
"unsuccessful": 1
},
{
"name": "fF",
"date": 6,
"successful": 14,
"unsuccessful": 6
},
{
"name": "gG",
"date": 7,
"successful": 42,
"unsuccessful": 1
},
{
"name": "hH",
"date": 8,
"successful": 10,
"unsuccessful": 1
},
{
"name": "iI",
"date": 9,
"successful": 24,
"unsuccessful": 10
},
{
"name": "jJ",
"date": 10,
"successful": 23,
"unsuccessful": 6
},
{
"name": "kK",
"date": 11,
"successful": 21,
"unsuccessful": 15
},
{
"name": "lL",
"date": 12,
"successful": 28,
"unsuccessful": 15
}
]
function drawBarGraph(data) {
var status = ["successful", "unsuccessful"];
var colors = [
["Successful", "#50E3C2"],
["Unsuccessful", "#EF5C6E"]
];
var margin = {
top: 30,
right: 30,
bottom: 40,
left: 60
},
width = 860 - margin.left - margin.right,
height = 290 - margin.top - margin.bottom;
var z = d3.scale.ordinal()
.range(["#50E3C2", "#EF5C6E"]);
/* var n = 12;
var x = d3.scale.linear()
.domain([1, n - 1])
.rangeRound([0, width], .1);*/
var x = d3.scale.ordinal()
.domain(data.map(function(d) {
return d['name'];
}))
.rangeRoundBands([0, width], .5);
var y = d3.scale.linear()
.rangeRound([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
/*.tickFormat(d3.format("d"))
.ticks(30)*/
;
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(5)
.tickFormat(d3.format("d"));
var svg = d3.select("#chart-bar")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var layers = d3.layout.stack()
(status.map(function(c) {
return data.map(function(d) {
return {
x: d.name,
y: d[c],
};
});
}));
y.domain([
0, d3.max(layers[layers.length - 1], function(d) {
return d.y0 + d.y;
})
]);
// gridlines in y axis function
function make_y_gridlines() {
return d3.svg.axis().scale(y)
.orient("left").ticks(5);
}
// add the Y gridlines
svg.append("g")
.attr("class", "gridline")
.call(make_y_gridlines()
.tickSize(-width)
.tickFormat("")
);
svg.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(6," + height + ")")
.call(xAxis)
.append("text")
.attr("transform", "translate(364,0)")
.attr("y", "3em")
.style("text-anchor", "middle")
.text("Days");
svg.append("g")
.attr("class", "axis axis--y")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("x", "-5em")
.attr("y", "-2.5em")
.style("text-anchor", "end")
.text("Number of calls sent");
function type(d) {
// d.date = parseDate(d.date);
d.date;
status.forEach(function(c) {
d[c] = +d[c];
});
return d;
}
var tooltip = d3.select("#chart-bar").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
var layer = svg.selectAll(".layer")
.data(layers)
.enter().append("g")
.attr("class", "layer")
.style("fill", function(d, i) {
return z(i);
});
layer.selectAll("rect")
.data(function(d) {
return d;
})
.enter().append("rect")
.on("mouseover", function(d) {
tooltip.transition()
.duration(200)
.style("opacity", 1);
tooltip.html("<span>" + d.y + " calls" + "</span>")
.style("left", (d3.event.pageX - 25) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout", function(d) {
tooltip.transition()
.duration(500)
.style("opacity", 0);
})
.attr("x", function(d) {
return x(d.x) + x.rangeBand()/2;
})
.attr("y", function(d) {
return height;
})
.attr("width", 12)
.attr("height", 0)
.transition().duration(1500)
.attr("y", function(d) {
return y(d.y + d.y0);
})
.attr("height", function(d) {
return y(d.y0) - y(d.y + d.y0);
});
}
drawBarGraph(barDataset);
$('.count').each(function() {
$(this).prop('Counter', 0).animate({
Counter: $(this).text()
}, {
duration: 1500,
easing: 'swing',
step: function(now) {
$(this).text(Math.ceil(now));
}
});
});
#import url("https://fonts.googleapis.com/css?family=Overpass");
* {
box-sizing: border-box;
font: normal 14px/1.5 "Overpass", sans-serif;
}
body {
background: #76daff;
}
.chart-wrapper {
background: #333B66;
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
clear: both;
padding: 20px 0 10px;
}
text {
font-size: 12px;
fill: #A7B2EB;
}
.axis path,
.axis line,
.gridline line {
fill: none;
stroke: #fff;
opacity: 0.1;
shape-rendering: crispEdges;
}
.line {
stroke: #17EAD9;
stroke-width: 3px;
fill: none;
}
path.domain {
fill: none;
opacity: 0.1;
}
div.tooltip {
color: #4a4a4a;
position: absolute;
text-align: center;
padding: 3px 6px;
font-size: 12px;
background: #fff;
border-radius: 4px;
pointer-events: none;
}
.tick text {
font-size: 10px;
}
.vbroadcast-legend {
float: right;
margin-right: 40px;
margin-top: 16px;
}
.vbroadcast-legend li {
color: #fff;
font-size: 13px;
float: left;
list-style: none;
margin-left: 20px;
padding-left: 18px;
position: relative;
}
.vbroadcast-legend li:before {
content: "";
height: 12px;
left: 0;
position: absolute;
top: 3px;
width: 12px;
}
.vbroadcast-legend li.successful:before {
background: #50e3c2;
}
.vbroadcast-legend li.unsuccessful:before {
background: #ef5c6e;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="chart-bar" class="chart-wrapper"></div>
Hope this helps solve the issue.
If you have a list of 'aa' .. 'jj' and you set the domain of x to this list you most likely do not get tick marks like '0-15' and '15-30'.
Extract the x-domain values from the dataset or some other means of extracting the unique values. This is with the small dataset in the question.
var strn = ['aa', 'bb', 'cc', 'dd', 'ee', 'ff', 'gg', 'hh', 'ii', 'jj'];
// ...
//x.domain(strn);
x.domain(data.map(d=>d.DB));

Increase Distance Between Nodes in Network/Chart

I'm trying to figure out a way to space out the nodes in my network graph for my d3.js code. I don't necessarily care about how the shape of the network will be when I load the page since I can just click and drag around the nodes to make any kind of shape I want. But I'm not really sure where I start in trying to space out my nodes. I searched around and nothing I found seems to work for me. Help is very much appreciated.
Here is a picture of what the network looks like when I load the page:
https://i.gyazo.com/919ad4bde39d9fe6a6b6c91548dbcc2f.png
Here is what I'd like for it to look like roughly (again, shape does not really matter, I'm just looking to get a little distance on the inital load):
https://i.gyazo.com/fefa29cf861e204bc83f34cbc2d1a17d.png
(I only have 8 rep so I can't upload pictures sorry)
Here's my code so far:
<!DOCTYPE html>
<style>
.links line {
stroke-opacity: 0.6;
}
.nodes circle {
stroke: #fff;
stroke-width: 1.5px;
}
</style>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
<title>Group Comments</title>
<script src="http://d3js.org/d3.v4.min.js"></script>
</head>
<body>
<p> Not Ready: Group 6 Comments </p>
<svg width="960" height="600"></svg>
<script>
//fetches the svg
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
//Sets a color scale
var color = d3.scaleOrdinal(d3.schemeCategory20);
var strokeColor = d3.scaleLinear()
.domain([0, 1, 2])
.range(["white", "red", "green"]);
//Creates a force simulation
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))
//reads the JSON file
d3.json("NR6comments.json", function (error, graph) {
if (error) throw error;
//sets up the "links" between the nodes
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) })
.attr("stroke", function (d) { return strokeColor(d.value) });
//sets up the nodes
var node = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(graph.nodes)
.enter().append("circle")
.attr("r", 10)
.attr("fill", function (d) { return color(d.group); })
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
//displays the ID number on a node when hovering over
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("cx", function (d) { return d.x; })
.attr("cy", function (d) { return d.y; });
}
});
function dragstarted(d) {
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
function dragended(d) {
if (!d3.event.active) simulation.alphaTarget(0);
//d.fx = null;
//d.fy = null;
}
</script>
<p>Likes Chart</p>
</body>
</html>
I would greatly appreciate it if I could get some help with this problem. Thank you!
There are different ways to achieve what you want. The easiest one is setting the strength of your manyBody method. According to the API:
If strength is specified, sets the strength accessor to the specified number or function, re-evaluates the strength accessor for each node, and returns this force. A positive value causes nodes to attract each other, similar to gravity, while a negative value causes nodes to repel each other, similar to electrostatic charge.
Since I don't have access to your data, this is a simplified demo. The first version has no strength, just like your code:
var width = 400;
var height = 300;
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
var nodes = [{
name: "foo",
color: "blue"
}, {
name: "bar",
color: "green"
}, {
name: "baz",
color: "red"
}, {
name: "foofoo",
color: "yellow"
}, {
name: "foobar",
color: "blue"
}, {
name: "foobaz",
color: "green"
}, {
name: "barfoo",
color: "red"
}, {
name: "barbar",
color: "yellow"
}, {
name: "barbaz",
color: "blue"
}];
var links = [{
"source": 0,
"target": 1
}, {
"source": 0,
"target": 2
}, {
"source": 0,
"target": 3
}, {
"source": 1,
"target": 3
}, {
"source": 1,
"target": 4
}, {
"source": 2,
"target": 5
}, {
"source": 3,
"target": 6
}, {
"source": 1,
"target": 7
}, {
"source": 6,
"target": 8
}, {
"source": 0,
"target": 7
}, {
"source": 2,
"target": 6
}, {
"source": 3,
"target": 8
}];
var simulation = d3.forceSimulation()
.force("link", d3.forceLink())
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width / 2, height / 2));
var link = svg.selectAll(null)
.data(links)
.enter()
.append("line")
.style("stroke", "#ccc")
.style("stroke-width", 1);
var node = svg.selectAll(null)
.data(nodes)
.enter()
.append("circle")
.attr("r", function(d) {
return d.r = 10;
})
.attr("stroke", "gray")
.attr("stroke-width", "2px")
.attr("fill", function(d) {
return d.color
});
simulation.nodes(nodes);
simulation.force("link")
.links(links);
simulation.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
});
});
<script src="https://d3js.org/d3.v4.min.js"></script>
The second version, however, has the strength set to a negative value:
.force("charge", d3.forceManyBody().strength(-500))
Here it is:
var width = 400;
var height = 300;
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
var nodes = [{
name: "foo",
color: "blue"
}, {
name: "bar",
color: "green"
}, {
name: "baz",
color: "red"
}, {
name: "foofoo",
color: "yellow"
}, {
name: "foobar",
color: "blue"
}, {
name: "foobaz",
color: "green"
}, {
name: "barfoo",
color: "red"
}, {
name: "barbar",
color: "yellow"
}, {
name: "barbaz",
color: "blue"
}];
var links = [{
"source": 0,
"target": 1
}, {
"source": 0,
"target": 2
}, {
"source": 0,
"target": 3
}, {
"source": 1,
"target": 3
}, {
"source": 1,
"target": 4
}, {
"source": 2,
"target": 5
}, {
"source": 3,
"target": 6
}, {
"source": 1,
"target": 7
}, {
"source": 6,
"target": 8
}, {
"source": 0,
"target": 7
}, {
"source": 2,
"target": 6
}, {
"source": 3,
"target": 8
}];
var simulation = d3.forceSimulation()
.force("link", d3.forceLink())
.force("charge", d3.forceManyBody().strength(-500))
.force("center", d3.forceCenter(width / 2, height / 2));
var link = svg.selectAll(null)
.data(links)
.enter()
.append("line")
.style("stroke", "#ccc")
.style("stroke-width", 1);
var node = svg.selectAll(null)
.data(nodes)
.enter()
.append("circle")
.attr("r", function(d) {
return d.r = 10;
})
.attr("stroke", "gray")
.attr("stroke-width", "2px")
.attr("fill", function(d) {
return d.color
});
simulation.nodes(nodes);
simulation.force("link")
.links(links);
simulation.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
});
});
<script src="https://d3js.org/d3.v4.min.js"></script>

how to draw polygon by d3.js

I have created five node using d3.js, and make links each other to make a polygon but they are not adjacent position to make a polygon, instead its making a random view other than a polygon.Am I missing something here, please take a look and suggest me.
var width = 300,
height = 300
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var force = d3.layout.force()
.gravity(1)
.linkDistance(200)
.charge(-100)
.size([width, height]);
var datajson = {
"nodes": [{
"name": "a",
"group": 2
}, {
"name": "b",
"group": 1
}, {
"name": "c",
"group": 1
}, {
"name": "d",
"group": 2
}, {
"name": "e",
"group": 2
}],
"links": [{
"source": 0,
"target": 1,
"value": 1,
"distance": 90
}, {
"source": 1,
"target": 2,
"value": 2,
"distance": 90
}, {
"source": 2,
"target": 3,
"value": 3,
"distance": 90
}, {
"source": 3,
"target": 4,
"value": 5,
"distance": 90
}, {
"source": 4,
"target": 0,
"value": 5,
"distance": 90
}]
}
force
.nodes(datajson.nodes)
.links(datajson.links)
.start();
var drag = force.drag()
.on("dragstart", dblclick);
var link = svg.selectAll(".link")
.data(datajson.links)
.enter().append("line")
.attr("class", "link");
var node = svg.selectAll(".node")
.data(datajson.nodes)
.enter().append("g")
.attr("class", "node")
.call(force.drag);
node.append("image")
.attr("x", -8)
.attr("y", -8)
.attr("width", 45)
.attr("height", 45)
.attr("xlink:href", function(d) {
var rnd = Math.floor(Math.random() * 64 + 1);
return null;
});
node.append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.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("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
});
function dblclick(d) {
d3.select(this).classed("fixed", d.px = d.x, d.py = d.y);
console.log(d);
}
.link {
stroke: #dfdfdf;
}
.node text {
pointer-events: none;
font: 10px sans-serif;
}
.link.red {
stroke: blue;
}
<!DOCTYPE html>
<meta charset="utf-8">
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
</body>
When you call
force
.nodes(datajson.nodes)
.links(datajson.links)
.start();
d3 randomly picks starting positions for the nodes, because they don't yet have x and y properties assigned.
However, prior to calling the code above, you could loop over each node and assign it x and y of the corners of the polygon, and they should maintain that relationship. They might not bounce around much though, because they'd already be at their intended position. In that case, you can slightly vary their position relative to their final intended position, by adding some random x and y displacement to the starting values.
Working example
The code that pre-positions the nodes is
var numNodes = datajson.nodes.length
var r = 20;
datajson.nodes.forEach(function(node, i) {
node.x = width/2 + r * Math.sin(2 * Math.PI * i / numNodes)
node.y = height/2 + r * Math.cos(2 * Math.PI * i / numNodes)
console.log(node)
})
I also had to tweak charge (-1000) and linkDistance (100) to make it work.
var width = 300,
height = 300
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var force = d3.layout.force()
.gravity(.5)
.linkDistance(100)
.charge(-1000)
.size([width, height]);
var datajson = {
"nodes": [{
"name": "a",
"group": 2
}, {
"name": "b",
"group": 1
}, {
"name": "c",
"group": 1
}, {
"name": "d",
"group": 2
}, {
"name": "e",
"group": 2
}],
"links": [{
"source": 0,
"target": 1,
"value": 1,
"distance": 90
}, {
"source": 1,
"target": 2,
"value": 2,
"distance": 90
}, {
"source": 2,
"target": 3,
"value": 3,
"distance": 90
}, {
"source": 3,
"target": 4,
"value": 5,
"distance": 90
}, {
"source": 4,
"target": 0,
"value": 5,
"distance": 90
}]
}
var numNodes = datajson.nodes.length
var r = 20;
datajson.nodes.forEach(function(node, i) {
node.x = width/2 + r * Math.sin(2 * Math.PI * i / numNodes)
node.y = height/2 + r * Math.cos(2 * Math.PI * i / numNodes)
console.log(node)
})
force
.nodes(datajson.nodes)
.links(datajson.links)
.start();
var drag = force.drag()
.on("dragstart", dblclick);
var link = svg.selectAll(".link")
.data(datajson.links)
.enter().append("line")
.attr("class", "link");
var node = svg.selectAll(".node")
.data(datajson.nodes)
.enter().append("g")
.attr("class", "node")
.call(force.drag);
node.append("image")
.attr("x", -8)
.attr("y", -8)
.attr("width", 45)
.attr("height", 45)
.attr("xlink:href", function(d) {
var rnd = Math.floor(Math.random() * 64 + 1);
return null;
});
node.append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.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("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
});
function dblclick(d) {
d3.select(this).classed("fixed", d.px = d.x, d.py = d.y);
console.log(d);
}
.link {
stroke: #dfdfdf;
}
.node text {
pointer-events: none;
font: 10px sans-serif;
}
.link.red {
stroke: blue;
}
<!DOCTYPE html>
<meta charset="utf-8">
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
</body>

D3 label on a line in forced graph

There is example how to have a label on the node in a D3 forced graph. What I try to do is to have a label on the line instead.
Example of the label on node: http://bl.ocks.org/mbostock/2706022
This code will display the text for the line up in the left corner. It seems that it takes the x, y cordinates from the canvas and not from my line. How to fix this?
var labelLine = olinks.append("text")
.attr("x", 12)
.attr("dy", ".35em")
.text("eeeeee");
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.node {
stroke: #fff;
stroke-width: 1.5px;
}
.link {
stroke: #999;
stroke-opacity: .6;
}
</style>
<body>
<script src="./Script/d3.v3/d3.v3.min.js"></script>
<script>
var graph = {
"nodes": [
{ "name": "App1-main", "group": 1 },
{ "name": "App2", "group": 1 },
{ "name": "App3", "group": 1 },
{ "name": "App4", "group": 1 },
{ "name": "Content-1", "group": 3 },
{ "name": "Content-1", "group": 3 },
{ "name": "Content-1", "group": 3 },
{ "name": "Content-1", "group": 3 },
{ "name": "Pontmercy", "group": 3 }
],
"links": [
{ "source": 1, "target": 0, "value": 1 },
{ "source": 2, "target": 0, "value": 1 },
{ "source": 0, "target": 3, "value": 1 }
]
};
var width = 960,
height = 500;
var color = d3.scale.category20();
var force = d3.layout.force()
.charge(-300)
.linkDistance(60)
.size([width, height]);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var drawGraph = function (graph) {
force
.nodes(graph.nodes)
.links(graph.links)
.start();
var olinks = svg.selectAll("g.link")
.data(graph.links)
.enter().append("g")
.call(force.drag);
var link = olinks.append("line")
.attr("class", "link")
.style("stroke-width", function (d) { return Math.sqrt(d.value); });
var labelLine = olinks.append("text")
.attr("x", 12)
.attr("dy", ".35em")
.text("eeeeee");
var gnodes = svg.selectAll('g.gnode')
.data(graph.nodes)
.enter()
.append('g')
.classed('gnode', true);
var node = gnodes.append("circle")
.attr("class", "node")
.attr("r", 5)
.style("fill", function (d) { return color(d.group); })
.call(force.drag);
var labels = gnodes.append("text")
.attr("x", 12)
.attr("dy", ".35em")
.text(function (d) { return d.name; });
console.log(labels);
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; });
gnodes.attr("transform", function (d) {
return 'translate(' + [d.x, d.y] + ')';
});
});
};
drawGraph(graph);
</script>
This is an example that has correct behavior.
The key points are here:
1) You need to define link as SVG "g" element, so that you can define both lines and labels for each link, and that coordinates are computed correctly.
2) Label text must be centered horizontally (code: .attr("text-anchor", "middle")).
3) Inside tick(), you need to compute coordinate of the labels., as arithmetic mean between source and target node.
Hope this helps.
There also was another similar question recently.

Categories