I have the following d3 chart. It's simply a handful of nodes and links between them. The chart does 2 things:
You can click and drag individual nodes around the graph.
You can click and drag around the svg in order to pan (and zoom with mousewheel) using d3.behavior.zoom.
My problem I'm seeing is that if I drag a node around (for example, move a node 20px to the right), and then try to pan the chart, the entire graph immediately jumps 20px to the right (per the example). d3.behavior.zoom seems to initially use the d3.event of dragging the node around or something like that. Not sure. Here is the code:
var data = {
nodes: [{
name: "A",
x: 200,
y: 150,
size: 30
}, {
name: "B",
x: 140,
y: 300,
size: 15
}, {
name: "C",
x: 300,
y: 300,
size: 15
}, {
name: "D",
x: 300,
y: 180,
size: 45
}],
links: [{
source: 0,
target: 1
}, {
source: 1,
target: 2
}, {
source: 2,
target: 3
}, ]
};
var dragging = false;
var svg = d3.select("body")
.append("svg")
.attr("width", "100%")
.attr("height", "100%")
.call(d3.behavior.zoom().on("zoom", function() {
if (dragging === false) {
svg.attr("transform", "translate(" + d3.event.translate + ") scale(" + d3.event.scale + ")")
}
})).append("g");
var links = svg.selectAll("link")
.data(data.links)
.enter()
.append("line")
.attr("class", "link")
.attr("x1", function(l) {
var sourceNode = data.nodes.filter(function(d, i) {
return i == l.source
})[0];
d3.select(this).attr("y1", sourceNode.y);
return sourceNode.x
})
.attr("x2", function(l) {
var targetNode = data.nodes.filter(function(d, i) {
return i == l.target
})[0];
d3.select(this).attr("y2", targetNode.y);
return targetNode.x
})
.attr("fill", "none")
.attr("stroke", "white");
var nodes = svg.selectAll("node")
.data(data.nodes)
.enter()
.append("circle")
.each(function(d, i) {
d.object = d3.select(this);
console.log(d);
})
.attr("class", "node")
.attr("cx", function(d) {
return d.x
})
.attr("cy", function(d) {
return d.y
})
.attr("r", function(d) {
return d.size;
})
.on('click', function(d) {
})
.call(d3.behavior.drag()
.on('dragstart', function() {
dragging = true;
})
.on('dragend', function() {
dragging = false;
})
.on("drag", function(d, i) {
d.x += d3.event.dx
d.y += d3.event.dy
d3.select(this).attr("cx", d.x).attr("cy", d.y);
links.each(function(l, li) {
if (l.source == i) {
d3.select(this).attr("x1", d.x).attr("y1", d.y);
} else if (l.target == i) {
d3.select(this).attr("x2", d.x).attr("y2", d.y);
}
});
})
);
Here is a demo:
https://jsfiddle.net/25q1Lu3x/2/
How can I prevent that initially jump when trying to pan around? I have read a few things that suggest making an extra nested svg g element and move that around, but that doesn't seem to make a difference.
According to the D3 v3 API:
When combining drag behaviors with other event listeners for interaction events (such as having drag take precedence over zoom), you may also consider stopping propagation on the source event to prevent multiple actions
So, in your dragstart, just add d3.event.sourceEvent.stopPropagation();:
.on('dragstart', function() {
d3.event.sourceEvent.stopPropagation();
dragging = true;
});
Here is your fiddle: https://jsfiddle.net/hj7krohd/
Related
I am completely lost trying to spawn circles with pictures inside that all move within a force.drag. They should be created based on a list of arrays. Any help is really appreciated!
This is my code so far:
var data2 = [
{id: 1, name: "Sachin", pop: 200, x: 0, y: 0, color: 'red', image: "https://picsum.photos/900" },
{id: 2, name: "Murali", pop: 100, x: 200, y: 200, color: 'green', image: "https://random.imagecdn.app/500/500" }
]
var body = d3.select("body")
var svg = body.append("svg")
.attr("width", 800)
.attr("height", 800)
data2.forEach(function(d){
svg.append("clipPath")
.attr('id', d.id)
.append('circle')
.attr("cx", d.x + d.pop /2)
.attr("cy", d.y + d.pop / 2)
.attr("r", d.pop /2)
.attr("", console.log(d))
.style("fill", "green")
.attr("", console.log("done! with this one"))
})
data2.forEach(function(d){
svg.append('image')
.attr('xlink:href',d.image)
.attr('width', d3.sum([d.pop]))
.attr('height', d3.sum([d.pop]))
.attr('x', d.x)
.attr('y', d.y)
.attr('clip-path','url(#' + d.id + ')');
});
But this only gives me this:
But I am trying to make this...
and applying force to it as shown in this snippet:
var data2 = [
{id: 1, name: "Sachin", pop: 20, color: 'red', image: "https://picsum.photos/900" },
{id: 2, name: "Murali", pop: 10, color: 'green', image: "https://random.imagecdn.app/500/500" }
]
var width = 400//Dimensions.get('window').width,
var height = 400//Dimensions.get('window').height,
// create SVG
var body = d3.select("body") //SVG ÄR HELA ARBETSYTAN
var svg = body.append("svg")
.attr("width", width)
.attr("height", height)
//.style("background", "#000000")
// init a force layout, using the nodes and edges in dataset
var force = d3.layout.force()
.nodes(data2)
//.links(data.edges)
.size([width, height])
.charge([-300]) //.linkDistance([100])
.start()
// define 10 random colors
//var colors = d3.scale.category10()*/
var drums = svg.selectAll("circle")
.data(data2)
.enter()
.append("circle")
drums.attr("cx", function(d) { return width/2 }) //för att vara olika för varje item.
.attr("cy", height/2)
.attr("r", function(d) { return d.pop })
.attr("", function(d) { console.log(d) })
.call(force.drag)
drums.style("fill", function(d) {
if (!d.image) {
return ("url(#" + d.id + ")"); //console.log(d.image)
}else{
return d.color
}
})
force.on("tick", function() {
(svg.selectAll('circle') //drums
.attr("cx", function(d) { return d.x })
.attr("cy", function(d) { return d.y })
)
})
drums.on("click", function(d) {
console.log(d)
d3.select(this)
.attr("r" , d3.sum([d3.select(this).attr("r"),10]))
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.0.4/d3.min.js"></script>
Thank you so much for taking a look into this! :)
I have the following pie chart with a very nice transition:
http://plnkr.co/edit/b4uLimUSZzxiPzAkNpBt?p=preview
The code for the pie chart is the following:
function addPieChart(meas, dataFile) {
var width = 400,
height = 400,
radius = Math.min(width, height) / 2.1,
color = d3.scale.ordinal()
.range(["#016da9", "#4c96d5"])
.domain([0, 1]);
//check if the svg already exists
var plot = d3.select("#svgPIEChart")
if (plot.empty()) {
var vis = d3.select("#pieChart")
.append("svg") //create the SVG element
.attr({
id: "svgPIEChart"
})
} else {
var vis = d3.select("#svgPIEChart")
vis.selectAll("*").remove();
};
//svg element
vis.attr({
//set the width and height of our visualization (these will be attributes of the <svg> tag
width: width,
height: height
});
//group of the svg element
var svg = vis
.append("g")
.attr({
'transform': "translate(" + width / 2 + "," + height * .52 + ")"
});
var arc = d3.svg.arc()
.startAngle(function(d) {
return d.x;
})
.endAngle(function(d) {
return d.x + d.dx;
})
.outerRadius(function(d) {
return (d.y + d.dy) / (radius);
})
.innerRadius(function(d) {
return d.y / (radius);
});
d3.text(dataFile, function(text) {
var csv = d3.csv.parseRows(text);
var json = buildHierarchy(csv);
// it seems d3.layout.partition() can be either squares or arcs
var partition = d3.layout.partition()
.sort(null)
.size([2 * Math.PI, radius * radius])
.value(function(d) {
return d.Revenue;
});
var path = svg.data([json]).selectAll(".theArc")
.data(partition.nodes)
.enter()
.append("path")
.attr("class", "theArc") //<<<<<<<<<<new jq
.attr("id", function(d, i) {
return "theArc_" + i;
}) //Give each slice a unique ID //<<<<<<<<<<new jq
.attr("display", function(d) {
return d.depth ? null : "none";
})
.attr("d", arc)
.style("stroke", "#fff")
.style("fill", function(d) {
return color((d.children ? d : d.parent).name);
})
.attr("fill-rule", "evenodd")
.style("opacity", 1)
.on("mouseover", mouseover)
.on("mouseout", mouseout)
.on("click", up)
.each(stash);
// path.each(stash).on("click", up)
//this is a start>>>>>
path
.append("title") //mouseover title showing the figures
.text(function(d) {
return d.name + ": " + formatAsShortInteger(d.Revenue);
});
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
svg.data([json]).selectAll(".theTxts")
.data(partition.nodes)
.enter()
.append("text")
.attr("class", "theTxts")
.attr("dx", 10) //Move the text from the start angle of the arc
.attr("dy", 15) //Move the text down
.append("textPath")
.attr("class", "foo")
.attr("xlink:href", function(d, i) {
return "#theArc_" + i;
})
.text(function(d) {
if ((d.name != 'root') && ((d.name != 'B T') || (currentMeasure != 'W'))) {
return d.name;
}
})
.on("click", up) //<<<new jq;
svg.append("text")
.attr({
x: "0",
y: "0",
'class': "title",
"id": "titleX",
'text-anchor': "middle"
})
.text(currentMeasure + " split")
//>>
.append("tspan")
.attr({
dy: "1.1em",
x: 0,
'text-anchor': "middle"
})
.text("for " + latestMth)
.attr("class", "title");
d3.selectAll("input").on("change", function change() {
value = createValueFunc(this.value);
currentMeasure = this.value;
var path2 = svg.data([json]).selectAll(".theArc");
path2
.data(partition.value(value).nodes)
.transition()
.duration(1500)
.attrTween("d", arcTween)
//to update the tooltips
svg.selectAll("title")
.text(function(d) {
return d.name + ": " + formatAsShortInteger(value(d));
});
svg.selectAll("textPath")
.text(function(d) {
if ((d.name != 'root') && ((d.name != 'B T') || (currentMeasure != 'W'))) {
return d.name;
}
});
// the following deletes what was originally created and then recreates the text
svg.selectAll("#titleX").remove();
svg.append("text")
.attr({
x: "0",
y: "0",
'class': "title",
"id": "titleX",
'text-anchor': "middle"
})
.text(currentMeasure + " split")
.append("tspan")
.attr({
dy: "1.1em",
x: 0,
'text-anchor': "middle"
})
.text("for " + latestMth)
.attr("class", "title");
addProdTitl();
updateTOPbarChart(currentGrp, currentMeasure, currentColr);
updateBOTbarChart(currentGrp, currentMeasure, currentColr);
});
function mouseover() {
d3.select(this)
.transition()
.duration(200)
.style("opacity", 0.9);
};
function mouseout() {
d3.select(this)
.transition()
.duration(200)
.style("opacity", 1);
};
// update bar chart when user selects piece of the pie chart
function up(d, i) {
currentGrp = d.name;
// (d.children ? d : d.parent).name
currentColr = color((d.children ? d : d.parent).name); // color(d.name);
addProdTitl();
updateTOPbarChart(d.name, currentMeasure, currentColr); //color(d.name));
updateBOTbarChart(d.name, currentMeasure, currentColr); //color(d.name));
};
// Stash the old values for transition.
function stash(d) {
d.x0 = d.x;
d.dx0 = d.dx;
};
// Interpolate the arcs in data space.
function arcTween(a) {
var i = d3.interpolate({
x: a.x0,
dx: a.dx0
}, a);
return function(t) {
var b = i(t);
a.x0 = b.x;
a.dx0 = b.dx;
return arc(b);
};
};
});
};
If you change the selection of the radio button and then click on a section of the pie chart that section does not complete its transition as I believe it decides it needs to move on to its "on" event.
How do I ensure that the transition completes? (and the pie chart does not do a pac-man impersonation)
Clear your listeners when the tranistion starts.
Attach it when the transition is ended
Like this:
path2
.data(partition.value(value).nodes)
.transition()
.duration(1500)
.attrTween("d", arcTween)
.each("start", function(){
d3.select(this)
.on("mouseover", null) //CLEARING the listeners
.on("mouseout", null) //CLEARING the listeners
.on("click", null) //CLEARING the listeners
})
.each("end", function(){
d3.select(this)
.on("mouseover", mouseover) //attaching the listeners
.on("mouseout", mouseout) //attaching the listeners
.on("click", up) ////attaching the listeners
});
working code here
//bubble chart base.
http://jsfiddle.net/NYEaX/1450/
I am trying to animate the bubbles - via changing their scale -- and if possible fade them in and out. At some stage I need to cluster them with some kind of gravity to occupy more of a containing circumference.
(function() {
var diameter = 250;
var svg = d3.select('#graph').append('svg')
.attr('width', diameter)
.attr('height', diameter);
var bubble = d3.layout.pack()
.size([diameter, diameter])
.value(function(d) {
return d.size;
})
.padding(3);
var color = d3.scale.ordinal()
.domain(["Lorem ipsum", "dolor sit", "amet", "consectetur", "adipisicing"])
.range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56"]);
function randomData() {
var data1 = {
"children": [
{
name: "AA",
className: "aa",
size: 170
},
{
name: "BB",
className: "bb",
size: 393
},
{
name: "CC",
className: "cc",
size: 293
},
{
name: "DD",
className: "dd",
size: 89
}
]
};
var data2 = {
"children": [
{
name: "AA",
className: "aa",
size: 120
},
{
name: "BB",
className: "bb",
size: 123
},
{
name: "CC",
className: "cc",
size: 193
},
{
name: "DD",
className: "dd",
size: 289
}
]
};
var j = Math.floor((Math.random() * 2) + 1);
console.log("j", j);
if (j == 1) {
return data1;
} else {
return data2;
}
}
change(randomData());
d3.select(".randomize")
.on("click", function() {
change(randomData());
});
function change(data) {
console.log("data", data);
// generate data with calculated layout values
var nodes = bubble.nodes(data)
.filter(function(d) {
return !d.children;
}); // filter out the outer bubble
var vis = svg.selectAll('circle')
.data(nodes);
vis.enter()
.insert("circle")
.attr('transform', function(d) {
return 'translate(' + d.x + ',' + d.y + ')';
})
.attr('r', function(d) {
return d.r;
})
.style("fill", function(d) {
return color(d.name);
})
.attr('class', function(d) {
return d.className;
});
vis
.transition().duration(1000)
vis.exit()
.remove();
};
})();
These animated bubbles will be part of this overall chart - I need support in fine-tuning the animated arcs and bubbles accordingly. So the pie chart arcs should tween smoothly -- and the bubbles should fade in/out/grow in radius/reduce in radius
Latest fiddle.
http://jsfiddle.net/NYEaX/1505/
( http://jsfiddle.net/NYEaX/1506/ )- refactored
1. -- how to animate the arcs
2. -- how to animate the bubbles
3. -- adding back the randomise button to test with 2 dummy data sets.
this is the old pie animations and worked very well
/* ------- ANIMATE PIE SLICES -------*/
var slice = doughpie.select(".slices").selectAll("path.slice")
.data(pie(data), key);
slice.enter()
.insert("path")
.style("fill", function(d) {
return color(d.data.label);
})
.style("transform", function(d, i){
//return "translate(0, 0)";
})
.attr("class", "slice");
slice
.transition().duration(1000)
.attrTween("d", function(d) {
this._current = this._current || d;
var interpolate = d3.interpolate(this._current, d);
this._current = interpolate(0);
return function(t) {
return arc(interpolate(t));
};
})
slice.exit()
.remove();
/* ------- ANIMATE PIE SLICES -------*/
//this is the current pie arcs - but when I try and animate the pie in the same manner - it fails.
var g = svg.selectAll(".arc")
.data(pie(data))
.enter().append("g")
.attr("class", "arc");
g.append("path")
.attr("d", arc)
.style("fill", function(d) {
return color(d.data.label);
});
arc
.outerRadius(radius - 10)
.innerRadius(0);
Try and animate the pie chart first, then review the bubble animation
To animate the bubbles (grow in) use:
vis.enter()
.insert("circle")
.attr('transform', function(d) {
return 'translate(' + d.x + ',' + d.y + ')';
})
.attr('r',0)
.transition()
.duration(1000)
.attr('r', function(d) {
return d.r;
})
.style("fill", function(d) {
return color(d.name);
})
.attr('class', function(d) {
return d.className;
});
I tried adding this code to the jsfiddle you posted above : http://jsfiddle.net/NYEaX/1450/
How can I make a basic connected graph (two nodes and a link connecting them for example) that doesn't use a force() layout? I just want to be able to drag a node and have the link adjust to stay connected as a node is being dragged. I dont want any of the charge or positioning capabilities of force(). Essentially I want every node to be "sticky". Nodes will only move when being dragged.
But is there a simple way to do this? Every example I have seen is built around a force directed graph.
I've looked at this example, http://bl.ocks.org/mbostock/3750558 , but it starts with a force directed graph then makes the nodes sticky. This approach seems backwards for what I want.
Is there a basic example somewhere?
I have made a small code snippet. Hope this is helpful.
var data = {
nodes: [{
name: "A",
x: 200,
y: 150
}, {
name: "B",
x: 140,
y: 300
}, {
name: "C",
x: 300,
y: 300
}, {
name: "D",
x: 300,
y: 180
}],
links: [{
source: 0,
target: 1
}, {
source: 1,
target: 2
}, {
source: 2,
target: 3
}, ]
};
var c10 = d3.scale.category10();
var svg = d3.select("body")
.append("svg")
.attr("width", 1200)
.attr("height", 800);
var drag = d3.behavior.drag()
.on("drag", function(d, i) {
d.x += d3.event.dx
d.y += d3.event.dy
d3.select(this).attr("cx", d.x).attr("cy", d.y);
links.each(function(l, li) {
if (l.source == i) {
d3.select(this).attr("x1", d.x).attr("y1", d.y);
} else if (l.target == i) {
d3.select(this).attr("x2", d.x).attr("y2", d.y);
}
});
});
var links = svg.selectAll("link")
.data(data.links)
.enter()
.append("line")
.attr("class", "link")
.attr("x1", function(l) {
var sourceNode = data.nodes.filter(function(d, i) {
return i == l.source
})[0];
d3.select(this).attr("y1", sourceNode.y);
return sourceNode.x
})
.attr("x2", function(l) {
var targetNode = data.nodes.filter(function(d, i) {
return i == l.target
})[0];
d3.select(this).attr("y2", targetNode.y);
return targetNode.x
})
.attr("fill", "none")
.attr("stroke", "white");
var nodes = svg.selectAll("node")
.data(data.nodes)
.enter()
.append("circle")
.attr("class", "node")
.attr("cx", function(d) {
return d.x
})
.attr("cy", function(d) {
return d.y
})
.attr("r", 15)
.attr("fill", function(d, i) {
return c10(i);
})
.call(drag);
svg {
background-color: grey;
}
<script src="https://d3js.org/d3.v3.min.js"></script>
Gilsha has a great answer, but note that newer versions of d3 no longer use the behavior module.
Instead of this:
var drag = d3.behavior.drag()
.on("drag", function(d, i) {
d.x += d3.event.dx
d.y += d3.event.dy
d3.select(this).attr("cx", d.x).attr("cy", d.y);
links.each(function(l, li) {
if (l.source == i) {
d3.select(this).attr("x1", d.x).attr("y1", d.y);
} else if (l.target == i) {
d3.select(this).attr("x2", d.x).attr("y2", d.y);
}
});
});
Simply change d3.behavior.drag() to d3.drag()
var drag = d3.drag()
.on("drag", function(d, i) {
d.x += d3.event.dx
d.y += d3.event.dy
d3.select(this).attr("cx", d.x).attr("cy", d.y);
links.each(function(l, li) {
if (l.source == i) {
d3.select(this).attr("x1", d.x).attr("y1", d.y);
} else if (l.target == i) {
d3.select(this).attr("x2", d.x).attr("y2", d.y);
}
});
});
I am having an annoying issue with my d3 force directed map I'm building where I initially render the page with the nodes and links I need, then periodically check for new information via ajax. When I need a new node and link I draw them, which is fine
However because of the way SVG layers elements, new links draw over older nodes, so where I have nodes as circles and draw lines between them, any new nodes added draw over the top of the circles on older nodes. See image below:
(http://i40.tinypic.com/4fx25j.gif)
I know it is not technically a d3 issue but there must be a way of fixing this. I did try deleting all the circles and redrawing them, but the issue is that the svg:g node it is attached to is too low in the layers so it is still drawn over.
Demo at jsfiddle - look at the following section
draw() {
...
}
as that is where the magic happens.
http://jsfiddle.net/zuzzy/uwAhy/
I have simulated the ajax using a 5 second timer, it was easier for the demo.
Any ideas?
As far as I am aware, you can only control the depth of an SVG element by it's position in the DOM.
So what might work for you is to create two groups <g id='lines'> and <g id='circles'>.
When you append your elements, all of the lines should be added to the first group, and all of the circles to the second.
You might have to alter the way that you add the elements, but so long as you make sure that the lines group appears before the circles group then you should be golden.
I apologise if this totally does not fit your implementation. I ran into a very similar problem and found the only resolution for me was to draw the 'lower' elements first.
Worked first time! I had already grouped all my elements under one so I just replaced:
var vis = d3.select("body")
.append("svg:svg")
.attr("pointer-events", "all");
.append('svg:g')
where i used vis.xxxx to render both links and circles, with
var vis = d3.select("body")
.append("svg:svg")
.attr("pointer-events", "all");
var linkvis = vis.append('svg:g')
.attr("id","link_elements");
vis = vis.append('svg:g')
.attr("id","node_elements");
and referred to linkvis when drawing the links and vis drawing the circles.
(NB I know this should be a comment but I couldn't fit it in and I thought it might be helpful for someone. #Paul's answer has been marked as the answer)
Another way of resolving this issue would be to use insert method as shown in following code.
link.enter().insert("line",".node"); //Inserts link element before the first DOM element with class node.
Posting just because this may be helpful for other users who search solution for this question.
//Settings:
//width, height and the default radius of the circles
var w = 1024,
h = 768,
r = 10;
//test data - usually this is recieved via ajax
//Initial Data:
var hosts = eval({
"ITEM003": {
"name": "ITEM003",
"parents": [],
"status": 0,
"hostgroup": "Secure"
},
"ITEM004": {
"name": "ITEM004",
"parents": [],
"status": 0,
"hostgroup": "Secure"
},
"CORE": {
"name": "CORE",
"parents": ["ITEM004", "ITEM003"],
"status": 0,
"hostgroup": "DMZ"
}
});
var mylinks = eval({
"0": ["CORE", "ITEM004"],
"1": ["CORE", "ITEM003"]
});
//Data after update
var updated_hosts = eval({
"ITEM003": {
"name": "ITEM003",
"parents": [],
"status": 0,
"hostgroup": "Secure"
},
"ITEM004": {
"name": "ITEM004",
"parents": [],
"status": 0,
"hostgroup": "Secure"
},
"CORE": {
"name": "CORE",
"parents": ["ITEM004", "ITEM003"],
"status": 0,
"hostgroup": "DMZ"
},
"REMOTE": {
"name": "REMOTE",
"parents": [],
"status": 0,
"hostgroup": ""
}
});
var updated_mylinks = eval({
"0": ["CORE", "ITEM004"],
"1": ["CORE", "ITEM003"],
"2": ["CORE", "REMOTE"]
});
//I define these here so they carry between functions - not really necessary in this jsfiddle probably
window.link = undefined;
window.node = undefined;
//make up my node object
window.nodeArray = [];
window.node_hash = [];
for (var key in hosts) {
var a = {
id: "node_" + hosts[key].name,
labelText: hosts[key].name,
status: hosts[key].status,
hostgroup: hosts[key].hostgroup,
class: "node realnode",
iconimage: hosts[key].iconimage,
added: true
};
nodeArray.push(a);
node_hash[key] = a;
}
//make up my link object
window.linkArray = [];
for (var key in mylinks) {
var linkcolor = "#47CC60";
var a = {
source: node_hash[mylinks[key][0]],
target: node_hash[mylinks[key][1]],
color: linkcolor,
class: "link reallink"
};
linkArray.push(a);
}
//make up my node text objects
//these are just more nodes with a different class
//we will append text to them later
//we also add the links to the linkArray now to bind them to their real nodes
window.text_hash = [];
for (var key in hosts) {
var a = {
id: "label_" + hosts[key].name,
text: hosts[key].name,
color: "#ffffff",
size: "6",
class: "node label",
added: true
};
nodeArray.push(a);
text_hash[key] = a;
}
//because the text labels are in the same order as the
//original nodes we know that node_hash[0] has label text_hash[0]
//it doesn't matter which we iterate through here
for (var key in text_hash) {
var a = {
source: node_hash[key],
target: text_hash[key],
class: "link label"
};
linkArray.push(a);
}
//set up the environment in a div called graph using the settings baove
window.vis = d3.select("body")
.append("svg:svg")
.attr("height", 500)
.attr("width", 500)
.attr("pointer-events", "all")
.append('svg:g')
//object to interact with the force libraries in d3
//the settings here set how the nodes interact
//seems a bit overcomplicated but it stops the diagram going nuts!
window.force = d3.layout.force()
.friction("0.7")
.gravity(function(d, i) {
if (d.class == "link reallink") {
return "0.95";
} else {
return "0.1";
}
})
.charge(function(d, i) {
if (d.class == "link reallink") {
return "-1500";
} else {
return "-300";
}
})
.linkDistance(function(d) {
if (d.class == "link reallink") {
return "120";
} else {
return "35";
}
})
.linkStrength(function(d) {
if (d.class == "link reallink") {
return "8";
} else {
return "6";
}
})
.nodes(nodeArray)
.links(linkArray)
.on("tick", tick)
node = vis.selectAll(".node");
link = vis.selectAll(".link");
//create the objects and run it
draw();
for (key in nodeArray) {
nodeArray[key].added = false;
}
//wait 5 seconds then update the diagram TO ADD A NODE
setTimeout(function() {
//update the objects
//vis.selectAll("g.node").data(nodeArray).exit().transition().ease("elastic").remove();
//vis.selectAll("line").data(linkArray).exit().transition().ease("elastic").remove();
var a = {
id: "node_REMOTE",
labelText: "REMOTE",
status: "0",
hostgroup: "",
class: "node realnode",
iconimage: "",
added: true
};
nodeArray.push(a);
node_hash["REMOTE"] = a;
var linkcolor = "#47CC60";
var a = {
source: node_hash["CORE"],
target: node_hash["REMOTE"],
color: linkcolor,
class: "link reallink"
};
linkArray.push(a);
//make up my node text objects
var a = {
id: "label_REMOTE",
text: "REMOTE",
color: "#000000",
size: "6",
class: "node label",
added: true
};
nodeArray.push(a);
text_hash["REMOTE"] = a;
var a = {
source: node_hash["REMOTE"],
target: text_hash["REMOTE"],
class: "link label"
};
linkArray.push(a);
//redraw it
draw();
}, 5000);
//----- functions for drawing and tick below
function draw() {
link = link.data(force.links(), function(d) {
return d.source.id + "-" + d.target.id;
});
node = node.data(force.nodes(), function(d) {
return d.id;
});
//create the link object using the links object in the json
//link = vis.selectAll("line").data(linkArray);
link.enter().insert("line", ".node")
.attr("stroke-width", '0')
.transition()
.duration(1000)
.ease("bounce")
.attr("stroke-width", function(d, i) {
if (d.class == 'link reallink') {
return '3';
} else {
return '0';
};
})
.style("stroke", function(d, i) {
return d.color;
})
.attr("class", function(d, i) {
return d.class;
});
//node = vis.selectAll("g").data(nodeArray);
node.enter().append("svg:g")
.attr("class", function(d) {
return d.class
})
.attr("id", function(d) {
return d.id
})
.call(force.drag);
//append to each node an svg circle element
vis.selectAll(".realnode").filter(function(d) {
return d.added;
})
.append("svg:circle")
.attr("r", "0")
.transition()
.duration(1000)
.ease("bounce")
.attr("r", "6")
.style("fill", "#000000")
.style("stroke", function(d) {
return d.color;
})
.style("stroke-width", "4");
//append to each node the attached text desc
vis.selectAll(".label").filter(function(d) {
return d.added;
})
.append("svg:text")
.attr("text-anchor", "middle")
.attr("fill", "black")
.style("pointer-events", "none")
.attr("font-size", "9px")
.attr("font-weight", "100")
.text(function(d) {
return d.text;
})
.attr("transform", "rotate(180)")
.transition()
.duration(1000)
.ease("bounce")
.attr("transform", "rotate(0)");
node.exit().transition().ease("elastic").remove();
link.exit().transition().ease("elastic").remove();
//activate it all - initiate the nodes and links
force.start();
}
function tick() {
node.attr("cx", function(d) {
return d.x = Math.max(r + 15, Math.min(w - r - 15, d.x));
})
.attr("cy", function(d) {
return d.y = Math.max(r + 15, Math.min(h - r - 15, d.y));
})
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
link.data(linkArray).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;
});
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>