Given the following code which calls the update function which creates 4 nodes with a circle and text element nested in a g element, waits 500ms, then calls the function again with updated data:
var data1 = [
{ x: 10, y: 10, text: "A" },
{ x: 30, y: 30, text: "B" },
{ x: 50, y: 50, text: "C" },
{ x: 70, y: 70, text: "D" }
];
var data2 = [
{ x: 30, y: 10, text: "X" },
{ x: 50, y: 30, text: "Y" },
{ x: 70, y: 50, text: "Z" },
{ x: 90, y: 70, text: "W" }
];
var svg = d3.select("body").append("svg");
update(data1);
setTimeout(function() { update(data2); }, 500);
function update(data) {
var nodes = svg.selectAll(".node")
.data(data);
var nodesUpdate = nodes
.attr("class", "node update")
var nodesEnter = nodes.enter();
var node = nodesEnter.append("g")
.attr("class", "node enter")
node
.attr("transform", function(d) { return "translate("+d.x+","+d.y+")"; });
node.append("circle")
.attr("r", 10)
.style("opacity", 0.2);
node.append("text")
.text(function(d) { return d.text; });
}
With the code as it is the second call has no effect, because everything is set in the enter selection. I'm trying to make it so I can call update with new data, and change properties on both the enter and update selections, without duplicating code. I can achieve this for top-level elements (ie the g elements) using merge, by making this change:
node
.merge(nodesUpdate)
.attr("transform", function(d) { return "translate("+d.x+","+d.y+")"; });
Now the nodes update their position after 500ms. However, I haven't been able to figure out how to update the text element. If I do nodes.selectAll("text") I end up with nested data, which doesn't work.
I've scoured the following docs to try and figure this out:
https://bl.ocks.org/mbostock/3808218
https://github.com/d3/d3-selection
https://bost.ocks.org/mike/nest/
It should just be nodes.select when dealing with a subselection.
Here's a quick refactor with comments and clearer variable names:
<!DOCTYPE html>
<html>
<head>
<script data-require="d3#4.0.0" data-semver="4.0.0" src="https://d3js.org/d3.v4.min.js"></script>
</head>
<body>
<script>
var data1 = [{
x: 10,
y: 10,
text: "A"
}, {
x: 30,
y: 30,
text: "B"
}, {
x: 50,
y: 50,
text: "C"
}, {
x: 70,
y: 70,
text: "D"
}];
var data2 = [{
x: 30,
y: 10,
text: "X"
}, {
x: 50,
y: 30,
text: "Y"
}, {
x: 70,
y: 50,
text: "Z"
}, {
x: 90,
y: 70,
text: "W"
}];
var svg = d3.select("body").append("svg");
update(data1);
setTimeout(function() {
update(data2);
}, 500);
function update(data) {
var nodesUpdate = svg.selectAll(".node")
.data(data); // UPDATE SELECTION
var nodesEnter = nodesUpdate.enter()
.append("g")
.attr("class", "node"); // ENTER THE Gs
nodesEnter.append("text"); // APPEND THE TEXT
nodesEnter.append("circle") // APPEND THE CIRCLE
.attr("r", 10)
.style("opacity", 0.2);
var nodesEnterUpdate = nodesEnter.merge(nodesUpdate); // UPDATE + ENTER
nodesEnterUpdate // MOVE POSITION
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
nodesEnterUpdate.select("text") // SUB-SELECT THE TEXT
.text(function(d) {
return d.text;
});
}
</script>
</body>
</html>
Without refactoring a lot of your code, the simplest solution is using a key in the data function, followed by an "exit" selection:
var nodes = svg.selectAll(".node")
.data(data, d=> d.text);
nodes.exit().remove();
Here is the demo:
var data1 = [{
x: 10,
y: 10,
text: "A"
}, {
x: 30,
y: 30,
text: "B"
}, {
x: 50,
y: 50,
text: "C"
}, {
x: 70,
y: 70,
text: "D"
}];
var data2 = [{
x: 30,
y: 10,
text: "X"
}, {
x: 50,
y: 30,
text: "Y"
}, {
x: 70,
y: 50,
text: "Z"
}, {
x: 90,
y: 70,
text: "W"
}];
var svg = d3.select("body").append("svg");
update(data1);
setTimeout(function() {
update(data2);
}, 500);
function update(data) {
var nodes = svg.selectAll(".node")
.data(data, d => d.text);
nodes.exit().remove();
var nodesUpdate = nodes
.attr("class", "node update")
var nodesEnter = nodes.enter();
var node = nodesEnter.append("g")
.attr("class", "node enter")
node
.merge(nodesUpdate)
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
node.append("circle")
.attr("r", 10)
.style("opacity", 0.2);
node.append("text")
.text(function(d) {
return d.text;
});
}
<script src="https://d3js.org/d3.v4.min.js"></script>
This will create a different "enter" selection. If, on the other hand, you want to get the data bound to the "update" selection, you'll have to refactor your code.
Related
i want to update the graph but it does not work. I want to update the line and the circles. I tried to add
.exit().remove()
to update the circle and
g.selectAll("path").attr("d", line);
to update the paths.
However it does not work.
Updating the outside group with exit().remove() works fine. (Checkbox in this example).
Updating only the path and circle does not work. (Update Button in this example)
I dont want to remove all lines in the graph and append it again, because i want to add transition when data changes.
Here is a JS Fiddle: LINK
Here is my code:
var data = [
[{
point: {
x: 10,
y: 10
}
}, {
point: {
x: 100,
y: 30
}
}],
[{
point: {
x: 30,
y: 100
}
}, {
point: {
x: 230,
y: 30
}
},
{
point: {
x: 50,
y: 200
}
},
{
point: {
x: 50,
y: 300
}
},
]
];
var svg = d3.select("svg");
var line = d3.line()
.x((d) => d.point.x)
.y((d) => d.point.y);
function updateGraph() {
console.log(data)
var allGroup = svg.selectAll(".pathGroup").data(data);
var g = allGroup.enter()
.append("g")
.attr("class", "pathGroup")
allGroup.exit().remove()
g.append("path")
.attr("class", "line")
.attr("stroke", "red")
.attr("stroke-width", "1px")
.attr("d", line);
g.selectAll("path").attr("d", line);
g.selectAll(null)
.data(d => d)
.enter()
.append("circle")
.attr("r", 4)
.attr("fill", "teal")
.attr("cx", d => d.point.x)
.attr("cy", d => d.point.y)
.exit().remove()
}
updateGraph()
document.getElementById('update').onclick = function(e) {
data = [
[{
point: {
x: 10,
y: 10
}
}, {
point: {
x: 100,
y: 30
}
}],
[{
point: {
x: 30,
y: 100
}
}, {
point: {
x: 230,
y: 30
}
},
{
point: {
x: 50,
y: 300
}
},
]
];
updateGraph()
}
$('#cb1').click(function() {
if ($(this).is(':checked')) {
data = [
[{
point: {
x: 10,
y: 10
}
}, {
point: {
x: 100,
y: 30
}
}],
[{
point: {
x: 30,
y: 100
}
}, {
point: {
x: 230,
y: 30
}
},
{
point: {
x: 50,
y: 200
}
},
{
point: {
x: 50,
y: 300
}
},
]
];
} else {
data = [
[{
point: {
x: 10,
y: 10
}
}, {
point: {
x: 100,
y: 30
}
}]
];
}
updateGraph()
});
Problem
The reason why allGroup.exit().remove() does nothing is that the updated dataset still has the same number of items as the original one. The exit selection is therefore empty.
The variable data contains lines, not points. The one defined at page load, and the one inside update listener contain two lines, only the number of points in them differs.
You can check this by putting a console.log(data.length) inside function updateGraph.
Solution 1
Change your data structure. You can assign an id property to each line, and use .data's, key function. cf. d3-selection documentation.
Updated jsFiddle implementing solution 1: see here.
This solution requires less changes.
Solution 2
In case you have no control over the data structure, you can transition the line drawing inside the update selection, rather than the exit one.
This question builds on this question.
Using d3.js/dc.js, I have three (or more) charts. All have the same x-axis (a date series), so the nth datapoint on any chart will correspond exactly to the nth datapoint on the x-axis of the other charts.
When the user clicks on a dot point in one chart, I need to get the "y" data from the same point on the other 2+ charts and return an array or object or string with the chartID/y-datum from the other charts, something like this:
{"chart1":"30","chart2":"50","chart3":"10"}
Here is an example borrowed from Gerardo Furtado's answer to the above-referenced question. How would I modify Gerardo's example to return the datapoints from each chart?
var data = [{x:20, y:30},
{x:30, y:60},
{x:40, y:40},
{x:50, y:90},
{x:60, y:20},
{x:70, y:90},
{x:80, y:90},
{x:90, y:10}];
draw("#svg1");
draw("#svg2");
draw("#svg3");
function draw(selector){
var width = 250,
height = 250;
var svg = d3.select(selector)
.append("svg")
.attr("width", width)
.attr("height", height);
var xScale = d3.scaleLinear()
.domain([0, 100])
.range([30, width - 10]);
var yScale = d3.scaleLinear()
.domain([0,100])
.range([height - 30, 10]);
var circles = svg.selectAll("foo")
.data(data)
.enter()
.append("circle");
circles.attr("r", 10)
.attr("fill", "teal")
.attr("cx", d=>xScale(d.x))
.attr("cy", d=>yScale(d.y));
var xAxis = d3.axisBottom(xScale);
var yAxis = d3.axisLeft(yScale);
svg.append("g").attr("transform", "translate(0,220)")
.attr("class", "xAxis")
.call(xAxis);
svg.append("g")
.attr("transform", "translate(30,0)")
.attr("class", "yAxis")
.call(yAxis);
}
d3.selectAll("circle").on("mouseover", function(){
var thisDatum = d3.select(this).datum();
d3.selectAll("circle").filter(d=>d.x == thisDatum.x && d.y == thisDatum.y).attr("fill", "firebrick");
}).on("mouseout", function(){
d3.selectAll("circle").attr("fill", "teal")
})
#svg1 {
float: left;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<div id="svg1"></div>
<div id="svg2"></div>
<div id="svg3"></div>
As you have several different data sets, I'll modify the answer I wrote in your previous question so we can have different y values.
First, let't put all data in an object. That way, we can access the different data sets later:
var dataObject = {
data1: [{
x: 10,
y: 30
}, ...
}],
data2: [{
x: 10,
y: 70
}, ...
}],
data3: [{
x: 10,
y: 10
}, ...
}]
};
Then, we call the draw function:
draw("#svg1", dataObject.data1);
draw("#svg2", dataObject.data2);
draw("#svg3", dataObject.data3);
So, to get what you want, in the mouseover...
d3.selectAll("circle").on("mouseover", function() {
var thisDatum = d3.select(this).datum();
findPoints(thisDatum);
})
We call this function:
function findPoints(datum) {
var myObject = {};
for (var i = 1; i < 4; i++) {
myObject["chart" + i] = dataObject["data" + i].filter(e => e.x === datum.x)[0].y;
}
console.log(myObject)//use return instead of console.log
}
Here is the demo:
var dataObject = {
data1: [{
x: 10,
y: 30
}, {
x: 20,
y: 60
}, {
x: 30,
y: 40
}, {
x: 40,
y: 90
}, {
x: 50,
y: 20
}, {
x: 60,
y: 90
}, {
x: 70,
y: 90
}, {
x: 80,
y: 10
}],
data2: [{
x: 10,
y: 70
}, {
x: 20,
y: 60
}, {
x: 30,
y: 80
}, {
x: 40,
y: 10
}, {
x: 50,
y: 10
}, {
x: 60,
y: 20
}, {
x: 70,
y: 10
}, {
x: 80,
y: 90
}],
data3: [{
x: 10,
y: 10
}, {
x: 20,
y: 20
}, {
x: 30,
y: 40
}, {
x: 40,
y: 90
}, {
x: 50,
y: 80
}, {
x: 60,
y: 70
}, {
x: 70,
y: 50
}, {
x: 80,
y: 50
}]
};
draw("#svg1", dataObject.data1);
draw("#svg2", dataObject.data2);
draw("#svg3", dataObject.data3);
function draw(selector, data) {
var width = 200,
height = 100;
var svg = d3.select(selector)
.append("svg")
.attr("width", width)
.attr("height", height);
var xScale = d3.scaleLinear()
.domain([0, 100])
.range([30, width - 10]);
var yScale = d3.scaleLinear()
.domain([0, 100])
.range([height - 30, 10]);
var circles = svg.selectAll("foo")
.data(data)
.enter()
.append("circle");
circles.attr("r", 5)
.attr("fill", "palegreen")
.attr("cx", d => xScale(d.x))
.attr("cy", d => yScale(d.y));
var xAxis = d3.axisBottom(xScale);
var yAxis = d3.axisLeft(yScale).ticks(2);
svg.append("g").attr("transform", "translate(0,70)")
.attr("class", "xAxis")
.call(xAxis);
svg.append("g")
.attr("transform", "translate(30,0)")
.attr("class", "yAxis")
.call(yAxis);
}
d3.selectAll("circle").on("mouseover", function() {
var thisDatum = d3.select(this).datum();
findPoints(thisDatum);
d3.selectAll("circle").filter(d => d.x == thisDatum.x).attr("fill", "firebrick");
}).on("mouseout", function() {
d3.selectAll("circle").attr("fill", "palegreen")
})
function findPoints(datum) {
var myObject = {};
for (var i = 1; i < 4; i++) {
myObject["chart" + i] = dataObject["data" + i].filter(e => e.x === datum.x)[0].y;
}
console.log(JSON.stringify(myObject))
}
#svg1, #svg2 {
float: left;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<div id="svg1"></div>
<div id="svg2"></div>
<div id="svg3"></div>
I have a set of nodes data
var dataNodes = [
{ id: 1, x: 10, y:30, text: "node 1", muteText: false },
{ id: 2, x: 30, y:50, text: "node 2", muteText: false },
{ id: 3, x: 50, y:70, text: "node 3", muteText: false }
];
I add the elements in the DOM using this kind of function (not my real code because of a lot of business complexity) :
function redraw(newData) {
var node = d3
.select("body")
.selectAll("g.node")
.data(dataNodes)
.transition().duration(500)
.attr("transform", d => "translate(" + d.x + "," + d.y + ")");
node.enter()
.append("g")
.attr("class", "node")
.attr("transform", d => "translate(" + d.x + "," + d.y + ")")
.append("text")
.text(d => d.text)
.style("opacity", "0")
.transition().duration(500)
.style("opacity", "1");
node.exit()
.style("opacity", "0");
}
I want to be able to do all the following when the data get updated:
add entering nodes
make already existing nodes move with a transition
hide exitings nodes (opacity 0) because they may reappear
when nodes get their "muteText" property changed to true, make the inner text disapear
I'm quite confortable with the 3 first requiremeents but I really don't know how to do the last one : how can I remove (or even change) sub elements based on a filtered set of data ? Can I use the filter in the d3.data function to do it ?
Let me know if my question is unclear.
If you want to filter, do it on your update selection:
var node = svg
.selectAll("g.node")
.data(someData);
var nodeE = node.enter()
.append("g")
.attr("class", "node");
nodeE.append("text")
.text(d => d.text);
// node is now UPDATE + ENTER
node = nodeE.merge(node);
// filter the text and set how you care
node.filter(function(d) {
return d.muteText
})
.select("text")
.style("opacity", 1)
.transition()
.style("opacity", 0);
node.filter(function(d) {
return !d.muteText
})
.select("text")
.style("opacity", 0)
.transition()
.style("opacity", 1);
Here's a running example:
<!DOCTYPE html>
<html>
<head>
<script data-require="d3#4.0.0" data-semver="4.0.0" src="https://d3js.org/d3.v4.min.js"></script>
<style>
text {
fill: black;
font-family: arial;
}
</style>
</head>
<body>
<script>
var dataNodes = [{
id: 1,
x: 10,
y: 30,
text: "node 1",
muteText: false
}, {
id: 2,
x: 30,
y: 50,
text: "node 2",
muteText: false
}, {
id: 3,
x: 50,
y: 70,
text: "node 3",
muteText: false
}];
var svg = d3.select('body')
.append('svg')
.attr('width', 500)
.attr('height', 500);
redraw([{
id: 1,
x: 10,
y: 30,
text: "node 1",
muteText: false
}, {
id: 2,
x: 30,
y: 50,
text: "node 2",
muteText: false
}, {
id: 3,
x: 50,
y: 70,
text: "node 3",
muteText: false
}]);
setTimeout(function() {
redraw([{
id: 1,
x: 10,
y: 30,
text: "node 1",
muteText: true
}, {
id: 2,
x: 100,
y: 50,
text: "node 2",
muteText: false
}, {
id: 3,
x: 50,
y: 70,
text: "node 3",
muteText: true
}])
}, 2000)
setTimeout(function() {
redraw([{
id: 1,
x: 10,
y: 30,
text: "node 1",
muteText: true
}, {
id: 2,
x: 100,
y: 50,
text: "node 2",
muteText: false
}, {
id: 3,
x: 50,
y: 70,
text: "node 3",
muteText: false
},{
id: 4,
x: 60,
y: 90,
text: "node 4",
muteText: false
}])
}, 4000)
function redraw(someData) {
var node = svg
.selectAll("g.node")
.data(someData);
var nodeE = node.enter()
.append("g")
.attr("class", "node")
.attr("transform", d => "translate(" + d.x + "," + d.y + ")");
nodeE.append("text")
.text(d => d.text)
.style("opacity", 0)
.transition()
.style("opacity", 1);
node = nodeE.merge(node);
node.exit()
.style("opacity", "0");
node.transition().duration(500)
.attr("transform", d => "translate(" + d.x + "," + d.y + ")");
node.filter(function(d) {
return d.muteText
})
.select("text")
.transition()
.style("opacity", 0);
}
</script>
</body>
</html>
Because there's very little NVD3 documentation, I'm unsure about how to modify the code to create pulsing bubbles in a scatter chart. I'd like to be able to be able to iterate through an array of data for each bubble, and animate the size of the bubble based on that data, in a continuous loop. At the moment my code is pasting all the data points on top of each other as soon as the chart is called.
I've found various examples of doing something similar using D3, but nothing exactly the same. And I'm still not sure how I would apply this to NVD3 as it doesn't seem to use the same enter/exit way of doing things as D3. I've provided an example of my current code and data below. I've also created a JSFiddle so you can see it (not) working: http://jsfiddle.net/9oyaypz0/. Any help would be very greatly appreciated.
function makeScatter(data, div, showLabels,title) {
nv.addGraph(function() {
var width = 450,
height = 275;
var chart = nv.models.scatterChart()
.showDistX(true)
.showDistY(true)
.transitionDuration(350)
.sizeRange([0, 10000])
.margin({bottom:75})
.color(d3.scale.category10().range());
chart.tooltipContent(function(key, x, y, obj) {
return '<h4>' + key + '</h4><h5 style="clear:both;line-height:0.1rem;text-align:center;">' + obj.point.size + '</h5>';
});
chart.xAxis.tickFormat(function (d) { return ''; });
chart.yAxis.tickFormat(function (d) { return ''; });
chart.forceX([0]);
chart.forceX([5]);
chart.forceY([0]);
chart.forceY([6]);
chart.scatter.onlyCircles(false);
if(title) {
d3.select('title')
.append("text")
.attr("x", (width / 2))
.attr("y", 100)
.style("font-size", "16px")
.attr("text-anchor", "middle")
.text(title);
$(div).append("<h2>"+title+"</h2>");
}
d3.select(div)
.datum(data)
.transition().duration(1200)
.attr('width', width)
.attr('height', height)
.call(chart);
nv.utils.windowResize(chart.update);
return chart;
});
};
And the data is like so:
var data = [{ key: "Test Bubble 1",
values: [{ series: 0,
shape: "circle",
size: 1592,
x: 5,
y: 4 },
{ series: 1,
shape: "circle",
size: 1560,
x: 5,
y: 4 },
{ series: 2,
shape: "circle",
size: 1512,
x: 5,
y: 4 },
{ series: 3,
shape: "circle",
size: 1487,
x: 5,
y: 4 }]
},
{ key: "Test Bubble 2",
values: [{ series: 0,
shape: "circle",
size: 592,
x: 5,
y: 4 },
{ series: 1,
shape: "circle",
size: 560,
x: 5,
y: 4 },
{ series: 2,
shape: "circle",
size: 512,
x: 5,
y: 4 },
{ series: 3,
shape: "circle",
size: 487,
x: 5,
y: 4 }]
}]
I've seen this answer and this too, but they don't work.
My code is on Fiddle.
Two questions:
1. On clicking a node and pressing the delete button on the keyboard the node and corresponding links get deleted, but why am I not able to drag the remaining nodes afterward?
2. I tried attaching an image (using the path in the nodes array), but the image doesn't appear. The circles just disappear and no image appears (the path to the image is correct. In the same program I tried displaying the image at a corner of the screen and it worked).
The code:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.background { /*stroke: gray; stroke-width: 1px; fill: rgb(252,231,216);*/ cursor: move; }
.link { stroke: #000; stroke-width: 1.5px; }
.node { fill: #ccc; /*stroke: #000;*/ stroke-width: 1.5px; cursor: pointer;}
.node.fixed { fill: #f00; cursor: pointer;}
text { font: 10px sans-serif; pointer-events: none; text-shadow: 0 1px 0 #fff, 1px 0 0 #fff, 0 -1px 0 #fff, -1px 0 0 #fff; }
</style>
<body>
<script src="d3/d3.v3.js"></script>
<div id="topologyArea"></div>
<script>
var nodes = [//it's not necessary to give x and y values to nodes. One gets created for every empty object you insert here like this {}
{id: 1, x: 470, y: 410, icon: "images/abc.jpg"},
{id: 2, x: 493, y: 364, icon: "images/abc.jpg"},
{id: 3, x: 442, y: 365, icon: "images/abc.jpg"},
{id: 4, x: 467, y: 314, icon: "images/abc.jpg"},
{id: 5, x: 477, y: 248, icon: "images/fsd.jpg"},
{id: 6, x: 425, y: 207, icon: "images/sdfs.jpg"},
{id: 7, x: 402, y: 155, icon: "images/dfs.jpg"},
{id: 8, x: 369, y: 196, icon: "images/abc.jpg"},
{id: 9, x: 350, y: 148, icon: "images/abc.jpg"},
{id: 10, x: 539, y: 222, icon: "images/abc.jpg"},
{id: 11, x: 594, y: 235, icon: "images/abc.jpg"},
{id: 12, x: 582, y: 185, icon: "images/abc.jpg"},
{id: 13, x: 633, y: 200, icon: "images/abc.jpg"}
];
var links = [
{id: 1, source: 0, target: 1},
{id: 2, source: 1, target: 2},
{id: 3, source: 0, target: 2},
{id: 4, source: 1, target: 3},
{id: 5, source: 3, target: 2},
{id: 6, source: 3, target: 4},
{id: 7, source: 4, target: 5},
{id: 8, source: 5, target: 6},
{id: 9, source: 5, target: 7},
{id: 10, source: 6, target: 7},
{id: 11, source: 6, target: 8},
{id: 12, source: 7, target: 8},
{id: 13, source: 9, target: 4},
{id: 14, source: 9, target: 11},
{id: 15, source: 9, target: 10},
{id: 16, source: 10, target: 11},
{id: 17, source: 11, target: 12},
{id: 18, source: 12, target: 10}
];
var margin = {top: -5, right: -5, bottom: -5, left: -5}, width = 960 - margin.left - margin.right, height = 500 - margin.top - margin.bottom;
var iconOffset = -10, iconSize = 20;
var mousedown_node = null, mouseup_node = null, mousedown_link = null;
var nodeDeletionActivated = false;
var zoom = d3.behavior.zoom().scaleExtent([0.2, 2]).on("zoom", zoomed);
var svg = d3.select("#topologyArea").append("svg").attr("width", width + margin.left + margin.right).attr("height", height + margin.top + margin.bottom).attr('class', 'background').attr("transform", "translate(" + margin.left + "," + margin.right + ")");
var rect = svg.append("rect").attr("fill","transparent").attr("width", width + margin.left + margin.right).attr("height", height + margin.top + margin.bottom)
.on("mousedown", mousedownOnBackground);
rect.call(zoom);
var elementHolderLayer = svg.append("g");;
var linkLayer, nodeLayer;
d3.select(window).on("keydown", keydown);// add keyboard callback
redraw(elementHolderLayer);
function redraw(theLayer)//after updating the nodes and links arrays, use this function to re-draw the force graph
{
var force = d3.layout.force().size([width, height]).charge(-400).linkDistance(40).on("tick", tick);
var dragElement = force.drag().on("dragstart", dragstarted);
linkLayer = null; nodeLayer = null;
linkLayer = theLayer.selectAll(".link");
nodeLayer = theLayer.selectAll(".node");
linkLayer = linkLayer.data(links, function(d) {return d.id; }).exit().remove();
linkLayer = theLayer.selectAll(".link").data(links, function(d) {return d.id; }).enter().append("line").attr("class", "link");
nodeLayer = nodeLayer.data(nodes, function(d) {return d.id; }).exit().remove();
nodeLayer = theLayer.selectAll(".node").data(nodes, function(d) {return d.id; }).enter().append("circle").attr("class", "node").attr("r", 12)
.on("dblclick", dblclick).style("fill", function(d,i) { return d3.rgb(i*15, i*15, i*15); })
.on("mouseup", function(d,i) { mouseup(d,i);})
.on("mousemove", function(d,i) {mousemove(d,i);})
.on("mousedown", function(d,i) {mousedown(d,i);})
.call(dragElement)
//.classed("dragging", true)
.classed("fixed", function(d) {d.fixed = true;});
force.nodes(nodes).links(links).start();
}//redraw
function tick()
{
linkLayer.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; });
nodeLayer.attr("cx", function(d) {return d.x; }).attr("cy", function(d) { return d.y; });
}
function dblclick(d) { d3.select(this).classed("fixed", d.fixed = false); }
function dragstarted(d)
{
console.log("dragstarted for "+this);
//d3.event.sourceEvent.stopPropagation();
//d3.select(this).classed("dragging", true);
//d3.select(this).classed("fixed", d.fixed = true);
}
function zoomed() { elementHolderLayer.attr("transform", "translate("+d3.event.translate+")scale(" + d3.event.scale + ")"); }
function spliceLinksForNode(node) //remove the links attached to a node that got deleted
{
toSplice = links.filter(function(l) { return (l.source === node) || (l.target === node); });
toSplice.map(function(l) {links.splice(links.indexOf(l), 1); });
}
function keydown()
{
//if (!selected_node && !selected_link) return;
switch (d3.event.keyCode)
{
case 8:
{// backspace
}
case 46:
{ // delete
if (mousedown_node)
{
selected_node = mousedown_node;
if (selected_node)
{
nodes.splice(nodes.indexOf(selected_node), 1);
spliceLinksForNode(selected_node);
}
else if (selected_link) { links.splice(links.indexOf(selected_link), 1); }
selected_link = null;
selected_node = null;
redraw(elementHolderLayer);
}
break;
}
}
}//keydown
function mousedown(d,i) { mousedown_node = d; console.log("mousedown"); }
function mousedownOnBackground() {resetMouseVars();}
function mousemove(d, i) {console.log("mousemove");}
function mouseup(d, i) {console.log("mouseup");}
function resetMouseVars()
{
mousedown_node = null;
mouseup_node = null;
mousedown_link = null;
}
</script>
There is one problem in the redraw function in your code.
linkLayer = linkLayer.data(links, function(d) {return d.id; })
.exit()
.remove();
Above line has no use in your code since you are assigning the same variable with links having the old data again.
linkLayer = theLayer.selectAll(".link").data(links, function(d) { return d.id; })
.enter()
.append("line")
.attr("class", "link");
Same happens for nodes. Change your code as shown below.
//Creating links
linkLayer = theLayer.selectAll(".link").data(links, function(d) {
return d.id;
});
linkLayer.enter().append("line").attr("class", "link");
linkLayer.exit().remove();
//Creating Nodes with image icons
var gNodes = nodeLayer.enter().append("g")
.attr("class", "node")
.on("dblclick", dblclick).style("fill", function(d, i) {
return d3.rgb(i * 15, i * 15, i * 15);
})
.on("mouseup", mouseup)
.on("mousemove", mousemove)
.on("mousedown", mousedown)
.call(dragElement)
.classed("fixed", function(d) {
d.fixed = true;
});
gNodes.append("circle")
.attr("r", 12);
gNodes.append("svg:image")
.attr("class", "circle")
.attr("xlink:href",function(d){ return d.icon })
.attr("x", "-8px")
.attr("y", "-8px")
.attr("width", "16px")
.attr("height", "16px");
nodeLayer.exit().remove();
For updating position of circles and images easily, I have grouped them using g elements. So you will need to update the position of g element using transform attribute instead of updating cx and cy attributes of circle. Now, tick function will look like this. Updated fiddle
function tick() {
linkLayer.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; });
nodeLayer.attr("transform", function(d) {return "translate("+d.x+","+d.y+")"; });
}