Reputation: 685
I've seen this problem several times across multiple SO posts, while it feels wrong to post something resembling a duplicate, this error seems to present itself for lots of different reasons and as such every question is slightly different from the last. So here we go with my problem:
Here is my code;
window.onload = function () {
var width = 750,
height = 750,
counter = 0;
var force = d3.layout.force()
.size([width, height]);
d3.csv("Data/BubbleData.csv", function (error, links) {
var X = d3.scale.linear()
.domain([0, width])
.range([0, width]);
var Y = d3.scale.linear()
.domain([0, height])
.range([height, 0]);
var zoom = d3.behavior.zoom()
.x(X)
.y(Y)
.scaleExtent([1, 10])
.on("zoom", zoomed);
//handle zooming
function zoomed() {
circles.attr("transform", transform);
}
function transform(d) {
return "translate(" + X(d[0]) + "," + Y(d[1]) + ")";
};
var svg = d3.select("#GraphDiv")
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.call(d3.behavior.zoom().x(X).y(Y).scaleExtent([1, 10]).on("zoom", zoom))
.on("dblclick.zoom", null);
var rect = svg.append("rect")
.attr("width", width)
.attr("height", height)
.style("fill", "none")
.style("pointer-events", "all");
var color = d3.scale.category10();
var edges = [],
nodesByNames = {};
//create the nodes
links.forEach(function (link) {
link.App_No = nodeByName(link.App_No);
link.Server_No = nodeByName(link.Server_No);
edges.push({ source: link.App_No, target: link.Server_No })
});
var nodes = d3.values(nodesByNames);
//create container for all to go in
var container = svg.append("g")
.attr("class","container");
//create the links
var link = container.append("g")
.attr("class","LineGroup")
.selectAll(".link")
.data(edges)
.enter().append("line")
.attr("class", "link");
//define mouse behaviour for nodes
var drag = force.drag()
.on("dragstart", function (d) { d3.select(this).select("circle").classed("fixed", d.fixed = true); });
//create the nodes circles
var node = container.append("g")
.attr("class","circleGroup")
.selectAll(".node")
.data(nodes)
.enter().append("g")
.attr("class", "node")
.call(drag);
var circles = node.append("circle")
.attr("r", 10)
.attr("class", "circle")
.attr("fill", function (d) { return color(d.group); })
.on("dblclick", function (d) { d3.select(this).classed("fixed", d.fixed = false); });
var labels = node.append("text")
.attr("x", 12)
.attr("y", ".35em")
.text(function (d) { return d.Name; });
force
.nodes(nodes)
.links(edges)
.linkDistance(50)
.charge(-100)
.size([width, height])
.on("tick", tick)
.start();
//draws the lines
function tick() {
link.attr("x1", function (d) { return d.source.x })
.attr("y1", function (d) { return d.source.y })
.attr("x2", function (d) { return d.target.x })
.attr("y2", function (d) { return d.target.y });
node.attr("transform", function (d) { return "translate(" + d.x + "," + d.y + ")"; });
}
function nodeByName(name) {
var groupNo;
switch (name.substring(0, 1)) {
case "A":
groupNo = 1;
break;
case "S":
groupNo = 2;
break;
default:
groupNo = 0;
}
return nodesByNames[name] || (nodesByNames[name] = { Name: name, group: groupNo });
}
});
};
And my data:
App_Name,App_No,Server_Name,Server_No
App_01,A1,Server_01,S1
App_01,A1,Server_02,S2
App_01,A1,Server_03,S3
App_01,A1,Server_04,S4
App_02,A2,Server_03,S3
App_02,A2,Server_04,S4
App_02,A2,Server_05,S5
App_03,A3,Server_05,S5
App_03,A3,Server_06,S6
App_03,A3,Server_07,S7
I am attempting to combine these two examples (here and here) in order to have a drag behaviour on the nodes while being able to semantically zoom. I've had to disable the zoom on dblclick behaviour(see code), but otherwise the click
, drag
, fix
and un-fix
behaviour all works perfectly, however I am getting a "cannot read property "on" of undefined"
error that occurs inside the d3
library itself (debugger in chrome shows line number it breaks on (line 1321)).
Is my code correct? Am I applying the zoom behaviour in the right place (on the svg var
)? and am I applying the transformation to the right group(in this case the circles group?)
EDIT: The error happens on line 1321 of the d3.js library. it says that "g" is null. could I just have a wrong d3 library for some reason?
Upvotes: 1
Views: 7279
Reputation: 10642
I used CSV to JSON converter to convert your data so I could create a fiddle. So here goes :
With some help from this answer : semantic zooming of force directed graph in d3
And this JSFiddle : http://jsfiddle.net/cSn6w/6/
I have solved the problem. Basically you want to drag the nodes and keep them in place, which you have solved right? And you want to zoom in but keep the nodes the same size ? (semantic zoom).
So when you zoom, you need to change the size of the nodes but to get the correct size you need to get the scale factor and to get the positions you need to know the translate factor. So create to variables like so :
var scaleFactor = 1;
var translation = [0,0];
These will get overwritten.
Now when zooming you want to update these like so :
var zoom = d3.behavior.zoom()
.scaleExtent([0.1,10])
//allow 10 times zoom in or out
.on("zoom", zoomed);
function zoomed() {
// console.log("zoom", d3.event.translate, d3.event.scale);
scaleFactor = d3.event.scale;
translation = d3.event.translate;
tick(); //update positions
}
The zoomed
function updates the scale and translate variables we declared earlier. And you call the tick to update the positions of the nodes and links and size of the nodes. Updated tick function :
//draws the lines
function tick() {
link.attr("x1", function(d) {
return translation[0] + scaleFactor * d.source.x;
})
.attr("y1", function(d) {
return translation[1] + scaleFactor * d.source.y;
})
.attr("x2", function(d) {
return translation[0] + scaleFactor * d.target.x;
})
.attr("y2", function(d) {
return translation[1] + scaleFactor * d.target.y;
});
node.attr("cx", function(d) {
//console.log(translation[0] + scaleFactor*d.x)
return translation[0] + scaleFactor * d.x;
})
.attr("cy", function(d) {
return translation[1] + scaleFactor * d.y;
});
node.attr("transform", function(d) {
return "translate(" + (translation[0] + scaleFactor * d.x) + "," + (translation[1] + scaleFactor * d.y) + ")";
});
}
What you have to understand here is when call this function it uses both the translate and scale variables to give the nodes the correct positions.
Now this would work okay but the drag wouldn't as you haven't implemented the scale and translate there. I have changed the drag function around :
var drag = force.drag()
.origin(function(d) {
return d;
})
.on("dragstart", dragstarted)
.on("drag", dragged)
.on("dragend", dragended);
function dragstarted(d) {
console.log('start')
d3.event.sourceEvent.stopPropagation();
d3.select(this).classed("dragging", true);
}
function dragged(d) {
console.log('dragged')
d3.select(this).attr("cx", d.x = d3.event.x + translation[0]).attr("cy", d.y = d3.event.y + translation[1]);
}
function dragended(d) {
console.log('dragended')
d.fixed = true;
d3.select(this).classed("dragging", false);
}
Notice in the dragged
function I use the scale and translate to get the correct positions. Also the line d3.event.sourceEvent.stopPropagation();
stops flickering, if you take this out you can see what I mean.
Here is the updated fiddle : https://jsfiddle.net/reko91/rw0o9vxh/2/
Does that solve your problem ?
PS
Yours wasn't working as there was no values for d[0] and d[1].
Upvotes: 2
Reputation: 5353
What about this line :
.call(
d3.behavior.zoom()
.x(X)
.y(Y)
.scaleExtent([1, 10])
.on("zoom", zoom)
)
.on("dblclick.zoom", null);
You may have wanted to place the dbliclick.zoom in the parenthesis.
Otherwise since the loading of your csv is performed by using a callback, it may be possible that your file error get swallowed.
If it's not the code i link use intermediary variables and debogguer tool to check if you have an undefined returned somewhere. Builder pattern is nice for syntax but not for debugging.
Upvotes: -1