d3 js redraw graph add existing nodes again - d3.js

Trying to add new nodes to existing graph with add function end up with all new graph. here is my code:
function Graph(elementId) {
var svg;
var simulation;
var mNodesData = [];
var mEdgesData = [];
var mNode = null;
var mLink = null;
var elementId;
var heightDelta = 100;
var width = window.innerWidth;
var height = window.innerHeight - heightDelta;
return {
init: function () {
svg = d3.select('#' + elementId)
.append("svg")
.attr("width", width)
.attr("height", height);
simulation = d3.forceSimulation()
.force(".edge", d3.forceLink())
.force("charge", d3.forceManyBody().strength(-600))
.force("center", d3.forceCenter(width / 2, height / 2));
},
clearGraph: function () {
$('#' + this.elementId).empty();
},
getNodes: function () {
return mNodesData;
},
getEdges: function () {
return mEdgesData;
},
addNodes: function (nodes) {
mNodesData = mNodesData.concat(nodes);
},
addEdges: function (edges) {
mEdgesData = mEdgesData.concat(edges);
},
draw: function () {
mLink = svg.selectAll(".edge")
.data(mEdgesData)
.enter()
.append("line")
.attr("class", "edge")
.style("stroke", "#ccc")
.style("stroke-width", function (e) {
return 1
/* e.width*/
});
mNode = svg.selectAll(".node")
.data(mNodesData)
.enter()
.append("g")
.attr("class", "node")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
mNode.on('mouseover', function (d) {
function removePopup() {
$("#nodePopup").remove();
}
function showPopup(d) {
removePopup();
if (!d['data']) {
return;
}
var data = d['data'];
var htmlStr = '';
htmlStr += '<div id="nodePopup" >';
htmlStr += ' <div><button id="nodePopupCloseButton" type="button" class="close" data-dismiss="alert"><span class="glyphicon glyphicon-remove" style="font-size: 13px;"> </span> </div>';
htmlStr += ' <div class="nodePopupName">' + data['name'] + '</div>';
if (data['desc']) {
if (data['desc'].startsWith("http")) {
htmlStr += ' <a class="nodePopupLink" href="' + data['desc'] + '" target="_blank">Go to post..</a>';
}
else {
htmlStr += ' <div class="nodePopupDesc">' + data['desc'] + '</div>';
}
}
htmlStr += ' <div class="nodePopupGroup">GROUP: ' + data['groupId'] + '</div>';
htmlStr += ' <div class="nodePopupLeader">LEADER: ' + data['leaderId'] + '</div>';
htmlStr += ' <div class="nodePopupImage"><img src="' + d['image'] + '" style="width: 130px;" /></div>';
htmlStr += '</div>';
$("body").append(htmlStr);
$("#nodePopupCloseButton").click(removePopup);
}
showPopup(d);
mNode.filter(function (d1) {
return (d !== d1 && d1.adjacents.indexOf(d.id) == -1);
}).select("image").style("opacity", 0.2);
mNode.filter(function (d1) {
return (d !== d1 && d1.adjacents.indexOf(d.id) == -1);
}).select("circle").style("stroke", "#f6f6f6");
mLink.filter(function (d1) {
return (d !== d1.source && d !== d1.target);
}).style("opacity", 0.2);
mNode.filter(function (d1) {
return (d == d1 || d1.adjacents.indexOf(d.id) !== -1);
}).select("image").style("opacity", 1);
mNode.filter(function (d1) {
return (d == d1 || d1.adjacents.indexOf(d.id) !== -1);
}).select("circle").style("stroke", "gray");
mLink.filter(function (d1) {
return (d == d1.source || d == d1.target);
}).style("opacity", 1);
})
.on('mouseout', function () {
// removePopup();
mNode.select("image").style("opacity", 1);
mNode.select("circle").style("stroke", "gray");
mLink.style("opacity", 1);
});
var nodeCircle = mNode.append("circle")
.attr("r", function (d) {
return 0.5 * Math.max(d.width, d.height)
})
.attr("stroke", "gray")
.attr("stroke-width", "2px")
.attr("fill", "white");
var nodeImage = mNode.append("image")
.attr("xlink:href", function (d) {
return d.image
})
.attr("height", function (d) {
return d.height + ""
})
.attr("width", function (d) {
return d.width + ""
})
.attr("x", function (d) {
return -0.5 * d.width
})
.attr("y", function (d) {
return -0.5 * d.height
})
.attr("clip-path", function (d) {
return "circle(" + (0.48 * Math.max(d.width, d.height)) + "px)"
});
simulation.nodes(mNodesData);
simulation.force(".edge").links(mEdgesData);
simulation.on("tick", function () {
mLink.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;
})
mNode.attr("transform", function (d) {
return "translate(" + d.x + "," + d.y + ")"
});
mNode.attr("cx", function (d) {
return d.x = Math.max(d.width, Math.min(width - d.width, d.x));
})
.attr("cy", function (d) {
return d.y = Math.max(d.height, Math.min(height - heightDelta - d.height, 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;
}
}
}
}
function getData() {
return $.ajax({
url: 'api/v1/data.json',
type: "GET",
});
}
var graph = Graph('d3Graph');
graph.init();
$.when(getData()).then(function (data) {
graph.addNodes(data.nodes);
graph.addEdges(data.edges);
graph.draw();
});
function add() {
graph.addNodes([{
"id": 4,
"image": "images/4.jpeg",
"height": 20,
"width": 20,
"adjacents": [0],
"data": {
"name": "Number4",
"groupId": "Bla4",
"desc": "Desc4",
"leaderId": "1234-1234"
}
}]);
graph.addEdges([{
"source": 4,
"target": 3,
"width": 1
}])
graph.draw();
// graph.draw();
}
if i call to add the original graph stack on the page and a new one with another edge & node is added. How do I achieve that correct? (update the old one without creating all new..

What you currently have:
mLink = svg.selectAll("link")
.data(mEdgesData)
.enter()
.append("line")
Normally selectAll("link") means that you select all html elements which are links. But you append a line and not a link. Therefore the appended element is not part of your selectAll. There is no possibility to know that you just wanna add data to something which already exists.

The solution is to add merge to both links & nodes:
mLink = svg.selectAll(".edge")
.data(mEdgesData)
.enter()
.append("line")
.attr("class", "edge")
.style("stroke", "#ccc")
.style("stroke-width", function (e) {
return 1
/* e.width*/
}).merge(mLink);
mNode = svg.selectAll(".node")
.data(mNodesData)
.enter()
.append("g")
.attr("class", "node").merge(mNode);

I had a similar issue when I tried to create a general update procedure for my graph with dynamic data. When the nodes or links where updated, the redraw function was triggered.
The idea works as follows; on redrawing the graph, i.e. when the data has changed, all old nodes and links are removed and drawn again. Using .join() the nodes remain in their position.
Here is my working example:
redraw = () => {
simulation.nodes(nodes);
simulation.force('link')
.links(links)
.initialize(nodes);
d3.selectAll('g.node').remove(); // remove all old nodes
d3.selectAll('g.node').data(nodes).join();
d3.selectAll("line").data(links).join();
draw();
simulation.alpha(0.1)
simulation.restart();
}
In my draw() function, the nodes and links are created accordingly:
draw = () => {
container.selectAll('line').data(links).join('line');
// creates nodes with rectangles that contain text
container.selectAll('g.node').raise()
.data(nodes).join("g")
.each(function (d, i) {
d3.select(this).append("rect");
var text = d3.select(this).append("text").text(d => d.name)
// calculate text width for function tick() to adapt rect x position
d.width = text.filter(t => t.index == d.index).node()
.getBBox().width;
})
.call(drag(simulation));
}
Where container is a zoomable and draggable SVG element:
container = svg.append('g');
svg.call(d3.zoom()
.on('zoom', () => this.container.attr('transform', d3.event.transform))
.scaleExtent([0.25, 2]))
For completeness, here is my tick() function:
tick = () => {
container.selectAll('rect')
.attr('x', d => d.x - d.width / 2)
.attr('y', d => d.y);
container.selectAll('text')
.attr('x', d => d.x - d.width / 2)
.attr('y', d => d.y);
container.selectAll('line')
.attr('x1', d => d.source.x)
.attr('y1', d => d.source.y)
.attr('x2', d => d.target.x)
.attr('y2', d => d.target.y);
}
If I have time, I post a JSFiddle later.

Related

How can I highlight a slice of multiple donut chart d3

I am beginner in d3 v3. I created multiple donut charts. When I move my mouse over a slice, I get the tooltip that appears. But I would also like the slice to grow a little bit.
I have already tried several codes but I can't get there. I wonder if the problem may be related to d3 tip
this is my js file :
//Source : http://bl.ocks.org/mbostock/1305337
var m = 15,
r = 80,
z = d3.scale.ordinal()
.range(["#50FFC5", "#54E868", "#54CCE8", "#6395FF"]);
var pie = d3.layout.pie()
.value(function (d) {
return +d.count;
})
.sort(function (a, b) {
return b.count - a.count;
});
var arc = d3.svg.arc()
.innerRadius(r / 2)
.outerRadius(r);
var radius = d3.scale.linear()
.range([10, r])
var tip = d3.tip()
.attr('class', 'd3-tip')
.html(function (d) {
return d.data.genre + ": " + d.data.count;
})
.direction('s');
d3.csv("../CSV/genreHameau.csv", function (error, hameau) {
if (error) throw error;
var datas = d3.nest()
.key(function (d) {
return d.origine;
})
.entries(hameau);
datas.forEach(function (d) {
totalOrigin = d3.sum(d.values, function (d) {
return +d.count;
})
d.values.forEach(function (dd) {
dd.totalOrigin = totalOrigin
})
})
datas.sort(function (a, b) {
return d3.descending(a.values[0].totalOrigin, b.values[0].totalOrigin)
})
// définir le radius / rayon des arcs (rendre fonction de 'count')
var max = d3.max(datas, function (d) {
return d.values[0].totalOrigin
})
var min = d3.min(datas, function (d) {
return d.values[0].totalOrigin
})
radius.domain([min, max])
arc
.innerRadius(function (d) {
return radius(d.data.totalOrigin) / 2
})
.outerRadius(function (d) {
return radius(d.data.totalOrigin)
})
function size(d) {
return radius(d.values[0].totalOrigin) + m
}
var div = d3.select("body").selectAll("div")
.data(datas)
.enter().append("div") // http://code.google.com/p/chromium/issues/detail?id=98951
.style("display", "inline-block")
.style("width", function (d) {
return 2 * size(d) + "px"
})
.style("height", function (d) {
return 2 * size(d) + "px"
})
.style("min-width", "80px")
.style("min-height", "80px")
div.append("span")
.attr("class", "nomhameau")
.text(function (d) {
return d.key;
})
.append("span")
.attr("class", "nombrehameau")
.text(function (d) {
return " (" + d.values[0].totalOrigin + ")"
});
var svg = div.append("svg")
//.attr("width", (r + m) * 2)
//.attr("height", (r + m) * 2)
.attr("width", function (d) {
return 2 * size(d)
})
.attr("height", function (d) {
return 2 * size(d)
})
.append("g")
.attr("transform", function (d) {
return "translate (" + size(d) + "," + size(d) + ")"
});
svg.call(tip);
var g = svg.selectAll("g")
.data(function (d) {
return pie(d.values);
})
.enter().append("g")
.on("mouseover", tip.show)
.on("mouseout", tip.hide)
g.append("path")
.attr("d", arc)
.style("fill", function (d) {
return z(d.data.genre);
})
.append("title")
});
Thank you for your help
I would do it this way:
Just like you created the arc, create a new one with a greater radius, either inner, outer or both, something like:
var arcHighlight = d3.svg.arc()
.innerRadius(r / 2)
.outerRadius(r*1.1);
Then you add mouseover and mouseout events to the slice and modify the slice doing something like:
// Add a colored arc path, with a mouseover title showing the count.
g.append("path")
.attr("d", arc)
.style("fill", function(d) { return z(d.data.carrier); })
// new code
.on('mouseover', function(d) {
d3.select(this)
.transition()
.attr('d', arcHighlight(d));
})
.on('mouseout', function(d) {
d3.select(this)
.transition()
.attr('d', arc(d));
})
.append("title")
.text(function(d) { return d.data.carrier + ": " + d.data.count; });
The transition is optional, but it looks fancier ;)

d3 Uncaught TypeError: zoomed.x is not a function

I am trying to implement tooltips and a zoom on a graph, the tooltips work fine however I am having issues with the zoom function. I get an error on the debugger that says: Uncaught TypeError: zoomed.x is not a function. This occurs at the line 'return zoomed.x(x);' I am not sure how to change the code to get it to work.
//Builder for REal time flow chart
define((function () {
var categories = [
{
Name: "MediaServiceIndexes",
Title: "Media",
Style: "background-color:#ffffff;font-color:blue;",
Text: "Here are the top media companies with whom our customers shop.",
TitleStyle: "margin-left:0px"
},
];
var url = App.SiteUrl + "/Data/RequestDataFrom";
var svg, width, numberChecker;
var patterns = d3.scale.ordinal()
.range([
"url(#blue1)", "url(#yellow1)",
"url(#blue2)", "url(#yellow2)",
"url(#blue3)", "url(#yellow3)",
"url(#blue4)", "url(#yellow4)",
"url(#blue5)", "url(#yellow5)"
]);
var grayFill = "url(#gray)";
var colors = d3.scale.category10();
var getValues = function (values) {
var result = [];
try {
values.forEach(function (item) {
var o = {
Month: moment(item.month + "01", "YYYYMMDD"),
Value: parseFloat(item.change_in_market_share, 10)
};
var vendor = item.vendor.replace(/'/g, "");
//var e = result.find(function (it) { return it.Vendor == vendor; });
var e = false;
for (var x in result) {
if (result.hasOwnProperty(x) && typeof result[x] != "function") {
if (result[x].Vendor == vendor) {
e = (result[x]);
}
}
}
if (e) {
e.Values.push(o);
} else {
result.push({
Visible: true,
Vendor: vendor,
Values: [o]
});
}
});
result.forEach(function (item, i) {
item.Index = i;
item.Values.sort(function (a, b) {
return a.Month - b.Month;
});
});
} catch (e) {
result = null;
};
return result;
};
var createChart = function (chartElement, cd) {
var chartData = cd; //chart data is being passed in using cd via createChart function
//chart gives the location of the chart,
var chart = function (el, data) {
var margin = {
top: 20,
right: 180,
bottom: 50,
left: 110
};
var elem = el;
var chartEl = chartElement;
var fillData = [];
var defs,
gs,
height,
line,
maxDays,
minDays,
minValue,
maxValue,
x,
xAxis,
y,
yAxis;
var initialiseData = function (dataValues) {
minDays = d3.min(dataValues,
function (m) {
return d3.min(m.Values,
function (d) {
return d.Month;
});
});
maxDays = d3.max(dataValues,
function (m) {
return d3.max(m.Values,
function (d) {
return d.Month;
});
});
minValue = d3.min(chartData,
function (m) {
return d3.min(m.Values,
function (d) {
return d.Value;
});
});
maxValue = d3.max(chartData,
function (m) {
return d3.max(m.Values,
function (d) {
return d.Value;
});
});
console.log('min days: ' + minDays);
console.log('max days: ' + maxDays);
console.log('min value: ' + minValue);
console.log('max value: ' + maxValue);
dataValues.forEach(function (item) {
var nu = $.extend(true, {}, item);
nu.Values.push({ Month: maxDays, Value: minValue });
nu.Values.push({ Month: minDays, Value: minValue });
fillData.push(nu);
});
};
//initialise scales
var configSize = function () {
if (isNaN(numberChecker) === true) {
numberChecker = $(chartEl).width();
width = numberChecker;
}
width = 750;
height = 500 - margin.top - margin.bottom;
x = d3.scale.linear()
.range([0, width])
.domain([minDays, maxDays]);
y = d3.scale.linear()
.range([height, 0])
.domain([minValue, maxValue]);
$('.adjustmentZoom .tick text').attr('y', '25');
//initialise axis
xAxis = d3.svg.axis()
.scale(x)
.tickFormat(function (d, i) {
if (Math.floor(d) !== d) {
} else {
return moment(d).format("MMM YY");
}
})
.orient('bottom');
yAxis = d3.svg.axis()
.scale(y)
.tickFormat(function (d) {
return d3.round(d, 3) + "%"
})
.orient('left');
$('#clipper rect').attr('width', width);
$('.zoom-panel').attr('width', width);
};
var svgTransform = function(d) {
return "translate(" + x(d.Month) + "," + y(d.Value) + ")";
};
var zoomed = function () {
svg.select(".x.axis").call(xAxis);
svg.select(".y.axis").call(yAxis);
svg.selectAll(".datapoint").attr("transform", svgTransform);
};
//the path generator for the line chart
var initialise = function () {
line = d3.svg.line()
.interpolate(
'cardinal')
.x(function (d) {
return x(d.Month);
})
.y(function (d) {
return y(d.Value);
});
var zoomBeh = d3.behavior.zoom()
.x(x)
.y(y)
.scaleExtent([
1,
500
])
.on('zoom', zoomed);
//elem is window
svg = d3.select(elem).append('div').attr('id', 'scatter').append('svg')
.attr('width', '100%')
.attr('height', '100%')
.attr('viewBox', '0 0 1000 550')
.attr('preserveAspectRatio', 'xMinYMin meet')
.append('g')
.attr("class", "line-container")
.attr('transform',
function () {
if (window.innerWidth > 650) {
var marginWidth = margin.left;
}
else if (window.innerWidth <= 650 && window.innerWidth > 549) {
var marginWidth = margin.left + 30;
}
else if (window.innerWidth <= 549) {
var marginWidth = margin.left + 50;
}
return 'translate(' + marginWidth + ',' + margin.top + ')'
}).call(zoomBeh);
svg.append('rect')
.attr('class', 'zoom-panel')
.attr('width', width)
.attr('height', height)
.call(zoomBeh);
svg.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0,' + height + ')')
.call(xAxis)
.selectAll('text');
svg.append('text')
.attr('x', 400)
.attr('y', (height + 70))
.style('text-anchor', 'middle')
.attr('class', 'xTitle')
.style('font-weight', '500')
.text('Date by month');
svg.append('g').attr('class', 'y axis').attr('transform', 'translate(0,0)').style('text-anchor', 'end').call(yAxis);
svg.append('text')
.attr('transform', 'rotate(-90)')
.attr('y', -100)
.attr('x', -200)
.attr('dy', '1em')
.attr('class', 'yTitle')
.style('text-anchor', 'middle')
.style('font-weight', '500')
.text('Share prices in percentage');
//zoom.scaleExtent([
// 1,
// moment(maxDays).diff(minDays, "months")
//]);
}
var tooltip = function () {
svg = d3.select(elem)
.append('div')
.attr('class', 'tooltip')
.style('opacity', 0);
};
//draw calls drawData which will draw the lines of the chart
var draw = function () {
var drawData = function (dat, className, baseFill, area) {
//var selection (supermarket)
var supermarket, supermarketEnter;
supermarket = svg.selectAll('.' + className)
.data(dat,
function (c) {
return c.Vendor;
});
//var new selection (supermarketEnter)
supermarketEnter = supermarket.enter()
.append('g')
.attr('class', className)
.attr('data-vendor', function(d) { return d.Vendor; })
.attr('width', width)
.attr('height', height);
supermarketEnter.append('path');
//update new selection
supermarketEnter.attr('clip-path', 'url(#clipper)').attr('class', 'line').attr('id', function (d) { return d.Vendor; });
supermarketEnter
.selectAll("circle")
.data(function (d) {
return d.Values;
})
.enter()
.append('circle')
.attr('class', 'datapoint')
.attr('r', 4)
.style('fill',
function (d, i, j) {
return dat[j].Visible ? baseFill(j) : grayFill;
})
.attr('transform',
function (d) {
return 'translate(' + x(d.Month) + ',' + y(d.Value) + ')';
})
.on('mouseover',
function (d, i, j) {
d3.select('.tooltip').style('opacity', '1');
d3.select('.tooltip')
.html(dat[j].Vendor +
'<br/> (' +
moment(d.Month).format("MMM YYYY") +
', ' +
d.Value.toPrecision(2) +
'% )')
.style('left',
function () {
if (window.innerWidth >= 1200) {
var newWidth = d3.event.pageX -
($(chartEl).width() / 2) +
'px'
} else if (window.innerWidth < 1200) {
var newWidth = d3.event.pageX - ($(chartEl).width() / 10) + 'px'
}
return newWidth;
})
.style('top', (d3.event.pageY) - 300 + 'px');
})
.on("mouseout",
function (d) {
d3.select('.tooltip')
.style('transition', '500')
.style('opacity', 0)
.style('color', d3.select(this).style('fill'));
});
supermarket.select('path')
.transition()
.duration(500)
.attr('d',
function (d) {
return line(d.Values);
});
var path = supermarket.select('path')
.style('stroke', function (d, i) {
return (d.Visible ? baseFill(d.Index) : grayFill);
});
if (area) {
path.style('fill', function (d, i) { return (d.Visible ? baseFill(d.Index) : grayFill); });
}
supermarket.exit().remove();
supermarket.order();
}
svg.selectAll('.x.axis').call(xAxis);
svg.selectAll('.y.axis').call(yAxis);
drawData(chartData, 'supermarket', patterns, false);
return zoomed.x(x);
};
var render = function () {
configSize();
draw();
};
//insertion of drawLegend into chart function so that this will draw as well as the lines of the chart.
var drawLegend = function (dat, className) {
//DATA JOIN
//Join new data with old elements, if any.
var supermarket, supermarketEnter;
supermarket = svg.selectAll('legend_' + className)
.data(dat, function (c) { return c.Vendor; });
//UPDATE
//Update old elements as needed.
supermarket.attr('class', 'update');
//ENTER + UPDATE
//After merging the entered elements with the update selection, apply operations to both.
supermarketEnter = supermarket.enter().append('g')
.attr('class', 'legend_' + className)
.attr('data-vendor', function (d) { return d.Vendor; });
supermarketEnter
.append('text')
.attr('class', 'supermarket-name')
.attr('data-vendor', function (d) { return d.Vendor; });//
supermarket.select('text.supermarket-name')
.attr('x', width + 25)
.attr('y', function (d, i) { return (i * 1.25) * 20; })
.attr('dy', '.35em')
.text(function (d) {
return d.Vendor;
})
.on('click', function (d, i, j) {
dat[i].Visible = !dat[i].Visible;
var newOpacity = dat[i].Visible ? 1 : 0;
d3.select("[data-vendor='" + dat[i].Vendor + "']").style('opacity', newOpacity);
});
supermarketEnter.append('rect')
.attr('class', 'supermarket-dot');
supermarket.select('rect.supermarket-dot')
.attr('x', width + 10).attr('y', function (d, i) {
return ((i * 1.25) * 20) - 5;
})
.attr('width', 12)
.attr('height', 12)
.style('fill', function (d, i) {
return patterns(d.Index);
});
//EXIT
//Remove old elements as needed.
supermarket.exit().remove();
};
//insertion of drawLegend into chart function originally placed after configSize(); draw(); };
var toggleLines = function () {
var dat = $(this.closest("g")).data();
var obj = chartData.find(function (item) { return item.Vendor === dat.vendor; });
if (obj.Visible) {
obj.Visible = false;
} else {
obj.Visible = true;
}
chartData.sort(function (a, b) {
if (a.Visible === b.Visible) return 0;
if (a.Visible && !b.Visible) return 1;
return -1;
});
draw();
};
initialiseData(data);
configSize();
initialise();
draw();
drawLegend(chartData, 'supermarket');
tooltip();
$('.adjustmentZoom .tick text').attr('y', '25');
return {
Draw: render
};
};
var c = chart(chartElement, chartData);
//draw draws the lines of the chart. chart function draws the space arou
$(window).on("resize", function () {
c.Draw();
$('.adjustmentZoom .tick text').attr('y', '25');
});
}
var initialiseElement = function () {
categories.forEach(function (item) {
$(".slidesIntroduction").append('<div class="">\
<div class="insight-chart chart tradingData ' + item.Name + '" style="' + item.Style + '">\
<h3 class="chartTitle" ><br />\All ' + item.Title + '</h3>\
<p style="' + item.TitleStyle + '">\
<br />\
' + item.Text + '\
</p>\
</div>\
<//div>');
});
};
var renderInsightData = function () {
initialiseElement();
categories.forEach(function (item) {
if (item.DataValues) {
createChart("." + item.Name, item.DataValues);
}
});
};
var startWork = function () {
var catCount = 0;
var startRequest = function () {
App.Modules.ServerComms.PollForData({
Url: url,
Data: { src: categories[catCount].Name },
Success: processResponse,
});
};
var processResponse = function (response) {
categories[catCount].DataValues = getValues(response);
catCount++;
if (catCount < categories.length) {
startRequest();
} else {
renderInsightData();
$(".spinner").removeClass("spinner");
}
};
startRequest();
};
return {
Name: "Introduction",
Init: function () { },
Start: startWork
}
})());
//Builder for REal time flow chart
define((function () {
var categories = [
{
Name: "MediaServiceIndexes",
Title: "Media",
Style: "background-color:#ffffff;font-color:blue;",
Text: "Here are the top media companies with whom our customers shop.",
TitleStyle: "margin-left:0px"
},
];
var url = App.SiteUrl + "/Data/RequestDataFrom";
var svg, width, numberChecker;
var patterns = d3.scale.ordinal()
.range([
"url(#blue1)", "url(#yellow1)",
"url(#blue2)", "url(#yellow2)",
"url(#blue3)", "url(#yellow3)",
"url(#blue4)", "url(#yellow4)",
"url(#blue5)", "url(#yellow5)"
]);
var grayFill = "url(#gray)";
var colors = d3.scale.category10();
var getValues = function (values) {
var result = [];
try {
values.forEach(function (item) {
var o = {
Month: moment(item.month + "01", "YYYYMMDD"),
Value: parseFloat(item.change_in_market_share, 10)
};
var vendor = item.vendor.replace(/'/g, "");
//var e = result.find(function (it) { return it.Vendor == vendor; });
var e = false;
for (var x in result) {
if (result.hasOwnProperty(x) && typeof result[x] != "function") {
if (result[x].Vendor == vendor) {
e = (result[x]);
}
}
}
if (e) {
e.Values.push(o);
} else {
result.push({
Visible: true,
Vendor: vendor,
Values: [o]
});
}
});
result.forEach(function (item, i) {
item.Index = i;
item.Values.sort(function (a, b) {
return a.Month - b.Month;
});
});
} catch (e) {
result = null;
};
return result;
};
var createChart = function (chartElement, cd) {
var chartData = cd; //chart data is being passed in using cd via createChart function
//chart gives the location of the chart,
var chart = function (el, data) {
var margin = {
top: 20,
right: 180,
bottom: 50,
left: 110
};
var elem = el;
var chartEl = chartElement;
var fillData = [];
var defs,
gs,
height,
line,
maxDays,
minDays,
minValue,
maxValue,
x,
xAxis,
zoomBeh,
y,
yAxis;
var initialiseData = function (dataValues) {
minDays = d3.min(dataValues,
function (m) {
return d3.min(m.Values,
function (d) {
return d.Month;
});
});
maxDays = d3.max(dataValues,
function (m) {
return d3.max(m.Values,
function (d) {
return d.Month;
});
});
minValue = d3.min(chartData,
function (m) {
return d3.min(m.Values,
function (d) {
return d.Value;
});
});
maxValue = d3.max(chartData,
function (m) {
return d3.max(m.Values,
function (d) {
return d.Value;
});
});
console.log('min days: ' + minDays);
console.log('max days: ' + maxDays);
console.log('min value: ' + minValue);
console.log('max value: ' + maxValue);
dataValues.forEach(function (item) {
var nu = $.extend(true, {}, item);
nu.Values.push({ Month: maxDays, Value: minValue });
nu.Values.push({ Month: minDays, Value: minValue });
fillData.push(nu);
});
};
//initialise scales
var configSize = function () {
if (isNaN(numberChecker) === true) {
numberChecker = $(chartEl).width();
width = numberChecker;
}
width = 750;
height = 500 - margin.top - margin.bottom;
x = d3.scale.linear()
.range([0, width])
.domain([minDays, maxDays]);
y = d3.scale.linear()
.range([height, 0])
.domain([minValue, maxValue]);
$('.adjustmentZoom .tick text').attr('y', '25');
//initialise axis
xAxis = d3.svg.axis()
.scale(x)
.tickFormat(function (d, i) {
if (Math.floor(d) !== d) {
} else {
return moment(d).format("MMM YY");
}
})
.orient('bottom');
yAxis = d3.svg.axis()
.scale(y)
.tickFormat(function (d) {
return d3.round(d, 3) + "%"
})
.orient('left');
$('#clipper rect').attr('width', width);
$('.zoom-panel').attr('width', width);
};
//the path generator for the line chart
var initialise = function () {
line = d3.svg.line()
.interpolate(
'cardinal')
.x(function (d) {
return x(d.Month);
})
.y(function (d) {
return y(d.Value);
});
zoomBeh = d3.behavior.zoom()
.x(x)
.y(y)
.scaleExtent([
1,
500
])
.on('zoom', zoomed);
//elem is window
svg = d3.select(elem).append('div').attr('id', 'scatter').append('svg')
.attr('width', '100%')
.attr('height', '100%')
.attr('viewBox', '0 0 1000 550')
.attr('preserveAspectRatio', 'xMinYMin meet')
.append('g')
.attr("class", "line-container")
.attr('transform',
function () {
if (window.innerWidth > 650) {
var marginWidth = margin.left;
}
else if (window.innerWidth <= 650 && window.innerWidth > 549) {
var marginWidth = margin.left + 30;
}
else if (window.innerWidth <= 549) {
var marginWidth = margin.left + 50;
}
return 'translate(' + marginWidth + ',' + margin.top + ')'
}).call(zoomBeh).on("dblclick.zoom", null);
svg.append('rect')
.attr('class', 'zoom-panel')
.attr('width', width)
.attr('height', height)
.call(zoomBeh);
svg.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0,' + height + ')')
.call(xAxis)
.selectAll('text');
svg.append('text')
.attr('x', 400)
.attr('y', (height + 70))
.style('text-anchor', 'middle')
.attr('class', 'xTitle')
.style('font-weight', '500')
.text('Date by month');
svg.append('g').attr('class', 'y axis').attr('transform', 'translate(0,0)').style('text-anchor', 'end').call(yAxis);
svg.append('text')
.attr('transform', 'rotate(-90)')
.attr('y', -100)
.attr('x', -200)
.attr('dy', '1em')
.attr('class', 'yTitle')
.style('text-anchor', 'middle')
.style('font-weight', '500')
.text('Share prices in percentage');
//zoom.scaleExtent([
// 1,
// moment(maxDays).diff(minDays, "months")
//]);
}
//draw calls drawData which will draw the lines of the chart
var draw = function () {
var drawData = function (dat, className, baseFill, area) {
//var selection (supermarket)
var supermarket, supermarketEnter;
supermarket = svg.selectAll('.' + className)
.data(dat,
function (c) {
return c.Vendor;
});
//var new selection (supermarketEnter)
supermarketEnter = supermarket.enter()
.append('g')
.attr('class', className)
.attr('data-vendor', function(d) { return d.Vendor; })
.attr('width', width)
.attr('height', height);
supermarketEnter.append('path');
//update new selection
supermarketEnter.attr('clip-path', 'url(#clipper)').attr('class', 'line').attr('id', function (d) { return d.Vendor; });
supermarket.select('path')
.transition()
.attr('class', 'line-data')
.duration(500)
.attr('d',
function (d) {
return line(d.Values);
});
var path = supermarket.select('path')
.style('stroke', function (d, i) {
return (d.Visible ? baseFill(d.Index) : grayFill);
});
if (area) {
path.style('fill', function (d, i) { return (d.Visible ? baseFill(d.Index) : grayFill); });
}
supermarketEnter
.selectAll("circle")
.data(function (d) {
return d.Values;
})
.enter()
.append('circle')
.attr('class', 'datapoint')
.attr('r', 4)
.style('fill',
function (d, i, j) {
return dat[j].Visible ? baseFill(j) : grayFill;
})
.attr('transform',
function (d) {
return 'translate(' + x(d.Month) + ',' + y(d.Value) + ')';
})
.on('mouseover',
function (d, i, j) {
d3.select('.tooltip').style('opacity', '1');
d3.select('.tooltip')
.html(dat[j].Vendor +
'<br/> (' +
moment(d.Month).format("MMM YYYY") +
', ' +
d.Value.toPrecision(2) +
'% )')
.style('left',
function () {
if (window.innerWidth >= 1200) {
var newWidth = d3.event.pageX -
($(chartEl).width() / 2) +
'px'
} else if (window.innerWidth < 1200) {
var newWidth = d3.event.pageX - ($(chartEl).width() / 10) + 'px'
}
return newWidth;
})
.style('top', (d3.event.pageY) - 300 + 'px');
})
.on("mouseout",
function (d) {
d3.select('.tooltip')
.style('transition', '500')
.style('opacity', 0)
.style('color', d3.select(this).style('fill'));
});
supermarket.exit().remove();
supermarket.order();
}
svg.selectAll('.x.axis').call(xAxis);
svg.selectAll('.y.axis').call(yAxis);
drawData(chartData, 'supermarket', patterns, false);
};
var render = function () {
configSize();
draw();
};
//insertion of drawLegend into chart function so that this will draw as well as the lines of the chart.
var drawLegend = function (dat, className) {
//DATA JOIN
//Join new data with old elements, if any.
var supermarket, supermarketEnter;
supermarket = svg.selectAll('legend_' + className)
.data(dat, function (c) { return c.Vendor; });
//UPDATE
//Update old elements as needed.
supermarket.attr('class', 'update');
//ENTER + UPDATE
//After merging the entered elements with the update selection, apply operations to both.
supermarketEnter = supermarket.enter().append('g')
.attr('class', 'legend_' + className)
.attr('data-vendor', function (d) { return d.Vendor; });
supermarketEnter
.append('text')
.attr('class', 'supermarket-name')
.attr('data-vendor', function (d) { return d.Vendor + "-legend"; });//
supermarket.select('text.supermarket-name')
.attr('x', width + 25)
.attr('y', function (d, i) { return (i * 1.25) * 20; })
.attr('dy', '.35em')
.text(function (d) {
return d.Vendor;
})
.on('click', function (d, i, j) {
dat[i].Visible = !dat[i].Visible;
var newOpacity = dat[i].Visible ? 1 : 0;
d3.select("[data-vendor='" + dat[i].Vendor + "']").style('opacity', newOpacity);
});
supermarketEnter.append('rect')
.attr('class', 'supermarket-dot');
supermarket.select('rect.supermarket-dot')
.attr('x', width + 10).attr('y', function (d, i) {
return ((i * 1.25) * 20) - 5;
})
.attr('width', 12)
.attr('height', 12)
.style('fill', function (d, i) {
return patterns(d.Index);
});
//EXIT
//Remove old elements as needed.
supermarket.exit().remove();
d3.select(elem).append('div').attr('class', 'tooltip').style('opacity', 0);
return createChart().chart().initialise().zoomBeh.x(x);
};
var svgTransform = function (d) {
return "translate(" + x(d.Month) + "," + y(d.Value) + ")";
};
var lineTransform = function(d) {
return line(d.Values);
}
var zoomed = function () {
svg.select(".x.axis").call(xAxis);
svg.select(".y.axis").call(yAxis);
svg.selectAll(".datapoint").attr("transform", svgTransform);
svg.selectAll(".line-data").attr("d", lineTransform);
};
//insertion of drawLegend into chart function originally placed after configSize(); draw(); };
var toggleLines = function () {
var dat = $(this.closest("g")).data();
var obj = chartData.find(function (item) { return item.Vendor === dat.vendor; });
if (obj.Visible) {
obj.Visible = false;
} else {
obj.Visible = true;
}
chartData.sort(function (a, b) {
if (a.Visible === b.Visible) return 0;
if (a.Visible && !b.Visible) return 1;
return -1;
});
draw();
};
initialiseData(data);
configSize();
initialise();
draw();
drawLegend(chartData, 'supermarket');
tooltip();
$('.adjustmentZoom .tick text').attr('y', '25');
return {
Draw: render
};
};
var c = chart(chartElement, chartData);
//draw draws the lines of the chart. chart function draws the space arou
$(window).on("resize", function () {
c.Draw();
$('.adjustmentZoom .tick text').attr('y', '25');
});
}
var initialiseElement = function () {
categories.forEach(function (item) {
$(".slidesIntroduction").append('<div class="">\
<div class="insight-chart chart tradingData ' + item.Name + '" style="' + item.Style + '">\
<h3 class="chartTitle" ><br />\All ' + item.Title + '</h3>\
<p style="' + item.TitleStyle + '">\
<br />\
' + item.Text + '\
</p>\
</div>\
<//div>');
});
};
var renderInsightData = function () {
initialiseElement();
categories.forEach(function (item) {
if (item.DataValues) {
createChart("." + item.Name, item.DataValues);
}
});
};
var startWork = function () {
var catCount = 0;
var startRequest = function () {
App.Modules.ServerComms.PollForData({
Url: url,
Data: { src: categories[catCount].Name },
Success: processResponse,
});
};
var processResponse = function (response) {
categories[catCount].DataValues = getValues(response);
catCount++;
if (catCount < categories.length) {
startRequest();
} else {
renderInsightData();
$(".spinner").removeClass("spinner");
}
};
startRequest();
};
return {
Name: "Introduction",
Init: function () { },
Start: startWork
}
})());

D3 Zoomable Circle Packing throws NaN erros

I've constructed a D3 visualization that was working on my local machine. However, now i've exported to my server, the code breaks and throws several errors:
Error: invalid value for <circle> attribute transform="translate"(NaN,NaN)"
Error: invalid value for <text> attribute transform="translate"(NaN,NaN)"
Error: invalid value for <circle> attribute r="NaN"
I've had these errors before with similar code and was able to solve them. However, i cannot grasp what is going wrong. Any suggestions? Thanx!
function drawBubbles() {
var margin = 20,
diameter = 740;
var color = d3.scale.linear()
.domain([-1, 10])
.range(["hsl(152,80%,80%)", "hsl(228,30%,40%)"])
.interpolate(d3.interpolateHcl);
var pack = d3.layout.pack()
.size([diameter - margin, diameter - margin])
.value(function (d) { return d.size; })
var svg = d3.select("form").append("svg")
.attr("width", 1280)
.attr("height", 800)
.append("g")
.attr("transform", "translate(" + diameter / 2 + "," + diameter / 2 + ")");
d3.json("../Resources/output.json", function (error, root) {
if (error) throw error;
var focus = root,
nodes = pack.nodes(root),
view;
var circle = svg.selectAll("circle")
.data(nodes)
.enter().append("circle")
.attr("class", function (d) { return d.parent ? d.children ? "node" : "node node--leaf" : "node node--root"; })
.style("fill", function (d) { return d.children ? color(d.depth) : null; })
.on("click", function (d) { if (focus !== d) zoom(d), d3.event.stopPropagation(); });
var text = svg.selectAll("text")
.data(nodes)
.enter().append("text")
.attr("class", "label")
.style("fill-opacity", function (d) { return d.parent === root ? 1 : 0; })
.style("display", function (d) { return d.parent === root ? "inline" : "none"; })
.text(function (d) { return d.name; });
var node = svg.selectAll("circle,text");
d3.select("form")
.on("click", function () { zoom(root); });
zoomTo([root.x, root.y, root.r * 2 + margin]);
function zoom(d) {
var focus0 = focus; focus = d;
var transition = d3.transition()
.duration(d3.event.altKey ? 7500 : 750)
.tween("zoom", function (d) {
var i = d3.interpolateZoom(view, [focus.x, focus.y, focus.r * 2 + margin]);
return function (t) { zoomTo(i(t),d); };
});
transition.selectAll("text")
.filter(function (d) { return d.parent === focus || this.style.display === "inline"; })
.style("fill-opacity", function (d) { return d.parent === focus ? 1 : 0; })
.each("start", function (d) { if (d.parent === focus) this.style.display = "inline"; })
.each("end", function (d) { if (d.parent !== focus) this.style.display = "none"; });
}
function zoomTo(v) {
var k = diameter / v[2]; view = v;
console.log(d.x)
console.log(d.y)
console.log(d.r)
console.log(k)
node.attr("transform", function(d) { return "translate(" + (d.x - v[0]) * k + "," + (d.y - v[1]) * k + ")"; });
circle.attr("r", function(d) { return d.r * k; });
}
});
d3.select(self.frameElement).style("height", diameter + "px"); }
I had exactly this issue - although most browsers seemed to treat NaN as zero and so not complain, but Chrome wasn't happy at all.
For me I found the packed nodes collection returned from the packer had NaNs for zero value items passed in (which my data could contain). I just looped all the nodes after the pack call and set them to zero. Something like this should do it (it should probably be a recursive call to avoid duplication, but you get the idea!)...
// Replace any zero value items with zero x/y/r values
nodes.forEach( function (currNode) {
if (!currNode.count) {
currNode.x = 0;
currNode.y = 0;
currNode.r = 0;
currNode.children.forEach( function (currChild) {
if (!currChild.count) {
currChild.x = 0;
currChild.y = 0;
currChild.r = 0;
}
});
}
});
Hope this helps you

d3 fisheye(d) returns undefined object

So I am working with the d3 fisheye plugin, and I am having some pretty basic problems.
I implemented this very basic code, pretty much a direct copy from here https://github.com/d3/d3-plugins/tree/master/fisheye
fisheye = d3.fisheye.circular()
.radius(200)
.distortion(2);
//initialize fisheye
chart.on("mousemove", function() {
fisheye.focus(d3.mouse(this));
dataPoint.each(function(d) { d.fisheye = fisheye(d); })
.attr('y', function(d){ return d.fisheye.y; })
.attr('x', function(d){ return d.fisheye.x; });
});
But d.fisheye.x and d.fisheye.y are undefined. In fact, looking at fisheye(d), it returns:
{x: undefined, y: undefined, z: 1}
On the other hand, d3.mouse(this) is properly returning an array.
Does any one have suggestion on why this might be occurring?
More code: by the way, the code is like this because it is inside a ext-js panel, so each function (drawWords is a property of this object). It is kind of complicated which is why I hesitated to post it all, and this is still not all the code, but the relevant part I think. I didn't include the initialization of any of the other global variables, or helper functions.
//imagine some sort of onload function
onLoad: function () {
this.drawWords();
this.animateVis();
}
,drawWords: function () {
toolObject = this;
var h = this.body.getHeight(),
w = this.body.getWidth();
//initialize word text
this.dataPoint = this.chart.selectAll('text')
.data(toolObject.termometerData, function (d) {return d.word;})
.enter().append('text')
.attr('class', 'points')
.attr('id', function(d) {return d.word + '-point';})
.attr('x', function() {
return toolObject.xScale(toolObject.shiftCount);
})
.attr('y', function (d) {
return toolObject.fanVertical(d, toolObject.shiftCount);
})
.attr('transform', function (d) {
var thisXPosition = toolObject.xScale(toolObject.shiftCount),
thisYPosition = toolObject.fanVertical(d, toolObject.shiftCount);
return 'translate(0, 0) rotate(-20 ' + thisXPosition + ' ' + thisYPosition + ')';
})
.attr('text-anchor', 'middle')
.attr('fill-opacity', function (d) {return toolObject.opacityScale(0);})
.text(function(d) {return d.word;});
this.applyFisheye();
}
,fisheye: d3.fisheye.circular()
.radius(200)
.distortion(2)
,applyFisheye: function () {
var toolObject = this;
//initialize fisheye
this.chart.on("mousemove", function() {
fisheye.focus(d3.mouse(this));
toolObject.dataPoint.each(function(d) { d.fisheye = toolObject.fisheye(d); })
.attr('y', function(d){ return d.fisheye.y; })
.attr('x', function(d){ return d.fisheye.x; })
.attr('transform', function(d){
return 'translate(0, 0) rotate(-20 ' + d.fisheye.x + ' '+ d.fisheye.y + ')';
});
});
}
,animateVis: function () {
var toolObject = this;
var h = this.body.getHeight(),
w = this.body.getWidth();
var tick;
if(this.animationIdArray.length < 1){
tick = setInterval(function(){
animate();
}, this.duration);
this.animationIdArray.push(tick);
}
function animate() {
if(toolObject.shiftCount < toolObject.numDataPoints){
toolObject.shiftCount++;
//animate words
toolObject.dataPoint.transition()
.duration(toolObject.duration)
.ease('linear')
.attr('x', function(d){ return toolObject.xScale(toolObject.shiftCount - 1); })
.attr('y', function(d){ return toolObject.fanVertical(d, toolObject.shiftCount - 1); })
.attr('transform', function(d){
var thisXPosition = toolObject.xScale(toolObject.shiftCount - 1),
thisYPosition = toolObject.fanVertical(d, toolObject.shiftCount - 1);
return 'translate(0, 0) rotate(-20 ' + thisXPosition + ' '+ thisYPosition + ')';
})
.attr('fill-opacity', function(d){
return toolObject.opacityScale(d.series[toolObject.shiftCount - 1].freq);
});
toolObject.applyFisheye();
}else{
clearInterval(tick);
toolObject.animationIdArray.shift();
}
}
}
fisheye assumes that the x and y coordinates of your objects are defined by keys named "x" and "y". It's probably enough (but possibly overkill, depending how often this code is called) to use
this.dataPoint
.each(function(d) {
d.x = toolObject.xScale(toolObject.shiftCount);
d.y = toolObject.fanVertical(d, toolObject.shiftCount)
.attr('x', function(d) { return d.x; })
.attr('y', function(d) { return d.y; });
when you //initialize word text

d3js: _on()_ doesn't send the current datum object to the onmouse function

I would like to highlight the edges of a graph when the connected nodes are hovered.
I took inspiration from the Bundle example:
However the on() function is not giving the d object to the onmouse function:
d3.json("graph_file.json", function(json) {
force
.nodes(json.nodes)
.links(json.links)
.start();
var link = svg.selectAll("line.link")
.data(json.links)
.enter().append("line")
.attr("class", function(d) { return "link source-" + d.source.key + " target-" + d.target.key; })
.style("stroke-width", function(d) { return Math.sqrt(d.value); });
var node = svg.selectAll("circle.node")
.data(json.nodes)
.enter().append("circle")
.attr("class", "node")
.attr("r", function(d) { return d.connec; })
.style("fill", function(d) { return color(d.group); })
.on("mouseover", mouseover)
.on("mouseout", mouseout)
.call(force.drag);
...
function mouseover(d) {
svg.selectAll("line.link.target-" + d.key)
.classed("target", true);
svg.selectAll("line.link.source-" + d.key)
.classed("source", true);
}
any help appreciated.
Ultimately I was not able to reproduce your problem! The only thing that caught me at first was when I defined my mouseover and mouseout functions after setting them in .on(...) In that case they were undefined for the call and no mouse handlers were set - so the functions were simply never called.
Anyway, you can see what I tried here. The code:
var w = 400,
h = 400;
var vis = d3.select("svg").attr("width", 800).attr("height", 800);
var nodes = [];
var links = [];
for (var i = 0; i < 30; i++) {
var node = {
label: "node " + i,
value: Math.random(),
key: i
};
nodes.push(node);
};
for (var i = 0; i < nodes.length; i++) {
for (var j = 0; j < i; j++) {
if (Math.random() > .95) links.push({
source: nodes[i],
target: nodes[j],
weight: Math.random()
});
}
};
var force = d3.layout.force().size([w, h]).nodes(nodes).links(links);
force.start();
var link = vis.selectAll("line.link").data(links).enter().append("line").style("stroke", "#CCC").attr("class", function(d) {
return "link source-" + d.source.key + " target-" + d.target.key;
});
var mouseover = function(d) {
txt.text(JSON.stringify(d));
//txt.text("line.link.target-" + d.key);
vis.selectAll("line.link.target-" + d.key).classed("target", true).style("stroke", '#F00');
vis.selectAll("line.link.source-" + d.key).classed("source", true).style("stroke", '#F00');
}
var mouseout = function(d) {
vis.selectAll("line.link.target-" + d.key).classed("target", false).style("stroke", "#CCC");
vis.selectAll("line.link.source-" + d.key).classed("source", false).style("stroke", "#CCC");
}
var node = vis.selectAll("circle.node").data(force.nodes()).enter().append("circle").attr("class", "node").attr("r", 5).style("fill", function(d) {
return d3.rgb(55 * d.value, 255 * d.value, 155 * d.value)
}).style("stroke", "#FFF").style("stroke-width", 3).on("mouseover", mouseover).on("mouseout", mouseout).call(force.drag);
var txt = vis.append('text').attr({
transform: 'translate(5,400)'
}).text("Node Info");
var updateLink = function() {
this.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;
});
}
var updateNode = function() {
this.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
}
force.on("tick", function() {
node.call(updateNode);
link.call(updateLink);
});​

Resources